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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>