Same names, no conflicts: unrolling, renaming and scheduling
After this lesson you will list every dependence of a loop in the exam's vocabulary, unroll it with fresh registers, schedule it with corrected store offsets, count what the midterm asks you to count, and say exactly what Tomasulo's hardware does with all of it — and what it does not.
Take a loop of five instructions that multiplies every element of an array by a scalar, and rewrite it as fourteen. On a plain single-issue pipeline the five-line version needs 9 cycles per element, the fourteen-line version 3.5 — 2.6 times faster, with no new hardware. Three compiler moves make the difference: copy the body four times, give every copy its own registers, reorder the lines.
The notes print the scheduled fourteen lines on pages 191–192, and one of them stores its result to the wrong address — an element that is not even part of this iteration. The slip is a single number, and the professor's VLIW exercises forbid exactly the move that produces it. Find the line.
The wrong line is caught in section 3; section 4 asks how much of all this the hardware does by itself, at run time.
Two kinds of dependence, and only one of them carries a value
Lesson 7 sorted the ways two instructions collide into three kinds. A true dependence (RAW) carries a value from a producer to a consumer. An anti-dependence (WAR) and an output dependence (WAW) carry nothing: the two instructions merely use the same register name. Where the names come from is the first line of these pages (p. 186): name dependences "are constraints imposed by the limited number of registers visible in the Instruction Set Architecture" — thirty-two floating-point names, reused in every iteration of every loop.
The running example of pages 188–193 walks an array downwards and multiplies each element by the scalar in F2. R1 points at the current element; the notes' walkthrough starts it at 80.
I0 LOOP: LD F0, 0(R1) ; F0 ← M[R1]
I1 FMUL F4, F0, F2 ; F2: the scalar
I2 SD F4, 0(R1) ; M[R1] ← F4
I3 SUBI R1, R1, 8 ; 8 bytes down
I4 BNE R1, R2, LOOP ; R2 = 0
Exercise 1 of every paper of 2024–2026 opens with a table for exactly this: one row per instruction (I#, TYPE OF INSTRUCTION) and a column ANALYSIS OF DEPENDENCES listing 1. its true data dependences ("with I# for Fx"), 2. its anti-dependences, 3. its output dependences — inside one iteration. For this loop:
| I# | TYPE OF INSTRUCTION | ANALYSIS OF DEPENDENCES |
|---|---|---|
| I0 | LOOP: LD F0, 0(R1) | None |
| I1 | FMUL F4, F0, F2 | True data dependence with I0 for F0 |
| I2 | SD F4, 0(R1) | True data dependence with I1 for F4 |
| I3 | SUBI R1, R1, 8 | Anti-dependence with I0 and I2 for R1 |
| I4 | BNE R1, R2, LOOP | True data dependence with I3 for R1 |
What the exam table leaves out is the part this lesson lives on: the dependences between iterations. The next iteration writes F0 and F4 again and reads the R1 that this iteration's SUBI produced. A dependence that crosses iterations is loop-carried; page 188 lists part of the inventory, the complete one is below.
| Instruction of iteration i + 1 | True data dependence | Anti-dependence | Output dependence |
|---|---|---|---|
LD F0, 0(R1) | with SUBI(i) for R1 | with FMUL(i) for F0 | with LD(i) for F0 |
FMUL F4, F0, F2 | — | with SD(i) for F4 | with FMUL(i) for F4 |
SD F4, 0(R1) | with SUBI(i) for R1 | — | — |
SUBI R1, R1, 8 | with SUBI(i) for R1 | with BNE(i) for R1 | with SUBI(i) for R1 |
BNE R1, R2, LOOP | — | — | — |
Read the two tables together. Inside an iteration one chain of values runs LD → FMUL → SD, plus SUBI → BNE. Between iterations the only value that travels is R1 — one subtraction per element. Everything else that crosses the boundary is a name: F0 written by both loads, F4 by both multiplies, F0 read by this FMUL before the next LD overwrites it, F4 read by this SD before the next FMUL overwrites it. Those four arcs chain iteration i + 1 to iteration i only because both iterations are spelled with the same two registers.
Before: both iterations use F0 and F4
After: iteration i + 1 uses F6 and F8
Register renaming — replacing the architectural register names of a program by a larger set of storage locations, so that two instructions that share only a name no longer share a location. It deletes WAR and WAW dependences and leaves RAW untouched. The notes (p. 186) distinguish static renaming, by the compiler with ISA-visible registers, typically while unrolling a loop, from implicit (dynamic) renaming, by the hardware at run time — in Tomasulo's algorithm, through the tags of the reservation stations.
The exam asks what renaming is for: the June 2026 theory question lists "5. Dynamic Register Renaming" among the techniques whose "main concepts and benefits" you explain. The benefits are the notes' three ticks (p. 187): instructions that write the "same" architectural register can execute out of order, independent loop iterations can run concurrently, false dependences disappear. The cost depends on who renames: fresh ISA registers and longer code for the compiler, tag-matching hardware and bus bandwidth for Tomasulo.
WAR and WAW are name dependences born of a small register file; renaming deletes them and leaves the true dependences — the only ones that carry values — exactly where they were.
Unrolling: pay the overhead once per four elements
The loop spends two of its five instructions, SUBI and BNE, on nothing but getting to the next element. Loop unrolling is the compiler's answer: copy the body u times into one straight-line block, fold the addresses of the copies into the displacements, and keep one index update and one branch for the whole block. The notes unroll by four (p. 190):
LOOP: LD F0, 0(R1)
FMUL F4, F0, F2
SD F4, 0(R1)
LD F6, -8(R1) ; copy 2: fresh registers F6, F8
FMUL F8, F6, F2
SD F8, -8(R1)
LD F10, -16(R1) ; copy 3: F10, F12
FMUL F12, F10, F2
SD F12, -16(R1)
LD F14, -24(R1) ; copy 4: F14, F16
FMUL F16, F14, F2
SD F16, -24(R1)
SUBI R1, R1, 32 ; four elements of 8 bytes
BNE R1, R2, LOOP
Three things to notice. First, the three intermediate SUBI/BNE pairs are gone: fourteen instructions do the work of twenty. Second, every copy got its own registers, and that is not decoration: written with F0 and F4 four times, the copies would carry the four arcs of Fig. 1 between consecutive copies, and no later load could move above an earlier multiply. Unrolling without renaming replicates the name dependences. Third, F2 is only read, so it needs no copy: only the registers a copy writes are renamed, two for each of the three extra copies, six in all.
The notes list the benefits (p. 190): more independent work, the branch and the index update paid once per four elements, several functional units kept busy, a block the compiler can reschedule. The costs mirror them: longer code (fourteen static instructions instead of five, more instruction-cache pressure), a fuller register file, and a remainder loop of the original body when the number of elements is not a multiple of u. With R1 = 80 the array has ten elements, and the listing above would run R1 through 48, 16, −16 … without ever meeting R2 = 0.
Counting: the midterm's loop
The midterm of 5 May 2025 (notes pp. 534–537) turned this into a counting exercise on a loop that adds two arrays element by element; R1 and R2 start at 0, R3 at 400.
LOOP: LD F2, 0(R1)
LD F4, 0(R2)
FADD F6, F2, F4
SD F6, 0(R1)
ADDUI R1, R1, 4
ADDUI R2, R2, 4
BNE R1, R3, LOOP
Four of the seven instructions do useful work (two loads, one add, one store); three are loop overhead (two pointer increments, one branch) — the paper of 11 February 2025 (notes p. 567) asks for the same split as "control vs datapath instructions". R1 runs from 0 to 400 in steps of 4: N = 100 iterations, 700 instructions, 100 branches. Unrolled by u, the useful part is copied u times and the overhead stays three, with the increments scaled to 4u (Fig. 2).
In symbols, for any loop the exam invents: N is the trip count, w the useful and o the overhead instructions per iteration, u the unrolling factor and ρ the registers one copy writes.
- \(N\)
- iterations of the original loop: (R3 − R1 at entry) / stride = 400 / 4 = 100
- \(w,\ o\)
- useful and overhead instructions of one iteration: 4 and 3 here; 3 and 2 for the loop of section 1
- \(u\)
- unrolling factor, assumed to divide N (otherwise N mod u iterations of the original body remain)
- \(\rho\)
- registers one copy writes, which must be renamed: F2, F4, F6 here, so ρ = 3; F0 and F4 in section 1, so ρ = 2 (F2 there is only read)
The midterm's question "how much is the global instruction count decrease?" has a closed form worth deriving, because its limit says what unrolling can never do.
The original loop executes the body N times:
\[ IC_1 = N\,(w + o) = 100 \cdot 7 = 700 . \]Unrolled by u, with u dividing N, there are N/u iterations of u w + o instructions; the useful part stays N w, the overhead is divided by u:
\[ IC_u = \frac{N}{u}(u\,w + o) = N\,w + \frac{N\,o}{u} \] \[ IC_4 = 400 + \frac{300}{4} = 475 . \]Subtract: the useful work cancels, only overhead is saved:
\[ IC_1 - IC_u = N\,o\left(1 - \frac{1}{u}\right) \] \[ IC_1 - IC_4 = 300 \cdot 0.75 = 225 . \]Divide by the original count:
\[ \frac{IC_1 - IC_u}{IC_1} = \frac{o\,(1 - 1/u)}{w + o} \] \[ \frac{IC_1 - IC_4}{IC_1} = \frac{2.25}{7} = 32.14\,\% . \]Let u grow without bound. The factor 1 − 1/u tends to 1, so the saving tends to
\[ \frac{o}{w + o} = \frac{3}{7} = 42.9\,\% \]— the fraction of the original loop that was overhead. Unrolling can approach it and never pass it: the 400 useful instructions are not negotiable.
The table is the model answer, recomputed by the readers from the notes' solution:
| Quantity | original | ×2 | ×4 |
|---|---|---|---|
| iterations | 100 | 50 | 25 |
| instructions per iteration | 7 | 11 | 19 |
| overhead instructions per iteration | 3 | 3 | 3 |
| instructions executed globally | 700 | 550 | 475 |
| branches executed globally | 100 | 50 | 25 |
| extra registers (renamed copies of F2, F4, F6) | — | 3 (F8, F10, F12) | 9 (F8 … F24) |
| FP registers live in the body | 3 | 6 | 12 |
| decrease of the instruction count | — | 150 (21.43 %) | 225 (32.14 %) |
| overhead instructions per element | 3 | 1.5 | 0.75 |
The last question, "what is the best unrolling factor between 2 and 4?": 4 — overhead per element 0.75 instead of 1.5, 25 branches instead of 50 — provided the twelve live floating-point values fit in the register file and the longer body in the instruction cache; otherwise 2 is safer. The notes get the trade-off right and the mechanism wrong:
Unrolling by u divides the overhead by u and touches nothing else: saving = o(1 − 1/u)/(w + o), never more than o/(w + o); the price is (u − 1)ρ registers, a body about u times longer and a remainder loop.
Scheduling: loads first, multiplies next, stores last
Unrolling bought fewer instructions per element, but hardly any cycles: in the listing's order every copy still waits inside itself — the multiply for its load, the store for its multiply. The notes give no cycle counts here, so the lesson takes the model of Hennessy and Patterson (5th edition, §3.2), where this example comes from (with ADD.D instead of a multiply; the latencies are those of any FP operation): a single-issue, in-order pipeline where a consumer waits a fixed number of stall cycles after its producer.
| Producer | Consumer | Stall cycles between them |
|---|---|---|
| LD | FP operation (FMUL) | 1 |
| LD | SD (data) | 0 |
| FP operation | FP operation | 3 |
| FP operation | SD (data) | 2 |
| SUBI | BNE | 1 |
On this machine the original loop takes 9 cycles per element: LD, stall, FMUL, stall, stall, SD, SUBI, stall, BNE — in the formula sheet's symbols m = 5 instructions and k = 4 stalls per iteration, \(CPI_{AS} = (m + k)/m = 1.8\). In the notes' unrolled order it takes 27 cycles for four elements, 6.75 per element: the overhead and its stall are paid once, but the twelve stalls inside the copies remain — no renaming shortens a latency. What hides latencies is code scheduling: reorder the instructions so that independent work fills the waiting cycles. The notes' recipe (p. 191): group independent instructions, delay each dependent one just enough to satisfy its true dependence, hide long latencies with useful work. On the unrolled, renamed body it gives the textbook's order — four loads, four multiplies, the stores, and the index update slipped in before the last two stores so that the branch finds R1 ready:
LOOP: LD F0, 0(R1)
LD F6, -8(R1)
LD F10, -16(R1)
LD F14, -24(R1)
FMUL F4, F0, F2
FMUL F8, F6, F2
FMUL F12, F10, F2
FMUL F16, F14, F2
SD F4, 0(R1)
SD F8, -8(R1)
SUBI R1, R1, 32
SD F12, 16(R1) ; was -16(R1): R1 is 32 lower now
SD F16, 8(R1) ; was -24(R1)
BNE R1, R2, LOOP
Fourteen instructions, fourteen cycles, no stall (m = 14, k = 0): 3.5 cycles per element, the number of the hook. Three instructions sit between each LD and its FMUL (one stall cycle was needed), at least three between each FMUL and its SD (two were needed), two between SUBI and BNE (one was needed). Now look at the two stores below SUBI: they moved past the instruction that changes their base register, so the fourth element, at R1 − 24 before the update, sits at R1 + 8 after it (Fig. 3).
Before SUBI: the fourth element is −24(R1)
After SUBI: the same element is +8(R1)
SUBI R1, R1, 32. A store that moves below the update keeps its element only if its displacement grows by 32: −24 becomes +8, −16 becomes +16. Keep −24 and the store lands at address 24, the fourth element of the next iteration — overwritten before that iteration loads it, while 56 is never written.That is the slip of the hook. The notes' scheduled listing (pp. 191–192) puts the fourth store after the branch, in a delay slot, with its old displacement:
"SUBI and BNEZ are moved right before the branch to reduce branching penalties" (the notes, p. 192). BNEZ is the branch. What moved is SUBI, hoisted above the last stores so that the branch, which needs the new R1, does not stall on it — one instruction between them is enough here.
The notes park the last store in a branch delay slot, a MIPS habit of the slides the example comes from. RISC-V, the ISA of this course, has no delay slot: put the store before the branch — still 14 cycles. The papers' VLIW machine does count one delay slot per iteration: lesson 21 unrolls the same loop for it.
Stalls are latencies, and only reordering hides them: unrolling supplies independent work, renaming makes it movable, scheduling moves it — loads first, FP operations next, stores last, and every access below the index update gets its displacement corrected.
What Tomasulo does with all this: Table 20
Page 193 asks how much of the three moves a dynamically scheduled processor makes by itself, at run time, on the unchanged five-instruction loop. The answer is two of the three, through three mechanisms.
First, implicit renaming through the reservation stations. At issue, an operand that is ready is copied into the station as a value; one that is not becomes the tag of the station or buffer that will produce it, and the register file records for each register only the tag of its latest writer. Two iterations that both write F0 are two different tags — Load1 and Load2 — so the WAR and WAW arcs of Fig. 1 never form. Second, each instruction gets its own reservation station: the LD, FMUL and SD of the next iteration enter fresh stations while those of the current one still wait, so several iterations are active at once without copying the code — the notes' dynamic loop unrolling effect. Third, several functional units and branch prediction: the instances need units to execute on, and issue must go past the BNE before it is resolved, otherwise the next iteration waits for the branch. The notes' sentence for it: "instruction issuing moves faster than control flow".
The notes sum it up in Table 20 (p. 192), the cleanest answer to "compare static and dynamic approaches" you can write at the exam:
| Technique | How Tomasulo handles it |
|---|---|
| Register renaming | Yes, dynamically, through the reservation stations and their tags |
| Code scheduling | Yes, dynamically, in the order in which operands become available |
| Loop unrolling | No — but it overlaps iterations of the unchanged code, thanks to renaming and scheduling |
Why "no"? Unrolling transforms the program text, and a processor executes the text it is given, issuing each instruction of each iteration once. What the hardware reproduces is the effect — with fresh tags per instance, several iterations proceed at once. What it cannot reproduce is the other half of the gain: every SUBI and BNE is still fetched, issued and executed, five instructions per element instead of 3.5. The open question likes to hear both sides:
Needs the latencies at compile time, (u − 1)ρ extra ISA registers and a remainder loop for awkward trip counts. Costs code size and register pressure. Gains a fixed, stall-free schedule with no extra hardware — 3.5 cycles per element here, and the only way on a VLIW. Fails when a latency is not known in advance (a cache miss).
Needs stations, tag-matching logic, a common data bus and a branch predictor. Renames with tags invisible to the ISA — the 360/91 had four FP registers — on unchanged binaries, and adapts to the latencies that actually happen: a miss simply lets the other iteration run. Costs hardware and bus bandwidth, is limited by the number of stations, and never removes an overhead instruction.
One word of the third mechanism needs care, because the June 2026 question keeps "1. Dynamic Instruction Scheduling", "2. Dynamic branch prediction and speculative execution" and "7. Reorder Buffer" as separate items:
The hook's three moves are the compiler's; the hardware does two of them live and leaves the third to the program text — which is why the exam asks both: the midterm counts an unrolled loop, the papers' Tomasulo table runs one that was never unrolled.
Tomasulo renames with tags and schedules by operand availability, at run time and on unchanged code; it never unrolls — it overlaps iterations, which needs a station per instance, several units and a predicted branch, and without a reorder buffer it can predict but not undo.
Exam-style questions
(Two sub-points in the form of the June 2026 Question 1, which had eight of them for 8 points.) Explain the main concepts and benefits of each one of the following techniques, five lines each: (a) dynamic register renaming in a Tomasulo-style processor; (b) loop unrolling with static register renaming by the compiler. Say for each what it costs and which hazards it removes.
Show model answer
(a) WAR and WAW are name dependences, caused only by reusing the few ISA register names. At issue, the reservation station that will produce a result becomes its name (tag): the register status records the tag, consumers keep either the value (Vj, Vk) or the producer's tag (Qj, Qk), and results are broadcast on the CDB as (tag, value). Benefits: WAR/WAW never stall, more instructions in flight, loop iterations overlap on unchanged code. Costs: tag comparators in every station, CDB bandwidth, limited by the number of stations. RAW remains.
(b) The compiler copies the loop body u times, folds the addresses into the displacements, keeps one index update and one branch, and gives each copy fresh ISA registers ((u − 1)ρ more), so the copies share no names. Benefits: the overhead is paid once per u elements (w + o/u instructions per element instead of w + o) and a large block of independent instructions can be scheduled — loads first, FP operations next, stores last. Costs: code size, register pressure, a remainder loop when the trip count is not a multiple of u; the latencies must be known at compile time. RAW inside each copy remains.
The examiner checks that the two renamings are kept apart — tags invisible to the ISA versus real register names — and that each technique is credited with removing WAR/WAW only, never RAW. A benefit without its cost is half an answer.
(Midterm of 5 May 2025, Question 3, as reproduced in the notes p. 534.) Let's consider the following loop code where registers R1 and R2 are initialized to 0 and R3 is initialized to 400:
LOOP: LD F2, 0(R1)
LD F4, 0(R2)
FADD F6, F2, F4
SD F6, 0(R1)
ADDUI R1, R1, 4
ADDUI R2, R2, 4
BNE R1, R3, LOOP
How many iterations of the loop? How many instructions per iteration? How many instructions due to the loop overhead per iteration? How many instructions are executed globally? How many branch instructions are executed globally? Let's consider a loop unrolling version of the code with unrolling factor 2, and then with unrolling factor 4: answer the same questions, and in addition: how many more registers are needed due to register renaming? What are the registers that need to be renamed? How much is the global instruction count decrease? What is the best unrolling factor between 2 and 4? Explain why.
Show model answer
Original loop: R1 goes 0, 4, …, 396 before the branch falls through at 400, so 400/4 = 100 iterations; 7 instructions per iteration; 3 of them overhead (ADDUI R1, ADDUI R2, BNE), 4 useful; 700 instructions and 100 branches executed.
Unrolling factor 2 — the body with the second copy renamed:
LOOP: LD F2, 0(R1)
LD F4, 0(R2)
FADD F6, F2, F4
SD F6, 0(R1)
LD F8, 4(R1)
LD F10, 4(R2)
FADD F12, F8, F10
SD F12, 4(R1)
ADDUI R1, R1, 8
ADDUI R2, R2, 8
BNE R1, R3, LOOP
50 iterations (100 is a multiple of 2, no remainder loop); 11 instructions per iteration; overhead still 3; 550 instructions and 50 branches executed. Registers: with the same names, the second copy could not write F2, F4, F6 before the first copy had read them (WAR/WAW), so the copies would stay serialised; F2, F4, F6 are renamed to F8, F10, F12: 3 more registers, (u − 1) × 3. Decrease: (700 − 550)/700 = 21.43 %.
Unrolling factor 4: copies 3 and 4 use F14/F16/F18 and F20/F22/F24 with displacements 8 and 12; ADDUI R1, R1, 16 and ADDUI R2, R2, 16. 25 iterations; 19 instructions per iteration; overhead 3; 475 instructions and 25 branches executed; (4 − 1) × 3 = 9 more registers, F8 … F24 — the same three registers F2, F4, F6 renamed three times. Decrease: (700 − 475)/700 = 32.14 %.
Best factor: 4. Iterations and branches halve again (50 → 25) and the overhead per useful add falls from 1.5 to 0.75 instructions. The price is 12 live FP registers instead of 6 and a 19-instruction body; factor 4 is preferable if the architectural register file holds the 12 live values without spilling and the code size is acceptable, otherwise factor 2 is the safer choice.
Write the unrolled code: the displacements (0, 4 / 0, 4, 8, 12) and the scaled increments (8 / 16) are where points are lost. State explicitly that 100 is a multiple of 2 and of 4, so no remainder loop is needed. The renaming here is the compiler's, with ISA registers — do not write that "the hardware" renames them.
The loop of this lesson, I0 LD F0, 0(R1); I1 FMUL F4, F0, F2; I2 SD F4, 0(R1); I3 SUBI R1, R1, 8; I4 BNE R1, R2, LOOP, runs with R1 initialized to 80 and R2 to 0. (a) How many iterations, instructions per iteration, overhead instructions per iteration, instructions and branches executed globally? (b) Write the unrolled version of the loop with unrolling factor 2 by using register renaming. (c) For the unrolled version: iterations, instructions per iteration, overhead, global instructions and branches, how many more registers and which ones, and the decrease of the global instruction count.
Show model answer
(a) R1 takes the values 80, 72, …, 8 at the top of the body: 80/8 = 10 iterations; 5 instructions per iteration, 2 of them overhead (SUBI, BNE); 50 instructions and 10 branches executed.
(b)
LOOP: LD F0, 0(R1)
FMUL F4, F0, F2
SD F4, 0(R1)
LD F6, -8(R1)
FMUL F8, F6, F2
SD F8, -8(R1)
SUBI R1, R1, 16
BNE R1, R2, LOOP
(c) 5 iterations; 8 instructions per iteration; overhead still 2; 40 instructions and 5 branches executed. 2 more registers, (2 − 1) × 2: F0 → F6 and F4 → F8 in the second copy; F2 is only read and is not renamed. Decrease: (50 − 40)/50 = 20 %.
Two traps: forgetting that F2 needs no fresh name (it is never written), and leaving the second copy's displacements at 0 instead of −8. The SUBI immediate doubles; the branch is unchanged. Part (b) is a real exam request: the paper of 11 February 2025 asked to "Write the unrolled version of the loop with unrolling factor 2 by using Register Renaming" (notes p. 568) before running it through the reorder buffer of lesson 18.
Same loop, R1 = 80 (10 iterations). It is unrolled by a factor of 4; since 10 is not a multiple of 4, the leftover elements are handled by a remainder loop of the original body. How many instructions are executed in total?
Show model answer
10 = 2 × 4 + 2: two iterations of the unrolled body (3 × 4 + 2 = 14 instructions each) and two iterations of the original body (5 each): 2 × 14 + 2 × 5 = 38 instructions, with 2 + 2 = 4 branches; the decrease is (50 − 38)/50 = 24 %, less than the 30 % the formula o(1 − 1/u)/(w + o) promises for u = 4, because the remainder pays full overhead.
The examiner wants the split 8 + 2 elements written down; an answer of 35 (10/4 × 14 with a fractional iteration) shows the remainder loop was not understood.
(Single answer.) In the scheduled, unrolled-by-4 loop, SUBI R1, R1, 32 is moved above the store of the fourth element, originally SD F16, -24(R1). After the move the store must read:
Show model answer
SD F16, 8(R1): displacement = old displacement − (change of R1) = −24 − (−32) = 8.
This is the line of the notes' page 192 that the hook asked you to find. Whenever a memory access crosses the index update in a schedule, correct its displacement by the update's immediate.
(15 July 2024, quiz 1, single answer — also in lesson 7; it is the vocabulary of this lesson.) Let's consider the following loop: for (i=1; i<=100; i++) { X[i] = X[i] + X[i-1]; /*S1*/ Z[i] = X[i] + Y[i-1] /*S2*/ }. How many loop-carried dependencies are in the code?
Show model answer
Answer 2. The loop-carried dependence is S1 → S1 through X: iteration i reads X[i−1], produced by iteration i − 1. S2 depends on S1 within the iteration, and Y is read only.
Compare with the loop of this lesson: its only loop-carried true dependence is the index R1, which is why its iterations can be overlapped — a loop like S1 could not be, whatever the renaming.
(True/False, motivate your answer.) "Tomasulo's algorithm performs loop unrolling dynamically: the hardware replicates the loop body in the reservation stations so that several iterations execute together."
Show model answer
False. Loop unrolling is a compiler transformation of the program text. Tomasulo issues the instructions of each iteration as they arrive, in order, each into its own reservation station with its own tag; because operands are tracked by tag and not by register name, the LD/FMUL/SD of iteration i + 1 can execute while those of iteration i are still waiting — the "dynamic loop unrolling effect" — but the code is never modified or replicated.
The motivation must name the mechanism — stations and tags per instance — and the word "effect": the examiner wants to see that you know what the hardware does and does not do.
Check yourself
The loop is unrolled by 4 without renaming: every copy writes F0 and F4. Which dependences chain copy 2 to copy 1? Select all that apply.
Four arcs per boundary, all names: that is what fresh registers delete, and it is why unrolling without renaming leaves the copies a chain.
In the sandbox, renaming the unrolled loop without scheduling it left the cycle count at exactly 27. Why?
A compiler hoists SUBI R1, R1, 32 above the last two stores of an unrolled loop. What else must change?
What limits how far a compiler can unroll a loop? Select all that apply.
Of the three mechanisms of page 193, which one lets the next iteration's LD be issued before the BNE of the current iteration is resolved?
Two iterations of the loop are in flight in Tomasulo's machine, both written with F0 and F4, and no WAW hazard occurs. What makes that possible?
Key take-aways
- WAR and WAW are name dependences born of a small register file; renaming — fresh ISA registers by the compiler, tags by Tomasulo — deletes them, and the true dependences stay exactly where they were.
- Unrolling by u pays the loop overhead once per u elements: u·w + o instructions per iteration, N/u iterations, a saving of o(1 − 1/u)/(w + o) that can never pass o/(w + o) — 42.9 % for the midterm loop; it costs (u − 1)ρ registers, a body about u times longer and a remainder loop.
- Unrolling alone leaves the stalls inside every copy, and renaming alone removes none; together they let the scheduler put the loads first, the FP operations next and the stores last — 9 → 3.5 cycles per element on Hennessy and Patterson's pipeline.
- A memory access scheduled below the index update keeps its element only if its displacement grows by what the update subtracted: after
SUBI R1, R1, 32, −24(R1) becomes 8(R1). The notes' line 14 on page 192 misses it. - Tomasulo renames with tags and schedules by operand availability at run time, but never unrolls: it overlaps iterations of the unchanged code, given a station per instance, several units and a predicted branch (Table 20).
- Plain Tomasulo predicts but cannot undo; speculation that can be squashed is the reorder buffer's job, and the exam keeps the two apart.
What to watch and read
-
Modern Microprocessors: A 90-Minute Guide!
Jason R. C. Patterson, Lighterraone section
Read the section "Instruction Scheduling, Register Renaming & OOO": the compiler's static scheduling against the hardware's out-of-order execution with renamed registers, in plain English, with real processors of each kind.
-
CS252 Lecture 7: Scoreboard, Tomasulo, Register Renaming
UC Berkeley, John Kubiatowiczslides 2–6 and 74–97
The slides this example comes from, in the notes' mnemonics (ADDD where the notes multiply): slide 74 is the scheduled loop of section 3 with the store written
SD 8(R1),F16 ; 8-32 = -24; slides 5–6 are on loop-carried dependences; slides 75–97 are the Tomasulo loop example of lesson 16. -
Dynamic Scheduling with Tomasulo Algorithm — simulator
Virtual Labs, IIIT Hyderabad
Type two writes to the same register and step: the guided procedure asks you to "watch how different writes to R1 get different reservation station tags" — implicit renaming, live, after section 4.
-
6.823 L21: VLIW/EPIC — Statically Scheduled ILP
MIT OpenCourseWare, Arvind, Asanović, Emer34 slides
Loop unrolling, then software pipelining, then trace scheduling in one line of argument: where the compiler's three moves lead when the machine is a VLIW — the road to lessons 19–21.