Per-vertex GS fog end-to-end (gs_stub emit incl. persp_emit5, gs_prim_list_feeder XYZ2->XYZF2 on PRIM.FGE, gs_make_sh3_scheduler_fixture.py F/FGE packing), new fog TBs, fidelity attribution tooling. Functional baseline before removing the dead bilinear lerp8 clamps (Codex: 161-node comb loop -> -0.042ns setup fail). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.5 KiB
Ch438 — source-space 3x3 binomial scanout (pre-fit)
Why this chapter
Ch437 proved exact horizontal and vertical linear presentation, but the complete
f52 framebuffer still contains high-frequency speckle that the PCSX2 reference
does not. Adding more draws does not address that deficit. An offline filter
sweep identified a source-space separable [1 2 1]/4 low-pass followed by the
authentic nearest presentation map as the best small, bounded scanout change.
The renderer and LPDDR framebuffer are unchanged. Ch438 only changes pixels at the final HDMI scanout boundary, so the accepted f52 framebuffer SHA remains the rollback and equivalence anchor.
RTL architecture
gs_lpddr_scanout_lb.svadds opt-inBINOMIAL_3X3_FILTER.- SH3 full-frame scanout disables the Ch436/437 linear filters and enables the binomial filter.
- A third rotating line buffer retains source rows
y-1,y, andy+1. - Each physical line RAM still has one unconditional registered video read, the Quartus-safe inference form established in Ch437.
- Six 256-bit register-cache slots retain the even/odd beats for all three rows.
- Horizontal and vertical passes each compute
(a + 2*b + c + 2) >> 2. - Horizontal edges clamp at source columns 0 and 511. Vertical edges clamp at source rows 32 and 479.
- The filtered source pixel is presented with the captured nearest maps:
sx=floor(x*4/5)andsy=32+floor(y*14/15). - The AXI prefetcher still stays one source row ahead; its physical write target now rotates 0/1/2 rather than alternating 0/1.
Expected fit delta from the accepted Ch437 build is one 640-pixel physical RAM bank (approximately five M20Ks in the established 256-bit implementation), two additional 256-bit beat-cache registers, and small shift/add logic. Ch437 used 344/358 RAM blocks, so the expected result is about 349/358.
Exact preview result
Input framebuffer:
captures/gs/silenthill3/extracted/sh3_zsrt139f52_ch435_board.png- Its underlying f52 board dump is byte-identical to the accepted Ch436/437
framebuffer (
d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b).
Reference: captures/gs/silenthill3/pcsx2_ref_224139.png.
| Scanout | MAE | RMSE |
|---|---|---|
| Ch437 separable linear | 13.9525 | 20.6568 |
| Ch438 3x3 binomial + nearest | 13.1111 | 19.1051 |
Preview:
captures/gs/silenthill3/extracted/recon/sh3_zsrt139f52_ch438_binomial_preview.png
Preview SHA-256:
5a0402c523b64e252746067488177b08f0d23ab8a4a7dc09857e914c07f269f1
The preview is generated by tools/preview_scanout_ch438.py, which reproduces
the RTL's rounding after each separable pass.
Pre-fit verification
make -C sim tb_gs_lpddr_scanout_lb_binomial- 2,400/2,400 output pixels exact
- zero underflow
- zero read errors
make -C sim tb_gs_lpddr_scanout_lb_hstretch- Ch437 compatibility: 2,400/2,400 exact
- zero underflow/read errors
make -C sim tb_gs_lpddr_scanout_lb- legacy scanout PASS under its established one-pixel-per-line simulation alignment allowance
- zero underflow/read errors
- Targeted Verilator lint of the binomial parameterization: no errors, latches, multidriven signals, combinational loops, or range errors.
- Repository
git diff --check: clean.
The repository-wide make lint still stops at the pre-existing unsupported
default-valued rewind_i input in sif_dma_ee_ram_bridge_stub.sv; the targeted
scanout lint is clean and this unrelated baseline issue was not changed.
Owner fit gate
Ready for a fresh Quartus 26.1 GUI compile. Accept only if:
- fit succeeds;
- all setup/hold/recovery/removal/pulse-width checks are clean;
- RAM use remains at or below 358 blocks;
- all three scanline arrays infer as RAM rather than register banks.
First fit and timing-root correction
The first Ch438 GUI fit completed successfully and confirmed the scanout resource projection:
- 38,904 ALMs
- 57,173 registers
- 349/358 RAM blocks
- 160 DSP blocks
All non-setup checks were clean, but EMIF setup failed at -0.120 ns, TNS
-3.122, across 64 endpoints. The actual top-ten family was unrelated to the
new filter datapath:
- launch:
u_zc_emit|u_req|raddr_q[6] - capture: the deep request FIFO's packed
mem_rtl_0port-B address banks - launch-register fanout: 744
- data path: one 2.886 ns route, zero logic levels
This is the established 93-bit x 16K request-FIFO physical-address family. The additional scanout RAM changed placement enough to expose it again.
The correction keeps the full 16K request depth and banks only the payload into
two independently inferred RAM arrays. Each half has a separate preserved
registered read address, reducing the single 744-load launch net to two roughly
half-sized physical trees. FIFO order, pointer CDC, and registered-read latency
are unchanged. The banked form is enabled only on u_zc_emit.u_req; all other
gs_async_fifo users retain the monolithic default.
Post-cut verification:
- default async FIFO stress: 3,285 writes/reads, zero errors;
- monolithic registered-read stress: 3,285 writes/reads, zero errors;
- banked registered-read stress: 3,285 writes/reads, zero errors;
- full f52 production Verilator replay:
- 311 epochs;
- 2,339,914 fragments fed;
- 2,286,292 passed;
- zero drops, color overflow, BRESP errors, Z mismatches, or color mismatches;
- PASS;
- post-cut framebuffer SHA-256:
d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b, byte-identical to the accepted Ch437 board framebuffer.
The first timing-red RBF (7fa4724f...) was not deployed. A fresh GUI compile
is required for the banked-address correction.