5.2.10 · D3Processor Datapath & Pipelining

Worked examples — Hazard detection units

3,502 words16 min readBack to topic

This page is the drill ground for Hazard detection units. We will run one instruction sequence at a time through the pipeline and decide — for every case class — whether the hazard unit must stall, whether the forwarding unit can rescue us, or whether nothing needs doing at all.

Before we start, one tiny reminder so no symbol sneaks in undefined.

Figure — Hazard detection units

The scenario matrix

Every question this topic can ask lands in exactly one of these cells. The rest of the page fills each one.

# Case class Trigger What the hazard unit does Example
A No dependency reads/writes don't overlap nothing Ex 1
B ALU-ALU RAW, 1 apart prev instr result feeds next nothing — forwarding fixes it Ex 2
C Load-use, 1 apart lw then instr reads its Rd STALL 1 cycle, then forward Ex 3
D Load-use, 2 apart one instr sits between them nothing — forwarding fixes it Ex 4
E Degenerate: Rd = $0 destination is register zero nothing (never stall on $0) Ex 5
F Both Rs and Rt depend instr reads the loaded reg twice still one stall Ex 6
G Store-use RAW sw stores a just-produced value nothing — forwarding fixes it Ex 7
H Control hazard, taken branch changes PC FLUSH wrong-path instr Ex 8
I Control hazard, not-taken branch falls through nothing (guess was right) Ex 9
J Unconditional jump (j/jr) PC always changes FLUSH one instr Ex 10
K Exam twist: stall + forward chained lw then two dependents one stall + two forwards Ex 11
Recall What is the ONLY data hazard that forces a stall?

A load-use hazard: lw in EX and the very next instruction reads its Rd. ::: Because the loaded value isn't ready until the end of MEM, so there is nothing to forward yet.


Example 1 — Cell A: no dependency at all

Steps

  1. List what sub reads: Rs = $4, Rt = $5. Why this step? The hazard unit only ever compares the reads of the younger instruction against the writes of older ones. If nothing is read that was just written, there is no dependency.
  2. List what add writes: Rd = $3 (and add.RegWrite = 1, so it is a real destination). Why? $3 is the only register add could create a hazard on — and only because RegWrite = 1.
  3. Compare: is $3 == $4? No. Is $3 == $5? No. Why? The load-use condition and the forwarding condition both hinge on these equalities. Both fail.

Answer: Do nothing. Full speed, zero bubbles.

Recall Verify

Cycles used = 2 instructions overlapped normally, no extra cycles. Penalty = 0.


Example 2 — Cell B: ALU→ALU RAW, forwarding rescues it

Steps

  1. add finishes computing $1 at the end of its EX stage — the value is sitting in the EX/MEM register. Since add.RegWrite = 1, this value is a legitimate forwarding source. Why? An ALU result exists the moment EX completes; we don't need to wait for WB. The RegWrite check is what lets forwarding trust the Rd field.
  2. sub reaches EX exactly one cycle later. At that instant add's value is in EX/MEM. Why this matters: the forwarding unit can route EX/MEM straight into the ALU input. The data is already alive when needed.
  3. Hazard unit check: is this a load? add.MemRead = 0. So the load-use stall condition is false. Why? Stalling is reserved for the one case forwarding cannot cover. This isn't it.

Answer: No stall. Forwarding unit selects EX/MEM → ALU. Penalty = 0.

Figure — Hazard detection units
Recall Verify

The red arrow in the figure hands $1 from EX/MEM into sub's EX. Timing lines up → 0 stall cycles.


Example 3 — Cell C: load-use, the classic stall

Steps

  1. Evaluate the load-use condition (the producer lw is in ID/EX, the consumer add is in IF/ID): Here MemRead = 1, and Rd = $2 = Rs of add. Condition TRUE. Why this step? This is the exact combinational test the unit performs. IF/ID.Rs/IF/ID.Rt are add's read registers; ID/EX.Rd is lw's destination. When it fires, forwarding is provably too slow.
  2. Assert the three stall signals defined in the reminder: PCWrite = 0, IF/ID_Write = 0, Control_Mux = 0. Why? PCWrite = 0 and IF/ID_Write = 0 freeze the PC and the ID box so add retries next cycle; Control_Mux = 0 zeroes the control bits so the bubble going into EX does nothing.
  3. Next cycle, lw's data reaches MEM/WB; add (held back one cycle) now enters EX and forwards from MEM/WB. Why it works: the one-cycle delay is exactly enough for the loaded value to become forwardable.

Answer: 1 stall cycle, then forward. Penalty = 1.

Figure — Hazard detection units
Recall Verify

Without detection add reads stale $2. With detection: 1 bubble → total cycles for the pair rises by exactly 1. Penalty = 1.


Example 4 — Cell D: load-use, but two instructions apart

Steps

  1. When add is in ID, lw is no longer in EX — it has advanced to MEM/WB. Why this step? The load-use condition checks ID/EX.MemRead. But lw is in MEM/WB now, not ID/EX. So the stall condition is false.
  2. add reaches EX when lw's data sits in MEM/WB (with lw.RegWrite = 1, that box is a valid forward source). Why it matters: forwarding from MEM/WB → ALU is legal and the data is ready.
  3. Hazard unit does nothing; forwarding unit does the routing. Why? The gap instruction bought exactly the one cycle a load needs.

Answer: No stall. Penalty = 0. (This is why compilers reorder code to fill load delay slots.)

Recall Verify

Cycles: lw, or, add pipeline with no bubble → 0 extra cycles.


Example 5 — Cell E: degenerate destination $0

Steps

  1. In MIPS, register $0 is permanently 0; writing to it is discarded. Why this step? If a value never actually changes, there is no dependency to protect — reading $0 always gives 0, load or no load.
  2. So the real condition includes an extra guard: ID/EX.Rd ≠ 0. Why? Without it, any instruction reading $0 after a bogus lw $0 would stall needlessly — pure waste.
  3. Here Rd = $0, so the guard ID/EX.Rd ≠ 0 evaluates to false, forcing the whole AND to FALSE. Why? The stall condition is a big AND; one false term (the $0 guard) kills it no matter how many register numbers match. That is exactly what we want — never stall for a value that can't change.

Answer: No stall. Penalty = 0. Always add the Rd ≠ $0 guard.


Example 6 — Cell F: loaded register read twice

Steps

  1. Load-use condition uses Rs == Rd OR Rt == Rd. Why this step? It is a single boolean OR — either match is enough to fire it once.
  2. Both disjuncts are true, but OR of true-or-true is still just one true signal. Why? The stall is a decision about the whole instruction, not per-operand. One instruction stalls one cycle regardless of how many operands clash.
  3. After the single bubble, forwarding delivers $2 to both ALU inputs from MEM/WB.

Answer: 1 stall cycle (not two). Penalty = 1.

Recall Verify

OR(True, True) = True → one stall assertion → 1 bubble.


Example 7 — Cell G: store-use RAW (a store needs a fresh value)

Steps

  1. Identify sw's reads: sw $4, 0($5) reads $5 (the address base) and $4 (the data to store). Both are Rs/Rt fields. Why this step? A store reads registers just like any instruction; the hazard machinery compares those reads against older writes. Here add.Rd = $4 matches sw's data register.
  2. add.RegWrite = 1 and add.MemRead = 0, so add is an ALU producer, not a load. Why it matters: the load-use stall condition needs MemRead = 1 on the producer. add fails it → no stall. The store's own RegWrite = 0 is irrelevant here because sw is the consumer, not the source.
  3. add's result sits in EX/MEM (then MEM/WB) exactly when sw needs it. The forwarding unit routes it into sw's pipeline register before the MEM stage writes memory. Why? Store data is only consumed at the MEM stage — even later than an ALU input — so forwarding always has time. Stores never cause a load-use stall.

Answer: No stall. Forward $4 into the store data path. Penalty = 0.

Recall Verify

Producer add: MemRead = 0 → load-use condition false → 0 stall cycles.


Example 8 — Cell H: control hazard, branch taken

Steps

  1. Resolve the branch early (in ID): compare $1 and $2 with a comparator, compute target with an adder. This sets Branch = 1 and, since equal, BranchTaken = 1. Why this step? Resolving in ID means we learn "taken" one cycle after fetching beq — so only one wrong instruction (add) got fetched.
  2. Flush condition (using the flush signals from the reminder): . Both true → flush. Why? We must cancel the already-fetched wrong-path instruction before it corrupts state.
  3. Set IF/ID = NOP (turn add into a bubble) and load PC with Label's address. Why? The bubble does nothing in later stages; the corrected PC fetches sub next.

Answer: 1 flush (1 penalty cycle) with ID-stage resolution.

Figure — Hazard detection units
Recall Verify

Penalty with ID resolution = 1 cycle (one wrong fetch). If it resolved in EX instead, penalty would be 3. ID resolution → penalty 1.


Example 9 — Cell I: control hazard, branch NOT taken

Steps

  1. Under the simple predict-not-taken policy, the pipeline keeps fetching sequentially. Why this step? Guessing "not taken" means we speculatively fetch the fall-through instruction add.
  2. Branch resolves: Branch = 1, BranchTaken = 0 → the guess was correct. Why it matters: add was exactly the right instruction. Nothing to undo.
  3. Flush condition: . No flush.

Answer: 0 penalty. This is why not-taken prediction is free on fall-through. (See 5.2.11-Branch-prediction for smarter guessing.)

Recall Verify

AND(1, 0) = 0 → no flush → penalty 0.


Example 10 — Cell J: unconditional jump (j / jr)

Steps

  1. Recognise j (jump to a fixed address) and jr (jump to the address held in a register) as instructions that change the PC unconditionally. Why this step? Unlike beq, there is nothing to compare — the "taken" outcome is fixed at 1. So Flush for a jump is just Jump = 1 (no BranchTaken term needed).
  2. In the cycle after fetching j, we have already fetched the fall-through add. It is on the wrong path with certainty. Why it matters: since a jump is always taken, the speculatively fetched add is always garbage — there is no lucky "not-taken" case.
  3. Flush that one instruction: set IF/ID = NOP, and load PC with Target (for j) or with the register value (for jr, which is known after ID reads the register). Why? One wrongly-fetched instruction, one bubble — same 1-cycle penalty as a taken branch when the jump target is computed in ID.

Answer: 1 flush (1 penalty cycle) for a jump resolved in ID. Unlike a branch, this cost is unavoidable — you can't predict-not-taken your way out of an always-taken jump.

Recall Verify

Jump always redirects → always flush 1 fall-through instr → penalty 1 (ID resolution).


Example 11 — Cell K (exam twist): stall then two chained forwards

Steps

  1. lwadd is a load-use hazard (Ex 3 pattern). Stall 1 cycle. Why this step? add reads $2 while lw is in EX → forwarding impossible → one bubble.
  2. After the bubble, add executes and forwards $2 from MEM/WB. Why? By now the loaded value is forwardable — exactly what the stall bought us.
  3. sub reads $4 (produced by add, forward from EX/MEM) and $2 (from lw, now in WB — read normally or forward from MEM/WB). Both are pure ALU/available forwards → no extra stall. Why? Only load-use stalls. add is not a load, so sub's dependency on it needs only forwarding.

Answer: Total penalty = 1 stall cycle, plus forwarding into both add and sub. The exam trap is thinking sub adds a second stall — it does not.

Recall Verify

Load-use stalls fire only when the producer is a load in EX. Only lw→add qualifies → 1 stall total.


Wrap-up: the decision you now run automatically

The flowchart shows how the forwarding unit picks its source: it prefers EX/MEM (the most recent producer) over MEM/WB, and it only forwards after confirming the producer's RegWrite = 1 and Rd ≠ 0.

yes

yes

no

no

yes

no

yes

no

Younger instr reads a register Rx

Producer is a load in EX?

Rd equals Rx and Rd not zero?

STALL one cycle then forward

No hazard

EX MEM RegWrite and Rd equals Rx and Rd not zero?

FORWARD from EX MEM most recent

MEM WB RegWrite and Rd equals Rx and Rd not zero?

FORWARD from MEM WB

This ties back to pipeline control signals (the stall/flush lines) and the control unit that hosts this logic. For the Hinglish walkthrough see 5.2.10 Hazard detection units (Hinglish).