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.
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).
| Port | Dir | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | clock — write happens on rising edge |
| we | input | 1 | write enable — 1 = write rd; 0 = hold |
| waddr | input | 5 | destination register index (rd, 0–31) |
| wdata | input | 32 | value to write into rd |
| raddr1 | input | 5 | first source register index (rs1) |
| rdata1 | output | 32 | value of rs1 (combinational) |
| raddr2 | input | 5 | second source register index (rs2) |
| rdata2 | output | 32 | value of rs2 (combinational) |
// 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`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
endmoduleok 0000002a ok 00000063 ok 00000000 ok 0000002a ok 00000063 ALL TESTS PASSED
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.
we=1.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.
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.
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.