5.2.12 · D5Processor Datapath & Pipelining

Question bank — Precise exceptions in pipelines

2,591 words12 min readBack to topic

This is a self-test page. Each line is a Question ::: Answer reveal. Cover the answer, say your reasoning out loud, then check. If you cannot justify the answer in a sentence, you have not understood it yet — revisit the parent note.

The two conditions of precision (used repeatedly below)

The "clean line" picture

Every question on this page is really asking one thing: did the machine keep the clean line? Precision means there is a single dividing line in program order — everything to its left is fully committed (Condition 1), everything from the faulting instruction rightward is fully erased (Condition 2).

Figure — Precise exceptions in pipelines
Figure s01 (alt-text): five instruction boxes I1…I5 in program order. I1 and I2 are solid green ("done, committed"), a bold blue vertical "clean line" sits just before I3, and I3–I5 are dashed red boxes ("no-op, erased"). I3 is the faulting instruction.

Prerequisites that these traps lean on: 5.2.1-Pipelining-basics (the five stages above), 5.2.8-Data-hazardsand-forwarding, 5.2.11-Branch-prediction, 6.3.4-Reorder-buffer-in-superscalar, 7.2.5-Virtual-memory-exceptions, 3.4.7-Exception-handling-in-ISA.

Recall 15-second summary of the prerequisite mechanisms (so you needn't leave the page)

Reorder Buffer (6.3.4-Reorder-buffer-in-superscalar): a FIFO queue; instructions enter in program order, finish out of order writing results into their ROB slot, and commit in order at the ROB head — commit is when the result finally moves to the real register file. Branch prediction (5.2.11-Branch-prediction): the pipeline guesses a branch outcome and speculatively fetches; wrong guesses get flushed, taking any speculative faults with them.

Within-instruction exception priority

When one instruction could raise several faults, the earliest-in-its-lifetime fault wins. This ordering follows the pipeline stages left-to-right — every stage that can raise a fault must appear, including the MEM stage where loads and stores can page-fault, hit a data-TLB miss, or violate memory protection:

Figure — Precise exceptions in pipelines
Figure s02 (alt-text): four boxes in a left-to-right row labelled IF, ID, EX, MEM. Each names the fault it catches (IF: instruction page fault / instruction-TLB miss; ID: illegal opcode / privilege; EX: overflow / divide-by-zero; MEM: data page fault / data-TLB miss / protection violation). Gray arrows point rightward showing the instruction's lifetime; earlier stages rank higher.

Priority (highest = 1) Fault type Stage caught Why this rank
1 (highest) Instruction page fault / instruction-TLB miss IF Happens first in the instruction's life
2 Illegal opcode, privilege violation ID Decode comes after fetch
3 Overflow, divide-by-zero EX Execute is later still
4 (lowest) Data page fault / data-TLB miss / memory-protection violation (on a load or store) MEM Memory access is the last stage that can fault

True or false — justify

A pipeline that never reorders instructions automatically gives precise exceptions.
False — even a strict in-order pipeline lets instructions complete (write back) at different stages, so instruction can still be in MEM when faults in EX; you still need the stall-then-flush control (freeze the front, drain older instructions, NOP the rest) to force in-order completion.
Precise exceptions require that the faulting instruction leaves no trace on architectural state.
True — that is Condition 2 stated above: the faulting instruction and all younger ones must not modify registers or memory, so the handler sees state as if execution stopped just before it.
Out-of-order execution makes precise exceptions impossible.
False — OoO reorders execution, but a Reorder Buffer (ROB) restores in-order commit, so architectural state still updates in program order and exceptions stay precise.
The program counter saved in EPC (Exception Program Counter) should point to the instruction after the faulting one.
False — for a precise exception EPC points at the faulting instruction itself, so the handler (e.g. after loading a page) can retry the exact instruction that faulted.
If two instructions fault in the same cycle, the pipeline should report whichever was detected earliest in time.
False — it must report whichever is earliest in program order, because sequential semantics is the ground truth; time-of-detection is an artifact of which stage caught the fault.
A branch misprediction can cause a spurious exception if we are not careful.
True — speculatively fetched wrong-path instructions may fault (e.g. divide-by-zero), and if we took that exception we would report a fault for an instruction that was never meant to run.
In the ROB scheme, an instruction that finished executing has already updated the architectural registers.
False — completed results sit in the ROB entry, not in architectural registers; they move to architectural state only at commit, which happens in order at the ROB head.
Flushing younger instructions is enough to guarantee precision.
False — flushing satisfies Condition 2, but you must also let all older instructions finish committing to satisfy Condition 1; precision needs both halves of the line.
Precise exceptions are only about arithmetic faults like divide-by-zero.
False — the biggest use case is page faults (which strike in MEM for data accesses and in IF for instruction fetches), where the handler loads a page and must resume the exact faulting instruction; TLB misses, illegal opcodes and privilege violations all rely on the same machinery.
Interrupts (external, asynchronous) need less precision than exceptions.
Partly false — separate the two requirements. When the interrupt is taken is flexible: hardware may delay it and attach it to any convenient instruction boundary. But whatever boundary it picks, the state there must satisfy both precision conditions (older instructions all committed, younger ones all still un-executed), so the handler can resume cleanly. Timing is relaxed; state precision is not.

Spot the error

"I3 faults in EX, so we immediately halt every stage right where it is and jump to the handler."
Wrong — halting immediately freezes I1/I2 mid-way, violating Condition 1. You must stall the front, let older instructions (I1, I2) complete WB first, and only then flush I3 and younger and jump.
"When I3 faults we turn I3 into a NOP but keep executing I4 and I5 normally to save time."
Wrong — I4 and I5 are younger than the faulting instruction and must appear not executed (Condition 2); they must be flushed to NOPs, otherwise they'd corrupt the clean line.
"In the ROB, we take the exception the moment the execute unit detects the fault."
Wrong — the fault is only marked in the ROB entry at detection; the exception is actually taken when that entry reaches the ROB head (in-order), so older instructions still commit first.
"A speculative divide-by-zero on the wrong branch path is a real exception, we just fix it later."
Wrong — it is never a real exception. When the branch resolves as mispredicted, that instruction is flushed and its fault disappears entirely, as if it was never fetched.
"Among faults in the same instruction we always take the arithmetic fault first because it's most severe."
Wrong — priority follows the stage order, not severity: an instruction-fetch fault (IF) outranks an EX arithmetic fault, which in turn outranks a MEM data fault; the earliest stage in the instruction's lifetime wins.
"Stall-on-exception has no performance cost since exceptions are rare."
Only half right — each exception does cost cycles. Concretely, in a -stage pipeline the machine must drain the older instructions and flush the rest, so it loses on the order of cycles per exception (about 5 for the 5-stage pipeline, more for deeper ones). If exceptions occur with frequency (fraction of instructions), the average penalty is roughly cycles per instruction — tolerable only because is tiny for faults like page misses, not because the cost is zero.
"Since the ROB holds results, we can skip having separate architectural registers."
Wrong — you still need architectural registers as the committed checkpoint; the ROB is a temporary staging area, and commit is precisely the act of copying ROB results into that checkpoint.

Why questions

Why can't we just let every instruction write its result the instant it computes it?
Because writes would then happen out of program order, so a younger instruction could overwrite state before an older instruction faults — violating Condition 1's clean prefix and Condition 2's clean suffix.
Why does the ROB commit in order even though it executed out of order?
In-order commit is the only way to guarantee that architectural state at any commit point equals what a sequential machine would have — decoupling fast execution from ordered commitment is the whole trick.
Why does a page fault specifically demand precise exceptions?
The handler must load the missing page and then resume the exact instruction that faulted; if state were imprecise, resuming would either skip work or redo committed work, giving wrong results.
Why do we tag exceptions with a sequence number rather than a timestamp?
Sequence number encodes program order, which is the semantics we must preserve; detection time only tells us which pipeline stage noticed first, which is irrelevant to correctness.
Why is flushing "cheaper" in a stall-on-exception design than it sounds?
Flushing just clears the control bits of in-flight instructions (turning them into NOPs), so they harmlessly drain without writing state — no rollback of committed data is ever required because nothing younger was committed.
Why does speculative execution force us to defer raising exceptions?
Because we don't yet know if a speculatively fetched instruction is on the real path; deferring until branch resolution lets wrong-path faults vanish and only real-path faults survive to commit.
Why does Condition 1 require all older instructions to complete, not just "enough"?
The handler and any later resumption assume a fully sequential prefix; a single un-committed older instruction would make the pre-fault state incomplete and un-resumable.

Edge cases

What if the very first instruction of a program faults?
Then there are no older instructions to commit (Condition 1 is trivially satisfied); you flush the faulting instruction and everything younger, set EPC to that first instruction, and enter the handler with the initial (unmodified) architectural state.
What if the faulting instruction is also a branch that was mispredicted?
Resolve the misprediction first: if the faulting branch itself is on the correct path it still faults and commits its exception; the flush of wrong-path successors happens regardless, and the branch's own fault is judged by whether the branch instruction is committed.
What if two exceptions occur on the same instruction (e.g. an instruction-TLB miss in IF and a divide-by-zero in EX)?
Use the within-instruction priority order — IF faults rank highest, then ID (illegal/privilege), then EX (arithmetic), then MEM (data faults) — and report the highest-priority one, since it would occur earliest in that instruction's sequential lifetime.
What if an older instruction faults after a younger instruction already computed its result out of order?
No problem — the younger result is trapped in its ROB entry and never committed; when the older fault reaches the ROB head we flush all younger entries, discarding that result, so precision holds.
What if the exception handler itself causes an exception (a nested fault)?
The same precise-exception machinery must apply recursively; typically EPC/state is saved to a separate location or the stack before re-entering, and the ISA (see 3.4.7-Exception-handling-in-ISA) defines how nested faults are sequenced so each level stays precise.
What if an instruction faults with zero older instructions in flight but several younger already in WB?
Since younger instructions cannot legally reach WB before an older one in an in-order-commit design, this state is impossible; if it appears, the ordering logic is broken — that's exactly the invariant precise exceptions defend.
Recall One-line summary to lock in

A precise exception keeps a clean line: everything before the fault is fully committed (Condition 1), the fault and everything after is fully erased (Condition 2), EPC points at the fault, and among simultaneous faults the oldest in program order wins — with per-instruction ties broken by stage order IF > ID > EX > MEM.

Where next: review the ROB mechanics in 6.3.4-Reorder-buffer-in-superscalar and the ISA-level save/restore contract in 3.4.7-Exception-handling-in-ISA; or read this bank's ideas in Hinglish.