Files
retroDE_ps2/docs/ch356_audit_log.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

7.2 KiB

Ch356 audit log — N-TEXTURE SCHEDULER (data-driven epoch descriptors)

Codex rung after Ch355. Generalizes Ch355's hard-coded two-group flow to a DATA-DRIVEN scheduler over ordered authentic draw epochs, each with a DIFFERENT TEX0/CLUT, accumulating into ONE LPDDR framebuffer via scene-level texture rebind + runtime staged-list retriggering. Default N_EPOCHS=3 (dump order): E0=idx11671 (tbp=10240) E1=idx19562 (tbp=11264) E2=idx89761 (tbp=9216). Runtime CLUT UPLOAD is OUT of scope (Codex): the scheduler operates over a PRELOADED CLUT table (the bootlet uploads all N relocated CLUTs); each epoch SELECTS its palette via its list's scene-level TEX0 CBP.

Preflight (gs_make_sh3_scheduler_fixture.py) — fail-CLOSED, PASS

  • =3 epochs; authentic ascending dump order; single frame f1.

  • =3 DISTINCT textures (tbp 10240/11264/9216); each PSMT8 512x512 perspective TME (fst=0) — supported format/state.

  • each texture + CLUT INDEPENDENTLY RESIDENT at its draw-time epoch (per-epoch local-memory reconstruction).
  • palettes pairwise-DISTINCT (real multi-texture, no aliasing).
  • CLUTs relocated to distinct, non-overlapping, in-BRAM CBPs: E0->480 E1->484 E2->488 (each 1 KiB / 4 blocks, VRAM 512 blocks).
  • per-triangle Q-canonicalization: all-neg -> negate S/T/Q (exact); mixed-sign/zero -> FAIL CLOSED. neg-Q per epoch: 68/68/0.
  • each list fits staging (619 words < 2048); deterministic union framebuffer geometry.

Emit — union geometry + N of everything + composed + per-epoch references

  • union origin (170,9), content 321x381 -> stored FB 384x381 (FBW=6), stride 1536 B, size 0x8ee00, scanout 18288 beats/frame.
  • N feeder lists (canonicalized): feeder_sh3_sched{k}.mem, each 68 tris / 619 words. Each list's TEX0 CBP = its relocated palette.
  • N textures de-swizzled (LINEAR) -> LPDDR single region 0x200000: sh3_sched{k}_tex_lpddr.mem. Per-epoch CRC (the cache-fill CRCs): E0=0x26a88b77 E1=0x95a83cbc E2=0xfbdeaa32 (E1/E2 == Ch355's texA/texB).
  • N relocated CLUTs; bootlet (bios/payload_sh3_sched) preloads ALL N via N 256x1 BITBLTs; DISPLAY1 = 384x381.
  • composed reference (paint-order DECAL) sh3_sched_refmap.mem: [31]cov [30]int [28]multi(>=2) [26:24]owner_epoch [17:9]tu [8:0]tv. 42414 covered px, 6124 multi-epoch (>=2 distinct) px. Per-epoch refmaps sh3_sched{k}_refmap.mem carry each epoch's OWN (tu,tv).
  • descriptor table sh3_sched_epochs.txt (META line + one row/epoch: k idx tbp cbp tex_file lpddr size crc list_file words records) — the host reads it.
  • params sh3_sched_params.vh (FBW/FBPXW/FBH, TEX_*, N_EPOCHS, EPk_CRC/EPk_NTRIS/EPk_CBP).
  • canonicalization self-test PASS: a positive-Q triangle and its all-negated twin pack BIT-IDENTICALLY.

Integration sim (tb_top_psmct32_sh3_sched) — PASS. All Codex acceptance gates:

  • preclear EXACTLY once (fb precleared in the initial block, never between epochs).
  • 3 FRESH cache fills, each fill_done low->high (rearm), beats=8192 bytes=262144 rd_errs=0, epoch CRC exact (0x26a88b77/0x95a83cbc/0xfbdeaa32).
  • 3 lists STREAMED through the feeder staging WRITE PORT (feeder_stg_we/waddr/wdata), NOT the $readmemh backdoor — closes the Ch355 sim gap where the write-port sequencing was untested. Each streams the full 2048 STG words (staging fully reset); word0 (ntris header) verified in the DUT staging = 68 per epoch.
  • 3 FRESH ORDERED drains: E0 low->high (first render, falls 0->0 rises 0->1); E1 high->low->high (falls 0->1 rises 1->2); E2 high->low->high (falls 1->2 rises 2->3). records_emitted=68 each; writer ovf=0 bresp=0.
  • scanout held OFF until the last fresh drain; scanout beats/frame = 18288 EXACT; underflow=0 rd_errs=0; 171720 px checked.
  • oracle (per-pixel, multi-texture correctness): each covered px must equal ONE covering epoch's texel (each epoch's own (tu,tv) from refmap_ep[k]) within <=1 texel; clut_bad = colour in NO epoch palette. Result: ALL=94.1% <=1texel, MULTI(>=2)=92.2%, clut_bad=0. The <=1texel % is RTL reciprocal-LUT fidelity (per-epoch isolated ONLY=k: 93.3/93.0/95.6% — matches Ch355), NOT accumulation. Gated at the documented 90% reciprocal floor.

Accumulation correctness — 100% BIT-FOR-BIT (the decisive proof; make tb_top_psmct32_sh3_sched_compose)

  • render each epoch ALONE (+ONLY=k +FBDUMP) over a precleared FB, then composite the isolation dumps in paint order (nonzero-wins DECAL) and compare to the joint ALL render.
  • compose_sched.py: 146304/146304 px, 0 mismatch — 100.0000% BIT-FOR-BIT. The scheduler drawing the epochs together produces exactly the same FB as compositing the individually-rendered epochs: no cache bleed, no stale pixels, correct rebind.
  • (An earlier 365-px discrepancy was a bug in MY compose heuristic — a reference-covered-but-RTL-unwritten epoch-2 pixel wrongly "owned" a value-0 slot, masking the lower epoch that actually drew. Pure nonzero-wins DECAL is the correct operator.)

Host (ps2_sh3_sched.c) — data-driven, builds -Wall clean, dry-run OK

  • reads sh3_sched_epochs.txt (META + rows); loads each epoch's texture+list; verifies local CRC per epoch.
  • flow: wait ready -> FB base 0 -> preclear ONCE (146304 words) -> for each epoch: upload tex -> LPDDR 0x200000, FRESH fill
    • verify CRC/beats/rd_errs -> STREAM list (0x0D8=0 reset, 0x0DC/0x0E4 commit) -> arm writer (k==0) -> GO + fresh ordered drain (k==0 low->high; k>0 high->low->high anti-stale) -> records==tris -> enable scanout after the LAST drain.
  • register map identical to ps2_sh3_multitex (frame_drained 0x02C[6], staging 0x0D8/0x0DC/0x0E4, GO 0x0E8).
  • BOARD RUN: sudo ./ps2_sh3_sched sh3_sched_epochs.txt (all fixtures in the cwd).

Board prep — fit-ready, owner-gated

  • de25 top: new GS_SH3_LPDDR_FB_SCHED branch -> SCANOUT_LB 1536/48/381 (384x381, 18288 beats/frame) + FEEDER_STG_WORDS=2048. Elaborates clean under GS_SH3_LPDDR_FB+_SCHED+GS_LPDDR4B_FB+GS_LPDDR_TEX+USE_QSYS_TOP (only the known Quartus-only EMIF_Qsys is unresolved in iverilog — same status as multitex; Quartus has the real IP).
  • profile sh3_lpddr_fb_sched (select_de25_profile.sh): fixtures bios/payload_sh3_sched; macros GS_SH3_LPDDR_FB + GS_SH3_LPDDR_FB_SCHED + GS_LPDDR_TEX. QSF verified (macros + fixtures applied; MULTITEX cleared). NO FEEDER_STG_INIT_FILE (runtime-staged, feeder starts empty).
  • fixtures (.mem/.vh), the epoch descriptor table (sh3_sched_epochs.txt), and rendered FB dumps (sim/build/sched_fb_.hex, dump-derived per-pixel data) are gitignored. Added *sh3*.txt to .gitignore for the descriptor table. Only OWN code (tb_top_psmct32_sh3_sched.sv, gs_make_sh3_scheduler_fixture.py, compose_sched.py, ps2_sh3_sched.c) is trackable.

Two claims

  1. Accumulation/scheduling PROVEN EXACT: joint N-epoch render == composited isolation dumps 100% bit-for-bit; 3 fresh cache rearms with per-epoch CRCs; 3 write-port-streamed lists (word0 verified); 3 fresh ordered anti-stale drains; exact 18288 scanout beats; clut_bad=0; 6124 multi-epoch overlap px exercised.
  2. Fidelity = 94.1% ALL / 92.2% MULTI <=1texel, i.e. the RTL 11-bit reciprocal floor (per-epoch isolated 93.0-95.6%, consistent with Ch355). Signed reciprocal for mixed-sign triangles remains a later platform extension.

Next rungs (Codex earlier): 640x480; persistent cross-draw Z (only if a census shows the chosen draws need it — no speculative Z).