A synchronous memory returns data L cycles after the address is applied. Plain block RAM has L = 1.
An output register or a bus adds cycles. The sequencer issues the address, waits a further L − 1 cycles, then captures the word it asked for.
The waveform shows a LAT = 3 read of address 5. The address goes out in ISSUE and the word returns in CAPTURE.
ISSUE: mem_en rises and address 5 goes out.
WAIT: two cycles pass while the RAM answers.
CAPTURE: mem_rdata holds 0x58 and data loads it at the end of this cycle.
Next cycle: done is high and data shows 0x58, so a consumer that registers data when it sees done gets the new word. The sequencer is back in IDLE and can accept the next req in this same cycle.
Latency L | States visited | Cycles from ISSUE to CAPTURE |
|---|---|---|
1 | ISSUE → CAPTURE | 1 |
2 | ISSUE → WAIT → CAPTURE | 2 |
3 | ISSUE → WAIT → WAIT → CAPTURE | 3 |
L | ISSUE, L − 1 × WAIT, CAPTURE | L |
Three WAIT cycles (L − 1). CAPTURE occurs four cycles after ISSUE, the cycle in which the RAM's output holds the requested word. done and the registered data then appear one cycle later, five cycles after ISSUE.
Use acceptance as edge 0: ISSUE launches the RAM read at edge 1, the first of L registered pipeline edges; RAM data is available after edge L. CAPTURE registers data and done after edge L + 1. A downstream register consumes them at edge L + 2, which can also accept the next request. Availability latency is L + 1 periods; consumer latency and minimum initiation interval are L + 2. Counting the accepting slot inclusively gives L + 2 slots, a different observation.
Edge (LAT = 3) | Action / post-edge result |
|---|---|
0 | Accept address; enter ISSUE |
1 | RAM launches read; enter WAIT |
2 | WAIT; RAM pipeline advances |
3 | Enter CAPTURE; RAM word becomes available |
4 | Register data and done; return IDLE |
5 | Consumer captures data; next request may be accepted |
Legal parameters are LAT >= 1, AW >= 1 and DW >= 1. The initial diagnostic is a time-zero simulation check; it cannot guarantee that a tool accepts malformed ranges before simulation. Zero-latency combinational memory needs a different contract.
This sequencer starts one read at a time. For one word per cycle, issue a new address every cycle instead. Delay a valid bit and the destination tag by L stages to meet the returning data, as in the pipeline pattern.
The sequencer, a latency-parameterized memory model, and the sweep over latencies 1 to 5 plus an AW=DW=1 boundary instance. Each starts with addresses zero and all-ones, then random addresses. The model launches reads only when mem_en is high, and the test requires exactly one enabled access per request. It checks both data availability with done and the downstream register's capture. Every instance must complete all 200 reads.
module rd_seq #(parameter int LAT = 3, parameter int AW = 4, parameter int DW = 8) (
input logic clk, rst_n,
input logic req,
input logic [AW-1:0] addr,
output logic mem_en,
output logic [AW-1:0] mem_addr,
input logic [DW-1:0] mem_rdata, // LAT registered stages, counting the launch edge as stage 1
output logic busy,
output logic done, // registered with data: high in the cycle data first shows the new word
output logic [DW-1:0] data
);
typedef enum logic [1:0] {IDLE, ISSUE, WAIT, CAPTURE} state_t;
initial if (LAT < 1 || AW < 1 || DW < 1)
$fatal(1, "rd_seq: LAT, AW and DW must be at least 1");
state_t state;
logic [AW-1:0] addr_r;
logic [$clog2(LAT+1):0] wait_cnt;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin state <= IDLE; addr_r <= '0; wait_cnt <= '0; data <= '0; done <= 1'b0; end
else begin
done <= (state == CAPTURE); // set together with data, one pulse
unique case (state)
IDLE: if (req) begin addr_r <= addr; state <= ISSUE; end
ISSUE: begin // address is presented this cycle
wait_cnt <= LAT - 1;
state <= (LAT == 1) ? CAPTURE : WAIT;
end
WAIT: begin
wait_cnt <= wait_cnt - 1'b1;
if (wait_cnt == 1) state <= CAPTURE;
end
CAPTURE: begin data <= mem_rdata; state <= IDLE; end // data is valid this cycle
default: state <= IDLE;
endcase
end
assign mem_en = (state == ISSUE);
assign mem_addr = addr_r;
assign busy = (state != IDLE);
endmodule
module rd_env #(parameter int LAT = 3, AW = 4, DW = 8)
(input logic clk, input logic rst_n, output int errors, output int reads);
logic req = 0, mem_en, busy, done;
logic [AW-1:0] addr = 0, mem_addr;
logic [DW-1:0] mem_rdata, data, got;
logic [DW-1:0] mem [2**AW];
logic [DW-1:0] pipe [LAT];
int accesses = 0;
initial for (int i = 0; i < 2**AW; i++) mem[i] = DW'(i * 17 + 3);
// synchronous RAM with LAT cycles of read latency
always_ff @(posedge clk) begin
if (rst_n && mem_en) begin
pipe[0] <= mem[mem_addr];
accesses <= accesses + 1;
end
for (int i = 1; i < LAT; i++) pipe[i] <= pipe[i-1];
end
assign mem_rdata = pipe[LAT-1];
rd_seq #(.LAT(LAT), .AW(AW), .DW(DW)) dut (.*);
// a downstream consumer that registers data on the edge where done is high
always_ff @(posedge clk) if (done) got <= data;
int cycles;
initial begin
errors = 0; reads = 0;
@(posedge rst_n);
repeat (200) begin
@(negedge clk) begin
req = 1;
if (reads == 0) addr = 0;
else if (reads == 1) addr = '1;
else addr = AW'($urandom_range(0, 2**AW-1));
end
@(negedge clk) req = 0;
cycles = 0;
while (!done && cycles < 20) begin @(negedge clk); cycles++; end
reads++;
if (accesses != reads) errors++; // exactly one enabled RAM access per request
if (!done) errors++;
else begin
if (data !== mem[addr]) errors++; // data is already valid in the cycle where done is high
if (cycles != LAT + 1) errors++; // ISSUE + (LAT-1) WAIT + CAPTURE, then the done cycle
@(posedge clk); #1; // the consumer registered data on this edge
if (got !== mem[addr]) errors++; // it must hold the new word, not the previous read
end
end
end
endmodule
module tb_rd;
logic clk = 0, rst_n = 0;
always #5 clk = ~clk;
int e0, e1, e2, e3, e4, e5, r0, r1, r2, r3, r4, r5;
rd_env #(1, 1, 1) u0 (clk, rst_n, e0, r0);
rd_env #(1) u1 (clk, rst_n, e1, r1);
rd_env #(2) u2 (clk, rst_n, e2, r2);
rd_env #(3) u3 (clk, rst_n, e3, r3);
rd_env #(4) u4 (clk, rst_n, e4, r4);
rd_env #(5) u5 (clk, rst_n, e5, r5);
initial begin
#12 rst_n = 1;
#30000;
if (e0 + e1 + e2 + e3 + e4 + e5 == 0 && r0 == 200 && r1 == 200 && r2 == 200 && r3 == 200 && r4 == 200 && r5 == 200)
$display("PASS: read sequencer LAT=1..5 and AW=DW=1 boundary, 200 reads each");
else $fatal(1, "Read sweep FAIL: errors %0d %0d %0d %0d %0d %0d; reads %0d %0d %0d %0d %0d %0d",
e0, e1, e2, e3, e4, e5, r0, r1, r2, r3, r4, r5);
$finish;
end
endmodule