Ch443: combined timing repair for both Ch442 setup families
Ch442's fit exposed two failing setup families (plus a stale max-skew). Attack both structurally; no reseeding. Family 1 - AWREADY -> Z-FSM (-0.410): gen_p2c_ff[23] (EMIF AWREADY) reached u_zc_emit|u_z's S_SFLUSH_AW/S_FILL_R next-state combinationally. Ch441 registered only the W channel; add the AW twin: - new gs_axi_aw_regbuf (one-entry fully-registered AW buffer), inserted in zc_emit between u_z's AW output and the arbiter s2 AW port. The FSM now sees registered occupancy, never EMIF's combinational AWREADY. Family 2 - texcache drain_idx_q -> tex_mem (-0.370, x7): a single index fanned across the whole 65536x32, 128-M20K macro. Split by WIDTH into four 65536x8 banks, each with its own (* preserve, dont_merge *) write- address launch register; write the four byte lanes together in F_WRITE; reconstruct the sample word by concatenating four registered read bytes. Selector structure and 1-cycle read latency unchanged; total M20Ks unchanged (4x32 == 128); fill_crc still sums the full 32-bit word. Max-skew: relax ONLY the Ch357 tile-write CDC set_max_skew 2.0 -> 2.5 (quasi-static bundle, >=2 dclk stability window); retain set_net_delay 2.0 (the real arrival bound). SDC comment updated. Tests: new tb_gs_axi_aw_regbuf (AW scoreboard: exactly-once/order/no- combinational-AWREADY-bypass/stable-while-stalled); tb_gs_texture_cache strengthened to distinct-per-byte-bank data + per-bank + full-word checks. All pass: aw/w regbuf, texture_cache, texture_psmt8_clut, scanout_diag, ps2_hps_bridge, and the complete f52 replay BYTE-IDENTICAL (Z 0/307200, COLOR 0/245760). No Quartus, board, or push from here. Ready for one owner GUI fit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+11
-1
@@ -586,6 +586,7 @@ RTL_SRCS := \
|
||||
$(RTL_ROOT)/gif_gs/gs_lpddr_axi_master.sv \
|
||||
$(RTL_ROOT)/gif_gs/gs_lpddr_z_rmw.sv \
|
||||
$(RTL_ROOT)/gif_gs/gs_axi_w_regbuf.sv \
|
||||
$(RTL_ROOT)/gif_gs/gs_axi_aw_regbuf.sv \
|
||||
$(RTL_ROOT)/gif_gs/gs_lpddr_zc_emit.sv \
|
||||
$(RTL_ROOT)/gif_gs/gs_lpddr_rd_probe.sv \
|
||||
$(RTL_ROOT)/gif_gs/gs_lpddr_scanout.sv \
|
||||
@@ -1153,6 +1154,15 @@ tb_gs_axi_w_regbuf: dirs
|
||||
@echo "=== run tb_gs_axi_w_regbuf ==="
|
||||
@cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_axi_w_regbuf.vvp
|
||||
|
||||
tb_gs_axi_aw_regbuf: dirs
|
||||
@echo "=== build tb_gs_axi_aw_regbuf ==="
|
||||
$(IVERILOG) $(IVERILOG_FLGS) \
|
||||
-o $(BUILD_DIR)/tb_gs_axi_aw_regbuf.vvp \
|
||||
-s tb_gs_axi_aw_regbuf \
|
||||
$(RTL_SRCS) $(TB_ROOT)/gif_gs/tb_gs_axi_aw_regbuf.sv
|
||||
@echo "=== run tb_gs_axi_aw_regbuf ==="
|
||||
@cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_axi_aw_regbuf.vvp
|
||||
|
||||
tb_gs_grad_divider: dirs
|
||||
@echo "=== build tb_gs_grad_divider ==="
|
||||
$(IVERILOG) $(IVERILOG_FLGS) \
|
||||
@@ -6282,7 +6292,7 @@ run: tb_top_psmct32_sh3_zs640c12_cap tb_top_psmct32_sh3_zint640c12
|
||||
run: tb_top_psmct32_sh3_zs640b24_cap tb_top_psmct32_sh3_zint640b24
|
||||
|
||||
.PHONY: tb_top_psmct32_sh3_zs640c24c_cap tb_top_psmct32_sh3_zint640c24c sh3_zs640c24c_fixture sh3_zs640motionabc_bootlet
|
||||
run: tb_top_psmct32_sh3_zs640c24c_cap tb_top_psmct32_sh3_zint640c24c tb_gs_axi_w_regbuf
|
||||
run: tb_top_psmct32_sh3_zs640c24c_cap tb_top_psmct32_sh3_zint640c24c tb_gs_axi_w_regbuf tb_gs_axi_aw_regbuf
|
||||
|
||||
run: tb_ee_fetch tb_gs tb_intc tb_platform_video tb_bgcolor_via_dma tb_sif_mailbox \
|
||||
tb_sif_command_echo tb_sif_command_echo_rearm tb_sif_negative_path \
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// retroDE_ps2 — tb_gs_axi_aw_regbuf (Ch443)
|
||||
//
|
||||
// AW twin of tb_gs_axi_w_regbuf. Focused scoreboard for the one-entry fully-
|
||||
// registered AXI AW buffer. Verifies:
|
||||
// (1) EXACTLY-ONCE, IN-ORDER delivery with payload integrity {AWADDR,AWLEN,
|
||||
// AWSIZE,AWBURST} under randomized upstream offer + downstream backpressure
|
||||
// (a drop, dup, or reorder trips the sequence scoreboard).
|
||||
// (2) The NO-COMBINATIONAL-BYPASS contract: u_awready === !full every cycle, so a
|
||||
// fall-through `u_awready = !full || d_awready` (which would leak downstream
|
||||
// AWREADY back upstream into the Z FSM) is caught. A directed phase forces the
|
||||
// full && d_awready case.
|
||||
// (3) Downstream payload held STABLE while d_awvalid && !d_awready.
|
||||
// (4) Full drain leaves the buffer empty with equal produced/consumed counts.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_gs_axi_aw_regbuf;
|
||||
localparam int AW = 32, LN = 8, SZ = 3, BR = 2;
|
||||
logic clk = 0; always #5 clk = ~clk; // 100 MHz
|
||||
logic rst_n;
|
||||
|
||||
logic [AW-1:0] u_awaddr; logic [LN-1:0] u_awlen; logic [SZ-1:0] u_awsize; logic [BR-1:0] u_awburst; logic u_awvalid, u_awready;
|
||||
logic [AW-1:0] d_awaddr; logic [LN-1:0] d_awlen; logic [SZ-1:0] d_awsize; logic [BR-1:0] d_awburst; logic d_awvalid; logic d_awready;
|
||||
|
||||
gs_axi_aw_regbuf #(.ADDR_W(AW), .LEN_W(LN), .SIZE_W(SZ), .BURST_W(BR)) dut (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.u_awaddr(u_awaddr), .u_awlen(u_awlen), .u_awsize(u_awsize), .u_awburst(u_awburst), .u_awvalid(u_awvalid), .u_awready(u_awready),
|
||||
.d_awaddr(d_awaddr), .d_awlen(d_awlen), .d_awsize(d_awsize), .d_awburst(d_awburst), .d_awvalid(d_awvalid), .d_awready(d_awready)
|
||||
);
|
||||
|
||||
int errors; initial errors = 0;
|
||||
|
||||
// distinct payload per sequence value (fills all fields)
|
||||
function automatic logic [AW-1:0] mk_addr(input logic [31:0] s); mk_addr = (s ^ 32'hCAFE_0000) | 32'd4; endfunction
|
||||
function automatic logic [LN-1:0] mk_len (input logic [31:0] s); mk_len = s[7:0]; endfunction
|
||||
function automatic logic [SZ-1:0] mk_size(input logic [31:0] s); mk_size = s[2:0] ^ 3'd5; endfunction
|
||||
function automatic logic [BR-1:0] mk_brst(input logic [31:0] s); mk_brst = s[1:0] | 2'b01; 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_awready = force_ready ? 1'b1 : (force_stall ? 1'b0 : (dl[0] | dl[3]));
|
||||
|
||||
// AXI-legal producer: assert u_awvalid 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_awvalid && u_awready) 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_awvalid = pending;
|
||||
assign u_awaddr = mk_addr(wr_seq);
|
||||
assign u_awlen = mk_len(wr_seq);
|
||||
assign u_awsize = mk_size(wr_seq);
|
||||
assign u_awburst = mk_brst(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_awvalid && d_awready) begin
|
||||
if (d_awaddr !== mk_addr(rd_seq) || d_awlen !== mk_len(rd_seq) ||
|
||||
d_awsize !== mk_size(rd_seq) || d_awburst !== mk_brst(rd_seq)) begin
|
||||
if (errors < 20) $error("[awbuf] drop/dup/reorder/payload at seq %0d: addr %h len %h size %h burst %b",
|
||||
rd_seq, d_awaddr, d_awlen, d_awsize, d_awburst);
|
||||
errors++;
|
||||
end
|
||||
rd_seq <= rd_seq + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// (2) NO combinational downstream-ready bypass: u_awready must equal !full.
|
||||
always_ff @(posedge clk) if (rst_n) begin
|
||||
if (u_awready !== !dut.full) begin
|
||||
if (errors < 20) $error("[awbuf] u_awready(%b) != !full(%b) — combinational bypass?", u_awready, 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_awready) saw_full_and_ready <= 1'b1;
|
||||
|
||||
// (3) while stalled, the SAME beat must still be presented.
|
||||
logic [AW-1:0] hold_a; logic [LN-1:0] hold_l; logic [SZ-1:0] hold_s; logic [BR-1:0] hold_b; logic hold_v;
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin hold_v <= 1'b0; hold_a <= '0; hold_l <= '0; hold_s <= '0; hold_b <= '0; end
|
||||
else begin
|
||||
if (hold_v) begin
|
||||
if (!d_awvalid) begin
|
||||
if (errors < 20) $error("[awbuf] d_awvalid deasserted while stalled"); errors++;
|
||||
end else if (d_awaddr !== hold_a || d_awlen !== hold_l || d_awsize !== hold_s || d_awburst !== hold_b) begin
|
||||
if (errors < 20) $error("[awbuf] downstream {awaddr,awlen,awsize,awburst} changed while stalled"); errors++;
|
||||
end
|
||||
end
|
||||
hold_v <= d_awvalid && !d_awready;
|
||||
hold_a <= d_awaddr; hold_l <= d_awlen; hold_s <= d_awsize; hold_b <= d_awburst;
|
||||
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_awvalid !== 1'b0) begin $error("[awbuf] 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_awready must read 0 = !full), then hold full while d_awready=1.
|
||||
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_awvalid !== 1'b0) begin $error("[awbuf] not empty after drain (d_awvalid=%b)", d_awvalid); errors++; end
|
||||
if (wr_seq !== rd_seq) begin $error("[awbuf] count mismatch: in %0d out %0d", wr_seq, rd_seq); errors++; end
|
||||
if (wr_seq < 32'd2000) begin $error("[awbuf] too few transfers (%0d) — not meaningful", wr_seq); errors++; end
|
||||
if (!saw_full_and_ready) begin $error("[awbuf] coverage: full && d_awready never observed — no-bypass case unexercised"); errors++; end
|
||||
|
||||
$display("[tb_gs_axi_aw_regbuf] in=%0d out=%0d errors=%0d", wr_seq, rd_seq, errors);
|
||||
if (errors == 0) $display("[tb_gs_axi_aw_regbuf] PASS");
|
||||
else $display("[tb_gs_axi_aw_regbuf] FAIL");
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin #2000000; $error("[tb_gs_axi_aw_regbuf] TIMEOUT"); $finish; end
|
||||
endmodule : tb_gs_axi_aw_regbuf
|
||||
@@ -37,8 +37,17 @@ module tb_gs_texture_cache;
|
||||
logic [31:0] tex_rd_addr, tex_rd_data;
|
||||
logic tex_ready;
|
||||
|
||||
// golden source: 64 words. tex_word(i) = 0xC0DE_0000 | i (distinct per lane).
|
||||
function automatic [31:0] tex_word(input int i); tex_word = 32'hC0DE_0000 | i[31:0]; endfunction
|
||||
// golden source. Ch443: every byte lane carries DISTINCT data — different per index
|
||||
// AND different across the four banks (distinct XOR keys + distinct bases) — so the
|
||||
// four-bank tex_mem split is genuinely exercised: a bank swap, misroute, or dropped
|
||||
// bank produces a wrong reconstructed byte that the full-word + per-bank checks catch.
|
||||
// (The old 0xC0DE_0000|i had constant bytes 2/3, which a bank fault could hide.)
|
||||
function automatic [31:0] tex_word(input int i);
|
||||
tex_word = { 8'(((i*4 + 3) & 32'hFF) ^ 32'hA3),
|
||||
8'(((i*4 + 2) & 32'hFF) ^ 32'h5C),
|
||||
8'(((i*4 + 1) & 32'hFF) ^ 32'h91),
|
||||
8'(((i*4 + 0) & 32'hFF) ^ 32'h2E) };
|
||||
endfunction
|
||||
|
||||
// force a bad rresp on a chosen beat to exercise rd_errs (set <0 to disable)
|
||||
int err_beat = -1;
|
||||
@@ -122,9 +131,16 @@ module tb_gs_texture_cache;
|
||||
check(tex_ready, "tex_ready never synced");
|
||||
|
||||
// ---- verify every word via the sampler 1-cycle read port ----
|
||||
// Full reconstructed word AND per-bank byte pinpoint (Ch443 four-bank split).
|
||||
for (int i=0; i<TEX_WORDS; i++) begin
|
||||
logic [31:0] exp;
|
||||
read_word(i, got);
|
||||
check(got == tex_word(i), $sformatf("word[%0d]=%08x exp %08x", i, got, tex_word(i)));
|
||||
exp = tex_word(i); // temp: iverilog forbids part-select on a function-call result
|
||||
check(got == exp, $sformatf("word[%0d]=%08x exp %08x", i, got, exp));
|
||||
check(got[7:0] === exp[7:0], $sformatf("bank0 byte[%0d]=%02x exp %02x", i, got[7:0], exp[7:0]));
|
||||
check(got[15:8] === exp[15:8], $sformatf("bank1 byte[%0d]=%02x exp %02x", i, got[15:8], exp[15:8]));
|
||||
check(got[23:16] === exp[23:16], $sformatf("bank2 byte[%0d]=%02x exp %02x", i, got[23:16], exp[23:16]));
|
||||
check(got[31:24] === exp[31:24], $sformatf("bank3 byte[%0d]=%02x exp %02x", i, got[31:24], exp[31:24]));
|
||||
end
|
||||
|
||||
$display("[texcache] clean: fill_done=%0d beats=%0d bytes=%0d rd_errs=%0d words_checked=%0d errors=%0d",
|
||||
|
||||
Reference in New Issue
Block a user