← Back to projects

Project 03 · Computer Architecture

Pipelined RV32I RISC-V Core

A fully-pipelined, five-stage, in-order RV32I RISC-V core written in SystemVerilog. A dedicated hazard unit sustains near one-instruction-per-cycle throughput through data forwarding, load–use and write–decode stalls, and control-flow flushes — verified end-to-end against the full rv32-bmarks suite.

EECS 4201 · York University — Computer Architecture & Organization · final deliverable (PD5)

System Overview

This is the culmination (PD5) of an incremental course project that builds a RISC-V processor across successive deliverables. It reuses the datapath blocks proven in the single-cycle design and transforms them into a genuine five-stage pipeline. The essential new engineering is not the arithmetic or decoding — those are inherited — but the machinery that makes a pipeline behave correctly under hazards: the pipeline registers that decouple the stages, the forwarding network that resolves most data dependencies with no penalty, the stall logic for the dependencies forwarding cannot cover, and the flush logic that discards wrongly-fetched instructions after control-flow changes.

Three classes of hazard can corrupt results in a pipeline. Structural hazards are avoided by design (separate instruction/data memory ports, and a register file that writes and reads in the same cycle). Data hazards are resolved by forwarding, with stalls for the rest. Control hazards are handled by fetching sequentially and flushing on taken branches and jumps.

RV32IBase ISA
5Pipeline stages
2Forwarding paths
1-cycleStall penalty
1 MiBFlat memory
64/64Tests pass

Architecture — Five-Stage Datapath

The top-level pd5 module wires together the five stages, the two memory instances, the register file, the four pipeline registers, and the hazard unit. Solid paths are the normal per-cycle dataflow; the dashed feedback paths are forwarding from the EX/MEM (MX) and MEM/WB (WX) pipeline registers back into Execute.

MX forward WX forward IF fetch ID decode EX ALU · branch MEM data mem WB write-back IF/ID ID/EX EX/MEM MEM/WB Instr. Memory Register File Data Memory
Per-cycle dataflow MX forward (EX/MEM → EX) WX forward (MEM/WB → EX)
StageModule(s)Responsibility
IFfetch, memory (imem)Hold/advance PC; read instruction word
IDdecode, igen, control, register_fileExtract fields; build immediate; generate control; read rs1/rs2
EXalu (execute), branch_controlArithmetic/logic; address calc; branch decision
MEMmemory (dmem)Load/store data-memory access
WBwritebackSelect ALU / memory / PC+4 result to write back
  1. 1
    IF — Instruction Fetch PC in a reset-initialised register (0x01000000); advances by 4, holds during a stall, or loads a branch/jump target. Instruction word read from a read-only imem instance.
  2. 2
    ID — Decode, Immediate & Control decode splits the fields, igen assembles the sign-extended immediate (I/S/B/U/J), and a combinational control decoder drives the datapath — with safe defaults so unknown opcodes are inert.
  3. 3
    EX — Execute The ALU runs the full RV32I repertoire and computes effective / branch / jump targets; branch_control produces equal and less-than flags that resolve each branch type. Forwarded operands feed both the ALU and the comparator.
  4. 4
    MEM — Memory Access Byte-addressable with byte/half/word accesses by funct3 — combinational reads with sign/zero extension (lb/lh vs lbu/lhu), synchronous size-aware writes (sb/sh/sw).
  5. 5
    WB — Write-Back A two-bit wbsel commits the ALU result (00), loaded data (01) or the return address PC+4 (10). A second instance of the block computes the next-PC target for Fetch.

Instruction Set — RV32I Formats

All instructions are 32 bits wide and fall into six encoding formats distinguished by how the immediate is assembled. The opcode occupies the low seven bits in every format.

R-type — register/register (add, sub, sll, slt)
31:25funct7
24:20rs2
19:15rs1
14:12funct3
11:7rd
6:0opcode
I-type — immediate, load & jalr (addi, lw, jalr)
31:20imm[11:0]
19:15rs1
14:12funct3
11:7rd
6:0opcode
S-type — store (sb, sh, sw)
31:25imm[11:5]
24:20rs2
19:15rs1
14:12funct3
11:7imm[4:0]
6:0opcode
B-type — branch (beq, bne, blt, bge, bltu, bgeu)
31:25imm[12|10:5]
24:20rs2
19:15rs1
14:12funct3
11:7imm[4:1|11]
6:0opcode
U-type — upper immediate (lui, auipc)
31:12imm[31:12]
11:7rd
6:0opcode
J-type — jump (jal)
31:12imm[20|10:1|11|19:12]
11:7rd
6:0opcode

The ALU implements the full arithmetic, logic, shift and comparison repertoire, plus the upper-immediate operations:

Arithmetic
  • ADD
  • SUB
Logic
  • AND
  • OR
  • XOR
Shift
  • SLL
  • SRL
  • SRA
Compare
  • SLT
  • SLTU
Upper immediate
  • LUI
  • AUIPC
ClassregwrenmemrenmemwrenpcselwbselALU op
R-type100000 (ALU)by funct3/funct7
I-type100000 (ALU)by funct3
Load110001 (mem)ADD (address)
Store001000ADD (address)
Branch000000SUB (compare)
jal / jalr100110 (PC+4)ADD (target)
lui100000pass immediate
auipc100000PC + immediate

Pipeline Registers

All four inter-stage registers live in one pipeline_registers module. Each latches the data and control its downstream stage needs, supports stalling via a write-enable, and resets to a defined idle state; the two front-end registers (IF/ID, ID/EX) additionally support flushing.

RegisterCarries (selected)
IF/IDPC, fetched instruction word
ID/EXPC, rs1/rs2 data & indices, rd, immediate, funct3/funct7, opcode, all control signals
EX/MEMPC, ALU result, store data & rs2 index, rd, funct3, control, branch-taken flag
MEM/WBPC, ALU result, loaded memory data, rd, regwren, wbsel

A flush inserts a bubble — a benign no-op. In IF/ID a flush loads the canonical NOP encoding in place of the fetched instruction; in ID/EX it clears the memory and branch control signals so the squashed instruction touches nothing:

pipeline_registers.sv · bubble insertion
// IF/ID flush loads the canonical NOP:
0x00000013   // addi x0, x0, 0  →  decodes to a harmless no-op
// ID/EX flush clears memren / memwren / branch controls
// so the bubbled instruction cannot access memory or alter control flow

Hazard Handling

The hazard unit is the defining contribution of this deliverable. It observes the register indices and control signals across ID, EX, MEM and WB and drives three families of output: forwarding selects, stall/write-enable signals, and flush signals.

Hazard Unit observes rs1 / rs2 / rd & control across ID · EX · MEM · WB

Forward

A later stage produces a value an EX operand needs → bypass it directly into Execute. No penalty. MX (EX/MEM) has priority over WX (MEM/WB).

Stall + bubble

Load–use (data one cycle too late) or write–decode dependency → freeze PC & IF/ID, bubble ID/EX for exactly one cycle. It then becomes an ordinary forward.

Flush

Branch resolved taken, or jal/jalr in EX → squash the two wrongly-fetched instructions in IF/ID & ID/EX and redirect the PC to the target.

Most data dependencies are resolved without any stall by forwarding a result from a later pipeline stage straight into Execute, before it is written back. Each source operand has a 3:1 select — and crucially, the forwarded operands feed both the ALU and the branch comparator, so branches that depend on a just-computed value compare against the correct data.

ID/EX register value sel 00 · no hazard EX/MEM ALU result — MX sel 01 · from Memory MEM/WB writeback — WX sel 10 · from Write-back mux ALU / branch comparator rs1_sel / rs2_sel
MX — EX/MEM bypass WX — MEM/WB bypass Hazard-unit select
HazardDetection conditionAction
Forward MXMEM stage writes a register (≠ x0) an EX operand needsBypass EX/MEM ALU result into EX
Forward WXWB stage writes a register an EX operand needs (no MX match)Bypass MEM/WB data into EX
Load–useEX holds a load whose rd matches an ID source registerStall 1 cycle + bubble
Write–decodeWB writes a register an ID source needsStall 1 cycle + bubble
ControlEX signals branch-taken, or EX opcode is jal/jalrFlush IF/ID and ID/EX
Store forward (WM)MEM holds a store whose rs2 equals the WB destinationSubstitute WB data as store data
  1. MX
    EX/MEM → EX forward Routes the ALU result waiting in EX/MEM back into Execute — covers a dependency on the immediately preceding instruction.
  2. WX
    MEM/WB → EX forward Routes the writeback-selected value in MEM/WB back into Execute — covers a dependency on the instruction two ahead. MX wins when both match.
  3. LU
    Load–use stall A load's data isn't ready until end of MEM — one cycle too late. Freeze PC & IF/ID, bubble ID/EX one cycle; the dependency then becomes an ordinary forward.
  4. WD
    Write–decode stall A combinational ID read of a register being written synchronously in WB could see the stale value; stall one cycle so the updated value is read next cycle.
  5. CF
    Control-flow flush On a taken branch or jal/jalr in EX, squash the two speculatively-fetched instructions to bubbles and load the computed target into the PC. Suppressed while stalling.
  6. WM
    Store-data forward For a load immediately followed by a store of the same register, substitute the WB data for the store data before it reaches memory (resolved in top-level wiring).

Pipeline in Action

Two cycle-by-cycle views of hazards resolving. In the first, back-to-back dependent instructions proceed with zero penalty thanks to forwarding, while a load–use dependency costs exactly one bubble.

c1
c2
c3
c4
c5
c6
c7
c8
c9
add x5,x1,x2
IF
ID
EX
MEM
WB
sub x6,x5,x1
IF
ID
EX
MEM
WB
lw x7,0(x6)
IF
ID
EX
MEM
WB
add x8,x7,x1
IF
ID
EX
MEM
WB
IF ID EX MEM WB ⊘ stall bubble

In the second, a taken branch is resolved in Execute (c3). The two instructions already fetched onto the wrong path are squashed to bubbles, and the correct target is fetched from the next cycle.

c1
c2
c3
c4
c5
c6
c7
c8
beq x8,x0,L ✔
IF
ID
EX
MEM
WB
next+4 (wrong)
IF
flush
next+8 (wrong)
flush
target L (correct)
IF
ID
EX
MEM
WB
flush → squashed to NOP

Verification

The design was developed and verified on Linux, driven entirely from bash. The primary simulator was ModelSim (Intel FPGA / Quartus), with Verilator as an open-source alternative; waveforms were captured as VCD and inspected in gtkwave. Verification is anchored on probe signals exposed at every stage, compared each cycle against a golden reference so any mismatch pinpoints the exact diverging cycle and signal.

verif/scripts · GNU Make flow
make compile -C verif/scripts/ VSIM=1
make run     -C verif/scripts/ VSIM=1 TEST=<name>
make run     -C verif/scripts/ VSIM=1 TEST=<name> PATTERN_CHECK=1
# a correct design reports "Checks passed"

Because the benchmarks are self-contained programs, the top level watches the instruction stream for termination:

program-termination detection
ecall  0x00000073   // ends the simulation immediately
ret    0x00008067   // function test: end once x2 (sp) unwinds to top of memory
LevelCountPurpose
Directed pattern tests3Baseline datapath correctness via golden patterns
Pipeline micro-tests13One test per hazard path — each forward / stall / flush in isolation
rv32-bmarks — instruction38rv32ui-p-* ISA tests, one per RV32I instruction
rv32-bmarks — full programs10Complete C benchmarks (BubbleSort, Fibonacci, gcd, …)
CategoryTestsResult
Directed pattern tests3PASS
Pipeline hazard micro-tests13PASS
rv32-bmarks instruction tests38PASS
rv32-bmarks full-program benchmarks10PASS
Total64ALL PASS

Results & Future Work

PD5 passes the complete verification suite. Waveform inspection confirmed the intended behaviour: forwarded operands arrive at Execute the same cycle they are needed; load–use and write–decode dependencies each cost exactly one bubble; and taken branches and jumps redirect the PC with the two mis-fetched instructions cleanly squashed.

Results

  • 64 / 64 tests pass — directed, hazard micro-tests, RV32UI ISA, and C benchmarks
  • Forwarded operands reach both the ALU and the branch comparator correctly
  • Load–use & write–decode each cost one bubble; branches squash exactly two instructions

Highlights

  • Two-source forwarding network (MX + WX) with MX priority
  • Single-cycle stalls for the cases forwarding cannot cover
  • Single-cycle flush via cheap NOP-bubble insertion
  • Store-data (WM) forwarding for back-to-back load→store pairs

Future Work

  • Fold the WM store-forward fully inside the hazard unit
  • Branch prediction to cut the control-flow penalty
  • RISC-V M extension (multiply / divide); CSR & exception handling
  • Carry the design through Quartus synthesis for timing & resource analysis