The interviewer asks: a 32-entry 64-bit file needs four read ports and two write ports, and a result must be readable the cycle after it is written. Flops or SRAM, and what happens on a same-cycle read-after-write?
This tests storage choice by ports and size, not by habit. The answer picks the structure, then defines the collision behavior instead of leaving it to chance.
A small multi-ported file with combinational reads is a candidate for flops and muxes; a larger, low-port-count array is a candidate for SRAM. There is no universal kilobyte crossover. Compare the available macros, port conflicts, replication/banking or specialized regfile options, required latency, and measured area, timing and power for this configuration.
Flop-based regfile | SRAM macro | |
|---|---|---|
Ports | Flexible, but muxing, write priority and forwarding grow with port count. | Fixed by the available macro; often one/two ports, with specialized alternatives where provided. |
Density | Large area per bit. | Dense storage per bit. |
Read | Combinational read uses a mux with load and timing costs. | Target-specific: many FPGA block RAMs use clocked reads; distributed RAM or ASIC macros may offer other interfaces. |
Collision | Defined by your bypass logic. | Macro mode and same-/cross-clock collision contract must be checked. |
A combinational read can map to flops/muxes, asynchronous distributed RAM, or a supported macro interface. It commonly prevents mapping to synchronous FPGA block RAM. The single-port RAM template targets a clocked-read resource; an ASIC SRAM may require explicit instantiation and its own timing contract. Confirm the resource and output-register absorption in mapping reports, then compare PPA.
Same-cycle read-after-write has three honest answers: forward the new data, return the old word, or stall. The testbench below forwards. The bug is leaving the case undefined.
Each slot is one clock cycle, sampled just after the rising edge. Read port A is held at address 3 throughout; entry 3 starts at 0x23. rdata_a comes from the page's regfile_2r1w; rdata_old from the same array with the two forward lines deleted.
Slot 1 (the collision): we is high, waddr is 3 and wdata is 0xAA. With the bypass, rdata_a already reads 0xAA. Without it, rdata_old still reads 0x23.
Slot 2: the write landed at the edge that ended slot 1, so both ports read 0xAA. The two designs differ only in the collision cycle.
Slot 3: a write to address 5 does not touch port A, so both keep 0xAA.
As capacity grows or port count drops, a compatible SRAM macro often becomes attractive. Banking, replication, utilization, peripheral overhead and the workload can reverse an apparent area or power advantage; compare implemented candidates.
Two writes hit the same address in the same cycle. Give one port priority or prohibit the case by construction. If it should never happen, flag it with a checker rather than silently picking.
The bypass mux is on the timing path. Register the read address and take one cycle of latency, or move the forward check a stage earlier. Do not delete the bypass and hope.
These examples choose different latency/resource contracts. A flop read needs a mux with delay and capacitance; the RAM template targets synchronous block RAM. Neither style alone guarantees a particular mapping, and asynchronous RAM resources also exist.
Same-cycle read-after-write returns stale data. Any consumer that feeds a result straight back reads the old word.
Always SRAM because it is denser. Density per bit is not density per usable port; replication, banking conflicts and specialized macro availability can change the comparison. Measure the candidate implementations.
“Async read is free” ignores mux depth, routing and fanout on flop arrays. It may map well to distributed RAM or an asynchronous macro, but not to an incompatible synchronous block RAM. Choose the target and latency, then verify mapping and timing.
Read-write collisions resolve themselves in hardware. They resolve to something, on each device differently. Portable RTL defines the winner.
A two-read one-write flop regfile with same-cycle bypass, plus a testbench that writes every word, reads it back on both ports, and checks the forward.
`timescale 1ns/1ps
module regfile_2r1w #(
parameter int AW = 3,
parameter int DW = 8
) (
input logic clk, we,
input logic [AW-1:0] waddr, raddr_a, raddr_b,
input logic [DW-1:0] wdata,
output logic [DW-1:0] rdata_a, rdata_b
);
logic [DW-1:0] mem [2**AW];
always_ff @(posedge clk) begin
if (we) mem[waddr] <= wdata;
end
always_comb begin
rdata_a = mem[raddr_a];
rdata_b = mem[raddr_b];
if (we && (waddr == raddr_a)) rdata_a = wdata; // same-cycle forward
if (we && (waddr == raddr_b)) rdata_b = wdata;
end
endmodule
Two-read, one-write regfile bench Run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It writes every word, reads it back on both ports and checks the forward.
The collision waveform is simulated with Icarus Verilog 13.0 (2026-10-17) from the page's regfile_2r1w and a copy with the two forward lines deleted; the expected values were written by hand and matched.