Implementing a Register File for a RISC-V 32I Processor on FPGA
This article is part of our series on designing and implementing a RISC-V 32I processor on the Tang Nano 20K FPGA. Today we’ll dive into an essential component of any processor: the register file. We’ll explore what it is, why it’s critical to RISC-V architecture, and how to efficiently implement it in Verilog. Additionally, we’ll discuss key optimizations to ensure robustness and compatibility with real hardware.
What is a Register File and Why is it Important?
Think of a processor like a chef preparing a meal. If the chef had to leave the kitchen to get each ingredient individually, cooking would become very slow and inefficient. But if all the ingredients are neatly organized and within easy reach, cooking becomes smooth and efficient. The register file in a CPU serves this exact purpose: it’s ultra-fast memory allowing the processor to quickly access and temporarily store data without constantly accessing the main memory.1
In the RISC-V 32I architecture, we have 32 registers, each 32 bits wide (x0
to x31
), which are essential for arithmetic operations, logic operations, and program flow control.
Key Rules for the Register File in RISC-V:
- Register
x0
must always be zero: it cannot be modified by any instruction. - Two registers can be read and one can be written per clock cycle: maximizing instruction efficiency.
- Reading is immediate (combinational)2: there’s no delay, independent of the clock.
- Writes occur on the positive clock edge3: ensuring stability and synchronization.
Complete Register File Code in Verilog
Here’s the optimized Verilog code, following the RISC-V architecture:
module RegisterFile (
input wire clk,
input wire rst, // Global reset
input wire we, // Write enable signal
input wire [4:0] rs1, rs2, rd, // Register addresses
input wire [31:0] wd, // Data to write
output reg [31:0] rd1, rd2 // Data read
);
reg [31:0] registers [0:31]; // 32 registers, each 32 bits wide
// Initialize registers to zero to avoid undefined states
integer i;
initial begin
for (i = 0; i < 32; i = i + 1)
registers[i] = 32'b0;
end
// Register write logic
always @(posedge clk) begin
if (we && rd != 5'b00000) // Prevent writing to x0
registers[rd] <= wd;
end
// Combinational read, with x0 always returning zero
always @(*) begin
rd1 = (rs1 == 5'b00000) ? 32'b0 : registers[rs1];
rd2 = (rs2 == 5'b00000) ? 32'b0 : registers[rs2];
end
endmodule
Conclusion: Laying the Foundation for Our RISC-V CPU
With this register file implementation, we’ve taken a decisive step toward building our RISC-V 32I CPU on the Tang Nano 20K FPGA. Our processor now has efficient and reliable storage, laying a solid foundation for future development stages.
In the next article, we’ll explore the Control Unit, which interprets and executes instructions. Stay tuned for more! 🚀
General-purpose registers (GPRs) temporarily store data, avoiding slower accesses to main memory. ↩︎
A combinational circuit produces outputs based solely on current inputs, without clock delays or internal storage. ↩︎
The positive clock edge is the transition of the digital clock signal from low (
0
) to high (1
), used to synchronize updates in synchronous registers. ↩︎