Intel's Itanium put hardware under every idea of this lesson: bundles of three operations with explicit stop bits, predication of almost every instruction, speculative loads, and rotating registers that give a software-pipelined loop fresh register names on every pass. Intel announced the name on 4 October 1999; the nickname "Itanic" appeared the same day. The last Itanium shipments ended on 29 July 2021.
Same loop, three shapes: unrolling, software pipelining and betting on the hot path
After this lesson you will unroll a loop and count, stall by stall, what it gains; turn a loop into its software-pipelined form and back again; schedule the papers' SP_LOOP on the VLIW table with its four metrics; and explain what trace scheduling, superblocks and predication buy and what each of them costs.
Exercise 2 of the 22 June 2026 paper prints two loops side by side. LOOP1 is the plainest loop there is: LD, FADD, SD, ADDUI, BNE. LOOP2 is made of the same five operations with the first three in reverse order: SD, FADD, LD. On the paper's 3-issue VLIW, scheduled by the same rules, LOOP1 needs 10 cycles per iteration and LOOP2 needs 5.
Read LOOP2 as a program and it looks absurd. It stores F6 before the FADD that computes F6, and the FADD reads F2 before the LD that loads F2. Nothing was added and nothing was removed, yet the loop runs twice as fast. Where did the waiting go — and what does that store actually store?
LOOP1 and LOOP2 are two shapes of one loop. Unrolling, which you met in lesson 15, gives it a third. And when a loop has an if inside, the compiler has one more move: it bets on the side that usually wins.
Unrolling, counted cycle by cycle
A machine that issues several operations per cycle is only as fast as the independent operations it is offered, and one iteration of a short loop offers almost none: every instruction waits for the one before it. The compiler's oldest remedy is loop unrolling: copy the body u times into one straight-line block, fold the addresses of the copies into the displacements, and keep a single index update and a single branch. Lesson 15 counted what that saves in instructions — N/u iterations of u·w + o instructions, (u − 1)·ρ extra registers. Here you count what it buys in cycles, on the notes' own example (p. 285), x[i] = x[i] + s:
LOOP: LD F0, 0(R1) ; F0 ← x[i]
FADD F4, F0, F2 ; F2 = s
SD F4, 0(R1) ; x[i] ← F4
SUBI R1, R1, 8 ; i ← i − 1
BNE R1, R2, LOOP
The notes give three numbers for this loop — 10 cycles per iteration, 16 for four iterations once unrolled and renamed, efficiency 87.5 % — but not the machine they were counted on. The first is Hennessy and Patterson's classic count for this very loop (third edition), on a single-issue pipeline whose branch has one delay slot; the other two follow from the same rules:
| Producer | Consumer | Stall cycles between them |
|---|---|---|
| LD | FADD that uses the loaded value | 1 |
| FADD | SD that stores the result | 2 |
| SUBI | BNE that tests the new index | 1 |
| BNE | the next instruction | 1 delay slot, always spent: it holds an independent instruction or nothing |
| Cycle | Original | ×4, regrouped | ×4, stores moved |
|---|---|---|---|
| C1 | LD F0, 0(R1) | LD F0, 0(R1) | LD F0, 0(R1) |
| C2 | stall | LD F6, -8(R1) | LD F6, -8(R1) |
| C3 | FADD F4, F0, F2 | LD F10, -16(R1) | LD F10, -16(R1) |
| C4 | stall | LD F14, -24(R1) | LD F14, -24(R1) |
| C5 | stall | FADD F4, F0, F2 | FADD F4, F0, F2 |
| C6 | SD F4, 0(R1) | FADD F8, F6, F2 | FADD F8, F6, F2 |
| C7 | SUBI R1, R1, 8 | FADD F12, F10, F2 | FADD F12, F10, F2 |
| C8 | stall | FADD F16, F14, F2 | FADD F16, F14, F2 |
| C9 | BNE R1, R2, LOOP | SD F4, 0(R1) | SD F4, 0(R1) |
| C10 | (delay slot) | SD F8, -8(R1) | SD F8, -8(R1) |
| C11 | SD F12, -16(R1) | SUBI R1, R1, 32 | |
| C12 | SD F16, -24(R1) | SD F12, 16(R1) | |
| C13 | SUBI R1, R1, 32 | BNE R1, R2, LOOP | |
| C14 | stall | SD F16, 8(R1) (delay slot) | |
| C15 | BNE R1, R2, LOOP | ||
| C16 | (delay slot) |
Read the second column first: five instructions, three data stalls, one stall before the branch and an empty delay slot — 10 cycles, half of them wasted. Now copy the body four times in the order of the listing, with F0 and F4 reused. Every copy brings its stalls along: 6 cycles per copy, plus 4 for SUBI, stall, BNE and the slot, makes 28 cycles, 7 per element. Unrolling alone buys little.
What makes the third column possible is renaming. Once each copy has its own registers — F0/F4, F6/F8, F10/F12, F14/F16 — the compiler may regroup: four loads, four adds, four stores. Every consumer now stands four instructions after its producer, more than any latency asks, so the data stalls vanish; left are the stall before BNE and the empty delay slot: 14 instructions in 16 cycles. The fourth column fills both holes with the last two stores, whose displacements grow by 32 because SUBI has already moved R1 (the offset rule of lesson 15): four elements in 14 cycles.
- \(T_{\text{iter}}\)
- execution time per original iteration, stalls and empty slots included
- \(IC\)
- instructions issued by the body; stalls and empty slots are not instructions
- \(\#\text{cycles}\)
- length of the body's schedule, delay slot included
- \(\#\text{issues}\)
- issue width: 1 here, 3 or 4 on the VLIW of the papers (it is the code-efficiency line of the example formula sheet)
| Version | Cycles | Elements | \(T_{\text{iter}}\) (66) | Efficiency (67) | Loop control per element |
|---|---|---|---|---|---|
| original | 10 | 1 | 10 | 5/10 = 50 % | 4 |
| ×4, copies in order | 28 | 4 | 7 | 14/28 = 50 % | 1 |
| ×4, regrouped | 16 | 4 | 4 | 14/16 = 87.5 % | 1 |
| ×4, stores moved | 14 | 4 | 3.5 | 14/14 = 100 % | 0.5 |
On a 3-issue VLIW the regrouped body pays off more: the four copies are independent of one another, so loads, adds and stores of different copies share bundles — 13 cycles for four elements against 10 for one (section 4). The price is the one lesson 15 named: eight FP registers instead of two, fourteen instructions instead of five (a bigger instruction-cache footprint), and a remainder loop when the trip count is not a multiple of 4.
Unrolling buys independent operations; renaming is what lets the scheduler use them; registers and code size pay for both.
Software pipelining: one body, three elements in flight
Unrolling makes the body bigger so that the scheduler finds independent work inside it. Software pipelining finds independent work without making anything bigger: it takes it from other iterations. Picture three people washing up. In every minute the stacker puts away plate i while the dryer dries plate i + 1 and the washer starts plate i + 2: three plates in progress, one leaving every minute. In the loop, LD washes, FADD dries, SD stacks — and the minute is one pass of the loop body, however many cycles that pass takes.
Software pipelining — reorganising a loop so that each execution of its body holds stages of several consecutive iterations. In Monica Lam's words (1988), "iterations of a loop in the source program are continuously initiated at constant intervals, before the preceding iterations complete". Hennessy and Patterson call it symbolic loop unrolling.
From here on the loop walks upwards, as the papers' loops do — ADDUI R1, R1, 8 instead of SUBI — and the construction takes five steps.
Unroll symbolically. Write iterations i, i + 1 and i + 2 under each other, each one step later than the one above: LD, FADD, SD of iteration i in steps 1–3, of i + 1 in steps 2–4, of i + 2 in steps 3–5.
Look at step 3. It holds the SD of iteration i, the FADD of i + 1 and the LD of i + 2 — one instruction from each, and none waits for another, because each consumes what an earlier step produced. That column becomes the loop body.
Write the body in that order, SD first. Each instruction must read its register before the next one overwrites it: SD reads the F4 that FADD is about to replace, FADD reads the F0 that LD is about to replace. Both are WAR dependences, and on the papers' VLIW a writer may share the cycle of its reader. No register was added: F0 and F4 serve three elements in turn.
Fix the offsets. R1 moves by 8 once per body. The SD works on the element R1 points at, 0(R1); the LD works two elements ahead:
\[ \text{LD offset} = 2 \times 8 = 16 . \]Fill and drain. Before the first body, elements 0 and 1 must already be under way: LD x[0], FADD x[0], LD x[1] — the prologue. After the last body, elements n − 2 and n − 1 still owe their last stages: SD x[n−2], FADD x[n−1], SD x[n−1] — the epilogue. The body itself runs n − 2 times, so the exit test compares R1 with R5, the address of x[n−2].
START_UP:
LD F0, 0(R1) ; x[0]
FADD F4, F0, F2 ; x[0]
LD F0, 8(R1) ; x[1]
SP_LOOP:
SD F4, 0(R1) ; x[i]
FADD F4, F0, F2 ; x[i+1]
LD F0, 16(R1) ; x[i+2]
ADDUI R1, R1, 8
BNE R1, R5, SP_LOOP
FINISH_UP:
SD F4, 0(R1) ; x[n-2]
FADD F4, F0, F2 ; x[n-1]
SD F4, 8(R1) ; x[n-1]
| Body instruction | Works on | Address |
|---|---|---|
| SD F4, 0(R1) | x[i], computed in the previous pass | R1 |
| FADD F4, F0, F2 | x[i+1], loaded in the previous pass | — (registers only) |
| LD F0, 16(R1) | x[i+2] | R1 + 2 × 8 |
How fast is it? Every execution of the body finishes one element — the SD at its top stores a completed result. The cycles between the starts of two consecutive bodies are the initiation interval, II, simply the length of the body's schedule: 5 cycles on the papers' VLIW (section 4), so one element every 5 cycles against 10 for the original loop. Each element now spends three passes between its load and its store, longer than before: only the rate improved, not the time of one element.
- \(II\)
- initiation interval: cycles of one execution of the body
- \(n - 2\)
- executions of a three-stage body; with S stages, \(n - S + 1\)
- \(T_{\text{pro}},\ T_{\text{epi}}\)
- cycles of the prologue and epilogue, paid once per loop
Unrolling or software pipelining?
The body grows with u: 14 instructions instead of 5 for u = 4.
Removes the loop control of u − 1 iterations: one SUBI and one BNE per four elements.
Needs (u − 1)·ρ more registers: F6 … F16.
The overlap starts and stops inside every unrolled body; a remainder loop is needed when the trip count is not a multiple of u.
The body keeps its size, five instructions, plus a prologue and an epilogue once per loop.
Removes the waiting inside an iteration, not the loop control: one ADDUI and one BNE per element.
Same registers, unless a value must outlive one pass.
The overlap ramps up and down once per loop; the loop must have at least as many elements as stages.
Hennessy and Patterson put it in one line: the two techniques remove different overheads — unrolling the loop-control instructions, software pipelining the time spent below full speed, which an unrolled loop pays at both ends of every unrolled body and a pipelined loop only once. Compilers often do both: unroll a little, then pipeline the unrolled body.
Betting on the hot path: traces, superblocks, predication
Loops are the easy case: their body repeats, so the next iteration is always there to borrow from. Ordinary code is cut into short basic blocks — "only around 4 to 7 instructions are found between branches", the notes say (p. 273) — and a block that short, with its own dependences inside, cannot keep a 3-issue VLIW busy. Global scheduling moves instructions across the branches. Profiling finds the path the program takes most often; the compiler schedules that path as if the branches were not there and pays only when the bet fails.
Trace scheduling (Fisher, 1981) does it in two steps. Trace selection picks the most likely sequence of basic blocks, the trace. Trace compaction then list-schedules the whole trace as if it were one block, moving instructions up and down across the branches inside it — loads first of all, to start their long latency early.
Moving an instruction across a branch makes it run where the original program did not run it, or stop running where it did. Three moves recur, and the sandbox below shows each on a four-block loop:
- Up above a branch — speculation. The instruction now runs on the off-trace path too: allowed without repair only if its result is dead there and it does no harm — no store, no exception the original program would not raise.
- Down below a branch, into the on-trace block. The off-trace path no longer executes it, so a copy must go on the exit path: compensation code.
- Up above a join, where an off-trace block enters the trace from the side. The entering path would skip the instruction, so a copy goes on that path.
Side entrances make trace scheduling expensive: every move above one costs a copy on every entering path. A superblock (Hwu et al., 1993) is a trace with a single entry and any number of exits, obtained by tail duplication: the part of the trace below a side entrance is copied, and the off-trace path jumps to the copy. Repairs are then needed only at the exits, outside the superblock; the price is the duplicated tail.
The notes stop here. Hennessy and Patterson add a fourth tool, which removes the branch instead of scheduling around it — background, not in the notes. A predicated instruction names a one-bit predicate register: it is always issued, but writes its result only when the predicate is true. If-conversion turns the diamond of the sandbox into one block: a compare sets P1 and its complement P2, then (P2) FADD and (P1) FMUL follow without a branch. The control dependence has become a data dependence on a predicate: a bigger block, nothing to mispredict, nothing to compensate. The cost: both arms occupy issue slots on every pass, whichever way the condition goes.
"A loop over 260 elements that does not fit a vector register? Software-pipeline it." The papers ask exactly this (July 2024, quiz 5; June 2026, quiz 3) with trace scheduling and software pipelining, two VLIW compiler techniques, as distractors. A vector processor whose registers hold 64 or 32 elements needs strip mining: strips of at most one register length — lesson 31.
How the papers ask it: LOOP1 against LOOP2, and the midterm's SP_LOOP
Every paper of 2024–2026 has a 5-point VLIW exercise, and two of them schedule a software-pipelined loop. The rules are those of lesson 20: latency 3 for Memory and FP, 1 from the Integer unit to the next Int/FP/L/S instruction and 2 to a branch; one delay slot after the branch, counted; a WAR writer may share the cycle of the last reader of its register ("in the Register File, it is possible to read and write at the same address at the same clock cycle"); no NOPs written. In the solved papers the "critical path for a single iteration" is the schedule length including the delay slot, CPI = cycles / instructions, code efficiency = IC / (cycles × issue width).
The 22 June 2026 paper asks for both loops of the hook, with C1 already filled in, and then for the four metrics of each and the speed-up in CPI:
LOOP1: LD F2, 0(R1)
FADD F6, F2, F2
SD F6, 0(R1)
ADDUI R1, R1, 4
BNE R1, R3, LOOP1
LOOP2: SD F6, 0(R2)
FADD F6, F2, F2
LD F2, 0(R2)
ADDUI R2, R2, 4
BNE R2, R3, LOOP2
| Memory Unit | Floating Point Unit | Integer Unit | |
|---|---|---|---|
| C1 | LD F2, 0(R1) | ||
| C2 | |||
| C3 | |||
| C4 | FADD F6, F2, F2 | ||
| C5 | |||
| C6 | |||
| C7 | SD F6, 0(R1) | ADDUI R1, R1, 4 | |
| C8 | |||
| C9 | BNE R1, R3, LOOP1 | ||
| C10 | (delay slot) |
| Memory Unit | Floating Point Unit | Integer Unit | |
|---|---|---|---|
| C1 | SD F6, 0(R2) | FADD F6, F2, F2 | |
| C2 | LD F2, 0(R2) | ADDUI R2, R2, 4 | |
| C3 | |||
| C4 | BNE R2, R3, LOOP2 | ||
| C5 | (delay slot) |
| LOOP1 | LOOP2 | |
|---|---|---|
| How long is the critical path for a single iteration? | 10 cycles | 5 cycles |
| What performance did you achieve in CPI? | 10/5 = 2 | 5/5 = 1 |
| What performance did you achieve in FP ops per cycles? | 1/10 = 0.1 | 1/5 = 0.2 |
| How much is the code efficiency? | 5/30 = 0.17 | 5/15 = 0.33 |
| How much is the speed up in terms of CPI? | 2 / 1 = 2 | |
Before: LOOP1, 10 cycles
After: LOOP2, 5 cycles
Add the third shape. LOOP1 unrolled by four with renamed registers schedules on the same machine in 13 cycles: four loads in C1–C4, four adds in C4–C7, four stores in C7–C10 with ADDUI in C10, BNE in C12 and the delay slot in C13.
| LOOP1 | LOOP2 | ×4 | |
|---|---|---|---|
| static instructions | 5 | 11 | 14 |
| FP registers | 2 | 2 | 8 |
| cycles per pass | 10 | 5 | 13 |
| elements per pass | 1 | 1 | 4 |
| cycles per element | 10 | 5 | 3.25 |
| code efficiency | 0.17 | 0.33 | 0.36 |
The midterm of 5 May 2025: backwards, then forwards
Exercise 1 of the 2025 midterm (notes pp. 506–512) runs the construction backwards. Part A gives a two-array software-pipelined loop and asks for the original:
START_UP:
LD F0, 0(R1)
LD F2, 0(R2)
FADD F4, F0, F0
FADD F6, F2, F2
LD F0, 8(R1)
LD F2, 8(R2)
SP_LOOP:
SD F4, 0(R1) ; I1
SD F6, 0(R2) ; I2
FADD F4, F0, F0 ; I3
FADD F6, F2, F2 ; I4
LD F0, 16(R1) ; I5
LD F2, 16(R2) ; I6
ADDUI R1, R1, 8 ; I7
ADDUI R2, R2, 8 ; I8
ADD R3, R2, R1 ; I9
BNE R3, R4, SP_LOOP ; I10
Follow one element. LD F0, 16(R1) loads it; one pass later, when R1 has moved by 8, FADD F4, F0, F0 doubles it; one pass after that, when R1 has moved by 16, SD F4, 0(R1) stores it — at the address it was loaded from. Put the three stages of one element back into one pass, in natural order and with offset 0, and keep the loop control. START_UP and FINISH_UP disappear:
LOOP: LD F0, 0(R1)
LD F2, 0(R2)
FADD F4, F0, F0
FADD F6, F2, F2
SD F4, 0(R1)
SD F6, 0(R2)
ADDUI R1, R1, 8
ADDUI R2, R2, 8
ADD R3, R2, R1
BNE R3, R4, LOOP
Part B schedules SP_LOOP on the 3-issue VLIW. Its only true dependences are in the tail ADDUI → ADD → BNE.
| Memory Unit | Floating Point Unit | Integer Unit | |
|---|---|---|---|
| C1 | SD F4, 0(R1) | FADD F4, F0, F0 | |
| C2 | SD F6, 0(R2) | FADD F6, F2, F2 | |
| C3 | LD F0, 16(R1) | ADDUI R1, R1, 8 | |
| C4 | LD F2, 16(R2) | ADDUI R2, R2, 8 | |
| C5 | ADD R3, R2, R1 | ||
| C6 | |||
| C7 | BNE R3, R4, SP_LOOP | ||
| C8 | (delay slot) |
- C1–C2: each SD shares its cycle with the FADD that rewrites its register (WAR: the SD reads the old F4 or F6). The single memory unit pushes SD F6 to C2.
- C3–C4: the loads take the memory unit in turn. ADDUI R1 may not precede the last reader of the old R1, LD F0 in C3, so it shares C3 with it; ADDUI R2 shares C4 with LD F2. (The notes say ADDUI starts "when the first SD store finishes": that the store's 3 cycles end in C3 is a coincidence.)
- C5: ADD R3, one cycle after ADDUI R2. C6 stays empty because the text gives the Integer ALU a "2 cycle latency to next Branch": the branch reads R3 in ID, so a BNE in C6 would still see the old value. BNE goes to C7, the delay slot to C8. In the exam, give the stated latency as the reason.
Eight cycles: CPI = 8/10 = 0.8, FP ops per cycle = 2/8 = 0.25, code efficiency = 10/(8 × 3) = 0.417. The original loop of Part A, scheduled by the same rules, needs 12 cycles (LD C1 and C2, FADD C4 and C5, SD with ADDUI in C7 and C8, ADD C9, BNE C11, delay slot C12): CPI 1.2, a speed-up of 1.5.
"In our case, there are 8 cycles for each iteration, so the critical path is 8." The notes' solution (p. 512) defines the critical path as "the longest chain of dependent operations that must be executed in sequence because of true data dependencies (RAW)", then answers with the schedule length. 8 is the number the solved papers give, but the true-dependence chain ADDUI → ADD → BNE plus the delay slot is only 5 cycles long; the other 3 come from the single memory unit. Write: "8 cycles, C1–C8 including the delay slot, set by the four memory operations on one memory unit and the tail ADDUI → ADD → BNE".
Back to the puzzle of the hook. LOOP2's SD stores the F6 that the previous pass's FADD computed, and its FADD doubles the F2 that the previous pass's LD loaded: one pass holds three different elements (Fig. 1), linked only by anti-dependences that cost nothing on the papers' machine (Fig. 2). The 3 + 3 cycles of waiting have not disappeared; they now elapse between one pass and the next, while other elements are worked on. What remains inside a pass — SD with FADD, the LD behind them on the single memory unit, the branch 2 cycles after ADDUI, the delay slot — is 5 cycles: one element out of the loop every 5 cycles.
Exam-style questions
Explain the main concepts and benefits of each one of the following static scheduling techniques used by compilers for VLIW processors, and state the main drawback of each: (1) Loop unrolling; (2) Software pipelining; (3) Trace scheduling; (4) Superblock scheduling; (5) Predication (if-conversion).
Show model answer
(1) Loop unrolling replicates the loop body u times in one block, adjusts the displacements of the copies (0, 8, 16, …) and keeps a single index update and a single branch. The compiler renames the registers of each copy, removing the WAR and WAW dependences between copies, so the scheduler can interleave u independent chains and hide latencies. Benefits: fewer branches and index updates per element, fewer stalls, more operations per bundle (x[i] = x[i] + s: 10 cycles per element → 3.5 on a single-issue pipeline with a delay slot). Costs: code size, (u − 1) times the registers a copy writes, a remainder loop when the trip count is not a multiple of u.
(2) Software pipelining reorganises the loop so that each execution of the body contains stages of different iterations — store of i, compute of i + 1, load of i + 2 — in reverse order. The true dependences of an iteration no longer lie inside one body; only anti-dependences and the loop control do. A prologue fills and an epilogue drains the pipeline. Benefits: one iteration completes per execution of the body, with the body's original size and the same registers (June 2026: LOOP1 10 cycles, LOOP2 5 cycles, CPI speed-up 2). Costs: prologue and epilogue code, extra registers when a value must live longer than one pass, at least as many iterations as stages, compiler complexity.
(3) Trace scheduling is global scheduling across basic blocks: profiling selects the most likely path (the trace, acyclic), which is list-scheduled as one block; instructions move across branches (above them speculatively) and across joins. Compensation code — copies of moved instructions on the off-trace exit and entry paths — keeps the other paths correct. Benefits: fills wide instructions beyond the 4 to 7 instructions of a basic block and starts long-latency loads early. Costs: code growth from compensation code, slower off-trace paths, dependence on predictable branches, a complex compiler; stores and instructions that may raise exceptions cannot be speculated freely.
(4) Superblock scheduling restricts the region to a single entry and multiple exits, obtained by tail duplication: the part of the trace below a side entrance is copied for the entering path. Moves then need compensation only at the exits, outside the superblock, so speculation is simpler and safer. Cost: the duplicated code.
(5) Predication removes a branch instead of scheduling around it: a compare sets a predicate register and its complement, and the instructions of each arm are guarded by one of them — always issued, but writing their result only when their predicate is true. The control dependence becomes a data dependence on the predicate, so both arms form one larger basic block that the scheduler can fill, with nothing to mispredict and no compensation code. Cost: both arms take issue slots on every pass, so it pays only for short, balanced, hard-to-predict branches; it needs predicate registers in the ISA (IA-64).
The June 2026 paper asked eight such sub-points for 8 points, about five lines each. A complete sub-point has three things: the mechanism in one or two sentences, the benefit with a number from an exercise, the cost. The contrasts make the answer crisp — unrolling replicates while software pipelining interleaves; trace scheduling repairs at entries and exits, a superblock only at exits, predication nowhere — and so does one sentence saying that the compiler applies all five, the VLIW doing no dependence checking at run time.
Given the following software pipelined loop:
SP_LOOP: SD F6, 0(R1)
SD F8, 0(R2)
SD F10, 0(R3)
FADD F6, F0, F0
FADD F8, F2, F2
FADD F10, F4, F4
LD F0, 16(R1)
LD F2, 16(R2)
LD F4, 16(R3)
ADDUI R1, R1, 8
ADDUI R2, R2, 8
ADDUI R3, R3, 8
BNE R1, R4, SP_LOOP
Consider a 4-issue VLIW machine with fully pipelined functional units: 2 Memory Units with 3 cycles latency; 1 FP ALU with 3 cycles latency; 1 Integer ALU with 1 cycle latency to next Int/FP and 2 cycle latency to next Branch. The branch is completed with 1 cycle delay slot (branch solved in ID stage). No branch prediction. In the Register File, it is possible to read and write at the same address at the same clock cycle. Considering one iteration of the SP_LOOP, complete the table (Memory Unit 1, Memory Unit 2, Floating Point Unit, Integer Unit; C1 given: SD F6, 0(R1)) by using the list-based scheduling (do NOT introduce any loop transformation), including the branch delay slot. Then: how long is the critical path for a single iteration? What performance did you achieve in CPI and in FP ops per cycle? How much is the code efficiency?
Show model answer
There is no true dependence among the SDs, FADDs and LDs: each SD reads the value its FADD rewrites (WAR), each FADD reads the value its LD rewrites (WAR), and every ADDUI may share the cycle of the last instruction that reads its register. The integer unit has four instructions to issue (three ADDUI and BNE), so each stream's SD and LD are placed together to free its ADDUI as early as possible.
| Memory Unit 1 | Memory Unit 2 | Floating Point Unit | Integer Unit | |
|---|---|---|---|---|
| C1 | SD F6, 0(R1) | LD F0, 16(R1) | FADD F6, F0, F0 | ADDUI R1, R1, 8 |
| C2 | SD F8, 0(R2) | LD F2, 16(R2) | FADD F8, F2, F2 | ADDUI R2, R2, 8 |
| C3 | SD F10, 0(R3) | LD F4, 16(R3) | FADD F10, F4, F4 | ADDUI R3, R3, 8 |
| C4 | BNE R1, R4, SP_LOOP | |||
| C5 | (delay slot) |
Critical path = 5 cycles. CPI = 5/13 ≈ 0.38. FP ops per cycle = 3/5 = 0.6. Code efficiency = 13/(5 × 4) = 0.65.
Five cycles is optimal: six memory operations need three cycles on two units, the ADDUI of the stream loaded last cannot precede C3, and the single integer unit needs a fourth cycle for BNE. A pure program-order tie-break (SD F6 and SD F8 together in C1) is also a list schedule, but it delays LD F2 and LD F4, pushes ADDUI R3 to C4 and BNE to C5: 6 cycles, CPI 0.46, 0.5 FP ops per cycle, efficiency 0.54. Write the reasoning about the integer unit next to the table: it is what justifies pairing each SD and LD in one cycle. Lesson 20 drills the same table as a list-scheduling exercise.
Consider the following software pipelined loop SP_LOOP and its start-up code; R1 points to the first element of an array of n doubles.
START_UP: LD F0, 0(R1)
FADD F4, F0, F2
LD F0, 8(R1)
SP_LOOP: SD F4, 0(R1)
FADD F4, F0, F2
LD F0, 16(R1)
ADDUI R1, R1, 8
BNE R1, R5, SP_LOOP
(a) Reconstruct the original (non-pipelined) loop. (b) For each of the first three instructions of SP_LOOP, state which iteration of the original loop it belongs to. (c) Write the finish-up code. (d) For n = 100, how many times is the body of SP_LOOP executed, and which value must R5 hold?
Show model answer
(a) With R3 = initial R1 + 8n:
LOOP: LD F0, 0(R1)
FADD F4, F0, F2
SD F4, 0(R1)
ADDUI R1, R1, 8
BNE R1, R3, LOOP
(b) SD F4, 0(R1): iteration i, whose sum was computed in the previous pass. FADD F4, F0, F2: iteration i + 1, whose operand was loaded in the previous pass. LD F0, 16(R1): iteration i + 2 — two strides ahead, 16 = 2 × 8.
(c) FINISH_UP: SD F4, 0(R1) (element n − 2); FADD F4, F0, F2 (element n − 1); SD F4, 8(R1) (element n − 1).
(d) The body handles i = 0 … n − 3: it runs n − 2 = 98 times. R5 = initial R1 + 8 × 98 = R1 + 784, the address of element 98.
The classic mistake is to read SD as "store the result of this pass" and put it first in the reconstructed loop. Follow one value through the passes instead. In (c), remember that the last ADDUI of the body has already moved R1.
Let's consider the two following loops, where LOOP2 is the software-pipelined form of LOOP1 (its load fetches the element two iterations ahead):
LOOP1: LD F2, 0(R1)
FADD F6, F2, F2
SD F6, 0(R1)
ADDUI R1, R1, 4
BNE R1, R3, LOOP1
LOOP2: SD F6, 0(R2)
FADD F6, F2, F2
LD F2, 8(R2)
ADDUI R2, R2, 4
BNE R2, R3, LOOP2
Both are scheduled by list-based scheduling, including the branch delay slot, on a 4-issue VLIW machine with fully pipelined units: 2 Memory Units with 3 cycles latency, 1 FP ALU with 3 cycles latency, 1 Integer ALU with 1 cycle latency to next Int/FP and 2 cycle latency to next Branch; branch solved in ID with 1 delay slot; read and write at the same register-file address in the same clock cycle. How much is the speed up of LOOP2 over LOOP1 in terms of CPI?
Show model answer
LOOP1 does not change: its chain LD (C1) → FADD (C4) → SD with ADDUI (C7) → BNE (C9) → delay slot (C10) uses one memory unit at a time. 10 cycles, CPI = 10/5 = 2.
LOOP2: SD F6, 0(R2) and LD F2, 8(R2) both fit in C1 (two memory units, two different addresses), with FADD F6 (WAR on F6 and F2) and ADDUI R2 (WAR on R2) in the same cycle; BNE in C3, delay slot in C4. 4 cycles, CPI = 4/5 = 0.8.
\[ \text{speed-up} = \frac{2}{0.8} = 2.5 \]The second memory unit helps only the software-pipelined loop: LOOP1 is bound by its true-dependence chain, LOOP2 by resources and the branch. The paper itself prints LOOP2's load as LD F2, 0(R2): then the store and the load of one pass touch the same address, the load must not move up beside the store that precedes it, it stays in C2, and LOOP2 takes 5 cycles — a speed-up of 2. With one memory unit, as in the paper, the question never arises.
Let's consider the following code: for (i=0; i<260; i++) Y[i] = X[i] + Y[i]; Which code transformation can be applied to be executed by the VMIPS processor with a Vector Register File composed of 16 Vector registers of 32 elements and 64 bits/element? Motivate your answer.
Show model answer
Strip mining. The loop has 260 elements and a vector register holds 32: 260 = 8 × 32 + 4, so a first strip of 4 elements and then eight strips of 32, setting VLR for each strip.
The distractors are the techniques of this lesson; strip mining itself is lesson 31. The motivation point is the arithmetic 260 = 8 × 32 + 4.
True or false: in the steady state, a software-pipelined loop completes one iteration every clock cycle.
Show model answer
False. Each execution of the body finishes one iteration, so the throughput is one iteration per initiation interval — 5 cycles for LOOP2 on the 3-issue VLIW of June 2026, 8 cycles for the midterm's SP_LOOP.
The notes repeat the wrong version five times (pp. 289–291); the CPI you compute in the VLIW exercise contradicts it.
Which of the following statements about trace and superblock scheduling are true? (MULTIPLE ANSWERS)
Show model answer
Answers 1, 2 and 4.
The two directions of compensation are what the question checks: copies go on the exit path for moves below a branch and on the entering path for moves above a join.
Check yourself
The notes' loop unrolled four times, renamed and regrouped (four LD, four FADD, four SD, SUBI, BNE) takes 16 cycles on the delayed-branch pipeline. Which lost cycles are left?
In LOOP2 (SD F6, 0(R2); FADD F6, F2, F2; LD F2, 0(R2); …), what does the SD store in a pass?
Why does the three-stage loop load with LD F0, 16(R1) while it stores with SD F4, 0(R1)?
Which of these are costs of software pipelining compared with the original loop?
During trace scheduling, an instruction of B1 (above a branch) is moved down into B2, the on-trace successor. The off-trace block B3 also needs its result. What must the compiler do?
What does tail duplication buy a trace?
When is if-conversion (predication) likely to beat keeping the branch?
Short, balanced, unpredictable: the three words to write next to if-conversion.
Key take-aways
- Unrolling by u copies the body with renamed registers and corrected displacements and keeps one index update and one branch. On the single-issue delayed-branch pipeline the notes' loop goes from 10 cycles per element to 7 (copies in order), 4 (renamed and regrouped: the notes' 16 for four) and 3.5 (stores moved into the stall and the delay slot).
- A software-pipelined body holds the SD of element i, the FADD of i + 1 and the LD of i + 2, in that reverse order: only anti-dependences link the three, the load runs two strides ahead (16 for stride 8), a prologue fills and an epilogue drains the line, and the body runs n − 2 times.
- One element completes per execution of the body, every II cycles, not every cycle: LOOP2 needs 5 cycles per element on the papers' VLIW against 10 for LOOP1, with the same five instructions and two registers.
- In the exam, schedule the given body as it is: a true dependence waits the producer's latency (Memory and FP 3, Integer 1, 2 to a branch), a WAR writer may share its reader's cycle, BNE is followed by a counted delay slot, and the critical path for a single iteration is the schedule length — 8 cycles for the midterm's SP_LOOP, CPI 0.8, efficiency 0.417.
- Trace scheduling bets on the hot path and repairs the other paths: copies on the exit path for moves below a branch, on the entering path for moves above a join. A superblock removes the entering paths by tail duplication; if-conversion removes the branch and pays in issue slots, so it wins only for short, balanced, hard-to-predict diamonds.
- A 260-element loop on a vector machine is a strip-mining question, not a software-pipelining one.
What to watch and read
-
Modern Microprocessors: A 90-Minute Guide!
Jason Robert Carey Patterson (Lighterra)
Read the sections "Explicit Parallelism – VLIW" and "Eliminating Branches with Predication": the static side of the story and predication in plain English, with real processors.
-
Topic 12: Cyclic Instruction Scheduling
David August, Princeton COS 32021 pp.
Software pipelining explained as hardware pipelining of iterations: prologue, kernel and epilogue, the initiation interval and the two bounds that keep it from shrinking further.
-
Lecture 7: Scoreboard, Tomasulo, Register Renaming
John Kubiatowicz, Berkeley CS25225 pp.
It opens with the loop-unrolling and software-pipelining examples in the same mnemonics as the notes, including a before/after of the pipelined loop.
-
L21: VLIW/EPIC — Statically Scheduled ILP
MIT 6.823 (Arvind, Asanović, Emer)34 slides
The whole lesson as one line of argument: loop unrolling, software pipelining, trace scheduling, rotating register files and IA-64.
-
Software Pipelining: An Effective Scheduling Technique for VLIW Machines
Monica Lam, PLDI 198811 pp.
The paper behind the definition quoted in section 2, with modulo variable expansion — how a pipelined loop gets more registers when a value outlives one pass.