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:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user