Ch443d: registered AXI R buffer (texcache fill) + drop fill_data_q reset
Closes the last currently-visible EMIF-handshake -> FSM setup family the
Ch443c fit exposed (-0.043/-0.022/-0.005 ns), the read-response analogue
of the AW/W buffers.
- gs_axi_r_regbuf: one-entry FULLY-registered AXI R buffer (the R twin of
gs_axi_w_regbuf). Buffers the complete {rdata,rresp,rlast}; u_rready =
!full only (NO combinational dependence on the texture FSM's d_rready);
captures on u_rvalid && u_rready; d_rvalid = full with the payload held
stable until d_rvalid && d_rready; resets only . Inserted between
read-arbiter s2 and gs_texture_cache (u_texf_rbuf). The arbiter is
unchanged -- it completes its R transaction into the buffer, which then
owns delivery to the fill FSM. Cuts EMIF rvalid/rdata -> fst.F_R.
- gs_texture_cache: drop the unobservable fill_data_q reset. F_DRAIN (its
only reader) is reachable only after F_R loads it, so the reset value is
never observed; removing it kills the separate lock_sync|dreg[1] ->
fill_data_q[80] setup path (-0.005 ns).
- New tb_gs_axi_r_regbuf: exactly-once/in-order, randomized responses +
stalls, full backpressure, the full && d_rready no-fall-through case,
{rdata,rresp,rlast} stability, reset-while-empty AND reset-while-full.
All green: r-buffer TB, texture_cache, texture_psmt8_clut, scanout_lb,
scanout_restart, scanout_diag, ps2_hps_bridge, rd_arb, and the complete
f52 replay BYTE-IDENTICAL (Z 0/307200, COLOR 0/245760). No Quartus/board/
push from here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
// retroDE_ps2 — tb_gs_axi_r_regbuf (Ch443d)
|
||||
//
|
||||
// Focused scoreboard for the one-entry fully-registered AXI R buffer (the R twin of
|
||||
// tb_gs_axi_w_regbuf). Verifies:
|
||||
// (1) EXACTLY-ONCE, IN-ORDER delivery with payload integrity {RDATA,RRESP,RLAST}
|
||||
// under randomized upstream responses + downstream backpressure (a drop, dup,
|
||||
// or reorder trips the sequence scoreboard).
|
||||
// (2) NO-COMBINATIONAL-BYPASS: u_rready === !full every cycle, so a fall-through
|
||||
// `u_rready = !full || d_rready` (which would leak the texture FSM's downstream
|
||||
// READY back upstream into EMIF RVALID) is caught. A directed phase forces the
|
||||
// full && d_rready case.
|
||||
// (3) Downstream payload held STABLE while d_rvalid && !d_rready.
|
||||
// (4) Reset while empty AND while full both leave the buffer empty.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_gs_axi_r_regbuf;
|
||||
localparam int RD = 256, RS = 2;
|
||||
logic clk = 0; always #5 clk = ~clk; // 100 MHz
|
||||
logic rst_n;
|
||||
|
||||
logic [RD-1:0] u_rdata; logic [RS-1:0] u_rresp; logic u_rlast, u_rvalid, u_rready;
|
||||
logic [RD-1:0] d_rdata; logic [RS-1:0] d_rresp; logic d_rlast, d_rvalid; logic d_rready;
|
||||
|
||||
gs_axi_r_regbuf #(.RDATA_W(RD), .RRESP_W(RS)) dut (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.u_rdata(u_rdata), .u_rresp(u_rresp), .u_rlast(u_rlast), .u_rvalid(u_rvalid), .u_rready(u_rready),
|
||||
.d_rdata(d_rdata), .d_rresp(d_rresp), .d_rlast(d_rlast), .d_rvalid(d_rvalid), .d_rready(d_rready)
|
||||
);
|
||||
|
||||
int errors; initial errors = 0;
|
||||
|
||||
// distinct nonzero payload per sequence value
|
||||
function automatic logic [RD-1:0] mk(input logic [31:0] s);
|
||||
mk = {s^32'h1234ABCD, s+32'd7, ~s, s^32'h55AA55AA, s+32'd2, s^32'hF0F0F0F0, s+32'd9, s};
|
||||
endfunction
|
||||
function automatic logic [RS-1:0] mk_resp(input logic [31:0] s); mk_resp = s[1:0]; endfunction // 0..3 incl SLVERR
|
||||
function automatic logic mk_last(input logic [31:0] s); mk_last = s[2]; endfunction
|
||||
|
||||
// LFSR backpressure 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_rready = force_ready ? 1'b1 : (force_stall ? 1'b0 : (dl[0] | dl[3]));
|
||||
|
||||
// AXI-legal producer: assert u_rvalid with STABLE payload until accepted.
|
||||
logic [31:0] wr_seq; 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_rvalid && u_rready) begin
|
||||
wr_seq <= wr_seq + 1;
|
||||
pending <= (ul[0] | ul[3]) && !prod_freeze;
|
||||
end
|
||||
else if (!pending) pending <= (ul[0] | ul[3]) && !prod_freeze;
|
||||
end
|
||||
assign u_rvalid = pending;
|
||||
assign u_rdata = mk(wr_seq);
|
||||
assign u_rresp = mk_resp(wr_seq);
|
||||
assign u_rlast = mk_last(wr_seq);
|
||||
|
||||
// (1) downstream scoreboard: exactly-once, in-order, payload-correct
|
||||
logic [31:0] rd_seq;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) rd_seq <= 0;
|
||||
else if (d_rvalid && d_rready) begin
|
||||
if (d_rdata !== mk(rd_seq) || d_rresp !== mk_resp(rd_seq) || d_rlast !== mk_last(rd_seq)) begin
|
||||
if (errors < 20) $error("[rbuf] drop/dup/reorder/payload at seq %0d: rdata %h resp %h last %b",
|
||||
rd_seq, d_rdata, d_rresp, d_rlast);
|
||||
errors++;
|
||||
end
|
||||
rd_seq <= rd_seq + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// (2) NO combinational downstream-ready bypass: u_rready must equal !full.
|
||||
always_ff @(posedge clk) if (rst_n) begin
|
||||
if (u_rready !== !dut.full) begin
|
||||
if (errors < 20) $error("[rbuf] u_rready(%b) != !full(%b) — combinational bypass?", u_rready, dut.full);
|
||||
errors++;
|
||||
end
|
||||
end
|
||||
logic saw_full_and_ready; initial saw_full_and_ready = 1'b0;
|
||||
always_ff @(posedge clk) if (rst_n && dut.full && d_rready) saw_full_and_ready <= 1'b1;
|
||||
|
||||
// (3) while stalled, the SAME beat must still be presented.
|
||||
logic [RD-1:0] hold_d; logic [RS-1:0] hold_r; logic hold_l, hold_v;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin hold_v <= 1'b0; hold_d <= '0; hold_r <= '0; hold_l <= 1'b0; end
|
||||
else begin
|
||||
if (hold_v) begin
|
||||
if (!d_rvalid) begin
|
||||
if (errors < 20) $error("[rbuf] d_rvalid deasserted while stalled"); errors++;
|
||||
end else if (d_rdata !== hold_d || d_rresp !== hold_r || d_rlast !== hold_l) begin
|
||||
if (errors < 20) $error("[rbuf] downstream {rdata,rresp,rlast} changed while stalled"); errors++;
|
||||
end
|
||||
end
|
||||
hold_v <= d_rvalid && !d_rready;
|
||||
hold_d <= d_rdata; hold_r <= d_rresp; hold_l <= d_rlast;
|
||||
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_rvalid !== 1'b0) begin $error("[rbuf] not empty after reset (empty case)"); errors++; end
|
||||
|
||||
// Phase 1: reset while FULL — briefly run, stall to fill, then pulse reset. Done
|
||||
// EARLY so the later count/coverage checks accumulate after it (reset zeroes wr_seq).
|
||||
force_stall = 1; repeat (40) @(posedge clk);
|
||||
if (dut.full !== 1'b1) begin $error("[rbuf] expected full before reset-while-full case"); errors++; end
|
||||
rst_n = 0; repeat (3) @(posedge clk); rst_n = 1; force_stall = 0;
|
||||
@(posedge clk);
|
||||
if (d_rvalid !== 1'b0) begin $error("[rbuf] not empty after reset (full case)"); errors++; end
|
||||
|
||||
// Phase 2: randomized responses + backpressure (accumulate transfers)
|
||||
repeat (20000) @(posedge clk);
|
||||
|
||||
// Phase 3 (directed): stall downstream so the buffer fills and STAYS full
|
||||
// (u_rready must read 0 = !full), then hold full while d_rready=1 — the
|
||||
// distinguishing full && d_rready 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 4: 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_rvalid !== 1'b0) begin $error("[rbuf] not empty after drain (d_rvalid=%b)", d_rvalid); errors++; end
|
||||
if (wr_seq !== rd_seq) begin $error("[rbuf] count mismatch: in %0d out %0d", wr_seq, rd_seq); errors++; end
|
||||
if (wr_seq < 32'd2000) begin $error("[rbuf] too few transfers (%0d) — not meaningful", wr_seq); errors++; end
|
||||
if (!saw_full_and_ready) begin $error("[rbuf] coverage: full && d_rready never observed — no-bypass case unexercised"); errors++; end
|
||||
|
||||
$display("[tb_gs_axi_r_regbuf] in=%0d out=%0d errors=%0d", wr_seq, rd_seq, errors);
|
||||
if (errors == 0) $display("[tb_gs_axi_r_regbuf] PASS");
|
||||
else $display("[tb_gs_axi_r_regbuf] FAIL");
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin #2500000; $error("[tb_gs_axi_r_regbuf] TIMEOUT"); $finish; end
|
||||
endmodule : tb_gs_axi_r_regbuf
|
||||
Reference in New Issue
Block a user