Ch442: A+B scanout diagnostic (split LPDDR_STATUS[5] + first-failure snapshot)

Read-only diagnostic to disambiguate the three causes folded into
LPDDR_STATUS[5]. bit5 (0x02C) semantics are UNCHANGED.

- gs_lpddr_scanout_lb: register a live (rd_errs!=0) flag in the emif
  domain (the raw counter never crosses), and capture the FIRST raw
  underflow of each video-source-enabled session as a bundled-data
  snapshot (scan_y/nf_v/nf_s0 + base-vs-lookahead cause + line_valid +
  vphase), held stable until !enable. The existing sticky underflow_v
  latch, fetch FSM, pixel path and arbitration are untouched.
- ps2_hps_bridge: independently 2-FF sync the two split live flags;
  latch the snapshot on the rising synced-valid edge (coherent bundled
  data). New read-only regs 0x120 SCAN_DIAG_STATUS / 0x124
  SCAN_DIAG_FIRST (window addr[37:5]==9); 0x118/0x11C reserved slots
  untouched.
- top: drive 5 diag nets per scanout arm like scan_err_w; bit5 assign
  unchanged.
- tb_gs_scanout_diag (new): provokes AXI RRESP error, cold-start row-0
  starvation, first-failure capture + coherent bridge readback, and
  clear-via-video-source-disable (17/17 checks).
- Tie off the new bridge inputs in the four .*-instantiating TBs.

Sim set all PASS: focused TB, tb_ps2_hps_bridge, scanout_lb
{binomial,hstretch,psm32_256}, and the complete f52 replay (FB
byte-identical: Z 0/307200, COLOR 0/245760; sum32=0xaad0b94d).

No fix / gray-code / persistence-filter / scanout checksum. No Quartus,
no board, no push.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-21 22:40:07 -04:00
parent 2123e646c8
commit 6720caee12
9 changed files with 374 additions and 1 deletions
+59
View File
@@ -84,6 +84,14 @@ module gs_lpddr_scanout_lb #(
output logic underflow, // a pixel was read before its row was ready (sticky)
output logic [31:0] rd_errs, // non-OKAY read responses (cumulative)
// ---- Ch442 A+B diagnostic (read-only; NOTHING here feeds fetch/output/arbitration/underflow) ----
// Splits the single top-level scan-error bit into independently observable causes and captures
// the FIRST raw-underflow event of each video-source-enabled session as a bundled-data snapshot.
output logic diag_rderr_nz, // LIVE (rd_errs != 0), reduced+registered in the axi_clk domain (single bit; the raw counter never crosses)
output logic diag_valid, // first raw-underflow captured; held stable until video source disabled (!enable)
output logic [29:0] diag_first, // SNAPSHOT, stable while diag_valid: {nf_s0[9:0], nf_v[9:0], scan_y[9:0]}
output logic [6:0] diag_stat, // SNAPSHOT, stable while diag_valid: {vphase[3:0], line_valid, lookahead_cause, base_cause}
// ---- AXI4 read channel to the EMIF user port (axi_clk, 256-bit) ----
output logic [29:0] araddr,
output logic [1:0] arburst,
@@ -651,6 +659,10 @@ module gs_lpddr_scanout_lb #(
logic [255:0] lb0_wdata_q, lb1_wdata_q, lb2_wdata_q;
logic [RB_BITS-1:0] lb0_waddr_q, lb1_waddr_q, lb2_waddr_q;
logic lb0_we_q, lb1_we_q, lb2_we_q;
// Ch442 diag: registered single-bit reduction of the read-error counter (emif domain).
// Diagnostic-only; nothing downstream reads it, so it cannot perturb the fetch FSM.
logic rd_err_nz_q;
assign diag_rderr_nz = rd_err_nz_q;
always_ff @(posedge axi_clk) begin
if (!axi_rst_n) begin
@@ -664,6 +676,7 @@ module gs_lpddr_scanout_lb #(
cur_row <= '0; cur_buf <= 2'd0; beat <= '0;
line_valid <= 1'b0; rd_errs <= 32'd0; fs_pending <= 1'b0;
lb0_we_q <= 1'b0; lb1_we_q <= 1'b0; lb2_we_q <= 1'b0;
rd_err_nz_q <= 1'b0;
end else begin
fs_sync_e <= {fs_sync_e[1:0], frame_start};
disp_row_s0 <= disp_row_v; // 2-FF sync of the display row
@@ -684,6 +697,9 @@ module gs_lpddr_scanout_lb #(
lb0_we_q <= 1'b0;
lb1_we_q <= 1'b0;
lb2_we_q <= 1'b0;
// Ch442 diag: register the read-error-nonzero flag (emif domain). One cycle of lag
// vs rd_errs is irrelevant for a stuck-nonzero indicator; keeps a clean launch flop.
rd_err_nz_q <= (rd_errs != 32'd0);
// vsync: mark a prefetch restart. DEFER it to L_IDLE so an in-flight AXI
// read is never aborted mid-handshake (which would deadlock the slave).
if (fs_edge_e) fs_pending <= 1'b1;
@@ -776,4 +792,47 @@ module gs_lpddr_scanout_lb #(
underflow_v <= 1'b1;
end
assign underflow = underflow_v;
// ================= Ch442 A+B first-failure diagnostic (read-only) =================
// Re-express the EXACT raw-underflow predicate (identical boolean to the sticky latch
// above) as combinational wires so the capture can name WHICH sub-cause fired. These
// wires and the registers below drive ONLY the diag_* outputs — never the fetch FSM,
// the pixel path, arbitration, or underflow_v. The existing latch block is untouched.
wire uf_in_range = enable && in_window && (scan_y < ($clog2(N_ROWS)+1)'(N_ROWS));
wire uf_base_cond = uf_in_range && (scan_y >= nf_v);
wire uf_look_cond = uf_in_range &&
(BINOMIAL_3X3_FILTER ||
(V_LINEAR_FILTER && (stretch_vphase_q != 4'd0))) &&
(scan_y + 1'b1 < ($clog2(N_ROWS)+1)'(N_ROWS)) &&
(scan_y + 1'b1 >= nf_v);
wire raw_uf_cond = uf_base_cond || uf_look_cond;
// line_valid is axi_clk-domain; 2-FF into video_clk for coherent capture.
logic [1:0] line_valid_vsync;
// First-failure snapshot (video_clk). diag_valid holds from the FIRST raw underflow
// until the video source drops (!enable) — NOT cleared per frame (unlike underflow_v),
// so it names the first failure of the whole enabled session. The bundled snapshot
// ({nf_s0,nf_v,scan_y} + cause/line_valid/vphase) stays stable while diag_valid is high,
// so the bridge can transfer it coherently with a single synchronized valid.
logic diag_valid_q, diag_base_q, diag_look_q, diag_lv_q;
logic [3:0] diag_vphase_q;
logic [$clog2(N_ROWS):0] diag_scan_y_q, diag_nf_v_q, diag_nf_s0_q;
always_ff @(posedge video_clk) begin
line_valid_vsync <= {line_valid_vsync[0], line_valid};
if (!enable) begin
diag_valid_q <= 1'b0;
end else if (!diag_valid_q && raw_uf_cond) begin
diag_valid_q <= 1'b1;
diag_base_q <= uf_base_cond;
diag_look_q <= uf_look_cond;
diag_lv_q <= line_valid_vsync[1];
diag_vphase_q <= stretch_vphase_q;
diag_scan_y_q <= scan_y;
diag_nf_v_q <= nf_v;
diag_nf_s0_q <= nf_s0;
end
end
assign diag_valid = diag_valid_q;
assign diag_first = {10'(diag_nf_s0_q), 10'(diag_nf_v_q), 10'(diag_scan_y_q)};
assign diag_stat = {diag_vphase_q, diag_lv_q, diag_look_q, diag_base_q};
endmodule