One array, one clock, one address. The write is synchronous and the read is registered.
The full listing sits under Testbench below.
Part | Why |
|---|---|
| The storage array, no reset |
| Synchronous write with enable |
| Synchronous read; the result is registered, one cycle later |
Same | One shared address/access port; a write also observes the old word |
Data appears one cycle after the address. Control logic around the RAM must account for that cycle (a valid bit travels with the request).
In this version a write with the same address also reads: the output shows the old word (read-first). The next lesson compares the alternatives.
Throughput is one shared-address access per cycle. A write also produces a read-first observation at that address; there is no independent second read address.
Each slot is one clock cycle. Dout is the RAM output after the opening edge, and consumer is a flop sampling dout, so every dout word reaches consumer exactly one edge later.
Slot 0 offers the write with we high while dout still shows the previous request.
Slot 1 keeps dout at the old word 0x22 even though the write has landed: read-first.
Slot 2 shows the new word 0x11 on dout while consumer captures 0x22.
Slot 3 onward shows consumer holding 0x11: one cycle of RAM latency plus one consumer edge.
Instant (address 7 initially 0x22) | Request | Output / consumer observation |
|---|---|---|
Before rising edge E | addr=7, we=1, din=0x11 stable | dout still belongs to the previous request |
At E, before NBA updates | RAM samples the request | A clocked consumer samples the previous dout |
After E's NBA updates | mem[7] becomes 0x11 | dout becomes old mem[7] = 0x22 |
Before / at E+1 | addr=7, we=0 | Consumer captures 0x22; after NBA dout becomes 0x11 |
At E+2 | Next request may already be present | Consumer captures 0x11 from the read at E+1 |
dout show in the cycle after a write to address 7 if address 7 is also being read?The old contents of address 7 (read-first behaviour). The new data is visible only in a subsequent read.
The testbench runs a directed read-first and consumer-latency trace, then 20,000 random reference comparisons.
module ram_sp #(parameter int AW = 6, parameter int DW = 8) (
input logic clk, we,
input logic [AW-1:0] addr,
input logic [DW-1:0] din,
output logic [DW-1:0] dout
);
logic [DW-1:0] mem [2**AW]; // the array: no reset, so it can map to a block RAM
always_ff @(posedge clk) begin
if (we) mem[addr] <= din; // synchronous write
dout <= mem[addr]; // synchronous read: this is what makes it a block RAM
end
endmodule
Source checker, run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It runs a directed read-first and consumer-latency trace and 20,000 random reference comparisons. Target mapping is not checked.