Level 62: Between Stages
The task
Goal: slice the level-58 single-cycle datapath into five stages and place four pipeline registers between them: IF/ID (pc and instruction), ID/EX (operands, control, rd), EX/MEM (ALU result, store data, pc), MEM/WB (write value, rd). Each register is a dff bundle with en/clr inputs; en is dropped on an error (freeze), clr is tied to zero at this level. The top is PipeTop32: PC+4 → fetch → decode → ALU → memory → write-back; a bad access freezes the pipeline (en=0, PC hold, we-gates), and the .end word holds the PC in place. Acceptance is exact-step: the core state after EXACTLY refCycles cycles must match the single-cycle golden, and match again after +8. Acceptance programs are linear: no branches (branch resolution comes on 66), no dependencies closer than 4 instructions apart (forwarding — 64), no load-use (the bubble — 65): the 62 pipeline honestly pays latency, and that is why it is exact.
The level-61 pipeline ran programs on the reference engine. Now you build the pipeline yourself — structurally, from the ready-made blocks of the level-58 core and four pipeline registers: IfId32 (between fetch and decode), IdEx32 (between decode and ALU), ExMem32 (between ALU and memory), MemWb32 (between memory and write-back). The level name is literal: all the work happens between the stages, reliably carrying the instruction payload from stage to stage.
Each register is a dff bundle with en (clock enable) and clr (clear, a constant at this level). The idiom is the same for all four: dd = ((d & en) | (q & ~en)) & ~clr, a dff per bit through generate.
The top is PipeTop32: the contract is fixed up to level 66 — (clk, fwd_en, stall_en, flush_en, switches_in, pc, err, leds). The enable ports are not wired at 62: forwarding (64), the load bubble (65) and branch flushing (66) come later. That is why the acceptance programs are linear: no branches, no dependencies closer than 4 instructions (the regfile reads in ID and writes in WB — there is no bypass for values in flight), and no load-use.
The key top mechanisms:
- The error freeze.
err != 0→en=0of all four registers, PC hold, DMEM/MMIO we-gates. The regfile write is not gated: the instruction in WB is older than the faulting one — its result must land. - The pc output on error is the faulting instruction's PC taken from the ExMem payload (the freeze "rolls back" the counter to the fault site, like the single-cycle golden).
- The .end halt. The word
32'h0000006Fholds the PC in place (an eq-tree on the fetch). Repeated .end copies write x0 — harmless. - Valid bits. After reset the pipe is full of zero words: the word 0 decodes as illegal. The MSB of each payload means "a real instruction"; illegal is gated by valid — a clean fill with no false errors.
Solution for self-checking
Assemble it yourself first. The solution below is a checkpoint.
What you should get: the exact-step acceptance passes: the core state after exactly refCycles cycles matches the golden bitwise (walk: 11 retirements, refCycles = 15, halt PC 0x2C); the negative lw x6, 2(x5) yields err = 1 with PC = 4 and a side-effect-free freeze.
Step 1 — the registers. Widths: IfId 65 (pc · instr · valid), IdEx 155 (pc · rs1_val · rs2_val · imm · rd · regWrite · aluSrc · memWrite · memRead · jal · jalr · lui · illegal · iSel[9] · aluOp), ExMem 106 (alu_y · store_val · pc · rd · regWrite · memWrite · memRead · illegal · valid), MemWb 39 (wdata · rd · regWrite · valid). The MSB of each payload is valid.
Step 2 — the valid chain. The IfId valid input = constant 1 (a fetch is always real — the PC never produces fetch bubbles); valid then rides through all registers and gates illegal at the err_unit input: the garbage word 0 after reset raises no err.
Step 3 — the top. PC+4 → IMEM → IfId → Decoder + Control + ImmGen + RegFile(read) → IdEx → Alu32 + 8 I-modules (the I-module selection is a one-hot iSel[9:0] computed in ID and carried through IdEx), alu_y = jal/jalr ? pc+4 : lui ? imm : alu → ExMem → Dmem/Mmio (we gated by the freeze) + ErrUnit → wdata = memRead ? memRd : alu_y → MemWb → RegFile(write).
Step 4 — checkpoint numbers. The acceptance "walk" (12 words): 11 instructions = 11 retires, refCycles = 15 (11 + 4 fill), halt PC 0x2C. Memory after the run: 0x1000 = 3, 0x1008 = 9, 0x100C = 0x7F0, 0x1010 = 20; x10 = 3, x11 = 9.
Step 5 — the negative. lui x5, 0x1; lw x6, 2(x5); .end → DATA_MISALIGNED (err=1), PC = 4 (the lw address from the ExMem payload), a frozen state without side effects: the older instruction's regfile write lands, the faulting one's does not.
Check yourself
Why does the pipeline need valid bits in the payload?
After reset the pipeline is full of zero words, and word 0 decodes as illegal: the top valid bit of each payload gates the illegal signal into err_unit, so the fill phase runs without false errors and the garbage never reaches err.