# 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.sv` adds opt-in `BINOMIAL_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`, and `y+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)` and `sy=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:

1. fit succeeds;
2. all setup/hold/recovery/removal/pulse-width checks are clean;
3. RAM use remains at or below 358 blocks;
4. 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_0` port-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.
