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.

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.

Memory Floating Point Integer C1 C2 C3 C4 … C11 C12 C13 C14 LD F2, VB(R6) FADD F8, F8, F8 FADD F4, F2, F6 SD F4, VC(R7) ADDUI R7, R7, 4 BNE R7, R8, FOR (delay slot) 3 C1 + 3 = C4 R7 2 nothing ready
Fig. 1. The three rules in the exam table of January 2026 (rows C5–C10 cut out; the grey 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 LD F2 I5 FADD F8 I1 FADD F4 I2 SD F4 I3 LD F4 I6 ADDUI R6 I7 ADDUI R7 I4 SD F4 I8 BNE R7 delay slot 3 3 3 2 F4 F4 R6 R6 R7 R7 independent floor: 13 cycles
Fig. 2. The January 2026 loop as a dependence graph. Solid orange: RAW, with the latency in cycles. Dashed: WAR, which may share the cycle. Dotted: WAW. The highlighted chain sets the floor: 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) = \max_{p \,\in\, \text{pred}(i)} \text{LP}(p) + \text{Lat}(i) \] \[ \text{CP} = \max_i \ \text{LP}(i) \]
\(\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.

  1. 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) . \]
  2. 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 . \]
  3. 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.

  4. 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.

  5. 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.

Example 2 under ASAP (p. 280), corrected. Grey letters: the operation's latency is still running on its unit. Two cells differ from the notes: in cycle 3 the ALU1 column is empty, and in cycle 6 the MUL column holds d (wavy underline).
CycleReady listALU1ALU2L/SMUL
1a, b, cbca
2eea
3ffa
4dfd
5d
6d
7gg

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{prio}(i) = \text{Lat}(i) + \max_{s \,\in\, \text{succ}(i)} \text{prio}(s) \]
\(\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
a ×3 d ×6 b +1 e −2 f St4 g −7 c +1 3 3 1 1 2 1 the floor

Looking back: longest path from the start, eq. (65)

a ×7 d ×4 b +5 e −4 f St3 g −1 c +4 3 3 1 1 2 1 most urgent

Looking ahead: longest path to the end, the priority

Fig. 3. Two numbers on the same graph. Left, eq. 65: the cycle in which each operation finishes at the earliest; g carries 7, the critical path. Right, the priority: how many cycles the graph still runs from each operation on; a carries the same 7, seen from the start, and g carries 1. On the critical path, left + right − own latency = 7 in every node; elsewhere it is less, and the difference is the slack.

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.

  1. Build the dependence graph and compute every priority.
  2. 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.
  3. 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.
  4. 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.

Example 3 under list scheduling (p. 282), corrected, with the priorities of Fig. 3. Bold in the ready set: placed in that cycle. Grey: result latency still running. The L/S column holds f in cycles 4 and 5 (the notes print it in 3 and 4).
CycleReady set (priority)ALU1L/SMUL
1a (7), b (5), c (4)ba
2c (4), e (4)ca
3e (4)ea
4d (4), f (3)fd
5fd
6d
7g (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.

DAG and list-scheduling playground

Each node shows its name and, in the violet cell, its priority: the longest path from it to the end. Solid orange arrows are RAW, with the latency; dashed arrows are WAR, dotted ones WAW (a WAW that duplicates a RAW is not drawn); the highlighter marks the critical path, and starred boxes are copies drawn for readability, as in the notes. The table is the schedule on the machine set by the sliders: in the ready set, bold names are placed in that cycle and grey ones wait; "+k" means k cycles later than with unlimited units. The insight to take away: units can lengthen a schedule but never shorten it below the critical path, and the priority is the longest path to the end.

  1. Examples 2–3, one unit of each kind. The critical path is 7. Predict the length of the one-ALU schedule, then add ALUs: can the schedule drop below 7?
    What you should see

    7 with one ALU, 7 with two or three. The ALU operations b, c, e have slack, so a single ALU costs nothing; and no number of units goes below the critical path, because units remove waiting for a unit, never an edge.

  2. Still on Examples 2–3, set the MUL latency to 1. Predict the new critical path and the one-ALU schedule before you look.
    What you should see

    The critical path drops to 5 and moves: b → e → f → g, 1 + 1 + 2 + 1. With one ALU the schedule is 6: b, c, e and g all need the ALU, and f must wait for both c and e. Two ALUs bring it back to 5. A resource matters only when the critical path runs through it.

  3. Switch to the January 2026 loop. Read the priorities along the highlighted chain from the BNE upwards and predict the number of I0: each one is the next one plus the latency of the edge between them.
    What you should see

    I8 2 (its cycle and the delay slot), I7 2 + 2 = 4, I4 0 + 4 = 4, I3 3 + 4 = 7, I2 0 + 7 = 7, I1 3 + 7 = 10, I0 3 + 10 = 13 — the floor. The schedule on one unit of each kind is 14.

  4. Which slider closes the gap between 14 and 13? And does any slider push the schedule below the floor?
    What you should see

    Two memory units: I3 joins I2 in C7 and the schedule is 13. No units slider goes below 13. The latency sliders do change the result — memory latency 2 gives 12 — but they move the floor itself (to 11 in that case): latencies change the graph, units only the waiting.

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:

  1. Units are fully pipelined: each takes one new instruction per cycle, and the three units are the three issue slots of a bundle.
  2. 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.
  3. 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.
  4. The BNE is the last instruction. The row after it is the delay slot: write "(delay slot)", count it, put nothing in it.
  5. No NOPs are written: an empty cell stays empty. No unrolling, no software pipelining, no change of offsets or indexes: you only reorder.
  6. 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.

  1. C1. Ready: I0 (priority 13) and I5 (2). I6 is not ready although nothing feeds it a value: it overwrites R6, which I3 has not read yet. I0 takes the Memory Unit, I5 the FP unit.

  2. C4. I1 needs F2 from the load: C1 + 3. Nothing else is ready in C2 and C3, so they stay empty.

  3. C7. I2 needs F4 from I1: C4 + 3. I3 is ready in C7 too — its WAR with I2 allows the same cycle — but the single Memory Unit is taken.

  4. C8. I3 takes the Memory Unit; I6, whose last reader of R6 is I3, joins it in the Integer Unit. C9 and C10 wait for the load.

  5. C11. I4 stores the F4 just loaded: C8 + 3. I7 shares the cycle: I4 is the last reader of R7.

  6. C13. The BNE needs R7 from I7: C11 + 2. C14 is the delay slot. The iteration is 14 cycles long.

January 2026, Exercise 2: the completed table (pp. 489–492, recomputed). C15 stays empty.
Memory UnitFloating Point UnitInteger Unit
C1LD F2, VB(R6)FADD F8, F8, F8
C2
C3
C4FADD F4, F2, F6
C5
C6
C7SD F4, VA(R7)
C8LD F4, VC(R6)ADDUI R6, R6, 4
C9
C10
C11SD F4, VC(R7)ADDUI R7, R7, 4
C12
C13BNE 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:

\[ \text{critical path} = 14 \ \text{cycles} \] \[ \text{FP ops per cycle} = \tfrac{2}{14} \approx 0.14 \] \[ \text{code efficiency} = \tfrac{9}{14 \times 3} = \tfrac{9}{42} \approx 0.21 \] \[ \text{CPI} = \tfrac{14}{9} \approx 1.56 \]

"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:

February 2025, Exercise 2 (pp. 560–561, recomputed): 12 instructions, 3 units, 13 cycles. The notes' table prints the branch as BNE R1, R2, LOOP; the code compares R1 with R6.
Memory UnitFloating Point UnitInteger Unit
C1LD F1, A(R1)
C2LD F2, A(R2)ADDUI R2, R1, 8
C3LD F3, A(R3)ADDUI R3, R1, 8
C4
C5FADD F1, F1, F2
C6FADD F2, F2, F3
C7FADD F3, F3, F3
C8FMUL F1, F1, F2
C9
C10SD F1, B(R1)ADDUI R1, R1, 4
C11
C12BNE 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.

Exam-table trainer (VLIW)

The paper's grid: Memory Unit / Floating Point Unit / Integer Unit against C1, C2, … , with C1 given as in the paper. Click an instruction above the grid, then a cell of its unit; click a filled cell to pick its instruction up again. Red: a rule is broken. The sliders change the machine of the paper; "Show a list schedule" fills the grid the way the algorithm of section 3 does. The insight to take away: where every empty cell comes from, and which single parameter of the machine would shrink the schedule.

  1. January 2026, hints off. Fill the table yourself. Before you place I3: in which cycle would it go with two memory units, and where must it go with one?
    What you should see

    With two memory units, C7 next to I2: the WAR on F4 allows the same cycle. With one, C8 — and I4, I7, the BNE and the delay slot all move one row down with it. The iteration fills 14 of the 15 rows.

  2. When your table is complete, predict before you read the note: how many of the 42 slots are empty, and how many of them lie in rows where no unit works at all?
    What you should see

    33 empty slots. In the list schedule 7 whole rows are empty (C2, C3, C5, C6, C9, C10, C12 — 21 slots): nothing is ready there, every instruction is waiting for a latency. Add the delay slot (3) and 9 slots in rows where another unit works. Moving I5 or I6 changes how the 33 split, never the 33.

  3. Which single change of the machine shortens the January schedule? Predict for each slider, then try: two memory units, memory latency 2, FP latency 2, integer → branch latency 1.
    What you should see

    13, 12, 13 and 13 cycles. Every one of them touches the critical chain: the memory collision in C7, two load latencies, one FP latency, the branch latency. A second FP or integer unit (there is no slider for them) would give nothing: I5, I6 and I7 never wait for their unit.

  4. Switch to February 2025 and predict its length on its own machine (FP latency 2) before you ask for the list schedule. Then set two memory units.
    What you should see

    13 cycles, then 12. With two memory ports two loads share C1, but the third still waits until C2, so the floor of 11 is not reached; its loads are the bottleneck, as the note under the grid says.

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.

Mem FP Int Mem 2 C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12 C13 I0 I5 I1 I2 I3 I6 I4 I7 I8 delay slot floor

Unlimited units (ASAP): 13 cycles, the floor

Mem FP Int C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12 C13 C14 I0 I5 I1 I2 I3 I6 I4 I7 I8 delay slot floor +1 +1 +1 busy

One Memory Unit, as in the paper: 14 cycles

Fig. 4. The puzzle solved. Left: with unlimited units every instruction sits at its ASAP cycle; 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):

Table 23 of the notes, with a column for where each technique is taught.
TechniqueTypeWhat it doesWhere
Loop unrollingLocalreplicates the loop body to enlarge the basic block and expose ILPlesson 21
Software pipeliningLocalreorganises instructions of different iterations to overlap themlesson 21
Trace schedulingGlobalpredicts a frequent path (a trace) and schedules it aggressivelylesson 21
Superblock schedulingGlobalextends trace scheduling: a single entry, several exitslesson 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:

  1. Basic block
  2. Dependence graph
  3. Critical path
  4. ASAP scheduling
  5. List-based scheduling
  6. Priority based on the longest path to the sink
  7. Resource reservation table
  8. Local versus global scheduling
Show model answer
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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 UnitFloating Point UnitInteger Unit
C1LD F2, 0(R1)FADD F8, F0, F0
C2LD F4, 0(R2)
C3
C4SD F8, 0(R2)FADD F6, F2, F2ADDUI R2, R2, 4
C5FADD F4, F4, F4
C6
C7SD F6, 0(R1)ADDUI R1, R1, 4
C8
C9BNE 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 1Memory Unit 2Floating Point UnitInteger Unit
C1SD F6, 0(R1)LD F0, 16(R1)FADD F6, F0, F0ADDUI R1, R1, 8
C2SD F8, 0(R2)LD F2, 16(R2)FADD F8, F2, F2ADDUI R2, R2, 8
C3SD F10, 0(R3)LD F4, 16(R3)FADD F10, F4, F4ADDUI R3, R3, 8
C4BNE 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
LOOP1Memory UnitFloating Point UnitInteger Unit
C1LD F2, 0(R1)
C2
C3
C4FADD F6, F2, F2
C5
C6
C7SD F6, 0(R1)ADDUI R1, R1, 4
C8
C9BNE R1, R3, LOOP1
C10(delay slot)
LOOP2Memory UnitFloating Point UnitInteger Unit
C1SD F6, 0(R2)FADD F6, F2, F2
C2LD F2, 0(R2)ADDUI R2, R2, 4
C3
C4BNE R2, R3, LOOP2
C5(delay slot)
LOOP1LOOP2
Critical path10 cycles5 cycles
CPI10/5 = 25/5 = 1
FP ops per cycle1/10 = 0.11/5 = 0.2
Code efficiency5/(10 × 3) ≈ 0.175/(5 × 3) ≈ 0.33
Speed-up in CPI2 / 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)

  • Answer 1: it is always exactly 7 cycles long Only when the units never delay an instruction of the critical path; with the MUL latency at 1, the graph a–g of the notes needs 6 cycles on one ALU against a critical path of 5.
  • Answer 2: it is never shorter than 7 cycles The critical path is the length with unlimited resources: fewer resources can only add waiting.
  • Answer 3: it is never longer than 7 cycles Busy units can delay instructions of the critical path: the January 2026 loop has a floor of 13 and needs 14 on one Memory Unit.
  • Answer 4: its length does not depend on the priority function List scheduling is greedy: the order in which it serves the ready set can change the length. Two loads are ready in C1, one at the head of the critical chain, one whose value nobody waits for: serve the second first and the whole chain slips by a cycle.
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)

  • Answer 1: a second Memory Unit LD F4, VC(R6) can then share C7 with SD F4, VA(R7): 13 cycles, the floor.
  • Answer 2: a second FP ALU The FP unit is never the bottleneck: FADD F8 already sits in C1 and FADD F4 waits for its operand, not for the unit. Still 14.
  • Answer 3: a second Integer ALU The two ADDUIs never wait for the unit, and the BNE waits for R7. Still 14.
  • Answer 4: a Memory Unit with 2 cycles latency Two load latencies lie on the critical chain (LD F2 → FADD, LD F4 → SD): 12 cycles.
  • Answer 5: an FP ALU with 2 cycles latency FADD F4 → SD F4 is on the chain: 13 cycles.
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.

  • True The longest path is computed with unlimited resources: the number of units does not appear in it. A second unit can shorten the schedule, never the critical path.
  • False Correct: only latencies and dependences enter the longest path. Units change how close a real schedule gets to it.
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?

  • C6 Producer's cycle + latency: 3 + 3. C4 and C5 stay empty — two empty rows, not three.
  • C7 That would be three empty rows after the load: latency 3 means the consumer goes three rows below, not three rows plus one.
  • C4 The next cycle is enough only after an integer operation (latency 1).
  • C3 The same cycle is allowed for a WAR, never for a RAW: the value does not exist yet.

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?

  • It would overwrite R6 before LD F4, VC(R6) reads it A WAR: the writer may share the cycle of its last reader, but the reader I3 is placed only in C8.
  • It needs F2 from the first load ADDUI reads only R6; the edge from I0 is a WAR on R6, not a RAW on F2.
  • The Integer Unit is reserved for the branch The unit takes any integer instruction in any cycle; the BNE simply has to be the last one.
  • Nothing prevents it: any free slot of its unit will do The WAR does: I3 reads R6 in C8, so the ADDUI goes in C8 or later. Moving it up anyway would need new offsets, and the paper forbids "modifications to loop indexes".

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?

  • a, priority 7 It starts the longest chain still ahead: a → d → g, 3 + 3 + 1.
  • g, the deepest node g has priority 1: nothing follows it. "Deeper means more urgent" is the slip of p. 279.
  • b, because its chain runs through the store b's chain b → e → f → g weighs 1 + 1 + 2 + 1 = 5, less than a's 7.
  • c, because it comes first in the reservation table c has priority 4 and loses the ALU to b in cycle 1.

Which statements about the critical path are true? Select all that apply.

  • No schedule of the block, on any machine, is shorter than it Every path is a chain of inequalities t(i) ≥ t(p) + ℓ that no unit removes.
  • ASAP reaches it when units are unlimited ASAP puts every instruction at the earliest cycle its edges allow, which is exactly the bound.
  • In the exam's VLIW table, "critical path for a single iteration" is answered with the length of the table, delay slot included That is the convention of the notes' solutions: 14 for January 2026, where the graph's own longest path is 13.
  • It counts instructions, not cycles It is weighted by latencies: LD → FADD weighs 6 cycles with two instructions.
  • Adding a unit of the right kind always shortens it Units never appear in the longest path; they only bring a real schedule closer to it.

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?

  • 9/42 ≈ 0.21 Instructions over issue slots: 9 / (14 × 3).
  • 9/14 ≈ 0.64 That is instructions per cycle, the IPC: it ignores the issue width.
  • 14/9 ≈ 1.56 That is the CPI, cycles per instruction.
  • 9/39 ≈ 0.23 13 × 3 forgets the delay slot, which belongs to the iteration.

In the notes' Example 3, one ALU instead of two costs nothing: the schedule stays at 7 cycles. Why?

  • The ALU operations have slack: the critical path runs through the MUL b, c and e can be spread over cycles 1–3 without delaying d or g; make the MUL faster and the ALU becomes the bottleneck.
  • ASAP ignores resources True of ASAP, but Example 3 is a list schedule that respects one ALU.
  • List scheduling always reaches the critical path Not always: with the MUL latency at 1, one ALU gives 6 cycles against a critical path of 5.
  • The store also runs on the ALU The store uses only the L/S unit — that is one of the corrections of p. 280.

Key take-aways

What to watch and read