RV32I RISC-V Processor
A processor, built from the spec by hand.
WHAT IT IS
A single-cycle RV32I processor in SystemVerilog, written from the instruction set specification. Decode, register file, ALU, branching and memory access, all mapped down to digital circuit design.
WHAT I BUILT
- Instruction decode and register file, straight from the spec
- ALU operations, branching, and byte, halfword and word memory sizing
- A custom testbench with assembly and machine code test programs
- Simulation toolchain on the OSS-CAD Suite, driven by Make
IN GTKWAVE

THE SIGNALS
The save file groups them by colour. Four groups, all open at once, because the bug was never in just one of them.
clk · program_counter · current_instruction
opcode · rs1 · rs2 · rd · funct3 · funct7
imm_i · imm_s · imm_b · imm_u · imm_j · alu_result
memory_data · data_write · data_write_done · register_write_doneTHE BUGS
There were a lot of them. The worst was that PC-relative instructions ran twice. AUIPC, JAL, JALR and branches all got a second pass at the register file and the ALU, because the program counter moved before the instruction register caught up.
// ! - For some reason, the AUIPC instruction turns on PC enable
// which makes pc_enable go high. Anyone else want to try debugging?
// ! - ...it will "happen twice" because it will still try to load in
// the registers and do the ALU calculation when the program counter
// changes, but before the instruction changes. This happens no matter
// whether it's combinational or sequential.left in instructions.sv while it was still unsolved
It reproduced identically whether the logic was combinational or sequential, which ruled out a whole class of causes. Control got restructured around an explicit pc_enable output and a fetch_done flag, with branches and jumps gated separately.
THE SHIFT BUG
Two faults stacked. Arithmetic right shift behaved as a logical shift, and shift_amount was being driven from two places at once.
shift_result = signed'(signed_rs1) >>> shift_amount;
// and shift_amount moved out of alu_top, passed in from decode"something going on that's weird about shifting. unsure what it is"
THE TEST PROGRAM
Everything here is verified in simulation. These are the assembly programs the processor runs to prove each instruction group works. Every line carries the program counter, the expected register value and the encoding.
assembly/rv32i_test.s
lui x1, 0xFEDCC # x1 = 0xFEDCC000 fedcc0b7
addi x1, x1, 0xA98 # x1 = 0xFEDCBA98 a9808093
srai x3, x1, 4 # x3 = 0xFFEDCBA9 4040d193
add x6, x5, x4 # x6 = 0x00123458 00428333
auipc x10, 0x12345 # x10 = 0x12345028 12345517
jal x13, 0x28 # x13 = 0x00000038 028006ef
beq x15, x0, 12 # 00078663
jalr x14, 0(x13) # x14 = 0x00000060 00068767
sb x17, -4(x0) # ff100e23
lw x18, -4(x0) # x18 = 0xC0C0C0C0 ffc02903
lh x20, -4(x0) # x20 = 0xFFFFC0C0 ffc01a03
lb x22, -4(x0) # x22 = 0xFFFFFFC0 ffc00b03
lbu x23, -4(x0) # x23 = 0x000000C0 ffc04b83WHERE IT LANDED
Verified in simulation, not deployed to hardware. An rv32m branch extends toward the M extension, where integer division works and multiply does not yet.