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:
@@ -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
|
||||
|
||||
@@ -328,7 +328,17 @@ module ps2_hps_bridge (
|
||||
output logic lpddr_video_src_o, // 0x018[2] — 1 = HDMI sourced from LPDDR4B scanout (default 0 = BRAM)
|
||||
output logic lpddr_scanout_lb_o, // 0x018[3] — 1 = line-buffer scanout, 0 = frame-cache (Ch321; default 0)
|
||||
input logic lpddr_scan_valid_i, // scanout frame cache loaded (emif_clk; synced -> 0x02C[4])
|
||||
input logic lpddr_scan_err_i, // scanout read errors seen (emif_clk; synced -> 0x02C[5])
|
||||
input logic lpddr_scan_err_i, // scanout read errors seen (emif_clk; synced -> 0x02C[5]) — UNCHANGED (bit5 preserved)
|
||||
|
||||
// ---- Ch442 A+B scanout diagnostic (read-only; splits 0x02C[5] causes + first-failure snapshot) ----
|
||||
// 0x02C[5] keeps its exact meaning (still driven by lpddr_scan_err_i). These add a split view at
|
||||
// 0x120/0x124. Each single-bit flag is synchronized INDEPENDENTLY; the wide snapshot is transferred
|
||||
// via a bundled-data handshake (stable payload + one synchronized valid), never raw multi-bit reads.
|
||||
input logic scan_diag_uf_i, // LIVE line-buffer underflow alone (video_clk sticky; 2-FF -> 0x120[1])
|
||||
input logic scan_diag_rderr_nz_i,// LIVE (lb_rd_errs != 0), reduced in fabric (emif_clk; 2-FF -> 0x120[2])
|
||||
input logic scan_diag_valid_i, // first-failure captured, held to !enable (video_clk level; bundled-data valid)
|
||||
input logic [29:0] scan_diag_first_i, // SNAPSHOT {nf_s0[9:0],nf_v[9:0],scan_y[9:0]} (stable while valid -> 0x124)
|
||||
input logic [6:0] scan_diag_stat_i, // SNAPSHOT {vphase[3:0],line_valid,lookahead,base} (stable while valid -> 0x120)
|
||||
|
||||
// ---- Ch322: LPDDR write-probe (HPS stages texture words) + texture-cache fill ----
|
||||
// 0x040 LPDDR_WRADDR (W): set the LPDDR byte address (auto-increments +4 per data write).
|
||||
@@ -640,6 +650,12 @@ module ps2_hps_bridge (
|
||||
logic [1:0] lpddr_scan_valid_sync, lpddr_scan_err_sync;
|
||||
assign lpddr_video_src_o = lpddr_video_src_q;
|
||||
assign lpddr_scanout_lb_o = lpddr_scanout_lb_q;
|
||||
// Ch442 A+B diagnostic — independently synced live flags + bundled-data snapshot capture.
|
||||
logic [1:0] scan_diag_uf_sync, scan_diag_rderr_sync;
|
||||
logic [2:0] scan_diag_valid_sync; // 3-deep so the RISING synced edge latches the snapshot
|
||||
logic scan_diag_base_q, scan_diag_look_q, scan_diag_lv_q;
|
||||
logic [3:0] scan_diag_vphase_q;
|
||||
logic [9:0] scan_diag_scan_y_q, scan_diag_nf_v_q, scan_diag_nf_s0_q;
|
||||
// Ch322 — LPDDR write-probe + texture-cache fill registers + return-path CDC.
|
||||
logic [31:0] lpddr_wr_addr_q; // "next write" pointer (auto-increments)
|
||||
logic [31:0] lpddr_wr_addr_present_q;// the address that goes WITH the current data word
|
||||
@@ -924,6 +940,19 @@ module ps2_hps_bridge (
|
||||
3'h5: reg_read = osd_cfg1_q; // 0x114 OSD_CFG1
|
||||
default: reg_read = 32'd0; // 0x118 / 0x11C reserved-zero
|
||||
endcase
|
||||
end else if (addr[37:5] == 33'h09) begin
|
||||
// Ch442 — scanout A+B diagnostic window (0x120..0x13F). Read-only; distinct
|
||||
// from the reserved 0x118/0x11C (which stay in the 33'h08 window above).
|
||||
case (addr[4:2])
|
||||
// 0x120 SCAN_DIAG_STATUS: [0]valid [1]underflow [2]read-error-nonzero
|
||||
// [3]cause_base(scan_y>=nf_v) [4]cause_lookahead [5]line_valid [11:8]filter vphase.
|
||||
3'h0: reg_read = {20'd0, scan_diag_vphase_q, 2'd0,
|
||||
scan_diag_lv_q, scan_diag_look_q, scan_diag_base_q,
|
||||
scan_diag_rderr_sync[1], scan_diag_uf_sync[1], scan_diag_valid_sync[1]};
|
||||
// 0x124 SCAN_DIAG_FIRST: [9:0]scan_y [19:10]nf_v [29:20]nf_s0.
|
||||
3'h1: reg_read = {2'd0, scan_diag_nf_s0_q, scan_diag_nf_v_q, scan_diag_scan_y_q};
|
||||
default: reg_read = 32'd0;
|
||||
endcase
|
||||
end else if (addr[37:12] == 26'h1) begin
|
||||
// Ch227 — tile RAM 0x1000..0x1FFF, 1024 × 32-bit.
|
||||
reg_read = tile_mem[addr[11:2]];
|
||||
@@ -1083,6 +1112,16 @@ module ps2_hps_bridge (
|
||||
lpddr_scanout_lb_q <= 1'b0; // Ch321 — default frame-cache (line-buffer = opt-in)
|
||||
lpddr_scan_valid_sync <= 2'b00;
|
||||
lpddr_scan_err_sync <= 2'b00;
|
||||
scan_diag_uf_sync <= 2'b00;
|
||||
scan_diag_rderr_sync <= 2'b00;
|
||||
scan_diag_valid_sync <= 3'b000;
|
||||
scan_diag_base_q <= 1'b0;
|
||||
scan_diag_look_q <= 1'b0;
|
||||
scan_diag_lv_q <= 1'b0;
|
||||
scan_diag_vphase_q <= 4'd0;
|
||||
scan_diag_scan_y_q <= 10'd0;
|
||||
scan_diag_nf_v_q <= 10'd0;
|
||||
scan_diag_nf_s0_q <= 10'd0;
|
||||
lpddr_wr_addr_q <= 32'd0;
|
||||
lpddr_wr_addr_present_q <= 32'd0;
|
||||
lpddr_wr_data_q <= 32'd0;
|
||||
@@ -1116,6 +1155,21 @@ module ps2_hps_bridge (
|
||||
// Ch320 — scanout status sync (emif_clk -> clk).
|
||||
lpddr_scan_valid_sync <= {lpddr_scan_valid_sync[0], lpddr_scan_valid_i};
|
||||
lpddr_scan_err_sync <= {lpddr_scan_err_sync[0], lpddr_scan_err_i};
|
||||
// Ch442 — independent 2-FF syncs of the two split live flags (never the raw counter).
|
||||
scan_diag_uf_sync <= {scan_diag_uf_sync[0], scan_diag_uf_i};
|
||||
scan_diag_rderr_sync <= {scan_diag_rderr_sync[0], scan_diag_rderr_nz_i};
|
||||
// Bundled-data capture: sync the valid, and on its RISING synced edge the snapshot
|
||||
// (held stable in the video domain since first-failure) is quiescent -> latch coherently.
|
||||
scan_diag_valid_sync <= {scan_diag_valid_sync[1:0], scan_diag_valid_i};
|
||||
if (scan_diag_valid_sync[1] && !scan_diag_valid_sync[2]) begin
|
||||
scan_diag_scan_y_q <= scan_diag_first_i[9:0];
|
||||
scan_diag_nf_v_q <= scan_diag_first_i[19:10];
|
||||
scan_diag_nf_s0_q <= scan_diag_first_i[29:20];
|
||||
scan_diag_base_q <= scan_diag_stat_i[0];
|
||||
scan_diag_look_q <= scan_diag_stat_i[1];
|
||||
scan_diag_lv_q <= scan_diag_stat_i[2];
|
||||
scan_diag_vphase_q <= scan_diag_stat_i[6:3];
|
||||
end
|
||||
// Ch322 — texture-fill / write-probe status sync (emif_clk -> clk).
|
||||
tex_fill_done_sync <= {tex_fill_done_sync[0], tex_fill_done_i};
|
||||
lpddr_wr_busy_sync <= {lpddr_wr_busy_sync[0], lpddr_wr_busy_i};
|
||||
|
||||
@@ -523,6 +523,10 @@ module de25_nano_psmct32_raster_demo_top (
|
||||
wire scanout_lb_w; // Ch321 — 1 = line-buffer scanout, 0 = frame-cache
|
||||
wire [7:0] scan_r_w, scan_g_w, scan_b_w;
|
||||
wire scan_cache_valid_w, scan_err_w;
|
||||
// Ch442 A+B scanout diagnostic nets (top-level; driven per scanout arm like scan_err_w).
|
||||
wire scan_diag_uf_w, scan_diag_rderr_nz_w, scan_diag_valid_w;
|
||||
wire [29:0] scan_diag_first_w;
|
||||
wire [6:0] scan_diag_stat_w;
|
||||
// Ch320/Ch321 — LPDDR scanout frame-cache size: 256 beats (8 KiB, 64x64) by default,
|
||||
// 1024 beats (32 KiB, 128x128) for the Ch321 larger-frame demo.
|
||||
`ifdef GS_TILE_LPDDR128_DEMO
|
||||
@@ -2673,6 +2677,8 @@ module de25_nano_psmct32_raster_demo_top (
|
||||
.pixel_x(pixel_x), .pixel_y(pixel_y), .in_window(demo_pix_window),
|
||||
.r(lb_r_w), .g(lb_g_w), .b(lb_b_w),
|
||||
.line_valid(lb_valid_w), .underflow(lb_underflow_w), .rd_errs(lb_rd_errs_w),
|
||||
.diag_rderr_nz(scan_diag_rderr_nz_w), .diag_valid(scan_diag_valid_w),
|
||||
.diag_first(scan_diag_first_w), .diag_stat(scan_diag_stat_w),
|
||||
.araddr(lb_araddr), .arburst(lb_arburst), .arid(lb_arid),
|
||||
.arlen(lb_arlen), .arsize(lb_arsize), .arvalid(lb_arvalid),
|
||||
.arready(scan_ar_arready & scanout_lb_eff),
|
||||
@@ -2693,6 +2699,9 @@ module de25_nano_psmct32_raster_demo_top (
|
||||
assign scan_cache_valid_w = scanout_lb_eff ? lb_valid_w : fc_valid_w;
|
||||
assign scan_err_w = scanout_lb_eff ? (lb_underflow_w | (lb_rd_errs_w != 32'd0))
|
||||
: (fc_rd_errs_w != 32'd0);
|
||||
// Ch442 — the split underflow flag (bit5 above is UNCHANGED). Only meaningful on the
|
||||
// line-buffer path; the frame-cache fallback has no per-row prefetch to underflow.
|
||||
assign scan_diag_uf_w = scanout_lb_eff ? lb_underflow_w : 1'b0;
|
||||
|
||||
// 2:1 read arbiter — scanout (s0, priority) + probe (s1) onto the EMIF read channel.
|
||||
gs_lpddr_rd_arb u_lpddr_rd_arb (
|
||||
@@ -2823,6 +2832,8 @@ module de25_nano_psmct32_raster_demo_top (
|
||||
assign lpddr_rd_data_w=32'd0; assign lpddr_rd_done_w=1'b0; // no read-probe in the f2sdram path
|
||||
assign scan_r_w=8'd0; assign scan_g_w=8'd0; assign scan_b_w=8'd0;
|
||||
assign scan_cache_valid_w=1'b0; assign scan_err_w=1'b0; // no LPDDR scanout
|
||||
assign scan_diag_uf_w=1'b0; assign scan_diag_rderr_nz_w=1'b0; assign scan_diag_valid_w=1'b0;
|
||||
assign scan_diag_first_w=30'd0; assign scan_diag_stat_w=7'd0; // Ch442 diag tie-off (no LPDDR scanout)
|
||||
assign tex_fill_done_w=1'b0; assign tex_fill_beats_w=32'd0; assign tex_fill_bytes_w=32'd0;
|
||||
assign tex_rd_errs_w=32'd0; assign tex_fill_crc_w=32'd0;
|
||||
assign lpddr_wr_busy_w=1'b0; assign lpddr_wr_done_w=1'b0; assign lpddr_wr_bresp_err_w=32'd0;
|
||||
@@ -2843,6 +2854,8 @@ module de25_nano_psmct32_raster_demo_top (
|
||||
assign lpddr_rd_data_w=32'd0; assign lpddr_rd_done_w=1'b0; // no read-probe (inert path)
|
||||
assign scan_r_w=8'd0; assign scan_g_w=8'd0; assign scan_b_w=8'd0;
|
||||
assign scan_cache_valid_w=1'b0; assign scan_err_w=1'b0; // no LPDDR scanout
|
||||
assign scan_diag_uf_w=1'b0; assign scan_diag_rderr_nz_w=1'b0; assign scan_diag_valid_w=1'b0;
|
||||
assign scan_diag_first_w=30'd0; assign scan_diag_stat_w=7'd0; // Ch442 diag tie-off (no LPDDR scanout)
|
||||
assign tex_fill_done_w=1'b0; assign tex_fill_beats_w=32'd0; assign tex_fill_bytes_w=32'd0;
|
||||
assign tex_rd_errs_w=32'd0; assign tex_fill_crc_w=32'd0;
|
||||
assign lpddr_wr_busy_w=1'b0; assign lpddr_wr_done_w=1'b0; assign lpddr_wr_bresp_err_w=32'd0;
|
||||
@@ -3108,6 +3121,11 @@ module de25_nano_psmct32_raster_demo_top (
|
||||
.lpddr_scanout_lb_o(scanout_lb_w),
|
||||
.lpddr_scan_valid_i(scan_cache_valid_w),
|
||||
.lpddr_scan_err_i (scan_err_w),
|
||||
.scan_diag_uf_i (scan_diag_uf_w),
|
||||
.scan_diag_rderr_nz_i(scan_diag_rderr_nz_w),
|
||||
.scan_diag_valid_i (scan_diag_valid_w),
|
||||
.scan_diag_first_i (scan_diag_first_w),
|
||||
.scan_diag_stat_i (scan_diag_stat_w),
|
||||
// Ch322 — LPDDR write-probe (HPS stages texture words) + texture-cache fill.
|
||||
.lpddr_wr_addr_o (lpddr_wr_addr_w),
|
||||
.lpddr_wr_data_o (lpddr_wr_data_w),
|
||||
|
||||
@@ -1261,6 +1261,15 @@ tb_gs_lpddr_scanout_lb_binomial: dirs
|
||||
@echo "=== run tb_gs_lpddr_scanout_lb_binomial ==="
|
||||
@cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_lpddr_scanout_lb_binomial.vvp
|
||||
|
||||
tb_gs_scanout_diag: dirs
|
||||
@echo "=== build tb_gs_scanout_diag ==="
|
||||
$(IVERILOG) $(IVERILOG_FLGS) \
|
||||
-o $(BUILD_DIR)/tb_gs_scanout_diag.vvp \
|
||||
-s tb_gs_scanout_diag \
|
||||
$(RTL_SRCS) $(TB_ROOT)/gif_gs/tb_gs_scanout_diag.sv
|
||||
@echo "=== run tb_gs_scanout_diag ==="
|
||||
@cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_scanout_diag.vvp
|
||||
|
||||
tb_gs_texture_cache: dirs
|
||||
@echo "=== build tb_gs_texture_cache ==="
|
||||
$(IVERILOG) $(IVERILOG_FLGS) \
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
// retroDE_ps2 — tb_gs_scanout_diag (Ch442 A+B scanout diagnostic)
|
||||
//
|
||||
// Integration test for the READ-ONLY A+B diagnostic that splits the single
|
||||
// LPDDR_STATUS[5] scan-error bit into independently observable causes and
|
||||
// captures the FIRST raw-underflow event of each video-source-enabled session.
|
||||
//
|
||||
// Wires the REAL gs_lpddr_scanout_lb diag outputs into the REAL ps2_hps_bridge
|
||||
// diag inputs and reads back 0x120 SCAN_DIAG_STATUS / 0x124 SCAN_DIAG_FIRST over
|
||||
// the bridge AXI4-lite slave. A behavioral EMIF read responder provokes each
|
||||
// cause SEPARATELY:
|
||||
// P0 baseline : healthy reads, no active pixels -> all diag bits 0 (no false positive)
|
||||
// P1 starvation: arready held low + active row 0 -> underflow, first-failure snapshot captured
|
||||
// P2 clear : drop video source (enable=0) -> valid + underflow clear
|
||||
// P3 rrerror : SLVERR reads, NO active pixels -> read-error flag set WITHOUT underflow
|
||||
//
|
||||
// Three asynchronous clocks (emif / video / bridge) exercise every CDC on the
|
||||
// path: the two split live flags, and the bundled-data snapshot handshake.
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_gs_scanout_diag;
|
||||
// ---- three independent clock domains ----
|
||||
logic clk = 1'b0; always #10 clk = ~clk; // bridge (50 MHz, 20 ns)
|
||||
logic emif_clk = 1'b0; always #2.5 emif_clk = ~emif_clk; // EMIF (200 MHz)
|
||||
logic video_clk = 1'b0; always #4 video_clk = ~video_clk; // video (125 MHz)
|
||||
logic reset_n = 1'b0, emif_rst_n = 1'b0;
|
||||
|
||||
localparam int N_ROWS = 8;
|
||||
localparam int ROW_BEATS = 2;
|
||||
localparam int STRIDE = 64; // PSMCT32: 16 px/row, 2 beats
|
||||
|
||||
// ---- scanout_lb <-> EMIF responder ----
|
||||
logic so_enable, frame_start, in_window;
|
||||
logic [11:0] pixel_x, pixel_y;
|
||||
logic [7:0] so_r, so_g, so_b;
|
||||
logic so_line_valid, so_underflow;
|
||||
logic [31:0] so_rd_errs;
|
||||
logic so_diag_rderr_nz, so_diag_valid;
|
||||
logic [29:0] so_diag_first;
|
||||
logic [6:0] so_diag_stat;
|
||||
logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid;
|
||||
logic [7:0] arlen; logic [2:0] arsize; logic arvalid, arready;
|
||||
logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
|
||||
|
||||
gs_lpddr_scanout_lb #(.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
|
||||
.N_ROWS(N_ROWS), .PSMCT32(1'b1)) dut_scan (
|
||||
.axi_clk(emif_clk), .axi_rst_n(emif_rst_n), .enable(so_enable),
|
||||
.video_clk(video_clk), .frame_start(frame_start),
|
||||
.pixel_x(pixel_x), .pixel_y(pixel_y), .in_window(in_window),
|
||||
.r(so_r), .g(so_g), .b(so_b),
|
||||
.line_valid(so_line_valid), .underflow(so_underflow), .rd_errs(so_rd_errs),
|
||||
.diag_rderr_nz(so_diag_rderr_nz), .diag_valid(so_diag_valid),
|
||||
.diag_first(so_diag_first), .diag_stat(so_diag_stat),
|
||||
.araddr(araddr), .arburst(arburst), .arid(arid),
|
||||
.arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
|
||||
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready)
|
||||
);
|
||||
|
||||
// ---- behavioral EMIF read responder (emif_clk) ----
|
||||
localparam logic [1:0] M_NORMAL = 2'd0, M_STARVE = 2'd1, M_ERR = 2'd2;
|
||||
logic [1:0] resp_mode = M_NORMAL;
|
||||
logic pend; logic [1:0] pend_resp; logic [29:0] pend_addr;
|
||||
// grant AR when not starving and no response in flight
|
||||
assign arready = (resp_mode != M_STARVE) && arvalid && !pend && !rvalid;
|
||||
always_ff @(posedge emif_clk or negedge emif_rst_n) begin
|
||||
if (!emif_rst_n) begin
|
||||
pend <= 1'b0; rvalid <= 1'b0; rlast <= 1'b0; rresp <= 2'b00; rdata <= 256'd0;
|
||||
end else begin
|
||||
if (arready) begin
|
||||
pend <= 1'b1;
|
||||
pend_addr <= araddr;
|
||||
pend_resp <= (resp_mode == M_ERR) ? 2'b10 : 2'b00; // SLVERR vs OKAY
|
||||
end
|
||||
if (pend && !rvalid) begin
|
||||
rvalid <= 1'b1; rlast <= 1'b1; rresp <= pend_resp;
|
||||
rdata <= {8{2'b01, pend_addr}}; // deterministic nonzero payload
|
||||
pend <= 1'b0;
|
||||
end else if (rvalid && rready) begin
|
||||
rvalid <= 1'b0; rlast <= 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ---- bridge diag inputs, driven from scanout_lb (top wiring mirror) ----
|
||||
wire scan_diag_uf_i = so_underflow; // live sticky underflow alone
|
||||
wire scan_diag_rderr_nz_i = so_diag_rderr_nz; // live (rd_errs != 0)
|
||||
wire scan_diag_valid_i = so_diag_valid; // first-failure captured (held to !enable)
|
||||
wire [29:0] scan_diag_first_i = so_diag_first; // snapshot {nf_s0,nf_v,scan_y}
|
||||
wire [6:0] scan_diag_stat_i = so_diag_stat; // snapshot {vphase,line_valid,lookahead,base}
|
||||
|
||||
// ---- bridge AXI4-lite slave master signals ----
|
||||
logic [3:0] s_axi_awid=0; logic [37:0] s_axi_awaddr=0; logic [7:0] s_axi_awlen=0;
|
||||
logic [2:0] s_axi_awsize=0; logic [1:0] s_axi_awburst=0; logic s_axi_awlock=0;
|
||||
logic [3:0] s_axi_awcache=0; logic [2:0] s_axi_awprot=0; logic s_axi_awvalid=0, s_axi_awready;
|
||||
logic [127:0] s_axi_wdata=0; logic [15:0] s_axi_wstrb=0; logic s_axi_wlast=0, s_axi_wvalid=0, s_axi_wready;
|
||||
logic [3:0] s_axi_bid; logic [1:0] s_axi_bresp; logic s_axi_bvalid; logic s_axi_bready=1;
|
||||
logic [3:0] s_axi_arid=0; logic [37:0] s_axi_araddr=0; logic [7:0] s_axi_arlen=0;
|
||||
logic [2:0] s_axi_arsize=0; logic [1:0] s_axi_arburst=0; logic s_axi_arlock=0;
|
||||
logic [3:0] s_axi_arcache=0; logic [2:0] s_axi_arprot=0; logic s_axi_arvalid=0, s_axi_arready;
|
||||
logic [3:0] s_axi_rid; logic [127:0] s_axi_rdata; logic [1:0] s_axi_rresp; logic s_axi_rlast, s_axi_rvalid; logic s_axi_rready=0;
|
||||
|
||||
// Partial named-port bridge instance: only clk/reset/AXI-slave/scan_diag_* are
|
||||
// relevant to the 0x120/0x124 read path. All other ports feed unrelated registers.
|
||||
ps2_hps_bridge u_bridge (
|
||||
.clk(clk), .reset_n(reset_n),
|
||||
.s_axi_awid(s_axi_awid), .s_axi_awaddr(s_axi_awaddr), .s_axi_awlen(s_axi_awlen),
|
||||
.s_axi_awsize(s_axi_awsize), .s_axi_awburst(s_axi_awburst), .s_axi_awlock(s_axi_awlock),
|
||||
.s_axi_awcache(s_axi_awcache), .s_axi_awprot(s_axi_awprot), .s_axi_awvalid(s_axi_awvalid),
|
||||
.s_axi_awready(s_axi_awready),
|
||||
.s_axi_wdata(s_axi_wdata), .s_axi_wstrb(s_axi_wstrb), .s_axi_wlast(s_axi_wlast),
|
||||
.s_axi_wvalid(s_axi_wvalid), .s_axi_wready(s_axi_wready),
|
||||
.s_axi_bid(s_axi_bid), .s_axi_bresp(s_axi_bresp), .s_axi_bvalid(s_axi_bvalid), .s_axi_bready(s_axi_bready),
|
||||
.s_axi_arid(s_axi_arid), .s_axi_araddr(s_axi_araddr), .s_axi_arlen(s_axi_arlen),
|
||||
.s_axi_arsize(s_axi_arsize), .s_axi_arburst(s_axi_arburst), .s_axi_arlock(s_axi_arlock),
|
||||
.s_axi_arcache(s_axi_arcache), .s_axi_arprot(s_axi_arprot), .s_axi_arvalid(s_axi_arvalid),
|
||||
.s_axi_arready(s_axi_arready),
|
||||
.s_axi_rid(s_axi_rid), .s_axi_rdata(s_axi_rdata), .s_axi_rresp(s_axi_rresp),
|
||||
.s_axi_rlast(s_axi_rlast), .s_axi_rvalid(s_axi_rvalid), .s_axi_rready(s_axi_rready),
|
||||
.scan_diag_uf_i(scan_diag_uf_i), .scan_diag_rderr_nz_i(scan_diag_rderr_nz_i),
|
||||
.scan_diag_valid_i(scan_diag_valid_i), .scan_diag_first_i(scan_diag_first_i),
|
||||
.scan_diag_stat_i(scan_diag_stat_i)
|
||||
);
|
||||
|
||||
// ---- AXI read task (single-beat, lane by addr[3:2]) ----
|
||||
task automatic axi_read32(input logic [37:0] addr, output logic [31:0] data);
|
||||
@(posedge clk);
|
||||
s_axi_arid<=0; s_axi_araddr<=addr; s_axi_arlen<=0; s_axi_arsize<=3'd2;
|
||||
s_axi_arburst<=2'b01; s_axi_arvalid<=1'b1; s_axi_rready<=1'b1;
|
||||
wait (s_axi_arready); @(posedge clk); s_axi_arvalid<=1'b0;
|
||||
wait (s_axi_rvalid);
|
||||
case (addr[3:2])
|
||||
2'b00: data = s_axi_rdata[31:0];
|
||||
2'b01: data = s_axi_rdata[63:32];
|
||||
2'b10: data = s_axi_rdata[95:64];
|
||||
default: data = s_axi_rdata[127:96];
|
||||
endcase
|
||||
@(posedge clk); s_axi_rready<=1'b0;
|
||||
endtask
|
||||
|
||||
// sweep pixel_y across all rows (in the video domain) to advance disp_row so the
|
||||
// prefetcher loads rows; in_window stays as caller set it.
|
||||
task automatic sweep_rows(input int hold_cycles);
|
||||
for (int y = 0; y < N_ROWS; y++) begin
|
||||
@(posedge video_clk); pixel_y <= y[11:0]; pixel_x <= 12'd0;
|
||||
repeat (hold_cycles) @(posedge video_clk);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic pulse_frame_start();
|
||||
@(posedge video_clk); frame_start <= 1'b1;
|
||||
repeat (3) @(posedge video_clk); frame_start <= 1'b0;
|
||||
repeat (3) @(posedge video_clk);
|
||||
endtask
|
||||
|
||||
int errors = 0;
|
||||
task automatic chk(input string label, input logic cond);
|
||||
if (!cond) begin $error("[scanout_diag] FAIL: %s", label); errors++; end
|
||||
else $display("[scanout_diag] ok : %s", label);
|
||||
endtask
|
||||
|
||||
logic [31:0] st, fst;
|
||||
initial begin
|
||||
so_enable=0; frame_start=0; in_window=0; pixel_x=0; pixel_y=0; resp_mode=M_NORMAL;
|
||||
repeat (6) @(posedge clk); reset_n=1; emif_rst_n=1;
|
||||
repeat (6) @(posedge clk);
|
||||
|
||||
// -------- P1 FIRST: cold-start row-zero starvation -> first-failure snapshot --------
|
||||
// Run before any row ever loads so line_valid is genuinely 0 (line_valid is sticky,
|
||||
// reset only by axi_rst_n) — the authentic row-zero-miss the field defect resembles.
|
||||
resp_mode=M_STARVE; so_enable=1;
|
||||
pulse_frame_start();
|
||||
@(posedge video_clk) begin in_window<=1'b1; pixel_x<=12'd0; pixel_y<=12'd0; end
|
||||
repeat (60) @(posedge video_clk); // hold active row 0 while nothing loads
|
||||
repeat (60) @(posedge clk);
|
||||
axi_read32(38'h120, st);
|
||||
axi_read32(38'h124, fst);
|
||||
chk("P1 valid=1", st[0]===1'b1);
|
||||
chk("P1 underflow=1", st[1]===1'b1);
|
||||
chk("P1 rderr_nz=0", st[2]===1'b0);
|
||||
chk("P1 cause_base=1", st[3]===1'b1);
|
||||
chk("P1 cause_lookah=0", st[4]===1'b0);
|
||||
chk("P1 line_valid=0", st[5]===1'b0); // cold start: no row loaded yet
|
||||
chk("P1 snap scan_y=0", fst[9:0]===10'd0); // row-zero miss
|
||||
chk("P1 snap nf_v=0", fst[19:10]===10'd0);
|
||||
chk("P1 snap nf_s0=0", fst[29:20]===10'd0);
|
||||
|
||||
// -------- P2: clear via video-source disable --------------------------------------
|
||||
so_enable=0; in_window=0;
|
||||
repeat (40) @(posedge clk);
|
||||
axi_read32(38'h120, st);
|
||||
chk("P2 valid cleared", st[0]===1'b0);
|
||||
chk("P2 underflow cleared", st[1]===1'b0);
|
||||
|
||||
// -------- P3: healthy baseline (rows load, no active pixels) -> NO false positive --
|
||||
resp_mode=M_NORMAL; so_enable=1; in_window=0;
|
||||
pulse_frame_start(); sweep_rows(24);
|
||||
repeat (40) @(posedge clk);
|
||||
axi_read32(38'h120, st);
|
||||
chk("P3 valid=0", st[0]===1'b0);
|
||||
chk("P3 underflow=0", st[1]===1'b0);
|
||||
chk("P3 rderr_nz=0", st[2]===1'b0); // OKAY reads: still zero
|
||||
|
||||
// -------- P4: SLVERR reads, no active pixels -> read-error WITHOUT underflow -------
|
||||
resp_mode=M_ERR; in_window=0;
|
||||
pulse_frame_start(); sweep_rows(24); // reload every row; each read returns SLVERR
|
||||
repeat (60) @(posedge clk);
|
||||
axi_read32(38'h120, st);
|
||||
chk("P4 rderr_nz=1", st[2]===1'b1);
|
||||
chk("P4 valid=0", st[0]===1'b0); // in_window=0 -> underflow impossible
|
||||
chk("P4 underflow=0", st[1]===1'b0);
|
||||
|
||||
if (errors==0) $display("[tb_gs_scanout_diag] PASS");
|
||||
else $display("[tb_gs_scanout_diag] FAIL (%0d errors)", errors);
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin #500000; $error("[tb_gs_scanout_diag] TIMEOUT"); $finish; end
|
||||
endmodule : tb_gs_scanout_diag
|
||||
@@ -172,6 +172,10 @@ module tb_bridge_iop_pad_input;
|
||||
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; tied off (.* matches by name)
|
||||
logic [31:0] frag_drops_i = 32'd0; // Ch357 — persistent-Z drop count; tied off (.* matches by name)
|
||||
// Ch442 — scanout A+B diagnostic inputs; tied off (.* matches by name).
|
||||
logic scan_diag_uf_i = 1'b0, scan_diag_rderr_nz_i = 1'b0, scan_diag_valid_i = 1'b0;
|
||||
logic [29:0] scan_diag_first_i = 30'd0;
|
||||
logic [6:0] scan_diag_stat_i = 7'd0;
|
||||
ps2_hps_bridge u_bridge (
|
||||
.clk (bclk),
|
||||
.reset_n (breset_n),
|
||||
|
||||
@@ -252,6 +252,10 @@ module tb_ee_pad_buffer_branch;
|
||||
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; tied off (.* matches by name)
|
||||
logic [31:0] frag_drops_i = 32'd0; // Ch357 — persistent-Z drop count; tied off (.* matches by name)
|
||||
// Ch442 — scanout A+B diagnostic inputs; tied off (.* matches by name).
|
||||
logic scan_diag_uf_i = 1'b0, scan_diag_rderr_nz_i = 1'b0, scan_diag_valid_i = 1'b0;
|
||||
logic [29:0] scan_diag_first_i = 30'd0;
|
||||
logic [6:0] scan_diag_stat_i = 7'd0;
|
||||
ps2_hps_bridge u_bridge (
|
||||
.clk (bclk),
|
||||
.reset_n (breset_n),
|
||||
|
||||
@@ -203,6 +203,10 @@ module tb_pad_state_via_sif_to_ee;
|
||||
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; tied off (.* matches by name)
|
||||
logic [31:0] frag_drops_i = 32'd0; // Ch357 — persistent-Z drop count; tied off (.* matches by name)
|
||||
// Ch442 — scanout A+B diagnostic inputs; tied off (.* matches by name).
|
||||
logic scan_diag_uf_i = 1'b0, scan_diag_rderr_nz_i = 1'b0, scan_diag_valid_i = 1'b0;
|
||||
logic [29:0] scan_diag_first_i = 30'd0;
|
||||
logic [6:0] scan_diag_stat_i = 7'd0;
|
||||
ps2_hps_bridge u_bridge (
|
||||
.clk (bclk),
|
||||
.reset_n (breset_n),
|
||||
|
||||
@@ -215,6 +215,10 @@ module tb_ps2_hps_bridge;
|
||||
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)
|
||||
// Ch442 — scanout A+B diagnostic inputs; tied off (.* matches by name).
|
||||
logic scan_diag_uf_i = 1'b0, scan_diag_rderr_nz_i = 1'b0, scan_diag_valid_i = 1'b0;
|
||||
logic [29:0] scan_diag_first_i = 30'd0;
|
||||
logic [6:0] scan_diag_stat_i = 7'd0;
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user