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 @@
// Runtime palette staging: one CLOCK2_50 write port and one design-clock
// copy port. The host fills all 256 entries, then flips commit once. The
// design domain copies the stable bank into clut_stub and returns a sum32.
`timescale 1ns/1ps
module clut_stage_cdc (
input logic bclk,
input logic breset_n,
input logic b_we,
input logic [7:0] b_waddr,
input logic [31:0] b_wdata,
input logic b_commit_tgl,
input logic [31:0] b_expected_crc,
input logic dclk,
input logic dreset_n,
output logic clut_wr_en,
output logic [7:0] clut_wr_idx,
output logic [31:0] clut_wr_data,
output logic busy,
output logic done_tgl,
output logic [31:0] crc
);
// 256 x 32 fits in one M20K and remains a true dual-clock staging bank
// on the board instead of becoming a wide register array.
(* ramstyle = "M20K" *) logic [31:0] stage_mem [0:255];
logic [7:0] rd_addr;
logic [31:0] rd_data_q;
always_ff @(posedge bclk) begin
if (b_we)
stage_mem[b_waddr] <= b_wdata;
end
// This is the read side of the small dual-clock staging RAM. The host
// must finish writes before commit; commit is the coherency boundary.
always_ff @(posedge dclk) begin
rd_data_q <= stage_mem[rd_addr];
end
(* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION FORCED" *)
logic [2:0] commit_sync;
logic commit_seen;
logic [31:0] expected_crc_q;
logic [7:0] copy_idx;
logic [31:0] crc_acc;
typedef enum logic [1:0] { S_IDLE, S_PRIME, S_COPY } state_t;
state_t state;
always_ff @(posedge dclk or negedge dreset_n) begin
if (!dreset_n) begin
commit_sync <= 3'b000;
commit_seen <= 1'b0;
expected_crc_q <= 32'd0;
rd_addr <= 8'd0;
copy_idx <= 8'd0;
crc_acc <= 32'd0;
crc <= 32'd0;
clut_wr_en <= 1'b0;
clut_wr_idx <= 8'd0;
clut_wr_data <= 32'd0;
busy <= 1'b0;
done_tgl <= 1'b0;
state <= S_IDLE;
end else begin
commit_sync <= {commit_sync[1:0], b_commit_tgl};
clut_wr_en <= 1'b0;
// Consume any commit edge observed while a copy is live. A host
// retry belongs after done, never behind the current transaction.
if (busy && (commit_sync[2] != commit_seen))
commit_seen <= commit_sync[2];
case (state)
S_IDLE: begin
// A commit that arrives during a copy is deliberately not
// consumed. The host polls busy/done before reusing bank.
if (commit_sync[2] != commit_seen) begin
commit_seen <= commit_sync[2];
expected_crc_q <= b_expected_crc;
rd_addr <= 8'd0;
copy_idx <= 8'd0;
crc_acc <= 32'd0;
busy <= 1'b1;
state <= S_PRIME;
end
end
S_PRIME: begin
// Prime the synchronous read before writing entry zero.
rd_addr <= 8'd1;
state <= S_COPY;
end
S_COPY: begin
clut_wr_en <= 1'b1;
clut_wr_idx <= copy_idx;
clut_wr_data <= rd_data_q;
crc_acc <= crc_acc + rd_data_q;
if (copy_idx == 8'hFF) begin
crc <= crc_acc + rd_data_q;
busy <= 1'b0;
done_tgl <= ~done_tgl;
state <= S_IDLE;
end else begin
copy_idx <= copy_idx + 8'd1;
rd_addr <= rd_addr + 8'd1;
end
end
default: state <= S_IDLE;
endcase
end
end
// Keep expected_crc_q live in synthesis as an explicit documentation of
// the host/design transaction contract. The bridge returns `crc` on done;
// software compares it against its expected sum before GO.
wire _unused_expected_crc = ^expected_crc_q;
endmodule : clut_stage_cdc
+84 -2
View File
@@ -308,6 +308,13 @@ module ps2_hps_bridge (
input logic [31:0] lpddr_bresp_err_i, // (folded into 0x02C status)
input logic [31:0] lpddr_fifo_ovf_i, // (folded into 0x02C status)
input logic lpddr_idle_i, // 0x02C[0] — writer idle/done
input logic clear_done_i, // 0x02C[7] — Ch357 persistent-Z preclear complete (EMIF domain; synced).
// Host MUST wait for this before the first GO (z_rmw clear_start done).
input logic [31:0] frag_drops_i, // Ch357 — persistent-Z request-FIFO dropped-fragment count (design_clk).
// 0x0EC snapshot (latched while drained/quiescent) + 0x02C[8] sticky-nonzero.
input logic frame_drained_i, // 0x02C[6] — Ch353 ordered drain ack (EMIF domain; synced). STABLE,
// unlike transient idle: asserts only after the EOF marker's last BRESP,
// so the host confirms the FULL render drained (not a mid-batch idle).
// ---- Ch319 Brick 3: LPDDR4B read-probe (HPS reads FB content back through the
// bridge — no /dev/mem). 0x03C: WRITE sets the byte address + triggers a read;
@@ -393,6 +400,18 @@ module ps2_hps_bridge (
input logic [15:0] feeder_records_i, // 0x0E4 records_emitted (async counter)
input logic [31:0] feeder_waits_i, // 0x0E8 fifo_wait_cycles (async counter)
// Ch367 -- runtime CLUT staging. HPS writes a 1 KiB bank at
// 0x200..0x5FC, stores its expected sum32 at 0x1E4, then writes bit0
// at 0x1E0 to commit. One toggle crosses to the design-clock copier.
output logic [7:0] clut_stage_waddr_o,
output logic [31:0] clut_stage_wdata_o,
output logic clut_stage_we_o,
output logic clut_commit_tgl_o,
output logic [31:0] clut_expected_crc_o,
input logic clut_busy_i,
input logic clut_done_tgl_i,
input logic [31:0] clut_crc_i,
// ---- AXI4 slave (signature matches ps2_hps_bridge_null) ----
input logic [3:0] s_axi_awid,
input logic [37:0] s_axi_awaddr,
@@ -648,6 +667,10 @@ module ps2_hps_bridge (
// is IDLE, so latch them into clk-domain snapshots WHILE the synced idle is high (data stable -> coherent).
// The write-probe error counter latches on the synced write-done edge (count stable when a write completes).
logic [1:0] lpddr_idle_sync;
logic [1:0] frame_drained_sync; // Ch353 — 2-FF sync of frame_drained_i into the bridge clock
logic [1:0] clear_done_sync; // Ch357 — 2-FF sync of clear_done_i (persistent-Z preclear done)
logic [31:0] frag_drops_snap_q; // Ch357 — clk-domain snapshot of frag_drops_i, latched while drained (quiescent)
logic frag_drops_nz_sticky_q; // Ch357 — sticky: set once any per-scene snapshot is nonzero (fail-closed drop flag)
logic [31:0] lpddr_bytes_snap_q;
logic [31:0] lpddr_bursts_snap_q;
logic [31:0] lpddr_bresp_err_snap_q;
@@ -793,7 +816,13 @@ module ps2_hps_bridge (
// by addr[37:5]==33'h08 (= 0x100/32).
// Anything else reads 0.
reg_read = 32'd0;
if (addr[37:8] == '0) begin
if (addr == 38'h00000001E0) begin
reg_read = {30'd0, clut_done_sync[2], clut_busy_sync[1]}; // CLUT_STATUS
end else if (addr == 38'h00000001E4) begin
reg_read = clut_expected_crc_q; // CLUT_EXPECTED_CRC
end else if (addr == 38'h00000001E8) begin
reg_read = clut_crc_q; // CLUT_RESULT_CRC
end else if (addr[37:8] == '0) begin
case (addr[7:2])
6'h00: reg_read = CORE_ID; // 0x000
6'h01: reg_read = ABI_VERSION; // 0x004
@@ -808,7 +837,8 @@ module ps2_hps_bridge (
6'h0A: reg_read = raster_overflow_count; // 0x028
// 0x02C LPDDR_STATUS (R): [0]idle [1]bresp_err [2]fifo_ovf [3]rd_pending [4]scan_cache_valid [5]scan_rd_err.
// Ch352 — idle via 2-FF sync; bresp_err/fifo_ovf reduced from the clk-domain snapshots (coherent).
6'h0B: reg_read = {26'd0, lpddr_scan_err_sync[1], lpddr_scan_valid_sync[1], lpddr_rd_pending, (|lpddr_fifo_ovf_snap_q), (|lpddr_bresp_err_snap_q), lpddr_idle_sync[1]};
// Ch353 — [6]frame_drained (STABLE ordered drain ack; poll THIS, not transient [0]idle).
6'h0B: reg_read = {23'd0, frag_drops_nz_sticky_q, clear_done_sync[1], frame_drained_sync[1], lpddr_scan_err_sync[1], lpddr_scan_valid_sync[1], lpddr_rd_pending, (|lpddr_fifo_ovf_snap_q), (|lpddr_bresp_err_snap_q), lpddr_idle_sync[1]};
6'h0C: reg_read = lpddr_bytes_snap_q; // 0x030 LPDDR_BYTES (Ch352 — clk-domain snapshot)
6'h0D: reg_read = lpddr_bursts_snap_q; // 0x034 LPDDR_BURSTS (Ch352 — clk-domain snapshot)
6'h0E: reg_read = lpddr_bresp_err_snap_q; // 0x038 LPDDR_BRESP_ERRS (Ch352 — clk-domain snapshot)
@@ -859,6 +889,7 @@ module ps2_hps_bridge (
6'h37: reg_read = {20'd0, feeder_addr_q}; // 0x0DC FEEDER_STG_ADDR (current write index)
6'h39: reg_read = {16'd0, feeder_records_i}; // 0x0E4 FEEDER_RECORDS (records_emitted)
6'h3A: reg_read = feeder_waits_i; // 0x0E8 FEEDER_WAITS (fifo_wait_cycles)
6'h3B: reg_read = frag_drops_snap_q; // 0x0EC LPDDR_FRAG_DROPS (Ch357 R): persistent-Z dropped-fragment snapshot (per-scene delta must be 0)
6'h18: reg_read = video_status; // 0x060 VIDEO_STATUS (Ch225)
6'h19: reg_read = hdmi_diag; // 0x064 HDMI_DIAG (Ch225)
// Ch248 — replaced the Ch226 fake DS2 with the real
@@ -950,6 +981,11 @@ module ps2_hps_bridge (
wire write_is_feeder_hi = write_in_window && (aw_addr_q[7:2] == 6'h39); // 0x0E4 high 32 -> commit {hi,lo}, addr++
wire write_is_feeder_go = write_in_window && (aw_addr_q[7:2] == 6'h3A); // 0x0E8 bit0 -> retrigger pulse
wire write_is_clut_commit = (aw_addr_q == 38'h00000001E0);
wire write_is_clut_expected= (aw_addr_q == 38'h00000001E4);
wire write_in_clut_stage = (aw_addr_q >= 38'h0000000200)
&& (aw_addr_q <= 38'h00000005FC);
// Ch223 — OSD compatibility-sink decode at 0x100..0x11F. The OSD
// block is a 32-byte window outside the first-128-byte side-effect
// region, so it gets its own `addr[37:5] == 33'h08` guard
@@ -1009,6 +1045,9 @@ module ps2_hps_bridge (
assign core_ctrl_d = wdata_lane[2:0];
assign core_pulse_we = write_accept && write_is_pulse;
assign hdmi_clr_pulse = core_pulse_we && wdata_lane[3];
assign clut_stage_we_o = write_accept && write_in_clut_stage;
assign clut_stage_waddr_o = (aw_addr_q - 38'h0000000200) >> 2;
assign clut_stage_wdata_o = wdata_lane;
// Ch222 — input-latch register file. One always_ff covers all three
// 32-bit latches; the lane-aligned `wdata_lane` (selected by
@@ -1059,6 +1098,10 @@ module ps2_hps_bridge (
tex_rd_errs_snap_q <= 32'd0;
lpddr_ctrl_commit_q <= 1'b0;
lpddr_idle_sync <= 2'b00;
frame_drained_sync <= 2'b00;
clear_done_sync <= 2'b00;
frag_drops_snap_q <= 32'd0;
frag_drops_nz_sticky_q<= 1'b0;
lpddr_bytes_snap_q <= 32'd0;
lpddr_bursts_snap_q <= 32'd0;
lpddr_bresp_err_snap_q<= 32'd0;
@@ -1089,12 +1132,22 @@ module ps2_hps_bridge (
end
// Ch352 — remaining status CDC. idle: 2-FF sync. FB-writer counters: capture WHILE idle (stable).
lpddr_idle_sync <= {lpddr_idle_sync[0], lpddr_idle_i};
frame_drained_sync <= {frame_drained_sync[0], frame_drained_i};
if (lpddr_idle_sync[1]) begin
lpddr_bytes_snap_q <= lpddr_bytes_i;
lpddr_bursts_snap_q <= lpddr_bursts_i;
lpddr_bresp_err_snap_q <= lpddr_bresp_err_i;
lpddr_fifo_ovf_snap_q <= lpddr_fifo_ovf_i;
end
// Ch357 — persistent-Z preclear ack: 2-FF sync (single-bit, EMIF domain), same as frame_drained.
clear_done_sync <= {clear_done_sync[0], clear_done_i};
// Ch357 — drop count: latch WHILE drained (fragment flow stopped -> frag_drops_i stable, coherent multi-bit
// capture, same idle-gated idea as the FB-writer counters). The sticky flag OR-accumulates any nonzero
// snapshot so a single dropped fragment latches a fail-closed bit the host reads after every scene.
if (frame_drained_sync[1]) begin
frag_drops_snap_q <= frag_drops_i;
frag_drops_nz_sticky_q <= frag_drops_nz_sticky_q | (|frag_drops_i);
end
// write-probe error count: stable when a write completes -> latch on the synced write-done edge.
if (lpddr_wr_done_sync[2] != lpddr_wr_done_sync[1])
lpddr_wr_bresp_err_snap_q <= lpddr_wr_bresp_err_i;
@@ -1178,6 +1231,35 @@ module ps2_hps_bridge (
assign feeder_stg_wdata_o = feeder_wdata_q;
assign feeder_go_tgl_o = feeder_go_tgl_q;
// The staging write port is in this bridge clock domain. Only the
// commit toggle crosses domains, after the host has written all entries.
logic clut_commit_tgl_q;
logic [31:0] clut_expected_crc_q;
logic [1:0] clut_busy_sync;
logic [2:0] clut_done_sync;
logic [31:0] clut_crc_q;
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
clut_commit_tgl_q <= 1'b0;
clut_expected_crc_q <= 32'd0;
clut_busy_sync <= 2'b00;
clut_done_sync <= 3'b000;
clut_crc_q <= 32'd0;
end else begin
clut_busy_sync <= {clut_busy_sync[0], clut_busy_i};
clut_done_sync <= {clut_done_sync[1:0], clut_done_tgl_i};
if (clut_done_sync[2] != clut_done_sync[1])
clut_crc_q <= clut_crc_i;
if (write_accept) begin
if (write_is_clut_expected) clut_expected_crc_q <= wdata_lane;
if (write_is_clut_commit && wdata_lane[0] && !clut_busy_sync[1])
clut_commit_tgl_q <= ~clut_commit_tgl_q;
end
end
end
assign clut_commit_tgl_o = clut_commit_tgl_q;
assign clut_expected_crc_o = clut_expected_crc_q;
// Ch223 — OSD-sink register file. Same pattern as Ch222.
// Ch245 — OSD_TRIGGER added with sibling-ABI semantics: bits are
// SET by single-cycle pulses from the menu FSM and CLEARED by HPS
+1
View File
@@ -85,6 +85,7 @@ module tile_ram_cdc (
// Three stages let us compute an edge detector against the
// already-resampled bits ([2] ^ [1]), giving the wr_pulse a full
// dclk cycle of bclk_wr_index/data stability before we sample.
(* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION FORCED" *)
logic [2:0] toggle_sync;
always_ff @(posedge dclk or negedge dreset_n) begin
if (!dreset_n)