24 Commits

Author SHA1 Message Date
thejayman77 4e8c3a16b8 Ch444: serialize gradient numerator bank — reclaim ~half the FPGA's DSP
The per-triangle gradient engine already time-shared ONE divider across all
GRAD_STEPS attributes, but computed every grad_load_num[0:GRAD_STEPS-1] numerator
IN PARALLEL — ~44 wide multiplies (~100 physical DSP) for once-per-triangle setup
consumed one-at-a-time. Pure redundant hardware; the design was DSP-maxed
(187/188, 99%) so nothing new could fit (fog needed 191/188).

Replace the parallel bank + the grad_num_q[] pre-latch array with grad_num_step:
computes ONLY the current grad_step's numerator from ONE mux-selected pair of
signed multipliers (attribute triple by grad_step>>1, axis by grad_step[0]; shared
da1/da2, two shared products, signed subtract, <<<20). grad_word_q/grad_slot are
held stable the whole solve, so it is bit-identical to the old grad_num_q[grad_step].
Removed grad_num_dadx/dady (inlined once). FSM sequencing and throughput unchanged.

Width note: da1/da2 are 33-bit (products 50-bit), NOT operand-width 32-bit — the
original (a1-a0) lived in a signed-64-bit expression context and never wrapped;
full-32-bit Z with |a1-a0|>2^31 needs the wider intermediate. tb_gs_grad_num_equiv
(extreme signed corners + 200k random = 494770 checks, 0 errors) caught a 32-bit
first cut that f52's real data never exercised.

Resource (26.1 Seed-3 fit): DSP needed 168->83 / final placement 187->119, i.e.
99% -> 44%, ~85 blocks reclaimed (Codex gate >=70 met). ALM 40458->38784 (86->83%).
RAM 322/358 unchanged. Timing CLEAN: setup +0.077, all classes >=0, 0 violated.

Verification: tb_gs_grad_num_equiv 0/494770; f52 replay BYTE-IDENTICAL golden
d0047677 (drops=0, occupancy unchanged); gradient/perspective/texture regressions
(tri_interp, grad_divider, persp_uv, zbuffer, fog_persp, textured_triangle,
triangle/perspective/combined/gouraud demos) all PASS. Byte-identical => the
screen is unchanged; this is the resource unlock for fog + coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 05:28:12 -04:00
thejayman77 7d68577c75 Ch443f/Ch443e board validation evidence (compact)
Silicon closeout evidence for the Ch443f scanout CDC hardening (commits 064484c +
4110846). Text/hashes/verdict/samples only — RBFs and framebuffer .mem dumps left
uncommitted (preserved local / on board).

Ch443f board verdict: RBF 59c6372e, fit setup +0.068 / all classes clean, fpga0
operating, CORE_ID 0x50533200 ABI 0x100. 311-epoch f52 replay DONE rc=0 zero
drops, FB byte-identical golden d0047677. Scanout diagnostic 120 samples (60 @
50ms + 60 @ non-harmonic 7ms) all identical: 0x02C=0xD1 (scan-error clear),
0x120=0x18 (qualified valid=0 / underflow=0 / read-error=0 / live pmax=0). The
two phase-sweep logs are byte-identical -> no phase-dependent CDC artifact. The
former residual "underflow" was purely the raw-readiness CDC/sync transient; the
Ch443e 4-buffer/lead-2 already fixed the real lookahead starvation. No fifth
buffer justified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 20:11:11 -04:00
thejayman77 4110846c01 Ch443f timing: preload Z-RMW eviction awaddr at fill (kill cache_dirty->awaddr cone)
The Seed-3 fit of the Ch443f scanout hardening exposed one setup family in the
EMIF (iopll_0_outclk0, 3.225 ns) domain: u_zc_emit|u_z|cache_dirty -> awaddr[*],
WNS -0.062 / TNS -0.203, skew-dominated (-0.064 clock skew; the 5-level logic
path itself would land ~+0.002 at zero skew). Not the Ch443f logic — the added
EMIF-domain logic nudged this marginal Ch357-era control path negative.

Fix (Codex-directed, minimal): the normal-eviction AW address is a pure function
of the cache line's beat, known when the line is installed. Preload it in S_FILL_C
alongside cache_beat<=pf_beat (awaddr <= ZBASE + (pf_beat<<5)), and drop the two
later awaddr assignments gated by cache_dirty (the scene_flush branch and the
dirty cache-miss branch) — those now only assert awvalid on the already-prepared
address. Clear-path awaddr assignments unchanged. No new FSM state, pipeline
stage, buffer, or protocol change.

Safe: a freshly filled line cannot be dirty before S_FILL_C installs it; the
address is stable across hits and scene flushes; after a dirty eviction the next
fill re-runs S_FILL_C; clear invalidates the cache so the first subsequent fill
overwrites the clear address before any normal eviction. This removes the control
cone rather than placing around it, so closure is seed-robust.

Regressions green: z_rmw/zc_emit/axi_master_elastic/zbuffer/z_flush_writer/
tile_zflush, full Ch443f scanout set, sh3_zint (Z 0/53760, COLOR 0/11092),
sh3_zrop (errors=0), and f52 byte-identity (Z 0/307200, COLOR 0/245760, drops=0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 10:19:23 -04:00
thejayman77 064484c50d Ch443f: coherent Gray readiness CDC + qualified scanout underflow detector
Harden the LPDDR scanout underflow diagnostic (Codex direction). Keeps the
Ch443e 4-buffer + lead-2 fix; adds no 5th buffer.

RTL (gs_lpddr_scanout_lb):
- Replace the raw-binary next_fetch readiness sync with a reset-aware GRAY
  code. next_fetch is monotonic between frames, so one Gray bit changes per
  increment; the 2-FF-synced + decoded nf_v is always a real prior frontier
  (monotone, burst-safe), never a torn multi-bit combination. (A plain
  toggle-per-change handshake dropped bursts when two increments landed in one
  sync window; that is why the earlier attempt under-read nf_v and false-tripped.)
  fs_edge_v overrides the lone multi-bit reset transient.
- Qualify the underflow: only a miss persisting >= QUAL_CYCLES (4) sets sticky
  underflow. uf_pmax_q records the longest streak and uf_qual_q whether any
  qualified, so a host can distinguish a 1-cycle CDC lag from a real late row.
- Atomic snapshot: scan_y/nf_v/pmax/causes/vphase/line_valid all latched the
  same video cycle on the first qualified miss.

ABI: 0x120 adds [15:12]=live pmax (2-FF synced via scan_diag_pmax_i). 0x124
[29:20] now carries pmax-at-capture (was nf_s0). Bridge dst reg kept 10-bit.

SDC: scanout diag bundle source count 37 -> 31 (nf_s0[10] -> pmax[4]); new
async-in cut + max_skew/net_delay for the next_fetch Gray CDC; stage-0 cut for
the live pmax sync.

Tests: new tb_gs_scanout_cdc_qual (async-clock focused: no false event from the
readiness transition or ordinary sync latency; sub-QUAL transient does not
qualify; genuine late row qualifies with a self-consistent atomic snapshot;
frame reset + mod-4 reuse re-arm the detector). tb_gs_scanout_diag updated for
the pmax field. Tie off scan_diag_pmax_i (+ pre-existing clut_* gap from the
fog baseline) in the four .* bridge/integration TBs.

Regressions green: scanout (cdc_qual, binomial_lookahead, diag, restart,
lpddr_scanout_lb x3), regbuf (r/aw/w), bridge + 3 pad integration TBs, and the
f52 top-level golden FB (Z 0/307200, COLOR 0/245760 mismatch, drops=0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 23:17:41 -04:00
thejayman77 2e2c1e9ca6 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>
2026-07-23 15:24:37 -04:00
thejayman77 0c9b2bf5cd QSF: set fitter placement SEED 3 (bounded sweep winner)
RTL frozen at 6319d7c. The AW/W/R buffers + F_SETTLE drain multicycle
removed every structural EMIF-handshake -> FSM setup family; the residual
was a placement/route-marginal EMIF path (zc_emit QUAD_WIDTH4 Z-request
FIFO read, ~-0.087 ns: 3.132 ns data / -0.100 ns skew vs 3.225 ns period).

A bounded, Codex-authorized 4-seed fit+STA sweep (AGGRESSIVE AREA kept,
no asm) resolved it by placement:
  seed 2  EMIF -0.087  (10 violated)
  seed 3  EMIF +0.132  (0 violated, ALL classes >=0)   <- winner
  seed 4  EMIF +0.062  (0 violated, under +0.100 margin)
  seed 5  EMIF -0.130  (3 violated)

SEED 3 is the best complete result: EMIF setup +0.132 (>= +0.100),
design +5.561, hold/recovery/removal/MPW all nonnegative, no AWREADY /
texture-fill R-return / texcache drain / scanout-diagnostic violations,
RAM 317/358, ALM 85%. This is a fitter placement seed only -- no RTL or
optimization-mode change. Per-seed reports preserved under
synth/.../seed_sweep/ (not committed; build artifacts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 12:31:31 -04:00
thejayman77 6319d7ca85 Ch443d: registered AXI R buffer (texcache fill) + drop fill_data_q reset
Closes the last currently-visible EMIF-handshake -> FSM setup family the
Ch443c fit exposed (-0.043/-0.022/-0.005 ns), the read-response analogue
of the AW/W buffers.

- gs_axi_r_regbuf: one-entry FULLY-registered AXI R buffer (the R twin of
  gs_axi_w_regbuf). Buffers the complete {rdata,rresp,rlast}; u_rready =
  !full only (NO combinational dependence on the texture FSM's d_rready);
  captures on u_rvalid && u_rready; d_rvalid = full with the payload held
  stable until d_rvalid && d_rready; resets only . Inserted between
  read-arbiter s2 and gs_texture_cache (u_texf_rbuf). The arbiter is
  unchanged -- it completes its R transaction into the buffer, which then
  owns delivery to the fill FSM. Cuts EMIF rvalid/rdata -> fst.F_R.
- gs_texture_cache: drop the unobservable fill_data_q reset. F_DRAIN (its
  only reader) is reachable only after F_R loads it, so the reset value is
  never observed; removing it kills the separate lock_sync|dreg[1] ->
  fill_data_q[80] setup path (-0.005 ns).
- New tb_gs_axi_r_regbuf: exactly-once/in-order, randomized responses +
  stalls, full backpressure, the full && d_rready no-fall-through case,
  {rdata,rresp,rlast} stability, reset-while-empty AND reset-while-full.

All green: r-buffer TB, texture_cache, texture_psmt8_clut, scanout_lb,
scanout_restart, scanout_diag, ps2_hps_bridge, rd_arb, and the complete
f52 replay BYTE-IDENTICAL (Z 0/307200, COLOR 0/245760). No Quartus/board/
push from here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 10:03:01 -04:00
thejayman77 846eee06b6 Ch443c: per-frame scanout diagnostic + frame-restart latency fix
The Ch443 board A+B diagnostic captured a WARM-UP miss, not a displayed-
frame miss: the line-buffer reader is enabled by video_src_emif
immediately, but the HDMI mux only switches to it at the next vsync, and
the diagnostic (cleared only on !enable) froze that pre-display capture.
scan_y=32/nf=32 was the expected pre-display warm-up, and line_valid is
sticky so it only meant SOME row had loaded, not that row 32 was valid.

Two fixes (all accepted Ch443 timing repairs kept: AW buffer, F_SETTLE +
drain multicycle, tile max-skew, monolithic tex_mem):

1. Per-frame diagnostic: clear diag_valid_q on fs_edge_v as well as
   !enable (mirrors underflow_v). Discards the warm-up capture and
   records the first miss, if any, AFTER the real frame boundary.

2. Frame-restart latency: in L_R, after the single-beat response is
   accepted, if fs_pending || fs_edge_e, abandon the remainder of the
   obsolete row -- no beat commit, no next old-row AR, no publish/
   increment -- and return to L_IDLE, which restarts at V_SOURCE_START.
   Protocol-safe (the accepted AXI transaction is complete); removes up
   to a full row of restart latency during vertical blanking, so the
   restarted prefetch leads the first displayed row.

New tb_gs_scanout_restart proves: mid-fetch frame-start accepts the
in-flight response, issues NO further old-row AR, restarts at row 32,
loads rows 32/33 before active consumption, and no post-restart
underflow. Regressions green: scanout_lb {,_binomial,_hstretch,
_psm32_256,_fb}, scanout_diag (per-frame), ps2_hps_bridge, and the
complete f52 replay BYTE-IDENTICAL (Z 0/307200, COLOR 0/245760).

Also commits the previously-untracked Ch443 board A+B evidence
(docs/hardware/ch443_board_validation/: verdict, 3-session raw, board
FB, RBF sha). No Quartus/board/push from here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 18:10:55 -04:00
thejayman77 4358bc328b Ch443b: revert texture 4-bank split; 2-cycle drain (F_SETTLE + multicycle)
The four-bank tex_mem split (27dfd0b) closed the -0.370 EMIF drain
write-address fanout but the owner GUI fit showed the fitter SCATTERED
the banks, pushing the DESIGN-clock sampler read cone
(ras_v0_x -> perspective-UV -> texel addr -> tex_mem portbaddr, the
design's fundamental ~40ns critical path) to -2.208 ns. Net worse.

Codex's call (Option 1 + honest write-side multicycle), implemented:
- Restore the MONOLITHIC 65536x32 tex_mem, recovering the clean 25 MHz
  read-cone placement. Sampler/read-address path stays fully timed
  (Ch439g); nothing about it is relaxed.
- Make the EMIF drain write genuinely two-cycle: new F_SETTLE state
  between F_DRAIN and F_WRITE. drain_idx_q/drain_word_q are loaded in
  F_DRAIN, HELD unchanged through F_SETTLE (the load block gates on
  F_DRAIN), and the RAM write + CRC happen at the later F_WRITE edge.
- SDC: fail-closed 2-cycle-setup / 1-cycle-hold multicycle from ONLY
  u_texcache|drain_idx_q[*] to tex_mem (a 6.45 ns EMIF window for the
  drain write-address). Scoped -from the drain regs, so the sampler
  read path (different launch regs) is untouched. HALTs if tex_mem is
  present but drain_idx_q renamed.

The AW buffer (gs_axi_aw_regbuf) and the tile-CDC max-skew 2.5 relax
from 27dfd0b are KEPT unchanged (both closed their families in the fit).

Verified: tb_gs_texture_cache (monolithic + F_SETTLE, distinct-per-byte-
lane + full-word, 0 errors), aw/w regbuf, texture_psmt8_clut,
scanout_diag, ps2_hps_bridge, and the complete f52 replay BYTE-IDENTICAL
(Z 0/307200, COLOR 0/245760). No Quartus/board/push from here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 15:11:46 -04:00
thejayman77 27dfd0b0cf Ch443: combined timing repair for both Ch442 setup families
Ch442's fit exposed two failing setup families (plus a stale max-skew).
Attack both structurally; no reseeding.

Family 1 - AWREADY -> Z-FSM (-0.410): gen_p2c_ff[23] (EMIF AWREADY)
reached u_zc_emit|u_z's S_SFLUSH_AW/S_FILL_R next-state combinationally.
Ch441 registered only the W channel; add the AW twin:
- new gs_axi_aw_regbuf (one-entry fully-registered AW buffer), inserted
  in zc_emit between u_z's AW output and the arbiter s2 AW port. The FSM
  now sees registered occupancy, never EMIF's combinational AWREADY.

Family 2 - texcache drain_idx_q -> tex_mem (-0.370, x7): a single index
fanned across the whole 65536x32, 128-M20K macro. Split by WIDTH into
four 65536x8 banks, each with its own (* preserve, dont_merge *) write-
address launch register; write the four byte lanes together in F_WRITE;
reconstruct the sample word by concatenating four registered read bytes.
Selector structure and 1-cycle read latency unchanged; total M20Ks
unchanged (4x32 == 128); fill_crc still sums the full 32-bit word.

Max-skew: relax ONLY the Ch357 tile-write CDC set_max_skew 2.0 -> 2.5
(quasi-static bundle, >=2 dclk stability window); retain set_net_delay
2.0 (the real arrival bound). SDC comment updated.

Tests: new tb_gs_axi_aw_regbuf (AW scoreboard: exactly-once/order/no-
combinational-AWREADY-bypass/stable-while-stalled); tb_gs_texture_cache
strengthened to distinct-per-byte-bank data + per-bank + full-word
checks. All pass: aw/w regbuf, texture_cache, texture_psmt8_clut,
scanout_diag, ps2_hps_bridge, and the complete f52 replay BYTE-IDENTICAL
(Z 0/307200, COLOR 0/245760).

No Quartus, board, or push from here. Ready for one owner GUI fit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 11:19:09 -04:00
thejayman77 194f45bd05 Ch442 review fixes: fs_edge parity, dest snap_valid, sync attrs + SDC
Codex review containment/observability fixes (no behavior change, no redesign):

- scanout_lb: add !fs_edge_v to the diagnostic predicate so capture
  matches the underflow latch's frame-start CLEAR priority exactly
  (no capture on an fs_edge cycle the real latch suppresses).
- bridge: expose a destination snap_valid_q as 0x120[0] — set ON the
  payload-capture edge, cleared on synced source-valid deassert — so
  valid never leads the bundle by a cycle (was the middle sync stage).
- bridge: forced-synchronizer (SYNCHRONIZER_IDENTIFICATION FORCED) +
  dont_merge/preserve on the underflow/read-error/valid chains;
  preserve on the bundle capture regs (both domains).
- SDC: stage-0 async cuts on the three sync[0] inputs + the 37-bit
  stable bundle hold-false-path + 2ns max_skew + 2ns net_delay, with
  fail-closed src==37 / dst!=0 count checks (tile_ram_cdc idiom).
- tb_gs_scanout_diag: +fs_edge-suppression monitor (with coverage that
  the coincidence is exercised), +valid-ordering monitor, +snapshot
  stability after later misses, +production DUT (V_SOURCE_START=32,
  stretch, linear) proving cold-start scan_y=32, +cause/phase packing.
  32/32 checks pass.
- doc: production cold start is source row 32 (not 0), base+lookahead
  may both assert, and one snapshot narrows but does not prove
  starvation vs next_fetch CDC-lag.

Sim set all PASS: focused TB, tb_ps2_hps_bridge, scanout_lb
{binomial,hstretch,psm32_256}, complete f52 replay (FB byte-identical
Z 0/307200, COLOR 0/245760). No Quartus, board, push, or scanout
behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 23:50:04 -04:00
thejayman77 6720caee12 Ch442: A+B scanout diagnostic (split LPDDR_STATUS[5] + first-failure snapshot)
Read-only diagnostic to disambiguate the three causes folded into
LPDDR_STATUS[5]. bit5 (0x02C) semantics are UNCHANGED.

- gs_lpddr_scanout_lb: register a live (rd_errs!=0) flag in the emif
  domain (the raw counter never crosses), and capture the FIRST raw
  underflow of each video-source-enabled session as a bundled-data
  snapshot (scan_y/nf_v/nf_s0 + base-vs-lookahead cause + line_valid +
  vphase), held stable until !enable. The existing sticky underflow_v
  latch, fetch FSM, pixel path and arbitration are untouched.
- ps2_hps_bridge: independently 2-FF sync the two split live flags;
  latch the snapshot on the rising synced-valid edge (coherent bundled
  data). New read-only regs 0x120 SCAN_DIAG_STATUS / 0x124
  SCAN_DIAG_FIRST (window addr[37:5]==9); 0x118/0x11C reserved slots
  untouched.
- top: drive 5 diag nets per scanout arm like scan_err_w; bit5 assign
  unchanged.
- tb_gs_scanout_diag (new): provokes AXI RRESP error, cold-start row-0
  starvation, first-failure capture + coherent bridge readback, and
  clear-via-video-source-disable (17/17 checks).
- Tie off the new bridge inputs in the four .*-instantiating TBs.

Sim set all PASS: focused TB, tb_ps2_hps_bridge, scanout_lb
{binomial,hstretch,psm32_256}, and the complete f52 replay (FB
byte-identical: Z 0/307200, COLOR 0/245760; sum32=0xaad0b94d).

No fix / gray-code / persistence-filter / scanout checksum. No Quartus,
no board, no push.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 22:40:07 -04:00
thejayman77 2123e646c8 docs: Ch441 board validation + Ch437 A/B scanout evidence
Preserve the Ch441 timing/renderer board-validation evidence and the
Codex-directed follow-up:
- FB reconciliation: the Ch437 A/B dump's 8-word first-beat delta was a
  non-reproducible one-time transient; 3x render+dump and 8x pure-probe
  re-reads all return golden d0047677 (words 0-7 correct). Memory integrity
  confirmed deterministic. (fb_reconciliation.md)
- Structural audit + A-D diagnostic proposal for LPDDR_STATUS[5]: bit5 ORs
  sticky CDC-synced underflow with AXI read-errors; dump path (arid=1) is
  separate from scanout/HDMI (arid=3). Instrumentation proposal only, not
  implemented. (scanout_status_bit5_proposal.md)
- Raw board logs, status samples, RBF hashes, A/B FB dump, board PNG.

No RTL edits, no compile, no builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 20:34:20 -04:00
thejayman77 96d23ea8ee Ch441 TB review fixes: complete stall check + full&&d_wready coverage [READY FOR REVIEW]
Per Codex review of dd7ca4f (testbench only, RTL unchanged):
1. Stall check now requires d_wvalid to REMAIN asserted (catches a deassert) and
   compares the COMPLETE {d_wdata,d_wstrb,d_wlast} against the held beat, not just
   d_wdata.
2. Added saw_full_and_ready coverage flag (set on dut.full && d_wready) and a final
   check that FAILS if the distinguishing no-bypass case was never observed.

No RTL change, no simulations, no Quartus.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 14:25:17 -04:00
thejayman77 dd7ca4fb8e Ch441: one-entry fully-registered W buffer, Z-RMW master -> wr_arb s2 [READY FOR REVIEW]
Cuts the lone remaining EMIF setup fail (-0.016 ns, -0.259 skew): the combinational
EMIF gen_p2c_ff -> wr_arb s2_wready -> gs_lpddr_z_rmw next-state (st, endpoint
labelled S_FILL_R via the shared encoded state register).

New gs_axi_w_regbuf: fully-registered one-entry W buffer (Option B per Codex).
- u_wready = !full ONLY (registered occupancy) -> EMIF WREADY never reaches the Z
  FSM combinationally. NOT a fall-through skid (no !full-OR-d_wready term).
- Buffers WDATA/WSTRB/WLAST; downstream held stable until accepted; exactly-once.
- AW/B untouched; arbiter bready_q unchanged (still arms on real EMIF W handshake).
- z_rmw may enter B-wait once the beat is buffered -- safe: EMIF cannot return B
  until the buffered beat reaches it. Single-beat writes -> the 1-beat/2-cycle
  buffer rate is far above the Z write rate (no new FIFO pressure).

Wired in zc_emit between u_z W output (zi_*) and the z_w* ports. New file in sim
Makefile RTL_SRCS + synth QSF (both). Focused tb_gs_axi_w_regbuf: exactly-once/order/
payload scoreboard + no-combinational-bypass check (u_wready===!full incl. full &&
d_wready) + downstream-stable check; standalone target + in make run. Texture-cache
+0.016 paths NOT touched. No simulations or Quartus run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 14:12:11 -04:00
thejayman77 94d6293c43 Ch440 review fixes: 93-bit per-bank test coverage + soften M20K claims [READY FOR REVIEW]
Per Codex review of 471c1af:
1. tb_gs_async_fifo QUAD_WIDTH4 variant now runs at the PRODUCTION 93-bit width
   (exercising the odd 23/23/23/24 remainder split) and drives a DISTINCT NONZERO
   pattern into every width bank (seq XOR per-bank constants, bank2 inverted), with
   the scoreboard checking the FULL reconstructed word. A swapped/broken/zeroed
   upper bank now changes the word and trips the scoreboard. Depth-half crossing
   (DEPTH=8) + wrap/full coverage retained. mk() is generate-guarded so the 32-bit
   variants never elaborate the 93-bit selects.
2. Softened gs_async_fifo comments: 'same total M20K' -> EXPECTED-similar, pending
   synthesis (fact -> expectation).

No simulations or Quartus run. Awaiting review before any sim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:28:31 -04:00
thejayman77 471c1af9cd Ch440: gs_async_fifo QUAD_WIDTH4_READ (2 depth x 4 width) for Z-req FIFO [READY FOR REVIEW]
Candidate 2 from the accepted structural proposal. Adds a QUAD_WIDTH4_READ
generate branch to gs_async_fifo: like QUADRANT_READ (2 depth x 2 width) but
splits the payload into FOUR width banks, so each preserved read-address launch
register drives ~half the M20K load (targets the 310MHz raddr_hi1_q->mem_hi1
setup family, WNS -0.103). Keeps QUADRANT_READ's proven 2:1 depth OUTPUT selector
unchanged (no new/deeper mux, per the doc's warning). Depth (8192), one-cycle
read latency, ordering, CDC, capacity, and interface are identical.

zc_emit u_req switched QUADRANT_READ->QUAD_WIDTH4_READ. New default-off param
leaves the other two gs_async_fifo instances byte-identical. TB gains a
TEST_QUAD_WIDTH4 variant + standalone Makefile target tb_gs_async_fifo_quad_width4.

NO simulations or Quartus run (per authorization). Awaiting review before any sim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:19:09 -04:00
thejayman77 67aa332928 REVERT REQ_DEPTH to 8192 — 1024 was UNSAFE (Codex audit)
My REQ_DEPTH=1024 reduction was wrong: I sized it off a 478 peak from a SMALL
scene. The documented full-f52 replay peak is 6,115 (adjacent frame 3,610; even
2,048 clips) — ch439_timing_convergence_prefit.md. The raster cannot backpressure,
so an undersized FIFO SILENTLY DROPS Z-requests -> corrupt frame. The -0.074 fit
was on an invalid (drop-prone) config. Restore 8,192 as the correctness baseline.
The real timing fix is a registered/skid AR-handshake boundary (arbiter s2_arready
combinational -> texcache F_AR), NOT FIFO shrinking. Packaging that for Codex review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:37:39 -04:00
thejayman77 547811e89b Z-req FIFO REQ_DEPTH 8192->1024: kill zc_emit EMIF setup path, free 32 M20Ks
The 8K request FIFO was 8x oversized (measured peak ~478; Codex's TB uses 1024).
Its read address routed to 8 spread M20K blocks -> 310MHz EMIF setup miss (-0.103,
MAX_FANOUT can't help: Quartus refuses to duplicate RAM-address regs, Warning 22471).
Shrinking to 1024 (2 blocks): 26.1 fit shows that path GONE, design clk +0.631->+5.693,
RAM 309/358 (86%) -> 277/358 (77%). New worst is a DIFFERENT, skew-dominated EMIF->
texcache-FSM path at -0.074 (TNS -2.032 -> -0.164).

SAFETY GATE before board load: confirm the full zsrt139f52 scene never bursts >1024
Z-requests in flight (peak I have is 478 from a smaller scene). Bump to 2048 if unsure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 05:43:25 -04:00
thejayman77 3d27095c2b QSF: revert to AGGRESSIVE AREA + surgical MAX_FANOUT on Z-req raddr (close EMIF, keep design clk)
Fit data: AGGRESSIVE AREA gives design clk +0.631 / EMIF -0.103; global BALANCED
flipped it to EMIF +0.084 / design clk -6.119 (vert_count->texcache swung 6.7ns).
AGGRESSIVE AREA is far closer to closing, so keep it and fix the one 310MHz EMIF
miss surgically: MAX_FANOUT 3 on u_zc_emit|u_req|raddr_hi1_q forces the register
duplication Quartus asked for (TMC-20551) without the global placement disruption.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 04:36:42 -04:00
thejayman77 b29fb17a11 QSF: OPTIMIZATION_MODE AGGRESSIVE AREA -> BALANCED (close -0.103ns EMIF path)
After the fog prune the design clock closed (+0.631ns) but a 310MHz EMIF path
missed by 103ps: the Z-req FIFO read-address register u_zc_emit|u_req|raddr_hi1_q[4]
fans out to 6 spread M20K blocks. Quartus flagged TMC-20551 (needs more register
duplication), which AGGRESSIVE AREA mode was suppressing. That mode is a leftover
from the resolved LAB-overflow era; utilization has headroom (ALM 85%, DSP 45%,
RAM unaffected by opt mode). BALANCED lets the fitter duplicate the address
register to close the path. QSF-only, no RTL change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 22:04:00 -04:00
thejayman77 a72aaded22 Add FOG_ENABLE param, prune fog cone on the board profile (fix -6.165ns setup)
The fog blend's tex_color*F multiply landed on the texture->color critical path
(u_texcache RAM -> u_tex|tex_color -> mult_126 -> raster_pixel_color_q, 46ns,
-6.165ns setup). Fog is a proven no-op for the current board scene (96.5% F=255),
so pay zero for it here.

FOG_ENABLE (default 1, byte-identical fog) added to gs_stub; each fog mux gated
(FOG_ENABLE && ras_fge) so FOG_ENABLE=0 constant-folds the entire fog cone away
(multiply, FOGCOL adders, s2_fog_f/persp_fog_f5 interp). Threaded through
top_psmct32_raster_demo_bram; set FOG_ENABLE(1'b0) on the active GS_SH3_LPDDR_FB
board arm (elsif at :1086, the one the QSF profile compiles). Fog stays default-on
everywhere else.

Verified: FOG_ENABLE=1 fog TBs unchanged PASS; FOG_ENABLE=0 (new tb_gs_fog_disabled
+ board scene TB) emits raw color = pre-fog datapath; board elaborates clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:31:14 -04:00
thejayman77 dd75d78491 Fix signoff timing: remove dead bilinear clamp (comb loop) + relax gray skew 2.0->2.5ns
Two fixes for the failing STA (Codex-reviewed):
1. gs_texture_unit lerp8: remove the dead 0/255 clamp. For 8-bit taps and f in
   0..15, a+floor((b-a)*f/16) is always in [0,255], so the clamp never fired -
   but its comparators formed a 161-node combinational loop that failed
   design-clock setup by 42 ps (tap[0][11] -> tex_cache_sel_q). Functionally
   identical (tb_gs_texture_bilinear + texture TBs bit-identical PASS).
2. SDC: relax the u_zc_emit gray-code CDC max_skew 2.0 -> 2.5 ns. Post-fog
   placement pushed actual skew to 2.010 ns (fails 2.0 by ~10 ps). Real ceiling
   is the 3.225 ns EMIF period (one gray bit in flight); 2.5 clears the miss and
   stays well under it. Documented relax of a conservative round number.

Acceptance (owner fit): no comb-loop warning, no tap->tex_cache_sel_q path,
all setup + max_skew slacks >= 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 20:03:42 -04:00
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
516 changed files with 1630280 additions and 130187 deletions
+1 -1
View File
@@ -1 +1 @@
{"sessionId":"7df840c3-ba5a-42e3-bbe6-19e8a578a1b2","pid":2591198,"procStart":"89849917","acquiredAt":1780384810094}
{"sessionId":"7df840c3-ba5a-42e3-bbe6-19e8a578a1b2","pid":1635590,"procStart":"118103053","acquiredAt":1783156758224}
+13
View File
@@ -25,6 +25,16 @@ manifest.hex
*sh3*.hex
*sh3*.dat
sh3_*.mem
# Ch356/Ch357 epoch descriptor tables — dump-derived (dump indices + texture CRCs). Exact
# paths only (a broad *sh3*.txt would wrongly ignore legitimate docs like *_sh3_notes.txt).
/sim/data/top_psmct32_raster_demo/sh3_sched_epochs.txt
/sim/data/top_psmct32_raster_demo/sh3_s640_epochs.txt
/sim/data/top_psmct32_raster_demo/sh3_zsched_epochs.txt
/sim/data/top_psmct32_raster_demo/sh3_ztrio_epochs.txt
/sim/data/top_psmct32_raster_demo/sh3_zs640_epochs.txt
/sim/data/top_psmct32_raster_demo/sh3_zs640c6_epochs.txt
# dump-derived reference/board-FB images (render copyrighted game frames)
/sim/data/top_psmct32_raster_demo/sh3_*.png
# ---- python cache ----
__pycache__/
@@ -40,6 +50,9 @@ __pycache__/
/synth/**/dni/
/synth/**/qdb/
/synth/**/.qsys_edit/
/synth/**/tmp-clearbox/
/synth/**/sta_*.txt
*.tdf
/synth/de25_nano/top_psmct32_raster_demo/baseline_*/
/synth/de25_nano/experiments/
*.sof
+352
View File
@@ -0,0 +1,352 @@
# Ch353 audit log — full SH3 draw on an LPDDR-only direct PSMCT32 framebuffer
Purpose: complete, auditable trail of every change + verification for Codex review.
Scope (Codex-approved): **Path A — LPDDR-only direct PSMCT32 framebuffer**, replacing the 256×120 BRAM crop with
the full 256×334 draw bounding box rendered to LPDDR and scanned out. Path B (tile-spill) deferred.
Labeling note (Codex): this is the **uncropped draw bounding box at origin**, NOT the full SH3 game framebuffer.
## Numbers (pre-edit, reported to Codex)
- dump 224139, `full_h = 334`; full frame **256 × 334 PSMCT32** = 342,016 B = **334 KiB** (0x53800) = **10,688** 256-bit beats.
- row stride 1024 B. LPDDR ranges (disjoint): **FB 0x000000..0x053800 (334 KiB)**; **TEX 0x200000..0x240000 (256 KiB)**.
- 334 KiB > 128 KiB BRAM and > 256 KiB read2 cap → LPDDR required.
---
## Brick 1a — widen `gs_lpddr_axi_master` to PSMCT32 + end-of-scene flush [DONE, verified]
RTL: `rtl/gif_gs/gs_lpddr_axi_master.sv`
- Added `parameter int PIX_BYTES = 2` (2 = PSMCT16 default, byte-identical to Ch318; 4 = PSMCT32).
- Derived: `PIX_BITS = PIX_BYTES*8`, `LANE_LO = (PIX_BYTES==4)?2:1`, `STRB1 = '1` (per-pixel byte-strobe mask).
- Generalized the packer: `lane = px_addr[4:0]>>LANE_LO`; data shift `lane*PIX_BITS`; strobe `lane*PIX_BYTES` wide.
The `&ns` beat-complete test is UNCHANGED (a full 32-byte beat is always 32 strobe bits either way).
- Port `px_pix16[15:0]``px_pix32[31:0]` (PSMCT16 callers drive `{16'd0, pix16}`).
- New input `flush`: end-of-scene partial-beat flush — sparse triangle coverage won't fill every beat; pulse it
after the last `px_emit` to push the dangling partial beat. PSMCT16 tile path ties it to 0 (tile-rows always
complete beats). Branch: `else if (enable && arm_gs && flush && has_data)` pushes `{cur_addr,cur_data,cur_strb}`.
Callers updated: `rtl/top/de25_nano_psmct32_raster_demo_top.sv` (2 instantiations) → `.px_pix32({16'd0, demo_flush_pix16}), .flush(1'b0)`.
TB: `sim/tb/gif_gs/tb_gs_lpddr_axi_master.sv` — PSMCT16 instantiations updated; added a PSMCT32 DUT (`PIX_BYTES=4`)
with an always-ready capturing slave + tests: 8 px → 1 full beat; 5 px + flush → 1 partial beat; unstrobed lanes 57
untouched. Result: `[axi32] PSMCT32 ok ...`, `[axi] DISARMED/CANARY/STABILITY ok` (PSMCT16 byte-identical), **PASS**.
**Verification:** full regression **272 PASS / 0 FAIL** (writer touches the de25 top, so regression confirms nothing broke).
---
## Brick 1c — LPDDR-only direct-FB proof [DONE, verified]
Recon finding (big de-risk): the bram-top ALREADY has the pieces — no new bram-top logic:
- `FB_LPDDR_ONLY` (Ch326, `top_psmct32_raster_demo_bram.sv:1170`): `vram_we_pre = xfer_busy ? xfer_we : (raster_pixel_emit && !FB_LPDDR_ONLY)` — suppresses the BRAM raster mirror, keeps xfer/CLUT writes. = Codex's "suppress raster, keep CLUT".
- `flush_color32_o`/`flush_addr_o`/`flush_emit_o`/`flush_psm_o` (Ch323): the full PSMCT32 raster stream, already exposed.
- `feeder_ready_o` (C_READY): render-done, for the flush pulse.
TB: `sim/tb/top/tb_top_psmct32_sh3_lpddr_fb.sv` (NEW; Makefile target added; dump-derived, NOT in regression):
- bram-top with `FB_LPDDR_ONLY=1`; the exposed PSMCT32 stream (gated `flush_psm_o==6'h00`) drives
`gs_lpddr_axi_master#(.FIFO_DEPTH(64),.PIX_BYTES(4))` → a behavioral, strobe-honouring LPDDR FB.
- Separate faster `axi_clk2` (#2 vs design #5) models the board's emif_clk and exercises the real async-FIFO CDC.
- "ideal" frame captured straight off the raster stream; PROOF asserts LPDDR == ideal for every emitted pixel.
- End-of-scene `flush` pulse + a STABLE-idle drain gate before asserting.
**Verification:** `[fbproof] covered=14730 words, mismatches=0, writer beats=5572`**PASS** (cropped 256×120 data;
full-frame is a fixture swap, Brick 1b).
Two findings worth auditing:
1. Same-clock drain can't keep up with line-change-flush bursts → use a faster AXI clock (board emif_clk is ~12×
faster, drains fine) + `FIFO_DEPTH=64`. (No RTL backpressure added; the board clock ratio makes it unnecessary.)
2. **Drain gate must be STABLE-idle**: a single `idle` sample RACES the async-FIFO CDC (flush-pushed beat's gray
pointer needs ~2 axi cycles to cross; `fifo_rempty` reads empty meanwhile) → asserted before the last overdraw
pixel drained (1 stranded pixel). Fix: settle + require idle for 8 consecutive cycles. **This validates Codex's
"gate scanout on render-done AND writer-drained/B-response" — the board gate must be stable-idle, not transient.**
---
## Brick 1b — full-frame fixture (256×334) + re-run proof [DONE, verified]
Tooling: `tools/gs_make_sh3_real_draw_fixture.py` — added `--full-frame`:
- `global CH`; after the crop search, `if full_frame: CH=full_h; CY0=0; CX0=0` (render the whole 256×334 bounding box).
- Crop-dependent outputs written with a `tag` (`full` vs `real`): `feeder_sh3_{tag}.mem`, `sh3_{tag}_params.vh`,
`sh3_{tag}_refmap*.mem`, `bios_sh3_{tag}.mem`, `payload_sh3_{tag}.mem`. Shared texture/CLUT/idx/pal stay `sh3_real_*`.
→ the Ch352 cropped build (`sh3_real_*`) is byte-untouched.
- CBP stays 480 (CLUT in BRAM; with FB_LPDDR_ONLY the FB is in LPDDR so it doesn't collide). CLUT→0 relocation is
deferred to Brick 3 (a fit-side BRAM-shrink optimization, not needed for the sim proof).
Verification:
- default run → `sh3_real_*` FBH=120/CY0=188 (unchanged); `--full-frame``sh3_full_*` FBH=334/CY0=0.
- **Cropped oracle still bit-identical: `tb_top_psmct32_sh3_real_draw_demo` 96.2% PASS** (Ch352 build intact).
- proof TB: `\`ifdef SH3_FULL_FRAME` selects `sh3_full_params.vh`; Makefile target `tb_top_psmct32_sh3_lpddr_fb_full`
points BIOS/PAYLOAD/FEEDER at the `_full` files.
- **Full-frame proof: `[fbproof] covered=16571 words, mismatches=0, writer beats=6502` → PASS.**
## Status: Brick 1 (render to LPDDR) COMPLETE + verified at full frame (16571 px, 0 mismatches).
---
## Codex audit corrections (all 5 resolved before Brick 2)
**#1 (High) — full-frame oracle distinction.** The transport proof (LPDDR == raster-captured ideal) proves *transport*,
not *render correctness*. Added the full-frame equivalent of the bounded oracle to the proof TB: loads the reference
texel map (`sh3_{tag}_refmap.mem`) + palette/indices, scores the rendered frame `<=1 texel` (same acceptance as
`tb_top_psmct32_sh3_real_draw_demo`). Cropped reads **96.2% / INT 97.5% / clut_bad=0 — EXACTLY matching the standalone
oracle**; full-frame **96.1% / INT 97.6% / clut_bad=0**. (Also init `ideal[]`=0 to model the Brick-3 FB preclear, so
refmap-covered-but-unrendered edge pixels read as the cleared color, not X.)
**#2 (High) — production-safe drain ack (replaces the stable-idle heuristic).** `gs_lpddr_axi_master`: FIFO word
widened to 321 (marker bit). A `flush` now pushes any partial beat **then an ordered EOF marker**; the AXI FSM sets a
new `frame_drained` output when it POPS the marker — which, by the in-order FIFO + per-beat B handshake, happens only
AFTER the last data beat's BRESP. Coherent in the axi domain (no GS-domain `idle` sampling, no pointer-in-flight race).
The proof TB now waits on `frame_drained` (a stable level), not the heuristic. PSMCT16 path byte-identical (flush tied 0).
**#3 (Medium) — PSMCT32 AXI backpressure.** `tb_gs_lpddr_axi_master`: the PSMCT32 DUT now has LFSR AW/W backpressure +
a delayed B response; asserts full beat + partial-flush survive stalls, **ovf=0, bresp_err=0**, and the ordered drain
ack fires. PASS.
**#4 (Medium) — tagged generated PNGs.** `gs_make_sh3_real_draw_fixture.py` now writes `sh3_{tag}_ref.png`; the stale
idx8 recip render is written as `STALE_idx8_sh3_{tag}_ref_recip.png` (clearly marked). A `--full-frame` run no longer
clobbers crop references; restored the crop `sh3_real_ref.png` (256×120) and created `sh3_full_ref.png` (256×334).
**#5 (Hygiene) — gitignore.** Added `/synth/**/tmp-clearbox/`, `/synth/**/sta_*.txt`, `*.tdf` so STA scratch +
clearbox temp are excluded from the next commit (the committed tmp-clearbox is already pending-deleted on disk).
## Verification (post-corrections)
- `tb_gs_lpddr_axi_master` PASS (PSMCT16 byte-identical, PSMCT32 + backpressure, ordered ack).
- `tb_top_psmct32_sh3_lpddr_fb` (cropped) PASS: transport 0 mism + oracle 96.2%/97.5%/clut_bad=0.
- `tb_top_psmct32_sh3_lpddr_fb_full` (256×334) PASS: transport 0 mism + oracle 96.1%/97.6%/clut_bad=0.
- Cropped oracle `tb_top_psmct32_sh3_real_draw_demo` still 96.2% PASS (Ch352 build intact).
- Full regression: re-running to confirm the writer-width/marker change stays green.
## Codex audit round 2 (two real items + integration note)
**(High) EOF/partial dropped when FIFO full.** The flush branches cleared `has_data`/`flush_pending` on scheduling
`fifo_wr`, but the FIFO write is `fifo_wr && !fifo_wfull` — if full, the partial/marker vanished. Fix in
`gs_lpddr_axi_master`: (a) gate the flush branches on `!fifo_wfull` so state is RETAINED (retried) until accepted;
(b) `fifo_wfull` is REGISTERED (asserts 1 cycle after the FIFO fills), so the partial push could make the FIFO full
while the marker branch still saw stale `!wfull` and fired early → its write then dropped. Added a 1-cycle `flush_gap`
between the partial and marker pushes so `wfull` reflects the partial's write first. **Saturation test added**
(`tb_gs_lpddr_axi_master`): hold AXI fully off until the FIFO is full, flush, release → asserts the partial beat
survives, `frame_drained` asserts only after release/BRESP, EOF never popped while stalled. PASS.
**(Hygiene) tracked tmp-clearbox deletions.** `git rm --cached` on the 51 tracked `tmp-clearbox/*.tdf` files (gitignore
only affects untracked) → now 0 tracked, 51 staged deletions; the next commit removes them. No other STA/tdf scratch
tracked.
**Integration note (Codex, for Brick 2).** `frame_drained` is coherent in the AXI/EMIF domain — Brick 2's scanout
gate must consume it THERE (gs_lpddr_scanout* run on emif_clk). Any design-clock use needs a synchronizer. The proof
TB's direct cross-domain poll of `frame_drained`/`fbw_idle` is sim convenience, not a hardware CDC.
## Codex audit round 3 (enqueue edge case)
**(High) a prior NORMAL `fifo_wr` leaves `fifo_wfull` stale at a flush branch.** Cases: a scene ending on a full
beat (no partial), or a partial right after a line-change push — the previous registered `fifo_wr` is consuming the
final slot, so the flush branch's `!fifo_wfull` check is stale. The round-2 `flush_gap` only covered partial→marker.
Fix: gate BOTH flush enqueue branches on **`!fifo_wr && !fifo_wfull`** — no push in flight AND not full, so the
`!wfull` check is accurate; state is retained/retried until accepted. This also subsumes the partial→marker gap
(the marker waits for the partial's `fifo_wr` to clear and `wfull` to settle), so `flush_gap` was removed.
**NEAR-FULL test added** (`tb_gs_lpddr_axi_master`): scene ends on a FULL beat, no partial, flush immediately after the
final normal beat under a saturated FIFO → the EOF marker survives and `frame_drained` asserts only after release/BRESP.
PASS (alongside the saturation test).
## Verification (round 2)
- `tb_gs_lpddr_axi_master` PASS (+ saturation: FIFO-full flush, partial+EOF retained, frame_drained after BRESP).
- both proofs still PASS (cropped 96.2%/clut_bad=0; full-frame 96.1%/clut_bad=0; transport 0 mism).
- full regression re-running (writer gap fix).
Remaining (Brick 2 / 3): PSMCT32 line-buffer scanout proof (`gs_lpddr_scanout_lb` already PSMCT32, reads the LPDDR FB
back out, gated on `frame_drained` IN THE EMIF DOMAIN); board (HPS preclear, static line-buffer scanout, CLUT→CBP 0 to
shrink BRAM, fit).
## Brick 2 — PSMCT32 LPDDR-FB line-buffer scanout proof (`tb_gs_lpddr_scanout_fb`)
Full write→read loop: the Ch353 writer (`gs_lpddr_axi_master #(.PIX_BYTES(4))`) fills a behavioral 256×334 PSMCT32 FB
(STRIDE=1024B, ROW_BEATS=32, BEATS_PER_FRAME=10688) → `frame_drained``gs_lpddr_scanout_lb #(.STRIDE_BYTES(1024),
.ROW_BEATS(32),.N_ROWS(334),.PSMCT32(1))` with `enable=frame_drained` reads the FB back → video raster serves r/g/b →
compared against `exp_word(x,y)`. Three independent clocks (gs #5 / emif #2 / video #7). Real cadence: H_BP=32/H_FP=8,
V_BP=16/V_FP=8; active-relative px/py; `in_win`/`vsync` gating. FB precleared to 0 (models HPS preclear). Read model
has variable AR/R latency (LFSR). All 256×334 pixels compared **including the x≥128 black half**.
Codex acceptance gates — all met:
- **All pixels correct incl. black**: `errors=0` (checked=105968 ≥ 256×334 active).
- **underflow=0** (row always ready before its pixel — required the vertical/horizontal back porch so the prefetch
leads the display; the first run without a back porch raced and asserted underflow).
- **rd_errs=0** (no read-response errors).
- **valid through the final row**: `checked ≥ H_ACT*V_ACT`.
- **frame_drained gate consumed in the EMIF domain** (scanout runs on emif_clk).
Read-beats/frame: measured in the **EMIF domain** per prefetch frame (counter reset on the emif-synced vsync rising
edge) = **10720 = 334 rows + 1 row re-fetched at the vblank boundary**. Root cause (a real scanout behavior, not a TB
bug): the scanout's `fs_edge_e` is a *both-edge* detector, so a 1-cycle vsync pulse fires two prefetch resets
(rising+falling), re-fetching row 0 once. Harmless — every row is read correctly (pixels match). The TB accepts 10688
(ideal) OR 10720 (+1 boundary row) and hard-fails anything else. **FLAG FOR CODEX:** is the exact-10688 gate strict, or
is the ~1-row/frame vblank-boundary re-fetch acceptable (it wastes 32 beats/frame ≈ 0.3% of read bandwidth)? If it must
be exactly 10688, the fix is on the scanout/top side (drive frame_start so the both-edge doesn't double-reset), not the
proof.
Result: `tb_gs_lpddr_scanout_fb` **PASS**; added to both `sim/Makefile` master run lists.
Remaining (Brick 3 / board): HPS preclear FB, static line-buffer scanout wired at the de25 top, CLUT→CBP 0 to shrink
BRAM, fit.
## Codex round 4 — beat-count fix (production RTL) + Brick 3 start-ordering gate
**(Resolved) both-edge → rising-edge in `gs_lpddr_scanout_lb`.** The 10720-vs-10688 was a real production-RTL issue,
not test noise. Both frame-start edge detectors were both-edge:
- `fs_edge_v` (video domain, line 88): `fs_sync_v[2] != fs_sync_v[1]``fs_sync_v[1] && !fs_sync_v[2]`
- `fs_edge_e` (emif domain, line 132): `fs_sync_e[2] != fs_sync_e[1]``fs_sync_e[1] && !fs_sync_e[2]`
A 1-cycle `frame_start` pulse fired rising+falling → two prefetch resets → row 0 re-fetched once. Rising-edge only =
exactly one reset per frame. TB assertion tightened back to **exactly 10688** (`!== BEATS_PER_FRAME` is a hard fail).
Verification: `tb_gs_lpddr_scanout_fb`**10688/frame, underflow=0, rd_errs=0, errors=0, PASS**. Focused scanout
suite all PASS (scanout_lb, scanout_lb_psm32, scanout_lb_psm32_256, scanout_concurrency, scanout, scanout_psm32).
Full regression re-run for the RTL change.
## Brick 3 — start-ordering gate (Codex, mandatory sequence)
The de25 top must enforce this host-start order; the boot CLUT payload must NOT auto-start rendering before 14:
1. HPS preclears the framebuffer.
2. HPS uploads the texture.
3. Texture cache fill completes successfully.
4. CLUT boot upload/load completes.
5. Arm the PSMCT32 writer.
6. Start the feeder render.
7. Send EOF; await EMIF-domain `frame_drained`.
8. Enable the static LPDDR scanout.
## Codex round 5 — Brick 3 build rulings (board path)
1. **No hardware preclear engine for v1.** Extend the HPS write-probe (`ps2_sh3_tex_upload.c`) to zero
0x0000000x0537FF (85,504 words) — only ~30% more traffic than the proven 65,536-word texture upload — polling
`write_pending` and checking BRESP. A new AXI engine + arbiter are unjustified before measuring this path.
2. **Dedicated `GS_SH3_LPDDR_FB` profile**, separate from `GS_TILE_SPILL`, static PSMCT32 line-buffer scanout,
profile-specific geometry (256×334).
3. **`frame_drained` as a HARD gate, no timeout.** A timeout can display a partial FB — recreating the bug the
handshake fixes. Failure stays BLACK with diagnostics; a manual debug override is acceptable only if explicit.
4. **Feeder auto-start-once correction (CRITICAL).** `top_psmct32_raster_demo_bram.sv:807` C_SETUP asserts `fdr_start`
when the boot upload finishes → the disarmed boot render leaves a pending EOF that would assert `frame_drained` the
instant the host later arms the writer. Fix: **`FEEDER_AUTOSTART` param (default 1'b1), 0 only for this profile** →
C_SETUP goes straight to C_READY (no boot render); every render then waits for an explicit 0x0E8 GO.
5. **Expose `frame_drained` as a host-readable diagnostic** if a spare status bit exists.
Required host sequence (GS_SH3_LPDDR_FB): (1) wait setup/CLUT complete + feeder ready → (2) preclear LPDDR FB →
(3) upload texture → (4) fill + verify texture cache → (5) configure base 0, full mode, arm writer → (6) issue 0x0E8 GO
→ (7) await ordered `frame_drained` → (8) enable scanout automatically from that ack.
### Landed this round
- **`FEEDER_AUTOSTART` param + FSM** in `top_psmct32_raster_demo_bram.sv` (param after FEEDER_STG_WORDS; C_SETUP
branches on it → auto boot render when 1, straight to C_READY when 0). Additive, default 1'b1. Verified
byte-identical: tb_top_psmct32_sh3_lpddr_fb / feeder_runtime_demo / feeder_scene_retrigger_demo all PASS.
### Design clarification (derived)
`GS_SH3_LPDDR_FB` is a **feeder-based** profile: the host streams the SH3 draw list into the feeder (0x0DC/0x0E4) and
GOes (0x0E8) — matching Codex's host sequence. So its inner-instance config mirrors `GS_SH3_REAL_DRAW_DEMO`
(FEEDER_ENABLE=1, CLUT_CSM1_ENABLE=1, PERSPECTIVE_CORRECT=1, PERSP_RECIP_IDX_BITS=11, GS_LPDDR_TEX cache) but with:
FEEDER_AUTOSTART=0, FB_LPDDR_ONLY=1 (Ch326, suppress BRAM FB mirror), the full 256×334 PSMCT32 FB in LPDDR (not the
128 KiB crop), and the line-buffer scanout at 256×334.
### Remaining Brick 3 build (precise)
- **(RTL, board top)** add `elsif GS_SH3_LPDDR_FB` inner-instance param block (SH3 config + FEEDER_AUTOSTART=0 +
FB_LPDDR_ONLY=1); activate the GS_LPDDR4B_FB writer/scanout blocks for this profile; reconfigure the writer instance
to PIX_BYTES=4 / px_pix32=demo_flush_color32 / px_emit on psm==6'h00 / flush=end-of-scene / route `frame_drained`;
set scanout geometry (STRIDE=1024, ROW_BEATS=32, N_ROWS=334, PSMCT32); `frame_ready_emif = frame_drained` (hard gate,
no timeout).
- **(RTL, bridge)** expose `frame_drained` as a status bit (mind the `.*`-port-add trap: update every bridge
instantiation/TB).
- **(host)** `ps2_sh3_tex_upload.c` FB-preclear loop (zero 0x0000000x0537FF via write-probe, poll write_pending + BRESP).
- **(build)** add the `GS_SH3_LPDDR_FB` profile to `select_de25_profile.sh` + QSF VERILOG_MACRO (also defines
GS_LPDDR4B_FB + GS_LPDDR_TEX + the SH3 fixture macros).
- **(sim, MANDATORY before fit)** integration sim proving the sequence: FEEDER_AUTOSTART=0 → no boot render → arm →
GO → EOF → `frame_drained` fires exactly once (NOT prematurely from a boot render) → scanout serves the correct
full-frame. This is the correctness proof for Codex's round-5 item 4.
## Brick 3 — host-start SEQUENCE proof (integration sim) — PASS
`sim/tb/top/tb_top_psmct32_sh3_lpddr_fb_seq.sv` (full-frame fixtures; the mandatory pre-fit gate). Instantiates the
bram-top with **FEEDER_AUTOSTART=0** + FB_LPDDR_ONLY + the SH3 feeder config, the PSMCT32 writer, the behavioral
(precleared) LPDDR FB, and `gs_lpddr_scanout_lb` reading it back to a real video raster — with the **render-epoch**
flush logic (Codex round 5) driving the writer's EOF. Clocks: design/gs #5, emif #2, video #7, bridge #11.
Render epoch (mirrors what wires into the board top): `render_inflight` set by an accepted host GO (feeder_go while
ready); `flush = render_inflight && feeder_ready_rise`; cleared on flush. So the FEEDER_AUTOSTART=0 setup→C_READY edge
(render_inflight=0) can NOT enqueue a false EOF.
Codex acceptance — ALL met on the first run:
- Boot setup reaches C_READY. **Before GO: raster_emits=0, writer_beats=0, eof=0, frame_drained=0** (no boot render).
- Texture fill verifies (fill_crc=fbdeaa32, tex_rd_errs=0). FB precleared to 0; idle BRESP errors=0.
- Host sets base 0 / canary off / arms writer (latched via ctrl_commit).
- **GO → feeder_ready drops then re-rises** after render+drain.
- **Exactly ONE EOF → exactly ONE frame_drained.** Writer beats=6502, FIFO overflow=0, BRESP errors=0.
- Synchronized `frame_drained` reaches the bridge domain.
- Scanout auto-enabled from the ack: **exactly 10688 read beats/frame**, all 256×334 pixels match (incl. black),
underflow=0, rd_errs=0.
- Bounded oracle: 96.1% ≤1-texel ALL / 97.6% interior / clut_bad=0 (correct SH3 render).
- `errors=0`**PASS**.
The SH3 fixture TBs are dump-derived/LOCAL, so this is run manually (like its siblings) — NOT added to the master
`run:` list. Full regression re-run for the widely-used inner-module FEEDER_AUTOSTART change (additive, default 1'b1).
REMAINING Brick 3 (board plumbing, then owner fit): board-top GS_SH3_LPDDR_FB profile (param block + writer reconfig +
scanout geom + frame_ready_emif=frame_drained + the render-epoch logic wired in design_clk); bridge frame_drained
status bit; host preclear loop in ps2_sh3_tex_upload.c; select_de25_profile.sh + QSF profile. No fit until the board
build compiles clean under the profile and this sequence proof stays green.
## Brick 3 board plumbing — COMPLETE (awaiting owner fit)
All board-side wiring for GS_SH3_LPDDR_FB is landed and verified in sim; the fit itself is owner-gated.
**Board top (`de25_nano_psmct32_raster_demo_top.sv`), all gated on `GS_SH3_LPDDR_FB` (inert for every other profile):**
- Inner-instance param block `elsif GS_SH3_LPDDR_FB` — mirrors GS_SH3_REAL_DRAW_DEMO (FEEDER_ENABLE, CLUT_CSM1,
PERSPECTIVE_CORRECT, PERSP_RECIP=11, VRAM 128 KiB, GRAD_SEQ_DIVIDER=0) + **FEEDER_AUTOSTART=0** + **FB_LPDDR_ONLY=1**.
- TEXC block: `elsif GS_SH3_LPDDR_FB` → the same 512×512 PSMT8 LPDDR texture geometry as the crop.
- Render epoch (design_clk): `sh3_render_inflight` set by an accepted feeder GO; `sh3_fb_flush = inflight && feeder_ready
rise`; cleared on flush → exactly one EOF per render (Codex round 5 — the setup→C_READY edge can't leak an EOF).
- frame_ready gate: `frame_ready_emif = sh3_frame_drained` (EMIF prefetch enable) + `frame_ready_r` = design-synced
sh3_frame_drained (HDMI mux blanks black until drained). HARD gate, no timeout.
- Writer (u_lpddr_axi, the active GS_LPDDR4B_FB instance): `PIX_BYTES(4)`, `px_emit` on psm==6'h00,
`px_pix32=demo_flush_color32`, `flush=sh3_fb_flush`, `.frame_drained(sh3_frame_drained)`.
- Scanout: `SCANOUT_LB_NROWS=334` (STRIDE 1024 / ROW_BEATS 32), `SCANOUT_FB_BASE=0`, `SCANOUT_PSMCT32=1`,
`scanout_lb_eff` forced 1 (line-buffer; a 256×334 frame-cache would be 334 KiB).
- Verified: patched-copy iverilog elaboration under GS_SH3_LPDDR_FB+GS_LPDDR4B_FB+GS_LPDDR_TEX = exit 0 (the only
standalone-sim gap is the Quartus-only EMIF_Qsys IP + a pre-existing `lpddr_ctrl_commit_w` forward-ref, both
fit-proven). Default board-top TB still builds+passes (edits inert for non-SH3).
**Drain diagnostic:** used existing `LPDDR_STATUS 0x02C[0]` (writer idle, stable post-render) + `LPDDR_BYTES 0x030`
(beats!=0) — the narrowest path (Codex), avoiding a bridge port-add that would break 4 `.*` regression TBs.
`frame_drained` still gates the scanout internally.
**Build profile:** `scripts/select_de25_profile.sh sh3_lpddr_fb` case added (SCENE = GS_SH3_LPDDR_FB + GS_LPDDR_TEX +
feeder_sh3_full; FIXTURE = bios/payload_sh3_full). Ran it myself + verified the QSF: SCENE/FIXTURE blocks correct,
markers balanced (2×), static GS_LPDDR4B_FB=1 / GS_LPDDR_FB=1 / USE_QSYS_TOP=1 / EMIF IP intact.
**Host app:** `tools/ps2_sh3_tex_upload.c` extended with `--lpddr-fb` mode (Codex's no-hw-clear path): wait feeder
ready → PRECLEAR FB 0x000000..0x0537FF (85504 words) via the write-probe (poll write_pending, check BRESP) → upload
texture → fill+verify cache → set base 0 / canary off / arm writer → feeder GO → await drain (idle + bytes!=0) →
raise video_src. Compiles clean (-Wall); dry-run OK (tex sum32=fbdeaa32). Crop path byte-unchanged.
**Verification:** seq proof `tb_top_psmct32_sh3_lpddr_fb_seq` still PASS (10688 beats, 1 EOF→1 frame_drained, oracle
96.1%). Regression re-running after the board-top edits.
**Owner fit order:** (1) `python3 tools/gs_make_sh3_real_draw_fixture.py --full-frame` (regenerate LOCAL fixtures if
absent); (2) `./scripts/select_de25_profile.sh sh3_lpddr_fb` (DONE — QSF already set); (3) fit in Quartus; (4) on the
board: `gcc -O2 -o ps2_sh3_tex_upload tools/ps2_sh3_tex_upload.c && sudo ./ps2_sh3_tex_upload sh3_real_tex_lpddr.mem
--lpddr-fb`.
## Ch353 follow-up — host-visible frame_drained diagnostic bit (Codex-approved after silicon)
Silicon showed the host's `idle`-based drain confirmation reads a mid-render count (idle pulses between the feeder's
render batches → the board printed "beats=44"). The DISPLAY was correct (scanout gated on the ordered frame_drained),
but the host couldn't READ a stable drain signal. Added it (the `.*`-port-add done carefully):
- **Bridge (`ps2_hps_bridge.sv`):** new `input frame_drained_i` + `frame_drained_sync` (2-FF into the bridge clock) +
**LPDDR_STATUS 0x02C[6] = frame_drained_sync[1]** (STABLE — asserts only after the EOF marker's last BRESP).
- **Board top:** `sh3_frame_drained` now defined in both ifdef branches (driven by the writer under GS_SH3_LPDDR_FB,
tied 0 otherwise); bridge `.frame_drained_i(sh3_frame_drained)`.
- **`.*`-trap handled:** the 4 `.*` bridge TBs (tb_ps2_hps_bridge, tb_bridge_iop_pad_input, tb_pad_state_via_sif_to_ee,
tb_ee_pad_buffer_branch) each got a tied `logic frame_drained_i = 1'b0;` (matched by name). tb_lpddr_tex_staging is
explicit/partial — the new input floats like its other unconnected inputs (no change). null bridge unaffected
(its port list is a strict subset). All 5 bridge TBs PASS.
- **Host (`ps2_sh3_tex_upload.c --lpddr-fb`):** step 7 now polls **0x02C[6] frame_drained** (not [0]idle) and reports
`frame_drained=1, FB beats written=~6500`. Compiles clean (-Wall).
Verified: board top elaborates clean under GS_SH3_LPDDR_FB+GS_LPDDR4B_FB+GS_LPDDR_TEX (exit 0); seq proof still PASS
(10688, 1 EOF→1 frame_drained); full regression re-running. This is a diagnostic-only change — the functional path
(silicon-verified) is unchanged; it re-fits into the same profile.
+45
View File
@@ -0,0 +1,45 @@
# Ch354 audit log — draw-list expansion: multiple SH3 draws composited into one LPDDR framebuffer
Purpose: auditable trail for Codex. Builds directly on Ch353 (the LPDDR-only PSMCT32 framebuffer, silicon-verified).
Brick 1 scope (Codex, conservative): the SAME-TEXTURE trio idx89548/89761/89974 (tbp=9216/cbp=13952, frame f1)
composited into one FB — ONE new variable: multiple authentic draws accumulating into one LPDDR framebuffer. NOT in
Brick 1: multi-texture residency, full 640x480, large Z-buffer.
## Fixture generator (tools/gs_make_sh3_multidraw_fixture.py) — all guardrails fail-CLOSED
- #4 mechanical: the trio is one frame (f1), one texture key (9216,0x13,8,TRI_STRIP,tme=1,abe=0), ascending dump order.
- #2 ALL feeder-visible state identical: TEX0(all fields), PRIM/fst/tme/abe, TEST, ZBUF, CLAMP, ALPHA, TEXA.
- #3 CONTENT (epoch-aware): texture @tbp=9216 (256 KB) + CLUT @cbp=13952 (1 KB) byte-identical across all three
(per-draw local-memory reconstruction; a same-byte re-upload passes, a changed payload FAILS).
- #1 capacity: 204 clipped tris -> 1843 staging words. Correctly fail-closed at 768; Codex approved FEEDER_STG_WORDS
=2048 (16-bit tri count + 12-bit staging addr cover it). Feeder .mem padded to 2048.
- #3 union bbox: origin (243,52), 248x338 -> FB 256x338, stride 1024B, size 0x54800 (338 KiB).
- #5 INDEPENDENT oracle from reconstructed GS local memory (not feeder records); #6 overlap scored: 10370 overlap px.
## Integration sim (tb_top_psmct32_sh3_multidraw) — PASS on the accumulation acceptance
Combined list through the SAME Ch353 LPDDR-FB path (FEEDER_AUTOSTART=0 + render-epoch EOF + frame_drained hard gate +
line-buffer scanout). PASS: records_emitted=204, ONE frame_drained after all batches, writer overflow/BRESP=0,
raster_overflow=0, scanout 256x338 = 10816 beats/frame exact, underflow=0, transport exact, clut_bad=0, overlap
exercised (10370 px). Fidelity is REPORTED, not gated (Codex: not part of accumulation acceptance).
## Codex diagnostic branch — RESOLVED (two SEPARATE claims)
Isolation + per-draw + RTL-composition (tool --tag/--only/owner-map; TB +TAG/+FBDUMP; scratch compose_check.py):
- **89761 isolated through the new path: INTERIOR 97.5%** == Ch353's 97.6% -> the multidraw tool/origin translation
is CORRECT (not the source of the drop).
- Per-draw <=1-texel INTERIOR: 89761=97.5%, 89548=95.6%, 89974=93.2% (draw-dependent).
- **RTL-paint-order composition: combined RTL == overlay of the three isolated single RTL renders, 0/86528 px differ.**
**CLAIM 1 (accumulation/order/coalescing): bit-exact — 0/86528 px different from RTL-composed singles.**
**CLAIM 2 (authentic-reference fidelity): draw-dependent (97.5/95.6/93.2%), the EXISTING perspective-sampling
fidelity limitation — NOT part of the accumulation acceptance, NOT independently attributed to the reciprocal curve.**
The combined interior (94.4%) is the coverage-weighted mix; the overlap region is dominated by the least-accurate
draw (89974 owns the most covered pixels), which is why OVERLAP-INTERIOR looks lowest.
## Board prep (Codex-directed) — verified in sim, owner-gated for the fit
- Board top: `GS_SH3_LPDDR_FB_MULTI` overrides FEEDER_STG_WORDS 768->2048 and SCANOUT_LB_NROWS 334->338; everything
else shared with GS_SH3_LPDDR_FB. Elaborates clean (exit 0) under GS_SH3_LPDDR_FB+_MULTI+GS_LPDDR4B_FB+GS_LPDDR_TEX.
- Framebuffer 256x338, stride 1024, size 0x54800; scanout 10816 beats/frame.
- Host ps2_sh3_tex_upload.c: `--fb-rows N` -> preclears the exact 256xN range (338 => 0..0x54800). -Wall clean.
- frame_drained diagnostic (0x02C[6], Ch353 follow-up) is BUNDLED into this fit.
- Build profile `sh3_lpddr_fb_multi` (select_de25_profile.sh + QSF): SCENE GS_SH3_LPDDR_FB + GS_SH3_LPDDR_FB_MULTI +
GS_LPDDR_TEX + feeder_sh3_multi; FIXTURE bios/payload_sh3_multi. Ran profile-select + verified the QSF.
- BOARD RUN: `sudo ./ps2_sh3_tex_upload sh3_real_tex_lpddr.mem --lpddr-fb --fb-rows 338`.
+101
View File
@@ -0,0 +1,101 @@
# Ch355 audit log — MULTI-TEXTURE composition (different-texture draws accumulate into one LPDDR framebuffer)
Codex rung after Ch354. Brick 1 (narrow): TWO authentic SH3 draws with DIFFERENT TEX0/CLUT composited into one FB via
SCENE-LEVEL texture rebind + staged-list retriggering. Dump order (authentic): A=idx19562 (tbp=11264/cbp=14080) THEN
B=idx89761 (tbp=9216/cbp=13952). NOT Brick 1: per-primitive TEX0, multi-resident cache, 640x480, cross-draw Z.
## Preflight (gs_make_sh3_multitex_fixture.py) — fail-CLOSED, PASS
- authentic dump order (A idx < B idx); same frame f1; DIFFERENT texture keys (11264 vs 9216).
- on-screen overlap: A[203..340]x[10..282] ∩ B[245..490]x[57..390] = [245..340]x[57..282].
- each texture+CLUT INDEPENDENTLY RESIDENT at its draw-time epoch (per-draw local-mem reconstruction); the two
textures differ (real multi-texture, not aliasing); the two CLUTs differ.
- CLUTs RELOCATED to distinct, non-overlapping, in-BRAM CBPs: A->CBP=480 (0x1E000), B->CBP=484 (0x1E400).
## Emit — union geometry (Codex's numbers) + two of everything + independent A->B reference
- union origin (202,9), content 289x381 -> stored FB 320x381 (FBW=5), stride 1280 B, size 0x77100, scanout 15240
beats/frame; HDMI shows all 320 cols (31 right cols precleared-black beyond the 289px content).
- two feeder lists (dump order): A (TEX0 CBP_A) 68 tris/619 words; B (TEX0 CBP_B) 68 tris/619 words; max staging addr
618 < 12-bit 4096.
- two textures de-swizzled -> LPDDR + per-texture CRC: A sum32=0x95a83cbc, B sum32=0xfbdeaa32 (=Ch354's tbp=9216).
- two relocated CLUTs; bootlet uploads BOTH (A->CBP_A, B->CBP_B); each list's scene-level TEX0 (CLD=1) selects its CBP.
- independent A->B composed reference (dump order, DECAL paint-order, B over A): 27455 covered, 9849 A&B overlap px.
## Integration sim (tb_top_psmct32_sh3_multitex) — PASS on ALL Codex gates
- TWO cache fills, each a FRESH fill_done low->high (rearm), correct beats(8192)/bytes(262144)/0 rd_errs, texture-
specific CRC: A=0x95a83cbc THEN B=0xfbdeaa32.
- NO stale-frame_drained race: scene A = fresh low->high; scene B = high->low->high (an observed FALL proves B's beats
flowed and cleared A's stale high before the fresh rise). Enforced by a fd_before-aware run_scene task.
- preclear EXACTLY once; scanout (video_src) disabled until the SECOND fresh drain.
- records_emitted = 68 per list; writer ovf/BRESP = 0; scanout 15240 beats/frame EXACT; underflow=0.
- final FB scored vs the independent A->B reference: OVERLAP(A&B) COMPOSITE = 96.7% <=1-texel (gated >=95%) — proves B
(tex B + relocated CLUT B) correctly overwrote A. clut_bad=0 (every covered px in palette A or B).
## Two SEPARATE claims (as Ch354)
- CLAIM 1 multi-texture accumulation/composite: PROVEN — overlap composite 96.7% >=95%, clut_bad=0, cache rearmed with
correct per-texture CRCs, anti-stale drains, exact scanout beats.
- CLAIM 2 fidelity: ALL=69.3% (REPORTED, not gated) — dragged by the A-only region = draw 19562's OWN per-draw
perspective-sampling fidelity (the parked limit; B/89761 renders fine, hence the 96.7% B-dominated overlap). Whether
to independently verify 19562-alone is an open question for Codex.
## Remaining before board fit (Codex: no fit until runtime staging also passes)
- Host two-group RUNTIME-STAGING flow: preclear once; upload+fill+CRC tex A; STREAM list A (report words/records, 12-bit
bound); GO A + observe fresh drain; REFILL+CRC tex B; STREAM list B; GO B + observe high->low->high; video_src after
the 2nd drain. Then board fit (owner-gated).
## Codex round 2 — NEGATIVE-Q root cause + exact canonicalization (RESOLVED)
The combined ALL=69.3% was masking a real defect, NOT the parked perspective limit. Diagnostic (ONLY=A/B/AB +
FBDUMP + compose_mt.py):
- A→B RTL composition == combined AB, 0/121920 px BIT-EXACT (accumulation/order correct).
- CLUT relocation CORRECT: A rendered 100% in palette A, B 100% in palette B (palA ∩ palB = ∅).
- ONLY=B (89761, positive Q) = 95.6%; ONLY=A (19562) = 27.4% with a CORRECT CLUT.
- ROOT CAUSE: idx19562 has NEGATIVE q/s/t (q[-0.00039..-0.00024]); 89761 positive. Codex confirmed the RTL
perspective reciprocal input/products are UNSIGNED -> negative Q is unsupported (not merely imprecise).
FIX (Codex) — exact host-side homogeneous-coordinate canonicalization in the fixture tool, BEFORE fixed-point pack:
per triangle, all-Q-positive unchanged; all-Q-negative -> negate every vertex's S,T,Q (EXACT: (-s)/(-q)=s/q, and the
negation of a linear attribute interpolates identically); mixed-sign/zero Q -> FAIL CLOSED. Overflow-checked.
- SYNTHETIC GATE: a positive-Q triangle and its all-negated twin pack BIT-IDENTICALLY (self-test PASS).
- 19562-alone re-rendered vs the UNCHANGED reference: 27.4% -> 93.0%, clut_bad=0 (oracle fixed to not count
RTL-unwritten coverage-edge pixels as clut_bad).
- A→B composition re-run: still 0/121920 BIT-EXACT.
- Default AB acceptance: ALL 69.3% -> 95.1%, OVERLAP composite 96.4% (>=95%), clut_bad=0, 15240 beats, PASS.
RECORDED: the feeder CANONICALIZES homogeneous coordinates to positive Q. This is NOT native signed-Q hardware
support; a true signed reciprocal path remains a later platform extension for mixed-sign triangles.
## Ch355 Brick 1 status: integration PROVEN. Remaining before fit = host two-group runtime-staging flow.
## Board prep (Ch355 multi-texture) — verified in sim, owner-gated for the fit
- Board top: `GS_SH3_LPDDR_FB_MULTITEX` overrides SCANOUT_LB to STRIDE=1280/ROW_BEATS=40/NROWS=381 (320x381, FBW=5) and
FEEDER_STG_WORDS->2048; everything else shared with GS_SH3_LPDDR_FB. Elaborates clean (exit 0) under
GS_SH3_LPDDR_FB + GS_SH3_LPDDR_FB_MULTITEX + GS_LPDDR4B_FB + GS_LPDDR_TEX.
- FB 320x381, stride 1280, size 0x77100, scanout 15240 beats/frame.
- Bootlet (bios/payload_sh3_mt) uploads BOTH relocated CLUTs (CBP_A=480, CBP_B=484). RUNTIME staging -> NO
FEEDER_STG_INIT_FILE (feeder starts empty; the host streams each per-scene list).
- Host `tools/ps2_sh3_multitex.c` (the two-group runtime flow): wait feeder ready -> FB base 0 -> preclear ONCE
(121920 words, 0..0x77100) -> fill+CRC tex A (fresh fill_done, crc 0x95a83cbc, 8192 beats/262144 bytes/0 rd_errs) ->
STREAM list A (reset 0x0D8, per-word LO 0x0DC/HI 0x0E4; report words/records + 12-bit bound) -> arm writer, GO A,
fresh drain (0x02C[6]) -> REFILL+CRC tex B (0xfbdeaa32) -> STREAM list B -> GO B, frame_drained HIGH->LOW->HIGH ->
video_src after the 2nd fresh drain. Compiles -Wall clean; dry-run parses texA/texB CRCs + 68 tris/619 words each.
- Build profile `sh3_lpddr_fb_multitex` (select_de25_profile.sh + QSF): SCENE GS_SH3_LPDDR_FB + GS_SH3_LPDDR_FB_MULTITEX
+ GS_LPDDR_TEX (no bitstream feeder init); FIXTURE bios/payload_sh3_mt. Ran profile-select + verified the QSF.
- BOARD RUN: `sudo ./ps2_sh3_multitex sh3_mtA_tex_lpddr.mem sh3_mtB_tex_lpddr.mem feeder_sh3_mtA.mem feeder_sh3_mtB.mem`.
## Codex round 3 — bridge-streaming sim test (closes the gap that hid the board bug)
BOARD RUN #1 rendered 0 beats: the host wrote 0x0D8=1 (SET staging addr) instead of 0, so word0/header landed at
slot 1 -> feeder read ntris=0. Root cause is RTL: ps2_hps_bridge.sv:1171 `feeder_addr_q <= wdata_lane` (0x0D8 W SETS
the address to the written value). Host fixed to write 0 (matches ps2_feeder.c); BOARD RUN #2 = full success.
The gap: BOTH the multi-texture TB and tb_top_psmct32_feeder_bridge_demo drove feeder_stg via $readmemh / direct
waddr — neither exercised the real 0x0D8/0x0DC/0x0E4 protocol.
Closed in tb_ps2_hps_bridge (joins the full regression): a model_stg captures the bridge's commit strobe
(feeder_stg_we_tgl_o + waddr/wdata), and the test stages TWO complete lists EXCLUSIVELY via bridge register writes:
- Scene A (10 words): 0x0D8=0, per-word 0x0DC(lo)/0x0E4(hi) -> every word checked at its slot, ESPECIALLY word0;
final 0x0DC readback == nwords.
- NEGATIVE test: 0x0D8=1 -> word0 mis-slots to slot 1 (reproduces the exact board failure; pins the addr-set semantics).
- Scene B (6 words, different): re-stage after 0x0D8=0 (A->B, no $readmemh) -> every word + word0 + final addr.
- records_emitted readback (0x0E4 R) == 68.
tb_ps2_hps_bridge PASS. This exercises the exact host<->bridge address path that failed on silicon; the render/drain
per scene is silicon-verified (BOARD RUN #2) + covered by tb_top_psmct32_sh3_multitex.
## Ch355 Brick 1: BANKED — multi-texture composition on silicon + the runtime-staging gap closed.
+81
View File
@@ -0,0 +1,81 @@
# 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).
## Copyright — all SH3-derived data LOCAL/gitignored
- 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).
+59
View File
@@ -0,0 +1,59 @@
# Ch357 audit log — NATIVE 640×480 LPDDR framebuffer
Codex rung after Ch356. The Ch356 N-texture scheduler on a **native 640×480** LPDDR framebuffer. Draws land at their
**authentic screen coordinates** (the Ch356 union-origin translation is removed). The scheduler and host are **unchanged**
from Ch356 — only the framebuffer geometry differs. Same 3 epochs (dump order): E0=idx11671, E1=idx19562, E2=idx89761.
## Geometry (Codex's spec — all gated in the fixture generator, fail-closed)
- FBW=10, stride **2560 B**, FB size **0x12C000** (1,228,800 B), **80 beats/row**, **38,400 beats/frame**.
- authentic coords (no union translation): draws span x[170..490] y[9..390] — fit inside 640×480 (gated: fail-closed if any vertex escapes).
- FB region **[0, 0x12C000) disjoint** from the texture region **[0x200000, 0x240000)** (gated).
- texture kept at 0x200000 (single-region cache), unchanged from Ch356.
## Tooling — `gs_make_sh3_scheduler_fixture.py --fb640 --emit`
- new `--fb640` mode: native 640×480, OX=OY=0 (no translation), geometry/fit/disjoint gates, distinct fixture prefix
`sh3_s640` so Ch356 (`sh3_sched`) and Ch357 (`sh3_s640`) fixtures coexist. Ch356 output byte-identical (verified).
- emits: feeder_sh3_s640{k}.mem, sh3_s640{k}_tex_lpddr/idx/pal/refmap.mem, composed sh3_s640_refmap.mem, bootlet
bios/payload_sh3_s640 (preloads all N relocated CLUTs), sh3_s640_params.vh, descriptor table sh3_s640_epochs.txt.
- composed-reference pixel counts (42414 covered / 6124 multi-epoch) MATCH Ch356 exactly — translation-invariant, a good cross-check.
- Q-canonicalization self-test PASS.
## Integration sim (tb_top_psmct32_sh3_sched640) — PASS
- preclear once; 3 FRESH cache fills (CRC 0x26a88b77/0x95a83cbc/0xfbdeaa32, beats=8192, rd_errs=0); 3 lists STREAMED via
the feeder write port (word0=68 each); 3 FRESH ORDERED drains (E0 low→high; E1/E2 high→low→high); records=68 each; ovf=0.
- scanout held off until the last drain; **scanout beats/frame = 38400 EXACT** (the PASS verdict gates on this `$error` check);
underflow=0, rd_errs=0; full 640×480 frame scored.
- oracle: ALL=94.1% ≤1texel, MULTI(≥2)=92.7%, **clut_bad=0** (reciprocal-limited fidelity, same as Ch356; not accumulation).
## Accumulation correctness — 100% BIT-FOR-BIT on the FULL 640×480 frame (`make tb_top_psmct32_sh3_sched640_compose`)
- render ALL + each epoch in isolation (+ONLY=k +FBDUMP), composite (nonzero-wins DECAL, compose_sched.py --width 640),
compare to the joint ALL render.
- **307200/307200 px, 0 mismatch — 100.0000% BIT-FOR-BIT.** Full-frame reference; accumulation exact on the native FB.
## Host — UNCHANGED (proves "scheduler unchanged")
- the SAME `ps2_sh3_sched` binary drives the 640 descriptor table: reads `fbwords` from the META line (307200), preclears
0..0x12C000, iterates the same per-epoch flow. Dry-run green (all 3 CRCs, 619 words each, rc=0).
- BOARD RUN: `sudo ./ps2_sh3_sched sh3_s640_epochs.txt`.
## Board prep — fit-ready, owner-gated
- de25 top: new `GS_SH3_LPDDR_FB_640` branch → SCANOUT_LB 2560/80/480 + FEEDER_STG_WORDS=2048. Elaborates clean under
GS_SH3_LPDDR_FB+_640+GS_LPDDR4B_FB+GS_LPDDR_TEX+USE_QSYS_TOP (only the known Quartus-only EMIF_Qsys unresolved in iverilog).
- profile `sh3_lpddr_fb_640`: fixtures bios/payload_sh3_s640; macros GS_SH3_LPDDR_FB + GS_SH3_LPDDR_FB_640 + GS_LPDDR_TEX.
QSF verified (macros + fixtures applied; no stale SCHED/MULTITEX). NO FEEDER_STG_INIT_FILE (runtime-staged).
## Copyright
- `sh3_s640_epochs.txt` added to the exact-path ignore rule; all `sh3_s640*` fixtures + build FB dumps gitignored; only own
code (TB, generator, host, compose) trackable.
## Regression — strict tally (full `make run`)
- `make` exit **0**; completion marker `=== all testbenches complete ===` present.
- **272 / 272** run-list TBs → PASS verdict (the 7 "missing" are name-aliases; their `_stub` targets confirmed PASS).
- **0** `] FAIL` verdicts (the 2 `FAIL-pix` hits are per-pixel diagnostic fields; both TBs verdict PASS, errors=0).
- **0** `$fatal` / assertion failures. The lone `errors=128` is the pre-existing `tb_gs_lpddr_scanout_lb` internal diagnostic
counter (unmodified by Ch357; TB verdicts PASS). The many "error" grep hits are benign iverilog lint (`$error` un-synthesizable).
- Identical tally to the Ch356 regression → Ch357 introduced **zero regressions**.
## Claim
- Ch356 scheduler on a **native 640×480 framebuffer with authentic screen coordinates**. Accumulation 100% bit-for-bit on the
full frame; exact 38400 scanout beats; FB/texture ranges disjoint. Fidelity ~9294% (reciprocal floor, unchanged). Census
Z usage next; no speculative Z.
+59
View File
@@ -0,0 +1,59 @@
# Ch357 ZSCHED integer-XY closeout
This note pins the persistent-Z ZSCHED signoff contract after the float-oracle mismatch was isolated.
## Contract
- The RTL renderer consumes integer screen coordinates from XYZ2. `gs_stub.sv` takes the integer X/Y fields and has no GS 12.4 subpixel raster path in this milestone.
- The ZSCHED fixture generator also emits integer XYZ2 coordinates via `quant_xy` before packing vertices. The subpixel geometry from the dump is not present in the feeder data.
- Therefore the signoff reference maps must use the same quantized XY vertices. For ZSCHED, `gs_make_sh3_scheduler_fixture.py` defaults `--authz --tag zsched` to integer-XY reference. Use `--float-ref-xy` only as a report-only fidelity-debt diagnostic.
Canonical fixture emit:
```sh
make -C sim sh3_zsched_fixture
```
Equivalent explicit command:
```sh
python3 tools/gs_make_sh3_scheduler_fixture.py \
--draw-list 8634,12757,145742 --authz --tag zsched \
--pscale auto,auto,384 --xy-quant round,round,round --ref-xy-quant --emit
```
## Current Gates
- `tb_top_psmct32_sh3_zsched` gates the integer-XY oracle at the documented 90% MULTI <=1 texel reciprocal floor.
- `tb_top_psmct32_sh3_zrop` replays the captured fragment trace through the clamp16 persistent-Z scoreboard.
- `tb_top_psmct32_sh3_zint` drives the real raster into the persistent-Z/color LPDDR path and gates zero fragment drops.
- `sh3_zsched_board_compare` regenerates the fragment trace and zint framebuffer dump, requires the pulled board framebuffer to match zint byte-for-byte, then replays emitted fragment colors through clamp16 GEQUAL and requires exact framebuffer color agreement.
- These three targets are now prerequisites of the default `make -C sim run`.
Measured integer-XY fixture result: ALL=20227/21645 (93.4%), MULTI=7111/7587 (93.7%), with `tb_top_psmct32_sh3_zsched` PASS. The old float-XY diagnostic stayed below the 90% MULTI floor because it compared against geometry that the hardware was not fed.
Measured board readback result from the refreshed fixture: `sh3_zsched_board_fb.mem` and `sh3_zsched_zint_fb.mem` both hash to `acd4076dfe3843a384f8fa885613484c5e62a9bbcbb5dff4d5bf78b168a22e2f`; `cmp` reports no byte differences. `--owner replay-color` also reports `53760/53760 (100.00%)` exact for both board and zint, with 50184 replayed fragments, 29937 Z-pass updates, and 21590 covered pixels.
## Board Readback
After a board dump:
```sh
sudo ./ps2_sh3_sched --zbuf sh3_zsched_epochs.txt --dump-fb sh3_zsched_board_fb.mem
scp terasic@192.168.50.161:~/sh3_zsched_board_fb.mem sim/data/top_psmct32_raster_demo/
python3 tools/gs_fb_to_png.py sim/data/top_psmct32_raster_demo/sh3_zsched_board_fb.mem \
sim/data/top_psmct32_raster_demo/sh3_zsched_board_fb.png 256 210 3
make -C sim sh3_zsched_board_compare
```
Optional oracle diagnostic maps:
```sh
make -C sim tb_top_psmct32_sh3_zsched
python3 tools/analyze_zsched_fb.py sim/data/top_psmct32_raster_demo/sh3_zsched_board_fb.mem \
--owner replay-color --frags sim/traces/rtl/zsched_frags.txt
python3 tools/analyze_zsched_fb.py sim/data/top_psmct32_raster_demo/sh3_zsched_board_fb.mem \
--owner replay --frags sim/traces/rtl/zsched_frags.txt --maps
```
The `--owner replay` texture/refmap radius score and `sh3_zsched_board_fb_*oracle_*.png` images are diagnostic only. The hard hardware-vs-RTL readback gate is `make -C sim sh3_zsched_board_compare`; the hard emitted-fragment content check is `--owner replay-color`.
+10
View File
@@ -0,0 +1,10 @@
CODEX INDEPENDENT VERIFICATION + CLOSE (2026-07-10): Codex re-verified the artifacts himself — RBF sha 9e9c5910
matches board provenance; board FB and fresh zint640 FB share sha256 fd21e43f (cmp clean); replay-color
307200/307200, 0 mismatches, 0 stray. "Call it closed." TWO WORDING CORRECTIONS (precision): hold slack is
exactly 0.000 (met, NOT positive margin); "first-of-its-kind" is a project claim the local evidence cannot
prove — state the verified fact instead: native 640x480 persistent-Z for THIS three-draw strong-reject scene
on silicon, with scanout/texture/writer/depth paths all participating in the proof. NEXT CHAPTER (Codex):
draw-count scaling BEFORE motion — densify toward an SH3 texture cluster while preserving the board-vs-zint
gate.
+64
View File
@@ -0,0 +1,64 @@
# Ch359 audit log — zs640c6: shared-texture RESIDENCY, 2x204-tri lists, native 640x480 persistent Z
Codex-locked chapter: six SH3 draws sharing ONE PSMT8 texture (tbp=8192/cbp=13888), grouped into TWO ordered
204-triangle feeder lists (1843 words each), authentic GEQUAL/ZMSK=0/PSMZ16S, native 640x480. NEW capability:
EXPLICIT texture-cache residency — one upload+fill (epoch 0); epoch 1 carries reuse=1 in the descriptor and runs
on the resident cache. 408 tris vs Ch358's 122 (3.3x density).
## Fixture/host/TB stack
- Generator --group-size (per-member strip expansion, no phantom bridging tris); shared-texture fail-closed gates
(tex idxw + CLUT BYTE-identical across draws; list-local state equality; ZTE=1/ZTST=GEQUAL/ZMSK=0/PSMZ16S decode;
native bounds; <=2048 words); ONE relocated CLUT; reuse in descs/params/epochs.txt. EMIT: 2x204/1843 EXACT
(Codex's numbers to the digit), crc 0xcf4cae27, ref 21071 covered / 6129 multi px.
- Census lesson: strip draws' triangles = nprim-2 (my verts//3 probe was wrong; proven vs zsched 46/58/18).
- Host: optional trailing reuse column (back-compat verified); reuse epoch = no tex file, no upload/fill,
fail-closed resident CRC/beats register verify. Dry-run rc=0 (zs640c6 AND legacy tables).
- TBs zs640c6_cap / zint640c6: 2-epoch arrays + EP_REUSE, reuse-epoch residency assert (fill_crc register),
fills==1 hard assert. LESSON: per-scene TB wait budgets scale with tris/epoch (204-tri epochs blew the
zs640-calibrated 800k-cycle budgets -> records=172/204 cut-off, cascading counter errors + phantom 65% fidelity).
## THE FINDING — cold-Z burst vs request FIFO (and the Ch359 RTL change)
- zint640c6 @ REQ_DEPTH=256 (Ch358 silicon config): **424 fragment drops, ALL epoch 0** (empty-Z phase: every
z_rmw miss = dirty evict = 2x AXI), zero in epoch 1 (Z-reject heavy = clean misses). Scoreboard mismatches
(Z 320 / COLOR 316) == drop effects. High-water probe (Codex-corrected: write-domain wbin - gray2bin(rgray_s2),
parameterized modulus) saturated 256/256.
- Codex-authorized sim-only sweep: depth 512 -> zero drops, ep0 peak **478** (34 slots margin), scoreboard EXACT;
depth 1024 -> identical (bounded deterministic burst, not rate-bound).
- CODEX DECISION: **REQ_DEPTH=1024** (2.1x headroom for the next density rung; no Z-RMW/scene/SDC/clock change).
Changed: de25 top u_zc_emit + the 3 production-equivalent zint TBs; unit/replay depths (16/32) untouched.
## Verification at depth 1024 (2026-07-10)
- Parse audit (QSF file list, exact macro set): clean (only pll/EMIF_Qsys/qsys_top IP).
- zc_emit unit PASS; zrop PASS (zsched 93.4/93.7); baseline zint PASS (Z 0/53760, drops=0, golden-identical);
zint640 PASS (Z 0/307200, golden-identical); **zint640c6 PASS (ZERO drops, Z 0/307200 + COLOR 0/21032 exact,
204/204 records, ONE fill + REUSE verified)**; zs640c6_cap PASS (MULTI=90.2% >= 90 floor, ALL=85.3%
scene-dependent reciprocal floor, clut_bad=0 — flagged: floor margin is thin for future regenerations).
- Full regression: **282/282 PASS, 0 FAIL** (280 baseline + the two C6 gates now in the run list).
## Board Proof And Provenance Correction
- The first depth-1024 GUI fit met timing, and its board host run passed every transport/runtime gate: 204/204 records
in both epochs, one fill plus verified reuse, and zero drops. The board framebuffer nevertheless differed from zint
at all 21032 covered pixels while retaining the exact C6 bounds=(84,101)..(340,301). This isolated the failure to
color provenance, not geometry, persistent Z, LPDDR transport, or FIFO depth.
- Root cause, verified in that fit's synthesis report: the QSF still selected Ch358 `bios_sh3_zs640.mem` and
`payload_sh3_zs640.mem`, so the board booted the old CLUT rather than C6's CBP=13888 palette. The C6 fixture and
host files were correct; the RBF's boot-image inputs were not.
- Corrected fit inputs: `sh3_lpddr_fb_z640c6` now selects `bios_sh3_zs640c6.mem` and
`payload_sh3_zs640c6.mem` through `scripts/select_de25_profile.sh`. The REQ_DEPTH=1024 top change remains part of
the same compile. No SDC, clock, or Z-RMW change is needed.
## Handoff
Owner: run one full 26.1 GUI compile with the corrected C6 profile, then check STA before loading the RBF. Only after
timing is clean: run `scp_zs640c6_to_board.sh` (five files including the reuse-modified `ps2_sh3_sched.c`, recompile
on board), then `sudo ./ps2_sh3_sched --zbuf sh3_zs640c6_epochs.txt --dump-fb sh3_zs640c6_board_fb.mem`. Expect
`bounds=(84,101)..(340,301)`, retrieve the dump, and run `make -C sim sh3_zs640c6_board_compare`.
## Closure (2026-07-10)
- Quartus 26.1 GUI RBF `ad4e8c9627520dcaba9babf2019ca5b28892799e0500f8f3300f4f2a29b5218c` compiled the corrected
`bios_sh3_zs640c6.mem` and `payload_sh3_zs640c6.mem` inputs. STA met: setup +0.095 ns, hold 0.000 ns, recovery
+1.042 ns, removal +0.185 ns, minimum pulse width +0.200 ns.
- On silicon: both 204-record epochs drained; epoch 0 uploaded/filled the texture and epoch 1 verified resident reuse;
zero fragment drops; output bounds `(84,101)..(340,301)`; 21032 nonzero pixels.
- `make -C sim sh3_zs640c6_board_compare` passed. The board and zint framebuffers had identical SHA-256
`948d9c45df84ccf180faebe7b24e3e4445bcce390c8bfab91fb3b50885fcaa4`, and replay-color was exact at
307200/307200 pixels. Ch359 is closed.
+48
View File
@@ -0,0 +1,48 @@
# Ch360 audit log - zs640c12: 12-draw shared-texture residency at native 640x480 persistent Z
## Scope
Scale Ch359's six-draw shared-texture proof to twelve authentic SH3 draws without increasing feeder-list size:
`119471,119684,119897 / 120110,120323,120536 / 120749,120962,121175 / 121388,121601,121814`
All draws are PSMT8 `tbp=8192`, share the byte-identical resident CLUT at `cbp=13888`, have identical feeder-visible
state, and use authentic `ZTE=1`, `GEQUAL`, `ZMSK=0`, `PSMZ16S`. They form four ordered 204-triangle / 1843-word
epochs: one LPDDR texture upload/fill followed by three fail-closed cache reuses.
## Fixture And Fidelity Decision
- The generator preflight passed native 640x480 bounds, signed-Q legality, state equality, shared texture/CLUT bytes,
and all four feeder-list capacities.
- Per-triangle auto PSCALE failed the content fidelity gate: isolated epochs 2/3 were 79.4%/81.5% and the full
multi-epoch score was 83.6%.
- Fixed `PSCALE=1024` for every C12 epoch corrected that host-fixture range choice without RTL: isolated epochs 2/3
reached 92.0%/91.1%, while the full capture gate reached ALL=93.4% and MULTI=95.0%, with `clut_bad=0`.
- C12's BIOS and payload comment lines differ from C6, but their non-comment memory words hash identically. The
existing Ch359 signoff RBF therefore already contains the correct C12 bootlet contents; no QSF change or GUI fit.
## Simulation Proof
- Capture/residency: four `204/204` records, one fill, three verified reuses, four fresh drains, no writer overflow,
exact 38400 scanout beats/frame.
- Real-raster Z integration at `REQ_DEPTH=1024`: 151160 accepted fragments, 70796 Z passes, zero drops, four ordered
scene markers/drains, and exact scoreboards (Z 0/307200, COLOR 0/36022).
- Request-FIFO high-water by epoch: 478, 27, 416, 225. The 1024-depth production FIFO remains sufficient; no depth,
Z-RMW, LPDDR, clock, SDC, or RTL change is warranted.
## Board Closure (2026-07-10)
- The Ch359 signoff RBF `ad4e8c9627520dcaba9babf2019ca5b28892799e0500f8f3300f4f2a29b5218c` remained loaded and was
provenance-valid for C12 because the bootlet memory words are identical.
- Board host gates: four `204/204` drains, one fill plus three verified reuses, zero drops, 27041 framebuffer beats,
and output bounds `(84,94)..(367,385)` with 36022 nonzero pixels.
- Board and zint framebuffer SHA-256 matched exactly:
`1309fa4f307b9313231797c75973e5a42108460c19ec0a0c576d4da597d6303e`.
- Replay-color: 151160 fragments, 70796 persistent-Z updates, 36022 covered pixels, no stray coverage, and
`307200/307200` exact framebuffer pixels. Ch360 is closed.
## Next Direction
Keep draw-count scaling before motion, but first turn the C12 four-epoch test copies into one parameterized
shared-texture residency harness. The next density rung should then be selected from the same census and must retain
the fixed-1024 fixture contract unless a measured sweep proves another scale better for the chosen draws.
+18
View File
@@ -0,0 +1,18 @@
# Ch361 audit log -- C18 draw-count scaling
## Scope
Eighteen authentic, in-bounds SH3 PSMT8 draws were grouped into six ordered 204-triangle epochs at native 640x480. They share `tbp=8192`, `cbp=13888`, byte-identical texture/CLUT contents, and authentic `ZTE=1`, `GEQUAL`, `ZMSK=0`, `PSMZ16S` state.
## Evidence
- Fixture preflight passed; fixed `PSCALE=1024` and rounded XY were retained.
- Capture gate passed: ALL `44890/49149` (91.3%), MULTI `24969/26680` (93.6%), `clut_bad=0`, one fill plus five reuses, six fresh drains, no overflow or underflow.
- Real Z integration passed: 205744 fragments, 103300 Z passes, zero drops, exact Z `0/307200` and color `0/49071`.
- Board host gate passed: six 204-record epochs, one fill, five resident reuses, zero drops, 49071 nonzero pixels.
- Board framebuffer and Z integration framebuffer SHA-256: `a0eda56ef0140749451078a0fc82d85ea6e3342734dacdc22dd93f0a56dbb539`.
- Replay-color is exact: `307200/307200`; 205744 fragments, 103300 Z-pass updates, 49071 covered pixels.
## Result
Ch361 is closed on silicon with no RTL, SDC, QSF, or RBF change from the C6-signoff core.
+19
View File
@@ -0,0 +1,19 @@
# Ch362 audit log -- C24 draw-count scaling
## Scope
Twenty-four authentic SH3 draws were grouped into eight ordered 204-triangle epochs at native 640x480. The original next candidate `idx131306` was rejected before emission because its authentic Y extent reached 610; `idx132158` replaced it. The retained list stays in-bounds (`x=82..569`, `y=84..400`) and shares the C18 texture, CLUT, and authentic depth state.
## Evidence
- Fixture preflight passed with one shared texture/CLUT, fixed `PSCALE=1024`, and rounded XY.
- Capture gate passed: ALL `69277/73473` (94.3%), MULTI `34636/36352` (95.3%), `clut_bad=0`, one fill plus seven reuses, eight fresh drains, no overflow or underflow.
- Real Z integration passed: 346425 fragments, 167247 Z passes, zero drops, exact Z `0/307200` and color `0/73330`.
- New epoch request highs: epoch 6 = 330, epoch 7 = 234; both are bounded below the 1024-entry request FIFO.
- Board host gate passed: eight 204-record epochs, one fill, seven resident reuses, zero drops, 73330 nonzero pixels.
- Board framebuffer and Z integration framebuffer SHA-256: `6614d7211b9a668cfeda12e4fa2d6f469d3ee4d3a10bdf56c0119b35f93c7803`.
- Replay-color is exact: `307200/307200`; 346425 fragments, 167247 Z-pass updates, 73330 covered pixels.
## Result
Ch362 is closed on silicon with no RTL, SDC, QSF, or RBF change from the C6-signoff core.
+35
View File
@@ -0,0 +1,35 @@
# Ch363 Audit Log - Five-Texture Persistent-Z Scheduler
## Scope
- Native 640x480 persistent-Z scene with five ordered authentic SH3 draws:
`5328,11671,77112,89164,119471`.
- Five distinct texture/CLUT bindings, relocated at CBP `480,484,488,492,496`.
- The cache is intentionally refilled for every epoch; this chapter proves rebinds rather than shared-texture residency.
## Root Cause And Fix
- The generated five-CLUT bootlet is 350 qwords. The shared simulation harness had inherited the 256-qword EE RAM default, so `$readmemh` truncated the payload. Epoch 3 consequently read partial CLUT data and produced 195 palette-invalid pixels.
- The production `GS_SH3_LPDDR_FB` board profile already allocates 32 KiB of EE RAM. Both shared 640 harnesses now explicitly use that same `RAM_SIZE_BYTES(32 * 1024)` capacity.
- The earlier CLAMP investigation was rejected by trace evidence: all sampled epoch-2/3/4 coordinates were within the 512x512 texture bounds. No feeder or sampler RTL change is retained.
## Simulation Gates
- `tb_top_psmct32_sh3_zs640mt5_cap`: PASS.
- Five texture CRC fills: `55070afa`, `26a88b77`, `a727234d`, `fbdeaa32`, `cf4cae27`.
- Five fresh drains, records `54,68,68,68,68`, no writer overflow or scanout underflow.
- Scanout: exactly 38,400 beats/frame.
- Oracle: ALL `61595/63749` (96.6%), MULTI `7701/7877` (97.8%), `clut_bad=0`.
- `tb_top_psmct32_sh3_zint640mt5`: PASS.
- Z preclear: 19,200 beats.
- 129,752 fragments fed, 118,355 Z passes, zero fragment drops.
- Exact final scoreboards: Z `0/307200` mismatch; COLOR `0/63617` mismatch.
- Request FIFO depth 1024 high-water by epoch: `415,401,64,222,47`.
## GUI Fit And Board Closure
- The authoritative QSF points to `bios_sh3_zs640mt5.mem` and `payload_sh3_zs640mt5.mem`; the Quartus 26.1 flow report and synthesis report both resolve those exact images.
- The July 11 GUI compile is signoff-clean: setup `+0.095 ns`, hold `0.000 ns`, recovery `+1.042 ns`, removal `+0.185 ns`, and minimum pulse width `+0.200 ns`. The generated `retroDE_ps2.core.rbf` was written after the QSF update.
- The RBF loaded successfully on the DE25 (`fpga0: operating`). The five-epoch host run passed all transfer, CRC, preclear, drain, and zero-drop gates. Its final framebuffer summary was `nonzero=63617`, bounds `(36,10)..(562,360)`.
- The board framebuffer and a fresh `tb_top_psmct32_sh3_zint640mt5` dump have identical SHA-256: `9e87678c0c88e3e2e90a187e69991069d58e88187d360d560df59f25219b99c5`.
- Independent replay-color verification is exact: `307200/307200` pixels, zero mismatches. Ch363 is closed: five authentic SH3 draws, five texture/CLUT rebinds, native 640x480 scanout, and persistent-Z are proven on silicon for this fixture.
+44
View File
@@ -0,0 +1,44 @@
# Ch364 Audit Log - Mixed Texture Rebind And Residency
## Scope
- Native 640x480 persistent-Z composite of 28 ordered authentic SH3 draws.
- Four early single-draw texture/CLUT rebind epochs (`5328`, `11671`, `77112`, `89164`) followed by the 24-draw
`tbp=8192` cluster in eight 204-triangle feeder lists.
- Twelve epochs total: five upload/fill operations and seven consecutive, fail-closed verified reuses of the final
resident texture.
## Fixture Contract
- `gs_make_sh3_scheduler_fixture.py` now accepts `--group-sizes`, an explicit ordered epoch partition. Every
multi-draw group must have identical feeder-visible state and byte-identical texture/CLUT data; reuse is emitted
only when the immediately preceding descriptor leaves those same bytes resident.
- The selected partition is `1,1,1,1,3,3,3,3,3,3,3,3`. It passed authentic ordering, native bounds, state,
texture/CLUT, staging-capacity, and depth-state gates.
- The Ch364 BIOS and payload memory words hash identically to Ch363 MT5. The existing GUI-fitted RBF is therefore
provenance-valid; no RTL, QSF, SDC, GUI compile, or RBF reload was required.
## Simulation Proof
- Capture/residency: PASS. Five CRC-verified fills, seven verified reuses, 12 ordered fresh drains, no writer
overflow or scanout underflow, and exactly 38,400 scanout beats/frame.
- Texture oracle: ALL `105761/110544` (95.7%), MULTI `40756/42861` (95.1%), `clut_bad=0`.
- Real Z integration: PASS. 465,250 fragments fed, 214,897 persistent-Z updates, zero drops, 12 markers/drains,
exact Z `0/307200`, and exact color `0/110324`.
- Request FIFO high-water by epoch: `415,401,64,222,341,21,112,166,72,176,311,240`; all remain bounded below the
1024-entry production FIFO.
## Board Closure
- Board host gate: all five texture uploads passed CRC, all seven cache reuses verified the resident CRC, all 12
drains returned their expected records, and every epoch reported zero fragment drops.
- Final board framebuffer: 110,324 nonzero pixels, bounds `(36,10)..(568,399)`, sum `0x61884052`, xor `0x802cf68e`.
- Board and zint framebuffer SHA-256 match exactly:
`fdff2f34eac22ee140b64d8fac129627549bbb0f6db37d4b91807a9058e47a04`.
- Replay-color is exact: 465,250 fragments, 214,897 Z-pass updates, 110,324 covered pixels, no stray coverage, and
`307200/307200` exact framebuffer pixels.
## Result
Ch364 is closed on silicon: 28 authentic SH3 draws, five texture/CLUT bindings, seven verified residency reuses,
native 640x480 scanout, and persistent depth all agree with integration simulation and the independent replay oracle.
+44
View File
@@ -0,0 +1,44 @@
# Ch365 Audit Log - Two-Frame Motion Sequence
## Scope
- Native 640x480 persistent-Z presentation of two adjacent authentic SH3 capture states.
- Frame A is Ch364's 28-draw mixed-rebind/residency composite (`zs640m28`); frame B is the adjacent capture's
24-draw texture cluster (`zs640b24`).
- The production bootlet preloads the union of their six relocated CLUTs at CBPs `480,484,488,492,496,500`.
- The host sequencer clears color and Z at every frame boundary, then presents the A/B tables in order.
## Compile Provenance
- The GUI build loaded `bios_sh3_zs640motionab.mem` and `payload_sh3_zs640motionab.mem`.
- Quartus reports `Timing requirements were met`: setup `+0.095 ns`, hold `0.000 ns`, recovery `+1.042 ns`,
removal `+0.185 ns`, and minimum pulse width `+0.200 ns`.
- The loaded RBF was produced after the motion bootlet and QSF inputs.
## Frame-B Simulation And Board Proof
- Capture/residency: PASS. One CRC-verified fill, seven verified reuses, eight ordered fresh drains, no writer
overflow or scanout underflow, and 38,400 scanout beats/frame.
- Texture oracle: ALL `101111/108647` (93.1%), MULTI `66968/69063` (97.0%), `clut_bad=0`.
- Persistent-Z integration: PASS. 664,865 fragments fed, 252,657 Z updates, zero drops, exact Z `0/307200`, and
exact color `0/108435`. Per-epoch request-FIFO high-water was `546,297,393,498,32,572,617,592`, below the
1024-entry production depth.
- Board host gate: all eight drains were fresh with 204 records, the one fill and all seven reuses passed CRC,
and every epoch reported zero fragment drops.
- Board framebuffer: 108,435 nonzero pixels, bounds `(12,128)..(633,469)`, sum `0xdd9faa5a`, xor `0x80e70a5e`.
- Board and integration-simulation framebuffer SHA-256 match exactly:
`3191d75983e72a158f91f15cc1f57b6ff7c5e40d5bbb4a49957db06db4e2fded`.
- Replay-color is exact: 664,865 fragments, 252,657 Z-pass updates, 108,435 covered pixels, no stray coverage,
and `307200/307200` exact framebuffer pixels.
## Two-Way Sequencer Gate
- A one-loop run established the A-to-B boundary and captured the final B framebuffer above.
- A subsequent two-loop run exercised A-to-B, B-to-A, A-to-B again. All 40 epoch drains were fresh, all expected
record counts matched, every texture fill/reuse CRC gate passed, and every fragment-drop gate remained zero.
## Result
Ch365 is closed on silicon: two authentic 640x480 persistent-Z frame states are sequenced in both directions by the
host without reconfiguration. Each endpoint has independent integration-simulation and replay-oracle framebuffer
proof, and the hardware frame-boundary protocol has passed across a full A/B/A/B loop.
+45
View File
@@ -0,0 +1,45 @@
# Ch367 Audit Log - Runtime CLUT At 24 Draws
## Scope
Join the established native-640x480 24-draw shared-texture cluster to the runtime CLUT transport. The fixture has
eight ordered 204-record epochs, one texture fill, seven CRC-verified residency reuses, and a host runtime palette
copy before every epoch.
## FIFO Sizing
- At request depth 1024, the runtime fixture removes the boot-time CLUT-loader throttle and epoch 2 dropped 19
fragments after the high-water counter saturated at 1024.
- At depth 2048, epoch 2 accepted all 58,861 fragments at high-water 1041. Integration simulation also passed
epochs 0 through 6 with zero drops; epoch 5 was the largest observed simulation burst at 1855.
- The DE25 top therefore changes only `u_zc_emit` request depth from 1024 to 2048. No clock, SDC, or protocol
change is involved; `zc_g_drops` remains the fail-closed hardware guard.
## GUI Fit And Board Gate (2026-07-12)
- Quartus 26.1 STA finished at 08:40:47 and met every requirement: setup +0.090 ns, hold 0.000 ns, recovery
+1.043 ns, removal +0.201 ns, and minimum pulse width +0.200 ns.
- The 08:41 core RBF SHA-256 is `8e3fa2b8b14de9b4660a542ec7ffed5c59353ec8583458f31339a4b4183f42d2`; fit reports
the request FIFO as a 2048 x 74 dual-clock M20K implementation.
- The RBF was SHA-verified on the DE25 and loaded through `core_loader.sh`; `fpga0` reported `operating` and the
overlay reported `applied`.
- Board scheduler gate: all eight 204-record epochs drained freshly; one texture CRC-verified fill and seven
resident CRC checks passed; every runtime palette sum was `0x47097d4c`; every epoch reported zero drops.
- Board dump: 73,330 nonzero pixels, bounds `(84,85)..(568,399)`, sum `0xc521381a`, xor `0x8021455e`.
## Exact Closure
- The trace-enabled integration run passed all eight epochs: 346,425 accepted fragments, 167,247 persistent-Z
updates, eight scene markers/drains, zero drops, and exact local scoreboards (Z `0/307200`, color `0/73330`).
- The highest simulated request-FIFO occupancy was 1855 in epoch 5; the final epoch reached 1156. Both remain below
the production 2048 entries and agree with the board's zero-drop counters.
- Board and integration-simulation framebuffers are byte-identical with SHA-256
`6614d7211b9a668cfeda12e4fa2d6f469d3ee4d3a10bdf56c0119b35f93c7803`.
- Replay-color is exact at `307200/307200` pixels: 346,425 fragments, 167,247 Z-pass updates, 73,330 covered
pixels, and zero uncovered writes.
## Result
Ch367 is closed on silicon: 24 authentic SH3 draws, runtime CLUT staging on every epoch, one texture fill plus
seven verified resident reuses, native 640x480 scanout, and persistent depth all agree with integration simulation
and the independent replay oracle.
+65
View File
@@ -0,0 +1,65 @@
# Ch368 Audit Log - Runtime CLUT Adjacent-Frame Motion
## Scope
Add the adjacent SH3 capture frame to the established 24-draw runtime-CLUT path, then prove A/B motion with the
same native-640x480 persistent-Z, board-versus-simulation, and replay-color gates used by Ch367.
## Frame-B Capacity Measurement
- Frame B is the 24-draw shared-texture cluster from capture `20260624224121`, emitted as `zsrtb24`: eight ordered
204-record epochs, one texture fill, seven CRC-verified reuses, and runtime palette staging before every epoch.
- At request depth 2048, epoch 3 saturated the request FIFO and dropped 151 fragments. This is a bounded-capacity
failure, not a board candidate.
- A complete depth-4096 integration run passed all eight epochs with exact Z and color scoreboards: Z `0/307200`,
color `0/108435`, zero drops, and 664,865 accepted fragments.
- Per-epoch request-FIFO high water: 1060, 1074, 1431, 2198, 68, 3610, 2993, and 3340. The measured maximum is
3610, leaving 486 entries of headroom at depth 4096.
## Production Change
- The DE25 top changes only `u_zc_emit` request depth from 2048 to 4096. No clock, SDC, or protocol change is
involved. The existing `zc_g_drops` counter remains the fail-closed board guard.
- Focused regressions pass after the source change: `tb_gs_lpddr_zc_emit`, `tb_clut_stage_cdc`, and
`tb_top_psmct32_runtime_clut`. The complete `zsrtb24` depth-4096 integration run is the capacity and exactness
proof for this value.
## First GUI Fit And Response-Ready Cut
- The first 4096-entry GUI fit failed setup only: WNS `-0.063 ns`, TNS `-0.126 ns`; hold and every other signoff
class remained clean. The six failing paths terminated at the EMIF write-response FIFO.
- The two launch families were the Z-RMW `bready` and write-arbiter `grant` controls, both feeding the arbiter's
live `m_bready` mux. The arbiter now registers response-ready from the final W handshake, removing that live
grant/client-ready cone while retaining the selected transaction through B acceptance.
- The response-path regression is clean: `tb_gs_lpddr_wr_arb` passes its priority/transaction checks and
`tb_gs_lpddr_zc_emit` passes three complete Z/color drains. The full `zsrtb24` depth-4096 integration gate also
passes after the cut: 664,865 accepted fragments, zero drops, Z `0/307200`, and color `0/108435`.
- At that point, the pending gate was one full Quartus 26.1 GUI compile; the board remained on the Ch367-proven RBF
until STA was clean.
## Corrective GUI Fit And Silicon Proof
- Quartus 26.1 STA completed at 15:53 with timing requirements met: setup `+0.076 ns`, hold `0.000 ns`, recovery
`+1.045 ns`, minimum pulse width `+0.200 ns`, and zero TNS. The fitted request FIFO is a 4096 x 74 dual-clock
M20K implementation using 16 M20Ks. Core SHA-256: `b146503317ba8268fee06a83b2650cdf57cb79e64da3b6b5aa6a22e9b356cf60`.
- The RBF was SHA-verified on the DE25 and loaded through `core_loader.sh`; `fpga0` reported `operating`.
- Frame B board gate passed all eight 204-record epochs: one texture fill plus seven resident CRC reuses, eight
runtime palette copies, fresh drains, and zero drops. Its board framebuffer is byte-identical to integration
simulation at SHA-256 `3191d75983e72a158f91f15cc1f57b6ff7c5e40d5bbb4a49957db06db4e2fded`; replay-color is exact
at `307200/307200` pixels.
## A/B Motion Closure
- The host ran `A -> B -> A -> B` with a fresh color/Z preclear, texture fill, runtime palette staging, and eight
ordered drains for every frame. All 32 epochs reported zero drops.
- The four board boundary dumps alternate exactly: A dumps 0 and 2 hash to
`6614d7211b9a668cfeda12e4fa2d6f469d3ee4d3a10bdf56c0119b35f93c7803`; B dumps 1 and 3 hash to
`3191d75983e72a158f91f15cc1f57b6ff7c5e40d5bbb4a49957db06db4e2fded`. Each equals its matching integration
framebuffer byte-for-byte.
- Independent replay-color checks are exact for both alternating frames: A is `307200/307200` over 346,425
fragments and B is `307200/307200` over 664,865 fragments.
## Result
Ch368 is closed on silicon: native-640x480 persistent-Z runtime-CLUT motion between two adjacent authentic SH3
captures, with deterministic A/B/A/B board outputs, exact integration framebuffers, and exact replay-color evidence.
+46
View File
@@ -0,0 +1,46 @@
# Ch369 Audit Log - Runtime CLUT Mixed-Residency Expansion
## Scope
Extend the Ch368 runtime-CLUT motion content ladder with the native-640x480 28-draw mixed-residency SH3 frame:
four distinct texture/CLUT rebinds followed by the established eight-epoch shared-texture cluster.
## Fixture Preflight
- `zsrtm28` contains 28 authentic draws in 12 ordered epochs: four single-draw rebinds, then eight 204-triangle
shared-texture epochs.
- Native coordinates remain in bounds at x `35..569`, y `9..400`; every feeder list is within the 2048-word staging
capacity.
- Runtime palette staging is enabled for every epoch. The candidate is simulation-only until request pressure and
exact Z/color scoreboards pass at the Ch368 production depth.
## Integration Gate
- The full 12-epoch runtime-CLUT integration passes at request depth 4096: 465,250 accepted fragments, 214,897
persistent-Z updates, 12 fresh drains, zero drops, Z `0/307200`, and color `0/110324`.
- Per-epoch request-FIFO high-water values are 1895, 1468, 110, 432, 797, 85, 650, 381, 184, 1669, 616, and 1125.
The measured maximum is 1895, so the Ch368 core requires no RTL, timing, or fit change for this candidate.
## Silicon Closure
- The board host gate passes all 12 epochs: four texture fills/rebinds, eight resident reuses, twelve runtime palette
copies, fresh ordered drains, and zero fragment drops.
- Board and integration framebuffers are byte-identical at SHA-256
`fdff2f34eac22ee140b64d8fac129627549bbb0f6db37d4b91807a9058e47a04`.
- Replay-color is exact at `307200/307200`: 465,250 fragments, 214,897 Z-pass updates, 110,324 covered pixels, and
no stray uncovered writes.
## Result
Ch369 is closed on silicon: 28 authentic SH3 draws at native 640x480 with runtime CLUT updates, four real texture
rebinds, persistent Z, and exact board/simulation/replay agreement, all on the Ch368 core with no further fit.
## Mixed-Residency Motion Extension
- The same core ran `zsrtm28 -> zsrtb24 -> zsrtm28 -> zsrtb24`, resetting color/Z and staging the appropriate
textures and runtime palettes for every frame boundary. All 40 epoch drains were fresh and all drop gates stayed
zero.
- The four boundary dumps alternate exactly: mixed-residency dumps 0 and 2 equal
`fdff2f34eac22ee140b64d8fac129627549bbb0f6db37d4b91807a9058e47a04`; adjacent-frame dumps 1 and 3 equal
`3191d75983e72a158f91f15cc1f57b6ff7c5e40d5bbb4a49957db06db4e2fded`. Each matches its independently closed
integration framebuffer byte-for-byte.
+40
View File
@@ -0,0 +1,40 @@
# Ch370 Audit Log - Runtime CLUT Frame C
## Scope
Convert the established 24-draw Frame C cluster to host-staged runtime CLUT updates, retaining its distinct relocated
CBP `504` and proving it on the fitted Ch368 depth-4096 core.
## Fixture and Integration
- `zsrtc24` contains eight ordered 204-triangle epochs from the authentic Frame C draw set. Epoch 0 fills texture CRC
`0xcf4cae27`; epochs 1 through 7 verify that CRC as resident texture reuses. Every epoch stages its palette through
the runtime HPS path.
- Native geometry stays within x `35..569`, y `9..400`; each feeder list is 1,843 words, below the 2,048-word
staging capacity.
- The depth-4096 traced integration gate passes: 346,425 accepted fragments, 167,247 persistent-Z updates, eight
fresh drains, zero drops, Z `0/307200`, and color `0/73330`. Per-epoch request FIFO high-water values are 948,
131, 1041, 374, 377, 1855, 606, and 1156. The maximum 1855 stays well below the fitted 4096-entry depth.
## Silicon Closure
- The board host run completed all eight runtime palette copies, one texture fill, seven resident-CRC reuses, fresh
ordered drains, and zero fragment drops.
- Board and traced-integration framebuffers are byte-identical at SHA-256
`6614d7211b9a668cfeda12e4fa2d6f469d3ee4d3a10bdf56c0119b35f93c7803`.
- Replay-color is exact at `307200/307200`: 346,425 fragments, 167,247 Z-pass updates, 73,330 covered pixels, and
no uncovered writes.
## Result
Ch370 closes Frame C on silicon with runtime CLUT staging and persistent Z. No RTL, constraint, or Quartus fit change
was required; the current Ch368 core carries this third runtime scene within its measured request-FIFO envelope.
## Three-State Motion Extension
- The same loaded core ran `zsrtm28 -> zsrtc24 -> zsrtb24 -> zsrtm28 -> zsrtc24 -> zsrtb24`, with color/Z reset and
the appropriate texture and runtime palette staged at every frame boundary.
- The six boundary dumps match the independently closed integration framebuffers exactly: M28 dumps 0 and 3 hash to
`fdff2f34eac22ee140b64d8fac129627549bbb0f6db37d4b91807a9058e47a04`; Frame C dumps 1 and 4 hash to
`6614d7211b9a668cfeda12e4fa2d6f469d3ee4d3a10bdf56c0119b35f93c7803`; Frame B dumps 2 and 5 hash to
`3191d75983e72a158f91f15cc1f57b6ff7c5e40d5bbb4a49957db06db4e2fded`.
+30
View File
@@ -0,0 +1,30 @@
# Ch371 Audit Log - Earlier-Capture Runtime CLUT Probe
## Scope
Qualify the first on-screen PSMT8/perspective cluster from the earlier SH3 capture (`20260624224047`) through the
runtime CLUT, persistent-Z, and depth-4096 request path.
## Fixture and Integration
- `zsrte3` retains three authentic chronological draws as two ordered epochs: 68 triangles followed by 136 triangles.
It fills texture CRC `0x2d9a2fda` once, then proves a resident reuse. The relocated runtime CLUT base is CBP `508`.
- Geometry is in bounds at x `64..440`, y `44..471`; feeder lists are 619 and 1,231 words, both below the 2,048-word
staging limit.
- The traced integration gate passes at depth 4096: 279,804 accepted fragments, 251,464 Z-pass updates, two fresh
drains, zero drops, Z `0/307200`, and color `0/106017`. Request FIFO high-water is 454 then 2,055, below the fitted
4096-entry capacity.
## Silicon Closure
- The board completed one runtime palette copy, one texture fill, one verified resident reuse, two fresh drains, and
zero fragment drops.
- Board and traced-integration framebuffers are byte-identical at SHA-256
`1f060fc17bd0a41e02ae79303b1ffef88a05ff53b2cddde400de1ff2b796bbf4`.
- Replay-color is exact at `307200/307200`: 279,804 fragments, 251,464 Z-pass updates, 106,017 covered pixels, and
no uncovered writes.
## Result
Ch371 closes the first earlier-capture runtime-CLUT probe on silicon. The 2,055-entry peak retains substantial margin
inside the fitted 4,096-entry request FIFO, so the next rung is an in-cluster draw-count expansion rather than RTL.
+22
View File
@@ -0,0 +1,22 @@
# Ch372 Audit Log - Earlier-Capture Six-Draw Expansion
## Integration
- `zsrte6` groups six chronological authentic draws from capture `20260624224047` into two 204-triangle epochs.
It performs one runtime palette/texture fill followed by one CRC-verified resident reuse at CBP `508`.
- Native geometry remains in bounds at x `63..443`, y `40..471`; both feeder lists are 1,843 words.
- Depth-4096 traced integration passes: 446,447 accepted fragments, 373,770 persistent-Z updates, two fresh drains,
zero drops, Z `0/307200`, and color `0/123384`. Request-FIFO high-water is 2,090 then 940.
## Silicon Closure
- The board completed the runtime palette copy, one texture fill, one verified resident reuse, two fresh drains, and
zero fragment drops.
- Board and integration framebuffers are byte-identical at SHA-256
`1b82b94c232907acd037a47c22cbf6262eba6ba73ed25399cb0a4373ef388869`.
- Replay-color is exact at `307200/307200`: 446,447 fragments, 373,770 Z-pass updates, 123,384 covered pixels, and
no uncovered writes.
## Result
Ch372 closes the six-draw earlier-capture expansion on silicon with no RTL, constraint, or fit change.
+29
View File
@@ -0,0 +1,29 @@
# Ch373 Audit Log - Earlier-Capture Eight-Draw Expansion
## Fixture
- `zsrte8` groups eight chronological authentic draws from capture `20260624224047` into three
runtime-CLUT epochs: 204, 204, and 90 triangles.
- Epoch 0 performs the texture fill; epochs 1 and 2 perform CRC-verified resident reuse at CBP `508`.
- Native geometry is bounded by x `63..443`, y `40..471`. The feeder lists are 1,843, 1,843, and 817 words.
## Integration Gate
- Depth-4096 integration and independent trace runs both pass with 520,733 accepted fragments,
429,471 persistent-Z updates, three fresh drains, and zero drops.
- The exact scoreboards report Z `0/307200` and color `0/128277` mismatches.
- Request-FIFO high-water is bounded at 2,090, 940, and 1,757 requests across the three epochs,
below the production depth of 4,096.
## Silicon Closure
- The DE25 completed one texture fill, two CRC-verified resident reuses, all 498 expected draw records, and
zero fragment drops. Its framebuffer reports 128,277 nonzero pixels in bounds `(65,41)..(443,470)`.
- Board and integration framebuffers are byte-identical at SHA-256
`3a2395cc0d4829560016aeac852b029ba46248ee1dcae3d74d541a64688b2a32`.
- The complete 520,733-fragment trace independently replays to the board framebuffer exactly:
429,471 Z-pass updates, 128,277 covered pixels, zero uncovered writes, and `307200/307200` exact colors.
## Result
Ch373 closes the earlier-capture eight-draw expansion on silicon with no RTL, constraint, or fit change.
+34
View File
@@ -0,0 +1,34 @@
# Ch374 Audit Log - Earlier-Capture Eleven-Draw Rebind Expansion
## Fixture
- `zsrte11` keeps Ch373's eight chronological draws as three runtime-CLUT epochs, then adds draws
`11698`, `11911`, and `11938` as a fourth 142-triangle epoch.
- The scene runs at native 640x480 and allocates two runtime palette banks: CBP `504` for epochs 0-2
and CBP `508` for epoch 3. The residency pattern is `fill, reuse, reuse, fresh fill`.
- Feeder lists contain 1,843, 1,843, 817, and 1,285 words, each below the 4,096-word bridge capacity.
## Integration Gate
- Depth-4096 integration passes with 524,145 accepted fragments, 431,666 persistent-Z updates, four fresh drains,
and zero drops.
- Epoch request-FIFO high-water is 2,090, 940, 1,757, and 100. Exact scoreboards report Z `0/307200` and
color `0/129883` mismatches.
## Board Host Gate
- The DE25 completed the two texture fills, two resident CRC checks, four runtime palette copies, all 640 expected
records, and zero fragment drops.
- The board framebuffer has 129,883 nonzero words in bounds `(65,41)..(462,470)`.
## Silicon Closure
- Board and traced-integration framebuffers are byte-identical at SHA-256
`b44f5e98d79ffa2f451238182f798432edb5c413ea80fea2da6c231a0b4be151`.
- The complete trace independently replays to the board framebuffer exactly: 524,145 fragments,
431,666 Z-pass updates, 129,883 covered pixels, zero uncovered writes, and `307200/307200` exact colors.
## Result
Ch374 closes the first two-palette, eleven-draw earlier-capture expansion on silicon with no RTL, constraint,
or fit change.
+13
View File
@@ -0,0 +1,13 @@
# Ch375 Audit Log - Fourteen-Draw Post-Rebind Residency
- `zsrte14` extends Ch374 with three later, on-screen draws using CBP `508`, producing a five-epoch
`fill, reuse, reuse, rebind, reuse` runtime-CLUT sequence at native 640x480.
- Integration passes with 529,836 accepted fragments, 434,596 Z-pass updates, five fresh drains, zero drops,
exact Z `0/307200`, and exact color `0/132063`. Request-FIFO high-water is 2,090, 940, 1,757, 100, and 81.
- On the DE25, both palette banks, both texture fills, all 816 expected records, and all drop gates pass.
- Board and traced integration framebuffers are byte-identical at SHA-256
`3a4b8f12a08dd0a5e9872675a1dd7cf2d767b76c71def1c1f4398f8c89da104d`.
- Replay-color is exact at `307200/307200`: 529,836 fragments, 434,596 Z-pass updates, 132,063 covered pixels,
and zero uncovered writes.
Ch375 closes the fourteen-draw post-rebind residency expansion on silicon without RTL, constraint, or fit changes.
+14
View File
@@ -0,0 +1,14 @@
# Ch376 Audit Log - Seventeen-Draw Second-Palette Residency
- `zsrte17` extends Ch375 with three later, on-screen CBP `508` draws, producing a six-epoch
`fill, reuse, reuse, rebind, reuse, reuse` runtime-CLUT sequence at native 640x480.
- Local depth-4096 integration passes with 531,922 accepted fragments, 436,186 Z-pass updates, six fresh drains,
zero drops, exact Z `0/307200`, and exact color `0/132810`. Request-FIFO high-water is 2,090, 940, 1,757,
100, 81, and 84.
- On the DE25, both palette banks, both texture fills, all 934 expected records, and every drop gate pass.
- Board and traced integration framebuffers are byte-identical at SHA-256
`9101f42e3e575653a7e62c9dd5760fef69427b04cb45187e7a1697a5a0e0e864`.
- Replay-color is exact at `307200/307200`: 531,922 fragments, 436,186 Z-pass updates, 132,810 covered pixels,
and zero uncovered writes.
Ch376 closes the seventeen-draw second-palette residency expansion on silicon without RTL, constraint, or fit changes.
+14
View File
@@ -0,0 +1,14 @@
# Ch377 Audit Log - Eighteen-Draw Second-Palette Residency
- `zsrte18` adds the final later, on-screen CBP `508` candidate to Ch376, producing a seven-epoch
`fill, reuse, reuse, rebind, reuse, reuse, reuse` runtime-CLUT sequence at native 640x480.
- Local depth-4096 integration passes with 532,162 accepted fragments, 436,333 Z-pass updates, seven fresh drains,
zero drops, exact Z `0/307200`, and exact color `0/132936`. Request-FIFO high-water is 2,090, 940, 1,757,
100, 81, 84, and 21.
- On the DE25, both palette banks, both texture fills, all 956 expected records, and every drop gate pass.
- Board and traced integration framebuffers are byte-identical at SHA-256
`9b49a9b56cb99296d09b12c535145b64dd8ae80b32f06e2a9934801567fe95bb`.
- Replay-color is exact at `307200/307200`: 532,162 fragments, 436,333 Z-pass updates, 132,936 covered pixels,
and zero uncovered writes.
Ch377 closes the eighteen-draw two-palette residency expansion on silicon without RTL, constraint, or fit changes.
+18
View File
@@ -0,0 +1,18 @@
# Ch378 Audit Log - Nineteen-Draw Third-Binding Expansion
- `zsrte19` extends the frame-pure Ch377 scene with draw `93634` (`TBP=3072`, authentic CBP `14080`), adding a
third texture and runtime-CLUT binding in an eighth epoch at native 640x480.
- The initial three-bank layout at relocated CBPs `504/508/512` was rejected before simulation because `512` is
beyond the 512-block BRAM. The fixture is instead deliberately relocated to `500/504/508`; no RTL, constraint,
or fit change is involved.
- Local depth-4096 integration passes with 543,715 accepted fragments, 444,244 Z-pass updates, eight fresh drains,
zero drops, exact Z `0/307200`, and exact color `0/139585`. Request-FIFO high-water is 2,090, 940, 1,757,
100, 81, 84, 21, and 733.
- On the DE25, all three palette banks, all three texture fills, all 1,024 expected records, and every drop gate pass.
- Board and traced integration framebuffers are byte-identical at SHA-256
`941ee4579c220a82240f30aae86f74ae9c0873923c0749835e75f49a089d6186`.
- Replay-color is exact at `307200/307200`: 543,715 fragments, 444,244 Z-pass updates, 139,585 covered pixels,
and zero uncovered writes.
Ch378 closes the three-palette, three-texture native-640x480 persistent-Z expansion on silicon without RTL,
constraint, or fit changes.
+14
View File
@@ -0,0 +1,14 @@
# Ch383 Audit Log - Thirty-Four-Draw Third-Binding Extension
- `zsrte34` extends the native-640x480 persistent-Z scene to 34 authenticated draws in 13 epochs, retaining the
three runtime texture/CLUT bindings at relocated CBPs `500/504/508`.
- Local depth-4096 integration passes with 647,303 accepted fragments, 500,072 Z-pass updates, 13 fresh drains,
zero drops, exact Z `0/307200`, and exact color `0/166963`.
- The DE25 host run completed all 1,899 expected records with all palette and texture-fill checks passing and no
fragment drops. Its framebuffer bounds were `(46,3)..(634,473)` with 166,963 nonzero pixels.
- The board dump and the independently traced integration framebuffer are byte-identical at SHA-256
`61169f615b327f235405cabb19fff38d3b336c359cd30c498357b6b16338bd19`.
- Replay-color is exact at `307200/307200`: 647,303 replay fragments, 500,072 Z-pass updates, 166,963 covered
pixels, and zero uncovered writes.
Ch383 closes the 34-draw, 13-epoch extension on the timing-closed core. No RTL, SDC, QSF, or Quartus fit changed.
+14
View File
@@ -0,0 +1,14 @@
# Ch384 Audit Log - Forty-Three-Draw Fourth-Palette Expansion
- `zsrte43` expands the native-640x480 persistent-Z scene to 43 authenticated draws in 16 ordered epochs.
- A fourth runtime palette initially exceeded the old monotonic relocation window. Rebasing the fixture to
`496/500/504/508` kept all four palette banks inside the 512-block BRAM without an RTL, SDC, QSF, or fit change.
- Local depth-4096 integration passes with 715,304 accepted fragments, 561,898 Z-pass updates, 16 fresh drains,
zero drops, exact Z `0/307200`, and exact color `0/168654`.
- The DE25 completed all 2,103 expected records, four texture fills, four runtime-palette checks, and every drop
gate. Board and traced integration framebuffers are byte-identical at SHA-256
`fb4bce9ad6307960308d1d1c7624c5d18e560854eb1b82fa7f95e7dbc6e7356e`.
- Replay-color is exact at `307200/307200`: 715,304 fragments, 561,898 Z-pass updates, 168,654 covered pixels,
and zero uncovered writes.
Ch384 closes the 43-draw, four-runtime-palette scene on the timing-closed core.
+17
View File
@@ -0,0 +1,17 @@
# Ch385 Audit Log - Runtime-CLUT Reuse and Seventeen-Epoch Scaling
- The old 16-epoch cap lived only in the HPS scheduler and common integration testbench. Both descriptor capacities
were raised to 32; the FPGA RTL, constraints, and RBF were not changed.
- Runtime CLUT staging is serialized before each GO and each GO completes its drain before the next epoch begins.
The fixture generator now reuses one legal four-block CLUT slot in runtime mode, while preloaded fixtures retain
distinct-bank allocation. This removes the artificial four-palette scene cap.
- `zsrte45` carries 45 authenticated draws in 17 epochs, all using runtime CBP 500. Local depth-4096 integration
passes with 725,545 accepted fragments, 570,196 Z-pass updates, 17 fresh drains, zero drops, exact Z `0/307200`,
and exact color `0/168654`.
- The DE25 completed all 1,972 expected records, texture fills, palette copies, residency checks, and drop gates.
Board and traced integration framebuffers are byte-identical at SHA-256
`a374ba18b05994f3223cd6b79139effc184ea35b436a6fece4e7733c1c0a03b8`.
- Replay-color is exact at `307200/307200`: 725,545 fragments, 570,196 Z-pass updates, 168,654 covered pixels,
and zero uncovered writes.
Ch385 closes reusable runtime-CLUT staging and host-side 32-epoch capacity on the timing-closed core.
+15
View File
@@ -0,0 +1,15 @@
# Ch386 Audit Log - Frame-3 Eighteen-Draw Native Scene
- Frame 1's remaining in-bounds PSMT8 candidates were rejected by the existing supported-state gates, so Ch386
starts a fresh, authentic frame-3 scene rather than weakening those gates.
- `zsrtf3e18` contains 18 draws from `TBP=12288/CBP=14208`, partitioned into five ordered staging-safe epochs
(70, 106, 58, 97, and 192 records) with one texture fill and four CRC-verified reuses.
- Local depth-4096 integration passes with 67,295 accepted fragments, 32,435 Z-pass updates, five fresh drains,
zero drops, exact Z `0/307200`, and exact color `0/20276`.
- The DE25 completed every record, runtime-palette copy, texture residency check, and zero-drop gate. Board and
traced integration framebuffers are byte-identical at SHA-256
`8a905aa968f252fdc029832a5a66ee910c93053f791132c5ebf35ddbd4c08818`.
- Replay-color is exact at `307200/307200`: 67,295 fragments, 32,435 Z-pass updates, 20,276 covered pixels, and
zero uncovered writes.
Ch386 closes the first fresh frame-3 native scene on the Ch368 timing-closed core.
+132
View File
@@ -0,0 +1,132 @@
# 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.
@@ -0,0 +1,83 @@
# Ch400 — chronological base geometry plus bounded alpha tail
## Goal
Move fidelity forward by replaying the missing in-bounds opaque draws before a
small authentic alpha-blended tail, rather than darkening the incomplete Ch399
base with the full alpha family.
## Fixture
- Tag: `sh3_zsrt139a6`
- 35 authentic draws in chronological order
- 20 epochs: 17 base epochs followed by 3 alpha epochs
- Independent reference coverage: 83,596 framebuffer pixels
- Simulation target: `make -C sim tb_top_psmct32_sh3_zint640rt139a6`
- Board loader: `tools/scp_zsrt139a6_to_board.sh`
## Blend ordering correction
The first full replay found two wrong color pixels with exact Z. Repeated ABE
fragments could begin their destination read after the previous blended result
entered the color writer, but before that partial beat received an AXI BRESP.
Waiting on the writer's raw `idle` signal is not sufficient because the elastic
packer may retain a partial beat while its AXI FIFO is empty.
`gs_lpddr_zc_emit` now treats each ABE result as an ordered dependency:
1. accept one ABE input and hold subsequent color inputs;
2. enqueue its blended output and pulse the color writer flush;
3. wait for the ordered drain acknowledgement to transition low then high;
4. admit the next color input only after the preceding write is memory-visible.
Opaque flow remains unrestricted outside an active ABE dependency.
## Simulation acceptance
Final full replay:
- fragments fed: 279,134
- fragments passing Z: 234,373
- scene markers: 20
- request drops: 0
- color FIFO overflows: 0
- AXI BRESP errors: 0
- Z mismatches: 0 / 307,200
- color mismatches: 0 / 83,596
- framebuffer MEM SHA-256:
`38095a6c9136eb6618876c4cf2bf9a72c494a6f8161c247f2e1a36dcd0474bb8`
## Hardware gate
The RTL correction postdates the 2026-07-16 06:17 Quartus artifact. A fresh
owner-run Quartus 26.1 GUI compile is required before board deployment. After
that compile, accept Ch400 only if timing is clean, the RBF is fresh, the board
run completes all 20 epochs with zero drops/errors, and the board framebuffer is
byte-exact to `sh3_zsrt139a6_sim_fb.mem`.
## Silicon acceptance (2026-07-16)
The owner-run Quartus 26.1 GUI compile completed successfully after the final
RTL correction. The fit used 37,148/46,800 ALMs and 284/358 RAM blocks. Setup
slack was `+0.107 ns`, hold slack was `0.000 ns`, and all reported TNS values
were zero.
The fresh deploy RBF was written at 08:34:52, is 3,919,872 bytes, and has
SHA-256
`4619653217c8946b7ba4cd847a17f5e5b020c449ac4837784f72f9a7abed0b7d`.
The hash matched on the DE25 before loading, and `fpga0` reported `operating`.
The board completed all 20 epochs with every texture CRC, palette checksum,
staged-word count, triangle-record count, preclear, drain, and zero-drop gate
passing. The host returned `rc=0` and dumped all 307,200 framebuffer words.
Board and simulation are byte-exact across the complete framebuffer:
- board MEM SHA-256:
`38095a6c9136eb6618876c4cf2bf9a72c494a6f8161c247f2e1a36dcd0474bb8`
- simulation MEM SHA-256: same
- board PNG SHA-256:
`1785875df7c4c17fbf94ce1543fa3777be5601c874f5319ce4e2a20c828cecd0`
- simulation PNG SHA-256: same
Ch400 is accepted on silicon.
@@ -0,0 +1,32 @@
# Ch401 — fuller 56-draw static frame
Ch401 retains the accepted Ch400 chronological base, one correctly paired ABE
draw, two later-scene anchors, and the proven 24-draw central cluster. The
result is 56 authentic draws in 28 bounded epochs, within the existing 32-epoch
host/testbench limit.
## Simulation
- 638,022 fragments fed
- 321,817 fragments passed Z
- 28 ordered drains
- zero request drops, color FIFO overflows, or AXI BRESP errors
- Z mismatches: 0 / 307,200
- color mismatches: 0 / 128,998
## Silicon
No RTL changed after the accepted Ch400 RBF. The 28-epoch board run passed all
texture CRC, runtime palette, staging, record-count, drain, and zero-drop gates
and returned `rc=0`.
- nonzero pixels: 128,998
- bounds: `(36,10)..(599,399)`
- board/simulation MEM SHA-256:
`0383cc739aa8e9b921489a3fc1ffca7ab2a48763cb57028d4a90e3ae9a34142f`
- board/simulation PNG SHA-256:
`a4806d0ba39fcea1b904375f715d11f7a81d1364c78f201f40c1bb5906ac008b`
Board and simulation are byte-exact over all 307,200 framebuffer words.
Ch401 is accepted on silicon and remains displayed through the LPDDR HDMI
scanout.
+74
View File
@@ -0,0 +1,74 @@
# Ch402 — character draws through runtime CLAMP state
Ch402 extends the accepted Ch401 56-draw scene with twelve authentic draws from
the character family, each in its own epoch. These draws use GS region clamp
mode 2 with exact full-texture bounds (`MINU/MINV=0`, `MAXU/MAXV=511`). For a
512x512 texture that state is mathematically identical to ordinary clamp, so the
fixture generator proves those bounds and normalizes only the mode bits. Any
other region-clamp bounds continue to fail closed.
The feeder format is backward-compatible. Legacy lists retain the seven-word
header. Extended lists advertise word 8 with count-word bit 34 and emit
`CLAMP_1` between `TEX0_1` and `PRIM`. A focused feeder regression proves both
formats and the new vertex base.
An initial four-epoch grouping required 2,294 to 3,410 staging words and doubled
the allocated RAM. Although functionally exact in simulation, that placement
pressure moved the previously marginal color-address FIFO family to -0.233 ns.
Ch402 therefore uses twelve single-draw character epochs, retains the accepted
2,048-word staging allocation, and raises only the software epoch-table ceiling
from 32 to 64.
## Simulation
- 68 authentic draws in 40 ordered epochs
- 649,189 fragments fed
- 332,984 fragments passed Z
- 40 fresh ordered drains
- zero request drops, color FIFO overflows, or AXI BRESP errors
- Z mismatches: 0 / 307,200
- color mismatches: 0 / 131,618
- reference coverage: 131,830 pixels
- largest staging list: 1,844 / 2,048 words
- maximum measured request FIFO high-water: 1,753 / 4,096
- simulated framebuffer MEM SHA-256:
`d3a211671a444991e8032c97d3ba16c66621de1e9d3ad1432442bdb8be585010`
- simulated framebuffer PNG SHA-256:
`ae1b8fbdf1748723eede478d4ca349245920c32836df94cdf076cc6b983047b6`
The simulated image now includes the central character silhouette and reaches
the bottom of the 640x480 frame. It remains visibly short of the PCSX2 image in
lighting, fog/compositing, and precision; Ch402 closes the missing-character
state/capacity blocker, not the remaining fidelity debt.
## Timing gate
The first owner GUI fit used a 4,096-word staging allocation. It fit at 291 RAM
blocks but failed setup at -0.233 ns / -1.287 ns TNS on the known
`u_zc_emit|ca_tail/ca_head -> ca_mem` family; hold remained clean. The failure
was a placement regression rather than a new logical path through the feeder.
The 40-epoch repartition removes the seven added RAM blocks and restores the
accepted 2,048-word allocation. The follow-up owner GUI fit recovered timing at
+0.041 ns setup / 0.000 TNS; hold remained clean. It used 37,279 / 46,800 ALMs
and restored RAM usage to 284 / 358 blocks.
## Silicon
The timing-clean RBF (SHA-256
`a7691ff9203bc1a9097da88f0a341f6c14ed96910bb2d6f3fb46349e02b0fcce`)
was hash-verified on the board and loaded through `core_loader.sh`; `fpga0`
reported `operating`.
All 40 epochs passed texture/CLUT CRC, staging count, record count, ordered
drain, and zero-drop gates with `rc=0`.
- nonzero pixels: 131,618
- bounds: `(36,10)..(599,463)`
- board/simulation MEM SHA-256:
`d3a211671a444991e8032c97d3ba16c66621de1e9d3ad1432442bdb8be585010`
- board/simulation PNG SHA-256:
`ae1b8fbdf1748723eede478d4ca349245920c32836df94cdf076cc6b983047b6`
Board and simulation are byte-exact over all 307,200 framebuffer words.
Ch402 is accepted on silicon and remains displayed through the LPDDR HDMI
scanout.
@@ -0,0 +1,91 @@
# Ch403 — authentic vertex-color modulation
## Objective
Make the 224139 static-frame fixture use the captured GS lighting state instead
of drawing every texture at raw DECAL brightness. Geometry, draw order,
runtime CLUT handling, persistent Z, and the Ch402 40-epoch staging partition
remain unchanged.
## Finding
The selected draws carry `TEX0.TFX=MODULATE` and non-unity per-vertex RGB.
Ch402 deliberately forced `TFX=DECAL` and emitted zero placeholder RGB, which
made the composite much brighter than the PCSX2 frame.
The first Ch403 fixture proved a second, narrower gap: the existing MODULATE
implementation was only connected to the combined affine texture path. These
SH3 triangles use the five-cycle perspective path, whose emit was explicitly
hardwired to the raw texel. Consequently the live board completed every gate
but produced a framebuffer byte-identical to Ch402.
## Implementation
- `tools/gs_make_sh3_scheduler_fixture.py`
- adds opt-in `--auth-color-tfx`;
- preserves authentic `TEX0.TFX` and per-vertex RGB;
- interpolates RGB when clipping triangles;
- applies the same GS `texel * vertex / 128` saturation rule in the
independent reference.
- `rtl/gif_gs/gs_stub.sv`
- aligns the already-computed S2 Gouraud color with the perspective texel by
four registers;
- applies MODULATE at the perspective S1+5 emit;
- shares the existing three-channel modulation datapath between affine and
perspective paths, avoiding a duplicate multiplier set;
- leaves DECAL output byte-identical.
- `sim/tb/top/tb_top_psmct32_sh3_zint640_shared.sv`
- verifies the full 64-bit staged header and committed TEX0 state;
- fixes late-epoch diagnostic fill accounting.
## Fixture
`zsrt139a9` retains all 68 Ch402 draws in 40 staging-safe epochs. The largest
list is 1844 of 2048 words (204 words headroom).
The independent modulated reference is:
`captures/gs/silenthill3/extracted/recon/sh3_zsrt139a9_ref.png`
Against the PCSX2 224139 image resized to the same 640x480 comparison grid,
the fixture-only reference improves RGB MAE from 31.34 to 23.31 and RMSE from
45.94 to 33.61. This does not claim full fidelity: missing draws/fog and the
perspective/XY precision deficit remain visible.
## Verification
- Generator preflight: 40 epochs, max 1844 words, no staging overflow.
- Focused epoch 39 MODULATE proof:
- old DECAL hash: `37401414dd6ee3b2888d4ddb4aff98903900475e6cf2a47d973959ee05d05fb8`
- MODULATE hash: `c85edea3d42e983a31f630b7fffc1457c26233492c332860b7913c20ad7613c0`
- average nonblack RGB sum: 258.49 -> 113.23; geometry remains present.
- Focused Ch402 DECAL regression remains byte-identical at the old hash.
- Full 40-epoch RTL:
- 649,189 fragments fed; 332,984 passed persistent Z;
- 40 markers, 40 rises / 39 falls, zero drops/overflows/BRESP errors;
- Z mismatch `0/307200`; color mismatch `0/131618`;
- framebuffer SHA-256
`d286e9d5839fc7ab63ae6bc1aa3c26fa867397171ad3d1a5e5edc3b6c5b14964`;
- PASS.
- The final shared-multiplier implementation reproduces the focused MODULATE
hash exactly and its late-epoch diagnostic passes with zero errors.
- Owner Quartus 26.1 GUI fit:
- setup worst slack `+0.080 ns`, TNS `0`;
- hold passes, worst reported slack `0.000 ns`, TNS `0`;
- 37,353 / 46,800 ALMs, 53,238 registers, 284 / 358 RAM blocks,
4,177,064 memory bits, and 153 / 376 DSP blocks;
- core RBF SHA-256
`28659ebfccb19f03478fcd105904cc1b03195e8cc0907fad3903f56d7821975b`.
- Live-board acceptance:
- all 40 epochs completed with every texture CRC, palette checksum, staged
word/record count, fresh-drain, fragment-drop, and bus-response gate clean;
- host runner returned `0`;
- the 307,200-word board framebuffer compares byte-for-byte with RTL;
- board and RTL framebuffer SHA-256
`d286e9d5839fc7ab63ae6bc1aa3c26fa867397171ad3d1a5e5edc3b6c5b14964`.
## Status
Complete and accepted on silicon. The current HDMI image is the authentic
vertex-color-modulated 40-epoch composite; the next fidelity work is geometry
precision and remaining GS effects/draw coverage, not Ch403 correctness.
@@ -0,0 +1,39 @@
# Ch404 — chronological character-detail tail
Ch404 appends the next 36 authentic opaque draws after the accepted Ch403
frame. Thirty-five staging-safe epochs preserve dump order and cover the
character/lower-body region without changing RTL or requiring another fit.
## Fixture and infrastructure
- `zsrt139b1`: generated 35-epoch detail tail.
- `zsrt139b2`: merged Ch403 base plus the tail, 75 epochs total.
- `tools/merge_runtime_sched.py`: concatenates accepted runtime-CLUT fixtures
without regenerating or changing their staged records.
- Host and shared-testbench epoch ceilings increase from 64 to 128; staging RAM
remains 2,048 words and the FPGA image is unchanged.
## RTL verification
- 667,279 fragments fed; 351,074 passed persistent Z.
- 75 ordered markers and drains.
- Zero request drops, color FIFO overflows, or AXI response errors.
- Z mismatch `0/307200`; color mismatch `0/132783` written pixels.
- RTL framebuffer SHA-256:
`8715381278c2b0a6e527329fb02aaaaf53edbaefd637f115723a3425bbd1b209`.
The tail adds 1,003 nonblack pixels over Ch403. Against the resized PCSX2
frame, RGB MAE improves from 23.8474 to 23.7846 and RMSE from 34.8791 to
34.6956. This is a bounded character-detail gain, not the missing environment
or fog solution.
## Silicon acceptance
The timing-clean Ch403 RBF was retained. The board completed all 75 epochs
with every texture CRC, runtime palette checksum, staging count, record count,
fresh drain, zero-drop, and bus-error gate clean; the host returned `0`.
The 307,200-word board framebuffer is byte-for-byte identical to RTL and has
the same SHA-256
`8715381278c2b0a6e527329fb02aaaaf53edbaefd637f115723a3425bbd1b209`.
The 75-epoch composite is live on HDMI.
+52
View File
@@ -0,0 +1,52 @@
# Ch405 — authentic alpha-fan overlays (pre-fit gate)
## Objective
Add the 53 chronological PSMT8 alpha-fan draws at capture indices 196494
through 198058 after the accepted 75-epoch Ch404 opaque composite. These are
large translucent overlays using authentic `ZTE=1`, `GEQUAL`, `ZMSK=1`, and
`PSMZ16S`: they test the accumulated depth buffer, blend on pass, and must not
modify Z.
## RTL change
- Carry architectural `TEST.ZTE` and `ZBUF.ZMSK` alongside each perspective
fragment through `gs_stub` and `top_psmct32_raster_demo_bram` into the
existing external LPDDR Z/color ROP.
- Replace the de25 wrapper's former `ZTE=1/ZMSK=0` constants with those
sidebands.
- Increase the production async request FIFO from 4,096 to 16,384 entries.
The alpha writer preserves exact destination order and can be slower than
the unthrottled raster producer, so the fixture bounds each epoch to one
triangle. Measured worst-triangle occupancy is 12,953 entries, leaving
3,431 entries of observed headroom.
## Fixture
- `zsrt139c3`: 212 one-triangle alpha epochs, preserving all 212 triangles
expanded from the 53 authentic fan draws.
- `zsrt139c4`: Ch403 base + Ch404 detail tail + Ch405 alpha tail, 287 epochs.
- Authentic TEST/ZBUF header words are restored in the accepted base fixtures;
their geometry, colors, textures, palettes, and record counts are unchanged.
- Host/testbench epoch ceilings rise from 128 to 512.
## Pre-fit evidence
- The unbounded three-epoch diagnostic correctly failed closed at epoch 75:
169,213 request drops and FIFO high-water 4,096/4,096. It is retained only
as the measurement that justified bounded bursts.
- Worst-tail three-epoch test (empty Z): 43,680 fed/passed, zero drops,
high-water 12,953/16,384, exact color and Z, PASS.
- Checkerboard-Z test: 43,680 fed, 21,839 passed, zero drops, high-water
11,389/16,384, exact color, exact unchanged Z at all 307,200 pixels, PASS.
- Full Ch404 baseline replay with authentic headers reached its exact accepted
epoch-74 counts (667,279 fed / 351,074 passed) with zero drops before the
intentionally unbounded diagnostic alpha epoch.
- Host runner compiles cleanly; generated production assets total about 174 MiB.
## Status
Ready for owner Quartus 26.1 GUI compile. Do not deploy the prior
`28659ebf...` RBF as Ch405; the new RBF must contain the 16K request FIFO and
TEST/ZMSK sidebands. After a timing/resource-clean fit, deploy and run
`zsrt139c4`, then compare the board framebuffer byte-for-byte with RTL.
+55
View File
@@ -0,0 +1,55 @@
# Ch406 — deep request-FIFO timing repair (pre-fit gate)
## Trigger
The owner Quartus 26.1 GUI compile for Ch405 fit in the device but failed the
310 MHz EMIF clock by 0.267 ns, with design-wide TNS of -4.802 ns. All 77
failing endpoints belong to `u_zc_emit|u_req`. The leading path is the
registered empty flag through the 15-bit binary-pointer increment, Gray
conversion, equality reduction, and back into the empty flag. Secondary
violations are the same empty/pointer cone feeding the 16K FIFO RAM address,
plus the asynchronous RAM bank mux feeding request-head decode.
This is a local consequence of increasing the Ch405 request FIFO from 4K to
16K. Hold timing, Gray-pointer skew constraints, and all other clock domains
pass.
## Structural repair
- Treat `gs_async_fifo.rd` according to its existing interface contract: it is
an accepted read, already qualified by `!rempty` in each wrapper. Remove the
redundant internal `!rempty` gate from the read-pointer increment. This
removes the empty-to-pointer feedback and the empty-to-RAM-address fanout.
- Add an optional synchronous/registered read port to `gs_async_fifo`.
- Enable that port only for the 16K x 92-bit Z/color request FIFO. Keep the
smaller existing users on their original FWFT interface.
- Add a one-cycle pending bit in `gs_lpddr_zc_emit` so its request-head stage
captures the registered FIFO word on the following cycle and cannot issue a
second pop while a word is in flight.
- Make the standalone FIFO testbench explicitly qualify its randomized read
requests, matching the documented production interface.
No clock, SDC, FIFO depth, fixture, draw, texture, blend, or Z behavior changes.
## Pre-fit proof
- Async FIFO scoreboard: 3,284 writes / 3,284 reads, zero order, duplicate, or
drop errors, final empty asserted, PASS.
- Ch405 worst three epochs, empty Z: 43,680 fed and passed, zero drops,
high-water 12,953/16,384, exact color, exact Z, PASS.
- Ch405 worst three epochs, checkerboard Z: 43,680 fed, 21,839 passed, zero
drops, high-water 11,389/16,384, exact color, exact unchanged Z at all
307,200 pixels, PASS.
- A sequential production sweep additionally replayed epochs 0 through 19:
291,597 fragments, zero drops, and every scene drain completed. It was
stopped after this redundant prefix because the complete 287-epoch run would
consume more than an hour; the maximum-pressure tail and both Z outcomes had
already passed exact comparisons above.
- Production RTL elaborates with the 16K request FIFO and registered-read path.
## Owner fit gate
Run the next compile only in the owner-controlled Quartus 26.1 GUI. The fit
must confirm that the request RAM remains inferred in device memory, resources
still fit, setup and hold both pass, and the previous `rempty` feedback family
is absent. Do not deploy an RBF unless those checks pass.
+111
View File
@@ -0,0 +1,111 @@
# Ch415 — authentic fog fold and indexed perspective bilinear filtering
## Objective
Move the 224139 board image toward the PCSX2 reference through real captured
GS state rather than further draw-count expansion. Every selected draw has
`PRIM.FGE=1`, every vertex arrives through `XYZF2`, `FOGCOL=0`, and every
selected texture requests linear magnification.
## Changes
- Preserve the captured vertex fog factor and fold the zero fog color into
fixture vertex RGB as `RGB * F >> 8`. This is exact at vertices and uses
the existing Gouraud interpolator across each triangle.
- Preserve four fractional texel bits from the perspective reciprocal path.
- Serialize indexed perspective pixels through the existing four-tap texture
sampler, performing CLUT lookup before interpolation as required.
- Hold the raster walker while the single-port texture cache fetches four
taps, then release exactly once after emitting the completed pixel.
- Add bounded `START_EPOCH`/`END_EPOCH` integration runs and explicit cold
shared-asset selection so late reuse epochs can be tested independently.
## Reference evidence
Against the 640x480 PCSX2 frame, the full software reconstruction improved
from MAE 22.55 / RMSE 34.96 with nearest sampling to MAE 21.17 / RMSE 32.47
with bilinear sampling. The fog fold alone was smaller (MAE 22.51 / RMSE
34.88), so both are retained but filtering is the principal visual change.
## RTL evidence
- Texture sampler: PSMT8/PSMT4 CLUT-before-interpolation, clamp, repeat,
nearest fallback, and edge cases all pass.
- Perspective feeder and palette-bilinear tile integration pass.
- Legacy raster pipeline remains passing.
- Real fixture epoch 1: 3,949 accepted/passing fragments, zero drops, exact Z
and color-memory comparisons.
- Dense shared-texture epoch 75: 7,304 accepted/passing fragments, zero drops,
exact Z and color-memory comparisons.
## First-fit hardware correction
The first signoff-clean Ch415 fit (`91087214...`) passed all 124 board epochs,
but its capture exposed regular missing-pixel stripes: only 84,294 pixels had
nonzero RGB, versus 146,399 in the preceding board frame. The DONE cycle had
been reopened inside the normal pipeline-advance block, so both DONE and
PB_RELEASE advanced the held walker and skipped alternate samples.
The corrected RTL emits DONE outside the advance gate, keeps the walker frozen
that cycle, and advances exactly once on PB_RELEASE. Focused RTL coverage rose
from 2,889 to 3,949 fragments on epoch 1 and from 4,994 to 7,304 on dense epoch
75, with exact color/Z comparisons and all sampler, tile, feeder, and legacy
raster regressions passing.
## Second-fit timing repair
The walker-release fit succeeded but STA was unclean in two isolated families:
- Setup `-0.128 ns`, two endpoints: the registered request-pop pulse entered
the 15-bit read-pointer carry chain before Gray conversion and `rempty`.
- Hold `-4.817 ns`, two endpoints: the asynchronous CLUT-commit and tile-write
toggles were timed directly into their explicit first synchronizer stages.
The FIFO now precomputes hold/pop pointer and empty results independently, so
the pop pulse selects only the final mux rather than driving the carry chain.
The SDC now cuts only the two asynchronous source-to-stage-0 synchronizer paths,
with fatal one-register target-count checks; stages 1 and 2 remain timed. Both
synchronizers are explicitly identified to Quartus. The FIFO scoreboard,
CLUT CDC, tile CDC, and dense 7,304-fragment integration test all pass exactly.
## Third-fit setup repair
The next owner fit (`ae9729fb...`) proved the CDC/FIFO repair: hold closed at
0.000 ns. Setup remained unclean at `-0.220 ns` / `-1.193 ns` TNS across eight
310 MHz endpoints. The failing paths were now three concrete datapath cones:
- EMIF 256-bit read data through the 8:1 lane mux into `u_lpddr_rd|rd_data`;
- EMIF read data and address-selected blend operands into `diff_r_q`/`diff_g_q`;
- `u_zc_emit|ca_tail` through the color-alignment RAM write-address cone.
The read probe now captures the complete EMIF beat in `rdata_q`, then selects
the requested 32-bit lane in a new `S_SEL` stage. The color blender similarly
separates beat capture, lane selection, and operand/difference preparation into
`R`, `SEL`, and `PREP` states. The color-alignment RAM write enable, address,
and data are also registered before the memory write. This removes all three
reported source-to-endpoint combinational cones without adding timing
exceptions.
Directed read-probe, color-blend, and Z/color-emitter tests pass. Dense real
scene epoch 75 still accepts and passes 7,304 fragments with zero drops and
exact comparisons: 0/307,200 Z mismatches and 0/7,304 color mismatches.
## Hardware acceptance and fidelity finding
The owner-controlled GUI fit closed cleanly: setup slack `+0.052 ns`, hold
slack `0.000 ns`, zero setup/hold TNS, and clean recovery, removal, and minimum
pulse checks. The deployed RBF SHA-256 is
`6fd81615af95cf51e1a893e04ca34fa1bad7a69c933efcb7fca5c9c091e18a65`.
All 124 board epochs passed with every texture CRC, palette, staging-record,
drain, and drop gate clean. The captured framebuffer reported 114,231
nonzero words and bounds `(1,11)..(512,478)`; its MEM SHA-256 is
`e31820cfad1d550bb1ea9adf217e21cb59a02be76c6318ce0b34022307bc91ec`.
The hardware result did not realize the software-reference gain. Against the
bilinearly resized PCSX2 frame, the preceding board capture measured MAE
21.9782 / RMSE 32.5912, while Ch415 measured MAE 23.6438 / RMSE 35.0255.
Ch415 board versus its own software reconstruction measured MAE 12.2877 /
RMSE 26.4442. This isolates geometry/coverage precision, rather than
scheduler capacity, as the next fidelity boundary. Ch416 therefore preserves
the captured XYZ 12.4 fractions and moves coverage to GS pixel centers.
+113
View File
@@ -0,0 +1,113 @@
# Ch416 — native XYZ 12.4 coverage
## Objective
Carry the captured GS fractional screen coordinates through the fixture,
primitive FIFO, and raster coverage test. This is a fidelity chapter: draw
population, texture state, fog fold, bilinear filtering, Z behavior, and
chronological ordering remain the Ch415 baseline.
## RTL cut
- `gs_stub` has an opt-in `SUBPIXEL_XY` path that retains each vertex's four
X/Y fractional bits alongside the existing small primitive FIFO.
- Edge equations use 12.4 endpoints and sample the pixel center at `+8` in
that domain. The operands are bounded to signed 17-bit coordinates and
signed 35-bit edge results.
- The production `GS_SH3_LPDDR_FB` profile enables the path. Legacy profiles
retain integer corner-sampled coverage.
- Attribute gradients remain on the proven integer coordinate basis for this
first structural cut. Moving gradient setup to 12.4 is explicitly deferred
and is not hidden inside this coverage change.
## Fixture and generator controls
`--subpixel-xy` emits native XYZ2 low-half 12.4 coordinates. Capacity
scheduling measures the same 12.4 pixel-center coverage so narrow visible
triangles cannot be discarded after integer collapse. Runtime-CLUT fixtures
retain the proven single staging CBP and adjacent identical assets without the
obsolete preloaded-palette uniqueness gate.
The full `zsrt139f17` fixture uses `--legacy-strip-kicks` to preserve the exact
Ch415 strip population. This makes the experiment one-variable: changing the
newer strip-population interpretation at the same time would remove broad
scene layers and invalidate the A/B comparison.
## Evidence so far
- Legacy raster regression: 16/16 pixels PASS.
- Directed subpixel raster regression: 3/3 pixel-center samples PASS.
- Authentic three-draw integration: 5,909 fragments fed, 5,907 Z-passing,
zero drops, 0/307,200 Z mismatches, and 0/5,858 color mismatches.
- Full software reconstruction versus PCSX2 improves from Ch415 MAE 21.0852 /
RMSE 32.3385 to Ch416 MAE 19.8099 / RMSE 28.8334. Lit RGB pixels rise from
174,167 to 212,915.
- Full 263-epoch RTL integration PASS: 1,139,213 fragments fed, 969,301
Z-passing, zero drops, 263 markers and ordered drains, no color FIFO or AXI
response errors, 0/307,200 final Z mismatches, and 0/235,942 final color
mismatches.
- The Ch416 RTL framebuffer improves against PCSX2 to MAE 20.4941 / RMSE
30.0163 with 199,104 lit RGB pixels. This is a large improvement over the
Ch415 board result at MAE 23.6438 / RMSE 35.0255 and 110,329 lit RGB pixels.
- RTL versus the Ch416 software reconstruction measures MAE 10.1578 / RMSE
19.1650. Native coverage closes a substantial part of the hardware/software
gap, but fractional attribute-gradient setup remains visible fidelity debt.
Artifact SHA-256 identities:
- full epoch descriptor: `ba1484d23cc9a0caba9d4f82af7ed157fbe692c54a9138d5c0aa02f5ca9f3d87`;
- RTL framebuffer MEM: `6aa98a68ee59b5df8457b651baf12decbbb7b71c70c4029ff2cb144a3defa1de`;
- RTL framebuffer PNG: `709d6a1438355531b38723654f27b0b07e49524af10d33388fdbb744739cf7b3`;
- software reference PNG: `63944f31b8c5f55b70bbddbb89db4ac0ed782a4daf8bd6dad8bb8ad8ec9ad006`.
## First owner fit and timing repair
The first owner-controlled Quartus 26.1 GUI fit completed, but is not eligible
for deployment. EMIF user-clock setup was -0.433 ns WNS / -6.069 ns TNS;
hold was 0.000 ns with zero TNS, and recovery, removal, and minimum-pulse checks
were clean. The top-ten setup failures were one physical family from the EMIF
`lock_sync_inst|dreg[1]` reset source into duplicated
`u_zc_emit|u_req|...|address_reg_b[2]` registers. This was reset routing into
the request FIFO's synchronous-read RAM implementation, not the new subpixel
coverage datapath.
`gs_async_fifo` no longer resets `rdata_q` in `REGISTERED_READ` mode. That
value is invalid until an accepted read and every consumer already qualifies it
with the read transaction, so the reset value had no functional meaning. The
cut removes the 5,331-fanout EMIF lock/reset route from the duplicated RAM
registers without adding a timing exception or changing FIFO latency.
Post-repair evidence:
- generic asynchronous FIFO scoreboard PASS: 3,284 writes and reads, zero
duplicate/drop/order errors;
- Z/color emitter PASS: 1,500 fragments across three epochs plus directed
`ZTST=ALWAYS`, exact final Z/color, `col_ovf=0`, `bresp_err=0`;
- three-epoch native-subpixel integration PASS: 5,909 fed, 5,907 passed, zero
drops, exact 0/307,200 Z and 0/5,858 color mismatches.
## Owner fit gate
The repaired owner-controlled Quartus 26.1 GUI fit closed cleanly: EMIF setup
is +0.047 ns WNS / 0.000 ns TNS, hold is 0.000 ns WNS / 0.000 ns TNS, and
recovery, removal, and minimum-pulse checks are clean. The fit uses 37,959 /
46,800 ALMs (81%), 5,261,744 / 7,331,840 memory bits (72%), 336 / 358 RAM
blocks (94%), and 157 / 376 DSP blocks (42%). The deployed RBF SHA-256 is
`965e15eabdba5260a8d3c795b39a39bebc707d31fee70e3af857ec7e03f94f82`.
## Board acceptance
The full 263-epoch board run passed every texture CRC, runtime-palette sum,
staging-record count, ordered drain, and fragment-drop gate. The scheduler
finished `rc=0`; the FPGA manager remained `operating`. The captured board
framebuffer has 235,346 nonzero words, bounds `(0,0)..(511,479)`, sum32
`0x19291e79`, and xor32 `0x05d6395b`.
Most importantly, the board framebuffer is bit-for-bit identical to the full
RTL simulation. Both MEM files have SHA-256
`6aa98a68ee59b5df8457b651baf12decbbb7b71c70c4029ff2cb144a3defa1de`,
and both PNG files have SHA-256
`709d6a1438355531b38723654f27b0b07e49524af10d33388fdbb744739cf7b3`.
Board versus the bilinearly resized PCSX2 frame measures MAE 20.4941 / RMSE
30.0163 with 199,104 lit RGB pixels. Ch416 therefore realizes its simulated
fidelity gain on hardware and is accepted.
@@ -0,0 +1,153 @@
# Ch417 — native 12.4 attribute gradients
## Objective
Close the main fidelity debt left by Ch416: coverage already samples native
12.4 GS screen coordinates at pixel centers, but color, Z, UV, and STQ planes
were still solved from integer vertex coordinates. This chapter makes the
attribute setup and evaluation use the same native geometry as coverage while
leaving draw population, texture state, blending, fog, and ordering unchanged.
## RTL cut
- Triangle orientation and the post-swap gradient determinant now use the full
12.4 vertex coordinates. Coordinate deltas are signed 17-bit values and the
determinant is signed 35-bit.
- The shared gradient numerators are signed 64-bit. Because the determinant is
in sixteenths-of-a-pixel squared, the numerator is shifted by 20 rather than
16 to produce the existing Q16.16 per-pixel gradient.
- The gradient FIFO sideband retains the full 35-bit determinant through the
time-shared setup engine and its single sequential divider.
- Color, Z, affine UV, and perspective STQ evaluation now use the native
pixel-center offset `((pixel << 4) + 8) - vertex12.4`. Products are shifted
by 20 to return to the existing attribute domains.
- Affine UV filtering consumes the resulting Q16.16 fractional texel bits.
This keeps bilinear sampling aligned with the corrected attribute plane.
Two correctness defects exposed by the new directed tests were repaired in
the same path:
- The packed attribute-prefetch word was missing the LPDDR ABE bit in its
unpack destination. Every following field was consequently displaced by
one bit, including the vertex-coordinate context. The unpack now includes
`gw_d_lpddr_abe` and exactly matches the stored word width.
- The opaque affine fragment-Z output retained the reset value instead of the
interpolated triangle Z. The emit sideband now selects `s2_interp_z` for an
active affine triangle and retains the flat sprite value otherwise.
## Directed evidence
- Legacy raster regression: 16/16 pixels PASS.
- New native-gradient triangle at `(0.75,0.75)`, `(3.75,0.75)`, and
`(0.75,3.75)`: exact R values 72/104/136 and exact Z values
1225/1325/1425 at the three covered pixel centers.
- Triangle interpolation regression: 31 interior color samples within the
established tolerance and 62 exact Z checks, PASS.
- Shared gradient divider regression: PASS.
- Affine textured triangle: 31/31 UV texels exact, PASS.
- Perspective PSMCT32 demo: 96/96 samples exact, PASS.
- Combined texture/alpha/Z demo: PASS.
- PSMT8 runtime-CLUT triangle: affine, perspective, ABE, and MODULATE paths
PASS.
- Authentic three-epoch integration: 5,909 fragments fed, 5,909 Z-passing,
zero drops, 0/307,200 final Z mismatches, and 0/5,858 color mismatches. The
corrected affine Z sideband admits two fragments that Ch416 rejected.
## Full-scene simulation gate
The 263-epoch scene uses the same bit-exact divide arithmetic as production.
`SH3_FAST_GRAD` selects a one-cycle setup path for quick framebuffer iteration;
the final pre-fit gate uses `SH3_BOARD_GRAD`, the production combinational
divider and its five-cycle registered settle window. The widened 64/35-bit
divide therefore has a 200 ns functional and STA contract at 25 MHz. The
sequential-divider implementation also passes its focused regression.
The full chronological gate passes:
- 1,145,412 fragments fed and 971,347 Z-passing;
- zero fragment drops, request-FIFO high-water mark 1, no color overflow, and
no AXI response errors;
- 263 markers and 263 ordered read drains (the first drain begins from the
reset frame, hence 262 frame-count advances);
- 0/307,200 final Z mismatches and 0/235,951 written-color mismatches.
The dedicated production-control rerun also passes all 263 epochs with
`GRAD_SEQ_DIVIDER=0` and `GRAD_DIV_CYCLES=5`: the same 1,145,412 fed /
971,347 passing fragments, zero drops, request-FIFO high-water 1, all 263
ordered drains, and exact final Z/color. Its framebuffer is byte-identical to
the fast arithmetic run (`3d22b442...`), proving that the five-cycle settle FSM
changes latency only.
The focused production board-path regression was corrected in Ch417: its old
`GRAD_BOARDPATH` branch accidentally selected the sequential divider. It now
selects the actual board combinational divider at five cycles and passes all
252/252 interior perspective samples with zero errors. The directed subpixel
gradient test also runs at five cycles and passes exact R/Z checks; its timeout
is derived from the 14-step gradient setup latency rather than a fixed delay.
The first owner Analysis & Synthesis attempt exposed a Quartus-specific width
limit before fitting: signed `64/64` inference prepended a sign-magnitude bit
and requested unsupported `LPM_WIDTHN=65` (Error 272006). Ch417b now divides
an explicitly unsigned 64-bit numerator magnitude by the native unsigned
35-bit determinant magnitude and restores the quotient sign afterward. This
retains signed truncation-toward-zero exactly while fixing the inferred LPM at
64/35. All focused gates and the complete 263-epoch production-control gate
were rerun after this change; the framebuffer remains byte-identical at
`3d22b442...`.
The compiled Verilator run completed the full gate. The original Icarus run
independently matched every count through epoch 59 before it was retired after
the faster run passed, including the first Z-rejecting epochs. The two engines
therefore agree across both the all-pass prefix and mixed pass/reject behavior.
The Ch417 framebuffer improves against the bilinearly resized PCSX2 reference
from Ch416 MAE 20.4941 / RMSE 30.0163 to MAE 20.1177 / RMSE 29.4268. Lit RGB
pixels rise from 199,104 to 199,827. Ch417 versus Ch416 measures MAE 2.9100 /
RMSE 9.4742, with 135,574 pixels unchanged exactly.
Artifact SHA-256 identities:
- Ch417 RTL framebuffer MEM:
`3d22b4421194d444677f3bc9f938e43fd856c6615b92585a10209093d982a625`;
- Ch417 RTL framebuffer PNG:
`b55b411cc5f6679e6a1ed708ef21ad5eaf5750abb233c6e433ab03594ec9c01e`.
## Owner fit gate
The owner-controlled Quartus 26.1 GUI flow completed successfully at
2026-07-18 07:21 EDT. Synthesis confirms that the repaired divider is exactly
the intended unsigned `LPM_WIDTHN=64`, `LPM_WIDTHD=35`, with no pipeline
inserted. Final STA attached the Ch417 setup=5/hold=4 destination constraint
to 49 routed `grad_result_q` registers (including retimed internal divider
launches), giving the widened cone its 200 ns functional contract.
Final signoff is clean:
- setup +0.140 ns;
- hold 0.000 ns;
- recovery +0.998 ns;
- removal +0.143 ns;
- minimum pulse width +0.200 ns.
The fit uses 38,461 / 46,800 ALMs (82%), 5,261,744 / 7,331,840 memory bits
(72%), 336 / 358 RAM blocks (94%), and 160 / 376 DSP blocks (43%). The split
RBF SHA-256 is
`ef0ecc585b6e0506f30f56bbea44ba56023a305e9d22052877b0e51b1b65e643`.
## Board acceptance
The timing-clean RBF was installed and loaded on the Terasic board; the FPGA
manager reported `operating` before and after the run. The previous installed
image was preserved remotely as `retroDE_ps2.pre_ch417b.core.rbf`.
The live 263-epoch scheduler completed with `rc=0`. Every texture CRC,
runtime-palette sum, staging-record count, ordered drain, AXI response, and
fragment-drop gate passed. The final capture contains 307,200 words, sum32
`0xc552c256`, xor32 `0x90a0ec16`, 235,280 nonzero words, and bounds
`(0,0)..(511,479)`.
Most importantly, the board framebuffer, fast RTL framebuffer, and complete
five-cycle production-control framebuffer are byte-for-byte identical. All
three have SHA-256
`3d22b4421194d444677f3bc9f938e43fd856c6615b92585a10209093d982a625`.
Ch417 is therefore accepted in hardware.
@@ -0,0 +1,85 @@
# Ch418 — authentic SH3 display presentation mapping (prefit)
## Objective
Fix the large presentation mismatch between the accepted Ch417 framebuffer and
the PCSX2 224139 frame without changing any rasterized framebuffer byte. Ch418
is scanout-only: it maps the captured GS display source onto the board's
640x480 HDMI/VGA active raster.
## Captured register evidence
The vendored PCSX2 `GSPrivRegSet` layout was used to decode the initial and all
eight per-vsync register snapshots in dump 224139. The active context is
DISPLAY2 (`PMODE.EN2=1`), stable in every snapshot:
- `DISPFB2 = 0x0001000000009000` (FBP rotates later), `FBW=8`, `PSM=1`,
`DBX=0`, `DBY=32`;
- `DISPLAY2 = 0x0037f9ff0203228c`, `DX=652`, `DY=50`, `MAGH=4`,
`MAGV=0`, `DW=2559`, `DH=895`.
Therefore the display source is 512 pixels wide (`FBW*64`) and 448 interlaced
source lines beginning at framebuffer line 32. DISPLAY2 expands each source
pixel to five VCKs (`MAGH+1`) across 2560 display clocks. Reducing that
captured display to the board raster gives the exact integer maps:
```
source_x = floor(output_x * 4 / 5) // 0..639 -> 0..511
source_y = 32 + floor(output_y * 14 / 15) // 0..479 -> 32..479
```
## RTL change
`gs_lpddr_scanout_lb` now has opt-in divider-free presentation mapping:
- a five-state horizontal phase accumulator implements 512 -> 640;
- a fifteen-state vertical phase accumulator implements 448 -> 480;
- vertical prefetch starts at captured `DBY=32`;
- buffer parity, prefetch throttling, and underflow checks use mapped source y;
- all default parameters retain the legacy 1:1 behavior.
Only `GS_SH3_LPDDR_FB_640` enables the mapping in the DE25 top. The OSD remains
in the 640x480 output domain and the LPDDR framebuffer/dump remains byte-exact
to Ch417.
The mapper contains no `/` or variable multiply in synthesizable RTL. Its
datapath is one small phase decrement and one coordinate increment per output
pixel/line, both in the 25 MHz video domain.
## Prefit verification
- New two-axis scanout oracle: 600/600 RGB pixels exact, including horizontal
repeats, vertical repeats, DBY offset, alternating line buffers, zero
underflow, and zero AXI read errors.
- Legacy PSMCT16 line-buffer regression: PASS, underflow=0, read errors=0.
- Legacy PSMCT32 line-buffer/concurrency regression: 49,152 pixels exact,
underflow=0, read errors=0, 325/325 probes good.
- The actual QSF profile macros plus `USE_QSYS_TOP` elaborate cleanly under
Icarus (unavailable vendor IP treated as black boxes); no RTL binding or
syntax errors.
- `git diff --check` and the preview utility's Python compile pass.
Expected HDMI preview:
`sim/data/top_psmct32_raster_demo/sh3_zsrt139f17_ch418_scanout.png`
Preview SHA-256:
`b710aa7a09db6992faacdaa5a6257d490f1d4fa0112f6c6d20fc388f4b580a63`
The preview moves the already-rendered lamppost and character onto the PCSX2
landmark positions. Full-frame RGB MAE rises from 20.12 to 21.02 because the
board is still missing large bright/background draws that the transform now
spreads over more output pixels; that global statistic is a coverage metric at
this stage, not an alignment metric. The mapping is justified by the captured
registers and visible landmark alignment, not by claiming a false MAE win.
## Owner fit gate
Ready for the owner-controlled Quartus 26.1 GUI compile. Required acceptance:
- Analysis & Synthesis succeeds with no inferred display divider;
- setup, hold, recovery, removal, and minimum pulse all close;
- after deployment, the unchanged 263-epoch board run still passes and the
framebuffer dump remains byte-identical to Ch417;
- HDMI shows the 512x448 captured source presented across 640x480.
@@ -0,0 +1,158 @@
# Ch419 — request-path timing root cut plus rabbit/bench completion (prefit)
## Objective
Make one owner compile carry two concrete advances:
1. remove the actual Ch418 310 MHz setup topology instead of rerolling placement;
2. append a bounded set of authentic missing rabbit/bench draws to the accepted
Ch417 framebuffer, without regenerating or perturbing its 263 accepted epochs.
Ch418's captured 512x448-to-640x480 presentation mapping is retained unchanged.
## Ch418 fit diagnosis
The 2026-07-18 owner fit completed placement and routing but failed only the
EMIF setup domain:
- setup WNS `-0.388 ns`, TNS `-7.218 ns` at 310 MHz;
- hold `0.000 ns` and every other reported timing check clean;
- 38,328 / 46,800 ALMs (82%), 336 / 358 RAM blocks (94%).
The first nine setup endpoints are the same structural family:
```
u_zc_emit|req_rd
-> u_zc_emit|u_req|mem...ram_block...~reg1 (RAM enable)
```
`req_rd` has netlist fan-out 747. The worst paths contain no logic levels:
`3.370 ns` data delay is almost entirely the `3.035 ns` interconnect route to
the wide request FIFO's physical RAM enables. This is not a scanout-mapping
path and is not a seed-quality diagnosis.
The next distinct setup family is `-0.331 ns` inside `u_lpddr_rd_arb`, from a
duplicated encoded `grant` bit back into `grant` through response-completion
selection.
## Structural timing cuts
### Request FIFO RAM enable
In `gs_async_fifo` registered-read mode, the inferred synchronous RAM head is
now sampled every read clock instead of qualifying every physical RAM bank with
`rd`. The read pointer still advances only on an accepted pop and `rdata`
retains its one-cycle registered-read contract.
This removes `req_rd` from the 747-load RAM-enable net entirely; it is a
topology change, not a fitter hint or another seed roll.
### Read-arbiter grant release
`gs_lpddr_rd_arb` now registers the terminal accepted `RLAST` handshake and
releases ownership on the following cycle. `ARVALID/ARREADY` are masked after
the address handshake and throughout the deliberate release bubble, so a
selected requester cannot accidentally issue a second address.
This breaks the requester-`RREADY` selection cone out of the encoded grant
register's direct D path while preserving AXI ownership through response
acceptance.
## Fidelity batch
The accepted `zsrt139f17` fixture remains byte-for-byte intact. Ch419 appends
21 authentic opaque PSMT8 draw runs missing between the accepted group heads in
the PCSX2 rabbit/bench region:
```
71361, 71574, 72000, 72213, 72639, 72852, 73278,
73491, 73917, 74130, 74556, 74769, 75195, 75408,
75834, 76047, 76473, 76686, 77112, 77325, 77538
```
Capacity-safe grouping produces 20 appended epochs and 7,496 independently
covered reference pixels. The merged `zsrt139f18` scene has 283 epochs total.
Persistent authentic Z decides visibility against the accepted base.
This is deliberately bounded. An exhaustive supported-draw plan expanded to
6,977 epochs and was rejected as operationally useless before deployment.
## Prefit verification
The full production-control replay uses the exact board gradient-divider
selection and settle FSM, the modified request FIFO, the modified read arbiter,
one persistent color/Z memory, and all 283 chronological epochs:
- 1,177,489 fragments fed;
- 984,845 fragments passed authentic Z;
- 283 ordered markers and 283 fresh drains;
- zero fragment drops, color overflow, or AXI response errors;
- final Z mismatch `0 / 307,200`;
- final color mismatch `0 / 235,963` written pixels;
- testbench `PASS` with `errors=0`.
Final framebuffer SHA-256:
`a88f1f6c926dd0db2b72ae2873c399e7dc5666f8003190184c34d360b13a5f44`
Relative to the accepted Ch417 production replay, the tail changes 4,446
framebuffer pixels at `x=342..416, y=214..363`, the intended right-center
rabbit/bench region. Twelve pixels add previously empty coverage; the rest
replace Z-visible surface color/detail. Ch417's prior hash was:
`3d22b4421194d444677f3bc9f938e43fd856c6615b92585a10209093d982a625`
Supporting regressions also pass:
- async FIFO: 3,284 exact writes/reads, no duplicate/drop;
- read arbiter contention/priority: PASS;
- Z/color emit: 1,500 fragments across three epochs, exact;
- horizontal-stretch scanout: 600 pixels exact, no underflow/read errors;
- PSMCT32 linebuffer: 49,152 pixels exact, no underflow/read errors;
- full-frame scanout: 105,968 pixels checked, exact beat count, no errors;
- concurrent scanout/reload/probe: 16,384 pixels exact;
- host scheduler build and complete 283-epoch asset dry-run: PASS;
- board staging set: 323 unique assets, 21,733,472 bytes (duplicates removed);
- relevant `git diff --check`: PASS.
## Owner fit gate
This is ready for one owner-controlled Quartus 26.1 GUI compile. It is not
claimed timing-clean until that report exists.
Acceptance for the fit:
- the `req_rd -> request RAM enable` family is absent (not merely moved down);
- the encoded read-arbiter `grant -> grant` response family is absent or clean;
- setup, hold, recovery, removal, and minimum pulse all close;
- synthesis still infers the request FIFO RAM and remains inside device limits.
After a clean fit, deploy the RBF, stage `zsrt139f18`, run the 283-epoch board
scene, and require board framebuffer equality with the production replay hash
above before accepting the chapter.
## Owner fit result — 2026-07-18 11:07
The fit was successful, but setup remained narrowly red:
- setup improved from `-0.388 ns / -7.218 ns` to
`-0.115 ns / -1.150 ns`;
- hold improved to `+0.001 ns`; all other timing checks remained clean;
- ALMs decreased from 38,328 to 38,234; RAM use remained 336 / 358.
Both Ch419 targets disappeared completely from the violated top ten:
- no `req_rd -> request RAM enable` path;
- no encoded read-arbiter `grant -> grant` path.
All ten remaining violations are one newly exposed zero-logic family:
```
u_zc_emit|u_req|rbin[6]
-> u_zc_emit|u_req|mem...ram_block...portbaddr[6]
```
The launch bit has fan-out 713. Of the `3.192 ns` data delay, `2.892 ns`
(91%) is routing. Ch419 therefore succeeded as a structural peel, but is not
accepted for deployment. Ch420 applies the corresponding dedicated read-RAM
address stage before the next owner fit.
@@ -0,0 +1,81 @@
# Ch420 — request FIFO dedicated read-address stage (prefit)
## Trigger
Ch419 removed both intended timing families and reduced setup from
`-0.388 ns / -7.218 ns` to `-0.115 ns / -1.150 ns`. Its completed fit exposed
one uniform remaining family: request FIFO `rbin[6]` directly driving the wide
banked RAM's `portbaddr[6]` network.
This is not a new functional cone and not a reason to reroll placement. The
paths have zero logic levels, fan-out 713, and spend `2.892 ns` of `3.192 ns`
in one interconnect route.
## Structural cut
Registered-read mode in `gs_async_fifo` now has a dedicated RAM-facing
`raddr_q` stage, marked `dont_merge, preserve` so it cannot collapse back into
the binary CDC pointer.
`raddr_q` captures `rbin_nxt` while the synchronous RAM reads the prior
`raddr_q`. Before each edge, `raddr_q` equals the current head address, so the
returned entry and accepted-pop latency are unchanged for isolated and
back-to-back reads. The added boundary only separates pointer/empty logic from
the physical address fan-out and gives the fitter a placeable/duplicable RAM-
local launch register.
The stage intentionally has no reset or clock enable. The FIFO remains empty
for multiple read clocks while the write pointer crosses its two synchronizer
flops, so the address has settled to zero before any legal pop. Avoiding reset
and enable prevents recreating the prior high-fanout RAM-control failures.
## Prefit verification
- async FIFO randomized CDC test: 3,284 writes and reads exact, no duplicate or
drop;
- Z/color emit: 1,500 fragments across three epochs exact, including directed
`ZTST=ALWAYS`;
- concurrent scanout/reload/probe: 16,384 pixels exact;
- full 283-epoch production-control replay: PASS;
- 1,177,489 fragments fed, 984,845 passed Z, zero drops/errors;
- final Z mismatch `0 / 307,200` and color mismatch `0 / 235,963`;
- framebuffer SHA-256 remains exactly
`a88f1f6c926dd0db2b72ae2873c399e7dc5666f8003190184c34d360b13a5f44`;
- relevant `git diff --check`: PASS.
## Owner fit gate
Ready for one owner-controlled Quartus 26.1 GUI compile. Acceptance requires:
- the direct `rbin -> request RAM portbaddr` family is absent, not merely
exchanged for an equally long `rbin -> raddr_q` or `raddr_q -> portbaddr`
family;
- setup, hold, recovery, removal, and minimum pulse all close;
- request FIFO RAM inference and the 336 / 358 RAM-block budget remain intact.
Do not deploy the RBF until this report is clean. After timing acceptance, run
the already-staged Ch419 283-epoch board scene and require exact equality with
the production framebuffer hash above.
## Owner fit result — 2026-07-18 11:59
The fitter preserved `raddr_q`, and the direct `rbin -> portbaddr` family is
absent from the violated paths. Setup did not close:
- WNS `-0.273 ns`, TNS `-1.613 ns`;
- hold `0.000 ns`; all other timing checks clean;
- 38,227 ALMs and 336 / 358 RAM blocks.
The ten reported violations are the next request boundary, not either half of
the new address stage. They launch at the request RAM output and terminate at
the two packet-type validity registers:
```
u_zc_emit|u_req|mem...portbdataout
-> req_stage_frag_valid / req_stage_marker_valid
```
The worst path is `3.443 ns`, with the RAM output `uTco` accounting for
`1.396 ns` and three downstream decode/routing levels accounting for the rest.
Ch421 removes packet contents from stage-occupancy control and captures the
complete packet across one explicit boundary.
@@ -0,0 +1,70 @@
# Ch421 — complete request-packet boundary (prefit)
## Trigger
Ch420 successfully removed the direct request-pointer-to-RAM-address family.
Its fit exposed ten paths from the wide request RAM's registered data output,
through scene-bit decode, into separate fragment/marker valid registers. The
worst was `-0.273 ns`; no address-stage path appeared in the violated top ten.
## Structural cut
`gs_lpddr_zc_emit` now captures all `PW` request bits, including the scene bit,
into one `req_stage_packet` register. A single `req_stage_valid` occupancy bit
is driven only by the already-registered `req_read_pending` control.
Fragment versus marker selection is decoded after that complete packet
boundary:
```
req_stage_frag_valid = req_stage_valid && !req_stage_packet.scene
req_stage_marker_valid = req_stage_valid && req_stage_packet.scene
```
Therefore no request RAM output bit feeds stage occupancy or two competing
valid-register D cones. The packet register intentionally has no reset;
`req_stage_valid=0` makes it unobservable until a complete read is captured.
Pop, pending, consume, marker ordering, and external latency are unchanged.
## Prefit verification
- async FIFO randomized CDC test: 3,284 writes/reads exact;
- Z/color emit: 1,500 fragments exact across three epochs;
- concurrent scanout/reload/probe: 16,384 pixels exact;
- full 283-epoch production-control replay: PASS;
- 1,177,489 fragments fed, 984,845 passed Z, zero drops/errors;
- final Z mismatch `0 / 307,200`, color mismatch `0 / 235,963`;
- framebuffer SHA-256 remains exactly
`a88f1f6c926dd0db2b72ae2873c399e7dc5666f8003190184c34d360b13a5f44`;
- relevant `git diff --check`: PASS.
## Owner fit gate
Ready for one owner-controlled Quartus 26.1 GUI compile. Acceptance requires:
- no request RAM output path to stage occupancy/fragment/marker valid state;
- any RAM-output-to-`req_stage_packet` data path is timing-clean;
- setup, hold, recovery, removal, and minimum pulse all close;
- request FIFO RAM inference remains intact at the existing device budget.
Do not deploy the Ch420 RBF. If Ch421 closes, deploy that RBF and execute the
already-verified 283-epoch board scene before accepting the chapter.
## Owner fit result — request path closed, design path exposed
The owner-controlled Quartus 26.1 GUI fit completed successfully. Ch421 did
close the intended 310 MHz EMIF/request family:
- EMIF setup WNS `+0.155 ns`, TNS `0`;
- EMIF hold slack `0.000 ns` and all other timing classes clean;
- no request FIFO path appeared in the violated setup paths;
- 38,332 ALMs (82%), 55,712 registers, 336/358 RAM blocks (94%), 160 DSPs.
The fit is nevertheless **not deployable** because placement exposed a separate
25 MHz design-clock failure: setup WNS `-1.993 ns`, TNS `-14.856 ns`. All ten
reported paths launch at `u_tex|g_bilinear.tap[0][8/9]` and terminate at
`raster_pixel_color_q[16/18]`. The worst path contains 30 logic levels and
41.955 ns of data delay: both bilinear interpolation stages plus TEX0 vertex
modulation were being evaluated in one cycle. That is an independent, real
combinational boundary defect, not a reason to undo the now-clean request
packet cut. Ch422 addresses it structurally.
@@ -0,0 +1,77 @@
# Ch422 — registered bilinear output boundary (prefit)
## Trigger
Ch421 closed the 310 MHz EMIF/request family at `+0.155 ns`, but its owner GUI
fit exposed a design-clock path from a captured bilinear tap through both lerp
levels, the texture output mux, TEX0 vertex modulation, and finally
`raster_pixel_color_q`. The worst of ten identical-family paths had 30 logic
levels, 41.955 ns data delay, and `-1.993 ns` slack against the 40 ns clock.
## Root cause
`gs_texture_unit` already captured `tex_color_blend` into `tex_color_hold` while
leaving `BS_DONE`. However, during that same `BS_DONE` cycle its external
`tex_color` selected the live combinational blend and `out_valid` asserted.
Consequently, the perspective palette-bilinear emit path could consume the
unregistered value and append four channel-modulation multipliers before the
`raster_pixel_color_q` register. The intended hold register was present but
was bypassed at the only cycle that mattered for this caller.
## Structural cut
The bilinear FSM now has a distinct `BS_OUT` state after `BS_DONE`:
1. `BS_DONE` evaluates the unchanged two-stage bilinear math and captures it in
`tex_color_hold`;
2. `BS_OUT` asserts `out_valid` and exposes only `tex_color_hold`;
3. the FSM then returns to `BS_IDLE`.
This adds one internal sampler cycle but changes no pixel arithmetic and no
external handshake. Existing consumers already wait on `out_valid` or the
`busy` level, so their natural stalls absorb the cycle. The former 30-level
tap-to-emit path is split at `tex_color_hold`: bilinear arithmetic ends there,
while TEX0 modulation starts from a register on the following cycle.
## Prefit verification
- standalone PSMCT32/PSMT8/PSMT4 bilinear suite: all 12 cases PASS;
- tile PSMCT32 bilinear integration: PASS;
- tile palette-bilinear integration: PASS;
- full 283-epoch production-control replay: PASS;
- 1,177,489 fragments fed, 984,845 passed Z, zero drops/errors;
- final Z mismatch `0 / 307,200`, color mismatch `0 / 235,963`;
- framebuffer SHA-256 remains exactly
`a88f1f6c926dd0db2b72ae2873c399e7dc5666f8003190184c34d360b13a5f44`.
## Owner fit gate
Ready for one owner-controlled Quartus 26.1 GUI compile. Acceptance requires:
- no `g_bilinear.tap[*] -> raster_pixel_color_q[*]` setup family;
- bilinear tap paths terminate at `tex_color_hold` and close at 25 MHz;
- the already-clean 310 MHz EMIF/request family remains non-negative;
- setup, hold, recovery, removal, and minimum pulse all close;
- RAM inference and device fit remain within the existing budget.
Do not deploy the timing-unclean Ch421 RBF. Deploy and board-test Ch422 only if
the complete timing report is clean.
## Owner fit result
The owner-controlled Quartus 26.1 GUI fit confirmed that the Ch422 structural
cut worked. The design clock closed at `+8.604 ns`, the reference clock closed
at `+7.564 ns`, and hold, recovery, removal, and minimum-pulse checks were
clean. The former bilinear tap-to-`raster_pixel_color_q` family was absent.
The only remaining failure was the 310 MHz EMIF clock at WNS `-0.012 ns`, TNS
`-0.047 ns`, across six endpoints. Five endpoints were color-align tuple data
register enables (`ca_wdata_q[21/24/25/28/30]|ena`) reached through
`ca_tail -> ca_full -> feed_ok`. The sixth was
`req_stage_packet[79]|ena`, whose enable cone included the synchronized EMIF
calibration reset. These are independent control-to-data-enable artifacts;
Ch423 removes both in one combined structural closeout.
Ch422 is therefore functionally verified and its intended timing family is
closed, but its RBF remains non-deployable because the complete fit was not
timing-clean.
@@ -0,0 +1,111 @@
# Ch423 — control-free data stages (prefit)
## Trigger
Ch422 eliminated the design-clock bilinear critical path and left only six
310 MHz EMIF setup endpoints: WNS `-0.012 ns`, TNS `-0.047 ns`. The actual
post-fit netlist showed that all six ended at data-register clock enables, not
at the RAMs or payload datapaths themselves:
- five `ca_wdata_q[*]|ena` endpoints were driven by the
`ca_tail -> ca_full -> feed_ok` control cone;
- `req_stage_packet[79]|ena` was driven through the async-reset request-stage
process, pulling the high-fanout synchronized EMIF calibration reset into
the payload register's enable implementation.
This chapter handles both members of that same root class in one fit cycle.
## Structural cuts
### Request packet stage
`req_stage_packet` now has its own non-reset `always_ff` block and captures
only when the local registered `req_read_pending` pulse is high. Occupancy
and observability remain controlled by the separately reset
`req_stage_valid`. This preserves the request FIFO protocol while preventing
the asynchronous reset cone from being folded into the wide payload register
enable.
### Color-align RAM write tuple
`ca_waddr_q` and `ca_wdata_q` now capture the prospective write tuple every
cycle without reset or `feed_ok` enables. The separately reset `ca_wr_q`
continues to be asserted only for an accepted fragment. On the next edge the
RAM consumes the prior captured tuple exactly when the prior `feed_ok` raised
`ca_wr_q`, preserving address, payload, and commit ordering while removing the
FIFO-full comparison from 82 data-register enables.
No clock constraint, multicycle exception, frequency reduction, or functional
pixel change was introduced.
## Prefit verification
- asynchronous request FIFO regression: 3,284 exact transfers, PASS;
- Z/color emitter regression: 1,500 fragments, zero errors, PASS;
- LPDDR scanout-concurrency regression: 16,384 exact pixels, PASS;
- full 283-marker / 282-epoch production-control replay: PASS;
- 1,177,489 fragments fed, 984,845 passed Z, zero drops/errors;
- final Z mismatch `0 / 307,200`, color mismatch `0 / 235,963`;
- framebuffer SHA-256 remains exactly
`a88f1f6c926dd0db2b72ae2873c399e7dc5666f8003190184c34d360b13a5f44`.
## Owner fit gate
Ready for one owner-controlled Quartus 26.1 GUI compile. Inspect the actual
post-fit timing and synthesis reports. Acceptance requires:
- no `ca_tail -> ca_wdata_q[*]|ena` or
`ca_tail -> ca_waddr_q[*]|ena` setup family;
- no synchronized EMIF calibration-reset / `lock_sync` path to
`req_stage_packet[*]|ena`;
- the Ch422 bilinear family remains absent and the design clock stays clean;
- setup, hold, recovery, removal, and minimum pulse are all non-negative;
- color-align RAM inference and overall resource use remain intact.
Do not deploy the Ch422 RBF. If the complete Ch423 report is clean, its RBF is
the next board-deployment candidate.
## Owner fit result — 2026-07-18 17:24
The owner-controlled Quartus 26.1 GUI fit is signoff-clean:
- EMIF setup WNS `+0.025 ns`, TNS `0.000 ns`;
- 25 MHz design setup WNS `+4.124 ns`, TNS `0.000 ns`;
- reference-clock setup WNS `+8.056 ns`, TNS `0.000 ns`;
- hold has zero violations (reported minima `0.000 ns`);
- recovery minimum `+1.071 ns`, removal minimum `+0.167 ns`;
- minimum-pulse-width minimum `+0.200 ns`;
- Timing Analyzer completed successfully with zero errors.
The retired `ca_tail -> ca_wdata_q[*]|ena`, synchronized calibration-reset to
`req_stage_packet[*]|ena`, and Ch422 bilinear families are absent from the
setup top paths. The color-align memory remains an inferred simple dual-port
M20K (`8,832` bits). Final resources are 38,241 / 46,800 ALMs (82%),
5,261,744 block-memory bits (72%), and 336 / 358 RAM blocks (94%).
The accepted deploy artifact is `output_files/retroDE_ps2.core.rbf`, 4,120,576
bytes, SHA-256:
`17d1c1b49aac4d893ed56285cf0703bf375db2ee9f472ea36862cca376ae63fe`
## Board acceptance
The RBF was checksum-verified on the DE25-Nano and loaded successfully through
`core_loader.sh`; `fpga0` reported `operating`. Live bridge preflight passed:
- `CORE_ID = 0x50533200`, ABI `0x00000100`, status `0x0000001f`;
- frame counter advanced by 145 over two seconds;
- raster-overflow count and delta were both zero.
The exact 283-epoch `sh3_zsrt139f18` production fixture then completed with
all texture CRC, CLUT sum, staged-list, fresh-drain, and record-count gates
passing. Every epoch reported zero fragment drops and the scheduler ended
with `DONE rc=0`.
The board dumped all 307,200 framebuffer words. Its SHA-256 is
`a88f1f6c926dd0db2b72ae2873c399e7dc5666f8003190184c34d360b13a5f44`,
identical to the full production simulation. `cmp` confirms byte-for-byte
equality. Ch423 is therefore timing-, simulation-, and hardware-accepted and
is the new deployment baseline.
@@ -0,0 +1,67 @@
# Ch424 — restore the full scene population under native 12.4 coverage
## Root cause
Ch416 was described as a one-variable native-subpixel conversion of Ch415,
but its fixture was regenerated from only 113 draw IDs. Ch415 actually fed
766 captured draws grouped into 254 compatible state runs. The 113 IDs were
only the surviving groups' representative indices, so treating them as the
source draw list silently discarded most of the scene. The resulting
software reference already contained the long spike/band geometry later seen
bit-exactly in simulation and on the board; this was not an FPGA timing fault.
The `--legacy-strip-kicks` compatibility switch also ignored captured ADC
kick suppression. Ch424 uses the parsed kick semantics and does not enable
that switch.
## Fixture repair
`sh3_zsrt139f19` starts from the audited 766-draw / 254-group plan in
`/tmp/sh3_opaque_qclip_plan.json`, retains authentic chronological grouping,
and enables the already-proven production feature set:
- authentic Z, color modulation, zero-color fog fold, and bilinear reference;
- native GS XYZ 12.4 coordinates and pixel-center coverage;
- authentic scissor clipping and normalized full-region clamp;
- runtime CLUT staging and 14,500-fragment capacity epochs;
- parsed ADC/kick behavior, with no legacy strip override.
The repaired fixture schedules 125 epochs. Its software reference restores
the coherent booth, floor, lamp, character, fence, and bench geometry. With
the accepted Ch418 scanout mapping, comparison against the PCSX2 224139 frame
is MAE 18.5560 / RMSE 26.2740. The raw 640x480 buffer is MAE 21.0802 / RMSE
32.3348.
## Acceptance gates
- Production-gradient full RTL replay must finish all 125 epochs with zero
drops and exact final Z/color comparisons.
- The unchanged, timing-clean Ch423 RBF must pass the same fixture on hardware.
- The board framebuffer must match the production-gradient RTL framebuffer
bit for bit before Ch424 becomes the visible baseline.
## Production RTL acceptance
The production registered-gradient Verilator replay completed all 125 epochs:
157,208 fragments were fed, 138,825 passed Z, fragment drops remained zero,
and the final comparisons reported 0/307,200 Z mismatches and 0/114,550 color
mismatches. Marker/drain counts were complete, `col_ovf=0`,
`bresp_err=0`, and the testbench finished PASS.
The raw RTL frame measures MAE 22.9740 / RMSE 33.6203 against PCSX2. Through
the accepted Ch418 scanout mapping it measures MAE 20.9469 / RMSE 29.7626.
Unlike the lower scalar score previously claimed for Ch416, this frame also
passes the essential visual gate: its scene topology is coherent rather than
dominated by false long triangles.
## Board acceptance
No new fit was required. The timing-clean Ch423 RBF ran the new assets and
passed every one of the 125 texture CRC, runtime-palette, staging-record,
drain, and zero-drop gates. The board dumped 307,200 words with 114,504
nonzero pixels, bounds `(0,10)..(511,479)`, sum32 `0x42ee1faa`, and xor32
`0x0dc19c66`; HDMI was switched to the resulting line-buffer scanout.
The board and production RTL framebuffer dumps are byte-identical. Both have
SHA-256 `2bd7a81d3650c00b1397d5196713634bbd4c87a5e607b75bcf0df346cb7b3126`.
Ch424 is accepted as the new visible baseline.
@@ -0,0 +1,34 @@
# Ch425 — post-process bisection closeout
## Objective
Append the captured post-process families to the accepted Ch424 scene and
admit them only if the production replay stays exact and the PCSX2 fidelity
score improves.
## Candidates and isolation
`zsrt139f20` concatenated the 125-epoch Ch424 scene, the 20-epoch PSMCT32
darken pass, the 212-epoch Ch405 PSMT8 alpha-fan fixture, and the 18-epoch
PSMT4 pass. The 375-epoch production replay was functionally clean but its
frame was catastrophically white (mapped MAE 165.23 / RMSE 189.17), so it was
not deployed.
Epoch-boundary replays isolated each family:
- Ch424 + CT32 darken: mapped MAE 21.3646 / RMSE 30.1358, worse than the
Ch424 baseline 20.9469 / 29.7626.
- Ch424 + CT32 + old alpha fans: mapped MAE 161.9749 / RMSE 185.4484; this is
the white-frame source.
- Ch424 + PSMT4 only (`zsrt139f21`): mapped MAE 23.4472 / RMSE 32.3453 and
most scene detail was erased.
Every isolation replay had zero fragment drops and exact RTL Z/color
scoreboards. The problem was therefore captured-state interpretation, not
timing, loss, or board nondeterminism. Ch425 rejected all three fixtures as
generated and left the live board on Ch424 while the alpha source was audited.
## Status
Closed as a diagnostic chapter. Ch426 supersedes the alpha result after
repairing the fixture's RGBAQ alpha packing.
@@ -0,0 +1,63 @@
# Ch426 — authentic vertex-alpha repair and atmospheric composite
## Root cause
The scheduler generator's `--auth-color-tfx` path preserved captured RGB but
called `bake.rgbaq_with_q()`, a legacy helper that always writes alpha
`0xff`. The first captured alpha fan actually uses vertex alpha `0x03` at
the anchor and `0x00` at the remaining vertices. Under MODULATE+TCC, forcing
that alpha to `0xff` made the authentic additive blend roughly two orders of
magnitude stronger and produced Ch425's white rectangles.
`gs_make_sh3_scheduler_fixture.py` now packs the complete captured 32-bit
RGBA word when authentic color is enabled. It also fails closed if emitted
RGBAQ alpha differs from the canonicalized source vertex. Legacy fixtures
without authentic color retain their old opaque-alpha behavior.
## Repaired fixture
`zsrt139f22` regenerates the same 53 visible PSMT8 fan draws used by Ch405,
in three exact-state groups, with:
- captured RGBAQ alpha;
- native GS 12.4 screen coordinates;
- parsed kick semantics, authentic scissor, and framebuffer clipping;
- authentic Z/TEST/ZMSK and ALPHA state;
- runtime CLUT, MODULATE color, and production bilinear sampling.
The repaired geometry expands to 218 one-triangle bounded epochs. The final
chronological candidate `zsrt139f23` is Ch424's 125 epochs followed by the 20
authentic CT32 darken epochs and the 218 repaired alpha epochs: 363 total.
The rejected PSMT4 pass is not included.
## Production RTL acceptance
The complete replay passed:
- 1,009,406 fragments fed; 990,062 passed;
- 363 markers, zero fragment drops, zero color overflows, zero AXI errors;
- Z mismatch 0/307,200 and color mismatch 0/245,314;
- framebuffer SHA-256
`ccf574d7e7ab06a519488c187da08b8c56dce5b923b6909dafba11cad6b4ce4a`.
Against the PCSX2 frame, the accepted display mapping measures MAE 20.0713 /
RMSE 28.9639, improving on Ch424's 20.9469 / 29.7626. The new image adds a
soft captured atmospheric veil without the false white rectangles.
A controlled no-darken replay (`zsrt139f24`, Ch424 + repaired alpha) also
passed exactly but measured 20.0890 / 29.0439. The chronological CT32 pass
therefore provides a small measured improvement as well as preserving the
captured order; `zsrt139f23` remains the accepted candidate.
## Silicon acceptance
No new Quartus compile was needed. The timing-clean Ch423 RBF
(`17d1c1b49aac4d893ed56285cf0703bf375db2ee9f472ea36862cca376ae63fe`)
ran all 363 epochs on the DE25. Every texture CRC, palette checksum, staged
record count, ordered drain, and zero-drop gate passed; the host returned
`rc=0` and enabled the HDMI line-buffer source.
The board dumped all 307,200 words with 196,889 nonzero pixels, bounds
`(0,0)..(511,479)`, sum32 `0xb59376d4`, and xor32 `0x28c61f44`. Board and RTL
framebuffer dumps are byte-identical and share the SHA-256 above. Ch426 is
accepted on silicon and is the current live HDMI baseline.
@@ -0,0 +1,64 @@
# Ch427 — complete authentic alpha family
## Objective
Replace Ch426's hand-selected 53-draw alpha subset with every captured PSMT8
alpha-fan draw in the chronological post-process range, while retaining strict
RTL and silicon equality and admitting the result only on measured PCSX2
fidelity improvement.
## Precision control
Before expanding the family, a controlled replay increased the shared
reciprocal table from 11 to 13 bits. It changed 32,173 output pixels, but the
mapped PCSX2 score was effectively flat: MAE 20.0716 / RMSE 28.9629 versus
20.0713 / 28.9639 at 11 bits. The extra table cost therefore had no useful
fidelity return and was rejected.
## Complete family and epoch packing
The captured interval contains 93 authentic PSMT8 alpha-fan draws, not the 53
draws selected by the older Ch405 fixture. The omitted 40 include captured
anchor alpha as high as `0x11`; they are real atmospheric contributions, not
transparent no-ops.
An initial one-triangle expansion produced 408 alpha epochs. It also exposed
a valid marker corner: a fully Z-rejected epoch emits no color write, leaving
the host's stale `frame_drained` observation high and preventing a new drain
edge. The acceptance gate was not weakened. Instead, the same ordered draws
were safely capacity-packed into 129 productive alpha epochs, with maximum
coverage 14,497 fragments. `zsrt139f28` is Ch424's 125 scene epochs, the 20
captured CT32 darken epochs, and those 129 alpha epochs: 274 total.
## Production RTL acceptance
The complete replay passed:
- 1,325,665 fragments fed; 1,306,030 passed;
- 274 markers and drains;
- zero fragment drops, color overflows, and AXI errors;
- Z mismatch 0/307,200 and color mismatch 0/245,423;
- framebuffer SHA-256
`2aaaabfbe4054b6445458eb43dffb5bcb9d7989c3121875c64fcf0839478d976`.
Against the PCSX2 reference using the accepted display mapping, the result is
MAE 18.9384 / RMSE 27.6464. That is a material improvement over Ch426's
20.0713 / 28.9639. The complete family changes 142,182 pixels relative to the
53-draw subset and restores broad captured atmosphere without the former
white-frame failure.
## Silicon acceptance
No new Quartus compile was required. The timing-clean Ch423 RBF
(`17d1c1b49aac4d893ed56285cf0703bf375db2ee9f472ea36862cca376ae63fe`)
ran all 274 epochs on the DE25. Every texture CRC, palette checksum, staged
record count, ordered drain, and zero-drop gate passed; the host returned
`rc=0` and enabled the HDMI line-buffer source.
The board dumped all 307,200 words with 212,213 nonzero pixels, bounds
`(0,0)..(511,479)`, sum32 `0x9c2fcd95`, and xor32 `0x6e7390f3`. Board and RTL
framebuffer dumps are byte-identical and share the SHA-256 above. The accepted
board image is preserved as
`captures/gs/silenthill3/extracted/sh3_zsrt139f28_ch427_board.png`.
Ch427 is accepted on silicon and is the current live HDMI baseline.
@@ -0,0 +1,83 @@
# Ch428 — missing blended population and logical 256x256 PSMT8
## Objective
Recover captured blended geometry omitted from the Ch427 baseline without
returning to draw-count churn: census the missing PSMT8 population, preserve
dump chronology, reject families that do not improve the PCSX2 comparison,
and require production RTL/silicon identity for any accepted addition.
## Population census
The frame contains two previously omitted PSMT8 ABE families before the final
opaque scene draws:
- 70 triangle-strip draws interleaved with the 766 accepted opaque strips;
- 240 triangle-list draws using a logical 256x256 PSMT8 texture, concentrated
on the foreground character at draw indices 172601 through 175947.
The coverage planner now supports explicit ABE inclusion, primitive subsets,
and maximum draw-index boundaries while retaining its historical opaque-strip
default.
## Rejected interleaved-strip experiment
`zsrt139f30` inserted all 70 missing ABE strips in authentic dump order and
then retained the accepted CT32 and 93-draw alpha families. Its 307-epoch
production RTL replay passed every structural gate and produced framebuffer
SHA-256
`5ec8549ab3b88d429403e5abe1a5074bd329eac576ed87421174b92c1dfe28ae`.
The addition changed only 836 pixels and slightly worsened the mapped PCSX2
score to MAE 18.9599 / RMSE 27.6639 from Ch427's 18.9384 / 27.6464. It was
therefore rejected and never deployed to the board.
## Logical 256x256 texture support
The 240 triangle-list draws all share the same captured state and texture.
The fixture generator now preserves TEX0 logical TW/TH=8 while padding the
256x256 PSMT8 source row-by-row into the existing physical 512-texel LPDDR
cache stride. No RTL or RBF change is required. Fail-closed generation
checks require every logical row to match the reconstructed GS local memory
byte-for-byte and require the entire unused right/lower cache padding to be
zero. The emitted descriptor was independently decoded as TBP=1024, TBW=8,
PSMT8, TW=8, TH=8; its texture CRC is `0x68e8cb9d`.
The independent software-reference path was also corrected to use logical
dimensions for UV wrap/clamp and sampling while retaining the physical
512-texel cache stride.
## Accepted f33 result
`zsrt139f33` preserves chronology by inserting the three capacity-packed
triangle-list epochs between Ch424 scene rows 122 and 123, before the final
two opaque scene epochs, CT32 darken, and complete alpha family. The result
has 277 epochs.
Production RTL replay passed with:
- 1,326,469 fragments fed and 1,306,834 passed;
- 277 markers and drains;
- zero fragment drops, color overflows, and AXI errors;
- Z mismatch 0/307,200 and color mismatch 0/245,423;
- framebuffer SHA-256
`c4e26d4f93d9b30f98b0abe0d99ccb4be8d362401b49f07ef76ca85c316b84de`.
Relative to Ch427, the recovered character family changes 249 pixels within
`(323,240)..(360,280)`. The mapped PCSX2 score improves narrowly but in both
metrics, to MAE 18.9378 / RMSE 27.6458. This is accepted as authentic local
detail, not claimed as a broad visual milestone.
## Silicon acceptance
The timing-clean Ch423 RBF remained loaded. The DE25 completed all 277
epochs with every texture CRC, palette checksum, staged-record count, ordered
drain, and zero-drop gate passing; the host returned `rc=0` and selected the
HDMI line-buffer source. The board dump reports 212,225 nonzero pixels,
bounds `(0,0)..(511,479)`, sum32 `0x9c95eca0`, and xor32 `0x6e5a9090`.
Board and production RTL framebuffer files are byte-identical at the SHA-256
above. The accepted board image is preserved as
`captures/gs/silenthill3/extracted/sh3_zsrt139f33_ch428_board.png`.
Ch428 is accepted on silicon and f33 is the current live HDMI baseline.
+80
View File
@@ -0,0 +1,80 @@
# Ch429 — native PSMT4 character recovery
## Objective
Recover visible captured geometry that was still absent from the accepted
Ch428 frame, while preserving authentic draw order and refusing additions
that merely increase epoch count or worsen the presentation.
## Deferred PSMT4 postprocess
`zsrt139f34` appended the existing 18-epoch tiled PSMT4 overlay after the
Ch428 frame, in its authentic late-frame position. Its 295-epoch production
RTL replay passed every structural gate and produced framebuffer SHA-256
`7b167db3927c6e765c464d0e8ddbfb37f23d5b980d87ebdfc9af97b20128e044`.
The replay also established that the captured overlay is a real contrast
operation, not an opaque texture: its alpha equation is
`(Cd-Cs)*0xdc/128 + Cs` with COLCLAMP enabled and source RGB values of 0 or
24. Applied to the still-underlit reconstructed base, it crushes most of the
scene to black and worsens the mapped PCSX2 score to MAE 24.0034 / RMSE
32.9396. The overlay is therefore deferred until the missing illumination
and base-color population is present; f34 was not deployed.
## Native 512x1024 PSMT4 support
A separate captured family of 19 opaque PSMT4 triangle strips covers the
foreground character body at draw indices 165536 through 172233. The draws
share TBP 9216, TBW 8, a logical 512x1024 texture, and several authentic CLUT
banks.
The coverage planner now accepts explicit PSMT4 selection. The fixture
generator reconstructs the canonical GS-swizzled 512x1024 PSMT4 local-memory
image, packs its nibbles into the fixed 262,144-byte texture cache, preserves
PSM/TBW/TW/TH in TEX0, and samples packed nibbles in the independent software
reference. Fast-fit scaling now uses the logical texture dimensions.
Generation is fail-closed. An independent host check confirmed that all
262,144 emitted texture bytes exactly match the local-memory reconstruction;
the emitted TEX0 decodes to TBP 1024, TBW 8, PSM `0x14`, TW 9, TH 10. The
texture CRC is `0x9ecc08f5`.
## Accepted f36 result
`zsrt139f35` groups the 19 strips into 13 exact-state/asset epochs.
`zsrt139f36` inserts those epochs at their authentic position before the
final opaque draws, CT32 darken, and complete alpha family. The resulting
frame has 290 epochs.
Production RTL replay passed with:
- 1,331,626 fragments fed and 1,311,991 passed;
- 290 markers and drains;
- zero fragment drops, color overflows, and AXI errors;
- Z mismatch 0/307,200 and color mismatch 0/245,437;
- framebuffer SHA-256
`102bf7925b859bcacc91172ecdcd1755b4f01210f327ac918a1c8d7954905d2b`.
Relative to Ch428, the recovered family changes 3,099 source pixels within
`(319,242)..(361,479)` and restores the visible foreground character-body
silhouette. On the mapped changed-pixel support its RMSE improves from 15.10
to 12.82. Whole-frame mapped RMSE improves from 27.6458 to 27.6303, while
MAE moves narrowly from 18.9378 to 18.9407. This is accepted as authentic
structural fidelity with a mixed scalar-score result, not described as a
broad color-fidelity win.
## Silicon acceptance
No new compile or RBF was required; the timing-clean Ch423 image remained
loaded. The DE25 completed all 290 epochs with every texture CRC, palette
checksum, staged-record count, ordered drain, and zero-drop gate passing.
The scheduler returned `rc=0`, selected the HDMI line-buffer source, and
reported 212,673 nonzero pixels, bounds `(0,0)..(511,479)`, sum32
`0x00e0b761`, and xor32 `0x9622a8a3`.
The board and production RTL framebuffer files are byte-identical at the
SHA-256 above. The mapped board score is MAE 18.9407 / RMSE 27.6303, and the
accepted board image is preserved as
`captures/gs/silenthill3/extracted/sh3_zsrt139f36_ch429_board.png`.
Ch429 is accepted on silicon and f36 is the current live HDMI baseline.
@@ -0,0 +1,68 @@
# Ch431 — direct-color character highlights
## Objective
Restore two captured direct-color highlight strips omitted from the foreground
character, preserve their exact interleaving with the accepted PSMT4 body
draws, and extend the scheduler fixture path to PSMCT32 without changing RTL
or the timing-clean RBF.
## Preceding sprite rejection
The three late PSMT8 sprites at indices 198700, 198710, and 198720 were first
recovered as an authentic two-epoch candidate. A 14,500-sample bound exceeded
the production fresh-drain deadline, so the identical geometry was repacked
into four epochs under a 6,000-sample estimate. The 294-epoch production
replay then passed with zero drops and exact scoreboards, but the resulting
framebuffer was byte-identical to Ch429: persistent authentic Z rejected the
entire late family. It was rejected and never deployed.
## PSMCT32 fixture support
The draw census identified two ABE PSMCT32 triangle strips at indices 165358
and 170407. Both sample a resident 64x64 direct-color texture at TBP 13760,
TBW 1 and write the visible framebuffer around the character torso.
The coverage planner and scheduler generator now accept this direct-color
shape. Each source texel is reconstructed through the GS PSMCT32 local-memory
reader and placed in the upper-left 64x64 region of the fixed 512-wide,
256-KiB LPDDR cache. TEX0 keeps the logical 64x64 dimensions and selects
PSMCT32; CLUT staging is deterministic zero data and is ignored by the direct
sampler. The cache checksum is `0xb0a82094`.
The two draws remain separate singleton epochs. `zsrt139f42` inserts the
first before the PSMT4 character family and the second between captured draws
170177 and 170637. The runtime scheduler merge was also hardened so a sliced
input carrying `reuse=1` is forced fresh whenever the immediately preceding
output texture checksum differs. This prevents false cache residency across
fixture seams.
## Production and fidelity result
The complete 292-epoch production replay passed with:
- 1,331,684 fragments fed and 1,312,049 passed;
- 292 markers and drains;
- zero fragment drops, color overflows, and AXI errors;
- Z mismatch 0/307,200 and color mismatch 0/245,437;
- framebuffer SHA-256
`8bf59d48638c16dc9cd4a96d6ddb1104b919dfcefb514a35c6a3298d192e6eb6`.
The authentic addition changes 14 pixels at `(319,306)..(323,317)`. Mapped
PCSX2 comparison improves narrowly in both metrics, from MAE 18.9407 / RMSE
27.6303 to MAE 18.9405 / RMSE 27.6301. This is accepted as exact missing
character detail, not claimed as a broad visual milestone.
## Silicon acceptance
The timing-clean Ch423 RBF remained loaded. The DE25 completed all 292
epochs with every texture checksum, palette checksum, staged-record count,
ordered drain, and zero-drop gate passing. The host returned `rc=0`, selected
the HDMI line-buffer source, and reported 212,680 nonzero pixels, bounds
`(0,0)..(511,479)`, sum32 `0x0108df89`, and xor32 `0x962ca2af`.
Board and production RTL framebuffer files are byte-identical at the SHA-256
above. The accepted board image is preserved as
`captures/gs/silenthill3/extracted/sh3_zsrt139f42_ch431_board.png`.
Ch431 is accepted on silicon and f42 is the current live HDMI baseline.
@@ -0,0 +1,69 @@
# Ch432 — perspective palette-bilinear handshake repair
## Result
Ch432 removes the alternate-sample loss in the serialized perspective
palette-bilinear raster path. It restores a broad missing surface population
in the unchanged 292-epoch f42 composition and is accepted on silicon.
The mapped PCSX2 score improves from Ch431's MAE 18.9405 / RMSE 27.6301 to
MAE 15.0765 / RMSE 21.9717. This is a fidelity improvement from corrected
RTL coverage, not an increase in draw count or a fixture substitution.
## Root and repair
Dense perspective epochs emitted almost exactly half of the independently
rasterized coverage. Isolated f19 epoch 74 produced 7,301 fragments against
14,371 reference pixels. The sampler advanced once when launching the held
S1 pixel and again on `PB_RELEASE`, skipping the next S1 sample.
Launch is now the single normal advance, `PB_WAIT` holds while the four-tap
sample returns, and `PB_RELEASE` contributes a final hold cycle. Perspective
valid is qualified by the existing Z and texture-alpha advance enables so a
frozen beat cannot resubmit the same pixel. The expensive triangle-inside
result remains off the global pipeline-enable path.
An ALWAYS-Z diagnostic had previously added only 571 lit pixels and worsened
the reference score, independently ruling out Z as the large coverage root.
## Simulation acceptance
The focused perspective PSMT8 regression emits all 31/31 independently
covered samples, with no missing pixels, valid colors, correct alpha metadata,
and correct MODULATE behavior. The isolated production epoch-74 regression
passes its explicit 14,341-fragment count, up from 7,301, with zero drops and
exact scoreboards. The affine palette-bilinear regression remains clean.
The complete unchanged f42 replay passes:
- 2,339,405 fragments fed and 2,285,783 passed;
- 292 markers and drains;
- zero fragment drops, color overflows, and AXI errors;
- Z mismatch 0/307,200 and color mismatch 0/245,760;
- framebuffer SHA-256
`31d21fb5c38816ef99ace1d7b55186dc749720e874ecb055a68b21584ec27037`.
The repaired f19 scene improves against its independent software reference
from MAE 11.7491 / RMSE 24.6649 to MAE 8.7494 / RMSE 18.7008. Its mapped
PCSX2 score improves from 20.9469 / 29.7626 to 18.3393 / 25.8374.
## Fit and silicon acceptance
The owner GUI fit is timing-clean. Runtime-fabric audit reports 38,367 ALMs,
336 RAM blocks, required feeder/texture/Z hierarchy present, and 310 MHz setup
met at +0.069 ns. Hold is nonnegative. The deployed RBF SHA-256 is
`69b1f91b26d0d78ba654b3d0ccc48f747fa3249f08e2a23535007ff3df40d63e`.
After explicit loader confirmation, the DE25 completed all 292 epochs with
every texture checksum, palette checksum, staged-record count, ordered drain,
and zero-drop gate passing. The host returned `rc=0`, selected the HDMI
line-buffer source, and dumped 307,200 words with sum32 `0x8304eb9b`, xor32
`0x89a68e4f`, 240,414 nonzero 32-bit words, and bounds `(0,0)..(511,479)`.
The board dump is byte-identical to the production-gradient simulation at the
framebuffer SHA above. The accepted board image is
`captures/gs/silenthill3/extracted/sh3_zsrt139f42_ch432_board.png`; its PNG
SHA-256 is
`45d7f3eed485ab4650bcf61e849598423c46302c42465dbd2a2579ae4518fab3`.
Ch432 is accepted and is the current live HDMI baseline.
@@ -0,0 +1,81 @@
# Ch432 — perspective palette-bilinear handshake repair (prefit)
## Objective
Remove the regular missing-sample pattern from the authentic SH3 perspective
scene without changing its fixture, chronology, Z state, textures, palettes,
or accepted display mapping. The live f42 composition remains the input; this
chapter is an RTL fidelity repair and therefore requires a new owner GUI fit.
## Root cause
Dense perspective palette-bilinear epochs emitted almost exactly half of their
independent software coverage. Representative f19 epoch 74 emitted 7,301
fragments against 14,371 independently rasterized covered pixels. Other dense
epochs showed the same approximately 2:1 deficit. A GEQUAL-to-ALWAYS Z-only
diagnostic added just 571 lit pixels and worsened PCSX2 score, ruling out Z as
the broad missing-surface cause.
The serialized four-tap sampler launched an S1 pixel in `PB_IDLE`, held the
pipeline in `PB_WAIT`, emitted the completed pixel, and then advanced again in
`PB_RELEASE`. Because the launch cycle had already advanced the raster walker,
the release advance skipped the next waiting S1 sample. This produced the
regular screen-door population loss while all existing scoreboards still
passed for the subset that was actually emitted.
## RTL repair
The sampler now treats launch as the single normal pipeline advance, holds
through `PB_WAIT`, and uses `PB_RELEASE` as a final hold cycle. The next S1
sample is therefore launched rather than overwritten. Perspective input valid
is also qualified by the existing Z and texture-alpha advance enables, so a
frozen beat cannot submit the held sample twice.
This sequencing deliberately keeps the 35-bit triangle-inside result off the
high-fanout pipeline-enable path. The only added global gating term is the
already-registered two-bit sampler state, minimizing timing risk after the
timing-clean Ch431 baseline.
## Regression gates
The PSMT8 perspective triangle test now enables the serialized
palette-bilinear path and checks independent coverage. It passes 31/31 covered
samples with no missing pixels, valid colors, correct alpha metadata, and
correct MODULATE behavior. The affine palette-bilinear tile regression also
passes unchanged.
The production isolated epoch-74 gate is now explicit through
`+EXPECT_FED=14341`. It passes at 14,341 fragments (up from 7,301), with zero
drops, exact Z/color comparisons, and complete marker/drain accounting. The
30-pixel difference from the independent 14,371 coverage map is the existing
edge-rule/model boundary, not alternate-sample loss.
The complete unchanged f42 composition passes all 292 epochs:
- 2,339,405 fragments fed and 2,285,783 passed;
- 292 markers and drains;
- zero fragment drops, color overflows, and AXI errors;
- Z mismatch 0/307,200 and color mismatch 0/245,760;
- framebuffer SHA-256
`31d21fb5c38816ef99ace1d7b55186dc749720e874ecb055a68b21584ec27037`.
## Fidelity result
The repaired f19 opaque scene lights 173,354 pixels instead of 111,705. Its
error against the independent f19 software reference improves from MAE 11.7491
/ RMSE 24.6649 to MAE 8.7494 / RMSE 18.7008. Against the PCSX2 frame through
the accepted display map it improves from MAE 20.9469 / RMSE 29.7626 to MAE
18.3393 / RMSE 25.8374.
On the complete f42 composition, 240,224 pixels are nonzero. The mapped PCSX2
score improves materially from Ch431's MAE 18.9405 / RMSE 27.6301 to MAE
15.0765 / RMSE 21.9717. The remaining visual noise is real follow-on work, but
this chapter restores a large missing surface population rather than merely
adding more draw capacity.
## Fit boundary
The preceding owner GUI fit is clean and the runtime-fabric audit passes at
38,241 ALMs and 336 RAM blocks, with 310 MHz setup met. Those reports predate
this RTL repair. Ch432 is simulation-complete and ready for the owner to run a
new Quartus GUI compile; no command-line Quartus flow was used.
+69
View File
@@ -0,0 +1,69 @@
# Ch433 — UV-optimized STQ packing
## Root cause
The repaired Ch432 sampler restored the missing perspective-bilinear samples,
but the f19 base scene still showed granular spatial misregistration against
its independent floating-point reference. Post-repair isolation rejected a
13-bit reciprocal table and rejected nearest filtering: both worsened the
software-reference and PCSX2 scores.
The remaining fixture path used `--fast-fit-scale`. That shortcut selected
the largest representable common S/T/Q scale, normally 6144, even though the
scheduler generator already had a per-triangle search that scores packed RTL
UVs against authentic floating-point UVs. A common scale cancels only at
infinite precision; vertex quantization, integer gradients, and reciprocal
quantization make the choice observable.
## One-variable repair
`zsrt139f43` retains f19's exact 766-draw population, 254 chronological state
groups, 125 capacity epochs, native 12.4 geometry, textures, palettes, fog,
bilinear reference, Z state, and alpha state. Only `--fast-fit-scale` was
removed. The optimizer selected scales from 0.375 through 6144 rather than
forcing almost every triangle to 6144.
The coverage planner no longer emits the shortcut by default. It remains
available through an explicit planner `--fast-fit-scale` option for quick
diagnostic fixture generation.
## Production result
The full registered-gradient f43 replay passed all 125 epochs:
- 282,363 fragments fed and 232,326 passed Z;
- zero drops, color overflows, and AXI errors;
- 125 complete markers and drains;
- final Z mismatch 0/307,200 and color mismatch 0/176,888.
Against its independent software reference, f43 improves from f19's
MAE/RMSE 8.7494/18.7008 to 4.1633/11.9970. Its mapped PCSX2 score improves
from 18.3393/25.8374 to 17.7172/24.8787.
`zsrt139f44` substitutes the optimized f43 base slices into the complete f42
composition while preserving the chronology-correct PSMT4 character,
direct-color highlights, and alpha/postprocess population. The production
replay passed all 292 epochs with 2,339,405 fragments fed, 2,285,783 passed,
zero drops/errors, and exact final Z and color scoreboards. Relative to f42,
87,282 pixels change. The mapped PCSX2 score improves from
15.0765/21.9717 to 14.8127/21.5567.
## Silicon acceptance
No compile was required. The timing-clean Ch432 RBF ran both asset-only
fixtures. f43 passed all 125 runtime gates and matched simulation byte for
byte at SHA-256
`f4ae82153791a66ae8ed7c4b3dd01f609b9706e826e971311c602181f8a08bc7`.
The complete f44 board run passed all 292 texture, palette, staging, drain,
and zero-drop gates. It reported 240,492 nonzero words, bounds
`(0,0)..(511,479)`, sum32 `0x96f07662`, xor32 `0x0a031310`, and selected the
HDMI line-buffer source. Its 307,200-word framebuffer is byte-identical to
production simulation at SHA-256
`3f492ab65c12415f4d0cd1aeafc64072870fd43bed237be4a923efbce30d2081`.
The accepted board image is
`captures/gs/silenthill3/extracted/sh3_zsrt139f44_ch433_board.png`; the mapped
comparison is
`captures/gs/silenthill3/extracted/recon/sh3_zsrt139f44_ch433_board_mapped.png`.
Ch433 is accepted, and f44 is the current live HDMI baseline.
@@ -0,0 +1,80 @@
# Ch434 — current-frame light-buffer reconstruction
## Fidelity gap
Ch433's accepted `zsrt139f44` frame used the captured contents of TBP 11264
for the two late full-screen darkening sprites. That texture was stale GS
local memory rather than the intermediate produced by the current frame.
The result retained recognizable scene structure, but its lighting was not
chronologically connected to the scene being rendered.
The omitted current-frame chain is:
1. a PSMCT16S light target at FBP 256/TBP 8192, built by a clear, 2,053
untextured Z-tested additive/subtractive volume draws, and a final bias
sprite;
2. conversion to the CT32 target at FBP 320/TBP 10240;
3. three CT32 ping-pong sprites through FBP 352/TBP 11264 and FBP 320;
4. the authentic draws 196167 and 196177, which sample the final TBP 11264
image and blend it across the display.
Ch434 reconstructs this chain as ordered static fixtures. The PSMCT16S
target is rendered with the accepted scene Z, decoded to linear CT32, and
then passed through the captured CT32 sprite states before the two display
composites. This is a static fidelity bridge; animated render-to-texture
still requires native target residency and ping-pong ownership in RTL.
## Triangle-record topology repair
The first integrated attempt exposed a fixture-contract bug. The scheduler
generator expanded captured TRI_STRIP, TRI_FAN, and SPRITE topology into
independent three-vertex records, while preserving the captured PRIM type in
the feeder header. Because the feeder reissues PRIM for every record, a
SPRITE record was assembled as a two-vertex sprite plus a dangling vertex,
and strip/fan records restarted with the wrong topology.
`tools/gs_make_sh3_scheduler_fixture.py` now advertises TRIANGLE for every
expanded three-vertex record while preserving authentic TME, ABE, FST, and
CTXT state. This changed the broken all-white ping-pong result into a stable
binary light mask that retains the booth and character geometry through all
three copies.
## Rejected diagnostics
- `zsrt139f46` painted the untextured volume family directly into the display.
It was useful for isolation but architecturally wrong because the family
targets PSMCT16S off-screen storage. It was never deployed.
- `zsrt139f48` used the reconstructed chain before the triangle-record repair.
It passed the internal RTL scoreboard but collapsed the intermediate to
white and over-darkened the display. Its mapped PCSX2 score was
MAE/RMSE 19.4763/29.3660, so it was rejected and never deployed.
## Accepted result
`zsrt139f49` combines the accepted Ch433 scene, the topology-correct
current-frame light composite, and the existing textured-alpha tail. The
production registered-gradient simulation passed all 311 epochs:
- 2,339,914 fragments fed and 2,286,292 passed;
- zero fragment drops, color overflows, and AXI errors;
- 311 complete markers and drains;
- zero final Z and color scoreboard mismatches.
Against the PCSX2 frame through the accepted display map, f49 scores
MAE/RMSE 14.1067/21.3468. This improves both metrics from f44's
14.8127/21.5567, with a 0.7060 MAE reduction.
No compile was required. The timing-clean Ch432 RBF ran the new assets on
the Terasic board. All 311 runtime texture, palette, staging, drain, and
zero-drop gates passed. The host reported 244,981 nonzero words, bounds
`(0,0)..(511,479)`, sum32 `0x385dc4f6`, xor32 `0x6330653c`, and selected HDMI
line-buffer source 1.
The 307,200-word board framebuffer is byte-identical to production
simulation at SHA-256
`adb21828465758b0601956abef39b649f1f441a0a51831f90826ae76936f6606`.
The accepted board image is
`captures/gs/silenthill3/extracted/sh3_zsrt139f49_ch434_board.png`; its mapped
comparison is
`captures/gs/silenthill3/extracted/recon/sh3_zsrt139f49_ch434_board_mapped.png`.
Ch434 is accepted, and f49 is the current live HDMI baseline.
@@ -0,0 +1,94 @@
# Ch435 — FRAME.FBMSK light-buffer alpha
## Root cause
The Ch434 current-frame light chain was structurally correct but the second
draw of the CT32 conversion stage, draw 195973, has
`FRAME.FBMSK=0xff000000`. The scheduler previously discarded that field when
relocating the framebuffer, and the external LPDDR color path had no byte-mask
transport. Draw 195973 therefore replaced alpha across the full 256x256
target instead of preserving the alpha produced by draw 195957. Feeding that
incorrect all-128 alpha target through the remaining ping-pong passes made the
integrated frame much too dark.
## Static fidelity result
The first correction was an asset-boundary reconstruction. It retained alpha
128 on the 20,861 lit texels and alpha zero on the 44,675 black texels, then
propagated that target through draws 196087, 196108, 196129, 196167, and
196177. The resulting 311-epoch `zsrt139f52` replay passed production
simulation and the DE25-Nano runtime gates with zero drops, overflows, AXI
errors, Z mismatches, or color mismatches.
The board framebuffer is byte-identical to simulation at SHA-256
`d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`.
Its mapped mean RGB `(26.38, 23.32, 21.19)` is close to the PCSX2 frame mean
`(28.47, 23.54, 21.33)`, and the booth, lamp, rabbit, bench, and character are
all readable. This supersedes the over-dark f49 result and is the live HDMI
asset baseline. The remaining dominant visual defect is spatial speckle and
edge noise, not global brightness.
## Native RTL repair
The production path now carries a four-bit byte enable derived from
`FRAME.FBMSK` with each fragment:
- the fixture generator preserves the captured upper 32 FRAME bits and
rejects masks that are not byte-granular;
- the raster wrapper converts each `00` mask byte to an enabled write byte and
each `ff` byte to a preserved destination byte;
- the request FIFO and color-align RAM carry the byte enable across the GS to
EMIF path;
- partial opaque writes use the same ordered destination-read/modify/write
path as alpha blends;
- the color stage merges disabled bytes from the destination after either the
GS ALPHA equation or opaque source selection.
The directed color-blend regression preserves destination alpha with
`BE=0111`, producing `0x8024180c`. The 1,500-fragment Z/color regression also
passes with zero errors.
The regenerated authentic two-draw fixture `zsrt139l3f` contains
`ff000000000a0000` in draw 195973's FRAME header. Its registered-gradient
simulation passes 22 epochs and 131,072 fragments with zero drops, overflows,
AXI errors, Z mismatches, or color mismatches. Most importantly, the native
256x256 output is byte-identical in all 65,536 words to the static corrected
target: 44,675 black/alpha-zero texels and 20,861 lit/alpha-128 texels.
## Accepted fit
The owner-controlled Quartus 26.1 GUI compile completed successfully on
2026-07-19. The accepted RBF is 4,112,384 bytes with SHA-256
`55b23b94f51f1aab271d91aa4fa766561c044b9633146f25f4242b431f6ce902`.
All signoff classes are nonnegative:
- EMIF setup WNS `+0.083 ns`, TNS `0.000 ns`;
- 25 MHz design setup WNS `+6.324 ns`, TNS `0.000 ns`;
- hold minimum `0.000 ns`, with zero TNS;
- recovery minimum `+1.074 ns`;
- removal minimum `+0.017 ns`;
- minimum-pulse-width minimum `+0.200 ns`.
Final resources are 38,387 / 46,800 ALMs (82%), 5,327,792 / 7,331,840
block-memory bits (73%), 344 / 358 RAM blocks (96%), and 160 / 376 DSP
blocks (43%).
## Hardware acceptance
The RBF checksum was verified on the DE25-Nano and `core_loader.sh` loaded it
successfully; `fpga0` reported `operating`. The authentic 22-epoch
`zsrt139l3f` fixture then passed every texture CRC, staging, fresh-drain, and
zero-drop gate. Its 307,200-word framebuffer has SHA-256
`e2b9f847fb64f5dbc70f112df91c6fccc54bf439138f772927150ff99aa026ce`,
byte-identical to production simulation. The native target contains exactly
20,861 lit/alpha-128 pixels and 44,675 black/alpha-zero pixels in its 256x256
region, proving that draw 195973 preserves the destination alpha byte on
silicon.
The complete 311-epoch f52 scene was then replayed on the same RBF. All
epochs completed with zero fragment drops and its framebuffer remained
byte-identical to simulation at SHA-256
`d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`.
HDMI therefore ends on the readable f52 scene. The accepted artifact is now
the board's standard `/home/terasic/cores/retroDE_ps2.core.rbf`; the previous
artifact is retained as `retroDE_ps2.pre_ch435.core.rbf`.
+168
View File
@@ -0,0 +1,168 @@
# Ch436 — vertical-linear authentic-display reconstruction prefit
## Root cause
The accepted Ch435 framebuffer is byte-identical between production RTL and
the DE25, and its mean RGB is already close to the PCSX2 frame. The remaining
high-frequency defect is also present in the independent software
reconstruction, so it is not a board-transfer or framebuffer-write error.
The Ch418 presentation path reduced the captured 512x448 display to the
board's 640x480 raster with nearest-neighbour selection:
- `source_x = floor(output_x * 4 / 5)`;
- `source_y = 32 + floor(output_y * 14 / 15)`.
That repeats source samples at both axes and exposes the already-granular base
scene more harshly than linear display reconstruction.
## RTL correction
`gs_lpddr_scanout_lb` now optionally uses the existing 15-state vertical
presentation phase as the exact interpolation numerator. It reads the
current and already-prefetched next row from the two alternating line buffers.
This adds no LPDDR traffic, line-buffer storage, or scheduler/render ordering
change.
The AXI prefetch side is notified of a source-row advance during horizontal
blanking. This gives it the full blank interval to refill the retired parity
buffer before the next active line requires the adjacent row. The directed
test explicitly models that interval and checks all 600 samples against the
same rounded separable equations as RTL, with zero errors or underflow.
Legacy profiles retain nearest/1:1 behavior through a default-off parameter.
Only the full-frame SH3 profile enables vertical linear reconstruction.
Horizontal presentation retains the proven Ch418 nearest mapper.
An initially simulated horizontal-linear extension duplicated the line-buffer
storage to obtain an `x+1` read port. Quartus implemented those mirrors as
30,720 registers instead of M20Ks, making the design require 6,270 LABs on a
4,680-LAB device. That architecture was removed completely before this
prefit candidate; none of the mirrored arrays or horizontal blend logic remain.
## Measured preview and regression evidence
On the accepted Ch435 board framebuffer, the exact RTL presentation model
improves the PCSX2 comparison from nearest MAE/RMSE `14.7226/22.3097` to
`14.2678/21.3085`. Mean RGB remains effectively unchanged at
`(26.38, 23.33, 21.19)`, so this is a spatial reconstruction improvement, not
a brightness retune.
Passing directed regressions:
- `tb_gs_lpddr_scanout_lb_hstretch`: 600 checked, zero errors, zero underflow;
- `tb_gs_lpddr_scanout_lb`: pass;
- `tb_gs_lpddr_scanout_lb_psm32`: 49,152 checked, zero errors/underflow;
- `tb_gs_lpddr_scanout_lb_psm32_256`: 196,608 checked, zero errors/underflow.
The framebuffer itself remains unchanged by construction, so the existing
Ch435 22-epoch native-FBMSK and 311-epoch full-scene scoreboards remain the
render-data acceptance baselines. The next owner GUI fit must establish RAM
cost and signoff timing before board deployment.
## First owner fit — resources recovered, narrow EMIF setup miss
The vertical-only candidate fit successfully and recovered the exact Ch435
memory/DSP budget: 5,327,792 block-memory bits, 344 / 358 RAM blocks, and
160 / 376 DSPs. The 25 MHz presentation domain was comfortably clean at
`+4.611 ns`; hold, recovery, removal, and minimum-pulse checks were also clean.
The RBF is not deployable because EMIF setup finished at `-0.086 ns`. The
actual post-fit top paths contained two unrelated 310 MHz routing families:
- the high-fanout synchronized EMIF calibration reset driving duplicated
texture-cache `drain_idx_q` payload registers (`-0.086 ns`);
- one request-FIFO `raddr_q` copy driving the complete 93-bit by 16K physical
RAM address network (`-0.073 ns` / `-0.062 ns`).
Neither path contains the vertical filter or its 25 MHz arithmetic. This is
therefore a structural timing repair, not a presentation rollback or another
placement-only reroll.
## First timing-repair fit — rejected RAM banking
The texture fill's selected word/index boundary is now control-free and
unreset. `F_WRITE` is reachable only after `F_DRAIN` has loaded both payload
registers, so their reset values were unobservable; removing reset eliminates
the EMIF calibration-reset launch family without changing fill ordering.
The first repair also split the production request memory into three explicit
31-bit by 16K banks. Although the logical storage remained 1,523,712 bits,
each bank rounded up to 25 physical M20Ks. The request memory therefore used
75 blocks instead of the packed macro's 59, raising the complete design from
344 to 360 / 358 blocks. Fitter correctly rejected it before routing. This
banked-memory architecture has been removed completely.
## Current zero-RAM-cost compile candidate
The request FIFO is back to its original single packed 93-bit by 16K memory,
restoring the proven 59-block request-macro shape and expected 344 / 358 total
RAM budget. Its existing unreset RAM-facing `raddr_q` now carries a
`maxfan=64` synthesis directive. Quartus therefore duplicates only the
address launch register as needed; the storage remains one efficiently packed
memory and the pointer/read latency are unchanged.
Verification after both cuts:
- asynchronous FIFO: 3,284 writes/reads exact, zero errors;
- texture cache: 64 words exact, refill error injection exact;
- Z/color emitter: 1,500 fragments across three epochs, zero errors;
- complete 311-epoch `zsrt139f52` production replay: 2,339,914 fragments fed,
2,286,292 passed Z, zero drops/overflows/AXI errors, final Z mismatch
`0 / 307,200`, final color mismatch `0 / 245,760`;
- framebuffer SHA-256 remains exactly
`d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`,
byte-identical to both accepted Ch435 board dumps;
- relevant `git diff --check`: PASS.
This zero-RAM-cost RTL is ready for a new owner-controlled Quartus 26.1 GUI
compile. Acceptance requires all timing classes nonnegative, the request FIFO
to return to the 59-block packed macro / 344-block total design budget, and the
retired reset-to-`drain_idx_q` / high-fanout request-address families to be
absent. Do not deploy either prior Ch436 RBF.
## Accepted fit
The owner-controlled Quartus 26.1 GUI compile completed successfully on
2026-07-19. The packed request-memory shape and the complete Ch435 memory/DSP
budget were restored. All timing classes are nonnegative:
- EMIF setup WNS `+0.050 ns`, TNS `0.000 ns`;
- 25 MHz design setup WNS `+2.387 ns`, TNS `0.000 ns`;
- reference-clock setup WNS `+8.985 ns`, TNS `0.000 ns`;
- hold minimum `0.000 ns`, with zero TNS;
- recovery minimum `+1.054 ns`;
- removal minimum `+0.163 ns`;
- minimum-pulse-width minimum `+0.200 ns`.
Final resources are 38,522 / 46,800 ALMs (82%), 5,327,792 / 7,331,840
block-memory bits (73%), 344 / 358 RAM blocks (96%), and 160 / 376 DSPs
(43%). Quartus explicitly ignored the `maxfan` directive because every load
is a RAM address. The request-address family remains the clean EMIF limiter
at `+0.050 ns`; the reset-to-`drain_idx_q` family is absent. Closure therefore
comes from the valid reset cut and the resulting legal placement, not from a
claimed address-register duplication.
The accepted RBF is 4,100,096 bytes with SHA-256
`1c8945e3e8ad85d91a682aa7a068cee487a1459a7f2b899600b24ff7d9751294`.
## Hardware acceptance
The RBF checksum was verified on the DE25-Nano, the accepted Ch435 image was
preserved as `retroDE_ps2.pre_ch436.core.rbf`, and `core_loader.sh` loaded the
new artifact successfully with `fpga0: operating`. Live preflight passed:
- `CORE_ID=0x50533200`, ABI `0x00000100`, status `0x0000001f`;
- HDMI initialization complete and no I2C error;
- frame counter advanced from `0x0000117c` to `0x00001302`;
- raster-overflow count remained zero.
The complete 311-epoch `zsrt139f52` scene then passed every texture CRC,
runtime-palette sum, staged-record count, fresh-drain, and zero-fragment-drop
gate. The scheduler dumped all 307,200 framebuffer words and ended
`DONE rc=0`. Board SHA-256 is
`d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`,
byte-identical to the complete production simulation and both accepted Ch435
board dumps. HDMI is live on the Ch436 vertical-linear presentation of that
accepted f52 framebuffer. Ch436 is timing-, simulation-, and
hardware-accepted.
+151
View File
@@ -0,0 +1,151 @@
# Ch437 — zero-line-RAM horizontal-linear presentation prefit
## Purpose
Ch436 made the captured 15:14 vertical presentation linear while retaining the
Ch418 nearest-neighbour 5:4 horizontal map. The accepted framebuffer and board
replay are exact, so this chapter changes presentation only: it reconstructs
the fractional horizontal samples that nearest presentation repeats.
For output column `x`, the exact source coordinate is:
- `x0 = floor(x*4/5)`;
- `xf = (x*4) mod 5`;
- `x1 = min(x0+1, 511)`.
The existing five-state mapper already produces both `x0` and `xf`; no divider
or new wide coordinate cone is introduced.
## Architecture
The rejected Ch436 horizontal prototype mirrored both complete line buffers to
obtain an `x+1` read port. Quartus implemented the mirrors as 30,720 registers
and rejected the design at 6,270 required LABs. Ch437 does not restore those
arrays.
Instead, each physical line buffer retains one video read address. Two
256-bit register slots hold the current and next beat for each of the two
resident source rows. Horizontal blanking alternately primes beats 0 and 1.
When active scanout enters beat K, the retired parity slot is refilled with
beat K+1. The 5:4 cadence provides ten output clocks for each eight-source-pixel
beat, so the next beat is resident well before the single lane-7 boundary
sample needs it.
The interpolation order is separable and matches the software oracle:
1. blend row L and L+1 at x0 with denominator 15;
2. blend row L and L+1 at x1 with denominator 15;
3. blend those two results with denominator 5.
The source-right edge clamps at pixel 511. Other profiles retain their existing
nearest/1:1 path through default-off parameters.
## Evidence before owner fit
Directed and legacy regressions:
- `tb_gs_lpddr_scanout_lb_hstretch`: 2,400 checked, zero errors, zero underflow,
across eight 256-bit beats and the clamped right edge;
- `tb_gs_lpddr_scanout_lb_psm32`: 49,152 checked, zero errors/underflow;
- `tb_gs_lpddr_scanout_lb_psm32_256`: 196,608 checked, zero errors/underflow;
- `tb_gs_lpddr_scanout_fb`: 105,968 checked, zero errors/underflow;
- Verilator lint: no errors;
- relevant `git diff --check`: pass.
The exact software presentation model improves the accepted f52 framebuffer's
PCSX2 comparison again:
- Ch436 vertical-linear: MAE/RMSE `14.2678 / 21.3085`;
- Ch437 separable-linear: MAE/RMSE `13.9525 / 20.6568`.
This is a visible smoothing of the horizontal staircase/noise without changing
framebuffer bytes, render ordering, LPDDR traffic, or draw count.
## Owner GUI compile acceptance
### Rejected first fit
The first owner fit rejected the initial cache implementation at placement:
6,337 LABs were required on the 4,680-LAB device. The synthesis/netlist report,
not the summary alone, showed the exact cause:
- `u_lpddr_scan_lb` contained 32,312 registers;
- the SDC line-buffer reset collection found 30,720 destinations;
- `lb0[0..79]` and `lb1[0..79]` appeared as individual flip-flop nodes;
- no line-buffer M20K payload remained in the scanout hierarchy.
The conditional reads used to prime/refill the cache prevented Quartus from
recognizing the dual-clock RAM template. This was the same physical failure
mode as the rejected Ch436 mirrors, despite the source-level arrays not being
duplicated.
### Corrected inference-safe candidate
Each physical array now has exactly one unconditional video-clock read:
```systemverilog
lb0_video_rd_q <= lb0[video_rd_addr_q];
lb1_video_rd_q <= lb1[video_rd_addr_q];
```
Address, valid, and beat tag are registered beside those outputs. A separate
control process fills the two-beat register cache only from the registered
RAM outputs. It never indexes `lb0` or `lb1` directly. This is the canonical
simple-dual-port, dual-clock inference form already proven by the accepted
line-buffer implementation.
The corrected candidate retains identical horizontal results: the expanded
2,400-pixel test, all PSMCT32 legacy regressions, Verilator lint, and relevant
`git diff --check` pass again.
The corrected RTL was accepted by an owner-controlled Quartus 26.1 GUI compile.
The generated core is:
- `retroDE_ps2.core.rbf`, 4,132,864 bytes;
- SHA-256
`3dd624cd34e56cf553fc92c47c3a0d47dc6a9c0513f37cbf72f2c4f6f9c71559`.
All report gates pass:
- fitter successful at 39,077 / 46,800 ALMs (83%), 56,526 registers,
5,327,792 block-memory bits, 344 / 358 RAM blocks, and 163 / 376 DSPs;
- both line buffers are inferred as simple-dual-port, dual-clock
`altera_syncram` instances, five M20Ks and 15,360 bits apiece;
- the scanout hierarchy contains 1,602 registers rather than the rejected
implementation's 32,312;
- EMIF setup slack is +0.069 ns with zero TNS;
- the 25 MHz design-clock setup slack is +5.473 ns with zero TNS;
- all hold classes have zero TNS and minimum slack 0.000 ns;
- minimum recovery, removal, and pulse-width slack are respectively
+1.056 ns, +0.164 ns, and +0.200 ns.
## Hardware acceptance
The accepted core was installed on the DE25-Nano and loaded successfully.
The live fabric reported the expected core ID/ABI, healthy status, active HDMI
scanout, an advancing frame counter, and zero raster overflow.
The complete f52 scheduler replay then passed on hardware:
- all 311 epochs completed;
- all fresh texture CRCs matched and resident reuse checks passed;
- every epoch reported zero fragment drops;
- `DONE rc=0 (all gates passed)`;
- the dump contained 307,200 words, `sum32=0xaad0b94d`,
`xor32=0x33138181`, 245,155 nonzero pixels, and bounds `(0,0)..(511,479)`.
The retrieved Ch437 board framebuffer has SHA-256
`d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`
and is byte-identical to the accepted Ch436 framebuffer. This is the intended
result: Ch437 changes only the HDMI presentation filter, after framebuffer
storage, while preserving the proven renderer and replay output.
Ch437 is accepted. Its closure criteria were:
- fitter completes within the device's 4,680-LAB capacity;
- line buffers remain inferred M20Ks and total RAM stays at 344 / 358 blocks;
- all setup, hold, recovery, removal, and pulse-width classes are nonnegative;
- the narrow EMIF request-address family remains clean;
- the 25 MHz presentation domain remains clean.
All five criteria passed before deployment.
+136
View File
@@ -0,0 +1,136 @@
# 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.
+279
View File
@@ -0,0 +1,279 @@
# Ch439 — measured FIFO right-sizing and scanout write-port timing convergence
## Why this chapter exists
The second Ch438 GUI fit completed successfully but was not timing-clean and
was not deployed. Its RBF SHA-256 was
`b5865b599101d429b86c67c25ae9688c0f6dda334aa7ebbcbd664f31226aa602`.
The fit used 39,442 ALMs, 56,446 registers, 349/358 M20Ks, and 160 DSPs.
The actual post-fit STA report showed three concrete families:
- setup WNS -0.924 ns / TNS -12.931 ns in the 310 MHz EMIF domain;
- six of the top ten setup paths were
`u_zc_emit|u_req|raddr_lo_q[1]` to physical `mem_lo` RAM address ports;
- one setup path at -0.403 ns was `u_lpddr_scan_lb|r_data_q[240]` to the
`lb1` RAM write port;
- the only negative design-clock hold path was -4.221 ns from the bridge's
quasi-static `lpddr_video_src_q` to `vsrc_cap_d` across unrelated clocks.
The preceding two-way width bank did not reduce the request RAM count and made
placement worse than the original packed 16K FIFO. This chapter therefore
removes that banked form from the production instance instead of chasing it
with another seed.
## Structural correction
### Request FIFO: 16K to 8K from measured occupancy
The complete 311-epoch f52 production replay was rerun at depth 16,384 while
recording the per-epoch request occupancy. The maximum was 6,115 entries at
epoch 148. The next power-of-two depth, 8,192, therefore leaves 2,077 entries
or 34 percent measured headroom. It also halves the packed 93-bit request
FIFO's depth, expected M20K footprint, and physical read-address tree.
The production `u_zc_emit` instance now uses `REQ_DEPTH=8192` and the ordinary
monolithic registered-read FIFO. `zc_g_drops` remains the fail-closed runtime
guard if a future fixture exceeds the measured envelope.
### Scanout line buffers: RAM-local physical write stage
The response stage no longer drives all three line-buffer RAM write ports.
`L_R` copies each returned beat directly into data/address/enable registers
dedicated to the selected physical RAM. Those registers commit independently
while the FSM issues or waits for the next single-beat read; `L_C` is used only
once to flush the final beat of a row. The fitter can place each 256-bit launch
bank beside its RAM without adding a state to every beat.
### LPDDR video-source bundled CDC
`lpddr_video_src_q` is written before `lpddr_ctrl_commit_w` toggles.
`vsrc_cap_d` samples the stable data only after that toggle traverses the
three-flop `commit_d_sync`. The raw zero-cycle hold analysis between unrelated
clocks is therefore non-functional. The SDC now cuts only HOLD for this exact
source/destination pair and retains a 2 ns maximum net-delay bound. Setup is
not false-pathed.
## Verification before the next GUI compile
- binomial scanout: 2,400/2,400 exact pixels, zero underflow/read errors, PASS;
- horizontal-stretch scanout: 2,400/2,400 exact pixels, zero underflow/read
errors, PASS;
- registered asynchronous FIFO stress: 3,285 writes/reads, zero errors, PASS;
- directed Z/color integration: 1,500 fragments, zero color overflow/BRESP
errors, PASS;
- complete f52 replay at the shipping 8K request depth:
- 311 epochs;
- 2,339,914 fragments fed and 2,286,292 passed;
- zero drops, Z mismatches, color mismatches, color overflow, or BRESP errors;
- PASS;
- Ch439 framebuffer SHA-256:
`d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`;
- byte-identical to the accepted Ch437 framebuffer and the prior Ch438
production simulation.
No Ch438 timing-red RBF has been deployed. A fresh Quartus 26.1 GUI compile is
required for this consolidated Ch439 correction.
## First Ch439 fit and hardware feedback
The first owner GUI fit of the resource-relieved design was fully signoff-clean:
EMIF setup +0.098 ns, all hold classes nonnegative, 39,514 ALMs, 57,331
registers, 309/358 M20Ks, and 160 DSPs. It also proved that the 8K cut removed
exactly 40 RAM blocks as predicted. The generated RBF SHA-256 was
`7167deb651c08057828a360b6083ec27eb140d59eb08b7b06315cbc1d235ec13`.
It loaded successfully and the complete 311-epoch hardware replay passed every
renderer gate; its framebuffer was byte-identical to simulation at SHA-256
`d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`.
However, the HDMI line-buffer status was not acceptable. `LPDDR_STATUS` held
`0xF1`, with bit 5 set. Disabling LPDDR scanout cleared it to `0xD1`, and
re-enabling scanout restored `0xF1`, proving sustained line-buffer underflow
rather than a sticky AXI read error or startup-only event.
Root cause was the first Ch439 `L_R -> L_C -> L_W` write pipeline, which added
one EMIF cycle to every 32-byte beat and crossed the real just-in-time refill
margin. Ch439b removed `L_W`, but board testing proved that retaining `L_C`
after every beat was still too slow. Its clean fit used 39,616 ALMs, 57,109
registers, 309/358 M20Ks, and 160 DSPs; EMIF setup was +0.050 ns and every
other timing class was also clean. RBF SHA-256 was
`56e45346338907eb6c307320757774ac65efa56dabb644906a5600f5ea73a982`.
The full 311-epoch board replay again passed every renderer gate and produced
the exact expected framebuffer, but four live samples across advancing frames
all read `LPDDR_STATUS=0xF1`. Source-off cleared status to `0xD1`; source-on
restored `0xF1` immediately. Ch439b is therefore rejected as well.
Its board evidence is retained as
`sim/data/top_psmct32_raster_demo/sh3_zsrt139f52_ch439b_board_run.log` and
`sh3_zsrt139f52_ch439b_board_fb.mem`; the framebuffer is byte-identical to the
8K production simulation at SHA-256
`d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`.
Ch439c turns the RAM-local write stage into an actual pipeline. A captured
beat commits from its local register while the AXI FSM advances to the next
read. Only the final beat takes `L_C`, as a one-cycle row-end flush before
`next_fetch` is published. This restores the accepted pre-Ch439 per-beat
cadence while retaining the physical timing cut. Directed binomial and
horizontal-stretch tests are exact at 2,400/2,400 pixels with zero underflow or
read errors; the 640x192 and 256x768 PSMCT32 tests are exact at 49,152/49,152
and 196,608/196,608 pixels with zero underflow or read errors. A fresh owner
GUI compile is required for Ch439c.
## Ch439c fit feedback and Ch439d address-tree cut
The owner Ch439c GUI compile completed successfully but was not deployed. It
used 39,472 ALMs, 56,640 registers, 309/358 M20Ks, and 160 DSPs. Hold,
recovery, removal, and minimum-pulse-width checks were clean, but EMIF setup
failed at -1.009 ns / -60.117 ns TNS across 166 endpoints. All ten reported
worst paths were the same zero-logic-level route:
- launch: `u_zc_emit|u_req|raddr_q[7]`;
- capture: the monolithic request FIFO's physical `mem_rtl_0` port-B address
registers;
- data delay: 4.077 ns, of which 3.759 ns was interconnect.
This is not a scanout-pipeline path. The Ch439c placement exposed the remaining
8K request-RAM address tree. Ch439d enables the already-regressed two-way
payload bank only on `u_zc_emit.u_req`. At the present 8K depth, each preserved
read-address copy drives half the width at half the original Ch438 depth: about
one quarter of the physical RAM load that defeated the earlier 16K banked fit.
Depth, ordering, registered-read latency, and the measured 2,077-entry capacity
margin are unchanged.
Post-cut verification is complete:
- banked asynchronous FIFO stress: 3,285 writes and reads, zero errors, PASS;
- directed Z/color integration: 1,500 fragments, zero overflow/BRESP errors,
PASS;
- complete 311-epoch f52 replay at 8K: 2,339,914 fragments fed, 2,286,292
passed, zero drops/mismatches/errors, PASS;
- framebuffer remains byte-identical at SHA-256
`d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`.
The Ch439c timing-red RBF SHA-256 is
`d006cc1ede2db1eeb5b8ae961eafc047eff3e6ef3ce58a173ae4f8e3bf7c3cac` and
was not deployed. A fresh owner GUI compile is required for Ch439d.
Board evidence is retained as
`sim/data/top_psmct32_raster_demo/sh3_zsrt139f52_ch439_board_run.log` and
`sh3_zsrt139f52_ch439_board_fb.mem`. The HDMI-equivalent filtered preview is
`captures/gs/silenthill3/extracted/recon/sh3_zsrt139f52_ch439_hdmi.png` and
remains MAE 13.1111 / RMSE 19.1051 against the PCSX2 reference.
## Ch439d fit feedback and Ch439e quadrant cut
The owner Ch439d GUI compile completed successfully and the two width banks
were inferred as intended, but the RBF was not deployed because EMIF setup
still failed. Its RBF SHA-256 is
`fe6eb909b8d875b617587f8afafdc510f77e4b5369c66c225ba9cea404ef6ba9`.
The actual post-fit results were:
- 39,410 required ALMs and 42,843 final-placement ALMs (91 percent);
- 56,822 dedicated logic registers, 309/358 M20Ks, and 160 DSPs;
- setup WNS -0.515 ns / TNS -36.152 ns, with 120 failing endpoints;
- hold, recovery, removal, and minimum-pulse-width checks all clean.
Every reported worst setup path remained one zero-logic-level physical-route
family from `u_req|raddr_{lo,hi}_q[*]` to the corresponding `mem_{lo,hi}` M20K
address input register. The worst path was `raddr_lo_q[10]` to a `mem_lo`
address register, with 3.615 ns data delay into the 3.225 ns EMIF period.
Width banking improved WNS by about 0.5 ns versus Ch439c but left each address
copy spanning the full 8K depth, so Ch439d is rejected rather than reseeded.
Reducing the FIFO below 8K is not valid for the current producer. The existing
instrumented complete f52 replay measured a peak request occupancy of 6,115 at
epoch 148, and the producer drops fragments when `zc_g_ready` deasserts. The
8,192-entry depth and its measured 2,077-entry margin therefore remain fixed.
Similarly, directly feeding the next binary pointer into the RAM address port
was already exercised before Ch420 and exposed pointer logic plus the same
high-fanout physical address family.
Ch439e instead crosses two depth banks with the existing two payload-width
banks. The request FIFO is physically four 4K-deep RAM quadrants:
`mem_lo0`, `mem_lo1`, `mem_hi0`, and `mem_hi1`. Each has its own preserved
11-bit RAM-facing read-address register, reducing one launch tree to roughly
one quarter of the original physical RAM load. A one-bit selector delayed with
the synchronous RAM data performs only a 2:1 lower/upper-depth selection at
the registered output. Capacity, ordering, CDC publication, accepted-read
latency, and atomic tuple writes are unchanged.
Ch439e prefit verification is complete:
- quadrant asynchronous FIFO stress: 3,285 writes and reads, zero errors,
PASS;
- directed Z/color integration: 1,500 fragments, zero overflow/BRESP errors,
PASS;
- complete 311-epoch f52 production replay at 8K: 2,339,914 fragments fed,
2,286,292 passed, zero drops/mismatches/errors, PASS;
- binomial and horizontal-stretch scanout: 2,400/2,400 exact pixels apiece,
zero underflow/read errors, PASS;
- framebuffer SHA-256 remains byte-identical at
`d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`.
The next owner GUI compile must confirm that all four quadrant memories and
four independent read-address trees survive synthesis, that setup closes, and
that the new RAM-output depth selector does not become a replacement setup
family. Only a fully signoff-clean Ch439e artifact is eligible for deployment.
## Ch439e fit feedback and Ch439f arithmetic-boundary correction
The owner Ch439e GUI compile completed successfully and confirmed the intended
request-FIFO result. All four quadrant memories were inferred, request-memory
usage stayed at 309/358 M20Ks, and the 310 MHz EMIF setup domain closed at
+0.019 ns. Hold, recovery, removal, and minimum-pulse-width checks were also
clean. The design used 38,849 required ALMs, 42,880 final-placement ALMs
(92 percent), 57,233 dedicated registers, and 160/376 DSP blocks.
The artifact is nevertheless rejected because the 25 MHz design clock failed
at WNS -1.294 ns / TNS -89.732 ns across 208 endpoints. Its rejected RBF
SHA-256 is
`4deb590b83036e250e042158896a3a69c731bbccc09eef04c06f523cc382c6e5`.
Every reported worst path was the same 27-logic-level, 41.141 ns arithmetic
family:
- launch: `u_demo|u_gs|u_tex|g_bilinear.tap[0][11]`;
- capture: `u_texcache|tex_mem...portbaddr[10]`;
- middle: both bilinear lerps, GS modulation/blend arithmetic, and the next
texture-coordinate/address arithmetic.
The initial hypothesis was that the project-wide `AUTO_RESOURCE_SHARING ON`
setting caused this apparently cross-transaction path. The fit report records
GS arithmetic operators packed with `u_tex` bilinear operators, including
`u_demo|u_gs|mult_66` merged into `u_demo|u_gs|u_tex|mult_9`; Intel's Quartus
26.1 setting reference also warns that sharing mux/control logic can reduce
Fmax. Ch439f tests that hypothesis directly below rather than assuming it.
Ch439f disabled automatic resource sharing globally while retaining
`AGGRESSIVE AREA`. This was a controlled synthesis experiment, not a seed or
RTL-latency change.
The fresh owner Ch439f compile conclusively rejected that hypothesis.
Synthesis reported `Auto Resource Sharing: Off`, but the complete result was
bit-for-bit structurally unchanged at report precision: 38,849 required ALMs,
42,880 final-placement ALMs, 57,233 registers, 309 M20Ks, 160 DSPs, EMIF setup
+0.019 ns, and the identical design-clock -1.294 ns / -89.732 ns / 208-endpoint
tap-to-texture-cache family with the same 41.141 ns path. The project setting
is therefore restored to its area-oriented value.
## Ch439g functional exception for the impossible packed-DSP arc
The RTL cone proves that a captured bilinear tap cannot reach the texture read
address combinationally. `tap[*]` feeds the two lerp levels only in `BS_DONE`,
where the result is captured into `tex_color_hold`; the external filtered
result is exposed from that register only in the distinct `BS_OUT` cycle.
Texture-memory address capture occurs under `tex_rd_en` in `BS_ISSUE` or a
nearest-read issue cycle. Those state conditions are mutually exclusive.
The remaining path is therefore a conservative physical arc introduced by
packing mutually exclusive arithmetic into Agilex DSP structures, not a
single-cycle functional requirement. Ch439g adds a fail-closed SDC exception
from exactly the architectural 128 `g_bilinear.tap[0:3][31:0]` registers to
the existing `u_texcache.tex_mem` endpoint collection. The exception does not
cut any walker, affine-UV, perspective-UV, descriptor, or address register;
all real texture-address launches remain timed normally. The SDC halts the
compile if the tap population is not exactly 128 or the cache collection is
empty.
No functional RTL changed after the already-exact Ch439e simulations. The next
owner GUI compile must show the Ch439g collection-count message, retain the
four request quadrants and EMIF closure, and expose the true next design-clock
family. Deployment remains forbidden unless every timing class is clean.
@@ -0,0 +1,8 @@
55b23b94f51f1aab271d91aa4fa766561c044b9633146f25f4242b431f6ce902 retroDE_ps2.ch435.core.rbf
3dd624cd34e56cf553fc92c47c3a0d47dc6a9c0513f37cbf72f2c4f6f9c71559 retroDE_ps2.ch437.candidate.rbf
9463602dd40f224330de125a68f96d377c28210d3f7dcce3e9adf775e548db5f retroDE_ps2.ch441.core.rbf
56e45346338907eb6c307320757774ac65efa56dabb644906a5600f5ea73a982 retroDE_ps2.core.rbf
6fd81615af95cf51e1a893e04ca34fa1bad7a69c933efcb7fca5c9c091e18a65 retroDE_ps2.pre_ch417b.core.rbf
17d1c1b49aac4d893ed56285cf0703bf375db2ee9f472ea36862cca376ae63fe retroDE_ps2.pre_ch435.core.rbf
55b23b94f51f1aab271d91aa4fa766561c044b9633146f25f4242b431f6ce902 retroDE_ps2.pre_ch436.core.rbf
1c8945e3e8ad85d91a682aa7a068cee487a1459a7f2b899600b24ff7d9751294 retroDE_ps2.pre_ch437.core.rbf
@@ -0,0 +1,7 @@
Ch441 board A/B scanout comparison (2026-07-21)
Candidate Ch441 = 9463602d... : replay DONE rc=0, 311 drains, zero drops, FB SHA d0047677 (byte-identical golden). LPDDR_STATUS[5] SET ~15/16 (0xF1).
Accepted Ch437 = 3dd624cd... : replay DONE rc=0, 311 drains. LPDDR_STATUS[5] SET 16/16 (0xF1) — SAME underflow.
Canonical retroDE_ps2.core.rbf = 56e45346... = Ch439b (previously REJECTED for this exact 0xF1).
VERDICT: both Ch441 and the ACCEPTED Ch437 underflow identically -> reproduced BASELINE scanout defect (LPDDR_STATUS[5]), NOT a Ch441 regression. Ch441 remains the timing-clean / renderer-correct candidate; scanout defect stays OPEN. Ch441 NOT promoted to canonical.
@@ -0,0 +1,59 @@
# Ch441 board validation — framebuffer reconciliation (zsrt139f52)
Scope: reconcile the one framebuffer-content discrepancy seen during the Ch441↔Ch437
A/B, and record the Codex-authorized zero-RTL re-dump triage that classifies it.
## The A/B FB discrepancy (as first observed)
During the Ch441-vs-Ch437 A/B, the Ch437 `--dump-fb` readback logged
`sum32=0xaf7b63f3 xor32=0x33178b8f`, differing from the accepted golden
`sum32=0xaad0b94d xor32=0x33138181`. Local word-level diff localized the entire
difference to **exactly 8 words — the first 256-bit LPDDR beat, row 0, x=0..7, y=0**
(307192/307200 = 99.997% byte-identical). In all 8 words the **alpha byte matched**;
only RGB differed (golden ≈ 0x0b dark, dump ≈ 0xb6 bright, a uniform ~+0xaa lift).
Those 8 words fully account for the logged sum32/xor32 deltas and nothing else.
Artifacts:
- `sh3_zsrt139f52_ch437_ab_fb.mem` SHA-256 `8fd30cdb926ab7a73c39827a2e3352b72c92af78ebaa7b4d69fd9715bf693aec`
- golden / prior-Ch437 / Ch441 A/B all: SHA-256 `d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`
Key architectural fact: `--dump-fb` reads LPDDR via `gs_lpddr_rd_probe` (**arid=1**),
a different AXI master from the scanout line-buffer (`gs_lpddr_scanout_lb`, **arid=3**).
So this discrepancy lives on the probe-readback / write path, NOT the scanout/HDMI path.
## Zero-RTL re-dump triage (Codex-authorized; no RTL / Quartus / builds)
Image under test: `retroDE_ps2.ch437.candidate.rbf` (the anomaly image), CORE_ID 0x50533200.
Two independent axes, on the same f52 replay (`sh3_zsrt139f52_epochs.txt`):
1. **Write-side determinism** — three full render+dumps (A/B/C).
2. **Readback determinism** — repeated pure-probe re-reads of words 0..15 via the
`OFF_LPDDR_RDADDR` (0x03C) latch, with NO re-render, on the frame left by dump C.
### Result — anomaly did NOT reproduce
| dump | SHA-256 | sum32 | words 0..7 |
|------|---------|-------|------------|
| A (render+dump) | `d0047677…` | `0xaad0b94d` | golden (dark) |
| B (render+dump) | `d0047677…` | `0xaad0b94d` | golden (dark) |
| C (render+dump) | `d0047677…` | `0xaad0b94d` | golden (dark) |
First 16 words, identical across A/B/C:
`040c0c0b 030c0b0b 030a0909 030b0b0b 030d0b0b 040e0c0c 050f0e0e 050f0e0e 050b0a0a 050a0909 05090808 050a0a0a 050e0d0d 050d0d0c 050d0c0b 050d0b0b`
Pure-probe re-reads (8 rounds, no re-render) returned those same values every round —
words 0..7 stable and golden, no bright first beat.
### Classification
The earlier 8-word bright first-beat was a **non-reproducible one-time transient** in that
single A/B dump — not a stable property of the stored framebuffer and not a deterministic
write or readback defect. Three fresh renders + repeated probe re-reads are byte-identical
to golden across the full frame, including words 0..7. **Framebuffer-memory integrity for
this scene is confirmed deterministic (`d0047677…`).** Evidence integrity is resolved.
This triage does NOT diagnose `LPDDR_STATUS[5]`; that is the scanout/HDMI (arid=3) path,
addressed separately (see `scanout_status_bit5_proposal.md`).
### Raw logs
`sh3_zsrt139f52_ch437_redump_[ABC].log`, `redump_triage_console.log`.
@@ -0,0 +1,34 @@
=== render+dump A ===
[sched] epoch 309: zero fragment drops (snap=0, sticky=0)
[sched] epoch 310: zero fragment drops (snap=0, sticky=0)
[sched] dump-fb: wrote 307200 words -> ch437_redump_A.mem sum32=0xaad0b94d xor32=0x33138181 nonzero=245155 bounds=(0,0)..(511,479) FB=640x480
[sched] DONE rc=0 (all gates passed)
d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b ch437_redump_A.mem
first16(A):
040c0c0b 030c0b0b 030a0909 030b0b0b 030d0b0b 040e0c0c 050f0e0e 050f0e0e 050b0a0a 050a0909 05090808 050a0a0a 050e0d0d 050d0d0c 050d0c0b 050d0b0b
=== render+dump B ===
[sched] epoch 309: zero fragment drops (snap=0, sticky=0)
[sched] epoch 310: zero fragment drops (snap=0, sticky=0)
[sched] dump-fb: wrote 307200 words -> ch437_redump_B.mem sum32=0xaad0b94d xor32=0x33138181 nonzero=245155 bounds=(0,0)..(511,479) FB=640x480
[sched] DONE rc=0 (all gates passed)
d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b ch437_redump_B.mem
first16(B):
040c0c0b 030c0b0b 030a0909 030b0b0b 030d0b0b 040e0c0c 050f0e0e 050f0e0e 050b0a0a 050a0909 05090808 050a0a0a 050e0d0d 050d0d0c 050d0c0b 050d0b0b
=== render+dump C ===
[sched] epoch 309: zero fragment drops (snap=0, sticky=0)
[sched] epoch 310: zero fragment drops (snap=0, sticky=0)
[sched] dump-fb: wrote 307200 words -> ch437_redump_C.mem sum32=0xaad0b94d xor32=0x33138181 nonzero=245155 bounds=(0,0)..(511,479) FB=640x480
[sched] DONE rc=0 (all gates passed)
d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b ch437_redump_C.mem
first16(C):
040c0c0b 030c0b0b 030a0909 030b0b0b 030d0b0b 040e0c0c 050f0e0e 050f0e0e 050b0a0a 050a0909 05090808 050a0a0a 050e0d0d 050d0d0c 050d0c0b 050d0b0b
=== pure-probe re-reads of words 0-15, 8 rounds, NO re-render (frame left by dump C) ===
round0: 0x040C0C0B 0x030C0B0B 0x030A0909 0x030B0B0B 0x030D0B0B 0x040E0C0C 0x050F0E0E 0x050F0E0E 0x050B0A0A 0x050A0909 0x05090808 0x050A0A0A 0x050E0D0D 0x050D0D0C 0x050D0C0B 0x050D0B0B
round1: 0x040C0C0B 0x030C0B0B 0x030A0909 0x030B0B0B 0x030D0B0B 0x040E0C0C 0x050F0E0E 0x050F0E0E 0x050B0A0A 0x050A0909 0x05090808 0x050A0A0A 0x050E0D0D 0x050D0D0C 0x050D0C0B 0x050D0B0B
round2: 0x040C0C0B 0x030C0B0B 0x030A0909 0x030B0B0B 0x030D0B0B 0x040E0C0C 0x050F0E0E 0x050F0E0E 0x050B0A0A 0x050A0909 0x05090808 0x050A0A0A 0x050E0D0D 0x050D0D0C 0x050D0C0B 0x050D0B0B
round3: 0x040C0C0B 0x030C0B0B 0x030A0909 0x030B0B0B 0x030D0B0B 0x040E0C0C 0x050F0E0E 0x050F0E0E 0x050B0A0A 0x050A0909 0x05090808 0x050A0A0A 0x050E0D0D 0x050D0D0C 0x050D0C0B 0x050D0B0B
round4: 0x040C0C0B 0x030C0B0B 0x030A0909 0x030B0B0B 0x030D0B0B 0x040E0C0C 0x050F0E0E 0x050F0E0E 0x050B0A0A 0x050A0909 0x05090808 0x050A0A0A 0x050E0D0D 0x050D0D0C 0x050D0C0B 0x050D0B0B
round5: 0x040C0C0B 0x030C0B0B 0x030A0909 0x030B0B0B 0x030D0B0B 0x040E0C0C 0x050F0E0E 0x050F0E0E 0x050B0A0A 0x050A0909 0x05090808 0x050A0A0A 0x050E0D0D 0x050D0D0C 0x050D0C0B 0x050D0B0B
round6: 0x040C0C0B 0x030C0B0B 0x030A0909 0x030B0B0B 0x030D0B0B 0x040E0C0C 0x050F0E0E 0x050F0E0E 0x050B0A0A 0x050A0909 0x05090808 0x050A0A0A 0x050E0D0D 0x050D0D0C 0x050D0C0B 0x050D0B0B
round7: 0x040C0C0B 0x030C0B0B 0x030A0909 0x030B0B0B 0x030D0B0B 0x040E0C0C 0x050F0E0E 0x050F0E0E 0x050B0A0A 0x050A0909 0x05090808 0x050A0A0A 0x050E0D0D 0x050D0D0C 0x050D0C0B 0x050D0B0B
=== triage done ===
@@ -0,0 +1,95 @@
# LPDDR_STATUS[5] scanout underflow — structural audit + diagnostic proposal
Read-only audit (no RTL edits, no compile). Purpose: define the smallest instrumentation
that separates the three causes folded into `LPDDR_STATUS[5]`, before any RTL is written.
Instrumentation implementation is NOT yet authorized — this is the proposal for review.
## What bit5 actually is
`de25_nano_psmct32_raster_demo_top.sv:2694`:
```systemverilog
assign scan_err_w = scanout_lb_eff ? (lb_underflow_w | (lb_rd_errs_w != 32'd0))
: (fc_rd_errs_w != 32'd0);
```
Bit5 is a **logical OR of two independent signals**, and one of them is itself ambiguous:
- `lb_rd_errs_w` — cumulative non-OKAY AXI read responses (real bus error). A 32-bit
counter that already exists but is never surfaced (folded into bit5).
- `lb_underflow_w` — a **sticky** latch (`gs_lpddr_scanout_lb.sv:767-778`) set when
`scan_y >= nf_v`, where `nf_v` is `next_fetch` 2-FF-synchronized into the video domain.
`next_fetch` is a **multi-bit binary counter with NO gray coding** (`:765-768`).
So bit5 conflates **three** roots:
1. **Real prefetch starvation** — the AXI row FSM (`L_IDLE→L_AR→L_R→L_C`, single-beat,
throttled to `disp_row+1`) falls behind the raster. Prime suspect: cold-start row 0
— at `frame_start`, `next_fetch` resets and the video side begins scanning almost
immediately (this is the frame-origin location; note it coincides with where the now-
resolved memory transient appeared, but that was arid=1, a different master).
2. **AXI read error**`lb_rd_errs_w != 0`, a genuine non-OKAY response.
3. **CDC false positive** — during a multi-bit `next_fetch` increment (e.g. 127→128 flips
8 bits) the ungray-coded 2-FF sync can momentarily present a *low* `nf_v`; the sticky
latch then sets `underflow` permanently off one transient cycle. The RTL comment even
assumes "a 1-off transient is harmless" — but a sticky latch makes it not harmless.
## What the current evidence can and cannot say
- The FB dump / PNG is a **probe (arid=1)** read of LPDDR memory. It proves memory content,
and says NOTHING about the **scanout/HDMI path (arid=3)** where bit5 lives.
- We have therefore never measured the actual scanout output. bit5 could be a real HDMI
starvation, a real AXI read error, or a pure CDC status artifact — currently indistinguishable.
## Proposed instrumentation (AD) — proposal only, not authorized
**(A) Split the OR — near-zero cost.** Surface `lb_rd_errs_w` in its own status register
(precedent: tex_rd_errs@0x068, reload_rd_errs@0x090) and give `lb_underflow` its own bit.
Immediately separates cause #2 (nonzero rd_errs ⇒ real bus error) from #1/#3
(underflow-set with rd_errs==0 ⇒ starvation-or-CDC).
**(B) First-failure latch (video domain, ~3 small regs).** On the 0→1 edge of `underflow_v`,
latch `fail_scan_y`, `fail_nf_v`, a free-running `frame_counter`, and 1 bit for which
sub-term tripped (`scan_y>=nf_v` vs the filter-lookahead term). Captures where/when/which.
`fail_scan_y==0 & fail_frame==0` ⇒ cold-start row-0 starvation; large `scan_y` with `nf_v`
momentarily small ⇒ CDC glitch.
**(C) Disambiguate #1 vs #3 without touching the CDC.** Also latch `nf_s0` and the next
cycle's `nf_v` at first failure: a one-cycle dip that self-heals = CDC transient; a persistent
`scan_y>=nf_v` = real starvation. (Definitive later fix, not now: gray-code `next_fetch`
across the sync, or require the compare to hold 2 consecutive video cycles before setting
sticky.)
**(D) HDMI-vs-memory — the key measurement.** Add a per-frame **scanout CRC**: rolling
sum32/xor32 over the in-window `r/g/b` stream, latched per frame, exposed via status
(~2×32-bit accumulators + frame latch). Compare against a golden scanout-CRC computed from
the same FB in sim. This exercises the actual HDMI-producing path independent of the probe,
and is the one measurement that separates "memory right, HDMI wrong" (real scanout/underflow)
from "memory wrong" (write path — already ruled out for this scene by the re-dump triage).
## Recommended smallest first step
(A) + (B) together are minimal, add no wide arithmetic, and already partition the three
causes for the observed persistent 0xF1. (D) is the follow-on if (A)/(B) point at the
scanout path rather than a status artifact. Selection is deferred to owner/Codex.
## Interpreting 0x120 / 0x124 on the production f52 scene (Ch442, Codex review)
The A+B diagnostic is IMPLEMENTED (Ch442, RTL + SDC + focused TB, sim-only). Read the
registers with these caveats:
- **Cold start is source row 32, NOT 0.** The SH3 DISPLAY2 profile runs `V_SOURCE_START=32`
(DISPFB2.DBY=32) with the 15:14 vertical map, so `scan_y` begins at 32. A row-zero-miss
therefore reads `0x124` `scan_y≈32` / `nf_v≈32`**not** zero. (Zero only appears in
synthetic `V_SOURCE_START=0` configs; the focused TB checks both.)
- **Both cause bits may assert.** With the vertical linear/binomial filter active, a miss can
satisfy the base term (`scan_y>=nf_v`) AND the lookahead term (`scan_y+1>=nf_v`) at the same
vphase, so `0x120[3]` and `0x120[4]` can both be 1. Treat them as "which rows were short,"
not mutually exclusive.
- **One snapshot NARROWS, it does not PROVE.** A single first-failure `nf_v`/`nf_s0` pair
distinguishes a real AXI error (`0x120[2]=1`) from an underflow (`[1]=1,[2]=0`), and locates
the first short row — but by itself it does **not** definitively separate a sustained
starvation from a transient `next_fetch` CDC-lag false positive. That separation needs the
later gray-code/2-cycle-agreement work (deferred), or repeated captures across frames.
`bit5` at `0x02C` is unchanged; `0x118/0x11C` remain reserved.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,17 @@
# Ch437 LPDDR_STATUS 0x02C active-scanout sampling (video_src=1)
sample 1: 0x000000F1 bit5=32
sample 2: 0x000000F1 bit5=32
sample 3: 0x000000F1 bit5=32
sample 4: 0x000000F1 bit5=32
sample 5: 0x000000F1 bit5=32
sample 6: 0x000000F1 bit5=32
sample 7: 0x000000F1 bit5=32
sample 8: 0x000000F1 bit5=32
sample 9: 0x000000F1 bit5=32
sample 10: 0x000000F1 bit5=32
sample 11: 0x000000F1 bit5=32
sample 12: 0x000000F1 bit5=32
sample 13: 0x000000F1 bit5=32
sample 14: 0x000000F1 bit5=32
sample 15: 0x000000F1 bit5=32
sample 16: 0x000000F1 bit5=32
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

File diff suppressed because it is too large Load Diff
@@ -0,0 +1,17 @@
# Ch441 LPDDR_STATUS 0x02C active-scanout sampling (video_src=1, CTRL 0x018=0x5)
sample 1: 0x000000F1 bit5=32
sample 2: 0x000000F1 bit5=32
sample 3: 0x000000F1 bit5=32
sample 4: 0x000000F1 bit5=32
sample 5: 0x000000F1 bit5=32
sample 6: 0x000000F1 bit5=32
sample 7: 0x000000F1 bit5=32
sample 8: 0x000000D1 bit5=0
sample 9: 0x000000F1 bit5=32
sample 10: 0x000000F1 bit5=32
sample 11: 0x000000F1 bit5=32
sample 12: 0x000000F1 bit5=32
sample 13: 0x000000F1 bit5=32
sample 14: 0x000000F1 bit5=32
sample 15: 0x000000F1 bit5=32
sample 16: 0x000000F1 bit5=32
@@ -0,0 +1,13 @@
SESSION 1
0x02C=0x000000F1
0x120=0x0000003B
0x124=0x02008020
SESSION 2
0x02C=0x000000F1
0x120=0x0000003B
0x124=0x02008020
SESSION 3
0x02C=0x000000F1
0x120=0x0000003B
0x124=0x02008020
DONE
@@ -0,0 +1,45 @@
# Ch443 board A+B diagnostic — verdict (zsrt139f52, 2026-07-22)
Image: `retroDE_ps2.ch443.core.rbf` SHA `d15deb42c99212d49715335d460357dc3f2487e14cf49bd60c7ad63ba52b9990`
(26.1.0 Build 110 GUI fit of commit 4358bc3; non-canonical diagnostic candidate). Canonical
`retroDE_ps2.core.rbf` untouched; Ch441 restored after capture.
## Fit acceptance (all met)
Timing: 0 violated setup/hold paths, EMIF setup +0.177; AWREADY family absent; drain multicycle
bound (`Ch443 SDC: texture drain_idx_q -> tex_mem 2-cycle setup / 1-cycle hold (16 src -> 2053 dst)`);
monolithic **128-M20K** `tex_mem`; RAM 317/358 (89%), ALM 85%.
## Board render
f52 replay `DONE rc=0`, zero drops, texture CRC `0x13cfe390` == expected, FB **byte-identical to
golden** (SHA `d0047677…`, sum32 `0xaad0b94d`). The AW buffer + F_SETTLE 2-cycle drain + monolithic
tex_mem are all functionally correct on silicon.
## A+B diagnostic — 3 independent source-enable sessions, BIT-IDENTICAL each time
| reg | raw | decode |
|-----|-----|--------|
| 0x02C LPDDR_STATUS | `0x000000F1` | idle, scan_cache_valid, **scan_rd_err(bit5)=1**, frame_drained, clear_done |
| 0x120 SCAN_DIAG_STATUS | `0x0000003B` | valid=1, **underflow=1**, **read-error-nonzero=0**, cause_base=1, cause_lookahead=1, line_valid=1, vphase=0 |
| 0x124 SCAN_DIAG_FIRST | `0x02008020` | **scan_y=32, nf_v=32, nf_s0=32** |
## VERDICT
The `LPDDR_STATUS[5]` `0xF1` symptom is definitively a **line-buffer prefetch UNDERFLOW, NOT an AXI
read error** — `0x120` shows underflow=1 with read-error-nonzero=**0**. This resolves the three-way
ambiguity Codex flagged: it is cause #1 (starvation-class), not cause #2 (AXI read errors).
The first failure is pinned to **scan_y = 32 = V_SOURCE_START** (SH3 DISPFB2.DBY=32, the first
displayed source row) — exactly the production cold-start row-32 miss predicted. `nf_v = nf_s0 = 32`
= `next_fetch` at its frame-start reset value, i.e. the prefetch had not advanced past row 32 when
row 32 was first displayed. Both cause bits assert (base + lookahead), consistent with the vertical
filter. line_valid=1 confirms the EMIF read path works — this is a first-displayed-row ordering race,
not total starvation. Deterministic across 3 independent sessions ⇒ a reproducible STRUCTURAL
first-row prefetch boundary, not a random glitch.
## Caveat (per Codex) + next
A single snapshot NARROWS but does not by itself PROVE sustained starvation vs a `next_fetch` CDC-lag
at reset (nf_v=32 is exactly the reset value). What is now PROVEN: (a) not an AXI read error; (b) the
first miss is the cold-start row-32 boundary; (c) deterministic. Definitive starvation-vs-CDC
separation needs the deferred follow-up (gray-code `next_fetch` across the sync, or a 2-cycle
compare-agreement, or multi-frame captures). The FB (memory) is byte-perfect; whether this scanout
underflow produces visible HDMI top-row corruption needs an actual HDMI capture (not a memory dump).
Evidence: `ch443_ab_3session_raw.txt`, `sh3_zsrt139f52_ch443_board_fb.mem`, `ch443_rbf.sha256`.
@@ -0,0 +1 @@
d15deb42c99212d49715335d460357dc3f2487e14cf49bd60c7ad63ba52b9990 docs/hardware/ch443_board_validation/retroDE_ps2.ch443.core.rbf
File diff suppressed because it is too large Load Diff
@@ -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
@@ -0,0 +1,6 @@
sample 1: 0x02C=0x000000D1 0x120=0x00000733 0x124=0x02A0A027
sample 2: 0x02C=0x000000D1 0x120=0x00000D33 0x124=0x03E0F03B
sample 3: 0x02C=0x000000D1 0x120=0x00000130 0x124=0x0421003F
sample 4: 0x02C=0x000000D1 0x120=0x00000D30 0x124=0x06A1A067
sample 5: 0x02C=0x000000F1 0x120=0x00000B30 0x124=0x0DA360D7
sample 6: 0x02C=0x000000D1 0x120=0x00000B30 0x124=0x0AA2A0A7
@@ -0,0 +1 @@
bef7d7bfe83ddcc6368105cd8f46594ef0407a2cdc8c2139a3b26ef0adfef7ff docs/hardware/ch443e_board_validation/retroDE_ps2.ch443e.core.rbf
@@ -0,0 +1,57 @@
# Ch443f board validation
Date: 2026-07-24
Candidate:
- Git commit: `4110846` (`Ch443f timing: preload Z-RMW eviction awaddr at fill`)
- RBF: `retroDE_ps2.ch443f.4110846.core.rbf`
- SHA-256: `59c6372e1cfeba62dccdce860f7ab365255ef32a77c3f7b13909b4c7cff16a87`
- Quartus: 26.1.0 Build 110, Seed 3
Fit acceptance:
- Setup: `+0.068 ns`
- Hold: `0.000 ns`
- Recovery: `+1.080 ns`
- Removal: `+0.003 ns`
- Minimum pulse width: `+0.200 ns`
- RAM: `322 / 358`
- Zero violated paths
Board load:
- Remote candidate hash matched the local artifact.
- `core_loader.sh` reported `Fabric loaded successfully`.
- `fpga0` state: `operating`
- `CORE_ID`: `0x50533200`
- ABI: `0x00000100`
- Board canonical `/home/terasic/cores/retroDE_ps2.core.rbf` was not modified and
remained SHA-256 `56e45346338907eb6c307320757774ac65efa56dabb644906a5600f5ea73a982`.
Functional replay:
- Complete 311-epoch `sh3_zsrt139f52` replay finished `DONE rc=0`.
- Every epoch passed texture CRC, drain, and zero-fragment-drop gates.
- Framebuffer SHA-256:
`d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b`.
- The board framebuffer was byte-identical to the retained Ch443e/golden
framebuffer.
Scanout diagnostic:
- 60 samples at 50 ms spacing plus 60 samples at non-harmonic 7 ms spacing.
- All 120 observations were identical:
- `0x02C = 0x000000D1`: scan-error bit 5 clear.
- `0x120 = 0x00000018`: qualified valid=0, underflow=0, read-error=0,
live `pmax=0`.
- `0x124 = 0x00408020`: stale snapshot payload; ignored because valid=0.
- No fifth line buffer is justified. The Ch443e four-buffer/lead-2 change fixed
the real lookahead starvation; the remaining pre-Ch443f indication was caused
by the raw readiness CDC / synchronization transient.
Disposition:
- Ch443f is timing-clean, renderer-correct, and scanout-clean on silicon.
- Board left running the Ch443f candidate.
- Canonical artifact left untouched pending an explicit promotion instruction.
@@ -0,0 +1,5 @@
59c6372e1cfeba62dccdce860f7ab365255ef32a77c3f7b13909b4c7cff16a87 retroDE_ps2.ch443f.4110846.core.rbf
d0047677371a0f6e4e319458926f604a8599c92baaf34d09b8e1fd452e31662b sh3_zsrt139f52_ch443f_board_fb.mem
6b336f86b5fc2e3636fa6fa602065041063611df51963b4a5978a130a517a5c9 sh3_zsrt139f52_ch443f_board_replay.log
c41cbfc3d12d8594244895f415a9179223f618e54797f4c3c316ed542bd74a9c ch443f_scan_diag_60samples.log
c41cbfc3d12d8594244895f415a9179223f618e54797f4c3c316ed542bd74a9c ch443f_scan_diag_60samples_phase_sweep.log
@@ -0,0 +1,60 @@
sample 01: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 02: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 03: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 04: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 05: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 06: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 07: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 08: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 09: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 10: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 11: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 12: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 13: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 14: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 15: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 16: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 17: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 18: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 19: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 20: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 21: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 22: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 23: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 24: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 25: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 26: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 27: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 28: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 29: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 30: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 31: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 32: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 33: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 34: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 35: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 36: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 37: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 38: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 39: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 40: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 41: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 42: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 43: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 44: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 45: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 46: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 47: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 48: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 49: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 50: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 51: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 52: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 53: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 54: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 55: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 56: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 57: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 58: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 59: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
sample 60: 0x02C=0x000000D1 0x120=0x00000018 0x124=0x00408020
+279 -13
View File
@@ -14,7 +14,29 @@
module gs_async_fifo #(
parameter int WIDTH = 320, // {addr[31:0], data[255:0], strb[31:0]}
parameter int DEPTH = 16 // power of two
parameter int DEPTH = 16, // power of two
// Infer a synchronous read port when set. This is useful for deep/wide
// FIFOs whose bank-select mux cannot meet a fast rclk as an FWFT output.
parameter bit REGISTERED_READ = 1'b0,
// Ch438 timing cut for very deep/wide registered-read FIFOs. Splitting the
// payload into two independently inferred RAMs gives each half its own
// preserved read-address launch register. This removes the single 744-load
// port-B address net seen on the 93x16K production request FIFO while
// preserving depth, order, and one-cycle read behavior.
parameter bit BANKED_READ = 1'b0,
// Ch439e: split a deep/wide memory in both dimensions. Two depth banks
// times two width banks leave each physical read-address copy driving
// roughly one quarter of the original M20K tree. The registered outputs
// need only a 2:1 depth-bank select; FIFO depth and latency are unchanged.
parameter bit QUADRANT_READ = 1'b0,
// Ch440: two depth banks x FOUR width banks (eight physical RAMs). Like
// QUADRANT_READ but splits the payload into four width banks instead of two,
// halving each preserved read-address register's M20K load AGAIN (total M20K
// is EXPECTED to stay ~the same, pending synthesis), while KEEPING
// QUADRANT_READ's 2:1 depth output selector (no
// new/deeper output mux). Depth, one-cycle read latency, ordering, capacity,
// and interface behaviour are identical to QUADRANT_READ.
parameter bit QUAD_WIDTH4_READ = 1'b0
) (
// write domain
input logic wclk,
@@ -32,11 +54,52 @@ module gs_async_fifo #(
localparam int AW = $clog2(DEPTH);
logic [WIDTH-1:0] mem [0:DEPTH-1];
localparam int BANK_LO_W = WIDTH / 2;
localparam int BANK_HI_W = WIDTH - BANK_LO_W;
logic [BANK_LO_W-1:0] mem_lo [0:DEPTH-1];
logic [BANK_HI_W-1:0] mem_hi [0:DEPTH-1];
localparam int HALF_DEPTH = DEPTH / 2;
localparam int HALF_AW = AW - 1;
logic [BANK_LO_W-1:0] mem_lo0 [0:HALF_DEPTH-1];
logic [BANK_LO_W-1:0] mem_lo1 [0:HALF_DEPTH-1];
logic [BANK_HI_W-1:0] mem_hi0 [0:HALF_DEPTH-1];
logic [BANK_HI_W-1:0] mem_hi1 [0:HALF_DEPTH-1];
// Dedicated write-port staging lets the fitter duplicate/place the RAM
// address register beside a wide banked memory. Driving every bank
// directly from the shared binary pointer created a 310 MHz high-fanout
// wbin -> RAM-address path in the 321-bit color FIFO. The opposite-domain
// pointer requires two synchronizer cycles before a reader can observe a
// write, so committing the RAM one local cycle later is CDC-safe.
logic [AW-1:0] waddr_q;
logic [WIDTH-1:0] wdata_q;
logic wwrite_q;
// ---- binary + gray pointers (one extra MSB for full/empty disambiguation) ----
logic [AW:0] wbin, wgray, wbin_nxt, wgray_nxt;
logic [AW:0] wbin, wgray, wbin_nxt;
logic [AW:0] wcommit, wcommit_nxt;
logic wfull_nxt; // Ch352 — combinational next-value for the now-REGISTERED wfull
logic [AW:0] rbin, rgray, rbin_nxt, rgray_nxt;
logic [AW:0] rbin_inc, rgray_inc;
(* keep *) logic rempty_if_hold, rempty_if_pop;
logic rempty_nxt; // Ch357 — combinational next-value for the now-REGISTERED rempty (read-side twin)
logic [WIDTH-1:0] rdata_q;
logic [BANK_LO_W-1:0] rdata_lo_q;
logic [BANK_HI_W-1:0] rdata_hi_q;
// Keep the RAM-facing address distinct from the binary/Gray pointer. The
// production request FIFO is one packed 93-bit x 16K macro; splitting it
// into explicit width banks wastes M20Ks at each bank boundary. Retain
// that efficient packing and ask synthesis to duplicate only this launch
// register so no copy drives the complete physical port-B address tree.
(* dont_merge, preserve *) logic [AW-1:0] raddr_q /* synthesis maxfan = 64 */;
(* dont_merge, preserve *) logic [AW-1:0] raddr_lo_q /* synthesis maxfan = 64 */;
(* dont_merge, preserve *) logic [AW-1:0] raddr_hi_q /* synthesis maxfan = 64 */;
(* dont_merge, preserve *) logic [HALF_AW-1:0] raddr_lo0_q /* synthesis maxfan = 32 */;
(* dont_merge, preserve *) logic [HALF_AW-1:0] raddr_lo1_q /* synthesis maxfan = 32 */;
(* dont_merge, preserve *) logic [HALF_AW-1:0] raddr_hi0_q /* synthesis maxfan = 32 */;
(* dont_merge, preserve *) logic [HALF_AW-1:0] raddr_hi1_q /* synthesis maxfan = 32 */;
logic [BANK_LO_W-1:0] rdata_lo0_q, rdata_lo1_q;
logic [BANK_HI_W-1:0] rdata_hi0_q, rdata_hi1_q;
logic rbank_addr_q, rbank_data_q;
// synchronized opposite-domain gray pointers (2-FF)
logic [AW:0] rgray_s1, rgray_s2; // read gray -> write domain
@@ -48,42 +111,245 @@ module gs_async_fifo #(
// ---------------- write domain ----------------
assign wbin_nxt = wbin + (wr && !wfull);
assign wgray_nxt = bin2gray(wbin_nxt);
// full: next write gray == read gray with top two bits inverted. Ch352 — wfull is now a REGISTERED flag
// (Cummings canonical). The previous `assign wfull = (wgray_nxt == ...)` was combinational, and since
// wgray_nxt <- wbin_nxt <- wfull, it formed a wbin_nxt->wgray_nxt->wfull->wbin_nxt COMBINATIONAL LOOP that
// Quartus reports and that made Place churn. Registering it breaks the loop with no overflow-behavior change:
// wfull still asserts the cycle after the filling write (full is computed from wgray_nxt = the pointer AFTER
// the current write), so the (DEPTH+1)th write is still blocked. rempty is intentionally left unchanged.
assign wfull_nxt = (wgray_nxt == {~rgray_s2[AW:AW-1], rgray_s2[AW-2:0]});
// the current write), so the (DEPTH+1)th write is still blocked. Ch357 — rempty is now the registered read-side twin.
assign wfull_nxt = (bin2gray(wbin_nxt) == {~rgray_s2[AW:AW-1], rgray_s2[AW-2:0]});
// `wbin` is the allocation pointer (an input handshake reserves an
// address). `wcommit` trails it by the one-entry write-port stage and is
// the ONLY pointer published to the read domain. Publishing allocation
// early is unsafe when rclk is faster than wclk: the reader can otherwise
// observe a new pointer before the staged RAM write has occurred.
assign wcommit_nxt = wcommit + wwrite_q;
always_ff @(posedge wclk or negedge wrst_n) begin
if (!wrst_n) begin
wbin <= '0; wgray <= '0; wfull <= 1'b0;
wbin <= '0; wcommit <= '0; wgray <= '0; wfull <= 1'b0;
rgray_s1 <= '0; rgray_s2 <= '0;
waddr_q <= '0; wdata_q <= '0; wwrite_q <= 1'b0;
end else begin
wbin <= wbin_nxt;
wgray <= wgray_nxt;
wcommit <= wcommit_nxt;
wgray <= bin2gray(wcommit_nxt);
wfull <= wfull_nxt;
rgray_s1 <= rgray; // sync read gray into write domain
rgray_s2 <= rgray_s1;
waddr_q <= wbin[AW-1:0];
wdata_q <= wdata;
wwrite_q <= wr && !wfull;
end
end
always_ff @(posedge wclk) if (wr && !wfull) mem[wbin[AW-1:0]] <= wdata;
// ---------------- read domain ----------------
assign rbin_nxt = rbin + (rd && !rempty);
assign rgray_nxt = bin2gray(rbin_nxt);
// `rd` is an accepted-read handshake by contract: every wrapper gates it
// with !rempty. Do not gate it again here. The redundant internal gate
// put rempty in front of the AW+1 pointer adder and, for a deep FIFO, also
// in front of every RAM read-address bank. That feedback was the complete
// Ch405 310 MHz setup-failure family.
// Precompute the increment independent of `rd`, then select between the
// hold/pop results. Writing this as `rbin + rd` put the registered pop
// pulse on the carry input of the complete AW+1 adder and then through
// Gray conversion + empty equality at 310 MHz. The explicit two-result
// form is behavior-identical but leaves `rd` driving only final muxes.
assign rbin_inc = rbin + {{AW{1'b0}}, 1'b1};
assign rgray_inc = bin2gray(rbin_inc);
assign rbin_nxt = rd ? rbin_inc : rbin;
assign rgray_nxt = rd ? rgray_inc : rgray;
assign rempty_if_hold = (rgray == wgray_s2);
assign rempty_if_pop = (rgray_inc == wgray_s2);
assign rempty_nxt = rd ? rempty_if_pop : rempty_if_hold;
always_ff @(posedge rclk or negedge rrst_n) begin
if (!rrst_n) begin
rbin <= '0; rgray <= '0;
rbin <= '0; rgray <= '0; rempty <= 1'b1;
wgray_s1 <= '0; wgray_s2 <= '0;
end else begin
rbin <= rbin_nxt;
rgray <= rgray_nxt;
rempty <= rempty_nxt;
wgray_s1 <= wgray; // sync write gray into read domain
wgray_s2 <= wgray_s1;
end
end
generate
if (QUAD_WIDTH4_READ) begin : g_quad_width4_storage
// Ch440: 2 depth banks x 4 width banks = eight RAMs. Each read-address
// register drives only a HALF_DEPTH x ~(WIDTH/4) RAM -> roughly half the
// M20K load of QUADRANT_READ's hi/lo banks, at an EXPECTED-similar total
// M20K (pending synthesis confirmation). The
// depth-half selection stays a 2:1 OUTPUT mux, byte-for-byte the selector
// QUADRANT_READ already uses -- no new/deeper output mux is introduced.
localparam int W4B0 = WIDTH/4;
localparam int W4B1 = WIDTH/4;
localparam int W4B2 = WIDTH/4;
localparam int W4B3 = WIDTH - 3*(WIDTH/4); // remainder bits
localparam int W4O0 = 0;
localparam int W4O1 = W4B0;
localparam int W4O2 = W4B0 + W4B1;
localparam int W4O3 = W4B0 + W4B1 + W4B2;
logic [W4B0-1:0] m4_0_0 [0:HALF_DEPTH-1]; logic [W4B0-1:0] m4_0_1 [0:HALF_DEPTH-1];
logic [W4B1-1:0] m4_1_0 [0:HALF_DEPTH-1]; logic [W4B1-1:0] m4_1_1 [0:HALF_DEPTH-1];
logic [W4B2-1:0] m4_2_0 [0:HALF_DEPTH-1]; logic [W4B2-1:0] m4_2_1 [0:HALF_DEPTH-1];
logic [W4B3-1:0] m4_3_0 [0:HALF_DEPTH-1]; logic [W4B3-1:0] m4_3_1 [0:HALF_DEPTH-1];
// Atomic staged write; depth-half selected by waddr_q[AW-1], exactly as
// QUADRANT_READ. Same wwrite_q / waddr_q / wdata_q pointer staging.
always_ff @(posedge wclk) begin
if (wwrite_q) begin
if (waddr_q[AW-1]) begin
m4_0_1[waddr_q[HALF_AW-1:0]] <= wdata_q[W4O0 +: W4B0];
m4_1_1[waddr_q[HALF_AW-1:0]] <= wdata_q[W4O1 +: W4B1];
m4_2_1[waddr_q[HALF_AW-1:0]] <= wdata_q[W4O2 +: W4B2];
m4_3_1[waddr_q[HALF_AW-1:0]] <= wdata_q[W4O3 +: W4B3];
end else begin
m4_0_0[waddr_q[HALF_AW-1:0]] <= wdata_q[W4O0 +: W4B0];
m4_1_0[waddr_q[HALF_AW-1:0]] <= wdata_q[W4O1 +: W4B1];
m4_2_0[waddr_q[HALF_AW-1:0]] <= wdata_q[W4O2 +: W4B2];
m4_3_0[waddr_q[HALF_AW-1:0]] <= wdata_q[W4O3 +: W4B3];
end
end
end
if (REGISTERED_READ) begin : g_registered_read
// Eight preserved read-address launch copies, one per RAM, so no
// copy drives more than one quadrant's physical address tree.
(* dont_merge, preserve *) logic [HALF_AW-1:0] r4a_0_0 /* synthesis maxfan = 32 */;
(* dont_merge, preserve *) logic [HALF_AW-1:0] r4a_1_0 /* synthesis maxfan = 32 */;
(* dont_merge, preserve *) logic [HALF_AW-1:0] r4a_2_0 /* synthesis maxfan = 32 */;
(* dont_merge, preserve *) logic [HALF_AW-1:0] r4a_3_0 /* synthesis maxfan = 32 */;
(* dont_merge, preserve *) logic [HALF_AW-1:0] r4a_0_1 /* synthesis maxfan = 32 */;
(* dont_merge, preserve *) logic [HALF_AW-1:0] r4a_1_1 /* synthesis maxfan = 32 */;
(* dont_merge, preserve *) logic [HALF_AW-1:0] r4a_2_1 /* synthesis maxfan = 32 */;
(* dont_merge, preserve *) logic [HALF_AW-1:0] r4a_3_1 /* synthesis maxfan = 32 */;
logic [W4B0-1:0] r4d_0_0, r4d_0_1;
logic [W4B1-1:0] r4d_1_0, r4d_1_1;
logic [W4B2-1:0] r4d_2_0, r4d_2_1;
logic [W4B3-1:0] r4d_3_0, r4d_3_1;
// depth-half selector + its 1-cycle-trailing twin, aligned with the
// registered RAM outputs -- identical timing to QUADRANT_READ.
logic r4bank_addr_q, r4bank_data_q;
always_ff @(posedge rclk) begin
r4a_0_0 <= rbin_nxt[HALF_AW-1:0];
r4a_1_0 <= rbin_nxt[HALF_AW-1:0];
r4a_2_0 <= rbin_nxt[HALF_AW-1:0];
r4a_3_0 <= rbin_nxt[HALF_AW-1:0];
r4a_0_1 <= rbin_nxt[HALF_AW-1:0];
r4a_1_1 <= rbin_nxt[HALF_AW-1:0];
r4a_2_1 <= rbin_nxt[HALF_AW-1:0];
r4a_3_1 <= rbin_nxt[HALF_AW-1:0];
r4bank_addr_q <= rbin_nxt[AW-1];
r4bank_data_q <= r4bank_addr_q;
r4d_0_0 <= m4_0_0[r4a_0_0];
r4d_1_0 <= m4_1_0[r4a_1_0];
r4d_2_0 <= m4_2_0[r4a_2_0];
r4d_3_0 <= m4_3_0[r4a_3_0];
r4d_0_1 <= m4_0_1[r4a_0_1];
r4d_1_1 <= m4_1_1[r4a_1_1];
r4d_2_1 <= m4_2_1[r4a_2_1];
r4d_3_1 <= m4_3_1[r4a_3_1];
end
// 2:1 depth-half select (unchanged from QUADRANT_READ); the four
// width banks are concatenated back into the payload word.
assign rdata = r4bank_data_q ? {r4d_3_1, r4d_2_1, r4d_1_1, r4d_0_1}
: {r4d_3_0, r4d_2_0, r4d_1_0, r4d_0_0};
end else begin : g_fwft_read
assign rdata = rbin[AW-1]
? {m4_3_1[rbin[HALF_AW-1:0]], m4_2_1[rbin[HALF_AW-1:0]], m4_1_1[rbin[HALF_AW-1:0]], m4_0_1[rbin[HALF_AW-1:0]]}
: {m4_3_0[rbin[HALF_AW-1:0]], m4_2_0[rbin[HALF_AW-1:0]], m4_1_0[rbin[HALF_AW-1:0]], m4_0_0[rbin[HALF_AW-1:0]]};
end
end else if (QUADRANT_READ) begin : g_quadrant_storage
// Four physical RAM quadrants: low/high payload width crossed with
// lower/upper address half. Writes remain atomic and use the
// staged allocation address exactly as the monolithic form does.
always_ff @(posedge wclk) begin
if (wwrite_q) begin
if (waddr_q[AW-1]) begin
mem_lo1[waddr_q[HALF_AW-1:0]] <= wdata_q[0 +: BANK_LO_W];
mem_hi1[waddr_q[HALF_AW-1:0]] <= wdata_q[BANK_LO_W +: BANK_HI_W];
end else begin
mem_lo0[waddr_q[HALF_AW-1:0]] <= wdata_q[0 +: BANK_LO_W];
mem_hi0[waddr_q[HALF_AW-1:0]] <= wdata_q[BANK_LO_W +: BANK_HI_W];
end
end
end
if (REGISTERED_READ) begin : g_registered_read
always_ff @(posedge rclk) begin
// Separate launch copies are intentional: each feeds only
// one depth/width quadrant. rbank_data_q trails the
// address-bank selector by the same cycle as the four
// synchronous RAM outputs.
raddr_lo0_q <= rbin_nxt[HALF_AW-1:0];
raddr_lo1_q <= rbin_nxt[HALF_AW-1:0];
raddr_hi0_q <= rbin_nxt[HALF_AW-1:0];
raddr_hi1_q <= rbin_nxt[HALF_AW-1:0];
rbank_addr_q <= rbin_nxt[AW-1];
rbank_data_q <= rbank_addr_q;
rdata_lo0_q <= mem_lo0[raddr_lo0_q];
rdata_lo1_q <= mem_lo1[raddr_lo1_q];
rdata_hi0_q <= mem_hi0[raddr_hi0_q];
rdata_hi1_q <= mem_hi1[raddr_hi1_q];
end
assign rdata = rbank_data_q ? {rdata_hi1_q, rdata_lo1_q}
: {rdata_hi0_q, rdata_lo0_q};
end else begin : g_fwft_read
assign rdata = rbin[AW-1]
? {mem_hi1[rbin[HALF_AW-1:0]], mem_lo1[rbin[HALF_AW-1:0]]}
: {mem_hi0[rbin[HALF_AW-1:0]], mem_lo0[rbin[HALF_AW-1:0]]};
end
end else if (BANKED_READ) begin : g_banked_storage
// Two physical payload banks, written atomically from the same
// staged tuple. Each registered read address drives only its own
// half of the inferred RAM instead of the entire packed macro.
always_ff @(posedge wclk) begin
if (wwrite_q) begin
mem_lo[waddr_q] <= wdata_q[0 +: BANK_LO_W];
mem_hi[waddr_q] <= wdata_q[BANK_LO_W +: BANK_HI_W];
end
end
if (REGISTERED_READ) begin : g_registered_read
always_ff @(posedge rclk) begin
raddr_lo_q <= rbin_nxt[AW-1:0];
raddr_hi_q <= rbin_nxt[AW-1:0];
rdata_lo_q <= mem_lo[raddr_lo_q];
rdata_hi_q <= mem_hi[raddr_hi_q];
end
assign rdata = {rdata_hi_q, rdata_lo_q};
end else begin : g_fwft_read
assign rdata = {mem_hi[rbin[AW-1:0]], mem_lo[rbin[AW-1:0]]};
end
end else begin : g_monolithic_storage
always_ff @(posedge wclk)
if (wwrite_q) mem[waddr_q] <= wdata_q;
if (REGISTERED_READ) begin : g_registered_read
// A synchronous read lets Quartus use the memory output register
// instead of timing a deep bank mux directly into request decode.
// Read the current head every cycle and qualify rdata only at the
// interface. The pointer still advances exclusively on `rd`, so
// this does not consume an entry or change the one-cycle accepted-
// read latency. Leaving the inferred RAM read enable permanently
// active is important for a very wide FIFO: using `rd` as the RAM
// enable made one pop register drive every physical data bank
// (749 loads in the production request FIFO) at 310 MHz.
//
// Ch420: the Ch419 fit proved the enable cut and exposed the same
// topology on portbaddr: rbin[6] directly drove 713 RAM-address
// loads. `raddr_q` tracks the pointer's selected next value, so
// before every edge it equals the current head address. The RAM
// read therefore returns the same entry on the same edge as the
// prior `mem[rbin]` form, including consecutive accepted pops,
// while splitting pointer selection from physical RAM addressing.
//
// raddr_q/rdata_q intentionally have neither enables nor resets.
// The FIFO cannot become nonempty until the synchronized write
// pointer arrives, giving raddr_q multiple clocks to initialize to
// zero after reset. Resetting the wide inferred read structure
// previously created its own high-fanout recovery/setup family.
always_ff @(posedge rclk) begin
raddr_q <= rbin_nxt[AW-1:0];
rdata_q <= mem[raddr_q];
end
assign rdata = rdata_q;
end else begin : g_fwft_read
assign rdata = mem[rbin[AW-1:0]];
assign rempty = (rgray == wgray_s2);
end
end
endgenerate
endmodule : gs_async_fifo
+88
View File
@@ -0,0 +1,88 @@
// retroDE_ps2 — gs_axi_aw_regbuf (Ch443)
//
// Fully-registered ONE-ENTRY AXI AW-channel (write-address) buffer. The AW twin
// of gs_axi_w_regbuf. Inserted between the Z RMW master's (gs_lpddr_z_rmw) AW
// OUTPUT and the write arbiter's s2 AW INPUT to cut the combinational path
// EMIF gen_p2c_ff[23] (AWREADY) -> wr_arb s2_awready -> gs_lpddr_z_rmw
// next-state (`st`, S_SFLUSH_AW / S_FILL_R) — the -0.410 ns EMIF setup family.
//
// Ch441 registered the W channel (gs_axi_w_regbuf u_z_wbuf) but left AW running
// straight from the FSM to the arbiter, so EMIF AWREADY still reached the FSM
// combinationally. This buffer applies the identical structural cut to AW.
//
// CONTRACT (identical to gs_axi_w_regbuf, must hold exactly):
// - FULLY REGISTERED, not a fall-through skid: u_awready depends ONLY on the
// registered occupancy `full`, never on d_awready. Downstream (EMIF) AWREADY
// therefore can never propagate combinationally back into the upstream FSM.
// (i.e. NOT `u_awready = !full || d_awready`.)
// - Buffers the complete {AWADDR, AWLEN, AWSIZE, AWBURST} payload and holds it
// stable downstream until the arbiter accepts it.
// - Exactly-once: an address accepted upstream is delivered downstream exactly
// once. One in flight (the Z RMW issues single-beat writes, AWLEN=0).
// - W and B channels are NOT touched here (W is separately buffered by
// gs_axi_w_regbuf; B passes through). AXI permits AW and W in either order,
// and each one-entry buffer holds its beat until the arbiter accepts it, so
// the AW/W pair still reaches the slave together.
`timescale 1ns/1ps
module gs_axi_aw_regbuf #(
parameter int ADDR_W = 32,
parameter int LEN_W = 8,
parameter int SIZE_W = 3,
parameter int BURST_W = 2
) (
input logic clk,
input logic rst_n,
// upstream — from the Z RMW master's AW output
input logic [ADDR_W-1:0] u_awaddr,
input logic [LEN_W-1:0] u_awlen,
input logic [SIZE_W-1:0] u_awsize,
input logic [BURST_W-1:0] u_awburst,
input logic u_awvalid,
output logic u_awready,
// downstream — to the write arbiter's s2 AW input
output logic [ADDR_W-1:0] d_awaddr,
output logic [LEN_W-1:0] d_awlen,
output logic [SIZE_W-1:0] d_awsize,
output logic [BURST_W-1:0] d_awburst,
output logic d_awvalid,
input logic d_awready
);
logic full;
logic [ADDR_W-1:0] awaddr_q;
logic [LEN_W-1:0] awlen_q;
logic [SIZE_W-1:0] awsize_q;
logic [BURST_W-1:0] awburst_q;
// Upstream ready = registered occupancy ONLY (no d_awready term) -> EMIF AWREADY
// never reaches the upstream FSM combinationally.
assign u_awready = !full;
// Downstream presents the held address, stable until the arbiter accepts it.
assign d_awvalid = full;
assign d_awaddr = awaddr_q;
assign d_awlen = awlen_q;
assign d_awsize = awsize_q;
assign d_awburst = awburst_q;
// One-entry register. Accept an offered upstream address only while empty;
// release only when the arbiter accepts the held address. When full and
// accepted in the same cycle, u_awready is still 0 (full is registered), so
// the next address waits one cycle -> a swap/drop/dup is impossible. Payload
// registers deliberately have no reset (qualified by `full`/d_awvalid; the
// writer cannot present an address until reset releases).
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
full <= 1'b0;
end else if (!full) begin
if (u_awvalid) begin
full <= 1'b1;
awaddr_q <= u_awaddr;
awlen_q <= u_awlen;
awsize_q <= u_awsize;
awburst_q <= u_awburst;
end
end else begin
if (d_awready) full <= 1'b0;
end
end
endmodule : gs_axi_aw_regbuf
+78
View File
@@ -0,0 +1,78 @@
// retroDE_ps2 — gs_axi_r_regbuf (Ch443d)
//
// Fully-registered ONE-ENTRY AXI R-channel (read-response) buffer. The R twin of
// gs_axi_w_regbuf / gs_axi_aw_regbuf. Inserted between the read arbiter's s2 R
// OUTPUT and the texture-cache fill FSM's R INPUT to cut the combinational path
// EMIF gen_p2c_ff[*] (rvalid/rdata) -> gs_texture_cache F_R next-state (`fst`)
// + fill_data_q capture — the -0.043 ns / -0.022 ns EMIF setup family.
//
// CONTRACT (per Codex review, identical shape to gs_axi_w_regbuf):
// - FULLY REGISTERED, not a fall-through skid: u_rready depends ONLY on the
// registered occupancy `full`, never on the texture FSM's downstream d_rready.
// EMIF RVALID therefore can never propagate combinationally into the fill FSM.
// (i.e. NOT `u_rready = !full || d_rready`.)
// - Buffers the COMPLETE {RDATA, RRESP, RLAST} payload and holds it stable on the
// downstream side until the texture FSM accepts it.
// - Capture only on u_rvalid && u_rready; exactly-once, in-order (one in flight —
// the texture fill issues single-beat reads, ARLEN=0/RLAST=1, but RRESP/RLAST
// are preserved regardless).
// - Downstream VALID is `full`; payload held stable until d_rvalid && d_rready.
// - Reset only `full`; payload registers deliberately unreset (qualified by full).
// - The arbiter is unchanged: it completes its R transaction when the response is
// accepted into this buffer (s2_rready = u_rready = !full); the buffer then owns
// delivery to the texture FSM.
`timescale 1ns/1ps
module gs_axi_r_regbuf #(
parameter int RDATA_W = 256,
parameter int RRESP_W = 2
) (
input logic clk,
input logic rst_n,
// upstream — from the read arbiter's s2 R output (EMIF read return)
input logic [RDATA_W-1:0] u_rdata,
input logic [RRESP_W-1:0] u_rresp,
input logic u_rlast,
input logic u_rvalid,
output logic u_rready,
// downstream — to the texture-cache fill FSM's R input
output logic [RDATA_W-1:0] d_rdata,
output logic [RRESP_W-1:0] d_rresp,
output logic d_rlast,
output logic d_rvalid,
input logic d_rready
);
logic full;
logic [RDATA_W-1:0] rdata_q;
logic [RRESP_W-1:0] rresp_q;
logic rlast_q;
// Upstream ready = registered occupancy ONLY (no d_rready term) -> EMIF RVALID
// never reaches the texture fill FSM combinationally.
assign u_rready = !full;
// Downstream presents the held response, stable until the texture FSM accepts it.
assign d_rvalid = full;
assign d_rdata = rdata_q;
assign d_rresp = rresp_q;
assign d_rlast = rlast_q;
// One-entry register. Accept an offered upstream response only while empty;
// release only when the texture FSM accepts the held response. When full and
// accepted in the same cycle, u_rready is still 0 (full is registered), so the
// next response waits one cycle -> a swap/drop/dup is impossible. Payload
// registers deliberately have no reset (qualified by `full`/d_rvalid).
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
full <= 1'b0;
end else if (!full) begin
if (u_rvalid) begin
full <= 1'b1;
rdata_q <= u_rdata;
rresp_q <= u_rresp;
rlast_q <= u_rlast;
end
end else begin
if (d_rready) full <= 1'b0;
end
end
endmodule : gs_axi_r_regbuf
+78
View File
@@ -0,0 +1,78 @@
// retroDE_ps2 — gs_axi_w_regbuf (Ch441)
//
// Fully-registered ONE-ENTRY AXI W-channel buffer. Inserted between the Z RMW
// master's (gs_lpddr_z_rmw) W OUTPUT and the write arbiter's s2 W INPUT to cut
// the combinational path EMIF gen_p2c_ff -> fbr wready -> wr_arb s2_wready ->
// gs_lpddr_z_rmw next-state (`st`) — the -0.016 ns / -0.259 ns-skew setup family.
//
// CONTRACT (per Codex review, must hold exactly):
// - FULLY REGISTERED, not a fall-through skid: u_wready depends ONLY on the
// registered occupancy `full`, never on d_wready. Downstream (EMIF) WREADY
// therefore can never propagate combinationally back into the upstream FSM.
// (i.e. NOT `u_wready = !full || d_wready`.)
// - Buffers the complete {WDATA, WSTRB, WLAST} payload and holds it stable on
// the downstream side until the arbiter accepts it.
// - Exactly-once: a beat accepted upstream is delivered downstream exactly once.
// One beat in flight (the Z RMW issues single-beat writes, AWLEN=0/WLAST=1).
// - AW and B channels are NOT touched (they pass straight through, outside this
// module). The write arbiter's bready_q is unaffected: it still arms only on
// the REAL downstream m_wvalid && m_wready && m_wlast, because this buffer
// sits UPSTREAM of the arbiter and presents a clean registered W to it.
// - The upstream FSM may enter its B-wait state once the beat is accepted here;
// that is safe because EMIF cannot return B until the buffered beat reaches it.
`timescale 1ns/1ps
module gs_axi_w_regbuf #(
parameter int WDATA_W = 256,
parameter int WSTRB_W = 32
) (
input logic clk,
input logic rst_n,
// upstream — from the Z RMW master's W output
input logic [WDATA_W-1:0] u_wdata,
input logic [WSTRB_W-1:0] u_wstrb,
input logic u_wlast,
input logic u_wvalid,
output logic u_wready,
// downstream — to the write arbiter's s2 W input
output logic [WDATA_W-1:0] d_wdata,
output logic [WSTRB_W-1:0] d_wstrb,
output logic d_wlast,
output logic d_wvalid,
input logic d_wready
);
logic full;
logic [WDATA_W-1:0] wdata_q;
logic [WSTRB_W-1:0] wstrb_q;
logic wlast_q;
// Upstream ready = registered occupancy ONLY (no d_wready term) -> EMIF WREADY
// never reaches the upstream FSM combinationally.
assign u_wready = !full;
// Downstream presents the held beat, stable until the arbiter accepts it.
assign d_wvalid = full;
assign d_wdata = wdata_q;
assign d_wstrb = wstrb_q;
assign d_wlast = wlast_q;
// One-entry register. Accept an offered upstream beat only while empty;
// release only when the arbiter accepts the held beat. When full and accepted
// in the same cycle, u_wready is still 0 (full is registered), so the next
// beat waits one cycle -> a swap/drop/dup is impossible. Payload registers
// deliberately have no reset (qualified by `full`/d_wvalid; the writer cannot
// present a beat until reset releases), avoiding a wide reset fanout.
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
full <= 1'b0;
end else if (!full) begin
if (u_wvalid) begin
full <= 1'b1;
wdata_q <= u_wdata;
wstrb_q <= u_wstrb;
wlast_q <= u_wlast;
end
end else begin
if (d_wready) full <= 1'b0;
end
end
endmodule : gs_axi_w_regbuf
+216 -26
View File
@@ -25,7 +25,17 @@
// overflow, done-ish (idle && fifo empty). enable=0 → fully inert.
module gs_lpddr_axi_master #(
parameter int FIFO_DEPTH = 16
parameter int FIFO_DEPTH = 16,
// Ch353 — pixel width in BYTES: 2 = PSMCT16 (default, byte-identical to Ch318), 4 = PSMCT32.
// A 32-byte (256-bit) beat holds 32/PIX_BYTES lanes (16 for PSMCT16, 8 for PSMCT32). The
// &ns beat-complete test is unchanged (a full beat is always 32 strobe bits either way).
parameter int PIX_BYTES = 2,
// Ch357 (Codex) — ELASTIC_BACKPRESSURE: when 1, insert a one-entry elastic (skid) stage between the packer and the
// async FIFO and expose real producer backpressure via px_ready (= the stage can accept a beat this cycle). The packer
// then advances ONLY on px_emit && px_ready, and the partial/EOF flushes retry until the stage accepts — so a beat is
// NEVER dropped even if the FIFO momentarily fills, WITHOUT relying on the "packer never outruns the drain" invariant.
// Default 0 = the legacy direct-write packer, byte-identical (px_ready tied 1). Only u_zc_emit|u_c sets this to 1.
parameter bit ELASTIC_BACKPRESSURE = 1'b0
) (
// GS clock domain — flush pixel stream
input logic gs_clk,
@@ -50,7 +60,15 @@ module gs_lpddr_axi_master #(
input logic ctrl_commit,
input logic px_emit,
input logic [31:0] px_addr, // FB-relative byte address (raster_pixel_fb_addr_q)
input logic [15:0] px_pix16,
input logic [31:0] px_pix32, // pixel data; PSMCT16 callers drive {16'd0, pix16} (low PIX_BITS used)
// Ch357 (Codex) — producer backpressure. ELASTIC_BACKPRESSURE=1: high when the packer can accept px_emit this cycle
// (the elastic stage can take a beat); the producer must HOLD px_emit/px_addr/px_pix32 until px_ready. Tied 1 for the
// legacy packer (no backpressure), so legacy producers that leave it unconnected are unaffected.
output logic px_ready,
// Ch353 — end-of-scene partial-beat flush (pulse). Sparse triangle coverage may leave the final
// beat incomplete; pulse `flush` after the last px_emit to push the dangling partial beat. The
// PSMCT16 tile path always completes beats on tile-row boundaries, so it ties this to 0.
input logic flush,
// f2sdram (LPDDR AXI) clock domain
input logic axi_clk,
@@ -79,15 +97,29 @@ module gs_lpddr_axi_master #(
output logic [31:0] bursts_issued,
output logic [31:0] bresp_err_count,
output logic [31:0] fifo_overflow_count,
output logic idle
output logic idle,
// Ch353 (Codex) — ORDERED drain ack: high after the EOF marker pops (i.e. after the last data beat's BRESP).
// Coherent in the axi_clk domain — the scanout gate uses THIS, not a GS-domain sample of fbw_idle.
output logic frame_drained
);
localparam int PW = 320; // {addr[31:0], data[255:0], strb[31:0]}
localparam int PW = 321; // {marker, addr[31:0], data[255:0], strb[31:0]}
localparam int MARK = PW-1; // Ch353 (Codex) — ordered EOF marker bit. A flush pushes any partial beat then an
// EOF marker; the AXI FSM sets frame_drained when it POPS the marker — which, by the
// in-order FIFO + per-beat B handshake, happens only AFTER the last data beat's BRESP.
// This is a coherent same-(axi)-domain drain ack for the scanout gate (no GS-domain
// sampling of fbw_idle, no pointer-in-flight race).
// Ch353 — pixel-width derived params. At PIX_BYTES=2 these reproduce the exact Ch318 PSMCT16 logic.
localparam int PIX_BITS = PIX_BYTES * 8; // 16 or 32
localparam int LANE_LO = (PIX_BYTES == 4) ? 2 : 1; // low addr bits inside one pixel ($clog2 PIX_BYTES)
localparam logic [PIX_BYTES-1:0] STRB1 = '1; // per-pixel byte-strobe mask (2'b11 or 4'b1111)
localparam logic [255:0] PIX_MASK = {{(256-PIX_BITS){1'b0}}, {PIX_BITS{1'b1}}};
// ============================ GS-clock PACKER ============================
logic [31:0] cur_addr;
logic [255:0] cur_data;
logic [31:0] cur_strb;
logic has_data;
logic flush_pending; // Ch353 — latched end-of-scene flush request (pushes partial then EOF marker)
logic fifo_wr;
logic [PW-1:0] fifo_wdata;
logic fifo_wfull;
@@ -112,10 +144,6 @@ module gs_lpddr_axi_master #(
end
end
// High for the one cycle the snapshot updates. Admission is blocked then so the FSM never consumes a beat
// straddling a config change (old base/arm on the pop cycle, new on the next).
wire commit_edge = (commit_sync[2] != commit_sync[1]);
// arm crosses from axi_clk into gs_clk — 2-FF synchronizer (from the COHERENT latched arm).
logic arm_s1, arm_gs;
always_ff @(posedge gs_clk or negedge gs_rst_n) begin
@@ -123,35 +151,50 @@ module gs_lpddr_axi_master #(
else begin arm_s1 <= arm_axi; arm_gs <= arm_s1; end
end
// Ch357 (Codex) — effective FIFO write bus (muxed by the generate below) + one-entry elastic stage (elastic mode only).
logic fifo_wr_ram; // drive to u_fifo.wr (already ANDed with !fifo_wfull in both modes)
logic [PW-1:0] fifo_wdata_ram; // drive to u_fifo.wdata
logic stage_valid; // elastic: a beat waits in the skid stage
logic [PW-1:0] stage_data; // elastic: the staged beat
wire stage_ready = !stage_valid || !fifo_wfull; // elastic: stage can accept a beat THIS cycle
wire stage_drain = stage_valid && !fifo_wfull; // elastic: staged beat enters FIFO THIS cycle
generate
if (!ELASTIC_BACKPRESSURE) begin : g_legacy
// ------- LEGACY packer (byte-identical to the pre-Ch357 direct-write path) -------
assign px_ready = 1'b1; // no backpressure exposed
assign fifo_wr_ram = fifo_wr && !fifo_wfull;
assign fifo_wdata_ram = fifo_wdata;
always_ff @(posedge gs_clk or negedge gs_rst_n) begin
if (!gs_rst_n) begin
cur_addr <= '0; cur_data <= '0; cur_strb <= '0; has_data <= 1'b0;
fifo_wr <= 1'b0; fifo_wdata <= '0; fifo_overflow_count <= '0;
fifo_wr <= 1'b0; fifo_wdata <= '0; fifo_overflow_count <= '0; flush_pending <= 1'b0;
end else begin
fifo_wr <= 1'b0;
if (flush) flush_pending <= 1'b1; // Ch353 — latch the end-of-scene flush request
if (enable && arm_gs && px_emit) begin // gate: no accumulation until armed
logic [31:0] abeat;
logic [3:0] lane; // 0..15 (which 16-bit lane)
logic [4:0] lane; // 0..LANES-1 (which PIX_BYTES-wide lane in the 32-byte beat)
logic [255:0] nd;
logic [31:0] ns;
abeat = {px_addr[31:5], 5'd0};
lane = px_addr[4:1];
lane = px_addr[4:0] >> LANE_LO;
if (has_data && (abeat != cur_addr)) begin
// line changed before the previous beat filled — flush it, restart
fifo_wdata <= {cur_addr, cur_data, cur_strb};
// line changed before the previous beat filled — flush it (marker=0), restart
fifo_wdata <= {1'b0, cur_addr, cur_data, cur_strb};
fifo_wr <= 1'b1;
cur_addr <= abeat;
cur_data <= (256'(px_pix16) << ({28'd0, lane} * 16));
cur_strb <= (32'd3 << ({28'd0, lane} * 2));
cur_data <= (256'(px_pix32[PIX_BITS-1:0]) << ({27'd0, lane} * PIX_BITS));
cur_strb <= (32'(STRB1) << ({27'd0, lane} * PIX_BYTES));
has_data <= 1'b1;
end else begin
nd = has_data ? cur_data : 256'd0;
ns = has_data ? cur_strb : 32'd0;
nd[ ({28'd0, lane} * 16) +: 16 ] = px_pix16;
ns[ ({28'd0, lane} * 2) +: 2 ] = 2'b11;
nd[ ({27'd0, lane} * PIX_BITS) +: PIX_BITS ] = px_pix32[PIX_BITS-1:0];
ns[ ({27'd0, lane} * PIX_BYTES) +: PIX_BYTES ] = STRB1;
if (&ns) begin
// beat complete (all 16 lanes) — flush, beat consumed
fifo_wdata <= {abeat, nd, ns};
// beat complete (all lanes strobed) — flush (marker=0), beat consumed
fifo_wdata <= {1'b0, abeat, nd, ns};
fifo_wr <= 1'b1;
has_data <= 1'b0;
end else begin
@@ -161,12 +204,148 @@ module gs_lpddr_axi_master #(
has_data <= 1'b1;
end
end
end else if (enable && arm_gs && flush_pending && has_data && !fifo_wr && !fifo_wfull) begin
// Ch353 — end-of-scene partial-beat flush: push the dangling beat (marker=0) so no pixels are
// stranded. Codex — gate on `!fifo_wr && !fifo_wfull`: a PRIOR registered push may still be entering
// the FIFO this cycle (fifo_wfull is REGISTERED, lags a cycle), so wait until no push is in flight
// AND not full, then the !wfull check is accurate. Covers a scene ending on a full beat, a partial
// right after a line-change, and (since the marker also waits for fifo_wr to clear) partial->marker.
// If gated, state is RETAINED and retried — the partial is never dropped.
fifo_wdata <= {1'b0, cur_addr, cur_data, cur_strb};
fifo_wr <= 1'b1;
has_data <= 1'b0;
end else if (enable && arm_gs && flush_pending && !has_data && !fifo_wr && !fifo_wfull) begin
// Ch353 (Codex) — partial (if any) is pushed; now push the ordered EOF MARKER (payload irrelevant),
// same `!fifo_wr && !fifo_wfull` gate. flush_pending retained until accepted. The AXI FSM sets
// frame_drained when it pops this, i.e. after the last data beat's BRESP.
fifo_wdata <= {1'b1, cur_addr, cur_data, cur_strb};
fifo_wr <= 1'b1;
flush_pending <= 1'b0;
end
// overflow witness: a push attempt while the FIFO is full (must stay 0)
if (fifo_wr && fifo_wfull)
fifo_overflow_count <= fifo_overflow_count + 32'd1;
end
end
end else begin : g_elastic
// ------- ELASTIC packer (Codex): full producer backpressure via a one-entry skid stage -------
// px_ready: the input stage can accept a pixel THIS cycle. The packer advances (accumulates, produces a
// beat, retries a flush) ONLY when stage_ready, so every produced beat lands in the stage the SAME cycle
// it is produced -> never dropped. The stage drains into the FIFO whenever !fifo_wfull; simultaneous
// drain+refill keeps the stage full with the new beat (the drain writes the OLD stage_data to the FIFO
// first, via fifo_wr_ram below).
//
// Ch358 (Codex) — REGISTERED INPUT STAGE: capture {derived beat address, lane, pixel} into in_* registers
// BEFORE the packer. The high-address equality (beat-change) compare and the lane shifts then run off
// LOCAL in_* registers instead of gating stage_data[255:0] straight from the producer's cross-module
// px_addr register — the Ch358 fit's failing setup cone (col_out_addr -> stage_data, WNS -0.176). +1 cycle
// pixel latency; ordering preserved: flush_pending is serviced only when in_valid is EMPTY, so the
// partial/EOF marker can never overtake a captured pixel.
logic in_valid;
logic [31:0] in_beat; // {px_addr[31:5], 5'd0} — derived beat address (low 5 bits constant 0)
logic [4:0] in_lane; // px_addr[4:0] >> LANE_LO
logic [PIX_BITS-1:0] in_pix;
// Ch358 (Codex) — PRE-REGISTERED beat-change decision (the 26.1 fit's residual -0.012 family was
// in_beat -> the compare -> stage_data[255:0]): in_same_q = (this pixel's beat == the PREVIOUSLY captured
// pixel's beat, last_beat_q), registered at capture. Exact by invariant: the packer reads the compare ONLY
// when has_data=1 at consumption, and has_data=1 implies the preceding captured pixel MERGED into cur_addr
// (beat-complete and flush both clear has_data and never read it) -> cur_addr == last_beat_q at capture.
// A pending flush cannot intervene while in_valid holds a pixel. No latency change.
logic in_same_q;
logic [31:0] last_beat_q;
// Ch367 — map the lane-selected pixel into full beat masks in a distinct registered stage. The beat
// accumulator below then sees only registered masks, not in_lane driving cur_data[255:0] directly.
logic map_valid, map_same_q;
logic [31:0] map_beat;
logic [255:0] map_data;
logic [255:0] map_mask;
logic [31:0] map_strb;
wire map_ready = !map_valid || stage_ready;
wire in_advance = in_valid && map_ready; // input stage advances into the map stage
assign px_ready = !in_valid || map_ready;
assign fifo_wr_ram = stage_drain; // drains the CURRENT stage_data (already implies !fifo_wfull)
assign fifo_wdata_ram = stage_data;
always_ff @(posedge gs_clk or negedge gs_rst_n) begin
if (!gs_rst_n) begin
cur_addr <= '0; cur_data <= '0; cur_strb <= '0; has_data <= 1'b0;
stage_valid <= 1'b0; stage_data <= '0; fifo_overflow_count <= '0; flush_pending <= 1'b0;
in_valid <= 1'b0; in_beat <= '0; in_lane <= '0; in_pix <= '0;
in_same_q <= 1'b0; last_beat_q <= '0;
map_valid <= 1'b0; map_same_q <= 1'b0; map_beat <= '0; map_data <= '0; map_mask <= '0; map_strb <= '0;
end else begin
logic stage_load; // a beat is being loaded into the stage this cycle
stage_load = 1'b0;
// (0) INPUT STAGE: consume first; a same-cycle capture below overrides (set wins) — classic
// pipeline advance. Capture is gated on px_ready so an occupied, non-advancing stage is never
// overwritten (the producer must HOLD px_emit/px_addr/px_pix32 until px_ready, as before).
if (in_advance) in_valid <= 1'b0;
if (enable && arm_gs && px_emit && px_ready) begin
in_valid <= 1'b1;
in_beat <= {px_addr[31:5], 5'd0};
in_lane <= px_addr[4:0] >> LANE_LO;
in_pix <= px_pix32[PIX_BITS-1:0];
in_same_q <= ({px_addr[31:5], 5'd0} == last_beat_q); // vs the PREVIOUS captured pixel's beat
last_beat_q <= {px_addr[31:5], 5'd0};
end
// (1) MAP: a drain and refill can coincide; the refill wins and preserves throughput.
if (in_advance) begin
map_valid <= 1'b1;
map_same_q <= in_same_q;
map_beat <= in_beat;
map_data <= (256'(in_pix) << ({27'd0, in_lane} * PIX_BITS));
map_mask <= (PIX_MASK << ({27'd0, in_lane} * PIX_BITS));
map_strb <= (32'(STRB1) << ({27'd0, in_lane} * PIX_BYTES));
end else if (stage_ready && map_valid) begin
map_valid <= 1'b0;
end
// (2) DRAIN: the staged beat enters the FIFO if there is room (may be re-loaded below same cycle).
if (stage_drain) stage_valid <= 1'b0;
// (3) latch the flush request UNCONDITIONALLY so it is never lost while backpressured.
if (flush) flush_pending <= 1'b1;
// (4) PACKER: only when the stage can accept a beat this cycle; consumes the mapped input.
if (stage_ready) begin
if (map_valid) begin
logic [255:0] nd; logic [31:0] ns;
if (has_data && !map_same_q) begin
stage_data <= {1'b0, cur_addr, cur_data, cur_strb}; stage_valid <= 1'b1; stage_load = 1'b1;
cur_addr <= map_beat;
cur_data <= map_data;
cur_strb <= map_strb;
has_data <= 1'b1;
end else begin
nd = has_data ? cur_data : 256'd0;
ns = has_data ? cur_strb : 32'd0;
// Preserve the legacy packer's last-writer-wins behavior when two accepted
// fragments target one pixel in the same beat. map_data is sparse, so OR
// would corrupt a later color whose bit pattern overlaps the earlier one.
nd = (nd & ~map_mask) | map_data;
ns = ns | map_strb;
if (&ns) begin
stage_data <= {1'b0, map_beat, nd, ns}; stage_valid <= 1'b1; stage_load = 1'b1;
has_data <= 1'b0;
end else begin
cur_addr <= map_beat; cur_data <= nd; cur_strb <= ns; has_data <= 1'b1;
end
end
end else if (!in_valid && enable && arm_gs && flush_pending && has_data) begin
// partial-beat flush — retries here every cycle until stage_ready (guaranteed inside this if)
stage_data <= {1'b0, cur_addr, cur_data, cur_strb}; stage_valid <= 1'b1; stage_load = 1'b1;
has_data <= 1'b0;
end else if (!in_valid && enable && arm_gs && flush_pending && !has_data) begin
// ordered EOF marker — retries until accepted (in_valid empty => no pixel can be overtaken)
stage_data <= {1'b1, cur_addr, cur_data, cur_strb}; stage_valid <= 1'b1; stage_load = 1'b1;
flush_pending <= 1'b0;
end
end
// overflow witness (accepted-write accounting): a stage load while the stage is occupied AND not
// draining would DROP the previous beat. Gating on stage_ready makes this impossible; the witness
// fires only if that invariant is ever violated. Must stay 0 (asserted by the saturation TB).
if (stage_load && stage_valid && !stage_drain)
fifo_overflow_count <= fifo_overflow_count + 32'd1;
end
end
end
endgenerate
// ============================ async FIFO (CDC) ============================
logic [PW-1:0] fifo_rdata;
@@ -183,7 +362,7 @@ module gs_lpddr_axi_master #(
end
wire fifo_wrst_n = wrst_sync[1];
gs_async_fifo #(.WIDTH(PW), .DEPTH(FIFO_DEPTH)) u_fifo (
.wclk(gs_clk), .wrst_n(fifo_wrst_n), .wr(fifo_wr && !fifo_wfull), .wdata(fifo_wdata), .wfull(fifo_wfull),
.wclk(gs_clk), .wrst_n(fifo_wrst_n), .wr(fifo_wr_ram), .wdata(fifo_wdata_ram), .wfull(fifo_wfull),
.rclk(axi_clk), .rrst_n(axi_rst_n), .rd(fifo_rd), .rdata(fifo_rdata), .rempty(fifo_rempty)
);
@@ -209,27 +388,38 @@ module gs_lpddr_axi_master #(
assign awvalid = (state == S_AW);
assign wvalid = (state == S_W);
assign bready = (state == S_B);
assign fifo_rd = (state == S_IDLE) && !fifo_rempty && arm_axi && !commit_edge;
// A commit and a FIFO admission may coincide safely: S_IDLE latches the full address and canary decision
// from the OLD snapshot at that edge, then AW/W/B runs solely from those registered values. The next
// admission sees the NEW snapshot. Do not gate this with the raw commit synchronizer: that turns the
// control edge into a read-pointer/rempty timing cone inside the async FIFO.
assign fifo_rd = (state == S_IDLE) && !fifo_rempty && arm_axi;
assign idle = (state == S_IDLE) && fifo_rempty;
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
if (!axi_rst_n) begin
state <= S_IDLE; beat_addr <= '0; beat_data <= '0; beat_strb <= '0; awaddr_q <= '0;
beats_written <= '0; bursts_issued <= '0; bresp_err_count <= '0;
beats_written <= '0; bursts_issued <= '0; bresp_err_count <= '0; frame_drained <= 1'b0;
end else begin
unique case (state)
S_IDLE: if (!fifo_rempty && arm_axi && !commit_edge) begin
beat_addr <= fifo_rdata[319:288]; // {addr, data, strb}
S_IDLE: if (!fifo_rempty && arm_axi) begin
if (fifo_rdata[MARK]) begin
// Ch353 — ordered EOF marker popped: every prior data beat's BRESP has completed (in-order
// FIFO + per-beat B). Assert the drain ack; consume the marker (fifo_rd pops it, no AXI).
frame_drained <= 1'b1;
state <= S_IDLE;
end else begin
frame_drained <= 1'b0; // new frame data in flight — drop the ack
beat_addr <= fifo_rdata[319:288]; // {marker, addr, data, strb}
beat_data <= fifo_rdata[287:32];
beat_strb <= fifo_rdata[31:0];
awaddr_q <= fb_base_axi + fifo_rdata[319:288]; // latch FULL AW addr from the STABLE base
// canary: write ONLY the offset-0 (top-of-frame) 32-byte line;
// discard every other beat (fifo_rd still pops it this cycle).
// canary: write ONLY the offset-0 (top-of-frame) 32-byte line; discard every other beat.
if (canary_axi && (fifo_rdata[319:288] != 32'd0))
state <= S_IDLE;
else
state <= S_AW;
end
end
S_AW: if (awready) begin
bursts_issued <= bursts_issued + 32'd1;
state <= S_W;
+145
View File
@@ -0,0 +1,145 @@
// In-order LPDDR destination-read / generic GS ALPHA_1 blend stage.
module gs_lpddr_color_blend (
input logic clk, input logic rst_n,
input logic in_valid, output logic in_ready,
input logic [31:0] in_addr, input logic [31:0] in_color, input logic [16:0] in_alpha,
input logic [3:0] in_be,
output logic out_valid, input logic out_ready, output logic [31:0] out_addr, output logic [31:0] out_color, output logic idle,
output logic [31:0] araddr, output logic [7:0] arlen, output logic [2:0] arsize, output logic [1:0] arburst,
output logic arvalid, input logic arready, input logic [255:0] rdata, input logic [1:0] rresp,
input logic rlast, input logic rvalid, output logic rready
);
// The EMIF user clock is ~310 MHz. Keep the destination read, operand
// select/subtract, multiply, and add/clamp on separate registered stages;
// the original combinational gs_alpha_blend instance put all of them on
// the rdata -> out_color path and could not meet a 3.225 ns period.
typedef enum logic [2:0] {IDLE, AR, R, SEL, PREP, MUL, MAC, SUM} state_t; state_t state;
logic [31:0] src_addr, src_color; logic [16:0] src_alpha; logic [3:0] src_be; logic [2:0] lane;
logic [255:0] rdata_q;
logic [31:0] dst_q;
wire [1:0] a_sel=src_alpha[15:14], b_sel=src_alpha[13:12], c_sel=src_alpha[11:10], d_sel=src_alpha[9:8];
wire [31:0] dst_lane=(lane==0)?rdata_q[31:0]:(lane==1)?rdata_q[63:32]:(lane==2)?rdata_q[95:64]:(lane==3)?rdata_q[127:96]:(lane==4)?rdata_q[159:128]:(lane==5)?rdata_q[191:160]:(lane==6)?rdata_q[223:192]:rdata_q[255:224];
function automatic logic [7:0] color_operand(
input logic [7:0] cs, input logic [7:0] cd,
input logic [1:0] sel);
return (sel == 2'd0) ? cs : (sel == 2'd1) ? cd : 8'd0;
endfunction
function automatic logic [7:0] clamp_sum(
input logic signed [17:0] prod, input logic [7:0] op_d);
logic signed [18:0] sum;
sum = (prod >>> 7) + $signed({11'd0, op_d});
if (sum < 19'sd0) return 8'd0;
else if (sum > 19'sd255) return 8'd255;
else return sum[7:0];
endfunction
// FRAME.FBMSK is compressed to a byte-enable before entering this block.
// A disabled byte preserves the destination value after either the GS
// ALPHA equation (ABE=1) or the opaque source selection (ABE=0).
function automatic logic [31:0] merge_be(
input logic [31:0] src, input logic [31:0] dst,
input logic [3:0] be);
for (int i=0; i<4; i++)
merge_be[i*8 +: 8] = be[i] ? src[i*8 +: 8] : dst[i*8 +: 8];
endfunction
wire [7:0] ar_op = color_operand(src_color[7:0], dst_q[7:0], a_sel);
wire [7:0] ag_op = color_operand(src_color[15:8], dst_q[15:8], a_sel);
wire [7:0] ab_op = color_operand(src_color[23:16], dst_q[23:16], a_sel);
wire [7:0] br_op = color_operand(src_color[7:0], dst_q[7:0], b_sel);
wire [7:0] bg_op = color_operand(src_color[15:8], dst_q[15:8], b_sel);
wire [7:0] bb_op = color_operand(src_color[23:16], dst_q[23:16], b_sel);
wire [7:0] dr_op = color_operand(src_color[7:0], dst_q[7:0], d_sel);
wire [7:0] dg_op = color_operand(src_color[15:8], dst_q[15:8], d_sel);
wire [7:0] db_op = color_operand(src_color[23:16], dst_q[23:16], d_sel);
wire [7:0] src_alpha_eff = (src_color[31:24] > 8'h80) ? 8'h80 : src_color[31:24];
wire [7:0] coef = (c_sel == 2'd0) ? src_alpha_eff :
(c_sel == 2'd1) ? dst_q[31:24] : src_alpha[7:0];
logic signed [8:0] diff_r_q, diff_g_q, diff_b_q;
logic signed [17:0] prod_r_q, prod_g_q, prod_b_q;
logic signed [17:0] mcand_r_q, mcand_g_q, mcand_b_q;
logic [7:0] multiplier_q;
logic [2:0] mac_count_q;
logic [7:0] coef_q, d_r_q, d_g_q, d_b_q, alpha_q;
assign in_ready=(state==IDLE)&&!out_valid; assign idle=(state==IDLE)&&!out_valid; assign araddr={src_addr[31:5],5'd0};
assign arlen=0; assign arsize=3'b101; assign arburst=2'b01; assign arvalid=(state==AR); assign rready=(state==R);
always_ff @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state<=IDLE;out_valid<=0;out_addr<=0;out_color<=0;
src_addr<=0;src_color<=0;src_alpha<=0;src_be<=4'hF;lane<=0;rdata_q<=0;dst_q<=0;
diff_r_q<=0;diff_g_q<=0;diff_b_q<=0;
prod_r_q<=0;prod_g_q<=0;prod_b_q<=0;
mcand_r_q<=0;mcand_g_q<=0;mcand_b_q<=0;
multiplier_q<=0;mac_count_q<=0;
coef_q<=0;d_r_q<=0;d_g_q<=0;d_b_q<=0;alpha_q<=0;
end
else begin
if(out_valid&&out_ready) out_valid<=0;
case(state)
IDLE: if(in_valid&&in_ready) begin
src_addr<=in_addr;src_color<=in_color;src_alpha<=in_alpha;src_be<=in_be;lane<=in_addr[4:2];
// A partial write is a destination RMW even when ABE is off.
if(in_alpha[16] || (in_be != 4'hF)) state<=AR;
else begin out_valid<=1;out_addr<=in_addr;out_color<=in_color;end
end
AR: if(arready) state<=R;
R: if(rvalid&&rlast) begin
rdata_q <= rdata;
state<=SEL;
end
SEL: begin
dst_q <= dst_lane;
state<=PREP;
end
PREP: begin
diff_r_q <= $signed({1'b0, ar_op}) - $signed({1'b0, br_op});
diff_g_q <= $signed({1'b0, ag_op}) - $signed({1'b0, bg_op});
diff_b_q <= $signed({1'b0, ab_op}) - $signed({1'b0, bb_op});
coef_q <= coef;
d_r_q <= dr_op; d_g_q <= dg_op; d_b_q <= db_op;
alpha_q <= src_color[31:24];
state<=MUL;
end
MUL: begin
// Exact signed-difference * unsigned-coefficient multiply,
// implemented as eight short shift/add cycles. Agilex 5's
// inferred 18x18 DSP has a 3.751 ns minimum period in this
// configuration, slower than the 3.225 ns EMIF user clock.
// The emitter is already single-request/in-order, so latency is
// harmless and this avoids both the primitive limit and any
// timing exception.
prod_r_q<=0;prod_g_q<=0;prod_b_q<=0;
mcand_r_q<={{9{diff_r_q[8]}},diff_r_q};
mcand_g_q<={{9{diff_g_q[8]}},diff_g_q};
mcand_b_q<={{9{diff_b_q[8]}},diff_b_q};
multiplier_q<=coef_q;mac_count_q<=0;state<=MAC;
end
MAC: begin
if(multiplier_q[0]) begin
prod_r_q<=prod_r_q+mcand_r_q;
prod_g_q<=prod_g_q+mcand_g_q;
prod_b_q<=prod_b_q+mcand_b_q;
end
mcand_r_q<=mcand_r_q<<<1;
mcand_g_q<=mcand_g_q<<<1;
mcand_b_q<=mcand_b_q<<<1;
multiplier_q<=multiplier_q>>1;
if(mac_count_q==3'd7) state<=SUM;
else mac_count_q<=mac_count_q+1'b1;
end
SUM: begin
out_valid<=1;out_addr<=src_addr;
out_color<=merge_be(
src_alpha[16]
? {alpha_q,clamp_sum(prod_b_q,d_b_q),clamp_sum(prod_g_q,d_g_q),clamp_sum(prod_r_q,d_r_q)}
: src_color,
dst_q, src_be);
state<=IDLE;
end
endcase
end
end
endmodule
+18
View File
@@ -0,0 +1,18 @@
// retroDE_ps2 — gs_lpddr_map_pkg (Ch357 — future-compatible private-LPDDR region map)
//
// Codex-locked LPDDR byte-offset map for the scheduler + persistent-Z path. These are OFFSETS within our PRIVATE reserved
// LPDDR region (fb_base is added at the board boundary). The layout fits the current 384x381 rung AND future 640x480 Z
// WITHOUT relocation, and keeps color / Z / texture ranges provably DISJOINT:
//
// color FB : 0x000000 .. (384x381x4 = 0x08EE00 ; 640x480x4 = 0x12C000 -> both < 0x140000)
// Z buffer : 0x140000 .. (384x381x2 = 0x047700 -> ..0x187700 ; 640x480x2 = 0x096000 -> ..0x1D6000)
// texture : 0x200000 .. (single-region tex cache; 0x1D6000 < 0x200000, so Z never reaches it)
//
// NOTE (Codex): the Z buffer is INTERNAL — it is never exposed as GS local memory, so it uses a simple LINEAR packed-16b
// layout. Only the PSMZ16S VALUE/TEST semantics (clamp16 source, GEQUAL, ZMSK) are authentic; its physical storage swizzle
// is deliberately NOT PSMZ16S-swizzled (we own the buffer). See gs_lpddr_z_rmw.sv.
package gs_lpddr_map_pkg;
localparam logic [31:0] LPDDR_COLOR_BASE = 32'h0000_0000;
localparam logic [31:0] LPDDR_Z_BASE = 32'h0014_0000;
localparam logic [31:0] LPDDR_TEX_BASE = 32'h0020_0000;
endpackage : gs_lpddr_map_pkg
+51 -20
View File
@@ -77,6 +77,20 @@ module gs_lpddr_rd_arb (
output logic s3_rvalid,
input logic s3_rready,
// ---- Port 4: destination-color read for SH3 alpha ROP ----
input logic [29:0] s4_araddr,
input logic [1:0] s4_arburst,
input logic [6:0] s4_arid,
input logic [7:0] s4_arlen,
input logic [2:0] s4_arsize,
input logic s4_arvalid,
output logic s4_arready,
output logic [255:0] s4_rdata,
output logic [1:0] s4_rresp,
output logic s4_rlast,
output logic s4_rvalid,
input logic s4_rready,
// ---- Master out: EMIF read channel ----
output logic [29:0] m_araddr,
output logic [1:0] m_arburst,
@@ -91,7 +105,7 @@ module gs_lpddr_rd_arb (
input logic m_rvalid,
output logic m_rready
);
// grant: 0=idle, 1=s0 scanout, 2=s1 probe, 3=s2 texfill, 4=s3 tile-reload.
// grant: 0=idle, 1=s0 scanout, 2=s1 probe, 3=s2 texfill, 4=s3 reload/Z, 5=s4 alpha destination.
// EXPLICIT priority (Ch323, Codex): scanout > tile_reload > probe > texture_fill — i.e.
// s0 > s3 > s1 > s2. Render-display (scanout) highest; the render-prep tile reload above
// the debug read-probe so a debug read can never starve a render's Z/color reload.
@@ -105,26 +119,37 @@ module gs_lpddr_rd_arb (
// transaction committed yet — safe to drop); after AR acceptance the grant is held until
// m_rvalid && m_rlast && selected_rready, regardless of how long the read takes.
reg ar_done; // AR handshake captured for the active grant -> never abort past here
// Register the terminal R handshake before releasing the grant. Besides
// making the ownership boundary explicit, this removes the selected
// requester's rready mux from the encoded grant register's D cone. The
// old direct clear path was the secondary 310 MHz setup family after the
// wide request-FIFO RAM-enable fanout.
reg response_done_q;
reg [21:0] watchdog; // pre-AR only (waiting for m_arready); ~6.7 ms @ 310 MHz dead-bus backstop
wire wd_expired = watchdog[21];
wire sel_rready = (grant==3'd1)?s0_rready:(grant==3'd2)?s1_rready:
(grant==3'd3)?s2_rready:(grant==3'd4)?s3_rready:1'b1;
(grant==3'd3)?s2_rready:(grant==3'd4)?s3_rready:(grant==3'd5)?s4_rready:1'b1;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
grant <= 3'd0; ar_done <= 1'b0; watchdog <= '0;
grant <= 3'd0; ar_done <= 1'b0; response_done_q <= 1'b0; watchdog <= '0;
end else if (grant == 3'd0) begin
ar_done <= 1'b0; watchdog <= '0;
ar_done <= 1'b0; response_done_q <= 1'b0; watchdog <= '0;
if (s0_arvalid) grant <= 3'd1; // scanout (highest)
else if (s3_arvalid) grant <= 3'd4; // tile reload (render-prep)
else if (s4_arvalid) grant <= 3'd5; // alpha destination RMW
else if (s1_arvalid) grant <= 3'd2; // read probe (debug)
else if (s2_arvalid) grant <= 3'd3; // texture fill (lowest)
end else if (response_done_q) begin
// The selected requester accepted RLAST on the preceding cycle.
// Hold ownership through that handshake, then release here.
grant <= 3'd0; ar_done <= 1'b0; response_done_q <= 1'b0; watchdog <= '0;
end else begin
if (m_arvalid && m_arready) ar_done <= 1'b1; // AR accepted -> COMMITTED
if (m_rvalid && m_rlast && sel_rready) begin
grant <= 3'd0; ar_done <= 1'b0; watchdog <= '0; // response delivered -> release
response_done_q <= 1'b1; watchdog <= '0; // response delivered; release next cycle
end else if (!ar_done) begin // still waiting for AR (nothing owed)
if (wd_expired) begin grant <= 3'd0; ar_done <= 1'b0; watchdog <= '0; end
if (wd_expired) begin grant <= 3'd0; ar_done <= 1'b0; response_done_q <= 1'b0; watchdog <= '0; end
else watchdog <= watchdog + 22'd1;
end
// ar_done && response not yet complete: HOLD the grant, never abort.
@@ -132,24 +157,30 @@ module gs_lpddr_rd_arb (
end
// AR mux
assign m_araddr = (grant==3'd4)?s3_araddr :(grant==3'd3)?s2_araddr :(grant==3'd2)?s1_araddr :s0_araddr;
assign m_arburst = (grant==3'd4)?s3_arburst:(grant==3'd3)?s2_arburst:(grant==3'd2)?s1_arburst:s0_arburst;
assign m_arid = (grant==3'd4)?s3_arid :(grant==3'd3)?s2_arid :(grant==3'd2)?s1_arid :s0_arid;
assign m_arlen = (grant==3'd4)?s3_arlen :(grant==3'd3)?s2_arlen :(grant==3'd2)?s1_arlen :s0_arlen;
assign m_arsize = (grant==3'd4)?s3_arsize :(grant==3'd3)?s2_arsize :(grant==3'd2)?s1_arsize :s0_arsize;
assign m_arvalid = (grant==3'd1)?s0_arvalid:(grant==3'd2)?s1_arvalid:(grant==3'd3)?s2_arvalid:(grant==3'd4)?s3_arvalid:1'b0;
assign s0_arready = (grant==3'd1)?m_arready:1'b0;
assign s1_arready = (grant==3'd2)?m_arready:1'b0;
assign s2_arready = (grant==3'd3)?m_arready:1'b0;
assign s3_arready = (grant==3'd4)?m_arready:1'b0;
assign m_araddr = (grant==3'd5)?s4_araddr :(grant==3'd4)?s3_araddr :(grant==3'd3)?s2_araddr :(grant==3'd2)?s1_araddr :s0_araddr;
assign m_arburst = (grant==3'd5)?s4_arburst:(grant==3'd4)?s3_arburst:(grant==3'd3)?s2_arburst:(grant==3'd2)?s1_arburst:s0_arburst;
assign m_arid = (grant==3'd5)?s4_arid :(grant==3'd4)?s3_arid :(grant==3'd3)?s2_arid :(grant==3'd2)?s1_arid :s0_arid;
assign m_arlen = (grant==3'd5)?s4_arlen :(grant==3'd4)?s3_arlen :(grant==3'd3)?s2_arlen :(grant==3'd2)?s1_arlen :s0_arlen;
assign m_arsize = (grant==3'd5)?s4_arsize :(grant==3'd4)?s3_arsize :(grant==3'd3)?s2_arsize :(grant==3'd2)?s1_arsize :s0_arsize;
// Once an address is accepted, do not expose another address from the
// selected requester while its response (or registered release) is active.
// This also makes the deliberate one-cycle release bubble AXI-safe.
wire ar_open = !ar_done && !response_done_q;
assign m_arvalid = ar_open && ((grant==3'd1)?s0_arvalid:(grant==3'd2)?s1_arvalid:(grant==3'd3)?s2_arvalid:(grant==3'd4)?s3_arvalid:(grant==3'd5)?s4_arvalid:1'b0);
assign s0_arready = (ar_open && grant==3'd1)?m_arready:1'b0;
assign s1_arready = (ar_open && grant==3'd2)?m_arready:1'b0;
assign s2_arready = (ar_open && grant==3'd3)?m_arready:1'b0;
assign s3_arready = (ar_open && grant==3'd4)?m_arready:1'b0;
assign s4_arready = (ar_open && grant==3'd5)?m_arready:1'b0;
// R demux (idle: rready=1 drains any stale/late response)
assign s0_rdata=m_rdata; assign s1_rdata=m_rdata; assign s2_rdata=m_rdata; assign s3_rdata=m_rdata;
assign s0_rresp=m_rresp; assign s1_rresp=m_rresp; assign s2_rresp=m_rresp; assign s3_rresp=m_rresp;
assign s0_rlast=m_rlast; assign s1_rlast=m_rlast; assign s2_rlast=m_rlast; assign s3_rlast=m_rlast;
assign s0_rdata=m_rdata; assign s1_rdata=m_rdata; assign s2_rdata=m_rdata; assign s3_rdata=m_rdata; assign s4_rdata=m_rdata;
assign s0_rresp=m_rresp; assign s1_rresp=m_rresp; assign s2_rresp=m_rresp; assign s3_rresp=m_rresp; assign s4_rresp=m_rresp;
assign s0_rlast=m_rlast; assign s1_rlast=m_rlast; assign s2_rlast=m_rlast; assign s3_rlast=m_rlast; assign s4_rlast=m_rlast;
assign s0_rvalid = (grant==3'd1)?m_rvalid:1'b0;
assign s1_rvalid = (grant==3'd2)?m_rvalid:1'b0;
assign s2_rvalid = (grant==3'd3)?m_rvalid:1'b0;
assign s3_rvalid = (grant==3'd4)?m_rvalid:1'b0;
assign m_rready = (grant==3'd1)?s0_rready:(grant==3'd2)?s1_rready:(grant==3'd3)?s2_rready:(grant==3'd4)?s3_rready:1'b1;
assign s4_rvalid = (grant==3'd5)?m_rvalid:1'b0;
assign m_rready = (grant==3'd1)?s0_rready:(grant==3'd2)?s1_rready:(grant==3'd3)?s2_rready:(grant==3'd4)?s3_rready:(grant==3'd5)?s4_rready:1'b1;
endmodule

Some files were not shown because too many files have changed in this diff Show More