HomeRISC-V from ScratchDay 9
DAY 9 · PHASE 2 — BUILD THE CPU

The Register File

By EcrioniX · Updated Jun 11, 2026

The register file is the CPU's fastest, smallest memory — 32 registers, each 32 bits, sitting right next to the ALU. Every arithmetic instruction reads two registers and writes one. Getting this module right is critical — it is at the heart of almost every instruction the CPU executes.

1. What the register file does

Every RISC-V instruction that does arithmetic reads up to two source registers (rs1, rs2) and writes one destination register (rd). The register file has two read ports (combinational — instant) and one write port (synchronous — on the clock edge).

REGFILE 32 × 32-bit registers raddr1[4:0] rdata1[31:0] raddr2[4:0] rdata2[31:0] waddr/wdata/we clk (write port only)
Register file: 2 async read ports (combinational), 1 sync write port (clocked). x0 reads always return 0.

2. Port table

PortDirWidthMeaning
clkinput1clock — write happens on rising edge
weinput1write enable — 1 = write rd; 0 = hold
waddrinput5destination register index (rd, 0–31)
wdatainput32value to write into rd
raddr1input5first source register index (rs1)
rdata1output32value of rs1 (combinational)
raddr2input5second source register index (rs2)
rdata2output32value of rs2 (combinational)

3. regfile.v — the register file

regfile.v — 32×32-bit register file
// RISC-V Register File: 32 × 32-bit registers
// 2 asynchronous read ports, 1 synchronous write port
// x0 (index 0) always reads as zero — writes to x0 are ignored
module regfile (
    input  wire        clk,
    input  wire        we,          // write enable
    input  wire [4:0]  waddr,       // write address (rd)
    input  wire [31:0] wdata,       // write data
    input  wire [4:0]  raddr1,      // read address 1 (rs1)
    output wire [31:0] rdata1,      // read data 1
    input  wire [4:0]  raddr2,      // read address 2 (rs2)
    output wire [31:0] rdata2       // read data 2
);
    reg [31:0] regs [0:31];
    integer i;
    initial for(i=0;i<32;i=i+1) regs[i]=32'b0; // power-on reset all zero

    // synchronous write — x0 stays zero (waddr==0 is ignored)
    always @(posedge clk)
        if (we && waddr != 5'd0) regs[waddr] <= wdata;

    // asynchronous reads — x0 always returns 0
    assign rdata1 = (raddr1 == 5'd0) ? 32'd0 : regs[raddr1];
    assign rdata2 = (raddr2 == 5'd0) ? 32'd0 : regs[raddr2];
endmodule

4. Testbench

tb_regfile.v — testbench
`timescale 1ns/1ps
module tb_regfile;
    reg        clk=0,we;
    reg  [4:0] waddr,raddr1,raddr2;
    reg  [31:0]wdata;
    wire [31:0]rdata1,rdata2;
    regfile dut(.clk(clk),.we(we),.waddr(waddr),.wdata(wdata),.raddr1(raddr1),.rdata1(rdata1),.raddr2(raddr2),.rdata2(rdata2));
    always #5 clk=~clk;
    integer errors=0;
    task chk(input [31:0]a,input [31:0]e);
        if(a!==e)begin $display("FAIL got=%0h exp=%0h",a,e);errors=errors+1;end
        else $display("ok   %0h",a);
    endtask
    initial begin
        // write 42 into x1
        we=1;waddr=5'd1;wdata=32'd42;raddr1=5'd1;raddr2=5'd0;
        @(posedge clk);#1;chk(rdata1,32'd42);  // x1=42

        // write 99 into x5
        waddr=5'd5;wdata=32'd99;raddr1=5'd5;
        @(posedge clk);#1;chk(rdata1,32'd99);  // x5=99

        // x0 always reads 0 even if written
        waddr=5'd0;wdata=32'hDEAD;raddr1=5'd0;
        @(posedge clk);#1;chk(rdata1,32'd0);   // x0=0

        // simultaneous read x1 and x5
        we=0;raddr1=5'd1;raddr2=5'd5;#1;
        chk(rdata1,32'd42);chk(rdata2,32'd99);

        if(errors==0) $display("ALL TESTS PASSED"); else $display("%0d FAILED",errors);
        $finish;
    end
endmodule
expected output
ok   0000002a
ok   00000063
ok   00000000
ok   0000002a
ok   00000063
ALL TESTS PASSED

💡 The register file is your CPU's whiteboard

Main memory is like a library — big but slow. The register file is the whiteboard right in front of you — tiny (32 slots) but instant. The CPU's job is to bring numbers from memory onto the whiteboard, do math on them, and write results back. You never compute with numbers still in the library.

🎯 Day 9 takeaways

FAQ

Why does x0 always read zero?

It's hardwired to zero so many common operations become simpler — mv rd, rs is add rd, rs, x0; li rd, imm is addi rd, x0, imm. No extra zero-source circuit needed.

Why async reads, sync write?

Reads must be available within the same clock cycle for decode and execute. Writes should only commit after the ALU produces the correct result — that requires a clock edge.

How many registers does RV32I have?

32 general-purpose registers (x0–x31), each 32 bits. x0 = zero; x1 (ra) = return address; x2 (sp) = stack pointer; x10–x17 (a0–a7) = function arguments/return values.

Previous
← Day 8: Instruction memory

← Full roadmap