Ch443e: binomial 4th line buffer + prefetch lead-2 (fix lookahead underflow)

The Ch443d board per-frame diagnostic identified the real displayed-frame
failure as a BINOMIAL vertical-lookahead starvation: displaying source
row r while interpolating r-1/r/r+1, the prefetch led by only one row
(next_fetch <= disp_row+1), so the lookahead row r+1 was still in flight
when the 3x3 filter read it (board: scan_y=33, nf_v=34, cause_lookahead=1,
read-error=0, deterministic every frame).

Fix (BINOMIAL_3X3_FILTER only; legacy 2/3-buffer, lead-1 paths unchanged):
- Add a 4th rotating line buffer (lb3) with its own RAM-local write/read/
  cache registers. The 3x3 filter needs r-1/r/r+1 resident (3 buffers), so
  leading by 2 (fetch r+2 while displaying r) without overwriting r-1
  requires a 4th buffer.
- Prefetch lead-2 for binomial: disp_row_limit_e = disp_row+2. The in-flight
  r+2 lands in the 4th buffer (b+2 mod 4), always distinct from prev/cur/next
  (b-1/b/b+1 mod 4), so it never clobbers a row being read.
- Modulo-4 rotation everywhere: V_SOURCE_BUF%4, stretch_buf_q, next_fetch_buf,
  reset alignment at V_SOURCE_START, and the read-cache prev/cur/next case
  extended to 4 branches with (b-1)/b/(b+1) mod 4 selection + first/last-row
  clamps preserved.

New tb_gs_scanout_binomial_lookahead reproduces the board condition under
realistic EMIF latency (LAT=7) + backpressure and proves: NO binomial
lookahead underflow, correct 3x3 output across modulo-4 wrap + clamps (full
oracle, 1280 px), and coverage that mid-frame rows past V_SOURCE_START+1
with vphase!=0 were exercised under prefetch pressure.

All green: binomial (4-buffer, identical output), lookahead (new),
scanout_lb {,_hstretch,_psm32_256,_fb}, scanout_restart, scanout_diag,
ps2_hps_bridge, and the complete f52 replay BYTE-IDENTICAL (Z 0/307200,
COLOR 0/245760). Also commits the Ch443d board evidence that identified
this defect. No Quartus/board/push from here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 15:24:37 -04:00
parent 0c9b2bf5cd
commit 2e2c1e9ca6
7 changed files with 307470 additions and 20 deletions
+9
View File
@@ -1281,6 +1281,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_binomial_lookahead: dirs
@echo "=== build tb_gs_scanout_binomial_lookahead ==="
$(IVERILOG) $(IVERILOG_FLGS) \
-o $(BUILD_DIR)/tb_gs_scanout_binomial_lookahead.vvp \
-s tb_gs_scanout_binomial_lookahead \
$(RTL_SRCS) $(TB_ROOT)/gif_gs/tb_gs_scanout_binomial_lookahead.sv
@echo "=== run tb_gs_scanout_binomial_lookahead ==="
@cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_scanout_binomial_lookahead.vvp
tb_gs_scanout_diag: dirs
@echo "=== build tb_gs_scanout_diag ==="
$(IVERILOG) $(IVERILOG_FLGS) \
@@ -0,0 +1,144 @@
// ============================================================================
// tb_gs_scanout_binomial_lookahead — Ch443e
//
// Reproduces the board's binomial vertical-lookahead underflow (per-frame diag:
// scan_y=33, next_fetch=34, cause_lookahead=1) under REALISTIC EMIF read latency
// and backpressure, and proves the Ch443e fix (4th rotating buffer + prefetch
// lead-2, modulo-4) eliminates it:
// - NO binomial lookahead underflow across the whole displayed frame;
// - correct previous/current/next 3x3 output across modulo-4 wrap (full oracle);
// - first/last-row clamping + 15:14 stretch-phase transitions correct;
// - coverage: mid-frame source rows past V_SOURCE_START+1 with vphase!=0 are
// actually displayed while the prefetch is under latency pressure.
// Production-like: V_SOURCE_START=32, V_STRETCH_15_TO_14, BINOMIAL, PSMCT32.
`timescale 1ns/1ps
module tb_gs_scanout_binomial_lookahead;
localparam int SRC_W=64, OUT_W=80, OUT_H=16;
localparam int STRIDE=320, ROW_BEATS=10, N_ROWS=48, VSTART=32;
logic axi_clk=0, video_clk=0, rst_n=0, enable=0;
always #2 axi_clk=~axi_clk; // fast EMIF
always #10 video_clk=~video_clk; // video
logic frame_start=0, in_window=0;
logic [11:0] pixel_x=0, pixel_y=0;
wire [7:0] r,g,b;
wire line_valid, underflow;
wire [31:0] rd_errs;
wire [29:0] araddr; wire [1:0] arburst; wire [6:0] arid;
wire [7:0] arlen; wire [2:0] arsize; wire arvalid, rready;
logic arready=0, rvalid=0, rlast=0;
logic [255:0] rdata=0; logic [1:0] rresp=0;
logic [255:0] mem [0:N_ROWS*ROW_BEATS-1];
initial begin
for (int beat=0; beat<N_ROWS*ROW_BEATS; beat++) mem[beat]='0;
for (int y=0; y<N_ROWS; y++)
for (int x=0; x<SRC_W; x++)
mem[y*ROW_BEATS + (x>>3)][(x&7)*32 +: 32] = {8'hff, 8'(8'h80+x+y), 8'(y), 8'(x)};
end
gs_lpddr_scanout_lb #(
.FB_BASE(30'd0), .STRIDE_BYTES(STRIDE), .ROW_BEATS(ROW_BEATS),
.N_ROWS(N_ROWS), .PSMCT32(1'b1), .H_STRETCH_5_TO_4(1'b1),
.V_SOURCE_START(VSTART), .V_STRETCH_15_TO_14(1'b1),
.V_LINEAR_FILTER(1'b0), .H_LINEAR_FILTER(1'b0),
.H_SOURCE_PIXELS(SRC_W), .BINOMIAL_3X3_FILTER(1'b1)
) dut (
.axi_clk(axi_clk), .axi_rst_n(rst_n), .enable(enable),
.video_clk(video_clk), .frame_start(frame_start),
.pixel_x(pixel_x), .pixel_y(pixel_y), .in_window(in_window),
.r(r), .g(g), .b(b), .line_valid(line_valid), .underflow(underflow),
.rd_errs(rd_errs), .araddr(araddr), .arburst(arburst), .arid(arid),
.arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
.rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready)
);
// ---- REALISTIC EMIF responder: AR accept, then R after LAT cycles, with
// periodic extra backpressure. Stresses the prefetch so a lead-1 design
// would starve the binomial lookahead (as the board did). ----
localparam int LAT=7;
typedef enum logic [1:0] {S_AR, S_WAIT, S_R} state_t;
state_t state=S_AR;
localparam int MEM_BEAT_BITS=$clog2(N_ROWS*ROW_BEATS);
logic [MEM_BEAT_BITS-1:0] beat_q;
int wait_c=0; logic [3:0] bp=0;
always_ff @(posedge axi_clk) begin
arready <= 1'b0;
bp <= bp + 1'b1;
if (!rst_n) begin state<=S_AR; rvalid<=1'b0; rlast<=1'b0; wait_c<=0; end
else case (state)
S_AR: if (arvalid && !arready) begin
beat_q<=araddr[MEM_BEAT_BITS+4:5]; arready<=1'b1; wait_c<=LAT + (bp[1:0]); state<=S_WAIT;
end
S_WAIT: begin
if (wait_c>0) wait_c<=wait_c-1;
else begin rdata<=mem[beat_q]; rresp<=2'b00; rlast<=1'b1; rvalid<=1'b1; state<=S_R; end
end
S_R: if (rready && rvalid) begin rvalid<=1'b0; rlast<=1'b0; state<=S_AR; end
endcase
end
// ---- golden binomial oracle (division only in TB) ----
function automatic int source_x(input int ox); source_x=(ox*4)/5; endfunction
function automatic int source_y(input int oy); source_y=VSTART+(oy*14)/15; endfunction
function automatic int clamp_x(input int x); clamp_x=(x<0)?0:((x>=SRC_W)?SRC_W-1:x); endfunction
function automatic int clamp_y(input int y); clamp_y=(y<VSTART)?VSTART:((y>=N_ROWS)?N_ROWS-1:y); endfunction
function automatic int sample(input int ch, input int x, input int y);
int xx,yy; begin xx=clamp_x(x); yy=clamp_y(y);
case (ch) 0:sample=xx; 1:sample=yy; default:sample=8'h80+xx+yy; endcase end
endfunction
function automatic int hbin(input int ch, input int x, input int y);
hbin=(sample(ch,x-1,y)+2*sample(ch,x,y)+sample(ch,x+1,y)+2)/4; endfunction
function automatic int binomial(input int ch, input int x, input int y);
binomial=(hbin(ch,x,y-1)+2*hbin(ch,x,y)+hbin(ch,x,y+1)+2)/4; endfunction
int errors=0, checked=0;
logic check_en=0, uflow_sticky=0, cov_midframe_vphase=0;
logic [11:0] x_d, y_d; logic in_d;
always_ff @(posedge video_clk) begin
x_d<=pixel_x; y_d<=pixel_y; in_d<=in_window;
if (rst_n && enable && underflow) uflow_sticky<=1'b1; // ANY underflow at ANY time fails
// coverage: a mid-frame source row (> VSTART+1) is on display with vphase!=0
if (rst_n && enable && in_window && dut.scan_y > ($clog2(N_ROWS)+1)'(VSTART+1)
&& dut.stretch_vphase_q != 4'd0)
cov_midframe_vphase<=1'b1;
if (check_en && in_d) begin
int sx,sy,er,eg,eb;
sx=source_x(x_d); sy=source_y(y_d);
er=binomial(0,sx,sy); eg=binomial(1,sx,sy); eb=binomial(2,sx,sy);
checked++;
if (r!==8'(er)||g!==8'(eg)||b!==8'(eb)) begin
if (errors<32) $display("[binlook] out=(%0d,%0d) src=(%0d,%0d) got=%02x/%02x/%02x exp=%02x/%02x/%02x",
x_d,y_d,sx,sy,r,g,b,8'(er),8'(eg),8'(eb));
errors++;
end
end
end
initial begin
repeat(8) @(posedge axi_clk); rst_n=1; enable=1;
frame_start=1; repeat(3) @(posedge video_clk); frame_start=0;
repeat(400) @(posedge axi_clk); // let the lead-2 prefetch prime rows 32,33,34,35
repeat(3) @(posedge video_clk);
check_en=1;
for (int y=0; y<OUT_H; y++) begin
for (int x=0; x<OUT_W; x++) begin
@(negedge video_clk); pixel_x=12'(x); pixel_y=12'(y); in_window=1;
@(posedge video_clk);
end
@(negedge video_clk); in_window=0;
repeat(25) @(posedge video_clk); // horizontal blank between output lines
end
check_en=0;
$display("[binlook] checked=%0d errors=%0d underflow(sticky)=%0b rd_errs=%0d cov_midframe_vphase=%0b",
checked,errors,uflow_sticky,rd_errs,cov_midframe_vphase);
if (checked==OUT_W*OUT_H && errors==0 && !uflow_sticky && rd_errs==0 && cov_midframe_vphase)
$display("[tb_gs_scanout_binomial_lookahead] PASS");
else
$display("[tb_gs_scanout_binomial_lookahead] FAIL");
$finish;
end
initial begin #4_000_000; $display("[tb_gs_scanout_binomial_lookahead] TIMEOUT"); $finish; end
endmodule