Skip to content

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