Ch443c: per-frame scanout diagnostic + frame-restart latency fix
The Ch443 board A+B diagnostic captured a WARM-UP miss, not a displayed-
frame miss: the line-buffer reader is enabled by video_src_emif
immediately, but the HDMI mux only switches to it at the next vsync, and
the diagnostic (cleared only on !enable) froze that pre-display capture.
scan_y=32/nf=32 was the expected pre-display warm-up, and line_valid is
sticky so it only meant SOME row had loaded, not that row 32 was valid.
Two fixes (all accepted Ch443 timing repairs kept: AW buffer, F_SETTLE +
drain multicycle, tile max-skew, monolithic tex_mem):
1. Per-frame diagnostic: clear diag_valid_q on fs_edge_v as well as
!enable (mirrors underflow_v). Discards the warm-up capture and
records the first miss, if any, AFTER the real frame boundary.
2. Frame-restart latency: in L_R, after the single-beat response is
accepted, if fs_pending || fs_edge_e, abandon the remainder of the
obsolete row -- no beat commit, no next old-row AR, no publish/
increment -- and return to L_IDLE, which restarts at V_SOURCE_START.
Protocol-safe (the accepted AXI transaction is complete); removes up
to a full row of restart latency during vertical blanking, so the
restarted prefetch leads the first displayed row.
New tb_gs_scanout_restart proves: mid-fetch frame-start accepts the
in-flight response, issues NO further old-row AR, restarts at row 32,
loads rows 32/33 before active consumption, and no post-restart
underflow. Regressions green: scanout_lb {,_binomial,_hstretch,
_psm32_256,_fb}, scanout_diag (per-frame), ps2_hps_bridge, and the
complete f52 replay BYTE-IDENTICAL (Z 0/307200, COLOR 0/245760).
Also commits the previously-untracked Ch443 board A+B evidence
(docs/hardware/ch443_board_validation/: verdict, 3-session raw, board
FB, RBF sha). No Quartus/board/push from here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -729,31 +729,39 @@ module gs_lpddr_scanout_lb #(
|
||||
end
|
||||
L_R: begin
|
||||
if (rvalid) begin
|
||||
// Capture directly into the selected RAM-local port
|
||||
// stage. L_C commits it on the following cycle.
|
||||
rready <= 1'b0;
|
||||
if (rresp != 2'b00) rd_errs <= rd_errs + 32'd1; // accepted read; a non-OKAY response is real
|
||||
lb0_we_q <= 1'b0;
|
||||
lb1_we_q <= 1'b0;
|
||||
lb2_we_q <= 1'b0;
|
||||
case (cur_buf)
|
||||
2'd1: begin lb1_wdata_q <= rdata; lb1_waddr_q <= beat[RB_BITS-1:0]; lb1_we_q <= 1'b1; end
|
||||
2'd2: begin lb2_wdata_q <= rdata; lb2_waddr_q <= beat[RB_BITS-1:0]; lb2_we_q <= 1'b1; end
|
||||
default: begin lb0_wdata_q <= rdata; lb0_waddr_q <= beat[RB_BITS-1:0]; lb0_we_q <= 1'b1; end
|
||||
endcase
|
||||
if (rresp != 2'b00) rd_errs <= rd_errs + 32'd1;
|
||||
rready <= 1'b0;
|
||||
if (beat == ROW_BEATS-1) begin
|
||||
// The local register captures this last response
|
||||
// now; L_C flushes it into RAM on the next edge.
|
||||
lst <= L_C;
|
||||
if (fs_pending || fs_edge_e) begin
|
||||
// Ch443c (Codex): a vsync restart is pending. This single-beat AXI
|
||||
// transaction is COMPLETE (rvalid accepted), so it is protocol-safe to
|
||||
// ABANDON the remainder of this now-obsolete row: do NOT commit the beat,
|
||||
// do NOT issue the next old-row AR, and do NOT publish/increment next_fetch.
|
||||
// Return to L_IDLE, where fs_pending restarts the prefetch at V_SOURCE_START.
|
||||
// This removes up to a full row of restart latency during vertical blanking
|
||||
// (the reason the warm-up first-fetch could lag the first displayed row).
|
||||
lst <= L_IDLE;
|
||||
end else begin
|
||||
// Previous behavior inserted L_C here and lost one
|
||||
// EMIF clock per beat. The RAM-local stage commits
|
||||
// independently above, so immediately issue the
|
||||
// next read just as the pre-Ch439 FSM did.
|
||||
beat <= beat + 1'b1;
|
||||
araddr <= araddr + 30'd32;
|
||||
arvalid <= 1'b1;
|
||||
lst <= L_AR;
|
||||
// Capture directly into the selected RAM-local port stage; L_C commits.
|
||||
case (cur_buf)
|
||||
2'd1: begin lb1_wdata_q <= rdata; lb1_waddr_q <= beat[RB_BITS-1:0]; lb1_we_q <= 1'b1; end
|
||||
2'd2: begin lb2_wdata_q <= rdata; lb2_waddr_q <= beat[RB_BITS-1:0]; lb2_we_q <= 1'b1; end
|
||||
default: begin lb0_wdata_q <= rdata; lb0_waddr_q <= beat[RB_BITS-1:0]; lb0_we_q <= 1'b1; end
|
||||
endcase
|
||||
if (beat == ROW_BEATS-1) begin
|
||||
// The local register captures this last response
|
||||
// now; L_C flushes it into RAM on the next edge.
|
||||
lst <= L_C;
|
||||
end else begin
|
||||
// RAM-local stage commits independently above, so
|
||||
// immediately issue the next single-beat read.
|
||||
beat <= beat + 1'b1;
|
||||
araddr <= araddr + 30'd32;
|
||||
arvalid <= 1'b1;
|
||||
lst <= L_AR;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -813,10 +821,13 @@ module gs_lpddr_scanout_lb #(
|
||||
|
||||
// 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,
|
||||
// First-failure snapshot (video_clk). Ch443c (Codex): PER-FRAME — diag_valid clears on
|
||||
// fs_edge_v as well as !enable, mirroring underflow_v, then records the first raw miss of
|
||||
// the NEW frame. This discards the pre-display WARM-UP capture: the line-buffer reader is
|
||||
// enabled by video_src_emif immediately, but the HDMI mux only switches to it at the next
|
||||
// vsync, so the very first post-enable miss is not an HDMI-visible one. Clearing per frame
|
||||
// makes the snapshot report the first miss AFTER the real frame boundary. The bundled
|
||||
// snapshot ({nf_s0,nf_v,scan_y} + cause/line_valid/vphase) stays stable within a frame,
|
||||
// so the bridge can transfer it coherently with a single synchronized valid.
|
||||
// (* preserve *): keep these capture regs as named keepers so the SDC bundled-data
|
||||
// hold-false/max-skew/net-delay constraint can bind to them (they must not be merged).
|
||||
@@ -825,8 +836,8 @@ module gs_lpddr_scanout_lb #(
|
||||
(* preserve *) 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;
|
||||
if (!enable || fs_edge_v) begin
|
||||
diag_valid_q <= 1'b0; // Ch443c: per-frame clear (discards pre-display warm-up capture)
|
||||
end else if (!diag_valid_q && raw_uf_cond) begin
|
||||
diag_valid_q <= 1'b1;
|
||||
diag_base_q <= uf_base_cond;
|
||||
|
||||
Reference in New Issue
Block a user