Ch441: one-entry fully-registered W buffer, Z-RMW master -> wr_arb s2 [READY FOR REVIEW]
Cuts the lone remaining EMIF setup fail (-0.016 ns, -0.259 skew): the combinational EMIF gen_p2c_ff -> wr_arb s2_wready -> gs_lpddr_z_rmw next-state (st, endpoint labelled S_FILL_R via the shared encoded state register). New gs_axi_w_regbuf: fully-registered one-entry W buffer (Option B per Codex). - u_wready = !full ONLY (registered occupancy) -> EMIF WREADY never reaches the Z FSM combinationally. NOT a fall-through skid (no !full-OR-d_wready term). - Buffers WDATA/WSTRB/WLAST; downstream held stable until accepted; exactly-once. - AW/B untouched; arbiter bready_q unchanged (still arms on real EMIF W handshake). - z_rmw may enter B-wait once the beat is buffered -- safe: EMIF cannot return B until the buffered beat reaches it. Single-beat writes -> the 1-beat/2-cycle buffer rate is far above the Z write rate (no new FIFO pressure). Wired in zc_emit between u_z W output (zi_*) and the z_w* ports. New file in sim Makefile RTL_SRCS + synth QSF (both). Focused tb_gs_axi_w_regbuf: exactly-once/order/ payload scoreboard + no-combinational-bypass check (u_wready===!full incl. full && d_wready) + downstream-stable check; standalone target + in make run. Texture-cache +0.016 paths NOT touched. No simulations or Quartus run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
// retroDE_ps2 — tb_gs_axi_w_regbuf (Ch441)
|
||||
//
|
||||
// Focused scoreboard for the one-entry fully-registered AXI W buffer. Verifies:
|
||||
// (1) EXACTLY-ONCE, IN-ORDER delivery with payload integrity {WDATA,WSTRB,WLAST}
|
||||
// under randomized upstream offer + downstream backpressure (a drop, dup, or
|
||||
// reorder trips the sequence scoreboard).
|
||||
// (2) The NO-COMBINATIONAL-BYPASS contract: u_wready === !full every cycle, so a
|
||||
// fall-through `u_wready = !full || d_wready` (which would leak downstream
|
||||
// WREADY back upstream into the Z FSM) is caught. A directed phase forces the
|
||||
// full && d_wready case.
|
||||
// (3) Downstream payload held STABLE while d_wvalid && !d_wready.
|
||||
// (4) Full drain leaves the buffer empty with equal produced/consumed counts.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_gs_axi_w_regbuf;
|
||||
localparam int WD = 256, WS = 32;
|
||||
logic clk = 0; always #5 clk = ~clk; // 100 MHz
|
||||
logic rst_n;
|
||||
|
||||
logic [WD-1:0] u_wdata; logic [WS-1:0] u_wstrb; logic u_wlast, u_wvalid, u_wready;
|
||||
logic [WD-1:0] d_wdata; logic [WS-1:0] d_wstrb; logic d_wlast, d_wvalid; logic d_wready;
|
||||
|
||||
gs_axi_w_regbuf #(.WDATA_W(WD), .WSTRB_W(WS)) dut (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.u_wdata(u_wdata), .u_wstrb(u_wstrb), .u_wlast(u_wlast), .u_wvalid(u_wvalid), .u_wready(u_wready),
|
||||
.d_wdata(d_wdata), .d_wstrb(d_wstrb), .d_wlast(d_wlast), .d_wvalid(d_wvalid), .d_wready(d_wready)
|
||||
);
|
||||
|
||||
int errors; initial errors = 0;
|
||||
|
||||
// distinct nonzero payload per sequence value (fills all 256 + 32 bits)
|
||||
function automatic logic [WD-1:0] mk(input logic [31:0] s);
|
||||
mk = {s^32'hDEADBEEF, s+32'd5, ~s, s^32'hA5A5A5A5, s+32'd3, s^32'h0F0F0F0F, s+32'd1, s};
|
||||
endfunction
|
||||
function automatic logic [WS-1:0] mk_strb(input logic [31:0] s);
|
||||
mk_strb = (s ^ 32'hFFFF0000) | 32'd1; // nonzero, varies with s
|
||||
endfunction
|
||||
|
||||
// LFSR backpressure on both sides
|
||||
logic [15:0] ul = 16'hACE1, dl = 16'h1357;
|
||||
always_ff @(posedge clk) begin
|
||||
ul <= {ul[14:0], ul[15]^ul[13]^ul[12]^ul[10]};
|
||||
dl <= {dl[14:0], dl[15]^dl[13]^dl[12]^dl[10]};
|
||||
end
|
||||
logic force_ready, force_stall, prod_freeze;
|
||||
assign d_wready = force_ready ? 1'b1 : (force_stall ? 1'b0 : (dl[0] | dl[3]));
|
||||
|
||||
// AXI-legal producer: assert u_wvalid with STABLE payload until accepted.
|
||||
logic [31:0] wr_seq; // beats accepted UPSTREAM (into the buffer)
|
||||
logic pending;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin wr_seq <= 0; pending <= 1'b0; end
|
||||
else if (u_wvalid && u_wready) begin
|
||||
wr_seq <= wr_seq + 1;
|
||||
pending <= (ul[0] | ul[3]) && !prod_freeze; // maybe offer the next beat
|
||||
end
|
||||
else if (!pending) pending <= (ul[0] | ul[3]) && !prod_freeze;
|
||||
end
|
||||
assign u_wvalid = pending;
|
||||
assign u_wdata = mk(wr_seq); // stable while pending (wr_seq only advances on accept)
|
||||
assign u_wstrb = mk_strb(wr_seq);
|
||||
assign u_wlast = 1'b1; // single-beat writes
|
||||
|
||||
// (1) downstream scoreboard: exactly-once, in-order, payload-correct
|
||||
logic [31:0] rd_seq; // beats delivered DOWNSTREAM
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) rd_seq <= 0;
|
||||
else if (d_wvalid && d_wready) begin
|
||||
if (d_wdata !== mk(rd_seq) || d_wstrb !== mk_strb(rd_seq) || d_wlast !== 1'b1) begin
|
||||
if (errors < 20) $error("[wbuf] drop/dup/reorder/payload at seq %0d: wdata %h strb %h last %b",
|
||||
rd_seq, d_wdata, d_wstrb, d_wlast);
|
||||
errors++;
|
||||
end
|
||||
rd_seq <= rd_seq + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// (2) NO combinational downstream-ready bypass: u_wready must equal !full.
|
||||
always_ff @(posedge clk) if (rst_n) begin
|
||||
if (u_wready !== !dut.full) begin
|
||||
if (errors < 20) $error("[wbuf] u_wready(%b) != !full(%b) — combinational bypass?", u_wready, dut.full);
|
||||
errors++;
|
||||
end
|
||||
end
|
||||
|
||||
// (3) stable downstream payload while stalled (d_wvalid && !d_wready)
|
||||
logic [WD-1:0] hold_d; logic hold_v;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin hold_v <= 1'b0; hold_d <= '0; end
|
||||
else begin
|
||||
if (hold_v && d_wvalid && (d_wdata !== hold_d)) begin
|
||||
if (errors < 20) $error("[wbuf] downstream payload changed while stalled"); errors++;
|
||||
end
|
||||
hold_v <= d_wvalid && !d_wready;
|
||||
hold_d <= d_wdata;
|
||||
end
|
||||
end
|
||||
|
||||
initial begin
|
||||
rst_n = 0; force_ready = 0; force_stall = 0; prod_freeze = 0;
|
||||
repeat (6) @(posedge clk); rst_n = 1;
|
||||
@(posedge clk);
|
||||
if (d_wvalid !== 1'b0) begin $error("[wbuf] not empty after reset"); errors++; end
|
||||
|
||||
// Phase 1: randomized offer + backpressure
|
||||
repeat (20000) @(posedge clk);
|
||||
|
||||
// Phase 2 (directed): stall downstream so the buffer fills and STAYS full
|
||||
// (u_wready must read 0 = !full), then hold full while d_wready=1 — this is
|
||||
// the full && d_wready case a fall-through skid would mishandle.
|
||||
force_stall = 1; repeat (200) @(posedge clk);
|
||||
force_stall = 0; force_ready = 1; repeat (200) @(posedge clk);
|
||||
force_ready = 0;
|
||||
|
||||
// Phase 3: freeze producer, drain fully
|
||||
prod_freeze = 1; force_ready = 1;
|
||||
begin int g; g = 0; while ((wr_seq !== rd_seq) && g < 4000) begin @(posedge clk); g++; end end
|
||||
repeat (10) @(posedge clk);
|
||||
|
||||
if (d_wvalid !== 1'b0) begin $error("[wbuf] not empty after drain (d_wvalid=%b)", d_wvalid); errors++; end
|
||||
if (wr_seq !== rd_seq) begin $error("[wbuf] count mismatch: in %0d out %0d", wr_seq, rd_seq); errors++; end
|
||||
if (wr_seq < 32'd2000) begin $error("[wbuf] too few transfers (%0d) — not meaningful", wr_seq); errors++; end
|
||||
|
||||
$display("[tb_gs_axi_w_regbuf] in=%0d out=%0d errors=%0d", wr_seq, rd_seq, errors);
|
||||
if (errors == 0) $display("[tb_gs_axi_w_regbuf] PASS");
|
||||
else $display("[tb_gs_axi_w_regbuf] FAIL");
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin #2000000; $error("[tb_gs_axi_w_regbuf] TIMEOUT"); $finish; end
|
||||
endmodule : tb_gs_axi_w_regbuf
|
||||
Reference in New Issue
Block a user