Files
retroDE_ps2/docs/ch397_lpddr_alpha_plan.md
thejayman77 ba74bbd5aa Snapshot: fog implementation + fidelity tooling baseline (pre bilinear-clamp fix)
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>
2026-07-20 19:56:46 -04:00

133 lines
7.1 KiB
Markdown

# Ch397 — 224139 LPDDR alpha integration plan
## Established state
- `zsrt139a2` is the current live 640x480 opaque board composite from the
matching 224139 capture: six epochs, 98,163 written pixels, zero board drops.
- The first in-bounds blended draw family is PSMT8 perspective triangles
(`idx31075+`, `TBP=13824`, `CBP=14282`, 128x128, `PRIM.ABE=1`).
- Its recorded `ALPHA_1` state is `0x0000008000000062`; this is a generic
selector/FIX blend, not the existing source-over-only shortcut.
## Implemented cut (local RTL)
The current SH3 renderer sends final opaque pixels directly to
`gs_lpddr_zc_emit`. Its full-frame colour lives only in LPDDR, while the
existing combined triangle alpha path reads destination colour from BRAM.
Therefore a full-frame alpha implementation must extend the LPDDR emitter:
1. carry `ABE` plus ALPHA selector/FIX metadata with each fragment packet;
2. issue a single-pixel colour read after a Z-pass (or immediately for a
non-Z alpha fragment);
3. apply the existing `gs_alpha_blend` generic selector equation in the
emitter's AXI domain;
4. enqueue that blended colour through the existing colour writer, preserving
the ordered scene-marker drain contract.
Implemented in the local RTL:
- `gs_lpddr_zc_emit` now carries `{ABE,A,B,C,D,FIX}` through its request and
colour-align FIFOs;
- `gs_lpddr_color_blend` bypasses opaque pixels and, for ABE pixels, reads the
containing 256-bit LPDDR beat, selects the addressed 32-bit lane, invokes
generic `gs_alpha_blend`, and emits the ordered write payload;
- `gs_lpddr_rd_arb` has a fifth client for that destination read, below
scanout and Z/reload traffic;
- the real raster wrapper forwards the per-primitive alpha snapshot.
`make -C sim tb_gs_lpddr_color_blend` passes the actual `0x62` selector
pattern: source `0x80402010` over destination `0x40203020` produces
`0x80001010` (`Cd - Cs`, clamped), proving the LPDDR read lane and blend
datapath together.
The complete three-epoch integration replay is exact: 102,493 fragments,
102,479 Z passes, three ordered drains, zero drops/overflows/BRESP errors,
Z `0/307200` mismatches, and color `0/51525` mismatches. The final
640x480 simulation framebuffer is
`sh3_zsrt139a3_sim_fb.mem` (SHA-256
`a331fd9d68aaccb35310051ad9091f6c02acafcc4b86391cecedaea8f08ce28f`).
## Timing closure and deploy artifact (2026-07-15)
The first alpha fit exposed the destination-read/blend arithmetic as an
unregistered 310 MHz cone. The final implementation uses a registered
shift/add blend engine (no new DSPs), a registered generic async-FIFO write
port, and registered request-head control. The last 42 ps setup family was
the staged scene-marker bit feeding the color-align RAM write enable; fragment
valid and marker valid are now separate registered controls, removing that
decode from color writes.
The final Quartus 25.3.1 build is signoff-clean: setup `+0.012 ns`, hold
`0.000 ns`, zero setup TNS, and synthesis/fit/STA/assembler all completed with
zero errors. The split loader artifact is
`output_files/retroDE_ps2.core.rbf`, SHA-256
`f921f0bcde7fc9a8fe01bf39166327101e6dcbf0faf68398082207c8498ca349`.
`build_quartus.sh` now explicitly runs `post_flow.tcl` after its direct
`quartus_asm` step, because direct assembler invocation does not dispatch the
QSF post-flow hook by itself.
The RBF and all `zsrt139a3` host assets were copied to the DE25 and SHA-checked.
The first runtime load hit an SDM service-buffer timeout and left configfs in
an uninterruptible write. A controlled remote reboot restored network ping,
but SSH had not restarted at closeout time; a physical power cycle is the
remaining prerequisite before retrying the load and taking the board FB dump.
## First silicon run and request-FIFO correction (2026-07-16)
The authoritative Quartus 26.1 GUI RBF loaded successfully and all three
texture uploads, readback CRCs, runtime palettes, staging counts, Z preclear,
and zero-drop checks passed. The render itself did not drain: epoch 0 reached
36,958 framebuffer beats after 120 seconds and `frame_drained` remained low.
The resulting interrupted composite was 298,888/307,200 pixels equal to the
simulation framebuffer, with 8,312 mismatches caused by rebinding texture and
palette state while old fragments were still executing.
Root cause was the Ch397 timing cut in generic `gs_async_fifo`: the allocation
pointer was published to the read clock when the write was accepted, one
writer cycle before the staged RAM write committed. The production request
FIFO crosses a 40 MHz writer into a roughly 310 MHz reader, so the consumer
could observe the pointer about 19 ns before the data existed and replay stale
RAM entries. The earlier FIFO regression used a faster writer than reader and
therefore did not exercise this CDC ordering failure.
The FIFO now maintains separate allocation and commit pointers. Full
accounting uses allocation, while only the pointer advanced by an actual
staged RAM commit crosses into the reader. A production-ratio stress test
(40 MHz write, 312.5 MHz read) transfers 3,284/3,284 ordered words with zero
duplicates/drops. The full `zsrt139a3` integration replay remains exact:
102,493 fragments, all three drains, zero drops/errors, and Z/color scoreboards
at zero mismatches. This correction is ready for the owner-controlled
Quartus 26.1 GUI compile and subsequent silicon rerun.
## Corrected FIFO silicon acceptance (2026-07-16)
The owner-controlled Quartus 26.1 GUI compile completed successfully. The
final fit used 37,153/46,800 ALMs and 284/358 RAM blocks; setup slack was
`+0.064 ns`, hold slack was `0.000 ns`, and every reported TNS was zero. The
fresh split RBF is `output_files/retroDE_ps2.core.rbf`, 3,915,776 bytes,
SHA-256 `a1adf0a706465709e3078dedc053433e34807a40a57733c1cd2e14c9d64461fa`.
Its hash was checked again on the DE25 before `core_loader.sh` loaded it, and
`fpga0` reported `operating`.
The corrected request FIFO is now proven on silicon. Epochs 0, 1, and 2
completed with 54, 68, and 10 records respectively; all texture CRC, runtime
palette, staging, Z-preclear, and zero-drop gates passed. The 10-record alpha
epoch is short enough for `frame_drained` to clear and rise entirely between
HPS bridge polls, so the host now accepts that missed-low case only if both the
new per-GO record count and framebuffer-beat count prove forward progress. A
one-second low-observation window replaces the old 120-second delay; the
full board run now completes in about 23 seconds with `rc=0`.
The final board framebuffer and simulation framebuffer are byte-for-byte
identical across all 307,200 PSMCT32 words (zero mismatches), both with
SHA-256 `a331fd9d68aaccb35310051ad9091f6c02acafcc4b86391cecedaea8f08ce28f`.
## First acceptance scope
The generated `zsrt139a3` fixture carries two opaque setup epochs followed by
authentic blended draw `32391` (10 triangles, safely within the 2048-word
staging limit). The six-draw family remains the next scale-up target; its
first four draws exceed that current staging window and must be split across
ordered epochs before board replay. Do not claim frame fidelity until the
subset is locally replayed and board-read back against its own trace.