· 5 min read

Building a 5-Stage Pipelined RISC-V CPU in SystemVerilog

A complete RV32IM pipelined processor with BTB branch prediction, dual-path data forwarding, hazard detection, CSR support, and multi-cycle multiply/divide — synthesized on Xilinx Kintex-7 xc7k325t with WNS 0.048ns.

#risc-v #cpu #systemverilog #pipeline #project

TL;DR

RV32IM 5-stage pipelined CPU. WNS +0.048ns on Xilinx Kintex-7 xc7k325t-2, 0.984W total power. 32-entry BTB branch prediction, dual-path data forwarding, hazard detection, CSR support, multi-cycle multiply/divide. 4 DSP48E1 slices, 4,573 registers, 46K LUTs.


Architecture Overview

5-Stage Pipeline Datapath
IF ──▶ ID ──▶ EX ──▶ MEM ──▶ WB
 │      │      │       │       │
 │      │      │       │       └── RegFile Write-back
 │      │      │       └── Data Memory Access
 │      │      └── ALU + Branch Compare + Mul/Div
 │      └── Decode + Imm Gen + CSR Read
 └── PC + BTB + Instr Fetch

Design Decisions

DecisionChoiceRationale
Issue widthSingle-issueArea-efficient, sufficient for embedded-class targets
Execution orderIn-order issue, in-order retirePredictable latency, simpler hazard logic
Register file32 × 32-bit GPR (x0 hardwired to 0)Standard RV32I spec
PC init0x8000_0000Typical RISC-V firmware base address
ISA supportRV32I + RV32M + ZicsrInteger + multiply/divide + CSR access
Branch resolutionEX stage (1-cycle penalty on mispredict)Trade-off: simpler than EX-early, better than MEM
Memory modelByte-addressable, little-endianStandard for RISC-V

The pipeline is deliberately simple: one instruction per cycle in, one instruction per cycle out. No out-of-order, no superscalar. The goal is a clean, verifiable design that closes timing at 200MHz on mid-range FPGAs.


Pipeline Stages

5-Stage Pipeline Detail
  ┌─────────────┐
  │ IF          │  PC → BRAM → Instr
  │ if_pc       │  BTB query → pred_jump
  │ if_instr    │  Next PC = branch ? target : PC+4
  └──────┬──────┘

  ┌─────────────┐
  │ ID          │  Decode + Imm Gen + RegFile Read
  │ id_imm      │  CSR read (mstatus, mtvec, mepc, etc.)
  │ id_alu_ctrl │  Control signal generation
  └──────┬──────┘

  ┌─────────────┐
  │ EX          │  ALU + Branch Compare + Mul/Div
  │ ex_alu_res  │  Forwarding MUX: MEM→EX, WB→EX
  │ ex_branch_miss│ Mispredict signal → PC flush
  └──────┬──────┘

  ┌─────────────┐
  │ MEM         │  Data Memory / Peripheral Access
  │ perip_addr  │  Byte/half/word select (perip_mask)
  │ perip_wen   │  Write enable for store instructions
  └──────┬──────┘

  ┌─────────────┐
  │ WB          │  Write-back to RegFile + CSR
  │ rf_we       │  RegFile write enable
  │ rf_wdata    │  Result from ALU or memory
  └─────────────┘

Stage-by-Stage Signal Reference

StageKey OperationsKey Signals
IFPC update, BTB query, BRAM instruction fetchif_pc, if_instr, pred_jump, pred_target
IDInstruction decode, immediate generation, register file read, CSR readid_imm, id_alu_ctrl, id_is_branch, id_rs1_data, id_rs2_data, id_csr_rdata
EXALU computation, branch comparison, multiply/divide execution, forwarding MUXex_alu_res, ex_branch_miss, ex_branch_target, ex_fwd_a, ex_fwd_b
MEMData memory read/write, byte/half/word selectionperip_addr, perip_wen, perip_mask, perip_rdata
WBResult write-back to register file, CSR writerf_we, rf_waddr, rf_wdata, csr_we, csr_wdata

Hazard Handling

The pipeline implements two forwarding paths to resolve data hazards without stalling (except load-use):

EX/MEM result ──▶ EX stage forwarding MUX (rs1, rs2)
WB/RegFile   ──▶ EX stage forwarding MUX (rs1, rs2)

Load-use hazard (load in EX, consumer in ID): 1-cycle stall. The hazard detection unit compares id_rs1/id_rs2 against the EX-stage destination when ex_mem_read == 1.

Branch misprediction: The branch is resolved in EX. On mispredict, the pipeline flushes IF and ID stages (2 wasted cycles). The BTB updates on correction.


Decoder: From 32-bit Instruction to Control Signals

Instruction Decode Architecture
  instr[31:0]

    ├── opcode[6:0]  ──▶ Control Signal Generator
    ├── funct3[14:12]──▶ ALU Control (BEQ/BNE/BLT/BGE/SLT/SLL/SRL/SRA)
    ├── funct7[31:25]──▶ ALU Op Select (ADD vs SUB, SRL vs SRA)
    ├── rs1[19:15]   ──▶ RegFile Read Addr 1
    ├── rs2[24:20]   ──▶ RegFile Read Addr 2
    └── rd[11:7]     ──▶ RegFile Write Addr

The decoder maps the 7-bit opcode + funct3 + funct7 fields into control signals that drive the entire datapath. The critical signals:

case (opcode)
    OPCODE_LUI: begin
        use_alu = 1'b1; alu_ctrl = ALU_ADD;
        op_a_sel = OP_A_ZERO; op_b_sel = OP_B_IMM;
        reg_we = 1'b1;
    end
    OPCODE_BRANCH: begin
        use_alu = 1'b1; is_branch = 1'b1;
        case (funct3)
            3'b000: alu_ctrl = ALU_BEQ;
            3'b001: alu_ctrl = ALU_BNE;
        endcase
    end

The immediate generator handles all four RISC-V immediate formats (I/S/B/U/J) within a single combinational block, extracting and sign-extending the appropriate bit fields based on instruction type.

CSR Subsystem

The CSR file is a small register bank mapped to standard RISC-V CSR addresses:

CSR AddressNamePurpose
0x300mstatusMachine status register
0x305mtvecMachine trap vector base
0x341mepcMachine exception PC
0x342mcauseMachine trap cause

CSR read/write is serialized in WB stage to avoid structural hazards. ECALL triggers a synchronous exception; MRET restores PC from mepc.


ALU: Single-Cycle Datapath

The ALU uses a 33-bit adder (32-bit data + carry-in) as its core, unified across arithmetic, comparison, and branch operations:

OperationAdder UsageNotes
ADDA + BDirect
SUBA + ~B + 1Two’s complement
SLT(A - B) < 0Sign bit of subtraction result
SLTU(A - B) < 0Carry-out of subtraction
BEQ/BNEA - B == 0Zero flag from subtraction
BLT/BGESign/carry flagsSigned/unsigned comparison via adder flags

The operand MUX selects between three sources for each input:

Operand A: rs1 / PC / 0       (LUI uses 0, AUIPC uses PC)
Operand B: rs2 / imm / 4      (JAL/JALR use imm, JAL uses PC+4 via adder)

Shift operations (SLL, SRL, SRA) bypass the adder and use dedicated barrel shifter logic. SRA sign-extends by replicating the MSB.

Multiply/Divide (RV32M)

The multiplier uses 1 DSP48E1 slice for 32×32→64-bit signed multiplication, pipelined over 3 cycles. Division implements a restoring algorithm over up to 32 cycles (one bit per cycle), controlled by a small FSM in the EX stage. The division result and remainder share the same DSP path.


Forwarding and Hazard Resolution

Two forwarding paths eliminate stalls for back-to-back ALU operations:

  EX/MEM ──┐
            ├──▶ Forwarding MUX ──▶ ALU operand A/B
  WB/MEM ──┘

Priority: EX/MEM forwarding takes precedence over WB/MEM. This handles the common case of producer-consumer with zero bubble penalty.

Load-use stall is the only unavoidable hazard. When a load instruction in EX feeds a dependent instruction in ID:

Cycle 1: lw   x1, 0(x2)    |  EX stage
Cycle 2: add  x3, x1, x4   |  ID stage → STALL (load-use detected)
Cycle 3: (bubble)            |  EX stage (lw advances)
Cycle 4: add  x3, x1, x4   |  EX stage (forwarded from MEM)

The stall inserts one bubble and enables MEM→EX forwarding on the next cycle. Total penalty: 1 cycle.


Synthesis Results

Target: Xilinx Kintex-7 xc7k325tffg900-2

MetricValueNotes
Devicexc7k325tffg900-2Kintex-7, speed grade -2
Slice LUTs46,064 (22.6%)
LUT as Logic13,296 (6.5%)Combinational logic
LUT as Distributed RAM32,768 (51.2%)RegFile (32×32-bit)
F7/F8 Muxes27,372 (13.4%)Decoder multiplexers
Slice Registers4,573 (1.1%)Pipeline registers
DSP48E14 (0.48%)Multiplier (1 slice × 4 sub-units)
PLL1 (10%)Clock generation (MMCM)
WNS+0.048ns ✅Setup timing met
WHS+0.041ns ✅Hold timing met
Total Power0.984W
Max Ambient83.3°C

Resource Analysis

The most surprising number: 51.2% of LUTs are Distributed RAM. The register file — 32 registers × 32 bits = 1,024 bits — is implemented using LUT-based distributed RAM rather than dedicated BRAM. This is intentional: BRAM has a 1-cycle read latency, which would complicate the ID stage timing. Distributed RAM provides single-cycle read, keeping the decode path clean.

Slice Registers at 1.1% confirms the pipeline is lightweight. The 5 pipeline stages use only ~4,573 flip-flops — roughly 180 FFs per stage average. The bulk of the storage is combinational (LUT RAM for the register file, mux trees for the decoder).

DSP48E1 at 0.48% — a single DSP slice handles all multiply operations. The division algorithm is purely combinational/sequential using LUT logic, not DSP.

Power at 0.984W with 0.048ns timing margin means the design has headroom. At 200MHz target on xc7k325t-2, the critical path is ~4.95ns — tight but closed. The power density is low enough for passive cooling on most boards.

Timing Breakdown

The critical path runs through:

RegFile read (Distributed RAM, ~0.8ns)
  → Decode mux tree (F7/F8 muxes, ~0.6ns)
  → Imm Gen + sign extension (~0.3ns)
  → Forwarding MUX priority logic (~0.4ns)
  → ALU execution (~1.2ns)
  → Setup to pipeline register (~0.3ns)
  ≈ 3.6ns + routing ≈ 4.95ns

The 33-bit adder is the ALU bottleneck at ~1.2ns. For a faster target, pipelining the ALU or using carry-lookahead would help, but at the cost of one additional pipeline stage.


What’s Next

Three immediate improvements:

1. Branch Prediction Upgrade

Current: always-predict-not-taken. Every branch is predicted not taken; on taken branches, the penalty is 2 cycles (flush IF + ID).

Target: 2-bit saturating counter BTB. This captures loop behavior (taken 99% of the time) and reduces the misprediction rate from ~50% on loops to <5%. Expected IPC improvement: 10-15% on branch-heavy code.

2. Instruction Cache

Current: single-port BRAM, 1-cycle latency. Sufficient for sequential code, but loops cause repeated fetches of the same cache line.

Target: direct-mapped I-cache (32-byte lines, 256 entries = 8KB). Eliminates redundant BRAM reads for hot loops. Adds ~2K LUTs but removes the BRAM bottleneck.

3. Interrupt Support

Current: ECALL / MRET for synchronous exceptions only. No asynchronous interrupts.

Target: PLIC (Platform-Level Interrupt Controller) with 32 external interrupt sources, priority-based arbitration, and vectored interrupt table at mtvec. Enables real-time response to peripheral events.


Related Articles:

🔙 Back to Projects