diff --git a/rtl/gif_gs/gs_lpddr_scanout_lb.sv b/rtl/gif_gs/gs_lpddr_scanout_lb.sv index 35c48f6..eb97de2 100644 --- a/rtl/gif_gs/gs_lpddr_scanout_lb.sv +++ b/rtl/gif_gs/gs_lpddr_scanout_lb.sv @@ -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 diff --git a/rtl/platform/ps2_hps_bridge.sv b/rtl/platform/ps2_hps_bridge.sv index 7dee95d..ec8c73e 100644 --- a/rtl/platform/ps2_hps_bridge.sv +++ b/rtl/platform/ps2_hps_bridge.sv @@ -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}; diff --git a/rtl/top/de25_nano_psmct32_raster_demo_top.sv b/rtl/top/de25_nano_psmct32_raster_demo_top.sv index 5391443..d066db3 100644 --- a/rtl/top/de25_nano_psmct32_raster_demo_top.sv +++ b/rtl/top/de25_nano_psmct32_raster_demo_top.sv @@ -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), diff --git a/sim/Makefile b/sim/Makefile index ec78ff4..cc387ef 100644 --- a/sim/Makefile +++ b/sim/Makefile @@ -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) \ diff --git a/sim/tb/gif_gs/tb_gs_scanout_diag.sv b/sim/tb/gif_gs/tb_gs_scanout_diag.sv new file mode 100644 index 0000000..10f75e8 --- /dev/null +++ b/sim/tb/gif_gs/tb_gs_scanout_diag.sv @@ -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 diff --git a/sim/tb/integration/tb_bridge_iop_pad_input.sv b/sim/tb/integration/tb_bridge_iop_pad_input.sv index 0e7dcd3..48110bf 100644 --- a/sim/tb/integration/tb_bridge_iop_pad_input.sv +++ b/sim/tb/integration/tb_bridge_iop_pad_input.sv @@ -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), diff --git a/sim/tb/integration/tb_ee_pad_buffer_branch.sv b/sim/tb/integration/tb_ee_pad_buffer_branch.sv index 34fc46f..acef07a 100644 --- a/sim/tb/integration/tb_ee_pad_buffer_branch.sv +++ b/sim/tb/integration/tb_ee_pad_buffer_branch.sv @@ -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), diff --git a/sim/tb/integration/tb_pad_state_via_sif_to_ee.sv b/sim/tb/integration/tb_pad_state_via_sif_to_ee.sv index 36ce497..d1ad665 100644 --- a/sim/tb/integration/tb_pad_state_via_sif_to_ee.sv +++ b/sim/tb/integration/tb_pad_state_via_sif_to_ee.sv @@ -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), diff --git a/sim/tb/platform/tb_ps2_hps_bridge.sv b/sim/tb/platform/tb_ps2_hps_bridge.sv index 8c4f696..dc55946 100644 --- a/sim/tb/platform/tb_ps2_hps_bridge.sv +++ b/sim/tb/platform/tb_ps2_hps_bridge.sv @@ -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