rtl/riscv_core.sv — RV32IM 5-Stage Pipeline Core
Single-issue, in-order, 5-stage pipeline (IF → ID → EX → MEM → WB) implementing the full RV32I base ISA plus RV32M (MUL/MULH/DIV/DIVU/REM/REMU). Written entirely with always @(*) + assign-only bit-slicing for Icarus Verilog 11 compatibility (no slices inside always blocks). Unmodified since before the v5 chiplet split — cpu_chiplet_top.sv wraps it without touching its internals.
Where it fits
Instantiated once by cpu_chiplet_top.sv as cpu_core_inst, which supplies its instruction memory (imem) and bridges its mem_* ports across the CPU UCIe link. Instantiates alu, imm_decode, and register_file internally.
Ports
| Direction | Name | Width | Description |
|---|---|---|---|
| in | clk, rst_n |
1, 1 | Clock / reset |
| out | instr_addr |
32 | Fetch address (if_pc) |
| in | instr_data |
32 | Instruction word at instr_addr |
| in | instr_valid |
1 | 1 if instr_addr is within the loaded imem region |
| out | mem_write |
1 | Store in MEM stage |
| out | mem_byte_en |
4 | Store byte enables (SB/SH/SW) |
| out | mem_addr |
32 | Load/store address (= EX-stage ALU result) |
| out | mem_wdata |
32 | Store data |
| in | mem_rdata |
32 | Load data |
| out | mem_read |
1 | 1 only during a load instruction in MEM stage |
| out | core_stall |
1 | = math_busy_r — high during multi-cycle MUL/DIV |
| in | mem_stall |
1 | L2/coherency miss stall — freezes the pipeline until ready |
Functionality — pipeline stages
| Stage | What happens |
|---|---|
| IF | if_pc increments by 4 unless stalled (mul_busy, load_use_stall, mem_stall) or redirected (branch_taken) |
| ID | Field extraction via assign wires; imm_decode + register_file instantiated here; control-signal decode (ALU op, wb_sel, is_branch/is_jal/is_jalr/is_mul/is_div) |
| EX | Forwarding mux (EX/MEM and MEM/WB), alu instantiated here, branch resolution, MUL/DIV math_start one-shot gate |
| MEM | Byte-enable encode for stores, load sign/zero-extension by funct3 |
| WB | 4-way mux selects alu_result / mem_data / pc_next / imm back to register_file |
Hazard handling:
| Hazard | Detection | Resolution |
|---|---|---|
| Load-use | id_ex_mem_read && (id_ex_rd == id_rs1 \|\| id_ex_rd == id_rs2) |
1-cycle stall, EX bubble |
| RAW (ALU→ALU/store) | Forwarding unit compares rd vs rs1/rs2 at EX/MEM and MEM/WB |
Forward, no stall |
| Branch | branch_taken resolved in EX |
Flush IF/ID (flush_if_id), 1-cycle penalty |
| MUL/DIV | math_busy_r from alu |
Freeze all stages for the op's full latency |
MUL/DIV stall protocol: math_start fires once per op (gated by math_fired, a one-shot latch cleared when math_busy_r falls). The ALU asserts math_busy_r for the op's latency (3 cycles for MUL/MULH, 34 for DIV/DIVU/REM/REMU — see alu.md). math_done = math_was_busy & !math_busy_r is a one-cycle pulse the EX/MEM register uses to capture the fresh result and resume the pipeline.
Example — MUL through the pipeline
EX: id_ex_is_mul=1, id_ex_valid=1, math_fired=0 -> math_start=1
alu_unit latches operands, math_busy_r asserts, math_fired<=1
cyc 1-2: math_busy_r=1 -> ALL stages frozen (if_pc holds, ID/EX/MEM/WB registers hold)
cyc 3: math_busy_r falls -> math_done=1 this cycle
EX/MEM captures ex_alu_result (the just-computed product)
math_fired clears (since !math_busy_r) -> next MUL can fire
Related
- cpu.md — full prose walkthrough of every stage, forwarding paths, and hazard table
- alu.md, imm_decode.md, register_file.md — sub-modules instantiated here
- cpu_chiplet_top.md — the wrapper supplying
instr_data/mem_stall