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
@@ -0,0 +1,8 @@
sample 1: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
sample 2: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
sample 3: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
sample 4: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
sample 5: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
sample 6: 0x02C=0x000000F1 0x120=0x00000D30 0x124=0x02208821
sample 7: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
sample 8: 0x02C=0x000000F1 0x120=0x00000D33 0x124=0x02208821
@@ -0,0 +1,44 @@
# Ch443d board verdict — per-frame scanout diagnostic (zsrt139f52, 2026-07-23)
Image: `retroDE_ps2.ch443d.core.rbf` SHA `87c382f1...` (26.1 GUI compile of `0c9b2bf` =
`6319d7c` RTL + SEED 3). Non-canonical candidate; Ch441 restored after capture.
## Fit + render (accepted)
Seed-3 GUI compile: EMIF setup +0.132, all timing classes >=0, 0 violated, no
AWREADY/texcache-fill/drain/scanout-diag families, RAM 317/358. Board f52 replay
byte-identical to golden (FB `d0047677`, texture CRC `0x13cfe390`, 0 drops).
## Per-frame diagnostic (8 samples over many displayed frames, stable)
| reg | raw | decode |
|-----|-----|--------|
| 0x02C | `0xF1` | scan underflow (bit5)=1, bresp=0 |
| 0x120 | `0x0D33` | valid=1, **underflow=1**, **read-error=0**, cause_base=**0**, **cause_lookahead=1**, line_valid=1, **vphase=13** |
| 0x124 | `0x02208821` | **scan_y=33, nf_v=34, nf_s0=34** |
## VERDICT — the REAL displayed-frame failure (NOT the warm-up, NOT a read error)
The Ch443c per-frame clear + restart-latency fix worked as intended: the pre-display
warm-up capture (previously scan_y=32/nf=32/base) is GONE. The diagnostic now reports the
true steady-state failure, and it is a **vertical LINEAR-FILTER LOOKAHEAD starvation**:
- scan_y=33 is resident (33 < nf_v=34 -> base cause correctly 0).
- At vphase=13 the V_LINEAR_FILTER interpolates between source rows 33 and 34, so it needs
the LOOKAHEAD row scan_y+1 = 34. But nf_v=34 means next_fetch is AT row 34 -> row 34 is
being fetched, NOT yet resident -> `scan_y+1 >= nf_v` -> lookahead cause fires -> underflow.
- read-error=0: not an AXI error. Deterministic every frame: structural, not a CDC glitch.
Root cause: the prefetch throttle leads by exactly ONE row (`next_fetch <= disp_row+1`), but
the vertical linear filter needs the lookahead row (disp_row+1) FULLY resident. The prefetch
is one row short of the filter's requirement, so displaying row 33 while row 34 is still
loading races the filter's read of row 34.
## Open question + fix direction (for Codex)
- Leading by 2 (`disp_row+2`) would make the lookahead row resident in time, but V_LINEAR uses
only 2 line buffers (parity) -- disp_row and disp_row+2 share a buffer, so leading by 2
needs a 3rd line buffer (as BINOMIAL already has) or a different prefetch/latency structure.
- The FB (memory) is byte-perfect; whether this lookahead underflow is HDMI-VISIBLE (a subtle
artifact at inter-row filter boundaries) needs an actual HDMI capture -- the flag is
conservative (fires when the lookahead row's fetch is in-flight, which may still complete
before the specific pixels are read).
Evidence: `ch443d_perframe_diag_8samples.txt`, `sh3_zsrt139f52_ch443d_board_fb.mem`,
`ch443d_rbf.sha256`.
@@ -0,0 +1 @@
87c382f1c1664fd049f1546f6ec250cc6f7d23a3d3475aa2ebad9194086aa73e docs/hardware/ch443d_board_validation/retroDE_ps2.ch443d.core.rbf
File diff suppressed because it is too large Load Diff