Level 64: The Short Path

Open level →

The task

Goal: build the Fwd32 block (comparing rs numbers against rdM/rdW, EX/MEM priority over MEM/WB, x0 and lw-from-EX/MEM exceptions), extend IdEx32 with the rs1/rs2 numbers (+10 bits), and add EX operand muxes gated by fwd_en. The level-62 pipeline handled RAW dependencies with honest latency: an instruction read registers in ID while results were written in WB — the value arrived three cycles later, and a close dependency read garbage. You close the hazard with a short path — forwarding: the ALU result from EX/MEM and the write value from MEM/WB feed the next instruction's ALU inputs directly, bypassing the regfile. The manual run on the pipeline engine shows the difference: the golden pays no cycle for RAW, while "hardware with fwd_en=0" reads garbage. Acceptance is exact-step against the engine plus the fwd_en=0 negative.

The level-62 pipeline resolves RAW dependencies with honest latency: an instruction reads registers in ID and writes its result in WB — the value only arrives at distance 4. A close dependency reads garbage. You close the hazard with a short path — forwarding: the ALU result from EX/MEM and the write value from MEM/WB feed the next instruction's ALU inputs directly, bypassing the regfile.

This is a hardware level: editable files are pipetop.v, fwd.v, idex.v; the level-62 pipeline registers and all level-58 core blocks are read-only. Tasks:

  1. Extend IdEx32 to 165 bits. To the level-62 layout add the numbers rs1N[9:5] and rs2N[4:0] — not the values, the numbers from Decoder32: the forwarding block will compare them.
  2. Build Fwd32 (fwd.v). Compare the operand numbers against rdM (EX/MEM) and rdW (MEM/WB): fwdX=01 when regWriteM & rdM!=0 & !memReadM, fwdX=10 when regWriteW & rdW!=0. Priority — EX/MEM over MEM/WB: the fresher value wins. Exceptions: x0 is never forwarded, lw from EX/MEM is not forwarded (the data is not there yet — level 65 fixes that).
  3. Add the EX operand muxes. Per bit, three shoulders: the payload / alu_y from EX/MEM / wdata from MEM/WB; every shoulder bit is gated by fwd_en. Use the forwarded values everywhere: Alu32, the eight I-modules, the alusrc mux, store_val.
  4. Close distance 3. Distances 1–2 are covered by forwarding, 4+ by the regfile; distance 3 needs write-first: if rdWb matches the register being read and regWriteW holds, read wdataWb instead of the regfile q (also under fwd_en).
  5. Run against the golden. The manual run goes on the pipeline engine: the RAW program flows without bubbles (15 instructions, 19 cycles), and with fwd_en=0 the registers fill with garbage.

Solution for self-checking

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

What you should get: exact-step against the pipeline golden passes: the RAW program runs without bubbles — 15 retirements, 19 cycles, nstall = 0, nflush = 0; the fwd_en = 0 negative keeps err = 0, but the registers must differ from the golden.

Step 1 — acceptance programs. A RAW program (distances 1–3, lw→use at distance 2), linear logic, and the fwd_en=0 negative (it must keep err=0 and change the registers — otherwise the unit is not in the datapath).

Step 2 — the Fwd32 comparators. Each one is 5 bits of xor+not and an and-tree. The EX/MEM shoulder: m1 = regWriteM & rdMnz & ~memReadM & eqM; the MEM/WB shoulder: m2 = regWriteW & rdWnz & eqW; the outputs: fwdX[0]=m1, fwdX[1]=m2 & ~m1 (the priority).

Step 3 — the muxes and the bypass. fwa0 = fwdA[0] & fwd_en, fwa1 = fwdA[1] & fwd_en, the zero shoulder ~(fwa0|fwa1); rs1F[b] = rs1E[b]&sel00 | aluYM[b]&fwa0 | wdataWb[b]&fwa1 (same for rs2F). Write-first: wf = eq(rdWb, rs) & weRfWb & fwd_en → the payload pack takes wdataWb instead of the regfile value.

Step 4 — the checkpoint numbers. The RAW program: 15 retires, 19 cycles (15 + 4 fill), no bubbles: nstall=0, nflush=0. Logic: 12/16. The fwd_en=0 negative: the program still halts normally on .end, but the registers must differ from the golden.

Check yourself

Why is lw from EX/MEM not forwarded?

At the EX/MEM stage only the address is ready — the data itself leaves memory at the end of MEM, so there is nothing to forward. The neighbour reading an lw result too early is saved by the level-65 bubble, not by Fwd32: the memReadM exception explicitly blocks the EX/MEM arm.