5.2.7 · D2Processor Datapath & Pipelining

Visual walkthrough — Load-use hazard and stalls

3,693 words17 min readBack to topic

This page rebuilds the central result of the parent topic from absolute zero. If a word looks unfamiliar, it gets a picture before it gets used.


Step 1 — What a pipeline stage even is

WHAT. A modern processor does not finish one instruction before starting the next. It splits the work into five little workshops and lets an instruction move through them one per clock beat, like a car down an assembly line. The five workshops (the Five-stage MIPS pipeline) are:

  • IFInstruction Fetch: grab the instruction from memory.
  • IDInstruction Decode: read the register values it needs.
  • EXExecute: the ALU does the arithmetic.
  • MEMMemory: talk to data memory (only loads/stores use this).
  • WBWrite Back: write the result into a register.

WHY these five and not one big blob? Because if each workshop is busy every beat, you finish (almost) one instruction per beat instead of one every five beats. That is the entire point of pipelining — throughput.

PICTURE. Below, one instruction is a horizontal strip sliding rightward, one box per drumbeat of the machine. That drumbeat is what we now name.

Figure — Load-use hazard and stalls

Step 2 — Stacking two instructions: the diagonal

WHAT. Put two instructions in the pipeline back-to-back. The second one starts one cycle later than the first. Draw both and they form a staircase — each instruction's strip is shifted one box to the right of the one above.

WHY a diagonal? Because the pipeline can only fetch one new instruction per cycle. So instruction #2 cannot enter IF until #1 has left IF. That forced one-cycle offset is the source of every timing story on this page — remember it.

PICTURE. Two strips, offset by one. Follow the yellow "offset" arrow: the second row is shoved right by exactly one cycle.

Figure — Load-use hazard and stalls

Now let us pin down the exact cycles each stage lands in. If instruction #1 starts IF in cycle 1:

Instruction #1
IF=1, ID=2, EX=3, MEM=4, WB=5
Instruction #2 (one cycle later)
IF=2, ID=3, EX=4, MEM=5, WB=6

Hold onto those two numbers: #1's MEM is cycle 4, and #2's EX is cycle 4. They collide. Step 4 is where that collision bites.


Step 3 — When is a value produced, and when is it needed?

WHAT. Every instruction has a moment it produces its result and a moment a follower needs it. We must locate both on the clock.

  • A normal ALU instruction (like add) produces its answer during EX.
  • A load (lw) does not compute — it reads memory, so it produces its value during MEM.
  • Any instruction that reads registers to feed the ALU needs those values at the start of EX.

WHY does the moment matter? Because we are about to ask: can we hand the value over in time? A hand-off in time only works if produced ≤ needed. If the value is born after it is needed, no clever wiring saves us.

PICTURE. Two vertical event-markers: a green "PRODUCED" flag and a red "NEEDED" flag, placed on the stages where each event happens for a load and its follower.

Figure — Load-use hazard and stalls

Step 3b — The actual forwarding wires (so nothing is hand-waved)

WHAT. "Forwarding" is not one magic wire; it is a small, fixed set of named bypass paths. Each path starts at a pipeline register (a latch named for the two stages it sits between, e.g. EX/MEM holds a result after EX and before MEM) and ends at a consumer input:

  • EX/MEM → EX: last instruction's EX result, one cycle old, into this instruction's ALU. (Fixes addadd back-to-back.)
  • MEM/WB → EX: a result two cycles old (from EX/MEM, or a load's memory value) into the ALU. (Fixes add→gap→add, and — after a stall — load→add.)
  • MEM/WB → MEM: a value delivered into the store-data port of a later store. (Fixes load→store, Step 6.)
  • MEM/WB → ID: a value routed into the register-compare inputs of an early-resolving branch sitting in ID. (Needed only for the branch case in Step 7; drawn there.)

WHY name them all? Because the entire load-use argument is "which of these wires can reach in time, and which cannot." The impossible path in Step 4 is precisely a MEM/WB → EX in the same cycle the memory read is happening — that path physically does not exist because the value is not in the MEM/WB latch yet.

PICTURE. A stage strip IF·ID·EX·MEM·WB with the bypass arrows drawn and labelled by source latch and destination input.

Figure — Load-use hazard and stalls

Step 4 — The collision: load's value is born one cycle too late

WHAT. Take the classic pair and overlay the produce/need flags on the real cycle numbers:

lw   $2, 20($1)     # value of dest reg produced in MEM
add  $4, $2, $3     # value of that reg needed at start of EX

From Step 2: the load's MEM is cycle 4; the add's EX is cycle 4.

  • The load's $2 is not ready until the end of cycle 4 (memory read finishes then).
  • The add wants $2 at the start of cycle 4 (its ALU fires then).

WHY forwarding cannot rescue this. A forwarding wire is spatial: it moves a value sideways between stages, always forward in clock time (into this cycle or a later one). Here the need happens at the start of cycle 4 and the value appears at the end of cycle 4 — later within the very same cycle. To satisfy it you would need a MEM/WB → EX wire firing before the MEM/WB latch has been loaded, i.e. a value flowing backward in time inside one cycle — physically impossible. Contrast the ALU-ALU case, where the producer's EX finishes at end of cycle 3 and the follower's EX needs it at start of cycle 4: there, EX/MEM → EX moves forward in time and works. Loads are special only because their birth stage (MEM) sits one stage later.

PICTURE. Red "impossible" arrow trying to point leftward (backward) in time from the end-of-cycle-4 value to the start-of-cycle-4 need. The arrow is crossed out.

Figure — Load-use hazard and stalls

Step 5 — The cure: insert exactly one bubble

WHAT. We delay the add (and everything behind it) by one cycle. Its EX slides from cycle 4 to cycle 5. Now:

  • Load's $2 ready: end of cycle 4 ().
  • add needs $2: start of cycle 5 (). Since , ✅ the inequality holds.

The value now lives in the load's MEM/WB register and the MEM/WB → EX wire from Step 3b delivers it in cycle 5.

WHY only one bubble, and how do we implement the delay? The failure was by one instant inside one cycle, so pushing the need into the next cycle closes it exactly. The Hazard detection unit performs the delay with three simultaneous moves (see Pipeline control signals):

  1. Freeze the PC — don't fetch anything new (the same next instruction is re-read).
  2. Freeze the IF/ID register — the add re-decodes next cycle as if for the first time.
  3. Zero the ID/EX control signals — the instruction currently leaving ID becomes a NOP: no register write, no memory touch, no real op. That zeroed slot travelling down the pipe is the bubble we just defined.

PICTURE. The staircase from Step 2, now with a grey NOP box wedged into the add's row, pushing its EX to cycle 5. A green forwarding arrow runs from the load's MEM/WB into the add's EX.

Figure — Load-use hazard and stalls

Step 6 — The forgiving case: load → store needs no bubble

WHAT. Not every use of a loaded value collides. A store consumes its store-data register late, in MEM, not in EX:

lw  $2, 0($1)     # dest reg produced in MEM  (cycle 4)
sw  $2, 4($1)     # same reg needed in MEM    (cycle 5)

Cycles: lw MEM = 4. sw (one cycle behind) MEM = 5.

  • Produced: end of cycle 4 (). Needed: cycle 5 (, its MEM). Since , the inequality holds — the MEM/WB → MEM wire from Step 3b carries $2 across with no stall.

WHY this matters. It proves the hazard is about which stage needs the value, not about "loads are slow." Change the consumer's need-stage from EX to MEM and the whole problem evaporates.

PICTURE. Same staircase, but now the need-flag for the store sits on MEM (cycle 5), safely after the load's produced-flag (end of cycle 4). Green forwarding arrow, no bubble.

Figure — Load-use hazard and stalls

Step 6b — The scheduling escape: slip an independent instruction in

WHAT. Put any instruction that does not use $2 between the load and the use:

lw   $2, 20($1)   # dest reg produced in MEM  (cycle 4)
and  $7, $8, $9   # independent filler, does not touch $2
add  $4, $2, $3   # needs $2 in EX

Cycles (each row starts IF one cycle after the row above): lw IF=1, so MEM=4. and IF=2. add IF=3, therefore its EX = 3 + 2 = cycle 5.

  • Produced: end of cycle 4 (). Needed: start of cycle 5 (). Since , the inequality holdsMEM/WB → EX delivers the value, no bubble.

WHY it works. The filler instruction pushes the dependent add one cycle later without wasting a slot on a NOP — the real work of and fills the gap that a bubble would otherwise fill. This is exactly what Compiler instruction scheduling and out-of-order hardware do: reorder real work into the delay slot.

PICTURE. Three-row staircase; the independent and sits in the row between load and use, and the add's EX (cycle 5) is safely after the load's produced-flag (end of cycle 4).

Figure — Load-use hazard and stalls

Step 7 — The nastier edge case: load feeding a branch (two bubbles)

WHAT. A branch (like beq) compares two registers to decide where to jump. In the classic five-stage MIPS the comparison is resolved early, in ID, to keep the branch penalty small (see Branch hazards and prediction). So a branch needs its source registers at the start of ID, not EX. Now feed one of them from a load:

lw   $2, 0($1)    # dest reg produced in MEM (cycle 4)
beq  $2, $3, L    # compares $2, needs it in ID (naively cycle 3)

Cycles: lw MEM = 4. beq (one cycle behind) ID = 3.

  • Produced: end of cycle 4 (). Needed: start of cycle 3 (). Since the inequality fails by a wide margin — the need comes a whole cycle plus an instant earlier.

WHY two bubbles here, not one. We must slide the branch's ID from cycle 3 to a cycle whose start is after . Cycle 4's start () is still before , so that is not enough — we need cycle 5, whose start . Moving ID from cycle 3 to cycle 5 is a two-cycle delay. Only then does the MEM/WB → ID wire (the fourth path added to Step 3b) deliver $2 into the branch's comparator in time. Loads feeding an early-compare branch are the worst load-use case.

HOW the hardware inserts two bubbles. The Hazard detection unit repeats its Step-5 recipe for two consecutive cycles instead of one (see Pipeline control signals):

  1. Cycle A: freeze PC, freeze IF/ID, zero the ID/EX control signals → first bubble enters the pipe, beq stays in ID.
  2. Cycle B: detect the hazard again (the load has only reached WB, value still not committed to the register file), freeze PC and IF/ID once more, zero ID/EX again → second bubble enters, beq still held in ID.
  3. Cycle C (= cycle 5): the load's value now sits in the MEM/WB latch; the MEM/WB → ID forward feeds the comparator, the branch resolves, and normal fetching resumes.

PICTURE. Load row and branch row; the branch's ID-need flag sits at cycle 3, far before the load's cycle-4 produced flag; two grey bubbles push ID to cycle 5, and a green MEM/WB → ID arrow delivers the value there.


Step 8 — Counting the cost: from one snippet to average CPI

WHAT. Turn the single bubble into a performance number. Take the three-instruction snippet lw; add; sub.

  • Ideal (no hazard): instructions in a 5-stage pipe finish in cycles. Here cycles.
  • With one required bubble: cycles.

WHY the ? The first instruction needs all 5 stages ( cycles), and each later instruction adds just more cycle (the diagonal from Step 2). So .

Now the average over a program. Define CPI = cycles per instruction. Each unavoidable bubble adds one stall cycle, weighted by how often it happens:

With loads = of instructions and half of them immediately used:

PICTURE. A bar chart: ideal 7 cycles vs stalled 8 cycles for the snippet, and a second bar pair for CPI 1.00 vs 1.10 — the red sliver on top is the tax the load-use hazard levies.


The one-picture summary

The final figure stacks the whole story: the load's row with its value born at the end of MEM (cycle 4, green flag); the naive add colliding at cycle-4 EX (red, impossible backward arrow); the bubble wedged in; and the fixed add at cycle-5 EX receiving the value via a legal forward-in-time green wire. One cycle lost — the price of a load-use hazard.

Recall Feynman retelling — say it to a friend

Imagine an assembly line with five stations, and a new part enters one beat after the previous one, so parts run on a staircase. A load part goes to the memory station (station 4) to pick up a value, and that value only lands in its hands at the very end of station 4. The part right behind it reaches the arithmetic station (also at beat 4) and, at the very start of that beat, holds out its hand for that value. But the value doesn't exist yet — it appears at the end of the beat, not the start. There is no wire that sends a value back in time within one beat, so the follower must wait. We freeze the front of the line for exactly one beat and slip an empty "do-nothing" slot — a bubble — into the pipe. Now the follower reaches the arithmetic station one beat later, at which point the value is already sitting in a holding latch and a normal forward wire hands it over. One wasted beat, no more, no less — unless the follower is a branch that needs the value even earlier (at decode), in which case it waits two beats; and if the follower merely stores the value (needs it late, at the memory station), it catches it with no wait at all. Multiply "how often this happens" by "cycles lost" and you get the little tax on CPI: becomes about .

Recall Quick self-test

Why can't forwarding fix a load-use hazard? ::: The value is produced at the end of the load's MEM stage (), but the dependent ALU needs it at the start of its EX stage () — the same cycle, an earlier instant. Forwarding moves data between stages but never backward in time within a cycle. How many bubbles does a load feeding an immediate add need? ::: Exactly one — the need-instant must be pushed to , one cycle. A load feeding another load that uses it as an address — stall or not? ::: One bubble — the second load computes its address in EX, so it needs the value at , exactly the ALU case. How many bubbles does a load feeding an early-compare beq need? ::: Two — the branch needs its register in ID (cycle 3), so ID must be pushed to cycle 5. Why does lw; sw $same need no stall? ::: The store consumes its data in MEM (cycle 5), which is after the load produces it (end of cycle 4), so holds. Give the CPI formula for load-use stalls. ::: , where = fraction of instructions incurring a load-use stall.

See also: Hazard detection unit · Data hazards and forwarding · Pipeline control signals · Compiler instruction scheduling · Branch hazards and prediction · Memory hierarchy and cache