Guess now, pay later: speculation, precise exceptions and the reorder buffer

After this lesson you will fill a reorder-buffer entry and a rename table snapshot by snapshot, say which entries a mispredicted branch tears off, and explain why the register file is written only at commit.

Robert Tomasulo's 1967 paper describes hardware that looks ahead about eight instructions. Intel's Golden Cove core of 2021 keeps 512 in flight. Most of those 512 sit behind a branch nobody has evaluated yet — about one instruction in five is a branch — so the core guesses the outcome, runs ahead, and is wrong now and then. Yet it never leaves a wrong value in a register, and a divide by zero three instructions back never leaves a half-finished mess behind it.

The trick is a queue with two pointers that delays the one step which cannot be undone. In the notes' own example an instruction computes the value 10 in cycle 4, and the register F0 becomes 10 only in cycle 5. Keep that puzzle in mind: why would a machine built for speed wait a whole cycle to write a number it already has?

One more number before you start. In April 2026, 57 students took the professor's second self-assessment test — ILP, dynamic scheduling, reorder buffer — and averaged 5.9 out of 10. This lesson is where those points are.

Guess now: speculation, and the one step that cannot be undone

Take the loop of the notes' February 2025 reorder-buffer exercise (lesson 18 fills its table). Registers are written as the 2026 paper writes them, without the dollar sign; older papers write $F2.

LOOP: LD    F2, 0(R1)
      FADD  F4, F2, F2
      SD    F4, 0(R1)
      ADDUI R1, R1, 4
      BNE   R1, R2, LOOP   ; pred. taken

The fetch unit reaches BNE at the end of the first iteration. It does not know whether R1 equals R2 — the ADDUI before it may not even have executed. A branch predictor (lesson 5) says "taken", and fetch continues at LOOP: the second iteration's LD, FADD and SD enter the machine while the first BNE is still a question mark. Figure 1 shows the stream: everything below the branch is a guess until the branch resolves.

I0 LD F2, 0(R1) I1 FADD F4, F2, F2 I2 SD F4, 0(R1) I3 ADDUI R1, R1, 4 I4 BNE R1, R2, LOOP predicted taken iteration 1 iteration 2 I5 LD F2, 0(R1) spec I6 FADD F4, F2, F2 spec I7 SD F4, 0(R1) spec still a guess
Fig. 1. The predictor says "taken", so I5–I7 enter the machine behind an unresolved BNE. They are speculative: they may execute, but nothing they produce may become visible until the branch is known.

On the classic Tomasulo machine of lesson 11 those guessed instructions would issue, execute and, at Write Result, put their results on the common data bus — which writes them straight into the register file. Suppose the guess was wrong and the loop was over. Now F2 and F4 hold values the program never asked for, and the SD may have written memory. Nobody kept the old values; there is nothing to roll back to. That is why a Tomasulo machine with a predictor may fetch down the predicted path but must not let those instructions change the state before the branch resolves — and waiting for every branch throws away most of the parallelism the predictor was supposed to buy.

Hardware-based speculation — executing instructions before it is known whether they are needed (they are control-dependent on an unresolved branch), with the ability to undo their effects if the prediction turns out wrong. Hennessy & Patterson make it three ideas: dynamic branch prediction to choose the path, speculation to execute along it before the branch resolves, and dynamic scheduling to overlap instructions from several basic blocks.

The problem it creates is in the notes' own words (p. 222): "how do we prevent side effects (e.g., register or memory updates) from mispredicted instructions?". The answer: defer the architectural state update until it is safe to commit. The architectural state is what the program can see — the register file and memory. Everything else, reservation stations, buses and the buffer below, is scaffolding that can be thrown away.

Reorder buffer (ROB) — a buffer that holds the results of instructions that have finished execution but are not yet committed. It lets results be written to the register file and memory in program order, and it lets the processor discard the results of a mispredicted path before they get there.

The notes list what the ROB is critical for (p. 222): decoupling execution completion from the state update, which they call commit; supporting precise exceptions, so the CPU can stop cleanly at the last correct instruction; and tracking which instructions are speculative and which are not. Hennessy & Patterson put it in one sentence: the bypassing of results among instructions, which speculative execution needs, must be separated from the actual completion of an instruction. Bypassing happens on the bus, as before. Completion — the write that counts — happens later, at a gate.

Completed is not committed: a result exists the moment the unit produces it; it becomes part of the program's state only when its instruction reaches the head of the reorder buffer.

Why go to this trouble? Here is the chain an examiner expects you to reproduce. Branches are frequent, so waiting for each one serialises the stream and wastes most of the available ILP. So predict and keep going. Some of that work is wrong. So it may compute, but it must not commit. So you need a place to park completed results and a rule to release them in order. The place is the ROB; the rule is in-order commit at the head. The cost sits next to the benefit: every misprediction wastes the work fetched down the wrong path (roughly the misprediction rate times the number of instructions behind the branch), recovery takes cycles, and the buffer is hardware — entries, ports, comparators and a copy of every result into the register file at commit.

Pay later: out of order, in order, and the fault that waits its turn

Out-of-order execution (lesson 8, lesson 9, lesson 11) exists because dependences and delays stop some instructions from executing now: a RAW hazard, a load that misses in the cache. Letting independent instructions bypass the stalled one keeps the functional units busy. The notes' example (p. 223): while an instruction waits for a memory load, an ALU operation behind it executes. The risk comes with it. Instructions complete in a different order from the program, which breaks the precise exception model; and the instructions behind a predicted branch may simply be wrong. The fix the notes state (p. 223): instructions must commit — retire — in order, exactly as they appear in the program. Execution out of order, commit in order, and the ROB bridges the two.

What the ROB does in each phase (pp. 223–224). At issue it allocates an entry, which marks the instruction's place in program order. During execution it stores the result as soon as the instruction finishes. At commit the instruction updates the architectural registers or memory only if three things hold:

  1. it is at the head of the ROB;
  2. its execution has completed;
  3. it is not speculative.

With these, the state is updated in program order only; speculative results stay in the buffer until validated; and a speculated instruction can be undone — its entry is simply dropped. The notes' summary (p. 224): the ROB lets the processor execute instructions as soon as their operands are ready, regardless of program order, while ensuring they commit their results in order.

Precise exceptions, by construction

Lesson 7 introduced the problem: once units complete out of order, a younger instruction may already have written its register when an older one faults — the exception is imprecise and the program cannot be restarted. The notes now define the goal (p. 224). An exception is precise if the processor can stop at a well-defined point corresponding to a specific instruction, all previous instructions are fully committed, and no subsequent instruction has modified the architectural state. Then the operating system or the handler can identify the error and resume the program.

The ROB delivers all three (p. 224): results go to the ROB, not to the register file or memory; only the head commits, in order; on an exception or a misprediction the entries after the faulting instruction are flushed and execution restarts from the faulting instruction or from the handler. The rollback is clean because the architectural state was never touched beyond the last committed instruction. One detail the notes leave out, from Hennessy & Patterson: the exception is recorded in the entry of the faulting instruction and is taken only when that instruction reaches the head. At that moment everything older is committed and everything younger is still in the buffer — the three conditions hold by construction. And an instruction on a mispredicted path that faults never reaches the head: it is flushed together with its entry, exception included. A speculative exception never happens.

The notes' Example 11 (p. 225) is the case to watch: four instructions, the third divides by zero, the fourth is independent and finishes early, writing R5. In the papers' mnemonics:

I1: LD    F2, 0(R1)
I2: FADD  F4, F2, F2
I3: FDIV  F6, F4, F0   ; F0 = 0: fault
I4: ADDUI R5, R5, 8    ; independent

The same fault, with and without a reorder buffer

The four instructions of Example 11 as cards, the register file on the right, and a stepper that lets results arrive in the order they really finish: I1, then I4 (early), then I2, then the faulting I3. The selector decides when the register file is written. The insight to take away: deferring the architectural write until commit is the whole precision mechanism — nothing else changes.

  1. Keep "Write Result". Before stepping, predict the value of R5 at the moment the divide by zero is reported (step 4). Then step there.
    What you should see

    R5 = 108. I4 wrote R5 at step 2, two steps before the older I3 faulted. The handler would find a register changed by an instruction that, in program order, has not happened yet: imprecise. Step 5 adds the consequence — restarting at I3 would run I4 again and leave R5 = 116.

  2. Switch to "Commit". Step to 4, then to 5. Predict: when is the fault taken, and what is R5 at that moment?
    What you should see

    At step 4 the fault is only recorded in ROB2; the register file still shows the values before I1. At step 5 I1 and I2 commit (F2 = 5, F4 = 10), I3 reaches the head and the fault is taken with R5 = 100; I4's 108 is discarded with its entry. Older instructions done, younger ones without effect: precise.

  3. Switch on the branch toggle and keep "Commit". Step to 5. Does the divide by zero ever reach the operating system? Then try the same toggle with "Write Result".
    What you should see

    No. The branch at the head resolves as mispredicted, the four entries behind it are torn off, and the recorded fault goes with them: a speculative exception never happens. With "Write Result" the opposite: a fault is taken for an instruction that should never have run, and F2, F4, R5 are already overwritten.

A word about condition 3. In the course's tables an entry carries an explicit Speculative flag. In Hennessy & Patterson's model there is no flag: a branch commits only after it has resolved, so by the time an instruction reaches the head every older branch has committed and the instruction is no longer speculative by construction. The two views agree; the flag just makes the flush set visible on paper. Real processors do not even wait for the branch to reach the head: they recover as soon as it resolves, clearing the younger entries and restarting fetch — the notes' Example 13 does this silently.

The notes sum up the ROB's functional roles (p. 225): result buffering — results of instructions that have completed but not committed; speculative result propagation — results pass among instructions issued after a predicted branch, through the ROB and the bus, without touching the architectural registers; precise interrupt support — only committed instructions affect the state, and a flush rolls back a misprediction or an exception. The closing line is worth keeping: the ROB "is not merely a commit buffer, it is also a speculation-aware result forwarding structure, enabling safe communication among instructions that may never actually commit."

Names, again: the ROB number is the new tag

Lesson 11 renamed registers with reservation-station names: the tag on the bus said "I am the result of RS3". With a reorder buffer the name changes. A ROB number is simply the index of an entry — the slot an issued instruction occupies, unique among the instructions in flight (p. 226). The notes use it in two ways.

As the destination tag (register renaming). When an instruction that writes a register issues, the register is not renamed to another physical register; the architectural register is mapped to the instruction's ROB number — "the identical logic of tag in Tomasulo algorithm", only in a different namespace. For operand dependency resolution. A later instruction that needs that result does not stall: it records the ROB number as its operand source, and when the entry becomes ready — the value is written into the ROB — it reads the value and proceeds. In Tomasulo this listening was the CDB's job, and it still is: the bus now carries the pair (ROB number, value), and every waiting reservation station and the ROB entry itself listen. As before, the mechanism avoids WAR and WAW hazards and forwards data before commit.

Put together, at issue each source register goes through three cases. The notes give two of them on p. 232 and the third on p. 239; Figure 2 has all three.

Source F2 at issue: the rename table decides rename table F2 → ? no mapping mapped, ready mapped, not ready register file: value ROB[h].Value: value tag h — wait for the CDB
Fig. 2. The three-way operand rule. Only the third case makes the instruction wait, and it waits for a ROB number on the bus, never for a commit. In the paper's Tomasulo table the third case is the row's "RAW with I#".

Why do WAR and WAW vanish? Every writer gets a fresh name — its own entry — so two writes to F0 never collide (WAW), and a later write to F0 cannot overtake an earlier read, because the reader captured the old value or the old tag at issue (WAR). Only RAW remains, and it is served by the bus. And why must the tag be the ROB number rather than the reservation-station number? Because the reservation station is freed at Write Result, while the value must stay nameable until commit: an instruction issuing after the broadcast finds it in the ROB, by its number.

Reservation stations stay, demoted

The ROB buffers results and hands them to dependents as soon as they are ready, so reservation stations are no longer responsible for naming (p. 227). Their role now: buffer decoded instructions before the functional units, and hold operand values or ROB tags temporarily. The notes call them "staging areas" and "lightweight instruction and operand buffers". The notes' Tables 21 and 22 summarise the change; two of their rows are wrong, so here is the comparison as it should read.

Tags are reservation-station names. At Write Result the CDB carries (RS tag, value) to the waiting reservation stations and to the register file; the producing station is freed. The register file is the only home of a result, so nothing can be undone: no speculation, imprecise exceptions. Stores wait in store buffers.

Tags are ROB entry numbers. At Write Result the CDB carries (ROB number, value) to the waiting reservation stations and to the ROB entry, which becomes Ready; the station is freed. The register file is written only at Commit, by the head. An operand may also be read from a ready entry at issue. Stores live in their entries: no store buffer.

"The ROB replaces the reservation stations." No. It replaces the store buffers and takes over the tag namespace; the stations stay as buffers between issue and execution. And "reservation stations help reduce structural hazards" (p. 227) is true in one sense only: an instruction can issue while its functional unit is still busy. But a full set of stations — or a full ROB — is a structural hazard, and it stalls issue.

The machine: Figure 27 and the six fields of an entry

Figure 27 of the notes (p. 228) is Hennessy & Patterson's Figure 3.11: the floating-point unit of lesson 11 extended to handle speculation. Put the two side by side and three things have moved.

Instr. queue Reservation st. FP unit Registers Store buf. Memory CDB at Write Result

Before: classic Tomasulo

Instr. queue Reservation st. FP unit Registers Reorder buffer Store buf. Memory CDB Write Result Commit Commit (stores)

After: Tomasulo with a reorder buffer

Fig. 3. The three changes of Figure 27: the CDB feeds the reorder buffer instead of the register file; the register file (and memory, for stores) is written only from the buffer, at Commit; the store buffer is gone, because a store's address and data live in its entry until it commits.

Walk the components (pp. 228–230). The instruction queue holds fetched instructions and supplies each one to a reservation station and to the ROB in parallel. The reservation stations are temporary buffers between issue and execution — three for the adders, two for the multipliers in the figure — holding the instruction with operand values or ROB tags. The FP units receive an instruction when its operands are ready. The CDB broadcasts result values with their ROB tag; all waiting instructions and all ROB entries listen. The ROB is central: it takes over the tag namespace and the store buffers. The load buffers and the address unit stay: a load still gets a buffer at issue, the address unit computes base plus offset, and — step 3 of the notes' four — the load may access memory early only if no earlier store has an unresolved or equal address; otherwise it waits until memory disambiguation clears it. That is a RAW hazard through memory. A store, on the other hand, keeps its address and its data in its ROB entry and writes memory at commit, in order: WAW and WAR hazards through memory disappear, and since a store can never be undone it had to wait for commit anyway — which is why the store buffer could go. Finally the register file: not updated after execution, updated only when instructions commit through the ROB. In the figure the "Reg #" and "Data" arrows come from the buffer, and the CDB no longer reaches the registers.

The six fields of an entry

Each entry holds (p. 229): Busy — the entry is active or free; Instruction type — branch (no result), store (the destination is a memory address), load or ALU (the destination is a register); Destination — the register number, or the memory address for a store; Value — the result after execution, kept until commit; Ready — set when execution has completed and the result is valid; Speculative — whether the instruction was issued after an unresolved branch. The exam's table (February 2025) keeps the same information under its own headings and adds one column, Res. Alloc.: the reservation station or load buffer the instruction occupies. Figure 4 shows the mapping.

ROB# Instruction Dest. Res. Alloc. Ready/Status Value Spec. ROB0 LD F0, 0(R1) F0 Load1 No, exec. – No – Instr. type Dest paper only Ready Value Spec Busy = the row is filled
Fig. 4. The entry of Example 12 (p. 231) in the paper's columns; under each column, the notes' name for the field. The highlighted column is the paper's own: which station or buffer the instruction holds. The paper has no Value column; this lesson keeps one while you learn, because it shows exactly what the register file does not yet have.

Conventions for the paper's columns, which lesson 18 drills: Res. Alloc. is Load1 … for loads, FP1 … for floating-point operations, Int1 … for integer operations and branches (a branch evaluates its condition on an integer unit), and "–" for a store — the ROB machine has no store buffer, the address and data live in the entry. A station is busy from issue to Write Result. Ready/Status is written "No, issued", "No, exec.(*)" for an instruction stuck in execution — the asterisk marks the cache miss the exercise announces — or "Yes". Dest. is the register, or "Mem" with the address for a store. A free entry is simply an empty row. Here is Example 12 in that format.

Example 12 of the notes (p. 231): one instruction in a four-entry ROB. Head at ROB0 (first to commit once ready), tail at ROB1 (the next free entry).
ROB#InstructionDest.Res. Alloc.Ready/StatusValueSpec.
ROB0In: LD F0, 0(R1)F0Load1No, exec.–No
ROB1––––––
ROB2––––––
ROB3––––––

Two of the fields are control bits with a life of their own (p. 230). The Ready bit is set when the instruction finishes execution and the result is written into the ROB; it tells dependents that the value can be read — from the bus or directly from the entry — and it enables commit: no commit without Ready. The Speculative flag is set at issue when the instruction follows an unresolved branch and cleared when the branch resolves as predicted; its purpose is that speculative instructions never update the state, and that all of them can be flushed on a misprediction or an exception. The notes' summary of the design (p. 230): the ROB replaces the store buffers, so memory writes wait for commit; renaming moves from the reservation stations to the ROB; precise exceptions and speculation are both served by tracking and flushing; the CDB stays critical for forwarding and readiness detection.

"The ROB replaces the register renaming logic" (p. 229). Overstated: the register status table of lesson 11 still exists — it now holds ROB numbers, and the exam calls it the Rename Table. What the ROB replaces is the station-based tag namespace and the store buffers.

Two pointers and a ring: head, tail, the rename table, and the notes' Example 13

The ROB is a circular buffer — a circular FIFO queue — run by two pointers (p. 231). The head points to the oldest instruction, the next to commit; it advances at commit. The tail points to the next free entry, where the next issued instruction goes; it advances at issue. Why two pointers and a ring (pp. 231–232)? Insertion at the tail preserves program order at issue. Retirement from the head makes commit in order. When a pointer reaches the end of the buffer it wraps around to 0, so entries never move: no data shifting, less silicon. And full and empty are told apart with the two pointers and one busy bit:

ROB0 In ROB1 In+1 ROB2 In+2 ROB3 In+3 head tail head = tail = ROB0, entry busy: FULL

Full: head = tail and the entry is busy — issue stalls

ROB0 ROB1 ROB2 ROB3 head tail head = tail = ROB0, entry not busy: EMPTY

Empty: head = tail and the entry is free — nothing to commit

Fig. 5. The same two pointers, the same position; only the busy bit of the entry they point at tells full from empty (p. 232). The left ring is cycle 3 of the corrected Example 13 below: ROB2 holds the branch, and the dashed outline of ROB3 marks the one speculative entry behind it.

In symbols, with \(N\) entries, head \(h\), tail \(t\) and the busy bit \(b[h]\) of the entry at the head (the full / empty rule is the notes'; the modular arithmetic is background):

\[ \text{full} \iff h = t \wedge b[h] \] \[ \text{empty} \iff h = t \wedge \neg b[h] \] \[ \text{issue: } t \leftarrow (t + 1) \bmod N \] \[ \text{commit: } h \leftarrow (h + 1) \bmod N \]
\(h\), \(t\)
head and tail pointers, as entry numbers
\(b[h]\)
busy bit of the entry the head points at
\(N\)
number of entries of the buffer

Full is the costly state. The head blocks everything behind it: if the oldest instruction waits on a cache miss, finished instructions behind it cannot commit, their entries do not free, and when the tail meets the head issue stops — the whole window is exhausted. Nothing commits out of order to make room, ever. That is the price of the design, and it is why the size of the buffer matters.

Only about fifteen IBM System/360 Model 91s were built. The first went to NASA's Goddard Space Flight Center in October 1967, where at 16.6 MIPS it was the most powerful computer in user operation. It ran Tomasulo's algorithm — and it had imprecise exceptions and out-of-order memory stores, the two problems the reorder buffer later fixed. The hook's 512-entry buffer is the same algorithm with a window 64 times wider than the Model 91's eight instructions.

Why do modern cores need hundreds of entries?

Background, not in the notes. To sustain a throughput of IPC instructions per cycle while each instruction stays in the buffer for T cycles from issue to commit, the buffer must hold about IPC × T entries (Little's law). A cache miss of a hundred cycles at the head, on a core that issues four instructions per cycle, needs hundreds of entries behind it — or issue stalls long before the miss is served.

The rename table

The rename table (p. 232) is a small hardware table that records where the most recent value of each register will come from while its producer is still in flight: it maps F0, F2, … to the ROB entry producing their new value. It is lesson 11's register status table with ROB numbers instead of station names, and the exam calls it the Rename Table. A writer issues: its destination is mapped to its new ROB number. A reader issues: it checks the table — mapped, so read the value from the ROB, or take the tag if the entry is not ready; not mapped, so read the register file. Two rules the notes leave implicit. At commit the mapping is cleared only if it still points to the committing entry: a younger writer may have re-mapped the register, and that mapping must stay. At a flush every mapping that points to a flushed entry goes back to the older in-flight producer of that register, or to "–" if there is none; real cores keep a checkpoint of the table at each branch for exactly this.

The rename table holds names, never values: a ROB number or nothing. The values live in the entries and, after commit, in the register file.

The ring: issue at the tail, finish anywhere, commit at the head

A reorder buffer drawn as a ring of entries with its head and tail pointers, the rename table and the register file under it. "Issue next" takes the next instruction of the program below (the notes' Example 13 with the branch put back, then three more); the slider picks which entry "Complete" finishes — in any order you like, as long as its operands have arrived; "Commit head" is the only button that can change the register file; the two branch buttons resolve the BNE. The insight to take away: completed is not committed, and the ring is what makes an out-of-order machine undo-able.

  1. Press "Issue next" four times. Before the fourth press, predict which entries will be marked spec and what the status line will say after it. Then, with the slider at 0, press "Complete ROB0" and look at F0 in the register file. Does it change?
    What you should see

    Only ROB3 (In+3) is marked spec: it sits behind the BNE in ROB2, which is itself not speculative. After the fourth issue the line reads head ROB0, tail ROB0, FULL. Completing ROB0 puts 10 in the entry with "ready", but the register file still says F0 = 0 and the rename table still says F0 → ROB0. Only "Commit head" writes F0 = 10 and clears the mapping.

  2. Start over, issue twice, set the slider to 1 and press "Complete ROB1" before completing ROB0. Predict the note. Then complete ROB0 and ROB1 in that order.
    What you should see

    It refuses: FADD F2 waits for F0 from ROB0 on the CDB — a RAW hazard, the third case of the operand rule. After ROB0 completes, ROB1 captures 10 from the bus and completes with 20, while the register file still says F0 = 0: forwarding before commit.

  3. Follow Example 13 to its cycle 7: Issue four times, Complete ROB0, Commit, Issue (In+4 lands in ROB0), Complete ROB1, Commit. The branch is now at the head and ROB0 and ROB3 are speculative. Predict what "Branch: mispredicted" does to the ring, the tail and the rename table — then press it, and press Commit once more.
    What you should see

    ROB3 and ROB0 are torn off (crossed out), the tail goes to ROB3, F6 and F8 go back to "–", and the register file still shows F0 = 10, F2 = 20 — nothing to undo there. The branch stays and commits on the next press, leaving head = tail = ROB3 with the entry free: empty. The next "Issue next" fetches the fall-through SD F2, 0(R1), not speculative.

  4. Switch to 8 entries, press "Issue next" seven times and look at F0 in the rename table. Predict what it will map to after "Branch: mispredicted": ROB6, "–", or ROB0?
    What you should see

    ROB0. In+6 (ROB6) re-mapped F0 while In (ROB0) was still in flight; the flush restores the older in-flight producer, not "–". A later reader of F0 must still wait for ROB0 — it must not read the stale register file.

Example 13, corrected: eight cycles, two commits, one flush

The notes' Example 13 (pp. 232–237) runs five instructions, In to In+4, through a four-entry ROB. Two things need fixing before it can be followed: there is no branch in the buffer although In+3 and In+4 are called speculative, and the rename table ends up holding values (both red pens are below). Here In+2 is the branch, and every instruction gets a mnemonic so that the paper's columns can be filled; the machine is the one of February 2025 — four load buffers, four FP reservation stations, two integer ones. The notes' numbers — 10, 20, the cycle of each event, the pointers — are unchanged. The notes count snapshots as cycles and issue two instructions in "cycle 1": read the cycles as snapshots, not as a timing table.

In:   LD   F0, 0(R1)    ; Load1, value 10
In+1: FADD F2, F0, F0   ; FP1, RAW F0, 20
In+2: BNE  R1, R2, NEXT ; Int1, pred. taken
In+3: FMUL F6, F2, F2   ; FP2, spec
In+4: FADD F8, F6, F2   ; FP3, spec
  1. Cycle 1. In and In+1 issue. Head ROB0, tail ROB2. F0 and F2 are renamed: whoever reads them now looks at ROB0 and ROB1, not at the register file. In+1 received the tag ROB0 for F0.

    ROB#InstructionDest.Res. Alloc.Ready/StatusValueSpec.
    ROB0In: LD F0, 0(R1)F0Load1No, exec.–No
    ROB1In+1: FADD F2, F0, F0F2FP1No, issued–No
    ROB2––––––
    ROB3––––––
    Reg #F0F2F4F6F8
    ROB #ROB0ROB1–––
  2. Cycle 2. The branch In+2 issues into ROB2 and takes Int1. It has no destination, so the rename table does not change; it is not speculative itself — it is the unresolved branch. Tail ROB3.

    ROB#InstructionDest.Res. Alloc.Ready/StatusValueSpec.
    ROB0In: LD F0, 0(R1)F0Load1No, exec.–No
    ROB1In+1: FADD F2, F0, F0F2FP1No, issued–No
    ROB2In+2: BNE R1, R2, NEXT–Int1No, issued–No
    ROB3––––––
    Reg #F0F2F4F6F8
    ROB #ROB0ROB1–––
  3. Cycle 3. In+3 issues into ROB3 with Spec = Yes: an older branch is unresolved. All four entries are busy, so the tail meets the head at ROB0 and the entry is busy — the ROB is full. Nothing more can issue until In commits.

    ROB#InstructionDest.Res. Alloc.Ready/StatusValueSpec.
    ROB0In: LD F0, 0(R1)F0Load1No, exec.–No
    ROB1In+1: FADD F2, F0, F0F2FP1No, issued–No
    ROB2In+2: BNE R1, R2, NEXT–Int1No, issued–No
    ROB3In+3: FMUL F6, F2, F2F6FP2No, issued–Yes
    Reg #F0F2F4F6F8
    ROB #ROB0ROB1–ROB3–
  4. Cycle 4. In completes: the CDB carries (ROB0, 10), ROB0 becomes Ready with Value 10, Load1 is freed, and In+1's station captures the 10. The register file still holds the old F0, and the rename table still says F0 → ROB0: the value is in the ROB, not in the register.

    ROB#InstructionDest.Res. Alloc.Ready/StatusValueSpec.
    ROB0In: LD F0, 0(R1)F0–Yes10No
    ROB1In+1: FADD F2, F0, F0F2FP1No, exec.–No
    ROB2In+2: BNE R1, R2, NEXT–Int1No, issued–No
    ROB3In+3: FMUL F6, F2, F2F6FP2No, issued–Yes
    Reg #F0F2F4F6F8
    ROB #ROB0ROB1–ROB3–
  5. Cycle 5. In commits: it is at the head, Ready, not speculative. The register file gets F0 = 10, ROB0 is freed, the head moves to ROB1 and the tail finally finds a free entry at ROB0. The rename entry of F0 is cleared — it pointed to the committing entry — and reads "–", as the paper writes it.

    ROB#InstructionDest.Res. Alloc.Ready/StatusValueSpec.
    ROB0––––––
    ROB1In+1: FADD F2, F0, F0F2FP1No, exec.–No
    ROB2In+2: BNE R1, R2, NEXT–Int1No, issued–No
    ROB3In+3: FMUL F6, F2, F2F6FP2No, issued–Yes
    Reg #F0F2F4F6F8
    ROB #–ROB1–ROB3–
  6. Cycle 6. In+4 issues into the freed ROB0 — the ring has wrapped — with Spec = Yes and the station FP3 (FP1 is still busy until In+1 writes its result). In+1 completes with 20 = 10 + 10; In+3 and In+4 capture the 20 from the bus. Full again: head = tail = ROB1, busy.

    ROB#InstructionDest.Res. Alloc.Ready/StatusValueSpec.
    ROB0In+4: FADD F8, F6, F2F8FP3No, issued–Yes
    ROB1In+1: FADD F2, F0, F0F2–Yes20No
    ROB2In+2: BNE R1, R2, NEXT–Int1No, issued–No
    ROB3In+3: FMUL F6, F2, F2F6FP2No, exec.–Yes
    Reg #F0F2F4F6F8
    ROB #–ROB1–ROB3ROB0
  7. Cycle 7. In+1 commits: F2 = 20 in the register file, ROB1 freed, head ROB2, tail ROB1, F2 → "–". The head is now the branch. ROB0 and ROB3 are speculative and still executing; they can neither commit nor be trusted.

    ROB#InstructionDest.Res. Alloc.Ready/StatusValueSpec.
    ROB0In+4: FADD F8, F6, F2F8FP3No, issued–Yes
    ROB1––––––
    ROB2In+2: BNE R1, R2, NEXT–Int1No, issued–No
    ROB3In+3: FMUL F6, F2, F2F6FP2No, exec.–Yes
    Reg #F0F2F4F6F8
    ROB #–––ROB3ROB0
  8. Cycle 8, before the flush. The branch executes: R1 equals R2 after all, the prediction "taken" was wrong. ROB2 becomes Ready (resolved) and the mispredict is detected. Everything issued after it — ROB3, then ROB0 — is on the wrong path.

    ROB#InstructionDest.Res. Alloc.Ready/StatusValueSpec.
    ROB0In+4: FADD F8, F6, F2F8FP3No, issued–Yes
    ROB1––––––
    ROB2In+2: BNE R1, R2, NEXT––Yes (mispredicted)–No
    ROB3In+3: FMUL F6, F2, F2F6FP2No, exec.–Yes
    Reg #F0F2F4F6F8
    ROB #–––ROB3ROB0
  9. Cycle 8, after the flush. ROB3 and ROB0 are freed and FP2, FP3 released; the tail moves back to ROB3, the entry after the last surviving instruction; F6 and F8 lose their mappings — no older producer of F6 or F8 is in flight, so they read "–"; fetch restarts at the fall-through instruction. The register file was never touched: F0 = 10 and F2 = 20 are exactly the state after In+1.

    ROB#InstructionDest.Res. Alloc.Ready/StatusValueSpec.
    ROB0––––––
    ROB1––––––
    ROB2In+2: BNE R1, R2, NEXT––Yes–No
    ROB3––––––
    Reg #F0F2F4F6F8
    ROB #–––––
  10. Cycle 9, one step beyond the notes. The branch commits: a resolved branch retires with no change of state. Head ROB3, tail ROB3, entry not busy — the ROB is empty, and the next instruction of the correct path will issue into ROB3 with Spec = No.

    ROB#InstructionDest.Res. Alloc.Ready/StatusValueSpec.
    ROB0––––––
    ROB1––––––
    ROB2––––––
    ROB3––––––
    Reg #F0F2F4F6F8
    ROB #–––––

Now the warm-up puzzle. In computes 10 in cycle 4 and F0 becomes 10 in cycle 5. The machine does not wait because it doubts the value; it waits because every result goes through the same gate — the head, in program order. For In the gate costs one cycle and buys nothing visible. For In+3 the same gate is what makes cycle 8 possible: its entry is torn off and F6 never changed. For a faulting instruction it is what makes the exception precise. The price is a delay of the architectural write, never of execution — dependents got the 10 from the bus in cycle 4 — and what it buys is undo. That rule, not the ring or the flag, is the reorder buffer.

Exam-style questions

Explain the main concepts and benefits of each one of the following techniques used by modern superscalar processors (Tomasulo-style), in about five lines each: (2) dynamic branch prediction and speculative execution; (5) dynamic register renaming; (6) out-of-order execution; (7) reorder buffer.

Show model answer

Dynamic branch prediction and speculative execution. The outcome and target of a branch are predicted at fetch from run-time history (BHT, BTB). The instructions after the branch are fetched, issued and executed before the branch resolves; their results are forwarded among them through the CDB and the ROB but kept out of the architectural state until the branch is resolved and the instruction commits. On a misprediction the younger entries of the ROB are flushed and fetch restarts at the correct target. Benefit: branches no longer drain the pipeline, and the window of exploitable ILP spans several basic blocks. Cost: wasted work and recovery time on mispredictions, so accuracy matters.

Dynamic register renaming. Each issued instruction that writes a register gets a fresh name for its result — a reservation station in classic Tomasulo, a ROB entry in the speculative version; the rename table maps the architectural register to its newest producer, and a consumer issuing later takes that producer's value or its tag. Benefit: WAR and WAW (name) hazards disappear, so instructions and loop iterations can be overlapped freely and only true (RAW) dependences remain. Cost: a rename table to keep, to restore after a misprediction, and the copy of each value into the architectural register at commit.

Out-of-order execution. Instructions wait in reservation stations and start as soon as their own operands are ready, independently of program order; results are broadcast on the CDB. Issue and commit remain in order. Benefit: a stalled instruction — a cache miss, a long-latency FP operation — does not block independent later ones; functional units stay busy and CPI drops. It requires renaming against WAR/WAW hazards and in-order commit to keep exceptions precise.

Reorder buffer. A circular FIFO: an entry is allocated at issue in program order, written out of order at Write Result (value, ready bit) and drained in order at Commit from the head (head, ready, not speculative). It supplies operands between completion and commit, provides the names for renaming (the ROB number is the tag) and replaces the store buffers, keeping a store's address and data until commit. Benefits: precise exceptions, since nothing younger than a faulting instruction has touched the state; cheap misprediction recovery by discarding the entries after the branch; speculation made safe. Cost: a finite window — a stalled head stops issue when the buffer fills.

Four of the eight sub-points of the June 2026 open question. The examiner looks for the "what it buys" line in each one and for the pairing cost / benefit; "the ROB makes the processor faster" alone earns nothing — it makes speculation safe, and the speed comes from executing past unresolved branches.

What is a precise exception (give the three conditions)? Explain how the reorder buffer guarantees precise exceptions in an out-of-order processor, and what happens if an instruction on a mispredicted path raises an exception.

Show model answer

An exception is precise if (1) the processor can stop at a well-defined point corresponding to a specific instruction, (2) all previous instructions are fully committed, and (3) no subsequent instruction has modified the architectural state. With a reorder buffer, results are written to the ROB entry at Write Result, never directly to the register file or memory; only the head commits, in program order. An exception is recorded in the entry of the faulting instruction and taken when that instruction reaches the head: at that moment every older instruction has committed and every younger one is still in the buffer, so the younger entries are discarded and fetch restarts at the handler — the three conditions hold by construction. An exception raised on a mispredicted path never reaches the head: when the branch resolves, the entries after it are flushed together with the recorded exception, so it is never taken.

Give the three conditions as a list and then map each one to a property of the buffer (results parked, in-order commit at the head, flush of the younger entries). The last sentence — a speculative exception is simply discarded — is the part most answers miss.

(Single answer.) In the speculative Tomasulo algorithm, when is the architectural register file updated?

  • At Write Result, when the CDB broadcasts the value At Write Result the value goes to the ROB entry and to the waiting reservation stations; the CDB no longer reaches the register file (Figure 27).
  • At Issue, when the destination register is mapped to a ROB entry Issue writes the rename table, not the register file: a name, not a value.
  • At Commit, when the instruction is at the head of the ROB with its result ready Only then is the instruction certainly part of the program's path and older than nothing unfinished.
  • As soon as the functional unit finishes That is completion; the result exists, but it is parked in the entry with Ready = Yes.
Show model answer

The third option: at Commit. The register file must reflect only instructions that are certainly part of the program's path; at Write Result the value goes to the ROB entry and to the waiting reservation stations.

Keep the contrast straight: in classic Tomasulo the register file is written at Write Result (Table 21's "commit on write-back"); with the ROB only at Commit. In neither design does the value wait inside the reservation station — the red pen on Table 21 above.

(Multiple answers.) Which of the following statements about the reorder buffer are true?

  • The tag broadcast on the CDB is the ROB entry number The ROB number is the unique name of the in-flight result, from issue to commit.
  • The reservation station is released at Commit The station is freed at Write Result; it is the ROB entry that is freed at Commit.
  • A store writes memory at Commit Its address and data wait in the entry; the ROB replaced the store buffer.
  • The tail pointer points to the newest issued instruction The tail points to the next free entry, one past the newest instruction.
  • When head = tail the ROB may be either full or empty The busy bit of the entry decides (p. 232).
  • A load may read memory only when no earlier store in the ROB has an unresolved or equal address Memory disambiguation: a RAW hazard through memory (p. 229, step 3).
Show model answer

True: 1, 3, 5, 6. False: 2 (the station is freed at Write Result, the ROB entry at Commit) and 4 (the tail points to the next free entry).

The two false statements are the two confusions the lesson warns against most: which structure is freed when, and what the tail points to.

(Single answer.) In the Speculative Tomasulo Architecture, what type of hardware block is used to undo speculative instructions in case of a mispredicted branch? Motivate your answer by explaining what happens in the case of a mispredicted branch.

  • Reorder Buffer Speculative results live only in ROB entries; discarding the entries younger than the branch undoes them.
  • Instruction Dispatcher Issue allocates entries; it has no record of results to undo.
  • Store Buffer The speculative design has no store buffer: stores wait in the ROB.
  • Reservation Stations They hold operands and tags, not the commit state; they are squashed as a consequence of the flush.
  • Load Buffers They queue loads before memory access; nothing architectural is kept there.
Show model answer

The Reorder Buffer. A speculative instruction's result is held only in its ROB entry (and in copies inside reservation stations), never in the register file or memory, and the entry is marked speculative. When the branch resolves as mispredicted: all ROB entries younger than the branch are invalidated and the tail is moved back to the entry after the branch; the reservation stations and load buffers working for them are squashed; the rename table is restored to the mapping valid at the branch, so that each register points to its older in-flight producer or to nothing; fetch is redirected to the correct target; the branch itself commits normally. Nothing has to be undone in the architectural state, because nothing speculative ever reached it.

The quiz of the February 2025 paper, as reproduced in the notes (p. 582). The motivation is worth the extra point: name the four actions (invalidate the younger entries, squash their stations, restore the rename table, redirect fetch) and the reason nothing in the registers needs undoing.

Consider the following instruction sequence executed on a speculative Tomasulo architecture with a 4-entry ROB, 4 load buffers (Load1–Load4), 4 FP reservation stations (FP1–FP4) and 2 integer reservation stations (Int1–Int2). The BNE is predicted taken. Complete the ROB table and the Rename Table (a) when the ROB becomes full, (b) after In has committed, (c) after the BNE has been found mispredicted and the ROB has been flushed.

In:   LD   F0, 0(R1)
In+1: FADD F2, F0, F0
In+2: BNE  R1, R2, NEXT
In+3: FMUL F6, F2, F2
In+4: FADD F8, F6, F2
Show model answer

(a) The ROB is full after four issues: head = tail = ROB0 and the entry is busy. In+3 is speculative because the BNE in ROB2 is unresolved; the BNE itself is not.

ROB#InstructionDest.Res. Alloc.Ready/StatusSpec.
ROB0In: LD F0, 0(R1)F0Load1No, exec.No
ROB1In+1: FADD F2, F0, F0F2FP1No, issuedNo
ROB2In+2: BNE R1, R2, NEXT–Int1No, issuedNo
ROB3In+3: FMUL F6, F2, F2F6FP2No, issuedYes
Reg #F0F2F4F6F8
ROB #ROB0ROB1–ROB3–

(b) In completes (Ready = Yes, value 10 in ROB0, Load1 released) and commits from the head: F0 = 10 in the register file, ROB0 freed, head ROB1, tail ROB0, and the mapping of F0 cleared. In+1 has captured F0 from the CDB and is executing.

ROB#InstructionDest.Res. Alloc.Ready/StatusSpec.
ROB0–––––
ROB1In+1: FADD F2, F0, F0F2FP1No, exec.No
ROB2In+2: BNE R1, R2, NEXT–Int1No, issuedNo
ROB3In+3: FMUL F6, F2, F2F6FP2No, issuedYes
Reg #F0F2F4F6F8
ROB #–ROB1–ROB3–

(c) In+4 has meanwhile issued into ROB0 (FP3, speculative) and In+1 has committed (F2 = 20, head ROB2, tail ROB1). The BNE resolves as mispredicted: ROB3 and ROB0 are flushed, FP2 and FP3 released, the tail moves to ROB3, the mappings of F6 and F8 are cleared (no older producer is in flight), fetch restarts at the fall-through instruction. The branch is Ready at the head and commits next; the register file holds F0 = 10, F2 = 20 and was never touched by the flushed instructions.

ROB#InstructionDest.Res. Alloc.Ready/StatusSpec.
ROB0–––––
ROB1–––––
ROB2In+2: BNE R1, R2, NEXT––YesNo
ROB3–––––
Reg #F0F2F4F6F8
ROB #–––––

The three snapshots are cycles 3, 5 and 8 of the corrected Example 13. Marks go to: the Spec column (Yes only behind the branch, No for the branch itself), the Res. Alloc. names and their release at Write Result, the head and tail positions in each snapshot, and the rename table — "–" after a commit, and after the flush "–" only because no older producer of F6 or F8 is still in flight.

Check yourself

In sits in ROB0 with Ready = Yes and Value = 10; the head is at ROB0, nothing has committed yet. In this cycle a new instruction that reads F0 issues. Where does it get F0 from?

  • From the register file The register file still holds the old F0: the commit has not happened. The rename table says F0 → ROB0, so the register file is not consulted.
  • From the ROB entry ROB0, as a value Mapped and ready: the second case of the operand rule. The value is copied into the reservation station at issue.
  • It receives the tag ROB0 and waits for the CDB That is the third case, for an entry that is not ready. ROB0 is ready; waiting would be a wasted cycle, and the broadcast has already passed.

You see head = tail = ROB5. Is the ROB full or empty?

  • Full: the tail has caught up with the head Possible, but the pointers alone cannot say — after the last commit the head catches up with the tail and the picture is identical.
  • Empty: there is nothing between the pointers Possible too; the same picture appears when the tail has filled the ring and come round.
  • It depends on the busy bit of ROB5 Busy → full (issue stalls); not busy → empty (nothing to commit). Two pointers and one bit, p. 232.

Renaming registers to ROB entries removes which hazards?

  • RAW A true dependence cannot be renamed away: the consumer waits for the producer's tag on the CDB.
  • WAR The reader captured the old value or the old tag at issue, so a later writer cannot overtake it.
  • WAW Each writer gets its own entry, and commit writes the register file in program order.
  • Structural A full ROB or a full set of reservation stations is a structural hazard that stalls issue; renaming does nothing about it.

Same answer as for classic Tomasulo — only the namespace of the tags changed.

The tail pointer of the reorder buffer points to…

  • the newest instruction in flight One entry too early: the newest instruction sits just before the tail.
  • the next free entry, where the next issued instruction will go And it advances at issue; the head advances at commit.
  • the oldest instruction, the next to commit That is the head.

F0 is mapped to ROB5, a speculative entry that is flushed; an older entry ROB0, which also writes F0, is still in flight. What should the rename table say for F0 after the recovery?

  • "–": no mapping Then a later reader of F0 would take the stale value in the register file, although ROB0 is about to produce a new one.
  • ROB5 The entry no longer exists; a reader would wait for a tag that is never broadcast.
  • ROB0 The mapping goes back to the older in-flight producer — the mapping that was valid at the branch. Real cores checkpoint the table at each branch for this.

An instruction on a mispredicted path divides by zero before the branch resolves. What happens?

  • The exception is taken immediately: a fault cannot wait It can and it must: the exception is recorded in the entry and would be taken only at the head.
  • Nothing: the entry is flushed with the path, exception included The instruction never reaches the head, so its recorded fault is never acted on. Architecturally it never existed.
  • The exception is taken when the branch commits When the branch resolves as mispredicted the younger entries are discarded, not committed.

Which statements about freeing and writing are correct in the speculative Tomasulo algorithm?

  • The reservation station is freed at Write Result Once the result is on the bus, the station's job is done.
  • The ROB entry is freed at Commit It must hold the value until the instruction reaches the head.
  • The ROB entry is freed at Write Result Then there would be nowhere to keep the result until commit, and nothing to flush on a misprediction.
  • The register file is written at Write Result That is classic Tomasulo; with the ROB the register file is written only at Commit.

The exam's Res. Alloc. column follows the first rule: a station name appears from issue to Write Result and then becomes free for the next instruction.

Key take-aways

What to watch and read