Graphs, critical paths and the exam table: list scheduling
After this lesson you will draw the dependence graph of a loop body with its latencies, find its critical path, rank its instructions by priority and fill the paper's Memory / Floating Point / Integer table cycle by cycle — with the four numbers the exercise asks for.
The paper of 8 January 2026, solved on pages 489–492 of the notes, hands you nine instructions and a VLIW that can start three of them in every cycle. The table you will fill at the end of this lesson is 14 cycles long: 42 issue slots, of which 9 are used. The other 33 stay empty — and the paper forbids the two tricks that would fill them.
Two questions. Is 14 the best any scheduler can get out of this machine? And the machine is a single sentence: one memory unit with a latency of 3, one FP unit with 3, one integer unit with 1 (2 into a branch), one delay slot. If you could change one number in that sentence, which change would buy a cycle — and which would buy nothing at all?
Half of the first question needs no table at all. A graph of the nine instructions, weighted with their latencies, sets a floor that no number of units can push down; the distance from that floor to 14 is what this machine's units cost. You will measure it.
One loop body, one graph
Lesson 19 left the compiler of a VLIW with one job: pack independent operations into bundles without changing what the program computes. The unit it works on is the basic block.
Basic block — a straight-line sequence of instructions with one entry (no jump lands in its middle) and one exit (only its last instruction may branch). If its first instruction runs, all of them run, in order. That is what makes reordering them safe (p. 276).
The body of every loop in the exam papers is one basic block, and the BNE that closes it is its exit. Here is the January 2026 body as the notes print it (p. 489), without its dollar signs: that paper and the July 2024 one write $F2, the June 2025 paper mixes both, the June 2026 paper writes F2 — and so does this notebook.
FOR: LD F2, VB(R6) # I0
FADD F4, F2, F6 # I1
SD F4, VA(R7) # I2
LD F4, VC(R6) # I3
SD F4, VC(R7) # I4
FADD F8, F8, F8 # I5
ADDUI R6, R6, 4 # I6
ADDUI R7, R7, 4 # I7
BNE R7, R8, FOR # I8
The compiler turns the block into a dependence graph, a DAG: one node per instruction, one edge from I to J whenever J must wait for I (p. 277). The edges are the three dependences of lesson 7, and on the exam's machine each kind has its own timing rule. Write the latency on the edge: it is the number the scheduler will use.
- RAW, the true dependence:
I1reads F2, whichI0writes. The value does not exist before the producer's latency has passed, so the consumer goes at the producer's cycle plus the latency: 3 after a load, 3 after an FP operation, 1 after an integer operation — and 2 when the consumer is the branch. - WAR, the anti-dependence:
I3writes F4, whichI2still has to read. Only the name is shared — another destination register would remove the edge (renaming, lesson 15), but in the exam table you only reorder, so it stays. The paper says that "it is possible to read and write at the same address at the same clock cycle", so the writer may share the cycle of its last reader — never come before it. - WAW, the output dependence:
I1andI3both write F4, and the second result must land after the first. In the papers' loops this never binds: RAW and WAR edges already order every such pair.
Two facts about the branch close the rule book. The BNE is the exit, so nothing is scheduled after it. And the branch is resolved in ID, with one delay slot: the cycle after the BNE belongs to the iteration, because the next iteration cannot start before it. Fig. 1 shows the three rules where you will meet them — in the exam table itself.
FADD F8 touches no other instruction). Solid orange arrows are RAW: the FADD goes three rows below its load, the BNE two rows below its ADDUI. The dashed arrow is a WAR on R7: the store reads R7 and the ADDUI overwrites it in the same cycle, which the register file allows. C14 is the delay slot — empty, but part of the iteration.Now draw all the edges of the January loop at once (Fig. 2). Read it as a set of promises: a solid arrow with a 3 says that the instruction at its head goes at least three cycles after the one at its tail; a dashed arrow says no earlier than its tail. Two things can be read off the picture (p. 276): which instructions are independent — no path joins them, so they may share a cycle if the units allow — and the longest chain, which limits everything else. I5 floats free: nobody else touches F8.
I0 in C1, I1 in C4, I2 and I3 in C7, I4 and I7 in C10, the BNE in C12, the delay slot in C13.The notes draw the same kind of graph for their Example 1 (p. 278), the loop of the paper of 11 February 2025, numbered from I1 there. Its machine differs in one number: the FP unit has a latency of 2.
I1 LOOP: LD F1, A(R1)
I2 LD F2, A(R2)
I3 LD F3, A(R3)
I4 FADD F1, F1, F2
I5 FADD F2, F2, F3
I6 FMUL F1, F1, F2
I7 FADD F3, F3, F3
I8 ADDUI R2, R1, 8
I9 ADDUI R3, R1, 8
I10 SD F1, B(R1)
I11 ADDUI R1, R1, 4
I12 BNE R1, R6, LOOP
Its graph has almost as many dashed edges as solid ones: nine RAW, eight WAR. R1 is read by I1, I8, I9 and I10 before I11 overwrites it, so four WAR edges converge on I11; two loads and two FADDs are the readers of the other four. Find them yourself, then compare with the playground of section 3, which draws every edge.
The longest path: a floor no machine can beat
Forget the machine for a moment and give it as many units as it wants. Every instruction can then start the moment its incoming edges allow, and the block lasts exactly as long as its heaviest chain of edges. That chain is the critical path. A kitchen knows the idea: if the sauce simmers three minutes before it goes on the plate, a second cook does not make it simmer faster.
The notes compute it node by node, from the sources down (eq. 65, p. 277; the notes write "Longest Path" and "Latency" in full). Take their Example 2: seven operations, no registers, latencies 3 for a multiply, 1 for an add or subtract, 2 for the store.
- \(\text{LP}(i)\)
- longest path from the start to \(i\), included: the earliest cycle in which \(i\) can finish
- \(\text{pred}(i)\)
- the instructions with an edge into \(i\); for a source, \(\text{LP}(i) = \text{Lat}(i)\)
- \(\text{Lat}(i)\)
- latency of \(i\) in cycles
- \(\text{CP}\)
- critical path: the largest \(\text{LP}\), reached at a sink
The left panel of Fig. 3 runs it on Example 2: a = 3, b = c = 1, e = 2, d = 3 + 3 = 6, f = max(2, 1) + 2 = 4, g = max(6, 4) + 1 = 7. The critical path is 7 cycles, along a → d → g. Why is that number a floor for every machine, and not just a property of this drawing? The stepper proves it.
Call \(t(i)\) the cycle in which \(i\) is issued and \(\ell(p \to i)\) the latency on an edge (0 on a dashed one). Every schedule, on any machine, must respect every edge:
\[ t(i) \ge t(p) + \ell(p \to i) . \]Follow a path \(p_0 \to p_1 \to \dots \to p_k\) and add its inequalities: the intermediate terms cancel.
\[ t(p_k) \ge t(p_0) + \textstyle\sum_j \ell_j . \]The first instruction issues in cycle 1 at the earliest, and the last one still needs its own latency: the block lasts at least
\[ 1 + \textstyle\sum_j \ell_j + \text{Lat}(p_k) - 1 , \]which is the weight of the path as eq. 65 counts it.
This holds for every path, so for the heaviest one: any schedule lasts at least CP cycles. Extra units only remove reasons to wait; they remove no edge.
With unlimited units each instruction can go exactly at \(\max_p [\,t(p) + \ell(p \to i)\,]\). That schedule meets the bound, so the bound is tight: with unlimited units the block takes exactly CP cycles.
The schedule of the last step has a name: ASAP, as soon as possible (p. 279). It schedules every operation in the first cycle its operands allow and ignores resources — the notes compare it to planning a trip as if every traffic light were green (p. 280). On a real machine it is rarely a legal schedule — it may ask for more units than exist — but it is precious as a measuring stick: it gives the lower bound, the earliest cycle of every instruction and, early in the compiler, a first estimate of how much parallelism there is to exploit. Here is Example 2 under ASAP, with the unit columns of the notes; the ready list holds the instructions whose operands are available in that cycle.
| Cycle | Ready list | ALU1 | ALU2 | L/S | MUL |
|---|---|---|---|---|---|
| 1 | a, b, c | b | c | a | |
| 2 | e | e | a | ||
| 3 | f | f | a | ||
| 4 | d | f | d | ||
| 5 | d | ||||
| 6 | d | ||||
| 7 | g | g |
The scheduler of the next section needs the opposite number. Not "how long did it take to reach this instruction", but "how long does the graph still run after it". That is the priority of list scheduling: the longest path from the instruction to a sink, computed backwards, from the sinks up (p. 281).
- \(\text{succ}(i)\)
- the instructions with an edge out of \(i\); for a sink, \(\text{prio}(i) = \text{Lat}(i)\)
- \(\text{prio}(i)\)
- the cycles the block still needs from the issue of \(i\) to its end; the higher, the more urgent
Looking back: longest path from the start, eq. (65)
Looking ahead: longest path to the end, the priority
On the exam machine one detail changes: the latency belongs to the edge, not to the node. An ADDUI that feeds a store counts 1; the same ADDUI feeding the BNE counts 2. The branch itself weighs 2 — its own cycle plus the delay slot — and every other instruction must be issued no later than the branch. With these weights, I0 of the January loop gets priority 13: the floor of the puzzle, the highlighted chain of Fig. 2.
List scheduling, one cycle at a time
A real machine has a fixed number of units, and that is all list scheduling adds to ASAP (p. 281). In every cycle it collects the ready set — the instructions whose operands are available in that cycle — and hands the free units to the most urgent ones. The bookkeeping of who occupies which unit in which cycle is the resource reservation table.
- Build the dependence graph and compute every priority.
- Start at cycle 1. An instruction is ready when every predecessor is placed early enough: RAW at least \(\ell\) cycles before, WAR in this cycle or before.
- While a unit is free and something ready fits it, take the highest-priority instruction (ties: program order) and place it in this cycle. Placing it may make a WAR successor ready in the same cycle.
- Move to the next cycle; repeat until every instruction is placed.
The algorithm is greedy: it never revisits a choice, so it is a heuristic for a problem that is hard in general, not an optimiser. With the longest-path priority it finds the shortest schedule on most graphs of exam size — the June 2025 loop of the exam questions is the exception that shows why it is called a heuristic. Here is the notes' Example 3: the graph of Example 2 on one ALU, one L/S unit and one MUL.
| Cycle | Ready set (priority) | ALU1 | L/S | MUL |
|---|---|---|---|---|
| 1 | a (7), b (5), c (4) | b | a | |
| 2 | c (4), e (4) | c | a | |
| 3 | e (4) | e | a | |
| 4 | d (4), f (3) | f | d | |
| 5 | f | d | ||
| 6 | d | |||
| 7 | g (1) | g |
Cycle 1: a takes the MUL; b (5) beats c (4) for the only ALU. Cycle 2: c and e tie at 4 and program order picks c. Cycle 4: d and f each have a unit of their own. The VLIW code, one bundle per row, reads {b, a}, {c}, {e}, {f, d}, two empty bundles, {g}: 7 operations in 21 slots. The schedule is 7 cycles long — the critical path. One ALU instead of two cost nothing, because the ALU operations have slack while the chain through the MUL is critical.
The playground runs the same algorithm on three graphs: the notes' Examples 2–3, their Example 1 (the February 2025 loop) and the January 2026 loop. Change the machine and watch two numbers: the floor and the schedule.
The exam table: conventions, a forced schedule, four numbers
Exercise 2 of the papers of July 2024, June 2025 and June 2026 is this algorithm on paper, worth 5 points: "complete the following table by using the list-based scheduling", one row per cycle, one column per unit — Memory Unit, Floating Point Unit, Integer Unit — with the first instruction already written in C1. The texts give the machine; the rest is the convention of the solutions worked out in the notes (pp. 491–492 and 561), which this notebook follows. Keep all of it on your formula sheet:
- Units are fully pipelined: each takes one new instruction per cycle, and the three units are the three issue slots of a bundle.
- RAW: the consumer goes at the producer's cycle + 3 after a load or an FP operation, + 1 after an integer operation, + 2 when the consumer is the branch.
- WAR: the writer may share the cycle of the last reader of its register, never come before it. WAW: the second writer's result lands later.
- The
BNEis the last instruction. The row after it is the delay slot: write "(delay slot)", count it, put nothing in it. - No NOPs are written: an empty cell stays empty. No unrolling, no software pipelining, no change of offsets or indexes: you only reorder.
- The answers: the critical path for a single iteration is the number of cycles of your table, delay slot included; CPI = cycles / instructions; FP ops per cycle = FP operations / cycles; code efficiency = instructions / (cycles × issue width).
Why two cycles from an integer operation to the branch, but one to anything else?
The paper says the branch is "solved in ID stage". An ordinary consumer needs its operand when it reaches EX; the branch needs it one stage earlier, in ID. So the result of the ADDUI, usable by an ordinary instruction issued one cycle later, is usable by the branch only one cycle after that. The same reasoning explains the delay slot: by the time the branch is decided in ID, the next bundle has already been fetched, and it executes — the solutions in the notes keep that slot empty and count it.
Now the January 2026 loop on its machine. The stepper places the instructions in the order list scheduling meets them; every placement on the chain is forced.
C1. Ready:
I0(priority 13) andI5(2).I6is not ready although nothing feeds it a value: it overwrites R6, whichI3has not read yet.I0takes the Memory Unit,I5the FP unit.C4.
I1needs F2 from the load: C1 + 3. Nothing else is ready in C2 and C3, so they stay empty.C7.
I2needs F4 fromI1: C4 + 3.I3is ready in C7 too — its WAR withI2allows the same cycle — but the single Memory Unit is taken.C8.
I3takes the Memory Unit;I6, whose last reader of R6 isI3, joins it in the Integer Unit. C9 and C10 wait for the load.C11.
I4stores the F4 just loaded: C8 + 3.I7shares the cycle:I4is the last reader of R7.C13. The
BNEneeds R7 fromI7: C11 + 2. C14 is the delay slot. The iteration is 14 cycles long.
| Memory Unit | Floating Point Unit | Integer Unit | |
|---|---|---|---|
| C1 | LD F2, VB(R6) | FADD F8, F8, F8 | |
| C2 | |||
| C3 | |||
| C4 | FADD F4, F2, F6 | ||
| C5 | |||
| C6 | |||
| C7 | SD F4, VA(R7) | ||
| C8 | LD F4, VC(R6) | ADDUI R6, R6, 4 | |
| C9 | |||
| C10 | |||
| C11 | SD F4, VC(R7) | ADDUI R7, R7, 4 | |
| C12 | |||
| C13 | BNE R7, R8, FOR | ||
| C14 | (delay slot) | ||
| C15 | |||
Two instructions have freedom, because nothing waits for them: I5 may sit in any free FP cell up to C13, and I6 in C8, C9, C10 or C12. Moving them changes no number. The paper's four questions, answered as you would write them:
"The critical path is 13: that is the longest path of the graph." On the paper the question reads "How long is the critical path for a single iteration?", and all three VLIW solutions in the notes (pp. 492, 512, 561) answer with the length of the table, delay slot included: 14 here. The 13 is the floor with unlimited units. Write 14; add the floor in one line if you like, saying which is which. And do not justify the 14 with a write-back of the BNE: a branch writes nothing — C14 is its delay slot.
The February 2025 loop (Example 1), on the same machine but with an FP latency of 2, gives 13 cycles:
BNE R1, R2, LOOP; the code compares R1 with R6.| Memory Unit | Floating Point Unit | Integer Unit | |
|---|---|---|---|
| C1 | LD F1, A(R1) | ||
| C2 | LD F2, A(R2) | ADDUI R2, R1, 8 | |
| C3 | LD F3, A(R3) | ADDUI R3, R1, 8 | |
| C4 | |||
| C5 | FADD F1, F1, F2 | ||
| C6 | FADD F2, F2, F3 | ||
| C7 | FADD F3, F3, F3 | ||
| C8 | FMUL F1, F1, F2 | ||
| C9 | |||
| C10 | SD F1, B(R1) | ADDUI R1, R1, 4 | |
| C11 | |||
| C12 | BNE R1, R6, LOOP | ||
| C13 | (delay slot) | ||
The three loads queue on one memory port (C1–C3), and each ADDUI writing R2 or R3 waits for the load that reads it. I4 needs F2 from C2: C5. I5 needs F3 from C3: C6. I7 fills C7 while FMUL waits for I5's F2 until C8. The answers: critical path 13 cycles; code efficiency = 12 / (13 × 3) = 12/39 = 30.77 %. With unlimited units the three loads would share C1 and the floor is 11: here the machine costs two cycles.
The trainer below is the exam table with the rules built in. Pick an instruction, then a cell; an illegal placement turns red and the note says which rule it breaks. When the table is complete it computes the four numbers and explains every empty cell.
Back to the puzzle. The floor of the January loop is 13 cycles: the chain of Fig. 2, which ASAP reaches when I2 and I3 share C7 (Fig. 4, left). The paper's machine has one Memory Unit, so I3 slips to C8 and the whole tail with it: 14 (right). And 14 is the best this machine allows. I3 may not go before I2 (WAR on F4) nor with it (one memory unit), so it lands in C8 at the earliest, and every later step of the chain is forced.
Unlimited units (ASAP): 13 cycles, the floor
One Memory Unit, as in the paper: 14 cycles
I2 and I3 share C7, and the delay slot ends the iteration in C13 — the floor. Right: the paper's single Memory Unit is taken by I2 in C7, so I3 waits until C8, and I4, I7, the BNE and the delay slot follow one cycle later. Thick outlines: the critical chain. The dashed ochre line is the floor: on the right, the delay slot falls one row below it.Now change one number of the machine sentence. A second Memory Unit gives 13: the gap closes, and nothing more can be gained by adding units. A memory latency of 2 gives 12, an FP latency of 2 or an integer-to-branch latency of 1 give 13: each of them shortens an edge of the chain, so it moves the floor itself. A second FP unit or a second integer unit gives nothing: I5 and the two ADDUI instructions never wait for their unit. Units can only close the distance to the floor; latencies move the floor.
Local and global scheduling: the map of the next lesson
Everything so far was local scheduling: it works inside one basic block, where control flow is a straight line and the compiler knows every instruction and every dependence (p. 283). Its goal is to fill the slots with real instructions instead of NOPs. Its limit is the block itself: instructions cannot cross the block's boundaries, so the parallelism is bounded by the block's size and its chains — nine instructions, one long chain, 33 empty slots.
Global scheduling works across blocks — across branches, across loop boundaries, along whole paths of control flow. It finds more parallelism and hides long latencies, at a price: control dependences must be preserved too, moves that guess wrong need compensation code, and the compiler becomes more complex and more expensive to build. The notes sort the techniques of the chapter in Table 23 (p. 284):
| Technique | Type | What it does | Where |
|---|---|---|---|
| Loop unrolling | Local | replicates the loop body to enlarge the basic block and expose ILP | lesson 21 |
| Software pipelining | Local | reorganises instructions of different iterations to overlap them | lesson 21 |
| Trace scheduling | Global | predicts a frequent path (a trace) and schedules it aggressively | lesson 21 |
| Superblock scheduling | Global | extends trace scheduling: a single entry, several exits | lesson 21 |
ASAP and list scheduling sit under all four: each technique reshapes the code, and then a list scheduler fills the bundles. The exam's Exercise 2 forbids the first two by name — "do NOT introduce any software pipelining, loop unrolling and modifications to loop indexes" — so the exercise tests the list scheduler alone. The June 2025 and June 2026 papers then hand you a loop that has already been software-pipelined and ask you to schedule it as it is: two of the exercises below.
Exam-style questions
Explain the main concepts and benefits of each one of the following techniques used by a compiler to schedule code statically for a VLIW processor:
- Basic block
- Dependence graph
- Critical path
- ASAP scheduling
- List-based scheduling
- Priority based on the longest path to the sink
- Resource reservation table
- Local versus global scheduling
Show model answer
- A straight-line sequence of instructions with one entry and one exit (only the last instruction may branch). All its instructions execute together, so the compiler can reorder them freely as long as the data dependences hold. It is the unit of local scheduling; its limit is its size — a few instructions between branches.
- A DAG with one node per instruction and one edge per dependence: RAW (true), WAR and WAW (name dependences). RAW edges carry the producer's latency. It shows what may run in parallel (unconnected nodes) and what must wait, and it is the input of every scheduler.
- The longest latency-weighted path from a source to a sink, LP(i) = max LP(p) + Lat(i). It is the minimum execution time of the block even with unlimited resources: no schedule can beat it. In the exam's VLIW exercise, "critical path for a single iteration" is answered with the length of the scheduled iteration, delay slot included.
- Each operation is scheduled in the first cycle in which its operands are available, ignoring resources. It gives the lower bound (= the critical path) and the earliest cycle of every operation; it is not a valid schedule on a real machine, because it can use more units than exist.
- ASAP plus resource constraints: in every cycle the ready instructions (operands available) are sorted by priority and the highest ones take the free units. It is greedy and fast, close to optimal on small graphs, and it is what a VLIW compiler — and the exam table — uses.
- The priority of an instruction is the longest path from it to the end of the block, computed backwards from the sinks. Instructions with a long chain still ahead go first; instructions with slack fill the holes; ties are broken in program order.
- A table of cycles × functional units recording which unit is busy in which cycle. The scheduler checks it before placing an instruction, so no unit is oversubscribed (with fully pipelined units: one new instruction per unit per cycle). Read row by row, it gives the bundles.
- Local scheduling works inside one basic block (ASAP, list scheduling; unrolling and software pipelining reshape a loop body); it is simple but limited by the block. Global scheduling moves instructions across branches (trace and superblock scheduling): more ILP, at the cost of compensation code and a more complex compiler.
The 2026 paper asks eight sub-points of this kind with five lines each, on superscalar techniques; this one transposes the form to the static side. The question asks for "main concepts and benefits": give each item a definition plus one benefit or limit. Sub-point 3 is the delicate one: say that the critical path is a resource-independent lower bound, and if you mention the exam table, say that there the iteration length is meant.
Let's consider the following LOOP code, where Ri are integer registers and Fi are floating-point registers.
LOOP: LD F2, 0(R1)
LD F4, 0(R2)
FADD F4, F4, F4
FADD F6, F2, F2
FADD F8, F0, F0
SD F6, 0(R1)
SD F8, 0(R2)
ADDUI R1, R1, 4
ADDUI R2, R2, 4
BNE R1, R3, LOOP
Given a 3-issue VLIW machine with fully pipelined functional units: 1 Memory Unit with 3 cycles latency; 1 FP ALU with 3 cycles latency; 1 Integer ALU with 1 cycle latency to next Int/FP/L/S & 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 loop, complete the table (columns Memory Unit, Floating Point Unit, Integer Unit; rows C1 to C15; C1 is given: LD F2, 0(R1)) by using the list-based scheduling (do NOT introduce any software pipelining, loop unrolling and modifications to loop indexes) including the BRANCH DELAY SLOT. Please do not write in NOPs. How long is the critical path for a single iteration? How much is the code efficiency for a single iteration?
Show model answer
Dependences: LD F2 → FADD F6 (RAW F2, 3) → SD F6 (RAW F6, 3); LD F4 → FADD F4 (RAW F4, 3); FADD F8 → SD F8 (RAW F8, 3); ADDUI R1 after LD F2 and SD F6 (WAR R1, same cycle allowed); ADDUI R2 after LD F4 and SD F8 (WAR R2); BNE at least 2 cycles after ADDUI R1.
| Memory Unit | Floating Point Unit | Integer Unit | |
|---|---|---|---|
| C1 | LD F2, 0(R1) | FADD F8, F0, F0 | |
| C2 | LD F4, 0(R2) | ||
| C3 | |||
| C4 | SD F8, 0(R2) | FADD F6, F2, F2 | ADDUI R2, R2, 4 |
| C5 | FADD F4, F4, F4 | ||
| C6 | |||
| C7 | SD F6, 0(R1) | ADDUI R1, R1, 4 | |
| C8 | |||
| C9 | BNE R1, R3, LOOP | ||
| C10 | (delay slot) | ||
Critical path for a single iteration: 10 cycles (LD F2 → FADD F6 → SD F6 / ADDUI R1 → BNE → delay slot). Code efficiency = 10 / (10 × 3) = 0.33. (CPI = 10/10 = 1; FP ops per cycle = 3/10 = 0.3.)
This schedule reaches the floor: with unlimited units the chain is also 10 cycles, so the single units cost nothing. Three traps. FADD F4, F4, F4 produces a value nobody uses in the iteration — schedule it anyway. ADDUI R2 cannot go before SD F8, which still reads R2. The printed text says "ADDDUI" and, in one sentence, "4-issue": the machine and the table are 3-issue, so divide by 3. Try it first in the trainer (preset July 2024).
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 & 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 (columns Memory Unit 1, Memory Unit 2, Floating Point Unit, Integer Unit; C1 is given: SD F6, 0(R1)) by using the list-based scheduling (do NOT introduce any loop transformation) including the BRANCH DELAY SLOT. Please do not write in NOPs. How long is the critical path for a single iteration? What performance did you achieve in CPI? What performance did you achieve in FP ops per cycles? How much is the code efficiency?
Show model answer
Inside the body there is no RAW chain except ADDUI R1 → BNE: each SD stores a value computed in the previous iteration, each FADD reads a value loaded in the previous iteration. Only WAR edges remain (SD F6 before FADD F6, FADD F6 before LD F0, the SD and LD of each pointer before its ADDUI), and the same cycle is allowed for all of them.
| 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 for a single iteration: 5 cycles. CPI = 5/13 ≈ 0.38. FP ops per cycle = 3/5 = 0.6. Code efficiency = 13 / (5 × 4) = 0.65.
The bottleneck is the single Integer ALU: three ADDUIs and the BNE, the BNE last, so the BNE cannot be before C4 — and it reaches C4 only if each ADDUI goes as early as possible, which means pairing each SD and LD with its own pointer, cycle by cycle. The longest-path priority of section 3 does not see this. After C1 every remaining instruction has priority 2, program order sends SD F8 and SD F10 together to the two Memory Units in C2, and LD F2 with ADDUI R2 slips to C3, ADDUI R3 to C4, the BNE to C5: 6 cycles (CPI 6/13 ≈ 0.46, efficiency 13/24 ≈ 0.54), a legal list schedule — and so is plain program order, which also ends at 6. Hand in the 5, with one line on the tie-break. Why the loop looks like this (iteration i is stored, i + 1 computed, i + 2 loaded, hence the offset 16) is lesson 21; here you only schedule it.
Let's consider the two following loops LOOP1 and LOOP2, where Ri are integer registers and Fi are floating-point registers:
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
Given a 3-issue VLIW machine with fully pipelined functional units: 1 Memory Unit with 3 cycles latency; 1 FP ALU with 3 cycles latency; 1 Integer ALU with 1 cycle latency to next Int/FP/L/S & 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 each loop, complete the following tables (rows C1 to C10; C1 is given: LD F2, 0(R1) for LOOP1 and SD F6, 0(R2) for LOOP2) by using the list-based scheduling (do NOT introduce any software pipelining, loop unrolling and modifications to loop indexes) on the 3-issue VLIW machine including the BRANCH DELAY SLOT. Please do not write in NOPs. For LOOP1 and LOOP2: How long is the critical path for a single iteration? What performance did you achieve in CPI? What performance did you achieve in FP ops per cycles? How much is the code efficiency? How much is the speed up in terms of CPI?
Show model answer
| LOOP1 | 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) | ||
| LOOP2 | 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 | |
|---|---|---|
| Critical path | 10 cycles | 5 cycles |
| CPI | 10/5 = 2 | 5/5 = 1 |
| FP ops per cycle | 1/10 = 0.1 | 1/5 = 0.2 |
| Code efficiency | 5/(10 × 3) ≈ 0.17 | 5/(5 × 3) ≈ 0.33 |
| Speed-up in CPI | 2 / 1 = 2 | |
LOOP1 is one RAW chain, LD → FADD → SD, plus the branch: 10 cycles, all forced. In LOOP2 the same three operations have only WAR edges between them: SD reads the old F6 before FADD overwrites it, FADD reads the old F2 before LD overwrites it. SD and FADD share C1; the LD waits for the single Memory Unit (C2), the ADDUI follows its last reader (C2), the BNE is two cycles later. The same five instructions, half the cycles — the before and after of software pipelining that lesson 21 builds. The paper prints "ADDDUI" for ADDUI.
The dependence graph of a basic block has a critical path of 7 cycles. Which statement is true for any list-based schedule of the block on a machine with one functional unit of each type? (SINGLE ANSWER)
Show model answer
Answer 2. The critical path is a resource-independent lower bound; resources and priorities can only make the schedule equal to it or longer.
A single-answer quiz in the style of the paper's block. The trap is Answer 1, which is what students conclude after seeing a few schedules that reach the floor.
The January 2026 loop needs 14 cycles per iteration on the 3-issue VLIW of the paper (1 Memory Unit, 1 FP ALU, 1 Integer ALU; latencies 3, 3, 1 and 2 to the branch). Which of the following single changes of the machine would shorten the schedule? (MULTIPLE ANSWERS)
Show model answer
Answers 1, 4 and 5: they give 13, 12 and 13 cycles. Answers 2 and 3 leave 14: the units they add are never waited for.
The question tests whether you know where the critical path runs and which constraints are latencies (they move the floor) and which are resources (they can only close the gap to the floor). The trainer above lets you verify each case.
True or False: the longest path of the dependence graph of a basic block, computed with the latencies of the machine, becomes shorter when a second memory unit is added to the machine.
Show model answer
False. LP(i) = max LP(p) + Lat(i) contains latencies and edges only; the number of functional units changes the list schedule, not the critical path.
A true/false quiz, one point. Keep apart the two meanings of "critical path": the graph's longest path (machine-independent) and, in the exam's VLIW exercise, the length of your scheduled iteration.
Check yourself
A load is in C3 on a Memory Unit with latency 3. In which cycle can the FADD that uses its result go, at the earliest?
In C1 of the January 2026 loop, the Integer Unit is free and ADDUI R6, R6, 4 has no RAW predecessor. Why is it not placed there?
In the graph a–g of the notes (Examples 2–3), which operation does the list scheduler treat as the most urgent in cycle 1?
Which statements about the critical path are true? Select all that apply.
Two numbers, two names: the floor of the graph and the length of your table. Know both, and say which one you write.
An iteration has 9 instructions and a 14-cycle schedule on a 3-issue VLIW, delay slot included. What is its code efficiency?
In the notes' Example 3, one ALU instead of two costs nothing: the schedule stays at 7 cycles. Why?
Key take-aways
- A basic block — one entry, one exit — becomes a dependence graph: RAW edges carry the producer's latency, WAR edges allow the same cycle, WAW edges order the two writes.
- The longest path of the graph (eq. 65) is the critical path: a floor for every schedule on every machine, reached by ASAP when units are unlimited.
- List scheduling is ASAP plus units: in every cycle the ready set's most urgent instructions take the free units, and urgency is the longest path to the end, computed backwards.
- The exam table follows fixed conventions: latencies 3 / 3 / 1 and 2 into the branch, WAR in the same cycle, the BNE last, the delay slot counted and empty, no NOPs, no unrolling or software pipelining.
- Its four numbers for January 2026: critical path 14 cycles, FP ops per cycle 2/14 ≈ 0.14, code efficiency 9/42 ≈ 0.21, CPI 14/9 ≈ 1.56; February 2025: 13 cycles and 12/39 = 30.77 %.
- Units can only close the gap to the floor (January: 14 → 13 with a second memory unit); latencies move the floor itself.
What to watch and read
-
Modern Microprocessors: A 90-Minute Guide!
Jason R. C. Patterson (Lighterra)
Read the sections "Explicit Parallelism – VLIW" and "Instruction Dependencies & Latencies": the plain-English picture of why a compiler must know every latency before it can fill a bundle.
-
Introduction to Instruction Scheduling
Keith Cooper & Linda Torczon, adapted by Christophe Dubach for McGill COMP 520 (2024)
The dependence graph, the latency-weighted priority and the list-scheduling loop step by step, from the compiler side — the same algorithm as section 3, with a second set of small examples to run by hand.
-
Computer Architecture — Lecture 27: VLIW Architectures (Fall 2023)
Onur Mutlu, ETH Zürich
The static-versus-dynamic trade-off behind the whole chapter: lock-step execution, NOP padding, why exact latencies are part of the binary.
-
VLIW/EPIC: Statically Scheduled ILP
MIT 6.823, Arvind, Asanović, Emer (2005)
Loop unrolling, software pipelining and trace scheduling in one line of argument: the map of Table 23, as a preview of lesson 21.