Snapshot: fog implementation + fidelity tooling baseline (pre bilinear-clamp fix)

Per-vertex GS fog end-to-end (gs_stub emit incl. persp_emit5, gs_prim_list_feeder
XYZ2->XYZF2 on PRIM.FGE, gs_make_sh3_scheduler_fixture.py F/FGE packing), new fog
TBs, fidelity attribution tooling. Functional baseline before removing the dead
bilinear lerp8 clamps (Codex: 161-node comb loop -> -0.042ns setup fail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-20 19:56:46 -04:00
parent ec82764bef
commit ba74bbd5aa
476 changed files with 696247 additions and 130119 deletions
+119
View File
@@ -0,0 +1,119 @@
// Focused proof for the runtime CLUT staging transaction.
`timescale 1ns/1ps
module tb_clut_stage_cdc;
logic bclk = 1'b0, dclk = 1'b0;
always #5 bclk = ~bclk;
always #7 dclk = ~dclk;
logic breset_n, dreset_n;
logic b_we;
logic [7:0] b_waddr;
logic [31:0] b_wdata;
logic b_commit_tgl;
logic [31:0] b_expected_crc;
logic clut_wr_en;
logic [7:0] clut_wr_idx;
logic [31:0] clut_wr_data;
logic busy, done_tgl;
logic [31:0] crc;
logic [31:0] observed [0:255];
logic [31:0] expected [0:255];
logic seen_done;
int writes, errors;
logic [31:0] expected_sum;
clut_stage_cdc u_dut (
.bclk, .breset_n, .b_we, .b_waddr, .b_wdata, .b_commit_tgl,
.b_expected_crc, .dclk, .dreset_n, .clut_wr_en, .clut_wr_idx,
.clut_wr_data, .busy, .done_tgl, .crc
);
always_ff @(posedge dclk) begin
if (clut_wr_en) begin
observed[clut_wr_idx] <= clut_wr_data;
writes <= writes + 1;
end
end
task automatic check(input string label, input logic got, input logic want);
if (got !== want) begin
$error("[%s] got %b expected %b", label, got, want);
errors = errors + 1;
end
endtask
task automatic stage_word(input logic [7:0] addr, input logic [31:0] data);
@(posedge bclk);
b_waddr <= addr;
b_wdata <= data;
b_we <= 1'b1;
@(posedge bclk);
b_we <= 1'b0;
endtask
initial begin
breset_n = 1'b0;
dreset_n = 1'b0;
b_we = 1'b0;
b_waddr = 8'd0;
b_wdata = 32'd0;
b_commit_tgl = 1'b0;
b_expected_crc = 32'd0;
writes = 0;
errors = 0;
seen_done = 1'b0;
expected_sum = 32'd0;
for (int i = 0; i < 256; i++) begin
expected[i] = 32'h3C000000 ^ (i * 32'h01020409);
observed[i] = 32'd0;
expected_sum = expected_sum + expected[i];
end
repeat (3) @(posedge bclk);
breset_n = 1'b1;
repeat (3) @(posedge dclk);
dreset_n = 1'b1;
for (int i = 0; i < 256; i++)
stage_word(i[7:0], expected[i]);
b_expected_crc = expected_sum;
@(posedge bclk);
b_commit_tgl <= ~b_commit_tgl;
@(posedge bclk);
// A second commit during the copy is intentionally ignored.
wait (busy);
@(posedge bclk);
b_commit_tgl <= ~b_commit_tgl;
@(posedge bclk);
wait (done_tgl != seen_done);
seen_done = done_tgl;
repeat (4) @(posedge dclk);
check("busy_after_copy", busy, 1'b0);
if (writes != 256) begin
$error("[write_count] got %0d expected 256", writes);
errors = errors + 1;
end
if (crc !== expected_sum) begin
$error("[sum32] got 0x%08x expected 0x%08x", crc, expected_sum);
errors = errors + 1;
end
for (int i = 0; i < 256; i++) begin
if (observed[i] !== expected[i]) begin
$error("[entry %0d] got 0x%08x expected 0x%08x", i, observed[i], expected[i]);
errors = errors + 1;
end
end
repeat (12) @(posedge dclk);
if (writes != 256) begin
$error("[busy_commit_ignored] writes=%0d expected 256", writes);
errors = errors + 1;
end
if (errors != 0) $fatal(1, "tb_clut_stage_cdc FAILED: %0d errors", errors);
$display("tb_clut_stage_cdc PASS: 256 entries, sum32=0x%08x", crc);
$finish;
end
endmodule
+160
View File
@@ -214,6 +214,44 @@ module tb_ps2_hps_bridge;
logic feeder_ready_i = 1'b0;
logic [15:0] feeder_records_i = 16'd0;
logic [31:0] feeder_waits_i = 32'd0;
logic frame_drained_i = 1'b0; // Ch353 — new bridge diagnostic input; tied off (.* matches by name)
logic clear_done_i = 1'b0; // Ch357 — persistent-Z preclear ack (driven by the Ch357 status test)
logic [31:0] frag_drops_i = 32'd0; // Ch357 — persistent-Z drop count (driven by the Ch357 status test)
// Ch367 -- runtime CLUT staging ports (the data-bank CDC itself is
// covered in tb_clut_stage_cdc; this TB pins the bridge ABI/decode).
logic [7:0] clut_stage_waddr_o;
logic [31:0] clut_stage_wdata_o;
logic clut_stage_we_o, clut_commit_tgl_o;
logic [31:0] clut_expected_crc_o;
logic clut_busy_i = 1'b0, clut_done_tgl_i = 1'b0;
logic [31:0] clut_crc_i = 32'd0;
integer clut_stage_writes;
logic [7:0] clut_stage_last_addr;
logic [31:0] clut_stage_last_data;
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
clut_stage_writes <= 0;
clut_stage_last_addr <= 8'd0;
clut_stage_last_data <= 32'd0;
end else if (clut_stage_we_o) begin
clut_stage_writes <= clut_stage_writes + 1;
clut_stage_last_addr <= clut_stage_waddr_o;
clut_stage_last_data <= clut_stage_wdata_o;
end
end
// Ch355 — model the feeder staging RAM exactly as the on-board feeder does: the bridge emits a COMMIT strobe
// (feeder_stg_we_tgl_o toggles) with waddr/wdata for each staged word. Capture it here to verify that a
// register-write stream lands every word at the correct slot (the board bug put word0 at slot 1).
logic [63:0] model_stg [0:63]; logic mstg_tgl_q;
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) mstg_tgl_q <= 1'b0;
else begin
mstg_tgl_q <= feeder_stg_we_tgl_o;
if (feeder_stg_we_tgl_o !== mstg_tgl_q) model_stg[feeder_stg_waddr_o] <= feeder_stg_wdata_o;
end
end
ps2_hps_bridge u_dut (
.clk (clk),
@@ -1183,6 +1221,128 @@ module tb_ps2_hps_bridge;
axi_read32(38'h0E8, rd); check_eq("FEEDER_waits", rd, 32'd7);
end
// --------------------------------------------------------
// Ch355 — BRIDGE FEEDER-STAGING address path (closes the gap that hid the board bug: the multi-texture TB
// staged via $readmemh and never exercised the real 0x0D8 set-addr / 0x0DC-lo / 0x0E4-hi-commit protocol).
// Stage two complete lists EXCLUSIVELY through bridge register writes; verify every word (esp word 0), the
// final address, and the records readback; plus a negative test that reproduces the exact board bug.
// --------------------------------------------------------
begin
logic [63:0] la [0:9]; logic [63:0] lb [0:5]; logic [31:0] rd;
la[0]=64'h0000000100000044; la[1]=64'hDEADBEEF00000001; la[2]=64'h1111111122222222; la[3]=64'h3333333344444444;
la[4]=64'h5555555566666666; la[5]=64'h7777777788888888; la[6]=64'h99999999000000AA; la[7]=64'hAAAAAAAABBBBBBBB;
la[8]=64'hCCCCCCCCDDDDDDDD; la[9]=64'hEEEEEEEEFFFFFFFF;
lb[0]=64'h0000000100000032; lb[1]=64'h0102030405060708; lb[2]=64'h1122334455667788; lb[3]=64'h99AABBCCDDEEFF00;
lb[4]=64'hFEDCBA9876543210; lb[5]=64'hCAFEBABE0BADF00D;
// ---- SCENE A: reset addr to 0 (0x0D8=0), stream {lo(0x0DC), hi(0x0E4)} per word ----
for (int i=0;i<64;i++) model_stg[i]=64'd0;
axi_write32(38'h0D8, 32'd0);
for (int i=0;i<10;i++) begin axi_write32(38'h0DC, la[i][31:0]); axi_write32(38'h0E4, la[i][63:32]); end
repeat(8) @(posedge clk);
for (int i=0;i<10;i++) check_eq($sformatf("bridge_stgA[%0d]",i), model_stg[i][31:0], la[i][31:0]);
if (model_stg[0] !== la[0]) begin $error("[bridge_stg] A: WORD0 not at slot 0 (0x0D8 addr-reset broken)"); errors=errors+1; end
axi_read32(38'h0DC, rd); check_eq("A staged_addr (=nwords)", rd, 32'd10);
// ---- NEGATIVE test: 0x0D8 SETS the addr to the written value; writing 1 mis-slots word0 to slot 1 (the
// EXACT board failure that produced 0 rendered beats). This pins the register semantics. ----
for (int i=0;i<64;i++) model_stg[i]=64'd0;
axi_write32(38'h0D8, 32'd1);
axi_write32(38'h0DC, la[0][31:0]); axi_write32(38'h0E4, la[0][63:32]);
repeat(8) @(posedge clk);
if (model_stg[0][31:0] !== 32'd0) begin $error("[bridge_stg] neg: slot0 should stay empty when addr set to 1"); errors=errors+1; end
if (model_stg[1] !== la[0]) begin $error("[bridge_stg] neg: word0 must land at slot 1 when 0x0D8=1 (bug repro)"); errors=errors+1; end
// ---- SCENE B: re-stage a DIFFERENT list after resetting addr to 0 (A -> B, no shortcut) ----
for (int i=0;i<64;i++) model_stg[i]=64'd0;
axi_write32(38'h0D8, 32'd0);
for (int i=0;i<6;i++) begin axi_write32(38'h0DC, lb[i][31:0]); axi_write32(38'h0E4, lb[i][63:32]); end
repeat(8) @(posedge clk);
for (int i=0;i<6;i++) check_eq($sformatf("bridge_stgB[%0d]",i), model_stg[i][31:0], lb[i][31:0]);
if (model_stg[0] !== lb[0]) begin $error("[bridge_stg] B: WORD0 not at slot 0"); errors=errors+1; end
axi_read32(38'h0DC, rd); check_eq("B staged_addr (=nwords)", rd, 32'd6);
// ---- records_emitted readback (0x0E4 R reflects the feeder count; board expected 68/scene) ----
feeder_records_i = 16'd68; repeat(4) @(posedge clk);
axi_read32(38'h0E4, rd); check_eq("records_emitted readback", rd, 32'd68);
$display("[bridge_stg] OK: two lists staged via bridge writes; word0@slot0, addr=nwords, addr-set semantics pinned, records=68");
end
// --------------------------------------------------------
// Ch357 — persistent-Z observability status: clear_done (0x02C[7]), drop snapshot (0x0EC),
// sticky drops-nonzero (0x02C[8]). These are the host's fail-closed gates for the Z fit.
// --------------------------------------------------------
begin
frame_drained_i = 1'b0; clear_done_i = 1'b0; frag_drops_i = 32'd0; repeat(4) @(posedge clk);
axi_read32(38'h02C, rd); check_eq("Ch357 clear_done idle 0x02C[7]=0", {31'd0, rd[7]}, 32'd0);
axi_read32(38'h0EC, rd); check_eq("Ch357 FRAG_DROPS idle=0", rd, 32'd0);
axi_read32(38'h02C, rd); check_eq("Ch357 drops_nz idle 0x02C[8]=0", {31'd0, rd[8]}, 32'd0);
// clear_done asserts -> synced high at 0x02C[7]
clear_done_i = 1'b1; repeat(4) @(posedge clk);
axi_read32(38'h02C, rd); check_eq("Ch357 clear_done synced 0x02C[7]=1", {31'd0, rd[7]}, 32'd1);
// drop snapshot latches ONLY while drained (quiescent). Drained + drops=0 -> snapshot 0, sticky 0.
frame_drained_i = 1'b1; frag_drops_i = 32'd0; repeat(4) @(posedge clk);
axi_read32(38'h0EC, rd); check_eq("Ch357 FRAG_DROPS drained,0", rd, 32'd0);
axi_read32(38'h02C, rd); check_eq("Ch357 drops_nz still 0 0x02C[8]=0", {31'd0, rd[8]}, 32'd0);
// inject a drop while drained -> snapshot captures the count, sticky trips
frag_drops_i = 32'd5; repeat(4) @(posedge clk);
axi_read32(38'h0EC, rd); check_eq("Ch357 FRAG_DROPS snapshot=5", rd, 32'd5);
axi_read32(38'h02C, rd); check_eq("Ch357 drops_nz sticky set 0x02C[8]=1",{31'd0, rd[8]},32'd1);
// sticky is FAIL-CLOSED: persists even after drops clear and frame_drained drops
frag_drops_i = 32'd0; frame_drained_i = 1'b0; repeat(4) @(posedge clk);
axi_read32(38'h02C, rd); check_eq("Ch357 drops_nz sticky persists 0x02C[8]=1", {31'd0, rd[8]}, 32'd1);
clear_done_i = 1'b0;
$display("[bridge_ch357] OK: clear_done sync + drop snapshot + fail-closed sticky-nonzero pinned");
end
// --------------------------------------------------------
// Ch367 -- runtime CLUT residency ABI. The 1 KiB palette window
// is intentionally outside every legacy side-effect window. Pin
// the address/data write pulse, expected-sum round trip, busy
// gate, commit toggle, and done/result readback independently of
// the actual dual-clock copier.
// --------------------------------------------------------
begin
logic commit_before;
integer writes_before;
axi_write32(38'h200, 32'h11223344);
@(posedge clk);
check_eq("CLUT_STAGE data@0", clut_stage_last_data, 32'h11223344);
check_eq("CLUT_STAGE addr@0", {24'd0, clut_stage_last_addr}, 32'd0);
writes_before = clut_stage_writes;
axi_write32(38'h400, 32'h55667788);
@(posedge clk);
check_eq("CLUT_STAGE write@128", clut_stage_writes, writes_before + 1);
check_eq("CLUT_STAGE data@128", clut_stage_last_data, 32'h55667788);
check_eq("CLUT_STAGE addr@128", {24'd0, clut_stage_last_addr}, 32'd128);
writes_before = clut_stage_writes;
axi_write32(38'h5FC, 32'hAABBCCDD);
@(posedge clk);
check_eq("CLUT_STAGE write@255", clut_stage_writes, writes_before + 1);
check_eq("CLUT_STAGE data@255", clut_stage_last_data, 32'hAABBCCDD);
check_eq("CLUT_STAGE addr@255", {24'd0, clut_stage_last_addr}, 32'd255);
axi_write32(38'h1E4, 32'h89ABCDEF);
axi_read32(38'h1E4, rd); check_eq("CLUT expected sum", rd, 32'h89ABCDEF);
commit_before = clut_commit_tgl_o;
axi_write32(38'h1E0, 32'h1);
repeat(2) @(posedge clk);
check_eq("CLUT commit toggle", clut_commit_tgl_o ^ commit_before, 1'b1);
clut_busy_i = 1'b1; repeat(3) @(posedge clk);
axi_read32(38'h1E0, rd); check_eq("CLUT busy status", rd[0], 1'b1);
commit_before = clut_commit_tgl_o;
axi_write32(38'h1E0, 32'h1);
repeat(2) @(posedge clk);
check_eq("CLUT busy blocks commit", clut_commit_tgl_o ^ commit_before, 1'b0);
clut_busy_i = 1'b0; clut_crc_i = 32'h89ABCDEF; clut_done_tgl_i = ~clut_done_tgl_i;
repeat(4) @(posedge clk);
axi_read32(38'h1E0, rd); check_eq("CLUT done status", rd[1], 1'b1);
axi_read32(38'h1E8, rd); check_eq("CLUT result sum", rd, 32'h89ABCDEF);
$display("[bridge_clut] OK: palette window, commit/busy gate, done/result ABI pinned");
end
// --------------------------------------------------------
// Done.
// --------------------------------------------------------