Exercises — Out-of-order execution
Parent: 5.3.2 Out-of-order execution · Related: 5.1.01-Pipelining, 5.3.01-Superscalar-architecture, 5.3.03-Speculative-execution, 5.3.04-Register-renaming, 6.2.03-Branch-prediction, 7.1.01-Instruction-level-parallelism.
Before starting, here is a tiny dictionary so no symbol is used before it is defined:
Level 1 — Recognition
L1.1 — Name the hazard
For each pair, say whether it is RAW, WAR, WAW, or none, and whether register renaming can remove it.
(a) I1: R1 = R2 + R3 I2: R4 = R1 + R5
(b) I1: R1 = R2 + R3 I2: R1 = R6 + R7
(c) I1: R4 = R1 + R5 I2: R1 = R6 + R7
(d) I1: R1 = R2 + R3 I2: R4 = R5 + R6
Recall Solution
- (a) RAW. I2 reads
R1, which I1 writes. This is a true data dependency — the value must actually be produced first. Renaming cannot remove it. - (b) WAW. Both write
R1. Only I2's write should survive. It is false (name reuse). Renaming removes it: give I2 a fresh physical register. - (c) WAR. I1 reads
R1, then I2 overwritesR1. False — the reuse of the nameR1is the only conflict. Renaming removes it. - (d) None. Disjoint registers, fully independent, can execute in any order.
L1.2 — Order the lifecycle
Put these four OoOE phases in the correct order and mark which run in program order vs out of order: Execute, Fetch/Decode, Commit/Retire, Dispatch.
Recall Solution
- Fetch/Decode — in-order
- Dispatch (into reservation stations) — in-order
- Execute — out of order (fires when operands ready)
- Commit/Retire — in-order (through the ROB, to keep precise exceptions)
Memory hook: in — in — OUT — in. Only the middle execute stage is free to scramble; the two ends stay ordered so the programmer never notices.
Level 2 — Application
L2.1 — Rename to expose parallelism
Rewrite this code using fresh names T1, T2, … so that all false dependencies are gone. Then state which instructions can now execute in parallel.
I1: R1 = R2 + R3
I2: R4 = R1 * R6 ; uses R1
I3: R1 = R7 - R8 ; reuses R1
I4: R9 = R1 + R5 ; uses new R1
Recall Solution
Map each write of an architectural register to a new physical name, and each read to the most recent physical name written:
I1: T1 = R2 + R3
I2: R4 = T1 * R6 ; RAW on T1 -> must wait for I1
I3: T2 = R7 - R8 ; independent of everything above
I4: R9 = T2 + R5 ; RAW on T2 -> must wait for I3
- The
R1in I1/I2 becameT1; theR1in I3/I4 becameT2. The WAW between I1 and I3 (and the WAR between I2 and I3) vanish. - Two independent chains remain: {I1 → I2} and {I3 → I4}. They run in parallel.
- With unlimited ports and 1-cycle ops: I1 and I3 fire in cycle 1; I2 and I4 fire in cycle 2. Whole block finishes in 2 cycles instead of 4.
L2.2 — Ready condition
A reservation station holds I: R5 = src1 + src2, where src1 is tagged as coming from producer P8 (not yet done) and src2 = 12 is already a value. On the Common Data Bus (CDB) appears (tag=P8, value=7). What happens, and in which cycle can I fire if the broadcast lands at the end of cycle 4?
Recall Solution
The station snoops the CDB, sees its waiting tag P8 matches, and captures the value:
Now .
Both operands available at end of cycle 4 ⇒ I fires the next cycle = cycle 5, computing .
Level 3 — Analysis
L3.1 — Build both timelines
For the code below, draw the in-order and out-of-order execution timelines and give the speedup. Ports unlimited; LOAD = 3 cycles, others = 1 cycle. Use the timing convention at the top.
I1: R1 = LOAD [x] ; 3 cycles
I2: R2 = R3 + R4 ; 1 cycle, independent
I3: R5 = R1 - R6 ; 1 cycle, needs I1
I4: R7 = R2 * R8 ; 1 cycle, needs I2
The dependency graph is shown below.

Recall Solution
In-order. Why does I2 wait? In a strict in-order machine an instruction may only begin executing after every earlier instruction has begun (the execute stage dispatches in program order). So even though I2 has no dependency on I1, it is stuck behind I1 in the dispatch queue and cannot start until I1's 3-cycle LOAD has cleared the execute slot. That single ordering rule is exactly what OoOE will relax. Each column below is one cycle; read straight down to see which op occupies which cycle:
Cycle: 1 2 3 4 5 6
I1: [L] [L] [L]
I2: [+]
I3: [-]
I4: [*]
Total span = 6 cycles
Out-of-order — look at the two chains: I1(3c) → I3(1c) and I2(1c) → I4(1c). With ports free, dependencies (not program order) decide firing:
Cycle: 1 2 3 4
I1: [L] [L] [L]
I2: [+] (independent, fires cycle 1)
I4: [*] (needs I2 done end of cy1, fires cycle 2)
I3: [-] (needs I1 done end of cy3, fires cycle 4)
Total span = 4 cycles
- Chain 1 critical length = cycles → this is the critical path.
- Chain 2 length = cycles, hidden entirely under chain 1.
- Speedup .
L3.2 — Where's the floor?
In L3.1, suppose you had infinitely many execution ports and infinite window. What is the smallest possible completion span, and why can't OoOE beat it?
Recall Solution
The limit is the critical-path-limited ILP: the longest dependency chain. Here that is I1 → I3 with length cycles. Even with infinite resources you cannot start I3 before I1's LOAD result exists (a true RAW dependency), so 4 cycles is the hard floor. OoOE removes waiting for unrelated work — it can never remove waiting for your own inputs.
Level 4 — Synthesis
L4.1 — ROB commit with a fault
An 8-entry ROB currently holds (✓ = execution complete, head marked):
ROB: [ I1✓ | I2✓ | I3(fault) | I4✓ | I5✓ | I6 | I7 | I8 ]
^head
I1, I2 finished cleanly. I3 divided by zero. I4, I5 finished (speculatively). I6–I8 still executing.
(a) How many instructions actually commit their results to architectural state?
(b) What happens to I4 and I5, which already computed answers?
(c) After the exception, where does the machine restart?
Recall Solution
Commit is in-order from the head, using .
- (a) Head advances: I1 commits, I2 commits. Then head = I3, which is complete but has
Exceptiontrue ⇒ stop. So exactly 2 instructions () commit before the fault. - (b) I4 and I5 are squashed / flushed. Their computed values live only inside the ROB, never in architectural registers or memory, so they simply disappear. This is why speculative results are safe: nothing after a fault ever became visible.
- (c) The exception handler runs; architectural state = post-I2. The OS may fix the cause and restart execution at I3. The programmer sees clean semantics: "I3 faulted, nothing after it happened."
L4.2 — Sizing the window
A program has abundant independent work but a memory system where LOADs take cycles. The machine commits (retires) about instructions per cycle on average, and its front-end can issue at least that fast, so the ROB stays full. Roughly how many in-flight instructions must the window hold to fully hide one 100-cycle LOAD without stalling commit?
Recall Solution
To keep retiring while a 100-cycle miss resolves, the window (ROB) must contain enough independent instructions to cover the whole latency at the commit rate: (Here issue rate ≥ commit rate, so the ROB fills and the commit rate is what must be sustained past the miss.) This is exactly why real designs push windows to 200–600 entries — the deeper the memory latency you want to hide, the bigger the window (and ROB, physical register file, load/store buffers) must be. Below this size the ROB fills, the head stalls on the outstanding LOAD, and commit halts.
Level 5 — Mastery
L5.1 — Design under a resource limit
Take the four independent-ish instructions of L3.1 again, but now you have only ONE execution port that can start at most one instruction per cycle (a multi-cycle LOAD keeps occupying that single port for its full 3 cycles). Build the best out-of-order schedule and give the new speedup versus in-order (in-order on one port is unchanged at 6 cycles).
I1: R1 = LOAD [x] (3c, occupies the port cycles it runs)
I2: R2 = R3 + R4 (1c, independent)
I3: R5 = R1 - R6 (1c, needs I1)
I4: R7 = R2 * R8 (1c, needs I2)
Recall Solution
With one port, the port is a shared resource. Total work = port-cycles, so we can never finish before cycle 6 no matter the order — execution-width-limited. A valid schedule (each column = one cycle):
Cycle: 1 2 3 4 5 6
Port: [L] [L] [L] [+] [*] [-]
Op: I1 I1 I1 I2 I4 I3
I2 and I4 fire once the port frees at cycle 4 (I4 in cycle 5 after I2 gives its result end of cycle 4); I3 fires last (its input I1 was ready end of cycle 3, but the port is busy until cycle 6).
- Span = 6 cycles, speedup . Reordering bought nothing here.
- Lesson: OoOE only helps when a resource other than the bottleneck is idle. With a single port fully occupied, you are limited by , the middle term of This is the flip side of L3.2, where the critical path was the binding limit.
L5.2 — Which limit binds?
For each scenario, state which of the three limits (Window, Execution width, Critical path) is the bottleneck.
(a) A long chain A→B→C→…→Z, each 1 cycle, all ports free, huge window.
(b) 1000 fully independent 1-cycle adds, 4 ports, window of 8.
(c) 1000 fully independent 1-cycle adds, 2 ports, window of 500.
Recall Solution
- (a) Critical path. The 26-deep RAW chain forces cycles no matter how many ports or how big the window — you cannot outrun your own dependencies.
- (b) Window. Independent work exists and ports are plentiful, but you can only see 8 instructions at a time, so you can never keep 4 ports fed for long. Enlarge the window and IPC rises.
- (c) Execution width. Window (500) and independence are both ample, so neither of those binds. The limiting term is the 2 ports: at most 2 adds can start per cycle, so 1000 independent adds take cycles. Adding more ports would speed it up; a bigger window would not.
Recall Quick self-test
False dependencies removed by renaming are ::: WAR and WAW (not RAW) Commit order in an OoOE machine is ::: strictly in program order, via the ROB The hard floor on completion time with infinite resources is set by ::: the critical path (longest true-dependency chain) Window needed to hide a 100-cycle miss at 4 commits/cycle is about ::: 400 in-flight instructions Speedup of L3.1 in-order vs OoOE (unlimited ports) ::: 6/4 = 1.5×