Level 65: The Load Bubble

Open level →

The task

Goal: build hazard.v (respecting whether the instruction uses rs1/rs2: only lui and jal skip rs1; rs2 is used by R, sw and branches), the next-PC hold, IfId.en = ~stall, IdEx.clr = stall — all under the stall_en gate. The level-64 forwarding closes almost every RAW — except one: lw from EX/MEM is not forwarded because the data is still in memory. If the next instruction uses the lw result, the cycle cannot be saved — a bubble is needed: the Hazard32 detector compares the lw rd in EX against the ID instruction's operands, freezes PC and IF/ID, and puts a bubble into ID/EX — exactly one idle cycle. The acceptance programs pay exactly one bubble after each lw; the stall_en=0 negative must corrupt the result without errors.

The level-64 forwarding closes almost every RAW — except one: lw from EX/MEM is not forwarded, because at that stage only the address is ready, and the data leaves memory at the end of MEM. If the neighboring instruction uses the lw result, the cycle cannot be saved — a bubble is needed: exactly one idle cycle.

Editable files — pipetop.v and hazard.v; the level-64 forwarding and idex are read-only and must stay in the datapath. Tasks:

  1. Build Hazard32 (hazard.v). stall = memReadE & rdE!=0 & (a match with rs1 when usesRs1 | a match with rs2 when usesRs2). Comparators as in fwd.v: not(xor) per bit + an and-tree.
  2. Respect whether the instruction reads the operand. Everything reads rs1 except lui and jal. Only R, sw and branches read rs2 — build the negation: usesRs2 = !(islui | isjal | isOpImm | memRead | isJalr). Number comparison without the masks would give false bubbles.
  3. Take rdE and memReadE from the IdEx payload (the qIx unpack): after the bubble the payload is zeroed by clr, so there is no repeated stall — exactly one bubble.
  4. Route the bubble. The stall_en gate: hold next-PC (add stallG to the hold tree), IfId.en = ~freeze & ~stallG (the instruction is held and re-enters), IdEx.clr = stallG & ~freeze (the err freeze wins).
  5. Count the price. The unrolled four-word sum pays exactly 4 bubbles (one per lw); the mini lw→add program — 1 bubble. The manual run shows the "bubble (stall)" capsules in the EX lane and the nstall counter.

Solution for self-checking

Build it yourself first. The solution below is a checkpoint.

What you should get: the exact-step acceptance passes: the unrolled four-word sum takes 19 retirements and 27 cycles (4 fill + 4 bubbles, nstall = 4); the mini lw→add program is 5/10 with a single bubble; the stall_en = 0 negative computes from a garbage lw value with no errors, but the registers must differ from the golden.

Step 1 — the detector. eq1/eq2 — 5×(xor→not)+and; stall = memReadE & rdNz & ((usesRs1&eq1)|(usesRs2&eq2)). In the top: qMemR = qIx[27], qRd = qIx[35:31], usesRs1 = ~(isjal|islui), usesRs2 = ~(islui|isjal|isOpImm|memRead|isJalr).

Step 2 — the wiring. stallG = stall & stall_en; the hold tree: hold = freeze | isEnd | stallG; IfId.en = nfreeze & ~stallG; IdEx.clr = stallG & nfreeze.

Step 3 — the checkpoint numbers. The unrolled sum: 19 retires, 27 cycles (12 + 4 fill + 4 bubbles), nstall=4. Mini lw→add: 5/10 (5 + 4 + 1 bubble), nstall=1. The stall_en=0 negative: the sum is computed from a garbage lw value — err=0, but the registers must differ from the golden.

Check yourself

Why is the bubble placed in ID/EX rather than in EX?

The conflict is between lw in EX and its consumer in ID: PC and IF/ID freeze (the instruction re-enters), and clr wipes ID/EX — EX receives emptiness instead of garbage operands. A cycle later the lw itself reaches MEM/WB, and the level-64 forwarding delivers the value.