5.2.7 · D5Processor Datapath & Pipelining

Question bank — Load-use hazard and stalls

1,609 words7 min readBack to topic

Before you start, a shared vocabulary so every reveal reads cleanly:


True or false — justify

A load always inserts exactly one bubble regardless of the instruction after it.
False — the bubble depends on the consumer. If the next instruction is independent (or is a store using MEM/WB→MEM forwarding), zero bubbles are needed.
Forwarding is useless for load-use hazards.
False — forwarding is essential after the one stall: it routes the value from MEM/WB→EX. Without forwarding you'd need two stalls, not one.
A load-use hazard needs a stall because memory is slow.
False — even with instant memory the value is only defined at the end of MEM, one workbench too late for the consumer's EX start. It's a timing-alignment problem, not a speed problem.
The stall freezes the load instruction so it finishes later.
False — the load flows through EX, MEM, WB at full speed. We freeze IF and ID (the consumer and everything behind it), never the load itself.
sw $2, 0($1) right after lw $2, 4($1) requires a stall.
False — a store consumes its data register only in MEM, which for the store lands one cycle after the load's MEM. A MEM/WB→MEM forward delivers it with no stall.
If the loaded register is $0, a stall is still inserted.
False — $0 is hardwired to zero, so there is no real data dependency. Condition 3 of the detection rule (RegisterRt ≠ 0) suppresses the stall.
A stall and a compiler-inserted nop produce the same pipeline timing.
True — both give the consumer one extra cycle before EX, so the load's value is ready for a MEM/WB→EX forward. The difference is who pays: hardware (stall) versus code size (nop).
Stalling only the ALU result of the load would also fix the hazard.
False — the load produces no ALU result to stall; its useful value comes from MEM. The fix is to delay the dependent instruction, not tweak the load's ALU stage.
Load-use hazards and ALU-ALU hazards are both solved the same way.
False — ALU-ALU hazards are fully solved by forwarding (data exists at end of EX, in time). Load-use hazards need a stall plus forwarding, because the data isn't ready when EX begins.

Spot the error

"Insert a stall whenever the previous instruction is a load."
Incomplete — a load only causes a stall if the next instruction actually reads the loaded register. An independent following instruction needs no bubble.
"Detection rule: stall if ID/EX.MemRead = 1."
Missing conditions — you also need a register match (loaded Rt equals the next instruction's Rs or Rt) and Rt ≠ 0. MemRead alone flags every load, not just the dangerous ones.
"During the stall we zero the PC so no new instruction is fetched."
Wrong action — we freeze the PC (hold its value), not zero it. Zeroing would jump execution to address 0; freezing simply re-fetches the same instruction next cycle.
"To make a bubble we clear the IF/ID control signals."
Wrong register — we zero the ID/EX control signals so the EX/MEM/WB path does nothing. IF/ID is frozen (held), not cleared.
"A load-use hazard means the load reads a stale register value."
Wrong direction — the load's inputs are fine; the danger is the load's output being consumed by a later instruction before it's produced. It's a producer-too-late problem, not a stale-input problem.
"With a 2-cycle stall we could drop forwarding entirely for loads."
Overkill but the reasoning is wrong-flavoured — two stalls would let the value reach the register file (WB) before the consumer's ID re-read, avoiding forwarding, but real designs use one stall + forwarding because it's cheaper in cycles.
"Stores never have data hazards."
False as stated — a store can depend on a prior load, but because it needs the value in MEM (late), forwarding covers it without a stall. The hazard exists; it just resolves for free.

Why questions

Why can forwarding move data between stages but not fix the load-use case by itself?
Forwarding moves data spatially (from a later pipeline register back to EX) but cannot move it backwards in time. The load's value simply doesn't exist yet at the start of the consumer's EX.
Why does the detection unit sit between IF and ID rather than after EX?
It must catch the hazard before the dependent instruction commits to EX, so it inspects the instruction in ID (the consumer) against the instruction already in EX (the load). That's exactly the IF/ID vs ID/EX boundary.
Why do stores tolerate a preceding load when ALU instructions don't?
A store needs its data register only in MEM, which arrives one cycle after the load's MEM — late enough for a MEM/WB→MEM forward. An ALU op needs it in EX, one cycle too early.
Why is condition 3 (Rt ≠ 0) part of the rule at all?
Because MIPS $0 always reads as zero and is never truly "produced," an instruction reading $0 has no real dependency. Skipping the stall here avoids a pointless bubble.
Why does one stall cycle, not two, suffice once forwarding exists?
One stall pushes the consumer's EX to the cycle after the load's MEM completes, so the freshly available MEM/WB value can be forwarded straight into EX. The single delay closes the exact one-cycle gap.
Why does compiler scheduling reduce CPI without changing the hardware?
By moving an independent instruction into the slot right after the load (see Compiler instruction scheduling), the consumer arrives at EX one cycle later naturally, so the hardware never needs to insert a bubble.
Why doesn't a better cache eliminate the load-use hazard?
A cache shortens average memory latency but the pipeline still schedules the load's value at end-of-MEM. The producer-consumer timing gap is structural, independent of how fast the cache is (see Memory hierarchy and cache).

Edge cases

lw $2, 0($1) followed by add $4, $5, $6 (uses neither $2).
No stall — the register-match condition fails, so the hazard detection unit lets both instructions flow at full speed.
lw $2, 0($1); lw $3, 0($2) — the second load uses $2 as its base address.
Stall required — the second load needs $2 in EX to compute its address, which is exactly the load-use timing clash. One bubble, then MEM/WB→EX forward.
lw $0, 0($1) followed by add $4, $0, $3.
No stall — the loaded register is $0, so condition 3 suppresses detection. The add sees zero either way, so there's no genuine dependency.
lw $2, 0($1) where the second-next instruction uses $2, but the immediate next does not.
No stall — with one independent instruction between them, the consumer already reaches EX in the cycle after the load's MEM, so ordinary MEM/WB→EX forwarding handles it.
A load whose consumer uses the value as both source operands (add $4, $2, $2).
Still just one stall — the match fires on Rs or Rt; using it twice doesn't change the timing, only one bubble is needed.
Back-to-back loads to the same register: lw $2, 0($1); lw $2, 8($1).
No load-use stall between them — the second load doesn't read $2 as an operand (its base is $1), so there's no dependency; the later $2 value simply overwrites the earlier.
Interrupt/flush arrives during the stall cycle.
The bubble is already a NOP in EX, so flushing costs nothing extra there; the frozen IF/ID instruction is discarded like any other on a flush (interaction studied under Pipeline control signals).
Recall Quick self-check

The three stall conditions in one breath ::: load in EX (MemRead=1), its Rt matches the next instruction's Rs or Rt, and that Rt isn't $0. Who gets frozen, who runs free ::: freeze PC + IF/ID (consumer side); let EX/MEM/WB (the load) run normally. Why one stall not two ::: forwarding covers the remaining gap via MEM/WB→EX.