The file below implements the contract from the previous lesson. One flop array holds 32 words, two combinational muxes read it, and one clocked port writes it.
This gives write-through semantics on address collisions, avoiding stale operand hazards in single-cycle execute paths. Register x0 is hardwired to zero: writes to it are dropped and reads of it return zero.
module regfile_2r1w #(
parameter int DW = 32,
parameter int AW = 5
) (
input logic clk,
input logic rst_n,
input logic we_i,
input logic [AW-1:0] waddr_i,
input logic [DW-1:0] wdata_i,
input logic [AW-1:0] raddr0_i,
input logic [AW-1:0] raddr1_i,
output logic [DW-1:0] rdata0_o,
output logic [DW-1:0] rdata1_o
);
logic [DW-1:0] rf [2**AW]; // flop array: resettable, unlike an SRAM bank
logic bypass0, bypass1;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
integer i;
for (i = 0; i < 2**AW; i++) rf[i] <= '0;
end else if (we_i && (waddr_i != '0)) begin
rf[waddr_i] <= wdata_i; // writes to x0 are dropped
end
end
assign bypass0 = we_i && (waddr_i != '0) && (waddr_i == raddr0_i);
assign bypass1 = we_i && (waddr_i != '0) && (waddr_i == raddr1_i);
always_comb begin
rdata0_o = (raddr0_i == '0) ? '0 : rf[raddr0_i];
rdata1_o = (raddr1_i == '0) ? '0 : rf[raddr1_i];
if (bypass0) rdata0_o = wdata_i; // same-cycle write wins on port 0
if (bypass1) rdata1_o = wdata_i; // ... and on port 1
end
endmoduleFollow the word from left to right. The first mux is the x0 rule from the always_comb block; the second is the bypass. Port 1 has the same pair with raddr1.
The select needs all three terms. Without the write enable, idle cycles forward stale write data. Without the nonzero check, a write to x0 leaks through.
The bypass is on the read timing path: the address comparator and one 2-to-1 mux follow the 32-to-1 array mux.
The bypass keys off the same-cycle write enable. It covers only a read and write asserted in the same cycle; a writeback one stage later needs forwarding in the pipeline, covered in the next lesson.
The top lanes are the stimulus. The bottom two lanes read the same address two ways, both combinational with the same latency: rdata_nb has no bypass, rdata_byp has the write-through bypass. Each slot is one clock cycle, and a write commits at the rising edge that ends its slot. So in a write slot the plain lane still shows the stored word, while the bypass lane already shows the write data.
Slot 0: reset held (rst_n 0), so both lanes read 0x0.
Slot 1: reset released with a write of 0xAA to register 3 (we 1, waddr 3). The bypass lane already shows 0xAA; the plain lane still shows 0x0.
Slot 2: idle read (we 0). Both lanes show 0xAA.
Slot 3: write of 0x11 to register 3 with a same-cycle read. The bypass lane shows the new 0x11; the plain lane still shows the old 0xAA.
Slot 4: idle read. Both lanes show 0x11.
Slot 5: read of register 0 (raddr0 0). Both lanes show 0x0: x0 is hardwired to zero.
Zero from the x0 hardwire (equal to rf[0] only because writes to x0 are dropped).
Both return the write data. Each port has its own bypass comparator and mux, so both fire on the same match.
The register file, a no-bypass sibling that exhibits the stale word, and a testbench that checks both against a reference model.
module regfile_2r1w #(
parameter int DW = 32,
parameter int AW = 5
) (
input logic clk,
input logic rst_n,
input logic we_i,
input logic [AW-1:0] waddr_i,
input logic [DW-1:0] wdata_i,
input logic [AW-1:0] raddr0_i,
input logic [AW-1:0] raddr1_i,
output logic [DW-1:0] rdata0_o,
output logic [DW-1:0] rdata1_o
);
logic [DW-1:0] rf [2**AW];
logic bypass0, bypass1;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
integer i;
for (i = 0; i < 2**AW; i++) rf[i] <= '0;
end else if (we_i && (waddr_i != '0)) begin
rf[waddr_i] <= wdata_i;
end
end
assign bypass0 = we_i && (waddr_i != '0) && (waddr_i == raddr0_i);
assign bypass1 = we_i && (waddr_i != '0) && (waddr_i == raddr1_i);
always_comb begin
rdata0_o = (raddr0_i == '0) ? '0 : rf[raddr0_i];
rdata1_o = (raddr1_i == '0) ? '0 : rf[raddr1_i];
if (bypass0) rdata0_o = wdata_i;
if (bypass1) rdata1_o = wdata_i;
end
endmodule
module regfile_2r1w_nobypass #(
parameter int DW = 32,
parameter int AW = 5
) (
input logic clk,
input logic rst_n,
input logic we_i,
input logic [AW-1:0] waddr_i,
input logic [DW-1:0] wdata_i,
input logic [AW-1:0] raddr0_i,
output logic [DW-1:0] rdata0_o
);
logic [DW-1:0] rf [2**AW];
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
integer i;
for (i = 0; i < 2**AW; i++) rf[i] <= '0;
end else if (we_i && (waddr_i != '0)) begin
rf[waddr_i] <= wdata_i;
end
end
always_comb rdata0_o = (raddr0_i == '0) ? '0 : rf[raddr0_i];
endmodule