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>
This commit is contained in:
2026-07-20 19:56:46 -04:00
parent ec82764bef
commit ba74bbd5aa
476 changed files with 696247 additions and 130119 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.
+191 -16
View File
@@ -14,7 +14,21 @@
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
) (
// write domain
input logic wclk,
@@ -32,11 +46,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 +103,162 @@ 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;
rbin <= rbin_nxt;
rgray <= rgray_nxt;
rempty <= rempty_nxt;
wgray_s1 <= wgray; // sync write gray into read domain
wgray_s2 <= wgray_s1;
end
end
assign rdata = mem[rbin[AW-1:0]];
assign rempty = (rgray == wgray_s2);
generate
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]];
end
end
endgenerate
endmodule : gs_async_fifo
+223 -33
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,26 +388,37 @@ 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}
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).
if (canary_axi && (fifo_rdata[319:288] != 32'd0))
state <= S_IDLE;
else
state <= S_AW;
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.
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;
+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
+19 -10
View File
@@ -58,7 +58,11 @@ module gs_lpddr_rd_probe #(
reg [2:0] rd_lane; // which 32-bit lane of the 256-bit beat (addr[4:2])
typedef enum logic [1:0] { S_IDLE, S_AR, S_R } st_t;
// Capture the complete EMIF beat before lane selection. Selecting one of
// eight 32-bit lanes directly from the EMIF response into rd_data placed a
// two-level mux plus long EMIF routing in one 310 MHz cycle.
logic [255:0] rdata_q;
typedef enum logic [1:0] { S_IDLE, S_AR, S_R, S_SEL } st_t;
st_t st;
always_ff @(posedge axi_clk) begin
@@ -72,6 +76,7 @@ module gs_lpddr_rd_probe #(
rd_data <= 32'd0;
rd_busy <= 1'b0;
rd_lane <= 3'd0;
rdata_q <= 256'd0;
end else begin
pulse_sync <= {pulse_sync[1:0], rd_pulse};
@@ -95,20 +100,24 @@ module gs_lpddr_rd_probe #(
S_R: begin
if (rvalid) begin
rready <= 1'b0;
rdata_q <= rdata;
st <= S_SEL;
end
end
S_SEL: begin
case (rd_lane)
3'd0: rd_data <= rdata[31:0];
3'd1: rd_data <= rdata[63:32];
3'd2: rd_data <= rdata[95:64];
3'd3: rd_data <= rdata[127:96];
3'd4: rd_data <= rdata[159:128];
3'd5: rd_data <= rdata[191:160];
3'd6: rd_data <= rdata[223:192];
default: rd_data <= rdata[255:224];
3'd0: rd_data <= rdata_q[31:0];
3'd1: rd_data <= rdata_q[63:32];
3'd2: rd_data <= rdata_q[95:64];
3'd3: rd_data <= rdata_q[127:96];
3'd4: rd_data <= rdata_q[159:128];
3'd5: rd_data <= rdata_q[191:160];
3'd6: rd_data <= rdata_q[223:192];
default: rd_data <= rdata_q[255:224];
endcase
rd_busy <= 1'b0;
rd_done <= ~rd_done;
st <= S_IDLE;
end
end
default: st <= S_IDLE;
endcase
+610 -44
View File
@@ -4,8 +4,8 @@
// LINE-BUFFER LPDDR4B scanout — the architectural successor to the whole-frame
// cache (gs_lpddr_scanout). Instead of mirroring the entire framebuffer in
// on-chip RAM (which defeats the point of putting the FB in LPDDR), this holds
// just TWO scanlines: it displays row L from one buffer while prefetching row
// L+1 into the other. On-chip cost is O(width), not O(width*height).
// just two scanlines (three with the optional Ch438 low-pass): it displays row
// L while prefetching row L+1. On-chip cost is O(width), not O(width*height).
//
// NARROW SCOPE (Ch321): the 128x128 PSMCT16 demo. The frame is LINEAR (the GS
// writer mirrors the rasterizer's linear flush addresses), display window at
@@ -30,7 +30,39 @@ module gs_lpddr_scanout_lb #(
// Ch327a — PSMCT32 (ABGR8888, 8 px/256-bit beat) vs the original PSMCT16 (RGBA5551,
// 16 px/beat). The Ch326 LPDDR-only spill framebuffer is PSMCT32 @ COLOR_SPILL_BASE, so the
// line-buffer must decode it — NOT a config flip of the Ch321 PSMCT16/FB-at-0 path.
parameter bit PSMCT32 = 1'b0
parameter bit PSMCT32 = 1'b0,
// Ch418 — the captured SH3 DISPLAY2 is 512 source pixels wide
// (FBW=8) and MAGH=4, i.e. five VCKs per source sample. The board
// emits a 640-pixel active line, so reducing that authentic 2560-VCK
// display domain to VGA requires the exact nearest-neighbour map
// source_x = floor(display_x * 4 / 5).
// Keep this opt-in: legacy demos and directed scanout tests remain 1:1.
// The implementation below is a five-state phase accumulator, not an
// inferred divider, and therefore adds no wide arithmetic timing cone.
parameter bit H_STRETCH_5_TO_4 = 1'b0,
// Captured SH3 interlace presentation: DISPLAY2.DH=895 represents
// 448 source lines, beginning at DISPFB2.DBY=32. Mapping those lines
// onto the board's 480-line active raster is source_y =
// V_SOURCE_START + floor(display_y*14/15). As with horizontal scale,
// this remains opt-in and uses a tiny phase accumulator.
parameter int V_SOURCE_START = 0,
parameter bit V_STRETCH_15_TO_14 = 1'b0,
// Ch436 — optional linear reconstruction between the two resident source
// rows. The 15:14 mapper already keeps row L and L+1 in the alternating
// line buffers, so this adds no framebuffer traffic or line storage.
parameter bit V_LINEAR_FILTER = 1'b0,
// Ch437 — horizontal linear reconstruction for the 5:4 presentation map.
// A two-beat register cache provides x and x+1 from each resident row while
// retaining ONE read port per physical line buffer. H_SOURCE_PIXELS is the
// active source width (512 for SH3 DISPLAY2); zero means the physical stride.
parameter bit H_LINEAR_FILTER = 1'b0,
parameter int H_SOURCE_PIXELS = 0,
// Ch438 — separable [1 2 1]/4 low-pass reconstruction in source space.
// Three rotating line buffers retain rows y-1/y/y+1; the horizontal taps
// reuse the Ch437 two-beat cache, so every physical RAM still has exactly
// one registered read port. This is mutually exclusive with the Ch436/437
// linear filters in the SH3 profile and uses only adds plus shifts.
parameter bit BINOMIAL_3X3_FILTER = 1'b0
)(
// ---- AXI read clock domain (emif_clk) ----
input logic axi_clk,
@@ -75,9 +107,11 @@ module gs_lpddr_scanout_lb #(
// burst (arlen=ROW_BEATS-1) was untested and garbled on hardware.
assign arsize = 3'b101; // 32 bytes
// Two line buffers, ROW_BEATS x 256-bit each (one display row).
// Two line buffers for legacy/linear scanout; Ch438 enables a third so the
// previous, current, and next source rows are resident simultaneously.
logic [255:0] lb0 [0:ROW_BEATS-1];
logic [255:0] lb1 [0:ROW_BEATS-1];
logic [255:0] lb2 [0:ROW_BEATS-1];
// ================= video side (video_clk) =================
// No miss-prone request toggle. The video side just exposes the current
@@ -85,81 +119,585 @@ module gs_lpddr_scanout_lb #(
// and staying one row ahead (see below). disp_row_v resets on vsync.
logic [$clog2(N_ROWS):0] disp_row_v;
logic [2:0] fs_sync_v;
wire fs_edge_v = (fs_sync_v[2] != fs_sync_v[1]);
wire fs_edge_v = fs_sync_v[1] && !fs_sync_v[2]; // RISING edge only: one reset per frame_start pulse
// The buffer holding display line L is L&1 (row L is fetched into L&1). Select
// it DIRECTLY from pixel_y[0] (tracks the current pixel) — a separately-registered
// "disp_buf" lags by one cycle and corrupts col 0 of each line.
wire disp_buf = pixel_y[0];
logic [$clog2(N_ROWS):0] stretch_src_y_q;
logic [3:0] stretch_vphase_q;
localparam int V_SOURCE_BUF = V_SOURCE_START % 3;
logic [1:0] stretch_buf_q;
logic in_window_v_q;
always_ff @(posedge video_clk) begin
if (!enable) begin
stretch_src_y_q <= ($clog2(N_ROWS)+1)'(V_SOURCE_START);
stretch_vphase_q <= 4'd0;
stretch_buf_q <= 2'(V_SOURCE_BUF);
in_window_v_q <= 1'b0;
end else begin
in_window_v_q <= in_window;
if (fs_edge_v) begin
stretch_src_y_q <= ($clog2(N_ROWS)+1)'(V_SOURCE_START);
stretch_vphase_q <= 4'd0;
stretch_buf_q <= 2'(V_SOURCE_BUF);
end else if (V_STRETCH_15_TO_14 && in_window_v_q && !in_window) begin
// End of one output line. phase 0 repeats the current
// source line once; phases 14..1 advance while counting
// down, giving 15 output lines per 14 source lines.
if (stretch_vphase_q == 4'd0)
stretch_vphase_q <= 4'd14;
else begin
stretch_src_y_q <= stretch_src_y_q + 1'b1;
stretch_vphase_q <= stretch_vphase_q - 1'b1;
stretch_buf_q <= (stretch_buf_q == 2'd2) ? 2'd0
: stretch_buf_q + 1'b1;
end
end
end
end
wire [$clog2(N_ROWS):0] scan_y = V_STRETCH_15_TO_14
? stretch_src_y_q
: ($clog2(N_ROWS)+1)'(pixel_y);
wire disp_buf = scan_y[0];
wire [1:0] scan_buf3 = stretch_buf_q;
always_ff @(posedge video_clk) begin
if (!enable) begin
disp_row_v <= '0; fs_sync_v <= 3'd0;
disp_row_v <= ($clog2(N_ROWS)+1)'(V_SOURCE_START); fs_sync_v <= 3'd0;
end else begin
fs_sync_v <= {fs_sync_v[1:0], frame_start};
if (fs_edge_v) disp_row_v <= '0;
else if (in_window && (pixel_y < N_ROWS)) disp_row_v <= ($clog2(N_ROWS)+1)'(pixel_y);
if (fs_edge_v) disp_row_v <= ($clog2(N_ROWS)+1)'(V_SOURCE_START);
// Publish a stretch-row advance during horizontal blanking, not
// only after the first active pixel. The AXI side then has the
// full blank interval to replace the retired parity buffer with
// row L+1 before linear scanout needs it.
else if (scan_y < ($clog2(N_ROWS)+1)'(N_ROWS))
disp_row_v <= scan_y;
end
end
// Registered (sync-read) pixel: pick buffer + beat + within-beat lane from pixel_x.
// PSMCT32: 8 px/256-bit beat -> beat = pixel_x>>3, lane = pixel_x[2:0] (32-bit).
// PSMCT16: 16 px/beat -> beat = pixel_x>>4, lane = pixel_x[3:0] (16-bit).
// Ch418 horizontal presentation mapper. At output x=0 the state is
// {src=0,phase=0}; successive active clocks produce source columns
// 0,0,1,2,3,4,4,5,6,7,...,511
// for output columns 0..639. Blanking resets the state before each line.
// phase==0 is the sole repeat; all other phases advance source_x. This is
// algebraically identical to floor(x*4/5), using only a 3-bit decrement
// and a 12-bit increment.
logic [11:0] stretch_src_x_q;
logic [2:0] stretch_phase_q;
always_ff @(posedge video_clk) begin
if (!enable || !in_window) begin
stretch_src_x_q <= 12'd0;
stretch_phase_q <= 3'd0;
end else if (H_STRETCH_5_TO_4) begin
if (stretch_phase_q == 3'd0) begin
stretch_phase_q <= 3'd4;
end else begin
stretch_src_x_q <= stretch_src_x_q + 12'd1;
stretch_phase_q <= stretch_phase_q - 3'd1;
end
end
end
wire [11:0] scan_x = H_STRETCH_5_TO_4 ? stretch_src_x_q : pixel_x;
// Registered (sync-read) pixel: pick buffer + beat + within-beat lane from scan_x.
// PSMCT32: 8 px/256-bit beat -> beat = scan_x>>3, lane = scan_x[2:0] (32-bit).
// PSMCT16: 16 px/beat -> beat = scan_x>>4, lane = scan_x[3:0] (16-bit).
localparam int PXSH = PSMCT32 ? 3 : 4; // px-per-beat shift
localparam int PX_PER_ROW = PSMCT32 ? (STRIDE_BYTES/4) : (STRIDE_BYTES/2);
wire [RB_BITS-1:0] col_beat = pixel_x[RB_BITS+PXSH-1 -: RB_BITS];
wire [3:0] col_lane = PSMCT32 ? {1'b0, pixel_x[2:0]} : pixel_x[3:0];
logic [255:0] word_q; logic [3:0] lane_q; logic in_q;
always_ff @(posedge video_clk) begin
word_q <= disp_buf ? lb1[col_beat] : lb0[col_beat];
lane_q <= col_lane;
in_q <= in_window && (pixel_x < PX_PER_ROW) && (pixel_y < N_ROWS);
end
localparam int H_SOURCE_PX = (H_SOURCE_PIXELS != 0) ? H_SOURCE_PIXELS : PX_PER_ROW;
localparam int H_LAST_BEAT = (H_SOURCE_PX-1) >> PXSH;
wire [RB_BITS-1:0] col_beat = scan_x[RB_BITS+PXSH-1 -: RB_BITS];
wire [3:0] col_lane = PSMCT32 ? {1'b0, scan_x[2:0]} : scan_x[3:0];
logic [255:0] prv_word_q, cur_word_q, nxt_word_q;
// Only lane zero of the lookahead beat can be selected: x+1 crosses a
// beat exactly when x is its final lane. Keep 32 bits, not another pair
// of 256-bit payload registers.
logic [31:0] prv_left_px_q, cur_left_px_q, nxt_left_px_q;
logic [31:0] prv_look_px_q, cur_look_px_q, nxt_look_px_q;
logic [3:0] lane_q, vphase_q;
logic [2:0] hphase_q;
logic first_source_x_q, last_source_x_q;
logic in_q;
generate
if (H_LINEAR_FILTER || BINOMIAL_3X3_FILTER) begin : g_hlinear_cache
// Slot parity equals beat parity. Horizontal blanking continually
// primes beats 0 and 1 after the vertical row selector settles.
// On entry to each subsequent beat, the retired slot is refilled
// with beat+1. No second line-buffer read port is required.
logic [255:0] lb0_cache0_q, lb0_cache1_q;
logic [255:0] lb1_cache0_q, lb1_cache1_q;
logic [255:0] lb2_cache0_q, lb2_cache1_q;
logic blank_prime_q;
logic [RB_BITS-1:0] active_beat_q;
logic [RB_BITS-1:0] video_rd_addr_q, video_rd_tag_q;
logic video_rd_req_q, video_rd_valid_q;
logic [255:0] lb0_video_rd_q, lb1_video_rd_q, lb2_video_rd_q;
// Keep the inferred line-buffer read ports canonical: exactly one
// unconditional registered address and one registered data output
// per physical array. The prior conditional multi-address reads
// made Quartus expand both arrays into 30,720 flip-flops.
always_ff @(posedge video_clk) begin
lb0_video_rd_q <= lb0[video_rd_addr_q];
lb1_video_rd_q <= lb1[video_rd_addr_q];
lb2_video_rd_q <= lb2[video_rd_addr_q];
video_rd_tag_q <= video_rd_addr_q;
video_rd_valid_q <= video_rd_req_q;
end
always_ff @(posedge video_clk) begin
// The registered RAM response arrives with its beat tag two
// control edges after the request. Store physical lb0/lb1
// independently; row parity is selected only at pixel output.
if (video_rd_valid_q) begin
if (video_rd_tag_q[0]) begin
lb0_cache1_q <= lb0_video_rd_q;
lb1_cache1_q <= lb1_video_rd_q;
lb2_cache1_q <= lb2_video_rd_q;
end else begin
lb0_cache0_q <= lb0_video_rd_q;
lb1_cache0_q <= lb1_video_rd_q;
lb2_cache0_q <= lb2_video_rd_q;
end
end
if (!enable) begin
blank_prime_q <= 1'b0;
active_beat_q <= '0;
video_rd_addr_q <= '0;
video_rd_req_q <= 1'b0;
in_q <= 1'b0;
end else if (!in_window) begin
// Alternate requests for beats 0 and 1 throughout blank.
// VGA supplies far more than the four clocks needed for
// both registered responses to settle into the cache.
video_rd_addr_q <= blank_prime_q ? RB_BITS'(1) : '0;
video_rd_req_q <= 1'b1;
blank_prime_q <= ~blank_prime_q;
active_beat_q <= '0;
in_q <= 1'b0;
end else begin
video_rd_req_q <= 1'b0;
// Source x and x+1 come from the two cached beats. The
// current beat is selected by parity; the opposite slot
// is its already-fetched successor.
if (BINOMIAL_3X3_FILTER) begin
// Select the three rotating physical rows. The first
// displayed source row clamps y-1 to y; the final row
// similarly clamps y+1. For a lane-zero sample the
// opposite cache slot still contains the preceding
// beat; by the time lane seven needs x+1 it contains
// the newly fetched successor beat.
case (scan_buf3)
2'd0: begin
prv_word_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
? (col_beat[0] ? lb0_cache1_q : lb0_cache0_q)
: (col_beat[0] ? lb2_cache1_q : lb2_cache0_q);
cur_word_q <= col_beat[0] ? lb0_cache1_q : lb0_cache0_q;
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb0_cache1_q : lb0_cache0_q)
: (col_beat[0] ? lb1_cache1_q : lb1_cache0_q);
prv_left_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
? (col_beat[0] ? lb0_cache0_q[255:224] : lb0_cache1_q[255:224])
: (col_beat[0] ? lb2_cache0_q[255:224] : lb2_cache1_q[255:224]);
cur_left_px_q <= col_beat[0] ? lb0_cache0_q[255:224] : lb0_cache1_q[255:224];
nxt_left_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb0_cache0_q[255:224] : lb0_cache1_q[255:224])
: (col_beat[0] ? lb1_cache0_q[255:224] : lb1_cache1_q[255:224]);
prv_look_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
? (col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0])
: (col_beat[0] ? lb2_cache0_q[31:0] : lb2_cache1_q[31:0]);
cur_look_px_q <= col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0];
nxt_look_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0])
: (col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0]);
end
2'd1: begin
prv_word_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
? (col_beat[0] ? lb1_cache1_q : lb1_cache0_q)
: (col_beat[0] ? lb0_cache1_q : lb0_cache0_q);
cur_word_q <= col_beat[0] ? lb1_cache1_q : lb1_cache0_q;
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb1_cache1_q : lb1_cache0_q)
: (col_beat[0] ? lb2_cache1_q : lb2_cache0_q);
prv_left_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
? (col_beat[0] ? lb1_cache0_q[255:224] : lb1_cache1_q[255:224])
: (col_beat[0] ? lb0_cache0_q[255:224] : lb0_cache1_q[255:224]);
cur_left_px_q <= col_beat[0] ? lb1_cache0_q[255:224] : lb1_cache1_q[255:224];
nxt_left_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb1_cache0_q[255:224] : lb1_cache1_q[255:224])
: (col_beat[0] ? lb2_cache0_q[255:224] : lb2_cache1_q[255:224]);
prv_look_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
? (col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0])
: (col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0]);
cur_look_px_q <= col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0];
nxt_look_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0])
: (col_beat[0] ? lb2_cache0_q[31:0] : lb2_cache1_q[31:0]);
end
default: begin
prv_word_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
? (col_beat[0] ? lb2_cache1_q : lb2_cache0_q)
: (col_beat[0] ? lb1_cache1_q : lb1_cache0_q);
cur_word_q <= col_beat[0] ? lb2_cache1_q : lb2_cache0_q;
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb2_cache1_q : lb2_cache0_q)
: (col_beat[0] ? lb0_cache1_q : lb0_cache0_q);
prv_left_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
? (col_beat[0] ? lb2_cache0_q[255:224] : lb2_cache1_q[255:224])
: (col_beat[0] ? lb1_cache0_q[255:224] : lb1_cache1_q[255:224]);
cur_left_px_q <= col_beat[0] ? lb2_cache0_q[255:224] : lb2_cache1_q[255:224];
nxt_left_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb2_cache0_q[255:224] : lb2_cache1_q[255:224])
: (col_beat[0] ? lb0_cache0_q[255:224] : lb0_cache1_q[255:224]);
prv_look_px_q <= (scan_y <= ($clog2(N_ROWS)+1)'(V_SOURCE_START))
? (col_beat[0] ? lb2_cache0_q[31:0] : lb2_cache1_q[31:0])
: (col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0]);
cur_look_px_q <= col_beat[0] ? lb2_cache0_q[31:0] : lb2_cache1_q[31:0];
nxt_look_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb2_cache0_q[31:0] : lb2_cache1_q[31:0])
: (col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0]);
end
endcase
end else if (disp_buf) begin
prv_word_q <= '0;
cur_word_q <= col_beat[0] ? lb1_cache1_q : lb1_cache0_q;
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb1_cache1_q : lb1_cache0_q)
: (col_beat[0] ? lb0_cache1_q : lb0_cache0_q);
cur_look_px_q <= col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0];
nxt_look_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0])
: (col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0]);
end else begin
prv_word_q <= '0;
cur_word_q <= col_beat[0] ? lb0_cache1_q : lb0_cache0_q;
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb0_cache1_q : lb0_cache0_q)
: (col_beat[0] ? lb1_cache1_q : lb1_cache0_q);
cur_look_px_q <= col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0];
nxt_look_px_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? (col_beat[0] ? lb0_cache0_q[31:0] : lb0_cache1_q[31:0])
: (col_beat[0] ? lb1_cache0_q[31:0] : lb1_cache1_q[31:0]);
end
if (!BINOMIAL_3X3_FILTER) begin
prv_left_px_q <= '0;
cur_left_px_q <= '0;
nxt_left_px_q <= '0;
prv_look_px_q <= '0;
end
lane_q <= col_lane;
vphase_q <= stretch_vphase_q;
hphase_q <= stretch_phase_q;
first_source_x_q <= (scan_x == 12'd0);
last_source_x_q <= (scan_x >= 12'(H_SOURCE_PX-1));
in_q <= (pixel_x < PX_PER_ROW) && (pixel_y < N_ROWS);
if ((col_beat != active_beat_q) &&
(col_beat < RB_BITS'(H_LAST_BEAT))) begin
active_beat_q <= col_beat;
video_rd_addr_q <= col_beat + 1'b1;
video_rd_req_q <= 1'b1;
end
end
end
end else begin : g_direct_read
always_ff @(posedge video_clk) begin
// One video read from each physical buffer supplies the current row
// and its already-prefetched successor in parallel. Select them in
// this same registered stage so parity cannot lag at a line boundary.
if (disp_buf) begin
prv_word_q <= '0;
cur_word_q <= lb1[col_beat];
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? lb1[col_beat] : lb0[col_beat];
end else begin
prv_word_q <= '0;
cur_word_q <= lb0[col_beat];
nxt_word_q <= (scan_y + 1'b1 >= ($clog2(N_ROWS)+1)'(N_ROWS))
? lb0[col_beat] : lb1[col_beat];
end
cur_look_px_q <= '0;
nxt_look_px_q <= '0;
prv_left_px_q <= '0;
cur_left_px_q <= '0;
nxt_left_px_q <= '0;
prv_look_px_q <= '0;
lane_q <= col_lane;
vphase_q <= stretch_vphase_q;
hphase_q <= stretch_phase_q;
first_source_x_q <= 1'b1;
last_source_x_q <= 1'b1;
in_q <= in_window && (pixel_x < PX_PER_ROW) && (pixel_y < N_ROWS);
end
end
endgenerate
// PSMCT32 ABGR8888 (r=[7:0],g=[15:8],b=[23:16]) — matches gs_lpddr_scanout (frame-cache).
wire [31:0] px32 = word_q[lane_q[2:0]*32 +: 32]; // 3-bit lane: always in-range (0..224)
wire [7:0] r32 = px32[7:0], g32 = px32[15:8], b32 = px32[23:16];
wire [31:0] px32_prv = prv_word_q[lane_q[2:0]*32 +: 32];
wire [31:0] px32_cur = cur_word_q[lane_q[2:0]*32 +: 32];
wire [31:0] px32_nxt = nxt_word_q[lane_q[2:0]*32 +: 32];
wire [31:0] px32_prv_left = first_source_x_q ? px32_prv :
((lane_q[2:0] == 3'd0)
? prv_left_px_q
: prv_word_q[(lane_q[2:0]-1'b1)*32 +: 32]);
wire [31:0] px32_cur_left = first_source_x_q ? px32_cur :
((lane_q[2:0] == 3'd0)
? cur_left_px_q
: cur_word_q[(lane_q[2:0]-1'b1)*32 +: 32]);
wire [31:0] px32_nxt_left = first_source_x_q ? px32_nxt :
((lane_q[2:0] == 3'd0)
? nxt_left_px_q
: nxt_word_q[(lane_q[2:0]-1'b1)*32 +: 32]);
wire [31:0] px32_prv_right = last_source_x_q ? px32_prv :
((lane_q[2:0] == 3'd7)
? prv_look_px_q
: prv_word_q[(lane_q[2:0]+1'b1)*32 +: 32]);
wire [31:0] px32_cur_right = last_source_x_q ? px32_cur :
((lane_q[2:0] == 3'd7)
? cur_look_px_q
: cur_word_q[(lane_q[2:0]+1'b1)*32 +: 32]);
wire [31:0] px32_nxt_right = last_source_x_q ? px32_nxt :
((lane_q[2:0] == 3'd7)
? nxt_look_px_q
: nxt_word_q[(lane_q[2:0]+1'b1)*32 +: 32]);
// PSMCT16 RGBA5551 5-bit lanes expanded to 8-bit.
wire [15:0] px16 = word_q[lane_q*16 +: 16];
wire [4:0] r5 = px16[4:0], g5 = px16[9:5], b5 = px16[14:10];
assign r = !in_q ? 8'd0 : (PSMCT32 ? r32 : {r5, r5[4:2]});
assign g = !in_q ? 8'd0 : (PSMCT32 ? g32 : {g5, g5[4:2]});
assign b = !in_q ? 8'd0 : (PSMCT32 ? b32 : {b5, b5[4:2]});
wire [15:0] px16_prv = prv_word_q[lane_q*16 +: 16];
wire [15:0] px16_cur = cur_word_q[lane_q*16 +: 16];
wire [15:0] px16_nxt = nxt_word_q[lane_q*16 +: 16];
wire [15:0] px16_prv_left = first_source_x_q ? px16_prv :
((lane_q == 4'd0)
? prv_left_px_q[15:0]
: prv_word_q[(lane_q-1'b1)*16 +: 16]);
wire [15:0] px16_cur_left = first_source_x_q ? px16_cur :
((lane_q == 4'd0)
? cur_left_px_q[15:0]
: cur_word_q[(lane_q-1'b1)*16 +: 16]);
wire [15:0] px16_nxt_left = first_source_x_q ? px16_nxt :
((lane_q == 4'd0)
? nxt_left_px_q[15:0]
: nxt_word_q[(lane_q-1'b1)*16 +: 16]);
wire [15:0] px16_prv_right = last_source_x_q ? px16_prv :
((lane_q == 4'd15)
? prv_look_px_q[15:0]
: prv_word_q[(lane_q+1'b1)*16 +: 16]);
wire [15:0] px16_cur_right = last_source_x_q ? px16_cur :
((lane_q == 4'd15)
? cur_look_px_q[15:0]
: cur_word_q[(lane_q+1'b1)*16 +: 16]);
wire [15:0] px16_nxt_right = last_source_x_q ? px16_nxt :
((lane_q == 4'd15)
? nxt_look_px_q[15:0]
: nxt_word_q[(lane_q+1'b1)*16 +: 16]);
wire [7:0] r16_cur = {px16_cur[4:0], px16_cur[4:2]};
wire [7:0] g16_cur = {px16_cur[9:5], px16_cur[9:7]};
wire [7:0] b16_cur = {px16_cur[14:10], px16_cur[14:12]};
wire [7:0] r16_nxt = {px16_nxt[4:0], px16_nxt[4:2]};
wire [7:0] g16_nxt = {px16_nxt[9:5], px16_nxt[9:7]};
wire [7:0] b16_nxt = {px16_nxt[14:10], px16_nxt[14:12]};
wire [7:0] r16_prv = {px16_prv[4:0], px16_prv[4:2]};
wire [7:0] g16_prv = {px16_prv[9:5], px16_prv[9:7]};
wire [7:0] b16_prv = {px16_prv[14:10], px16_prv[14:12]};
wire [7:0] r16_prv_left = {px16_prv_left[4:0], px16_prv_left[4:2]};
wire [7:0] g16_prv_left = {px16_prv_left[9:5], px16_prv_left[9:7]};
wire [7:0] b16_prv_left = {px16_prv_left[14:10], px16_prv_left[14:12]};
wire [7:0] r16_cur_left = {px16_cur_left[4:0], px16_cur_left[4:2]};
wire [7:0] g16_cur_left = {px16_cur_left[9:5], px16_cur_left[9:7]};
wire [7:0] b16_cur_left = {px16_cur_left[14:10], px16_cur_left[14:12]};
wire [7:0] r16_nxt_left = {px16_nxt_left[4:0], px16_nxt_left[4:2]};
wire [7:0] g16_nxt_left = {px16_nxt_left[9:5], px16_nxt_left[9:7]};
wire [7:0] b16_nxt_left = {px16_nxt_left[14:10], px16_nxt_left[14:12]};
wire [7:0] r16_prv_right = {px16_prv_right[4:0], px16_prv_right[4:2]};
wire [7:0] g16_prv_right = {px16_prv_right[9:5], px16_prv_right[9:7]};
wire [7:0] b16_prv_right = {px16_prv_right[14:10], px16_prv_right[14:12]};
wire [7:0] r16_cur_right = {px16_cur_right[4:0], px16_cur_right[4:2]};
wire [7:0] g16_cur_right = {px16_cur_right[9:5], px16_cur_right[9:7]};
wire [7:0] b16_cur_right = {px16_cur_right[14:10], px16_cur_right[14:12]};
wire [7:0] r16_nxt_right = {px16_nxt_right[4:0], px16_nxt_right[4:2]};
wire [7:0] g16_nxt_right = {px16_nxt_right[9:5], px16_nxt_right[9:7]};
wire [7:0] b16_nxt_right = {px16_nxt_right[14:10], px16_nxt_right[14:12]};
function automatic logic [7:0] blend15(
input logic [7:0] cur,
input logic [7:0] nxt,
input logic [3:0] frac
);
logic [12:0] weighted;
begin
if (frac == 4'd0)
blend15 = cur;
else begin
weighted = ((4'd15-frac) * cur) + (frac * nxt) + 13'd7;
blend15 = weighted / 13'd15;
end
end
endfunction
function automatic logic [7:0] blend5(
input logic [7:0] left,
input logic [7:0] right,
input logic [2:0] frac
);
logic [10:0] weighted;
begin
if (frac == 3'd0)
blend5 = left;
else begin
weighted = ((3'd5-frac) * left) + (frac * right) + 11'd2;
blend5 = weighted / 11'd5;
end
end
endfunction
function automatic logic [7:0] binom3(
input logic [7:0] left,
input logic [7:0] center,
input logic [7:0] right
);
logic [9:0] weighted;
begin
weighted = {2'b0,left} + {1'b0,center,1'b0} + {2'b0,right} + 10'd2;
binom3 = weighted[9:2];
end
endfunction
wire [7:0] prv_r = PSMCT32 ? px32_prv[7:0] : r16_prv;
wire [7:0] prv_g = PSMCT32 ? px32_prv[15:8] : g16_prv;
wire [7:0] prv_b = PSMCT32 ? px32_prv[23:16] : b16_prv;
wire [7:0] cur_r = PSMCT32 ? px32_cur[7:0] : r16_cur;
wire [7:0] cur_g = PSMCT32 ? px32_cur[15:8] : g16_cur;
wire [7:0] cur_b = PSMCT32 ? px32_cur[23:16] : b16_cur;
wire [7:0] nxt_r = PSMCT32 ? px32_nxt[7:0] : r16_nxt;
wire [7:0] nxt_g = PSMCT32 ? px32_nxt[15:8] : g16_nxt;
wire [7:0] nxt_b = PSMCT32 ? px32_nxt[23:16] : b16_nxt;
wire [7:0] prv_left_r = PSMCT32 ? px32_prv_left[7:0] : r16_prv_left;
wire [7:0] prv_left_g = PSMCT32 ? px32_prv_left[15:8] : g16_prv_left;
wire [7:0] prv_left_b = PSMCT32 ? px32_prv_left[23:16] : b16_prv_left;
wire [7:0] cur_left_r = PSMCT32 ? px32_cur_left[7:0] : r16_cur_left;
wire [7:0] cur_left_g = PSMCT32 ? px32_cur_left[15:8] : g16_cur_left;
wire [7:0] cur_left_b = PSMCT32 ? px32_cur_left[23:16] : b16_cur_left;
wire [7:0] nxt_left_r = PSMCT32 ? px32_nxt_left[7:0] : r16_nxt_left;
wire [7:0] nxt_left_g = PSMCT32 ? px32_nxt_left[15:8] : g16_nxt_left;
wire [7:0] nxt_left_b = PSMCT32 ? px32_nxt_left[23:16] : b16_nxt_left;
wire [7:0] prv_right_r = PSMCT32 ? px32_prv_right[7:0] : r16_prv_right;
wire [7:0] prv_right_g = PSMCT32 ? px32_prv_right[15:8] : g16_prv_right;
wire [7:0] prv_right_b = PSMCT32 ? px32_prv_right[23:16] : b16_prv_right;
wire [7:0] cur_right_r = PSMCT32 ? px32_cur_right[7:0] : r16_cur_right;
wire [7:0] cur_right_g = PSMCT32 ? px32_cur_right[15:8] : g16_cur_right;
wire [7:0] cur_right_b = PSMCT32 ? px32_cur_right[23:16] : b16_cur_right;
wire [7:0] nxt_right_r = PSMCT32 ? px32_nxt_right[7:0] : r16_nxt_right;
wire [7:0] nxt_right_g = PSMCT32 ? px32_nxt_right[15:8] : g16_nxt_right;
wire [7:0] nxt_right_b = PSMCT32 ? px32_nxt_right[23:16] : b16_nxt_right;
wire [3:0] filter_frac = V_LINEAR_FILTER ? vphase_q : 4'd0;
wire [2:0] hfilter_frac = H_LINEAR_FILTER ? hphase_q : 3'd0;
wire [7:0] left_r = blend15(cur_r, nxt_r, filter_frac);
wire [7:0] left_g = blend15(cur_g, nxt_g, filter_frac);
wire [7:0] left_b = blend15(cur_b, nxt_b, filter_frac);
wire [7:0] right_r = blend15(cur_right_r, nxt_right_r, filter_frac);
wire [7:0] right_g = blend15(cur_right_g, nxt_right_g, filter_frac);
wire [7:0] right_b = blend15(cur_right_b, nxt_right_b, filter_frac);
wire [7:0] linear_out_r = blend5(left_r, right_r, hfilter_frac);
wire [7:0] linear_out_g = blend5(left_g, right_g, hfilter_frac);
wire [7:0] linear_out_b = blend5(left_b, right_b, hfilter_frac);
wire [7:0] bin_prv_r = binom3(prv_left_r, prv_r, prv_right_r);
wire [7:0] bin_prv_g = binom3(prv_left_g, prv_g, prv_right_g);
wire [7:0] bin_prv_b = binom3(prv_left_b, prv_b, prv_right_b);
wire [7:0] bin_cur_r = binom3(cur_left_r, cur_r, cur_right_r);
wire [7:0] bin_cur_g = binom3(cur_left_g, cur_g, cur_right_g);
wire [7:0] bin_cur_b = binom3(cur_left_b, cur_b, cur_right_b);
wire [7:0] bin_nxt_r = binom3(nxt_left_r, nxt_r, nxt_right_r);
wire [7:0] bin_nxt_g = binom3(nxt_left_g, nxt_g, nxt_right_g);
wire [7:0] bin_nxt_b = binom3(nxt_left_b, nxt_b, nxt_right_b);
wire [7:0] out_r = BINOMIAL_3X3_FILTER ? binom3(bin_prv_r, bin_cur_r, bin_nxt_r)
: linear_out_r;
wire [7:0] out_g = BINOMIAL_3X3_FILTER ? binom3(bin_prv_g, bin_cur_g, bin_nxt_g)
: linear_out_g;
wire [7:0] out_b = BINOMIAL_3X3_FILTER ? binom3(bin_prv_b, bin_cur_b, bin_nxt_b)
: linear_out_b;
assign r = !in_q ? 8'd0 : out_r;
assign g = !in_q ? 8'd0 : out_g;
assign b = !in_q ? 8'd0 : out_b;
// ================= axi side (axi_clk) — row fill FSM =================
// free-running prefetcher: fetch rows sequentially, staying <= disp_row+1 ahead.
// disp_row crosses video->axi (slowly-changing; the +1 throttle tolerates a 1-off
// transient). frame_start is edge-detected here to reset next_fetch every frame.
logic [2:0] fs_sync_e;
wire fs_edge_e = (fs_sync_e[2] != fs_sync_e[1]);
logic [$clog2(N_ROWS):0] disp_row_s0, disp_row_e;
wire fs_edge_e = fs_sync_e[1] && !fs_sync_e[2]; // RISING edge only: one prefetch restart per frame_start pulse
logic [$clog2(N_ROWS):0] disp_row_s0, disp_row_limit_e;
logic [$clog2(N_ROWS):0] next_fetch; // next row to load (0..N_ROWS)
typedef enum logic [1:0] { L_IDLE, L_AR, L_R } lstate_t;
logic [1:0] next_fetch_buf;
typedef enum logic [1:0] { L_IDLE, L_AR, L_R, L_C } lstate_t;
lstate_t lst;
logic [$clog2(N_ROWS):0] cur_row;
logic cur_buf;
logic [1:0] cur_buf;
logic [RB_BITS:0] beat;
logic fs_pending; // a vsync restart is pending; applied in L_IDLE (never mid-read)
// Ch358 (Codex) — one-entry RESPONSE STAGE: the 26.1 STA leader (WNS -0.884 at 640) was the EMIF read-FIFO
// RAM output driving lb0/lb1's write port in the SAME rvalid cycle (RAM->bus->RAM in one 310MHz period).
// L_R now only CAPTURES {rdata, beat, buf, last} into per-buffer physical write registers. L_C commits the
// selected LB RAM; next_fetch/line_valid advance only after that commit. One outstanding read.
// Ch439b — physical write-port stage. The Ch438 fit exposed a route-only
// r_data_q -> lb1 RAM path at 310 MHz after adding the third line buffer.
// Give each inferred RAM its own data/address/enable launch registers so
// the fitter can place them beside that RAM instead of routing one shared
// 256-bit register bank across all three memories. Capture directly from
// AXI in L_R and commit in L_C: the first Ch439 L_R->L_C->L_W form added a
// cycle per beat and produced sustained line-buffer underflow on hardware.
// Data/address registers intentionally have no reset; the reset write-
// enables qualify them.
logic [255:0] lb0_wdata_q, lb1_wdata_q, lb2_wdata_q;
logic [RB_BITS-1:0] lb0_waddr_q, lb1_waddr_q, lb2_waddr_q;
logic lb0_we_q, lb1_we_q, lb2_we_q;
always_ff @(posedge axi_clk) begin
if (!axi_rst_n) begin
fs_sync_e <= 3'd0; disp_row_s0 <= '0; disp_row_e <= '0; next_fetch <= '0;
fs_sync_e <= 3'd0;
disp_row_s0 <= ($clog2(N_ROWS)+1)'(V_SOURCE_START);
disp_row_limit_e <= ($clog2(N_ROWS)+1)'(V_SOURCE_START + 1);
next_fetch <= ($clog2(N_ROWS)+1)'(V_SOURCE_START);
next_fetch_buf <= BINOMIAL_3X3_FILTER ? 2'(V_SOURCE_BUF)
: {1'b0, 1'(V_SOURCE_START)};
lst <= L_IDLE; araddr <= '0; arvalid <= 1'b0; rready <= 1'b0;
cur_row <= '0; cur_buf <= 1'b0; beat <= '0;
cur_row <= '0; cur_buf <= 2'd0; beat <= '0;
line_valid <= 1'b0; rd_errs <= 32'd0; fs_pending <= 1'b0;
lb0_we_q <= 1'b0; lb1_we_q <= 1'b0; lb2_we_q <= 1'b0;
end else begin
fs_sync_e <= {fs_sync_e[1:0], frame_start};
disp_row_s0 <= disp_row_v; // 2-FF sync of the display row
disp_row_e <= disp_row_s0;
// Register the already-incremented throttle limit. This remains the
// second CDC stage, but removes disp_row -> (+1) -> compare -> araddr
// enable from one 310 MHz cycle (the post-alpha fit's -0.125 ns family).
// The extra bit represents N_ROWS exactly on the final display row.
disp_row_limit_e <= disp_row_s0 + 1'b1;
// Ch439c — RAM-local response pipeline. Commit the response
// captured on the preceding cycle while the AXI FSM advances to
// (or waits for) the next single-beat read. This keeps the
// rdata->local-register->RAM timing cut without paying an L_C
// bubble after every beat. The final beat uses L_C only as a
// one-cycle row-end flush before next_fetch becomes visible.
if (lb0_we_q) lb0[lb0_waddr_q] <= lb0_wdata_q;
if (lb1_we_q) lb1[lb1_waddr_q] <= lb1_wdata_q;
if (lb2_we_q) lb2[lb2_waddr_q] <= lb2_wdata_q;
lb0_we_q <= 1'b0;
lb1_we_q <= 1'b0;
lb2_we_q <= 1'b0;
// vsync: mark a prefetch restart. DEFER it to L_IDLE so an in-flight AXI
// read is never aborted mid-handshake (which would deadlock the slave).
if (fs_edge_e) fs_pending <= 1'b1;
case (lst)
L_IDLE: begin
if (fs_pending) begin
next_fetch <= '0; // restart prefetch sequence from row 0
next_fetch <= ($clog2(N_ROWS)+1)'(V_SOURCE_START);
next_fetch_buf <= BINOMIAL_3X3_FILTER ? 2'(V_SOURCE_BUF)
: {1'b0, 1'(V_SOURCE_START)};
// restart at the captured display source row
fs_pending <= 1'b0;
end else if (enable && (next_fetch < N_ROWS) && (next_fetch <= disp_row_e + 1'b1)) begin
end else if (enable && (next_fetch < N_ROWS) && (next_fetch <= disp_row_limit_e)) begin
cur_row <= next_fetch;
cur_buf <= next_fetch[0];
cur_buf <= next_fetch_buf;
araddr <= FB_BASE + (next_fetch * STRIDE_BYTES);
beat <= '0;
arvalid <= 1'b1;
@@ -175,16 +713,27 @@ module gs_lpddr_scanout_lb #(
end
L_R: begin
if (rvalid) begin
if (cur_buf) lb1[beat[RB_BITS-1:0]] <= rdata;
else lb0[beat[RB_BITS-1:0]] <= rdata;
// Capture directly into the selected RAM-local port
// stage. L_C commits it on the following cycle.
lb0_we_q <= 1'b0;
lb1_we_q <= 1'b0;
lb2_we_q <= 1'b0;
case (cur_buf)
2'd1: begin lb1_wdata_q <= rdata; lb1_waddr_q <= beat[RB_BITS-1:0]; lb1_we_q <= 1'b1; end
2'd2: begin lb2_wdata_q <= rdata; lb2_waddr_q <= beat[RB_BITS-1:0]; lb2_we_q <= 1'b1; end
default: begin lb0_wdata_q <= rdata; lb0_waddr_q <= beat[RB_BITS-1:0]; lb0_we_q <= 1'b1; end
endcase
if (rresp != 2'b00) rd_errs <= rd_errs + 32'd1;
rready <= 1'b0;
if (beat == ROW_BEATS-1) begin
line_valid <= 1'b1;
next_fetch <= next_fetch + 1'b1; // advance prefetch (rows 0..next_fetch-1 loaded)
lst <= L_IDLE;
// The local register captures this last response
// now; L_C flushes it into RAM on the next edge.
lst <= L_C;
end else begin
// next single-beat read of this row (arlen=0 each).
// Previous behavior inserted L_C here and lost one
// EMIF clock per beat. The RAM-local stage commits
// independently above, so immediately issue the
// next read just as the pre-Ch439 FSM did.
beat <= beat + 1'b1;
araddr <= araddr + 30'd32;
arvalid <= 1'b1;
@@ -192,6 +741,18 @@ module gs_lpddr_scanout_lb #(
end
end
end
L_C: begin
// The global pipeline commit above writes the final beat
// on this edge. Publish the completed row only now.
line_valid <= 1'b1;
next_fetch <= next_fetch + 1'b1; // rows 0..next_fetch are now loaded
if (BINOMIAL_3X3_FILTER)
next_fetch_buf <= (next_fetch_buf == 2'd2) ? 2'd0
: next_fetch_buf + 1'b1;
else
next_fetch_buf <= {1'b0, ~next_fetch_buf[0]};
lst <= L_IDLE;
end
default: lst <= L_IDLE;
endcase
end
@@ -206,7 +767,12 @@ module gs_lpddr_scanout_lb #(
always_ff @(posedge video_clk) begin
nf_s0 <= next_fetch; nf_v <= nf_s0;
if (!enable || fs_edge_v) underflow_v <= 1'b0;
else if (in_window && (pixel_y < N_ROWS) && (($clog2(N_ROWS)+1)'(pixel_y) >= nf_v))
else if (in_window && (scan_y < ($clog2(N_ROWS)+1)'(N_ROWS)) &&
((scan_y >= nf_v) ||
((BINOMIAL_3X3_FILTER ||
(V_LINEAR_FILTER && (stretch_vphase_q != 4'd0))) &&
(scan_y + 1'b1 < ($clog2(N_ROWS)+1)'(N_ROWS)) &&
(scan_y + 1'b1 >= nf_v))))
underflow_v <= 1'b1;
end
assign underflow = underflow_v;
+13 -8
View File
@@ -121,24 +121,28 @@ module gs_lpddr_wr_arb (
// so AW sets it first, but tracking either makes this a GENERAL AXI write arbiter that never
// abandons a transaction regardless of AW/W ordering (Codex audit note).
reg aw_done; // a write beat/addr accepted for the active grant -> never abort past here
// Ch368 -- arm EMIF BREADY from the final W handshake, rather than feeding it through the
// live grant/client-ready mux. All current writers enter their B-wait state on that same
// handshake (or hold BREADY high), so this preserves the AW->W->B contract while cutting
// the grant/bready -> EMIF response-FIFO timing cone.
reg bready_q;
reg [21:0] watchdog; // pre-commit only; ~6.7 ms @ 310 MHz dead-bus backstop
wire wd_expired = watchdog[21];
wire sel_bready = (grant==3'd1)?s0_bready:(grant==3'd2)?s1_bready:
(grant==3'd3)?s2_bready:(grant==3'd4)?s3_bready:1'b1;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
grant <= 3'd0; aw_done <= 1'b0; watchdog <= '0;
grant <= 3'd0; aw_done <= 1'b0; bready_q <= 1'b0; watchdog <= '0;
end else if (grant == 3'd0) begin
aw_done <= 1'b0; watchdog <= '0;
aw_done <= 1'b0; bready_q <= 1'b0; watchdog <= '0;
if (s0_awvalid) grant <= 3'd1; // FB writer (highest)
else if (s2_awvalid) grant <= 3'd3; // Z spill (render-flush)
else if (s1_awvalid) grant <= 3'd2; // color spill (render-flush)
else if (s3_awvalid) grant <= 3'd4; // HPS write-probe (debug, lowest)
end else begin
if ((m_awvalid && m_awready) || (m_wvalid && m_wready)) aw_done <= 1'b1; // AW or W accepted -> COMMITTED
if (m_bvalid && sel_bready) begin
grant <= 3'd0; aw_done <= 1'b0; watchdog <= '0; // B delivered -> release
if (m_wvalid && m_wready && m_wlast) bready_q <= 1'b1;
if (m_bvalid && bready_q) begin
grant <= 3'd0; aw_done <= 1'b0; bready_q <= 1'b0; watchdog <= '0; // B delivered -> release
end else if (!aw_done) begin // still waiting for AW (nothing owed)
if (wd_expired) begin grant <= 3'd0; aw_done <= 1'b0; watchdog <= '0; end
else watchdog <= watchdog + 22'd1;
@@ -169,11 +173,12 @@ module gs_lpddr_wr_arb (
assign s2_wready = (grant==3'd3)?m_wready:1'b0;
assign s3_wready = (grant==3'd4)?m_wready:1'b0;
// B demux (idle: bready=1 drains any stale/late response)
// B demux. bready_q arms exactly after the selected final W handshake, one cycle before
// the earliest response the existing writers can consume. It is held through B acceptance.
assign s0_bresp = m_bresp; assign s1_bresp = m_bresp; assign s2_bresp = m_bresp; assign s3_bresp = m_bresp;
assign s0_bvalid = (grant==3'd1)?m_bvalid:1'b0;
assign s1_bvalid = (grant==3'd2)?m_bvalid:1'b0;
assign s2_bvalid = (grant==3'd3)?m_bvalid:1'b0;
assign s3_bvalid = (grant==3'd4)?m_bvalid:1'b0;
assign m_bready = (grant==3'd1)?s0_bready:(grant==3'd2)?s1_bready:(grant==3'd3)?s2_bready:(grant==3'd4)?s3_bready:1'b1;
assign m_bready = bready_q;
endmodule
+373
View File
@@ -0,0 +1,373 @@
// retroDE_ps2 — gs_lpddr_z_rmw (Ch357 — packed PSMZ16S persistent-Z LPDDR read-modify-write engine, STANDALONE unit)
//
// Codex Ch357 gate 1: "Build the packed PSMZ16S LPDDR RMW engine as a standalone unit first." This is the depth-test
// core for Option B (LPDDR-persistent Z). It owns a PRIVATE, LINEAR, packed-16-bit Z buffer in LPDDR.
//
// AUTHENTICITY NOTE (Codex): this Z buffer is INTERNAL — it is NEVER exposed as GS local memory. Therefore only the
// PSMZ16S VALUE/TEST semantics are authentic (clamp16 source, GEQUAL, ZMSK); its PHYSICAL storage layout is deliberately a
// simple linear packing, NOT the PSMZ16S memory swizzle. That is valid precisely because nothing outside reads it as GS VRAM.
//
// PSMZ16S depth semantics (pinned against PCSX2 SW raster — GSRendererSW.cpp:1439 z_max=0xFFFF, GSDrawScanline…:1129
// source clamp, :1157 dest mask + GEQUAL):
// src_z = min(frag_z, 0xFFFF) // clamp16 (NOT mask)
// dest_z = stored 16-bit Z at the pixel
// pass = (src_z >= dest_z) // GEQUAL, larger Z = nearer
// on pass && !zmsk: stored := src_z // ZMSK=1 => test still runs, WRITE suppressed only
//
// Storage: pixel_index = y*FB_PXW + x ; 16 Z per 256-bit (32-byte) beat ; beat = pixel_index>>4, lane = pixel_index[3:0];
// byte addr = ZBASE + beat*32. Raster (scanline) order gives strong beat locality.
//
// Hazards / backpressure (Codex gates): a SINGLE write-back cache line (one 256-bit beat) holds all 16 lanes, so
// consecutive fragments to the same beat (incl. the SAME pixel) read the latest pending Z IN-PLACE — same-beat RMW hazards
// are forwarded with 1-cycle throughput and no stale read. On a beat MISS the engine flushes the dirty line, reads the new
// beat, and BACKPRESSURES the fragment producer (f_ready=0) throughout — a bounded producer, never an unbounded stream
// feeding a latent LPDDR read. Color is emitted by the CONSUMER only after p_pass (this unit produces the pass bit).
//
// Single clock (axi_clk domain). Integration adds the raster(gs_clk)->axi_clk CDC (async-FIFO, like gs_lpddr_axi_master).
// AXI4: single-beat INCR (arsize/awsize=5=32B, len=0), full per-byte wstrb, backpressured handshakes.
module gs_lpddr_z_rmw #(
parameter [31:0] ZBASE = 32'h0030_0000, // LPDDR byte base of the private Z buffer (Linux-safe reserved region)
parameter int FB_PXW = 256, // framebuffer pixel width (pixel-index stride)
parameter int FB_H = 210, // framebuffer height (bounds the preclear loop)
parameter [15:0] Z_CLEAR = 16'h0000 // GEQUAL clear value (0 = farthest; any fragment passes first)
) (
input logic clk,
input logic rst_n,
input logic enable, // 0 => fully inert (no AXI activity)
// ---- preclear (Codex: preclear shared Z once to the GEQUAL clear value) ----
input logic clear_start, // pulse: write Z_CLEAR to every beat, then clear_done
output logic clear_done,
// ---- scene-end flush (Codex: on the ordered end-of-scene marker, flush the dirty Z line + wait its BRESP). The cache
// stays VALID and CLEAN afterwards so Z PERSISTS across scheduler epochs (texture rebind must not clear it). ----
input logic scene_flush, // hold high until z_drained; flushes the dirty line to LPDDR
output logic z_drained, // all Z durable in LPDDR (idle, cache clean, no pending AXI)
// ---- fragment input stream (valid/ready) ----
input logic f_valid,
output logic f_ready,
input logic [11:0] f_x,
input logic [11:0] f_y,
input logic [31:0] f_z, // fragment Z (pre-clamp)
input logic f_zmsk, // 1 => suppress Z write (test still occurs)
input logic [1:0] f_ztst, // 0 NEVER, 1 ALWAYS, 2 GEQUAL, 3 GREATER
// ---- result output stream (in fragment order): p_pass gates the color write downstream ----
output logic p_valid,
input logic p_ready,
output logic p_pass,
output logic [11:0] p_x,
output logic [11:0] p_y,
output logic [15:0] p_zq, // the clamped fragment Z (for the color path / debug)
// ---- AXI4 read (Z fetch) ----
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,
// ---- AXI4 write (Z flush) ----
output logic [31:0] awaddr,
output logic [7:0] awlen,
output logic [2:0] awsize,
output logic [1:0] awburst,
output logic awvalid,
input logic awready,
output logic [255:0] wdata,
output logic [31:0] wstrb,
output logic wlast,
output logic wvalid,
input logic wready,
input logic bvalid,
output logic bready,
input logic [1:0] bresp,
// ---- status ----
output logic [31:0] beats_read,
output logic [31:0] beats_written,
output logic [31:0] bresp_err,
output logic idle
);
localparam int NPX = FB_PXW*FB_H;
localparam int NBEATS = (NPX + 15) / 16;
localparam int BW = (NBEATS <= 1) ? 1 : $clog2(NBEATS);
// ---- clamp16 (PSMZ16S source clamp) ----
function automatic logic [15:0] clamp16(input logic [31:0] z);
clamp16 = (|z[31:16]) ? 16'hFFFF : z[15:0];
endfunction
// ---- single write-back Z-cache line ----
logic [255:0] cache_data; // 16 lanes x 16-bit Z
// Ch357 (Codex) — register the accepted AXI read data BEFORE updating cache_data, so the EMIF read-FIFO -> cache
// 310 MHz path is register->register (the -0.8 ns closure). S_FILL_R captures rdata here; S_FILL_C commits it.
logic [255:0] z_rd_q;
logic [BW-1:0] cache_beat;
logic cache_valid, cache_dirty;
// ---- pending fragment (latched during a miss) ----
logic [11:0] pf_x, pf_y;
logic [15:0] pf_zq;
logic pf_zmsk;
logic [1:0] pf_ztst;
logic [BW-1:0] pf_beat;
logic [3:0] pf_lane;
// Ch357 — DECODE-STAGE pipeline register (Codex: pipeline the 310 MHz u_req->cache path). The incoming fragment's
// index/lane/beat + clamp16(z) are computed combinationally and REGISTERED here on accept; the cache read/compare/
// write then runs the NEXT cycle off the registered fields. This splits the single-cycle FIFO->index->lane->256-bit
// cache-mux->compare->cache-write cone (routed -2.240 ns @ 310 MHz) into two shorter registered stages. Same-beat
// forwarding is preserved: fragments are 2 cycles apart in the RMW stage, so an in-place cache write is committed a
// cycle before the next same-beat read. Throughput 155 MHz >> the raster fragment rate (no new FIFO pressure).
logic d_valid;
logic [3:0] d_lane;
logic [15:0] d_zq;
logic d_zmsk;
logic [1:0] d_ztst;
logic [11:0] d_x, d_y;
logic [BW-1:0] d_beat;
// Ch358 (Codex) — registered HIT flag, the companion to d_beat: loaded from hit_c at stage-1 accept so stage 2
// branches on ONE registered bit instead of the BW-wide (15-bit at 640x480) d_beat==cache_beat equality that
// directly gated the 256-bit wdata<=cache_data dirty-evict load (the Ch358 fit's WNS -0.092 family). Exact by
// construction: accept happens only in S_RUN with the decode slot free (f_ready), and cache_beat/cache_valid
// change only in S_FILL_C (unreachable while d_* is pending) or at clear_start (precedes all fragments; the
// zint gates assert pre_clear_frags==0) -> hit_c cannot go stale between accept and the single stage-2 consume
// (a missed fragment is promoted to pf_* and handed to rmw_* by S_FILL_C; it never re-enters stage 2).
logic d_hit;
// Ch357 (Codex) — RMW-STAGE pipeline register (stage 3). Stage 2 does the barrel READ of the target lane
// (cache_data[d_lane] on a hit, z_rd_q[pf_lane] on a fill) into rmw_dz; stage 3 (next cycle) does GEQUAL + the
// barrel WRITE via an explicit 16-way case. Splitting read from compare+write removes the cache_data->cache_data
// cross-lane feedback mux (the -0.607ns 310MHz path). f_ready already spaces fragments 2 cyc apart, so an in-place
// lane write commits one cycle before the next same-beat lane read -> same-beat forwarding preserved, no bypass.
logic rmw_valid;
logic [3:0] rmw_lane;
logic [15:0] rmw_dz; // the read-out dest Z of the target lane (registered)
logic [15:0] rmw_zq; // clamped fragment Z
logic rmw_zmsk;
logic [1:0] rmw_ztst;
logic [11:0] rmw_x, rmw_y;
// Ch367 — register GEQUAL before it enables a cache-line update. This
// removes the compare -> 16-way cache_data write-enable cone at 310 MHz.
logic cmp_valid;
logic cmp_pass, cmp_write;
logic [3:0] cmp_lane;
logic [15:0] cmp_zq;
logic [11:0] cmp_x, cmp_y;
// combinational address/lane of the INCOMING fragment
logic [31:0] px_index_c;
logic [BW-1:0] beat_c;
logic [3:0] lane_c;
always_comb begin
px_index_c = f_y*FB_PXW + f_x;
beat_c = px_index_c[4 +: BW];
lane_c = px_index_c[3:0];
end
wire hit_c = cache_valid && (beat_c == cache_beat);
// ---- result register (1-deep skid; holds until p_ready) ----
logic res_full;
logic res_pass;
logic [11:0] res_x, res_y;
logic [15:0] res_zq;
assign p_valid = res_full;
assign p_pass = res_pass;
assign p_x = res_x;
assign p_y = res_y;
assign p_zq = res_zq;
// ---- FSM ----
typedef enum logic [3:0] { S_RUN, S_FLUSH_AW, S_FLUSH_B, S_FILL_AR, S_FILL_R, S_FILL_C, S_CLR, S_CLR_B,
S_SFLUSH_AW, S_SFLUSH_B } st_t;
st_t st;
logic [BW-1:0] clr_beat;
// helper: perform the RMW on the cache line for a given lane/zq/zmsk, return pass (comb) and next cache line
function automatic logic ztest_pass(input logic [1:0] op, input logic [15:0] s, input logic [15:0] d);
case (op)
2'd0: ztest_pass = 1'b0;
2'd1: ztest_pass = 1'b1;
2'd2: ztest_pass = (s >= d);
2'd3: ztest_pass = (s > d);
endcase
endfunction
// Ch357 (Codex) — EXPLICIT 16-way lane write (constant slices, NOT dynamic cache_data[lane*16+:16]). Each arm writes
// one fixed 16-bit slice; the other 15 lanes are unassigned -> HOLD. This is a per-lane write-enable, not a 256-bit
// cross-lane feedback mux, which is what kept the compare+write out of the critical cone.
task automatic cache_write_lane(input logic [3:0] lane, input logic [15:0] val);
case (lane)
4'd0: cache_data[ 15: 0] <= val; 4'd1: cache_data[ 31: 16] <= val;
4'd2: cache_data[ 47: 32] <= val; 4'd3: cache_data[ 63: 48] <= val;
4'd4: cache_data[ 79: 64] <= val; 4'd5: cache_data[ 95: 80] <= val;
4'd6: cache_data[111: 96] <= val; 4'd7: cache_data[127:112] <= val;
4'd8: cache_data[143:128] <= val; 4'd9: cache_data[159:144] <= val;
4'd10: cache_data[175:160] <= val; 4'd11: cache_data[191:176] <= val;
4'd12: cache_data[207:192] <= val; 4'd13: cache_data[223:208] <= val;
4'd14: cache_data[239:224] <= val; 4'd15: cache_data[255:240] <= val;
endcase
endtask
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
st<=S_RUN; cache_valid<=1'b0; cache_dirty<=1'b0; cache_beat<='0; cache_data<='0; z_rd_q<='0;
res_full<=1'b0; res_pass<=1'b0; res_x<='0; res_y<='0; res_zq<='0;
pf_x<='0; pf_y<='0; pf_zq<='0; pf_zmsk<='0; pf_ztst<=2'd2; pf_beat<='0; pf_lane<='0;
d_valid<=1'b0; d_lane<='0; d_zq<='0; d_zmsk<='0; d_ztst<=2'd2; d_x<='0; d_y<='0; d_beat<='0; d_hit<=1'b0;
rmw_valid<=1'b0; rmw_lane<='0; rmw_dz<='0; rmw_zq<='0; rmw_zmsk<='0; rmw_ztst<=2'd2; rmw_x<='0; rmw_y<='0;
cmp_valid<=1'b0; cmp_pass<=1'b0; cmp_write<=1'b0; cmp_lane<='0; cmp_zq<='0; cmp_x<='0; cmp_y<='0;
arvalid<=1'b0; araddr<='0; rready<=1'b0;
awvalid<=1'b0; awaddr<='0; wvalid<=1'b0; wdata<='0; wlast<=1'b0; bready<=1'b0;
beats_read<='0; beats_written<='0; bresp_err<='0; clr_beat<='0; clear_done<=1'b0;
end else begin
// constant AXI framing
arlen<=8'd0; arsize<=3'd5; arburst<=2'b01;
awlen<=8'd0; awsize<=3'd5; awburst<=2'b01; wstrb<=32'hFFFF_FFFF;
// clear the result reg when the consumer takes it
if (res_full && p_ready) res_full<=1'b0;
if (clear_start && st==S_RUN) begin
cache_valid<=1'b0; cache_dirty<=1'b0; clear_done<=1'b0; clr_beat<='0;
awaddr<=ZBASE; awvalid<=1'b1; wdata<={16{Z_CLEAR}}; wvalid<=1'b1; wlast<=1'b1; st<=S_CLR;
end
case (st)
// ---------------- normal processing ----------------
S_RUN: begin
// STAGE 4 — write a registered decision and publish its result. This is the only normal
// cache_data writer, and its enable is now register-local.
if (cmp_valid && (!res_full || p_ready)) begin
if (cmp_write) begin cache_write_lane(cmp_lane, cmp_zq); cache_dirty<=1'b1; end
res_full<=1'b1; res_pass<=cmp_pass; res_x<=cmp_x; res_y<=cmp_y; res_zq<=cmp_zq;
cmp_valid<=1'b0;
end
// STAGE 3 — register GEQUAL + write intent. Stage 2 waits for cmp_valid to clear, so a
// same-beat successor never samples cache_data in the cycle its predecessor writes it.
if (rmw_valid && !cmp_valid) begin
cmp_valid<=1'b1; cmp_pass<=ztest_pass(rmw_ztst, rmw_zq, rmw_dz);
cmp_write<=ztest_pass(rmw_ztst, rmw_zq, rmw_dz) && !rmw_zmsk;
cmp_lane<=rmw_lane; cmp_zq<=rmw_zq; cmp_x<=rmw_x; cmp_y<=rmw_y;
rmw_valid<=1'b0;
end
// scene-end flush (ordered after all fragments): push the dirty line, keep it cached (persist across
// epochs). Waits for every pipeline stage to drain so nothing is stranded.
if (scene_flush && !d_valid && !rmw_valid && !cmp_valid && cache_valid && cache_dirty) begin
awaddr<=ZBASE + (cache_beat<<5); awvalid<=1'b1;
wdata<=cache_data; wvalid<=1'b1; wlast<=1'b1; st<=S_SFLUSH_AW;
end
// STAGE 2 — barrel READ of the target lane into the RMW register (hit), or promote+fill (miss). Fires
// only when the RMW slot is empty (!rmw_valid): with f_ready's 2-cyc spacing the stages alternate, so
// stage 2 never collides with a same-cycle stage-3 cache write (miss flush reads a settled cache_data).
else if (d_valid && !rmw_valid && !cmp_valid) begin
d_valid <= 1'b0;
if (d_hit) begin // Ch358 — registered at accept (== cache_valid && d_beat==cache_beat there)
// HIT: register the read-out dest Z; stage 3 compares + writes next cycle.
rmw_valid<=1'b1; rmw_dz<=cache_data[d_lane*16 +: 16];
rmw_lane<=d_lane; rmw_zq<=d_zq; rmw_zmsk<=d_zmsk; rmw_ztst<=d_ztst; rmw_x<=d_x; rmw_y<=d_y;
end else begin
// MISS: promote the decoded fragment to pf_*, backpressure, flush-if-dirty then fill
pf_x<=d_x; pf_y<=d_y; pf_zq<=d_zq; pf_zmsk<=d_zmsk; pf_ztst<=d_ztst; pf_beat<=d_beat; pf_lane<=d_lane;
if (cache_valid && cache_dirty) begin
awaddr<=ZBASE + (cache_beat<<5); awvalid<=1'b1;
wdata<=cache_data; wvalid<=1'b1; wlast<=1'b1; st<=S_FLUSH_AW;
end else begin
araddr<=ZBASE + (d_beat<<5); arvalid<=1'b1; st<=S_FILL_AR;
end
end
end
// ACCEPT (stage-1): register the combinational index/lane/beat + clamp16(z). f_ready gates this to
// one decoded fragment in flight (d_valid), so accept and stage 2 never collide on d_* (2 cyc/frag).
if (enable && f_valid && f_ready) begin
d_valid<=1'b1; d_lane<=lane_c; d_zq<=clamp16(f_z); d_zmsk<=f_zmsk; d_ztst<=f_ztst;
d_x<=f_x; d_y<=f_y; d_beat<=beat_c; d_hit<=hit_c; // Ch358 — pre-registered hit decision
end
end
// ---------------- flush the dirty line (AW then W then B) ----------------
S_FLUSH_AW: begin
if (awready) awvalid<=1'b0;
if (wready) wvalid <=1'b0;
if ((awready||!awvalid) && (wready||!wvalid)) begin bready<=1'b1; st<=S_FLUSH_B; end
end
S_FLUSH_B: begin
if (bvalid) begin
bready<=1'b0; beats_written<=beats_written+1;
if (bresp!=2'b00) bresp_err<=bresp_err+1;
cache_dirty<=1'b0;
araddr<=ZBASE + (pf_beat<<5); arvalid<=1'b1; st<=S_FILL_AR; // now fill the wanted beat
end
end
// ---------------- fill the wanted beat (AR then R) ----------------
S_FILL_AR: begin
if (arready) begin arvalid<=1'b0; rready<=1'b1; st<=S_FILL_R; end
end
S_FILL_R: begin
// Ch357 — ACCEPT: register the read beat (z_rd_q) and take the R handshake; the EMIF read-FIFO ->
// z_rd_q path is now register->register. The cache update happens next cycle in S_FILL_C.
if (rvalid) begin
rready<=1'b0; beats_read<=beats_read+1;
if (rresp!=2'b00) bresp_err<=bresp_err+1;
z_rd_q<=rdata; st<=S_FILL_C;
end
end
S_FILL_C: begin
// Ch357 — COMMIT: fill the cache line from the registered beat, and hand the pending fragment to the
// RMW stage (barrel READ of the target lane off the just-read beat). Stage 3 does the GEQUAL + 16-way
// lane write next cycle in S_RUN (a guaranteed hit). Same result as the old single-cycle fill, staged.
cache_data<=z_rd_q; cache_beat<=pf_beat; cache_valid<=1'b1; cache_dirty<=1'b0;
rmw_valid<=1'b1; rmw_dz<=z_rd_q[pf_lane*16 +: 16];
rmw_lane<=pf_lane; rmw_zq<=pf_zq; rmw_zmsk<=pf_zmsk; rmw_ztst<=pf_ztst; rmw_x<=pf_x; rmw_y<=pf_y;
st<=S_RUN;
end
// ---------------- preclear: write Z_CLEAR to every beat (issue -> handshake -> B, loop) ----------------
S_CLR: begin
if (awready) awvalid<=1'b0;
if (wready) wvalid <=1'b0;
if ((awready||!awvalid) && (wready||!wvalid)) begin bready<=1'b1; st<=S_CLR_B; end
end
S_CLR_B: begin
if (bvalid && bready) begin
bready<=1'b0; beats_written<=beats_written+1;
if (bresp!=2'b00) bresp_err<=bresp_err+1;
if (clr_beat==BW'(NBEATS-1)) begin clear_done<=1'b1; st<=S_RUN; end
else begin
clr_beat<=clr_beat+1'b1;
awaddr<=ZBASE + ((clr_beat+1'b1)<<5); awvalid<=1'b1; wvalid<=1'b1; wlast<=1'b1; st<=S_CLR;
end
end
end
// ---------------- scene-end flush: write the dirty line, keep cache VALID+CLEAN (Z persists) ----------------
S_SFLUSH_AW: begin
if (awready) awvalid<=1'b0;
if (wready) wvalid <=1'b0;
if ((awready||!awvalid) && (wready||!wvalid)) begin bready<=1'b1; st<=S_SFLUSH_B; end
end
S_SFLUSH_B: begin
if (bvalid) begin
bready<=1'b0; beats_written<=beats_written+1;
if (bresp!=2'b00) bresp_err<=bresp_err+1;
cache_dirty<=1'b0; st<=S_RUN; // cache remains VALID (persist across epochs), now clean/durable
end
end
default: st<=S_RUN;
endcase
end
end
// f_ready: accept a new fragment in S_RUN (hit => processed this cycle; miss => latched, then flush/fill), whenever the
// result slot is free or draining this cycle. On a miss the drain empties res_full and it stays empty until S_FILL_R
// re-fills it with the pending fragment's result, so no clobber.
// hold off new fragments during a scene flush too (so the marker's flush isn't interleaved with a fresh RMW)
// Ch367 pipeline — accept only when every local stage is clear. The extra compare-result stage preserves
// same-beat forwarding without a bypass: the prior write commits before the successor can read cache_data.
assign f_ready = enable && (st==S_RUN) && !(scene_flush && cache_valid && cache_dirty)
&& !d_valid && !rmw_valid && !cmp_valid;
// idle / z_drained must see all three stages drained, else a fragment would be lost at a scene boundary.
assign idle = (st==S_RUN) && !d_valid && !rmw_valid && !cmp_valid && !res_full && !arvalid && !awvalid && !wvalid && !bready;
// z_drained: all Z durable in LPDDR — no local work, no dirty line, no pending AXI, no result stuck.
assign z_drained = (st==S_RUN) && !d_valid && !rmw_valid && !cmp_valid && !cache_dirty && !res_full && !arvalid && !awvalid && !wvalid && !bready;
endmodule : gs_lpddr_z_rmw
+368
View File
@@ -0,0 +1,368 @@
// retroDE_ps2 — gs_lpddr_zc_emit (Ch357 — Z-then-color emit integration: request FIFO + persistent-Z RMW + color writer)
//
// Codex Ch357 integration constraints:
// (1) Cross gs_clk->axi_clk with ONE async request FIFO carrying the COMPLETE fragment packet {XY, clamped Z, color,
// masks, scene marker}. No raw level-bus CDC.
// (2) Z decision FIRST; enqueue color ONLY on pass. A rejected fragment produces NO color write.
// (3) Ordered end-of-scene marker through the SAME FIFO: on it, flush the dirty Z line, wait Z BRESP + all accepted color
// BRESPs, then assert combined frame_drained.
// (4) Z cache persists across scheduler epochs (texture rebind must not clear it) — handled by gs_lpddr_z_rmw (scene_flush
// keeps the line valid+clean).
// (5) Preclear/invalidate Z once at frame start (clear_start).
// (6) LPDDR map (gs_lpddr_map_pkg): color 0x000000, Z 0x140000, tex 0x200000 — disjoint.
//
// Structure: the request async FIFO is the ONLY CDC. Everything downstream runs in axi_clk: the Z RMW (its own Z AXI
// read+write master), and the reused gs_lpddr_axi_master COLOR writer (its gs_clk tied to axi_clk, so its internal CDC FIFO
// is same-clock). A tiny in-order COLOR-ALIGN FIFO carries {coladdr,color} beside the Z pipeline so the pass decision lands
// with the right pixel. Two AXI masters are exposed (z_* and c_*); the system arbitrates them onto the shared LPDDR port.
module gs_lpddr_zc_emit #(
parameter [31:0] COLBASE = 32'h0000_0000,
parameter [31:0] ZBASE = 32'h0014_0000,
parameter int FB_PXW = 256,
parameter int FB_H = 210,
parameter [15:0] Z_CLEAR = 16'h0000,
parameter int REQ_DEPTH= 32,
parameter int COL_DEPTH= 64
) (
// ---- gs_clk fragment/marker producer ----
input logic gs_clk,
input logic gs_rst_n,
input logic enable,
input logic g_valid,
output logic g_ready,
input logic [11:0] g_x,
input logic [11:0] g_y,
input logic [15:0] g_zq, // clamped Z (clamp16 done upstream)
input logic g_zmsk,
input logic g_ztest, // 1 = depth-test this fragment; 0 = always pass (non-Z draw)
input logic [1:0] g_ztst, // GS TEST.ZTST: NEVER/ALWAYS/GEQUAL/GREATER
input logic [31:0] g_color,
input logic [16:0] g_alpha, // {ABE,A,B,C,D,FIX}, snapped at raster emit
input logic [3:0] g_be, // FRAME.FBMSK-compressed byte enables
input logic g_scene, // 1 = end-of-scene marker (x/y/z/color ignored)
// ---- axi_clk ----
input logic axi_clk,
input logic axi_rst_n,
input logic clear_start, // preclear Z once at frame start
output logic clear_done,
output logic frame_drained, // combined: color drained AND Z drained for the scene
// ---- Z AXI master (read+write) ----
output logic [31:0] z_araddr, output logic [7:0] z_arlen, output logic [2:0] z_arsize, output logic [1:0] z_arburst,
output logic z_arvalid, input logic z_arready,
input logic [255:0] z_rdata, input logic [1:0] z_rresp, input logic z_rlast, input logic z_rvalid, output logic z_rready,
output logic [31:0] z_awaddr, output logic [7:0] z_awlen, output logic [2:0] z_awsize, output logic [1:0] z_awburst,
output logic z_awvalid, input logic z_awready,
output logic [255:0] z_wdata, output logic [31:0] z_wstrb, output logic z_wlast, output logic z_wvalid, input logic z_wready,
input logic z_bvalid, output logic z_bready, input logic [1:0] z_bresp,
// ---- Color AXI master (write only) ----
output logic [31:0] c_awaddr, output logic [7:0] c_awlen, output logic [2:0] c_awsize, output logic [1:0] c_awburst,
output logic c_awvalid, input logic c_awready,
output logic [255:0] c_wdata, output logic [31:0] c_wstrb, output logic c_wlast, output logic c_wvalid, input logic c_wready,
input logic c_bvalid, output logic c_bready, input logic [1:0] c_bresp,
// ---- status ----
output logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err,
// Destination-color read AXI master. Inert for full-byte opaque writes;
// ABE and partial FRAME.FBMSK writes both use it for destination RMW.
output logic [31:0] d_araddr, output logic [7:0] d_arlen, output logic [2:0] d_arsize, output logic [1:0] d_arburst,
output logic d_arvalid, input logic d_arready,
input logic [255:0] d_rdata, input logic [1:0] d_rresp, input logic d_rlast, input logic d_rvalid, output logic d_rready,
output logic idle
);
// ---------------- request async FIFO (the ONLY CDC) ----------------
// X/Y have already been clipped to this framebuffer before reaching the
// external ROP. Do not spend twelve FIFO bits on each coordinate: the
// production 640x480 path needs only 10+9. Besides saving storage, this
// keeps the deep request FIFO below the RAM-banking threshold that put a
// bank mux on its 310 MHz registered-read path after ZTST was added.
localparam int XW = (FB_PXW <= 1) ? 1 : $clog2(FB_PXW);
localparam int YW = (FB_H <= 1) ? 1 : $clog2(FB_H);
localparam int PW = 74 + XW + YW; // {scene,be,alpha,color,ztest,ztst,zmsk,zq,y[YW],x[XW]}
localparam int Y_LSB = XW;
localparam int ZQ_LSB = XW + YW;
localparam int ZMSK_BIT = ZQ_LSB + 16;
localparam int ZTST_LSB = ZMSK_BIT + 1;
localparam int ZTEST_BIT = ZTST_LSB + 2;
localparam int COLOR_LSB = ZTEST_BIT + 1;
localparam int ALPHA_LSB = COLOR_LSB + 32;
localparam int BE_LSB = ALPHA_LSB + 17;
logic [PW-1:0] req_wdata, req_rdata; logic req_wfull, req_rempty, req_rd;
assign req_wdata = {g_scene, g_be, g_alpha, g_color, g_ztest, g_ztst, g_zmsk,
g_zq, g_y[YW-1:0], g_x[XW-1:0]};
assign g_ready = enable && !req_wfull;
// Ch439e — the measured f52 occupancy peak is 6,115, so 8K cannot be cut
// without dropping fragments from the intentionally unthrottled producer.
// Split that fixed capacity by both depth and width instead: each physical
// address copy drives one quarter of the original deep/wide RAM tree.
gs_async_fifo #(.WIDTH(PW), .DEPTH(REQ_DEPTH), .REGISTERED_READ(1'b1),
.QUADRANT_READ(1'b1)) u_req (
.wclk(gs_clk), .wrst_n(gs_rst_n), .wr(g_valid && g_ready), .wdata(req_wdata), .wfull(req_wfull),
.rclk(axi_clk), .rrst_n(axi_rst_n), .rd(req_rd), .rdata(req_rdata), .rempty(req_rempty)
);
// REGISTERED request-HEAD stage. Ch406 makes the deep request FIFO read
// synchronous: an accepted req_rd is followed by req_read_pending, which
// captures the complete registered RAM output here one cycle later.
// Ch421 keeps stage occupancy independent of packet contents: decoding the
// RAM's scene bit directly into separate fragment/marker valid registers
// was the complete post-Ch420 setup family. A single validity register is
// driven only by req_read_pending; scene/type decode happens after the full
// packet register boundary.
logic req_stage_valid;
logic req_read_pending;
logic [PW-1:0] req_stage_packet;
// Ch423 — packet DATA is intentionally unreset and lives outside the
// async-reset control process. Keeping the lone unreset data register in
// that process made Quartus fold axi_rst_n (the 4k-fanout EMIF-cal signal)
// into its clock enable. Only req_stage_valid makes this payload
// observable, so the local registered pending pulse is the complete and
// sufficient capture enable.
always_ff @(posedge axi_clk)
if (req_read_pending) req_stage_packet <= req_rdata;
wire [PW-2:0] req_stage_data = req_stage_packet[PW-2:0];
wire req_stage_frag_valid = req_stage_valid && !req_stage_packet[PW-1];
wire req_stage_marker_valid = req_stage_valid && req_stage_packet[PW-1];
wire rq_scene = req_stage_marker_valid;
wire [16:0] rq_alpha = req_stage_data[ALPHA_LSB +: 17];
wire [3:0] rq_be = req_stage_data[BE_LSB +: 4];
wire [31:0] rq_color = req_stage_data[COLOR_LSB +: 32];
wire rq_ztest = req_stage_data[ZTEST_BIT];
wire [1:0] rq_ztst = req_stage_data[ZTST_LSB +: 2];
wire rq_zmsk = req_stage_data[ZMSK_BIT];
wire [15:0] rq_zq = req_stage_data[ZQ_LSB +: 16];
wire [11:0] rq_y = {{(12-YW){1'b0}}, req_stage_data[Y_LSB +: YW]};
wire [11:0] rq_x = {{(12-XW){1'b0}}, req_stage_data[0 +: XW]};
`ifndef SYNTHESIS
initial begin
if (XW > 12 || YW > 12)
$fatal(1, "gs_lpddr_zc_emit framebuffer dimensions exceed 12-bit fragment coordinates");
end
`endif
// ---------------- Z RMW (axi_clk) ----------------
logic z_fvalid, z_fready, z_pvalid, z_pready, z_ppass, z_sflush, z_drained;
logic [11:0] z_px, z_py; logic [15:0] z_pzq;
gs_lpddr_z_rmw #(.ZBASE(ZBASE), .FB_PXW(FB_PXW), .FB_H(FB_H), .Z_CLEAR(Z_CLEAR)) u_z (
.clk(axi_clk), .rst_n(axi_rst_n), .enable(enable), .clear_start(clear_start), .clear_done(clear_done),
.scene_flush(z_sflush), .z_drained(z_drained),
.f_valid(z_fvalid), .f_ready(z_fready), .f_x(rq_x), .f_y(rq_y), .f_z({16'd0, rq_zq}),
.f_zmsk(rq_zmsk || !rq_ztest), .f_ztst(rq_ztest ? rq_ztst : 2'd1),
.p_valid(z_pvalid), .p_ready(z_pready), .p_pass(z_ppass), .p_x(z_px), .p_y(z_py), .p_zq(z_pzq),
.araddr(z_araddr), .arlen(z_arlen), .arsize(z_arsize), .arburst(z_arburst), .arvalid(z_arvalid), .arready(z_arready),
.rdata(z_rdata), .rresp(z_rresp), .rlast(z_rlast), .rvalid(z_rvalid), .rready(z_rready),
.awaddr(z_awaddr), .awlen(z_awlen), .awsize(z_awsize), .awburst(z_awburst), .awvalid(z_awvalid), .awready(z_awready),
.wdata(z_wdata), .wstrb(z_wstrb), .wlast(z_wlast), .wvalid(z_wvalid), .wready(z_wready),
.bvalid(z_bvalid), .bready(z_bready), .bresp(z_bresp),
.beats_read(z_beats_read), .beats_written(z_beats_written), .bresp_err(bresp_err), .idle(z_idle)
);
// A fragment carries ztest: when ztest=0 it must ALWAYS pass. Feed the RMW a zmsk so it never writes Z for a
// non-Z fragment, and force its zq to max so GEQUAL always passes. (All scheduler draws are ztest=1.)
// (Handled at feed below via the always-pass override on the pass decision.)
// ---------------- color-align FIFO (in-order {coladdr,color,ztest}) ----------------
localparam int CAW = 86; // {ztest, be[3:0], alpha[16:0], coladdr[31:0], color[31:0]}
localparam int CAPTR_W = $clog2(COL_DEPTH);
logic [CAW-1:0] ca_mem [0:COL_DEPTH-1];
logic [CAPTR_W:0] ca_head, ca_tail;
logic ca_wr_q;
logic [CAPTR_W-1:0] ca_waddr_q;
logic [CAW-1:0] ca_wdata_q;
wire ca_empty = (ca_head==ca_tail);
// Power-of-two ring full test: same low address with opposite wrap bit.
// This is equivalent to (tail-head)==COL_DEPTH while occupancy is bounded,
// but removes the pointer subtract/carry chain from feed_ok -> ca_mem write
// enable (the post-scanout fit's final -0.007 ns, two-endpoint family).
wire ca_full = (ca_tail[CAPTR_W] != ca_head[CAPTR_W])
&& (ca_tail[CAPTR_W-1:0] == ca_head[CAPTR_W-1:0]);
wire [31:0] frag_coladdr = COLBASE + (((rq_y*FB_PXW) + rq_x) << 2);
// Register the color-align RAM write port. Ch423 captures address/data
// every cycle instead of enabling these wide DATA registers with feed_ok.
// Their contents are observable only when the separately-reset ca_wr_q is
// high, so this removes ca_tail->ca_full->feed_ok from 82 data-register
// enables without changing the committed address/data/write-strobe tuple.
// Nonblocking ordering makes the RAM consume the prior cycle's captured
// tuple exactly when the prior feed_ok raised ca_wr_q.
always_ff @(posedge axi_clk) begin
ca_waddr_q <= ca_tail[CAPTR_W-1:0];
ca_wdata_q <= {rq_ztest, rq_be, rq_alpha, frag_coladdr, rq_color};
end
always_ff @(posedge axi_clk)
if (ca_wr_q) ca_mem[ca_waddr_q] <= ca_wdata_q;
// ---------------- color writer (reused; gs_clk tied to axi_clk => internal FIFO is same-clock) ----------------
logic col_px_emit; logic [31:0] col_px_addr; logic [31:0] col_px_pix32; logic col_flush, col_drained;
logic col_px_ready; // Ch357 (Codex) — color-writer producer-backpressure ready (elastic stage can accept)
// Ch357 (Codex) — ONE-ENTRY color output register. Registering the payload here breaks the ca_mem->barrel-insert
// cone (the -0.98ns 310MHz path) AND holds the emit valid until u_c accepts it, so no pixel is dropped.
logic col_out_valid; logic [31:0] col_out_addr; logic [31:0] col_out_color; logic [16:0] col_out_alpha; logic [3:0] col_out_be;
logic blend_in_ready, blend_out_valid, blend_idle, col_writer_idle;
logic blend_write_pending, blend_flush_issued, blend_drain_seen_low;
wire blend_input_gate = !blend_write_pending;
logic [31:0] blend_out_addr, blend_out_color;
gs_lpddr_color_blend u_blend (
.clk(axi_clk), .rst_n(axi_rst_n),
.in_valid(col_out_valid && blend_input_gate), .in_ready(blend_in_ready), .in_addr(col_out_addr), .in_color(col_out_color), .in_alpha(col_out_alpha), .in_be(col_out_be),
.out_valid(blend_out_valid), .out_ready(col_px_ready), .out_addr(blend_out_addr), .out_color(blend_out_color), .idle(blend_idle),
.araddr(d_araddr), .arlen(d_arlen), .arsize(d_arsize), .arburst(d_arburst), .arvalid(d_arvalid), .arready(d_arready),
.rdata(d_rdata), .rresp(d_rresp), .rlast(d_rlast), .rvalid(d_rvalid), .rready(d_rready)
);
logic col_commit; logic [1:0] commit_sr;
// one-shot ctrl_commit toggle after reset to latch fb_base=COLBASE
always_ff @(posedge axi_clk or negedge axi_rst_n)
if (!axi_rst_n) begin commit_sr<=2'b00; col_commit<=1'b0; end
else begin commit_sr<={commit_sr[0],1'b1}; if (commit_sr==2'b01) col_commit<=~col_commit; end
gs_lpddr_axi_master #(.FIFO_DEPTH(COL_DEPTH), .PIX_BYTES(4), .ELASTIC_BACKPRESSURE(1'b1)) u_c (
.gs_clk(axi_clk), .gs_rst_n(axi_rst_n), .enable(enable),
.arm(enable), .canary(1'b0), .fb_base(COLBASE), .ctrl_commit(col_commit),
.px_emit(col_px_emit), .px_addr(col_px_addr), .px_pix32(col_px_pix32), .px_ready(col_px_ready), .flush(col_flush),
.axi_clk(axi_clk), .axi_rst_n(axi_rst_n),
.awaddr(c_awaddr), .awlen(c_awlen), .awsize(c_awsize), .awburst(c_awburst), .awvalid(c_awvalid), .awready(c_awready),
.wdata(c_wdata), .wstrb(c_wstrb), .wlast(c_wlast), .wvalid(c_wvalid), .wready(c_wready),
.bvalid(c_bvalid), .bready(c_bready), .bresp(c_bresp),
.beats_written(c_beats_written), .bursts_issued(), .bresp_err_count(),
.fifo_overflow_count(col_ovf), .idle(col_writer_idle), .frame_drained(col_drained)
);
// Destination RMW reads must observe the preceding blended/masked write. Merely
// waiting for u_c.idle is insufficient: the elastic packer can hold a
// partial beat while the AXI-side FIFO still reports empty. Flush an
// ordered marker after every blended output and wait for col_drained to go
// low then high. That high transition is after the data beat's BRESP, so
// the next destination read sees the committed result. This deliberately
// serializes the color stream only while an ABE or partial-byte write is
// being committed.
wire blend_rmw_in_accept = col_out_valid && blend_in_ready && blend_input_gate
&& (col_out_alpha[16] || (col_out_be != 4'hF));
wire blend_write_accept = blend_out_valid && col_px_ready && blend_write_pending;
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
if (!axi_rst_n) begin
blend_write_pending <= 1'b0;
blend_flush_issued <= 1'b0;
blend_drain_seen_low <= 1'b0;
end else begin
if (blend_rmw_in_accept) begin
blend_write_pending <= 1'b1;
blend_flush_issued <= 1'b0;
blend_drain_seen_low <= 1'b0;
end else if (blend_write_accept) begin
blend_flush_issued <= 1'b1;
end else if (blend_write_pending && blend_flush_issued) begin
if (!col_drained)
blend_drain_seen_low <= 1'b1;
else if (blend_drain_seen_low) begin
blend_write_pending <= 1'b0;
blend_flush_issued <= 1'b0;
blend_drain_seen_low <= 1'b0;
end
end
end
end
// ---------------- control FSM (axi_clk): feed Z, emit color on pass, drain on scene marker ----------------
typedef enum logic [1:0] { C_RUN, C_MARK_WAIT, C_DRAIN } cst_t; cst_t cst;
// POP only when the head stage and the synchronous-read pipeline are empty;
// CONSUME the staged fragment via feed_ok. req_rd remains a registered
// pulse, isolated from feed_ok and the color-align control cones.
wire req_read_accept = req_rd && !req_rempty;
// feed the STAGED fragment into the Z RMW (and push its color) when it is a fragment and both can accept
wire feed_ok = (cst==C_RUN) && req_stage_frag_valid && z_fready && !ca_full;
// consume the STAGED end-of-scene marker once all fed fragments have drained (see C_MARK_WAIT)
wire marker_consume = (cst==C_MARK_WAIT) && req_stage_marker_valid && ca_empty && z_idle && !col_out_valid && blend_idle && !blend_write_pending;
assign z_fvalid = feed_ok;
// Ch357 (Codex) — PRODUCER BACKPRESSURE via a ONE-ENTRY output register (col_out_*). Consume a Z result only when the
// output register can accept the color it would emit: z_pready = !col_out_valid || col_px_ready (empty, OR draining
// this cycle -> simultaneous drain/refill). ca_head pops on z_pvalid && z_pready (pass OR reject; a reject consumes its
// CA entry WITHOUT loading the output register). col_px_* are driven from the REGISTER (not combinationally from
// ca_mem), which breaks the ca_mem->u_c barrel-insert cone (the -0.98ns 310MHz path) AND holds the payload valid until
// u_c accepts it (px_ready), so no pixel is dropped. No comb loop: col_px_ready depends only on u_c's registered stage.
assign z_pready = !col_out_valid || (blend_in_ready && blend_input_gate);
wire [CAW-1:0] ca_headword = ca_mem[ca_head[CAPTR_W-1:0]];
wire ca_head_zt = ca_headword[85];
wire [3:0] ca_head_be = ca_headword[84:81];
wire [16:0] ca_head_alpha= ca_headword[80:64];
wire [31:0] ca_head_addr = ca_headword[63:32];
wire [31:0] ca_head_col = ca_headword[31:0];
wire z_consume = z_pvalid && z_pready; // pop the color-align head this cycle
wire z_emit_pass = z_consume && (z_ppass || !ca_head_zt); // load the output register (Z pass or non-Z frag)
assign col_px_emit = blend_out_valid;
assign col_px_addr = blend_out_addr;
assign col_px_pix32 = blend_out_color;
always_ff @(posedge axi_clk or negedge axi_rst_n) begin
if (!axi_rst_n) begin
cst<=C_RUN; ca_head<=0; ca_tail<=0;
ca_wr_q<=1'b0;
col_flush<=0; z_sflush<=0; frame_drained<=0;
col_out_valid<=0; col_out_addr<=0; col_out_color<=0; col_out_alpha<=0; col_out_be<=4'hF;
req_stage_valid<=1'b0;
req_rd<=1'b0; req_read_pending<=1'b0;
end else begin
col_flush<=blend_write_accept;
ca_wr_q<=1'b0;
// u_req has a registered read port. Allow one accepted pop, wait
// one cycle for its RAM output, then fill the request-head stage.
// The pending gate prevents a second pop while that word is in
// flight but req_stage_valid has not asserted yet.
req_rd <= !req_stage_valid && !req_read_pending && !req_rempty && !req_rd;
req_read_pending <= req_read_accept;
// Capture one cycle after the synchronous FIFO pop. Capture needs
// an empty stage; consume needs a full one, so they remain mutually
// exclusive.
if (req_read_pending) begin
req_stage_valid <= 1'b1;
end
if (feed_ok || marker_consume) req_stage_valid <= 1'b0;
// push color-align entry as we feed a fragment
if (feed_ok) begin
ca_wr_q <= 1'b1;
ca_tail <= ca_tail + 1'b1;
end
// ONE-ENTRY color output register. DRAIN when u_c accepts (px_ready); may be re-loaded the same cycle (refill
// below) for simultaneous drain/refill. A consumed Z result (z_consume = z_pvalid && z_pready) pops the
// color-align head (pass OR reject); a PASS additionally loads the register (a reject writes no color).
if (col_out_valid && blend_in_ready && blend_input_gate) col_out_valid <= 1'b0;
if (z_consume) begin
ca_head <= ca_head + 1'b1;
if (z_emit_pass) begin
col_out_valid <= 1'b1;
col_out_addr <= ca_head_addr;
col_out_color <= ca_head_col;
col_out_alpha <= ca_head_alpha;
col_out_be <= ca_head_be;
end
end
case (cst)
C_RUN: if (req_stage_marker_valid) cst<=C_MARK_WAIT; // staged marker: stop feeding, wait in-flight to drain
C_MARK_WAIT: begin
// all fed fragments have resulted (CA empty), Z idle, AND the output register drained to u_c
if (ca_empty && z_idle && !col_out_valid && blend_idle && !blend_write_pending) begin
col_flush<=1'b1; // push color partial beat + EOF marker
z_sflush <=1'b1; // flush the dirty Z line (cache persists)
// req_rd pops the marker this cycle (see assign)
cst<=C_DRAIN;
end
end
C_DRAIN: begin
z_sflush<=1'b1; // hold until z_drained
if (col_drained && z_drained) begin
frame_drained<=1'b1; z_sflush<=1'b0; cst<=C_RUN;
end
end
default: cst<=C_RUN;
endcase
// frame_drained is a per-scene ack; drop it once new fragments flow again (feed_ok => a fresh fragment consumed)
if (feed_ok) frame_drained<=1'b0;
end
end
assign idle = (cst==C_RUN) && req_rempty && !req_stage_valid && ca_empty && z_idle && !col_out_valid && blend_idle && !blend_write_pending;
endmodule : gs_lpddr_zc_emit
+13 -1
View File
@@ -48,7 +48,9 @@ module gs_persp_uv #(
input logic [Q_W-1:0] q,
output logic out_valid,
output logic [TEXEL_W-1:0] u,
output logic [TEXEL_W-1:0] v
output logic [TEXEL_W-1:0] v,
output logic [3:0] u_frac,
output logic [3:0] v_frac
);
localparam int RLAT = 3; // gs_reciprocal_stub latency
@@ -96,13 +98,23 @@ module gs_persp_uv #(
out_valid <= 1'b0;
u <= '0;
v <= '0;
u_frac <= '0;
v_frac <= '0;
end else begin
logic [PROD_W-1:0] u_prod, v_prod;
logic [PROD_W-1:0] u_fixed4, v_fixed4;
out_valid <= recip_valid;
u_prod = uq_pipe[RLAT-1] * w_recip;
v_prod = vq_pipe[RLAT-1] * w_recip;
u <= clamp_texel(u_prod);
v <= clamp_texel(v_prod);
// Preserve four fractional texel bits for the palette-bilinear
// path. The legacy integer outputs remain the exact SCALE-bit
// truncation above; these are simply the next four product bits.
u_fixed4 = u_prod >> (SCALE - 4);
v_fixed4 = v_prod >> (SCALE - 4);
u_frac <= u_fixed4[3:0];
v_frac <= v_fixed4[3:0];
end
end
+38 -16
View File
@@ -24,6 +24,12 @@
// [7 + 9*i + 3..5] : tri i vtx1 RGBAQ/UV/XYZ2
// [7 + 9*i + 6..8] : tri i vtx2 RGBAQ/UV/XYZ2
//
// Ch402 optional clamp header (word0[34]=1), backward-compatible:
// [1..5] : FRAME,ALPHA,TEST,ZBUF,TEX0 as above
// [6] : CLAMP_1 data
// [7] : PRIM data
// [8 + 9*i ...] : triangle records
//
// One `start` pulse plays the whole list; `done` pulses when it finishes.
// Boring on purpose: 2 cycles per emitted register (present addr, then drive).
@@ -56,10 +62,15 @@ module gs_prim_list_feeder #(
localparam logic [7:0] REG_RGBAQ = 8'h01;
localparam logic [7:0] REG_ST = 8'h02; // Ch342 — perspective ST (S/T) for FST=0 tris
localparam logic [7:0] REG_UV = 8'h03;
// GS per-vertex FOG — XYZF2 vertex commit (reg 0x04). When the record's
// PRIM has FGE=1, the completing vertex is tagged XYZF2 so gs_stub decodes
// the fog byte (F=[63:56]) + 24-bit Z (Z=[55:32]); FGE=0 keeps XYZ2 (0x05).
localparam logic [7:0] REG_XYZF2 = 8'h04;
localparam logic [7:0] REG_XYZ2 = 8'h05;
localparam logic [7:0] REG_TEX0_1 = 8'h06;
localparam logic [7:0] REG_ALPHA_1 = 8'h42;
localparam logic [7:0] REG_TEST_1 = 8'h47;
localparam logic [7:0] REG_CLAMP_1 = 8'h48;
localparam logic [7:0] REG_FRAME_1 = 8'h4C;
localparam logic [7:0] REG_ZBUF_1 = 8'h4E;
@@ -70,7 +81,7 @@ module gs_prim_list_feeder #(
// Header registers (loaded once).
logic [15:0] tri_count;
logic [63:0] hdr_q [0:5]; // [0]=FRAME [1]=ALPHA [2]=TEST [3]=ZBUF [4]=TEX0 [5]=PRIM
logic [63:0] hdr_q [0:6]; // legacy q[5]=PRIM; extended q[5]=CLAMP, q[6]=PRIM
// setup-emit index -> GIF reg num (iverilog-12: no unpacked localparam array).
function automatic logic [7:0] hdr_reg_num(input logic [2:0] i);
unique case (i)
@@ -78,15 +89,16 @@ module gs_prim_list_feeder #(
3'd1: hdr_reg_num = REG_ALPHA_1;
3'd2: hdr_reg_num = REG_TEST_1;
3'd3: hdr_reg_num = REG_ZBUF_1;
default: hdr_reg_num = REG_TEX0_1;
3'd4: hdr_reg_num = REG_TEX0_1;
default: hdr_reg_num = REG_CLAMP_1;
endcase
endfunction
typedef enum logic [3:0] {
S_IDLE,
S_HDR_RD, S_HDR_LD, // read words 0..6 into tri_count/rect_count + hdr_q
S_SETUP, // emit FRAME/ALPHA/TEST/ZBUF/TEX0 from hdr_q
S_PRIM, // emit PRIM (hdr_q[5]) for the current tri
S_HDR_RD, S_HDR_LD, // read legacy words 0..6 or extended words 0..7
S_SETUP, // emit FRAME/ALPHA/TEST/ZBUF/TEX0[/CLAMP] from hdr_q
S_PRIM, // emit the selected legacy/extended PRIM word
S_VTX_RD, S_VTX_EMIT, // walk the 9 vertex words of the current tri
S_AFTER_TRIS, // Ch334 — tris done; start rects if any, else done
S_RECT_RD, S_RECT_LD, // Ch334 — read a rect's 3 words (color, corner0, corner1)
@@ -97,8 +109,8 @@ module gs_prim_list_feeder #(
localparam int WORDS_PER_RECT = 3; // Ch334 — color + corner0(XYZ2) + corner1(XYZ2)
state_t state;
logic [3:0] hdr_i; // 0..6 header-word read index
logic [2:0] setup_i; // 0..4 setup-emit index
logic [3:0] hdr_i; // 0..6 legacy or 0..7 extended header-word index
logic [2:0] setup_i; // 0..4 legacy or 0..5 extended setup-emit index
logic [15:0] tri_idx; // 0..tri_count-1
logic [3:0] vtx_word; // 0..8 within a tri
@@ -121,13 +133,22 @@ module gs_prim_list_feeder #(
// it); rects forced off. Same shared-state setup (FRAME/ALPHA/TEST/ZBUF/TEX0/PRIM). Narrow grammar:
// PSMCT32 dest+tex, UV affine, ABE source-over, TCC texel alpha — the Ch344-proven subset.
logic sprite_mode;
logic clamp_header_mode;
wire [3:0] header_last = clamp_header_mode ? 4'd7 : 4'd6;
wire [STG_ADDR_W-1:0] tris_base = clamp_header_mode ? STG_ADDR_W'(8) : STG_ADDR_W'(OFF_TRIS);
wire [63:0] prim_q = clamp_header_mode ? hdr_q[6] : hdr_q[5];
wire [4:0] words_per_prim = sprite_mode ? 5'd6 : 5'd9; // staging words per primitive
wire [3:0] last_vtx_word = sprite_mode ? 4'd5 : 4'd8; // final XYZ2 of the primitive (the kick)
// GS per-vertex FOG — the record's PRIM.FGE (bit 5) selects the vertex
// commit register: FGE=1 → XYZF2 (0x04, carries the per-vertex fog byte),
// FGE=0 → XYZ2 (0x05) exactly as before. prim_q flows to gs_stub with its
// FGE bit intact (S_PRIM emit, unmasked), so gs_stub's ras_fge tracks it.
wire prim_fge = prim_q[5];
logic [7:0] vtx_reg_num;
always_comb unique case (vtx_word % 3)
2'd0: vtx_reg_num = REG_RGBAQ;
2'd1: vtx_reg_num = perspective_mode ? REG_ST : REG_UV;
default: vtx_reg_num = REG_XYZ2;
default: vtx_reg_num = prim_fge ? REG_XYZF2 : REG_XYZ2;
endcase
wire vtx_completing = (vtx_word == last_vtx_word); // final XYZ2 = the FIFO push / kick
@@ -149,7 +170,7 @@ module gs_prim_list_feeder #(
default: rect_reg = REG_XYZ2; // 3,6,9,13,16,19
endcase
unique case (rect_emit)
5'd0, 5'd10: rect_dat = hdr_q[5]; // PRIM
5'd0, 5'd10: rect_dat = prim_q; // PRIM
5'd1,5'd4,5'd7,5'd11,5'd14,5'd17: rect_dat = rect_color; // RGBAQ
5'd2,5'd5,5'd8,5'd12,5'd15,5'd18: rect_dat = 64'd0; // UV (uniform texture)
5'd3: rect_dat = mk_xyz2(rx0, ry0, rz); // tri1 v0
@@ -166,13 +187,13 @@ module gs_prim_list_feeder #(
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE; tri_count <= 0; hdr_i <= 0; setup_i <= 0; perspective_mode <= 1'b0; sprite_mode <= 1'b0;
state <= S_IDLE; tri_count <= 0; hdr_i <= 0; setup_i <= 0; perspective_mode <= 1'b0; sprite_mode <= 1'b0; clamp_header_mode <= 1'b0;
tri_idx <= 0; vtx_word <= 0; stg_rd_addr <= '0;
gif_reg_wr_en <= 1'b0; gif_reg_num <= 8'd0; gif_reg_data <= 64'd0; done <= 1'b0;
records_emitted <= 16'd0; fifo_wait_cycles <= 32'd0;
rect_count <= 0; rect_idx <= 0; rect_word <= 0; rect_emit <= 0;
rect_color <= 64'd0; rect_c0 <= 64'd0; rect_c1 <= 64'd0;
for (int k=0;k<6;k++) hdr_q[k] <= 64'd0;
for (int k=0;k<7;k++) hdr_q[k] <= 64'd0;
end else begin
gif_reg_wr_en <= 1'b0;
done <= 1'b0;
@@ -192,11 +213,12 @@ module gs_prim_list_feeder #(
// (force rect_count 0 so the rect-expansion path can never run in this format).
perspective_mode <= stg_rd_data[32];
sprite_mode <= stg_rd_data[33]; // Ch345a
clamp_header_mode <= stg_rd_data[34]; // Ch402 optional CLAMP_1 header
// rects disallowed with the perspective OR sprite format.
rect_count <= (stg_rd_data[32] || stg_rd_data[33]) ? 16'd0 : stg_rd_data[31:16];
end
else hdr_q[hdr_i-4'd1] <= stg_rd_data;
if (hdr_i == 4'd6) begin // all of count + hdr_q[0..5] loaded
if (hdr_i == header_last) begin // legacy count+6 words, or extended count+7
setup_i <= 3'd0; state <= S_SETUP;
end else begin
hdr_i <= hdr_i + 4'd1;
@@ -210,7 +232,7 @@ module gs_prim_list_feeder #(
gif_reg_wr_en <= 1'b1;
gif_reg_num <= hdr_reg_num(setup_i);
gif_reg_data <= hdr_q[setup_i];
if (setup_i == 3'd4) begin
if (setup_i == (clamp_header_mode ? 3'd5 : 3'd4)) begin
tri_idx <= 16'd0;
state <= (tri_count == 16'd0) ? S_AFTER_TRIS : S_PRIM;
end else begin
@@ -220,9 +242,9 @@ module gs_prim_list_feeder #(
// ---- per triangle: PRIM, then 9 vertex words ----
S_PRIM: begin
gif_reg_wr_en <= 1'b1; gif_reg_num <= REG_PRIM; gif_reg_data <= hdr_q[5];
gif_reg_wr_en <= 1'b1; gif_reg_num <= REG_PRIM; gif_reg_data <= prim_q;
vtx_word <= 4'd0;
stg_rd_addr <= STG_ADDR_W'(OFF_TRIS) + STG_ADDR_W'(tri_idx * words_per_prim);
stg_rd_addr <= tris_base + STG_ADDR_W'(tri_idx * words_per_prim);
state <= S_VTX_RD;
end
S_VTX_RD: state <= S_VTX_EMIT; // vert-word addr presented; data next cycle
@@ -249,7 +271,7 @@ module gs_prim_list_feeder #(
S_AFTER_TRIS: begin
if (rect_count != 16'd0) begin
rect_idx <= 16'd0; rect_word <= 2'd0;
stg_rd_addr <= STG_ADDR_W'(OFF_TRIS) + STG_ADDR_W'(tri_count * words_per_prim);
stg_rd_addr <= tris_base + STG_ADDR_W'(tri_count * words_per_prim);
state <= S_RECT_RD;
end else state <= S_DONE;
end
+938 -214
View File
File diff suppressed because it is too large Load Diff
+25 -3
View File
@@ -88,13 +88,22 @@ module gs_texture_cache #(
(* ramstyle = "M20K" *) logic [31:0] tex_mem [0:TEX_WORDS-1];
// ================= fill side (axi_clk) =================
typedef enum logic [2:0] { F_IDLE, F_AR, F_R, F_DRAIN, F_DONE } fstate_t;
typedef enum logic [2:0] { F_IDLE, F_AR, F_R, F_DRAIN, F_WRITE, F_DONE } fstate_t;
fstate_t fst;
logic [$clog2(N_BEATS):0] beat; // 0..N_BEATS
logic [255:0] fill_data_q;
logic [2:0] fill_lane;
logic [WIDX_BITS-1:0] fill_word_base;
wire [WIDX_BITS-1:0] fill_word_idx = fill_word_base + WIDX_BITS'(fill_lane);
// Ch358 (Codex) — registered RESOLVED drain word + RAM index. The 26.1 STA showed fill_lane launching into
// tex_mem's data port: the dynamic 256->32 mux (fill_data_q[fill_lane*32+:32]) fed the RAM write directly.
// F_DRAIN now only registers the SELECTED word/index; F_WRITE commits that register to tex_mem next cycle
// (2 cycles/lane — harmless one-shot fill time). fill_crc accumulates the COMMITTED word, semantics unchanged.
// These payload registers are deliberately unreset. F_WRITE is reachable only after F_DRAIN has loaded both,
// so reset values are unobservable; keeping them out of the 4k-fanout EMIF calibration reset removes that reset
// from the duplicated RAM-address launch registers at 310 MHz.
logic [31:0] drain_word_q;
logic [WIDX_BITS-1:0] drain_idx_q;
// fill_start is an EDGE/TOGGLE (bridge toggles it on each arm), CDC-synced here so the
// cache is RE-FILLABLE: each arm reloads the texture (lets the HPS re-stage a different
// texture without a board reset). 3-FF sync + edge-detect, like the read/write probes.
@@ -142,8 +151,12 @@ module gs_texture_cache #(
end
end
F_DRAIN: begin
tex_mem[fill_word_idx] <= fill_data_q[fill_lane*32 +: 32];
fill_crc <= fill_crc + fill_data_q[fill_lane*32 +: 32]; // sum32 over the words written
fst <= F_WRITE;
end
F_WRITE: begin
// COMMIT half: registered word -> M20K; CRC over the word actually committed.
tex_mem[drain_idx_q] <= drain_word_q;
fill_crc <= fill_crc + drain_word_q; // sum32 over the words written
if (fill_lane == 3'd7) begin
fill_beats <= fill_beats + 32'd1;
fill_bytes <= fill_bytes + 32'd32;
@@ -158,6 +171,7 @@ module gs_texture_cache #(
end
end else begin
fill_lane <= fill_lane + 3'd1;
fst <= F_DRAIN;
end
end
F_DONE: begin
@@ -180,6 +194,14 @@ module gs_texture_cache #(
end
end
// Control-free payload boundary: observability is controlled by the reset FSM, not by reset on the data itself.
always_ff @(posedge axi_clk) begin
if (fst == F_DRAIN) begin
drain_word_q <= fill_data_q[fill_lane*32 +: 32];
drain_idx_q <= fill_word_idx;
end
end
// ================= sampler side (sample_clk) =================
// 1-cycle REGISTERED read, identical timing to vram_bram_stub.read2:
// present (tex_rd_addr) when tex_rd_en, data lands next cycle.
+26 -21
View File
@@ -502,9 +502,10 @@ module gs_texture_unit #(
// beat[k]'s 32-bit ABGR word -> capture into tap[k].
// If k<3: k++ and -> ISSUE (next neighbor). If k==3: -> DONE.
// DONE : combinationally lerp the 4 captured taps by u_frac/v_frac
// per channel; assert out_valid for 1 cycle with tex_color;
// drop busy; -> IDLE.
// => total ~ 4*(1+L)+1 cycles per filtered sample. Throughput is NOT a
// per channel and capture the result in tex_color_hold.
// OUT : assert out_valid for 1 cycle with the registered color;
// -> IDLE.
// => total ~ 4*(1+L)+2 cycles per filtered sample. Throughput is NOT a
// goal here (a later texture-cache pass collapses the 4 reads).
//
// Neighbor table (k -> du,dv): 0->(0,0) 1->(1,0) 2->(0,1) 3->(1,1).
@@ -519,12 +520,13 @@ module gs_texture_unit #(
// then defensively clamped to 0..255.
generate
if (BILINEAR_ENABLE) begin : g_bilinear
localparam logic [1:0] BS_IDLE = 2'd0;
localparam logic [1:0] BS_ISSUE = 2'd1;
localparam logic [1:0] BS_WAIT = 2'd2;
localparam logic [1:0] BS_DONE = 2'd3;
localparam logic [2:0] BS_IDLE = 3'd0;
localparam logic [2:0] BS_ISSUE = 3'd1;
localparam logic [2:0] BS_WAIT = 3'd2;
localparam logic [2:0] BS_DONE = 3'd3;
localparam logic [2:0] BS_OUT = 3'd4;
logic [1:0] state;
logic [2:0] state;
logic [1:0] beat; // which neighbor 0..3
logic [31:0] wait_cnt; // counts RD_LATENCY
logic [31:0] tap [0:3]; // captured ABGR per neighbor
@@ -641,7 +643,13 @@ module gs_texture_unit #(
wait_cnt <= wait_cnt + 32'd1;
end
end
default: begin // BS_DONE
BS_DONE: begin
// tex_color_hold captures the finished blend on this
// edge. Keep a distinct OUT cycle so no consumer can
// see the tap->two-lerp combinational cone directly.
state <= BS_OUT;
end
default: begin // BS_OUT
state <= BS_IDLE;
end
endcase
@@ -703,18 +711,15 @@ module gs_texture_unit #(
cv_a = lerp8(top_a, bot_a, lat_vf);
end
// Ch310 — HOLD register for the filtered color. The combined-renderer
// Ch310/Ch422 — HOLD register for the filtered color. The combined-renderer
// FSM (gs_stub CB_TWAIT) may latch the result a cycle or two AFTER the
// out_valid pulse (it steps at half-rate on z_advance beats), so the
// blended ABGR must stay STABLE from out_valid until the next sample.
// tex_color is the LIVE combinational blend during DONE (so an
// out_valid-keyed caller — tb_gs_texture_bilinear — reads the fresh
// value the SAME cycle out_valid pulses, byte-identical to before) and
// the LATCHED copy afterward (so a caller that reads one+ cycles later,
// like CB_TWAIT→CB_T, still sees it). The register captures the blend
// on the clk edge that LEAVES DONE; combining "live during DONE, held
// after" gives a value stable from out_valid until the next sample
// overwrites it at its DONE.
// The register captures the blend on the clk edge that LEAVES DONE.
// Ch422 makes OUT a separate following cycle, so tex_color and
// out_valid expose only this registered value. That is the timing
// boundary between the two-stage bilinear arithmetic and downstream
// TEX0 modulation; callers already wait on out_valid or !busy.
logic [31:0] tex_color_blend;
assign tex_color_blend = {cv_a, cv_b, cv_g, cv_r};
logic [31:0] tex_color_hold;
@@ -724,9 +729,9 @@ module gs_texture_unit #(
else if (state == BS_DONE)
tex_color_hold <= tex_color_blend; // capture the just-blended value
end
// live during the DONE pulse, held (last captured) otherwise
// Registered result, held until the next sample completes.
logic [31:0] tex_color_lin;
assign tex_color_lin = (state == BS_DONE) ? tex_color_blend : tex_color_hold;
assign tex_color_lin = tex_color_hold;
// --- output mux: bilinear FSM owns the outputs for a FILTERED PSMCT32
// sample (do_lin). When do_lin=0 — non-PSMCT32 psm OR MMAG=0 NEAREST —
@@ -738,7 +743,7 @@ module gs_texture_unit #(
// tex_rd_addr is the SAME addr-gen output for both paths (the wrap
// selects beat_u/beat_v vs port u/v); the FSM just gates rd_en.
assign tex_rd_addr = near_rd_addr;
assign out_valid = do_lin ? (state == BS_DONE) : near_out_valid;
assign out_valid = do_lin ? (state == BS_OUT) : near_out_valid;
assign tex_color = do_lin ? tex_color_lin : near_color;
assign busy = do_lin && (state != BS_IDLE);
end else begin : g_nearest
+119
View File
@@ -0,0 +1,119 @@
// Runtime palette staging: one CLOCK2_50 write port and one design-clock
// copy port. The host fills all 256 entries, then flips commit once. The
// design domain copies the stable bank into clut_stub and returns a sum32.
`timescale 1ns/1ps
module clut_stage_cdc (
input logic bclk,
input logic breset_n,
input logic b_we,
input logic [7:0] b_waddr,
input logic [31:0] b_wdata,
input logic b_commit_tgl,
input logic [31:0] b_expected_crc,
input logic dclk,
input logic dreset_n,
output logic clut_wr_en,
output logic [7:0] clut_wr_idx,
output logic [31:0] clut_wr_data,
output logic busy,
output logic done_tgl,
output logic [31:0] crc
);
// 256 x 32 fits in one M20K and remains a true dual-clock staging bank
// on the board instead of becoming a wide register array.
(* ramstyle = "M20K" *) logic [31:0] stage_mem [0:255];
logic [7:0] rd_addr;
logic [31:0] rd_data_q;
always_ff @(posedge bclk) begin
if (b_we)
stage_mem[b_waddr] <= b_wdata;
end
// This is the read side of the small dual-clock staging RAM. The host
// must finish writes before commit; commit is the coherency boundary.
always_ff @(posedge dclk) begin
rd_data_q <= stage_mem[rd_addr];
end
(* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION FORCED" *)
logic [2:0] commit_sync;
logic commit_seen;
logic [31:0] expected_crc_q;
logic [7:0] copy_idx;
logic [31:0] crc_acc;
typedef enum logic [1:0] { S_IDLE, S_PRIME, S_COPY } state_t;
state_t state;
always_ff @(posedge dclk or negedge dreset_n) begin
if (!dreset_n) begin
commit_sync <= 3'b000;
commit_seen <= 1'b0;
expected_crc_q <= 32'd0;
rd_addr <= 8'd0;
copy_idx <= 8'd0;
crc_acc <= 32'd0;
crc <= 32'd0;
clut_wr_en <= 1'b0;
clut_wr_idx <= 8'd0;
clut_wr_data <= 32'd0;
busy <= 1'b0;
done_tgl <= 1'b0;
state <= S_IDLE;
end else begin
commit_sync <= {commit_sync[1:0], b_commit_tgl};
clut_wr_en <= 1'b0;
// Consume any commit edge observed while a copy is live. A host
// retry belongs after done, never behind the current transaction.
if (busy && (commit_sync[2] != commit_seen))
commit_seen <= commit_sync[2];
case (state)
S_IDLE: begin
// A commit that arrives during a copy is deliberately not
// consumed. The host polls busy/done before reusing bank.
if (commit_sync[2] != commit_seen) begin
commit_seen <= commit_sync[2];
expected_crc_q <= b_expected_crc;
rd_addr <= 8'd0;
copy_idx <= 8'd0;
crc_acc <= 32'd0;
busy <= 1'b1;
state <= S_PRIME;
end
end
S_PRIME: begin
// Prime the synchronous read before writing entry zero.
rd_addr <= 8'd1;
state <= S_COPY;
end
S_COPY: begin
clut_wr_en <= 1'b1;
clut_wr_idx <= copy_idx;
clut_wr_data <= rd_data_q;
crc_acc <= crc_acc + rd_data_q;
if (copy_idx == 8'hFF) begin
crc <= crc_acc + rd_data_q;
busy <= 1'b0;
done_tgl <= ~done_tgl;
state <= S_IDLE;
end else begin
copy_idx <= copy_idx + 8'd1;
rd_addr <= rd_addr + 8'd1;
end
end
default: state <= S_IDLE;
endcase
end
end
// Keep expected_crc_q live in synthesis as an explicit documentation of
// the host/design transaction contract. The bridge returns `crc` on done;
// software compares it against its expected sum before GO.
wire _unused_expected_crc = ^expected_crc_q;
endmodule : clut_stage_cdc
+84 -2
View File
@@ -308,6 +308,13 @@ module ps2_hps_bridge (
input logic [31:0] lpddr_bresp_err_i, // (folded into 0x02C status)
input logic [31:0] lpddr_fifo_ovf_i, // (folded into 0x02C status)
input logic lpddr_idle_i, // 0x02C[0] — writer idle/done
input logic clear_done_i, // 0x02C[7] — Ch357 persistent-Z preclear complete (EMIF domain; synced).
// Host MUST wait for this before the first GO (z_rmw clear_start done).
input logic [31:0] frag_drops_i, // Ch357 — persistent-Z request-FIFO dropped-fragment count (design_clk).
// 0x0EC snapshot (latched while drained/quiescent) + 0x02C[8] sticky-nonzero.
input logic frame_drained_i, // 0x02C[6] — Ch353 ordered drain ack (EMIF domain; synced). STABLE,
// unlike transient idle: asserts only after the EOF marker's last BRESP,
// so the host confirms the FULL render drained (not a mid-batch idle).
// ---- Ch319 Brick 3: LPDDR4B read-probe (HPS reads FB content back through the
// bridge — no /dev/mem). 0x03C: WRITE sets the byte address + triggers a read;
@@ -393,6 +400,18 @@ module ps2_hps_bridge (
input logic [15:0] feeder_records_i, // 0x0E4 records_emitted (async counter)
input logic [31:0] feeder_waits_i, // 0x0E8 fifo_wait_cycles (async counter)
// Ch367 -- runtime CLUT staging. HPS writes a 1 KiB bank at
// 0x200..0x5FC, stores its expected sum32 at 0x1E4, then writes bit0
// at 0x1E0 to commit. One toggle crosses to the design-clock copier.
output logic [7:0] clut_stage_waddr_o,
output logic [31:0] clut_stage_wdata_o,
output logic clut_stage_we_o,
output logic clut_commit_tgl_o,
output logic [31:0] clut_expected_crc_o,
input logic clut_busy_i,
input logic clut_done_tgl_i,
input logic [31:0] clut_crc_i,
// ---- AXI4 slave (signature matches ps2_hps_bridge_null) ----
input logic [3:0] s_axi_awid,
input logic [37:0] s_axi_awaddr,
@@ -648,6 +667,10 @@ module ps2_hps_bridge (
// is IDLE, so latch them into clk-domain snapshots WHILE the synced idle is high (data stable -> coherent).
// The write-probe error counter latches on the synced write-done edge (count stable when a write completes).
logic [1:0] lpddr_idle_sync;
logic [1:0] frame_drained_sync; // Ch353 — 2-FF sync of frame_drained_i into the bridge clock
logic [1:0] clear_done_sync; // Ch357 — 2-FF sync of clear_done_i (persistent-Z preclear done)
logic [31:0] frag_drops_snap_q; // Ch357 — clk-domain snapshot of frag_drops_i, latched while drained (quiescent)
logic frag_drops_nz_sticky_q; // Ch357 — sticky: set once any per-scene snapshot is nonzero (fail-closed drop flag)
logic [31:0] lpddr_bytes_snap_q;
logic [31:0] lpddr_bursts_snap_q;
logic [31:0] lpddr_bresp_err_snap_q;
@@ -793,7 +816,13 @@ module ps2_hps_bridge (
// by addr[37:5]==33'h08 (= 0x100/32).
// Anything else reads 0.
reg_read = 32'd0;
if (addr[37:8] == '0) begin
if (addr == 38'h00000001E0) begin
reg_read = {30'd0, clut_done_sync[2], clut_busy_sync[1]}; // CLUT_STATUS
end else if (addr == 38'h00000001E4) begin
reg_read = clut_expected_crc_q; // CLUT_EXPECTED_CRC
end else if (addr == 38'h00000001E8) begin
reg_read = clut_crc_q; // CLUT_RESULT_CRC
end else if (addr[37:8] == '0) begin
case (addr[7:2])
6'h00: reg_read = CORE_ID; // 0x000
6'h01: reg_read = ABI_VERSION; // 0x004
@@ -808,7 +837,8 @@ module ps2_hps_bridge (
6'h0A: reg_read = raster_overflow_count; // 0x028
// 0x02C LPDDR_STATUS (R): [0]idle [1]bresp_err [2]fifo_ovf [3]rd_pending [4]scan_cache_valid [5]scan_rd_err.
// Ch352 — idle via 2-FF sync; bresp_err/fifo_ovf reduced from the clk-domain snapshots (coherent).
6'h0B: reg_read = {26'd0, lpddr_scan_err_sync[1], lpddr_scan_valid_sync[1], lpddr_rd_pending, (|lpddr_fifo_ovf_snap_q), (|lpddr_bresp_err_snap_q), lpddr_idle_sync[1]};
// Ch353 — [6]frame_drained (STABLE ordered drain ack; poll THIS, not transient [0]idle).
6'h0B: reg_read = {23'd0, frag_drops_nz_sticky_q, clear_done_sync[1], frame_drained_sync[1], lpddr_scan_err_sync[1], lpddr_scan_valid_sync[1], lpddr_rd_pending, (|lpddr_fifo_ovf_snap_q), (|lpddr_bresp_err_snap_q), lpddr_idle_sync[1]};
6'h0C: reg_read = lpddr_bytes_snap_q; // 0x030 LPDDR_BYTES (Ch352 — clk-domain snapshot)
6'h0D: reg_read = lpddr_bursts_snap_q; // 0x034 LPDDR_BURSTS (Ch352 — clk-domain snapshot)
6'h0E: reg_read = lpddr_bresp_err_snap_q; // 0x038 LPDDR_BRESP_ERRS (Ch352 — clk-domain snapshot)
@@ -859,6 +889,7 @@ module ps2_hps_bridge (
6'h37: reg_read = {20'd0, feeder_addr_q}; // 0x0DC FEEDER_STG_ADDR (current write index)
6'h39: reg_read = {16'd0, feeder_records_i}; // 0x0E4 FEEDER_RECORDS (records_emitted)
6'h3A: reg_read = feeder_waits_i; // 0x0E8 FEEDER_WAITS (fifo_wait_cycles)
6'h3B: reg_read = frag_drops_snap_q; // 0x0EC LPDDR_FRAG_DROPS (Ch357 R): persistent-Z dropped-fragment snapshot (per-scene delta must be 0)
6'h18: reg_read = video_status; // 0x060 VIDEO_STATUS (Ch225)
6'h19: reg_read = hdmi_diag; // 0x064 HDMI_DIAG (Ch225)
// Ch248 — replaced the Ch226 fake DS2 with the real
@@ -950,6 +981,11 @@ module ps2_hps_bridge (
wire write_is_feeder_hi = write_in_window && (aw_addr_q[7:2] == 6'h39); // 0x0E4 high 32 -> commit {hi,lo}, addr++
wire write_is_feeder_go = write_in_window && (aw_addr_q[7:2] == 6'h3A); // 0x0E8 bit0 -> retrigger pulse
wire write_is_clut_commit = (aw_addr_q == 38'h00000001E0);
wire write_is_clut_expected= (aw_addr_q == 38'h00000001E4);
wire write_in_clut_stage = (aw_addr_q >= 38'h0000000200)
&& (aw_addr_q <= 38'h00000005FC);
// Ch223 — OSD compatibility-sink decode at 0x100..0x11F. The OSD
// block is a 32-byte window outside the first-128-byte side-effect
// region, so it gets its own `addr[37:5] == 33'h08` guard
@@ -1009,6 +1045,9 @@ module ps2_hps_bridge (
assign core_ctrl_d = wdata_lane[2:0];
assign core_pulse_we = write_accept && write_is_pulse;
assign hdmi_clr_pulse = core_pulse_we && wdata_lane[3];
assign clut_stage_we_o = write_accept && write_in_clut_stage;
assign clut_stage_waddr_o = (aw_addr_q - 38'h0000000200) >> 2;
assign clut_stage_wdata_o = wdata_lane;
// Ch222 — input-latch register file. One always_ff covers all three
// 32-bit latches; the lane-aligned `wdata_lane` (selected by
@@ -1059,6 +1098,10 @@ module ps2_hps_bridge (
tex_rd_errs_snap_q <= 32'd0;
lpddr_ctrl_commit_q <= 1'b0;
lpddr_idle_sync <= 2'b00;
frame_drained_sync <= 2'b00;
clear_done_sync <= 2'b00;
frag_drops_snap_q <= 32'd0;
frag_drops_nz_sticky_q<= 1'b0;
lpddr_bytes_snap_q <= 32'd0;
lpddr_bursts_snap_q <= 32'd0;
lpddr_bresp_err_snap_q<= 32'd0;
@@ -1089,12 +1132,22 @@ module ps2_hps_bridge (
end
// Ch352 — remaining status CDC. idle: 2-FF sync. FB-writer counters: capture WHILE idle (stable).
lpddr_idle_sync <= {lpddr_idle_sync[0], lpddr_idle_i};
frame_drained_sync <= {frame_drained_sync[0], frame_drained_i};
if (lpddr_idle_sync[1]) begin
lpddr_bytes_snap_q <= lpddr_bytes_i;
lpddr_bursts_snap_q <= lpddr_bursts_i;
lpddr_bresp_err_snap_q <= lpddr_bresp_err_i;
lpddr_fifo_ovf_snap_q <= lpddr_fifo_ovf_i;
end
// Ch357 — persistent-Z preclear ack: 2-FF sync (single-bit, EMIF domain), same as frame_drained.
clear_done_sync <= {clear_done_sync[0], clear_done_i};
// Ch357 — drop count: latch WHILE drained (fragment flow stopped -> frag_drops_i stable, coherent multi-bit
// capture, same idle-gated idea as the FB-writer counters). The sticky flag OR-accumulates any nonzero
// snapshot so a single dropped fragment latches a fail-closed bit the host reads after every scene.
if (frame_drained_sync[1]) begin
frag_drops_snap_q <= frag_drops_i;
frag_drops_nz_sticky_q <= frag_drops_nz_sticky_q | (|frag_drops_i);
end
// write-probe error count: stable when a write completes -> latch on the synced write-done edge.
if (lpddr_wr_done_sync[2] != lpddr_wr_done_sync[1])
lpddr_wr_bresp_err_snap_q <= lpddr_wr_bresp_err_i;
@@ -1178,6 +1231,35 @@ module ps2_hps_bridge (
assign feeder_stg_wdata_o = feeder_wdata_q;
assign feeder_go_tgl_o = feeder_go_tgl_q;
// The staging write port is in this bridge clock domain. Only the
// commit toggle crosses domains, after the host has written all entries.
logic clut_commit_tgl_q;
logic [31:0] clut_expected_crc_q;
logic [1:0] clut_busy_sync;
logic [2:0] clut_done_sync;
logic [31:0] clut_crc_q;
always_ff @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
clut_commit_tgl_q <= 1'b0;
clut_expected_crc_q <= 32'd0;
clut_busy_sync <= 2'b00;
clut_done_sync <= 3'b000;
clut_crc_q <= 32'd0;
end else begin
clut_busy_sync <= {clut_busy_sync[0], clut_busy_i};
clut_done_sync <= {clut_done_sync[1:0], clut_done_tgl_i};
if (clut_done_sync[2] != clut_done_sync[1])
clut_crc_q <= clut_crc_i;
if (write_accept) begin
if (write_is_clut_expected) clut_expected_crc_q <= wdata_lane;
if (write_is_clut_commit && wdata_lane[0] && !clut_busy_sync[1])
clut_commit_tgl_q <= ~clut_commit_tgl_q;
end
end
end
assign clut_commit_tgl_o = clut_commit_tgl_q;
assign clut_expected_crc_o = clut_expected_crc_q;
// Ch223 — OSD-sink register file. Same pattern as Ch222.
// Ch245 — OSD_TRIGGER added with sibling-ABI semantics: bits are
// SET by single-cycle pulses from the menu FSM and CLEARED by HPS
+1
View File
@@ -85,6 +85,7 @@ module tile_ram_cdc (
// Three stages let us compute an edge detector against the
// already-resampled bits ([2] ^ [1]), giving the wr_pulse a full
// dclk cycle of bclk_wr_index/data stability before we sample.
(* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION FORCED" *)
logic [2:0] toggle_sync;
always_ff @(posedge dclk or negedge dreset_n) begin
if (!dreset_n)
+409 -15
View File
@@ -365,6 +365,10 @@ module de25_nano_psmct32_raster_demo_top (
// bridge into an unreadable state — only the design domain
// downstream of `core_rst_n` is held.
wire bridge_core_reset_req;
// Declared before every design-domain consumer. The runtime CLUT CDC is
// instantiated above the reset synchronizer, so an inline declaration at
// the assignment site would create an implicit, undriven early net.
wire core_rst_n;
// ---- Ch229 tile broadcast (bridge CLOCK2_50 → tile_ram_cdc) ----
// Driven by `u_hps_bridge` inside USE_QSYS_TOP; tied safe (zero +
@@ -413,6 +417,35 @@ module de25_nano_psmct32_raster_demo_top (
wire feeder_stg_we_pulse_w = fdr_we_sync[2] ^ fdr_we_sync[1]; // 1 design_clk pulse per commit
wire feeder_go_pulse_w = fdr_go_sync[2] ^ fdr_go_sync[1]; // 1 design_clk pulse per retrigger
// Ch367 -- runtime CLUT residency. The bridge writes a small palette
// bank at CLOCK2_50; one commit toggle starts the bounded design-clock
// copy into the existing CLUT writer port.
wire [7:0] clut_stage_waddr_w;
wire [31:0] clut_stage_wdata_w;
wire clut_stage_we_w, clut_commit_tgl_w;
wire [31:0] clut_expected_crc_w;
wire runtime_clut_wr_en_w, runtime_clut_busy_w, runtime_clut_done_tgl_w;
wire [7:0] runtime_clut_wr_idx_w;
wire [31:0] runtime_clut_wr_data_w, runtime_clut_crc_w;
clut_stage_cdc u_runtime_clut (
.bclk (CLOCK2_50),
.breset_n (~ninit_done),
.b_we (clut_stage_we_w),
.b_waddr (clut_stage_waddr_w),
.b_wdata (clut_stage_wdata_w),
.b_commit_tgl (clut_commit_tgl_w),
.b_expected_crc(clut_expected_crc_w),
.dclk (design_clk),
.dreset_n (core_rst_n),
.clut_wr_en (runtime_clut_wr_en_w),
.clut_wr_idx (runtime_clut_wr_idx_w),
.clut_wr_data (runtime_clut_wr_data_w),
.busy (runtime_clut_busy_w),
.done_tgl (runtime_clut_done_tgl_w),
.crc (runtime_clut_crc_w)
);
// ---- Ch248 platform DS2 wired controller --------------------------
// The shared `ds2_controller` runs on sys_clk (= CLOCK2_50 = 50 MHz),
// drives CLK/CMD/ATTN to the controller, samples DATA, and surfaces
@@ -502,7 +535,39 @@ module de25_nano_psmct32_raster_demo_top (
// Ch327a — line-buffer scanout geometry (the SCALABLE display path: O(width) BRAM, not
// O(width*height) like the frame-cache). For the Ch326 PSMCT32 spill FB: 128 px * 4 B =
// 512 B/row = 16 beats. (Default = the Ch321 128x128 PSMCT16 demo: 256 B/row = 8 beats.)
`ifdef GS_TILE_SPILL
`ifdef GS_SH3_LPDDR_FB
`ifdef GS_SH3_LPDDR_FB_Z
`ifdef GS_SH3_LPDDR_FB_640
localparam int SCANOUT_LB_STRIDE = 2560; // Ch358 — persistent-Z at NATIVE 640 px * 4 B PSMCT32 (FBW=10)
localparam int SCANOUT_LB_ROW_BEATS = 80; // 2560 / 32
localparam int SCANOUT_LB_NROWS = 480; // Ch358 — native 640x480, size 0x12C000, 38400 beats/frame
`else
localparam int SCANOUT_LB_STRIDE = 1024; // Ch357 — persistent-Z strong-reject fixture: 256 px * 4 B PSMCT32
localparam int SCANOUT_LB_ROW_BEATS = 32; // 1024 / 32
localparam int SCANOUT_LB_NROWS = 210; // Ch357 — strong-reject primary (256x210), 6720 beats/frame
`endif
`elsif GS_SH3_LPDDR_FB_640
localparam int SCANOUT_LB_STRIDE = 2560; // Ch357 — NATIVE 640 px * 4 B PSMCT32 (FBW=10)
localparam int SCANOUT_LB_ROW_BEATS = 80; // 2560 / 32
localparam int SCANOUT_LB_NROWS = 480; // Ch357 — native 640x480, size 0x12C000, 38400 beats/frame
`elsif GS_SH3_LPDDR_FB_SCHED
localparam int SCANOUT_LB_STRIDE = 1536; // Ch356 — 384 px * 4 B PSMCT32 (FBW=6, N-texture scheduler union)
localparam int SCANOUT_LB_ROW_BEATS = 48; // 1536 / 32
localparam int SCANOUT_LB_NROWS = 381; // Ch356 — 3-epoch union bbox (384x381), 18288 beats/frame
`elsif GS_SH3_LPDDR_FB_MULTITEX
localparam int SCANOUT_LB_STRIDE = 1280; // Ch355 — 320 px * 4 B PSMCT32 (FBW=5, multi-texture A+B union)
localparam int SCANOUT_LB_ROW_BEATS = 40; // 1280 / 32
localparam int SCANOUT_LB_NROWS = 381; // Ch355 — A+B union bbox (320x381)
`elsif GS_SH3_LPDDR_FB_MULTI
localparam int SCANOUT_LB_STRIDE = 1024; // 256 px * 4 B PSMCT32
localparam int SCANOUT_LB_ROW_BEATS = 32;
localparam int SCANOUT_LB_NROWS = 338; // Ch354 — multi-draw union bbox (256x338)
`else
localparam int SCANOUT_LB_STRIDE = 1024; // Ch353 — 256 px * 4 B PSMCT32
localparam int SCANOUT_LB_ROW_BEATS = 32; // 1024 / 32
localparam int SCANOUT_LB_NROWS = 334; // Ch353 — single SH3 draw bounding box (256x334)
`endif
`elsif GS_TILE_SPILL
localparam int SCANOUT_LB_STRIDE = 1024; // Ch327b — 256 px * 4 B PSMCT32
localparam int SCANOUT_LB_ROW_BEATS = 32; // 1024 / 32
localparam int SCANOUT_LB_NROWS = 256;
@@ -510,6 +575,26 @@ module de25_nano_psmct32_raster_demo_top (
localparam int SCANOUT_LB_STRIDE = 256;
localparam int SCANOUT_LB_ROW_BEATS = 8;
localparam int SCANOUT_LB_NROWS = 128;
`endif
// Ch418 — full-frame SH3 profiles preserve the dump's 512-pixel
// FBW=8 source geometry while presenting it across the board's
// 640-pixel VGA/HDMI active line. Other profiles remain 1:1.
`ifdef GS_SH3_LPDDR_FB_640
localparam bit SCANOUT_LB_H_STRETCH_5_TO_4 = 1'b1;
localparam int SCANOUT_LB_V_SOURCE_START = 32;
localparam bit SCANOUT_LB_V_STRETCH_15_TO_14 = 1'b1;
localparam bit SCANOUT_LB_V_LINEAR_FILTER = 1'b0;
localparam bit SCANOUT_LB_H_LINEAR_FILTER = 1'b0;
localparam bit SCANOUT_LB_BINOMIAL_3X3_FILTER = 1'b1;
localparam int SCANOUT_LB_H_SOURCE_PIXELS = 512;
`else
localparam bit SCANOUT_LB_H_STRETCH_5_TO_4 = 1'b0;
localparam int SCANOUT_LB_V_SOURCE_START = 0;
localparam bit SCANOUT_LB_V_STRETCH_15_TO_14 = 1'b0;
localparam bit SCANOUT_LB_V_LINEAR_FILTER = 1'b0;
localparam bit SCANOUT_LB_H_LINEAR_FILTER = 1'b0;
localparam bit SCANOUT_LB_BINOMIAL_3X3_FILTER = 1'b0;
localparam int SCANOUT_LB_H_SOURCE_PIXELS = 0;
`endif
// Initialize at declaration so iverilog doesn't bring up the
// sim with an X on hps_reset_sync[1] — that X would AND into
@@ -529,7 +614,7 @@ module de25_nano_psmct32_raster_demo_top (
end
wire hps_core_reset = hps_reset_sync[1];
wire core_rst_n = rst_sync[1] & ~hps_core_reset;
assign core_rst_n = rst_sync[1] & ~hps_core_reset;
// -------------------------------------------------------------
// core_go pulse — wait GO_DELAY cycles after core_rst_n
@@ -592,6 +677,7 @@ module de25_nano_psmct32_raster_demo_top (
wire demo_flush_emit;
wire [31:0] demo_flush_addr;
wire [15:0] demo_flush_pix16;
wire [3:0] demo_flush_be;
wire [5:0] demo_flush_psm;
wire [31:0] demo_vram_raddr; // Ch320 — PCRTC scanout VRAM byte address
wire demo_pix_window; // Ch320 — high inside the displayed frame window
@@ -622,6 +708,86 @@ module de25_nano_psmct32_raster_demo_top (
wire [31:0] demo_tile_reload_color; // reloaded color for raddr (1-cyc)
wire [31:0] demo_tile_reload_z; // reloaded Z for raddr (1-cyc)
wire [2:0] demo_tile_phase; // Ch323 diag — current tile phase (event-counter source)
// Ch357 — persistent-Z ROP fragment stream (perspective path). u_demo emits per-fragment
// {x,y,z} aligned to the flush color; gs_lpddr_zc_emit (under GS_SH3_LPDDR_FB_Z) does the
// PSMZ16S clamp16 read/compare/update and emits color ONLY on Z-pass. Inert otherwise.
wire [11:0] demo_flush_x;
wire [11:0] demo_flush_y;
wire [31:0] demo_flush_z;
wire demo_flush_abe;
wire [16:0] demo_flush_alpha;
wire demo_flush_ztest;
wire [1:0] demo_flush_ztst;
wire demo_flush_zmsk;
// ============================ Ch353 render epoch (GS_SH3_LPDDR_FB) ============================
// Codex round 5: with FEEDER_AUTOSTART=0 the boot setup ITSELF transitions into C_READY, so a flush gated on a
// feeder_ready RISE alone would enqueue a FALSE end-of-scene EOF at setup — which would assert frame_drained the
// instant the host later arms the writer. Gate the flush on an in-flight render instead:
// sh3_render_inflight = set by an ACCEPTED host GO (feeder_go pulse WHILE ready)
// sh3_fb_flush = sh3_render_inflight && feeder_ready RISE (render+drain complete) -> ONE EOF per render
// sh3_render_inflight = cleared when the flush is issued
// The writer's ordered drain ack (sh3_frame_drained, EMIF domain) then HARD-gates the scanout (no timeout).
// Proven end-to-end by tb_top_psmct32_sh3_lpddr_fb_seq. Inert (flush tied 0) for every other profile.
`ifdef GS_SH3_LPDDR_FB
wire sh3_frame_drained; // driven by u_lpddr_axi .frame_drained (EMIF/axi domain)
logic sh3_fb_flush, sh3_render_inflight, sh3_ready_q;
wire sh3_ready_rise = feeder_ready_w && !sh3_ready_q;
always_ff @(posedge design_clk or negedge core_rst_n) begin
if (!core_rst_n) begin sh3_fb_flush<=1'b0; sh3_render_inflight<=1'b0; sh3_ready_q<=1'b0; end
else begin
sh3_ready_q <= feeder_ready_w;
sh3_fb_flush <= 1'b0;
if (feeder_go_pulse_w && feeder_ready_w) sh3_render_inflight <= 1'b1; // accepted GO opens the epoch
if (sh3_render_inflight && sh3_ready_rise) begin
sh3_fb_flush <= 1'b1; // exactly one EOF per render
sh3_render_inflight <= 1'b0;
end
end
end
`else
wire sh3_fb_flush = 1'b0;
wire sh3_frame_drained = 1'b0; // non-SH3 profiles: tie off (fed to the bridge diagnostic, unused there)
`endif
`ifdef GS_SH3_LPDDR_FB_Z
// ---- Ch357 persistent-Z ROP feed (design_clk) ----
// The raster flush is the fragment producer; sh3_fb_flush (proven one-per-render ordered
// scene-completion marker) rides the SAME request FIFO as the end-of-scene marker. clamp16 is
// the vendored PCSX2 PSMZ16S source-Z saturation: min(z, 0xFFFF).
wire [15:0] zc_g_zq_w = (|demo_flush_z[31:16]) ? 16'hFFFF : demo_flush_z[15:0];
wire zc_frag_v = demo_flush_emit && (demo_flush_psm == 6'h00); // PSMCT32 raster fragment
wire zc_g_valid = zc_frag_v || sh3_fb_flush;
wire zc_g_scene = sh3_fb_flush; // ordered end-of-scene marker
wire [11:0] zc_g_x = demo_flush_x;
wire [11:0] zc_g_y = demo_flush_y;
wire [15:0] zc_g_zq = zc_g_zq_w;
wire zc_g_zmsk = demo_flush_zmsk;
wire zc_g_ztest = demo_flush_ztest;
wire [1:0] zc_g_ztst = demo_flush_ztst;
wire [31:0] zc_g_color = demo_flush_color32;
wire [16:0] zc_g_alpha = demo_flush_alpha;
wire [3:0] zc_g_be = demo_flush_be;
wire zc_g_ready; // driven by zc_emit
// Fragment-drop guard: the request async FIFO SILENTLY drops when g_ready is low and the raster
// cannot stall. Count every dropped request; the bridge snapshots this (0x0EC) + a sticky-nonzero bit
// (0x02C[8]) so the HOST proves zero drops per scene (fail-closed), not just the randomized sim.
reg [31:0] zc_g_drops_q;
always_ff @(posedge design_clk or negedge core_rst_n) begin
if (!core_rst_n) zc_g_drops_q <= 32'd0;
else if (zc_g_valid && !zc_g_ready) zc_g_drops_q <= zc_g_drops_q + 32'd1;
end
wire [31:0] zc_g_drops = zc_g_drops_q;
// Z-cache invalidate once at the initial host ARM (after the host preclears color+Z, before GO 1).
// arm rises exactly once per session; the pulse invalidates gs_lpddr_z_rmw's write-back line so Z
// then persists across every scheduler epoch (only clear_start invalidates; scene markers keep it).
wire zc_clear_done;
reg arm_e1, arm_e2, arm_e3;
always_ff @(posedge emif_clk or negedge emif_reset_n) begin
if (!emif_reset_n) begin arm_e1<=1'b0; arm_e2<=1'b0; arm_e3<=1'b0; end
else begin arm_e1<=lpddr_arm_w; arm_e2<=arm_e1; arm_e3<=arm_e2; end
end
wire zc_clear_start = arm_e2 && !arm_e3; // emif-domain rising edge of arm
`endif
// Ch352 (Codex guardrail #1) — LPDDR texture-cache geometry as PROFILE PARAMETERS, not hardcoded over the
// Ch322 tritex demo. SH3 real-draw crop: full 512x512 PSMT8 (262 KiB) in LPDDR, sampled at TBP=1024
// (0x40000). Every other GS_LPDDR_TEX profile keeps the tritex 8x8 (2 KiB) geometry.
@@ -629,6 +795,10 @@ module de25_nano_psmct32_raster_demo_top (
localparam int TEXC_VRAM_BASE = 32'h0004_0000; // crop TEX_VRAM_BASE = NEW_TBP(1024)*256
localparam int TEXC_BYTES = 262144; // 512x512 PSMT8
localparam int TEXC_NBEATS = 8192; // 262144 / 32 (single-beat 32 B reads)
`elsif GS_SH3_LPDDR_FB
localparam int TEXC_VRAM_BASE = 32'h0004_0000; // Ch353 — same 512x512 PSMT8 texture as the crop profile
localparam int TEXC_BYTES = 262144;
localparam int TEXC_NBEATS = 8192;
`else
localparam int TEXC_VRAM_BASE = 2048; // Ch322 tritex 8x8 PSMCT32
localparam int TEXC_BYTES = 2048;
@@ -642,12 +812,24 @@ module de25_nano_psmct32_raster_demo_top (
// this build path. Removing it retires the Ch159+ critical
// path and frees the timing budget for a faster clock.
.STRIP_HW_DIVIDER (1'b1),
// Ch352 — SEQUENTIAL gradient divider (registered iterations, no combinational cone). This REPLACES the
// combinational divide + the multicycle/false_path SDC approaches, which all made the Quartus fitter
// grind on the divider cone for hours. The sequential divider closes timing with NO SDC exception.
// Bit-exact to `/` (tb_gs_grad_divider) so the SH3 oracle stays 96.2%. GRAD_DIV_CYCLES below is ignored.
.GRAD_SEQ_DIVIDER (1'b1),
.GRAD_DIV_CYCLES (4),
// Ch357 (Codex) — SEQUENTIAL gradient divider (registered iterations, NO combinational cone), now the board
// default. The Ch352 seq-divider "prep trap" (>18 min, never reached Place) was caused by the combinational
// signed `/` remaining UNCONDITIONALLY in the RTL alongside the sequential divider — Quartus inferred BOTH,
// and the giant LPM divide cone is what ground Place/Prep for hours (twice: the SDC multicycle on the ~1157-
// source combinational cone, then the numerator-register variant, both ground). Ch357 makes the two dividers
// MUTUALLY EXCLUSIVE via generate (gs_stub g_combdiv/g_no_combdiv): at GRAD_SEQ_DIVIDER=1 the combinational `/`
// does not exist in the netlist at all, so there is no divider cone and NO gradient SDC exception is applied.
// Ch357 FINAL (Codex, 2026-07-05, after the isolation experiment ROUTED): keep the registered-numerator
// COMBINATIONAL divide (grad_num_sel_q/grad_det_q -> grad_result_q). Ch417 widens the divide from 56/32 to
// 64/35 bits, so use GRAD_DIV_CYCLES=5: design_clk = 40 ns (25 MHz) gives a 200 ns functional window instead
// of relying on the old 160 ns window measured against the former 108 ns routed cone. The setup=5/hold=4
// multicycle is applied STA-ONLY in the SDC (never
// during quartus_fit), so the fitter keeps its known-CONVERGENT "hopeless single-cycle" behavior (places in
// seconds) while final quartus_sta fail-closes at the 200 ns window. SEQUENTIAL divider (=1) is NOT used — it
// traps this Agilex fitter even with the combinational `/` generate-stripped. Bit-exact to `/`; SH3 oracle
// unchanged (tb_gs_grad_divider + the persp board-path equivalence 252/252).
.GRAD_SEQ_DIVIDER (1'b0),
.GRAD_DIV_CYCLES (5),
// Ch163 — strip the PCRTC magnification divider. The demo
// locks MAGH=MAGV=0 so the divisor is constant 1 and the
// `hwin_rel / hmag_factor` / `vwin_rel / vmag_factor` math
@@ -901,6 +1083,52 @@ module de25_nano_psmct32_raster_demo_top (
.CLUT_CSM1_ENABLE (1'b1),
.FEEDER_ENABLE (1'b1),
.FEEDER_STG_WORDS (768) // 68 clipped tris -> ~655 staging words
`elsif GS_SH3_LPDDR_FB
// Ch353 — the SH3 real draw on an LPDDR-ONLY direct PSMCT32 framebuffer (the FULL 256x334 bounding box, NOT
// the Ch352 256x120 BRAM crop). Same feeder-driven SH3 config as GS_SH3_REAL_DRAW_DEMO (real CSM1 CLUT +
// Ch351 widened perspective + GS_LPDDR_TEX cache, GRAD_SEQ_DIVIDER=0 from the shared prefix = the known-
// fitting combinational divide), but: FB_LPDDR_ONLY suppresses the BRAM raster mirror (the flush stream
// drives the PSMCT32 LPDDR writer instead of BRAM), and FEEDER_AUTOSTART=0 so boot setup reaches C_READY
// WITHOUT a render — the host arms the writer THEN pulses GO (0x0E8), and the render-epoch flush emits exactly
// one EOF -> one frame_drained, which HARD-gates the line-buffer scanout (no timeout). VRAM stays 128 KiB /
// CBP=480 (Codex option A — the known-fitting Ch352 config; the CLUT->CBP0 BRAM-shrink is deferred). Proven
// end-to-end by tb_top_psmct32_sh3_lpddr_fb_seq (the mandatory fit gate). GS_LPDDR4B_FB + GS_LPDDR_TEX must
// ALSO be defined (the build profile sets all three).
.VRAM_BYTES (128 * 1024),
.RAM_SIZE_BYTES (32 * 1024),
.VRAM_ENABLE_READ2 (1'b0),
.PSMCT32_SWIZZLE (1'b0),
.COMBINED_TAZ (1'b0),
.TILE_LOCAL (1'b0),
.TILE_COLS (1),
.TILE_ROWS (1),
.TILE_MULTIPRIM (1'b0),
.TILE_PRIM_COUNT (1),
.TILE_FIFO_DEPTH (8),
.BIN_BUFFER_ENABLE (1'b0),
.HEARTBEAT_SPLICE_ENABLE (1'b0),
.PERSPECTIVE_CORRECT(1'b1),
.SUBPIXEL_XY (1'b1), // Ch416 — preserve native XYZ 12.4 and sample pixel centers
.PERSP_RECIP_IDX_BITS(11),
.BILINEAR_ENABLE (1'b1),
.PALETTE_BILINEAR (1'b1),
.CLUT_CSM1_ENABLE (1'b1),
.FEEDER_ENABLE (1'b1),
`ifdef GS_SH3_LPDDR_FB_Z
.FEEDER_STG_WORDS (2048), // Ch402 — character draws split into staging-safe single-draw epochs
`elsif GS_SH3_LPDDR_FB_640
.FEEDER_STG_WORDS (2048), // Ch357 — native 640x480, same runtime-staged per-epoch list (619 words)
`elsif GS_SH3_LPDDR_FB_SCHED
.FEEDER_STG_WORDS (2048), // Ch356 — runtime-staged per-epoch list (68 tris / 619 words) + headroom
`elsif GS_SH3_LPDDR_FB_MULTITEX
.FEEDER_STG_WORDS (2048), // Ch355 — runtime-staged per-scene list (68 tris / 619 words) + headroom
`elsif GS_SH3_LPDDR_FB_MULTI
.FEEDER_STG_WORDS (2048), // Ch354 — 3-draw combined list = 204 tris / 1843 words (Codex-approved)
`else
.FEEDER_STG_WORDS (768), // Ch353 — single draw = 68 tris / ~655 words
`endif
.FEEDER_AUTOSTART (1'b0), // Ch353 — no boot render; host arms writer then GOes
.FB_LPDDR_ONLY (1'b1) // Ch353 — flush -> LPDDR writer, no BRAM FB mirror
`elsif GS_TILE_LATE_DEMO
// Ch316 — LEADING-EMPTY-TILE traversal fix. ONE prim, ONLY in the LAST tile
// t15 of the 4x4 grid; tiles t0..t14 are empty and precede it. Proves the
@@ -1282,6 +1510,7 @@ module de25_nano_psmct32_raster_demo_top (
.flush_emit_o (demo_flush_emit),
.flush_addr_o (demo_flush_addr),
.flush_pix16_o(demo_flush_pix16),
.flush_be_o (demo_flush_be),
.flush_psm_o (demo_flush_psm),
.vram_read_addr_o(demo_vram_raddr), // Ch320 — PCRTC scanout addr for LPDDR4B scanout
.pix_window_o (demo_pix_window), // Ch320 — displayed-frame window gate
@@ -1293,6 +1522,15 @@ module de25_nano_psmct32_raster_demo_top (
.tex_cache_ready_i(demo_tex_cache_ready),
.tex_cache_hits_o(demo_tex_cache_hits),
.tex_bram_hits_o (demo_tex_bram_hits),
// Ch357 — persistent-Z ROP fragment coords + Z (perspective path), aligned to the flush color.
.flush_x_o (demo_flush_x),
.flush_y_o (demo_flush_y),
.flush_z_o (demo_flush_z),
.flush_abe_o (demo_flush_abe),
.flush_alpha_o (demo_flush_alpha),
.flush_ztest_o (demo_flush_ztest),
.flush_ztst_o (demo_flush_ztst),
.flush_zmsk_o (demo_flush_zmsk),
// Ch323 — tile color+Z spill/reload (inert outputs when TILE_SPILL_ENABLE=0).
.flush_color32_o (demo_flush_color32),
.z_flush_emit_o (demo_z_flush_emit),
@@ -1317,7 +1555,11 @@ module de25_nano_psmct32_raster_demo_top (
.feeder_go_i (feeder_go_pulse_w),
.feeder_ready_o (feeder_ready_w),
.feeder_records_o (feeder_records_w),
.feeder_waits_o (feeder_waits_w)
.feeder_waits_o (feeder_waits_w),
.runtime_clut_wr_en_i (runtime_clut_wr_en_w),
.runtime_clut_wr_idx_i (runtime_clut_wr_idx_w),
.runtime_clut_wr_data_i(runtime_clut_wr_data_w),
.runtime_clut_busy_i (runtime_clut_busy_w)
);
`ifndef GS_LPDDR_TEX
// Ch322 — no texture cache: the texel tap returns BRAM (bram-top mux constant-folds
@@ -1592,8 +1834,21 @@ module de25_nano_psmct32_raster_demo_top (
end
end
wire video_src_eff = video_src_dclk;
`ifdef GS_SH3_LPDDR_FB
// Ch353 — HARD frame_drained gate (Codex round 5, NO timeout): blank BLACK until the ordered drain ack, THEN the
// line-buffer scanout serves the LPDDR FB. frame_ready_emif gates the EMIF prefetch enable; frame_ready_r (design)
// gates the HDMI source mux so unfilled line buffers can never show before the frame is drained. Failure (no ack)
// stays black forever — no timeout fallback (a partial FB is exactly the bug this handshake prevents).
logic [2:0] sh3_frd_dsync;
always_ff @(posedge design_clk or posedge async_rst_assert) begin
if (async_rst_assert) sh3_frd_dsync <= 3'd0; else sh3_frd_dsync <= {sh3_frd_dsync[1:0], sh3_frame_drained};
end
wire frame_ready_r = sh3_frd_dsync[2];
wire frame_ready_emif = sh3_frame_drained;
`else
wire frame_ready_r = 1'b1; // other profiles: BRAM mirror is a stable fallback, no gate
wire frame_ready_emif = 1'b1;
`endif
// emif-domain coherent bundled capture (LPDDR scanout enable + reader routing below).
logic [2:0] commit_e_sync; logic video_src_emif, scanout_lb_emif;
always_ff @(posedge emif_clk or negedge emif_reset_n) begin
@@ -2052,7 +2307,10 @@ module de25_nano_psmct32_raster_demo_top (
// Ch324 Brick 3 — LPDDR scanout config: the spill profile scans the 64x64 PSMCT32 color
// FB straight out of LPDDR at COLOR_SPILL_BASE (PCRTC addresses it BRAM-relative so
// VRAM_BASE stays 0). Other LPDDR profiles keep the FB-at-0 PSMCT16 mirror.
`ifdef GS_TILE_SPILL
`ifdef GS_SH3_LPDDR_FB
localparam [29:0] SCANOUT_FB_BASE = 30'd0; // Ch353 — FB at LPDDR base 0 (host sets 0x01C=0)
localparam bit SCANOUT_PSMCT32 = 1'b1; // full-frame PSMCT32
`elsif GS_TILE_SPILL
localparam [29:0] SCANOUT_FB_BASE = COLOR_SPILL_BASE;
localparam bit SCANOUT_PSMCT32 = 1'b1;
`else
@@ -2072,11 +2330,90 @@ module de25_nano_psmct32_raster_demo_top (
wire [7:0] fbw_awlen; wire [2:0] fbw_awsize; wire fbw_awvalid, fbw_awready;
wire [255:0] fbw_wdata; wire [31:0] fbw_wstrb; wire fbw_wlast, fbw_wvalid, fbw_wready;
wire [1:0] fbw_bresp; wire fbw_bvalid, fbw_bready;
gs_lpddr_axi_master #(.FIFO_DEPTH(16)) u_lpddr_axi (
`ifdef GS_SH3_LPDDR_FB_Z
// ================= Ch357 — persistent-Z emit (gs_lpddr_zc_emit) =================
// Replaces the plain color writer with a Z-then-color emitter: Z RMW (read/clamp16-compare/
// update) FIRST, color enqueued ONLY on pass. Three AXI streams, routed onto the shared LPDDR
// through the SAME de25 arbiters proven in tb_top_psmct32_sh3_zarb:
// * color writes (c_*) -> fbw_* == gs_lpddr_wr_arb s0 (FB-writer, priority)
// * Z writes (zw_*) -> gs_lpddr_wr_arb s2 (repurposed spill port; inert in this profile)
// * Z reads (zr_*) -> gs_lpddr_rd_arb s3 (reload port; inert in this profile)
// Fixture geometry: 256x210 strong-reject (Ch357) or NATIVE 640x480 (Ch358, +GS_SH3_LPDDR_FB_640).
// Z base 0x140000 (disjoint from color: 640x480x4=0x12C000 < 0x140000; and tex: Z top 0x1D6000 < 0x200000).
`ifdef GS_SH3_LPDDR_FB_640
localparam int ZFB_PXW = 640, ZFB_H = 480;
`else
localparam int ZFB_PXW = 256, ZFB_H = 210;
`endif
wire [31:0] zw_awaddr; wire [7:0] zw_awlen; wire [2:0] zw_awsize; wire [1:0] zw_awburst;
wire zw_awvalid, zw_awready;
wire [255:0] zw_wdata; wire [31:0] zw_wstrb; wire zw_wlast, zw_wvalid, zw_wready;
wire zw_bvalid, zw_bready; wire [1:0] zw_bresp;
wire [31:0] zr_araddr; wire [7:0] zr_arlen; wire [2:0] zr_arsize; wire [1:0] zr_arburst;
wire zr_arvalid, zr_arready;
wire [255:0] zr_rdata; wire [1:0] zr_rresp; wire zr_rlast, zr_rvalid, zr_rready;
wire [31:0] dr_araddr; wire [7:0] dr_arlen; wire [2:0] dr_arsize; wire [1:0] dr_arburst;
wire dr_arvalid, dr_arready;
wire [255:0] dr_rdata; wire [1:0] dr_rresp; wire dr_rlast, dr_rvalid, dr_rready;
wire [31:0] c_awaddr; wire [7:0] c_awlen; wire [2:0] c_awsize; wire [1:0] c_awburst;
wire c_awvalid;
wire [255:0] c_wdata; wire [31:0] c_wstrb; wire c_wlast, c_wvalid;
wire c_bready;
// Request-FIFO sizing history: Ch368's adjacent-frame runtime-CLUT test needed 3610 entries (4096 passed;
// 2048 clipped). The much larger Ch439 f52 production replay peaks at 6115 entries, establishing the 8192
// production size below. zc_g_drops remains the fail-closed hardware guard.
// (History: Ch357's tb_top_psmct32_sh3_zint showed a depth-64 request FIFO drops a small transient burst
// (2 frags) when the Z-RMW stalls on a run of cache-line misses while the raster — which cannot honor
// g_ready — keeps emitting; 256 sufficed for the zsched/zs640 scenes.)
gs_lpddr_zc_emit #(.COLBASE(32'h0000_0000), .ZBASE(32'h0014_0000),
// Ch439: the complete 311-epoch f52 replay peaks at 6,115
// queued requests. 8K retains 2,077 entries (34%) of
// measured headroom while halving the deep 93-bit FIFO's
// M20K/address-tree footprint at 310 MHz.
.FB_PXW(ZFB_PXW), .FB_H(ZFB_H), .REQ_DEPTH(8192), .COL_DEPTH(128)) u_zc_emit (
.gs_clk(design_clk), .gs_rst_n(core_rst_n), .enable(1'b1),
.g_valid(zc_g_valid), .g_ready(zc_g_ready), .g_x(zc_g_x), .g_y(zc_g_y), .g_zq(zc_g_zq),
.g_zmsk(zc_g_zmsk), .g_ztest(zc_g_ztest), .g_ztst(zc_g_ztst), .g_color(zc_g_color), .g_alpha(zc_g_alpha), .g_be(zc_g_be), .g_scene(zc_g_scene),
.axi_clk(emif_clk), .axi_rst_n(emif_reset_n),
.clear_start(zc_clear_start), .clear_done(zc_clear_done), .frame_drained(sh3_frame_drained),
.z_araddr(zr_araddr), .z_arlen(zr_arlen), .z_arsize(zr_arsize), .z_arburst(zr_arburst),
.z_arvalid(zr_arvalid), .z_arready(zr_arready),
.z_rdata(zr_rdata), .z_rresp(zr_rresp), .z_rlast(zr_rlast), .z_rvalid(zr_rvalid), .z_rready(zr_rready),
.d_araddr(dr_araddr), .d_arlen(dr_arlen), .d_arsize(dr_arsize), .d_arburst(dr_arburst),
.d_arvalid(dr_arvalid), .d_arready(dr_arready),
.d_rdata(dr_rdata), .d_rresp(dr_rresp), .d_rlast(dr_rlast), .d_rvalid(dr_rvalid), .d_rready(dr_rready),
.z_awaddr(zw_awaddr), .z_awlen(zw_awlen), .z_awsize(zw_awsize), .z_awburst(zw_awburst),
.z_awvalid(zw_awvalid), .z_awready(zw_awready),
.z_wdata(zw_wdata), .z_wstrb(zw_wstrb), .z_wlast(zw_wlast), .z_wvalid(zw_wvalid), .z_wready(zw_wready),
.z_bvalid(zw_bvalid), .z_bready(zw_bready), .z_bresp(zw_bresp),
.c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst),
.c_awvalid(c_awvalid), .c_awready(fbw_awready),
.c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(fbw_wready),
.c_bvalid(fbw_bvalid), .c_bready(c_bready), .c_bresp(fbw_bresp),
.z_beats_read(), .z_beats_written(lpddr_bursts_w), .c_beats_written(lpddr_bytes_w),
.col_ovf(lpddr_fifo_ovf_w), .bresp_err(lpddr_bresp_err_w), .idle(lpddr_idle_w)
);
// Color master -> fbw_* (gs_lpddr_wr_arb s0). c_* carries no awid; the arb tags s0 internally.
assign fbw_awaddr = c_awaddr[29:0]; assign fbw_awburst = c_awburst; assign fbw_awid = 7'd0;
assign fbw_awlen = c_awlen; assign fbw_awsize = c_awsize; assign fbw_awvalid = c_awvalid;
assign fbw_wdata = c_wdata; assign fbw_wstrb = c_wstrb; assign fbw_wlast = c_wlast;
assign fbw_wvalid = c_wvalid; assign fbw_bready = c_bready;
assign emif_m_awaddr = 32'd0; assign emif_m_awid = 5'd0; // unused under _Z
`else
gs_lpddr_axi_master #(.FIFO_DEPTH(16)
`ifdef GS_SH3_LPDDR_FB
, .PIX_BYTES(4) // Ch353 — PSMCT32 = 8 px / 256-bit beat
`endif
) u_lpddr_axi (
.gs_clk(design_clk), .gs_rst_n(core_rst_n), .enable(1'b1),
.arm(lpddr_arm_w), .canary(lpddr_canary_w), .fb_base(lpddr_fb_base_w), .ctrl_commit(lpddr_ctrl_commit_w),
`ifdef GS_SH3_LPDDR_FB
.px_emit(demo_flush_emit && (demo_flush_psm == 6'h00)), // Ch353 — PSMCT32 raster emits only
.px_addr(demo_flush_addr), .px_pix32(demo_flush_color32), .flush(sh3_fb_flush),
`else
.px_emit(demo_flush_emit && (demo_flush_psm == 6'h02)),
.px_addr(demo_flush_addr), .px_pix16(demo_flush_pix16),
.px_addr(demo_flush_addr), .px_pix32({16'd0, demo_flush_pix16}), .flush(1'b0),
`endif
.axi_clk(emif_clk), .axi_rst_n(emif_reset_n),
.awaddr(emif_m_awaddr), .awlen(fbw_awlen), .awsize(fbw_awsize),
.awburst(fbw_awburst), .awid(emif_m_awid), .awvalid(fbw_awvalid),
@@ -2087,9 +2424,13 @@ module de25_nano_psmct32_raster_demo_top (
.beats_written(lpddr_bytes_w), .bursts_issued(lpddr_bursts_w),
.bresp_err_count(lpddr_bresp_err_w), .fifo_overflow_count(lpddr_fifo_ovf_w),
.idle(lpddr_idle_w)
`ifdef GS_SH3_LPDDR_FB
, .frame_drained(sh3_frame_drained) // Ch353 — ordered drain ack -> hard scanout gate
`endif
);
assign fbw_awaddr = emif_m_awaddr[29:0]; // EMIF addr is 30-bit (base 0)
assign fbw_awid = {2'b00, emif_m_awid}; // EMIF awid is 7-bit
`endif
`ifdef GS_LPDDR_TEX
// Ch322 — HPS write-probe (stage texture words) + 2:1 write arbiter (FB writer = priority).
wire [29:0] wp_awaddr; wire [1:0] wp_awburst; wire [6:0] wp_awid;
@@ -2116,10 +2457,17 @@ module de25_nano_psmct32_raster_demo_top (
.s1_awsize(wp_awsize), .s1_awvalid(wp_awvalid), .s1_awready(wp_awready),
.s1_wdata(wp_wdata), .s1_wstrb(wp_wstrb), .s1_wlast(wp_wlast), .s1_wvalid(wp_wvalid), .s1_wready(wp_wready),
.s1_bresp(wp_bresp), .s1_bvalid(wp_bvalid), .s1_bready(wp_bready),
`ifdef GS_SH3_LPDDR_FB_Z
// Ch357 — s2 = persistent-Z write stream from gs_lpddr_zc_emit (zw_*).
.s2_awaddr(zw_awaddr[29:0]), .s2_awburst(zw_awburst), .s2_awid(7'd6), .s2_awlen(zw_awlen), .s2_awsize(zw_awsize),
.s2_awvalid(zw_awvalid), .s2_awready(zw_awready), .s2_wdata(zw_wdata), .s2_wstrb(zw_wstrb), .s2_wlast(zw_wlast),
.s2_wvalid(zw_wvalid), .s2_wready(zw_wready), .s2_bresp(zw_bresp), .s2_bvalid(zw_bvalid), .s2_bready(zw_bready),
`else
// Ch323 — ports 2/3 (Z spill / HPS write-probe) unused in the GS_LPDDR_TEX build; tie inert.
.s2_awaddr(30'd0), .s2_awburst(2'b01), .s2_awid(7'd6), .s2_awlen(8'd0), .s2_awsize(3'b101),
.s2_awvalid(1'b0), .s2_awready(), .s2_wdata(256'd0), .s2_wstrb(32'd0), .s2_wlast(1'b0),
.s2_wvalid(1'b0), .s2_wready(), .s2_bresp(), .s2_bvalid(), .s2_bready(1'b1),
`endif
.s3_awaddr(30'd0), .s3_awburst(2'b01), .s3_awid(7'd5), .s3_awlen(8'd0), .s3_awsize(3'b101),
.s3_awvalid(1'b0), .s3_awready(), .s3_wdata(256'd0), .s3_wstrb(32'd0), .s3_wlast(1'b0),
.s3_wvalid(1'b0), .s3_wready(), .s3_bresp(), .s3_bvalid(), .s3_bready(1'b1),
@@ -2284,6 +2632,8 @@ module de25_nano_psmct32_raster_demo_top (
// line-buffer; other profiles keep the runtime bridge bit (default frame-cache).
`ifdef GS_TILE_SPILL
wire scanout_lb_eff = 1'b1;
`elsif GS_SH3_LPDDR_FB
wire scanout_lb_eff = 1'b1; // Ch353 — always line-buffer (a 256x334 frame-cache = 334 KiB)
`else
wire scanout_lb_eff = scanout_lb_emif; // Ch352 — emif-domain coherent capture (not raw bridge bit)
`endif
@@ -2303,7 +2653,14 @@ module de25_nano_psmct32_raster_demo_top (
);
gs_lpddr_scanout_lb #(.FB_BASE(SCANOUT_FB_BASE), .STRIDE_BYTES(SCANOUT_LB_STRIDE),
.ROW_BEATS(SCANOUT_LB_ROW_BEATS), .N_ROWS(SCANOUT_LB_NROWS),
.PSMCT32(SCANOUT_PSMCT32)) u_lpddr_scan_lb (
.PSMCT32(SCANOUT_PSMCT32),
.H_STRETCH_5_TO_4(SCANOUT_LB_H_STRETCH_5_TO_4),
.V_SOURCE_START(SCANOUT_LB_V_SOURCE_START),
.V_STRETCH_15_TO_14(SCANOUT_LB_V_STRETCH_15_TO_14),
.V_LINEAR_FILTER(SCANOUT_LB_V_LINEAR_FILTER),
.H_LINEAR_FILTER(SCANOUT_LB_H_LINEAR_FILTER),
.H_SOURCE_PIXELS(SCANOUT_LB_H_SOURCE_PIXELS),
.BINOMIAL_3X3_FILTER(SCANOUT_LB_BINOMIAL_3X3_FILTER)) u_lpddr_scan_lb (
.axi_clk(emif_clk), .axi_rst_n(emif_reset_n),
.enable(video_src_emif & scanout_lb_eff & frame_ready_emif),
.video_clk(design_clk), .frame_start(demo_video_vsync),
@@ -2354,6 +2711,9 @@ module de25_nano_psmct32_raster_demo_top (
.s3_arready(reload_ar_arready),
.s3_rdata(reload_r_rdata), .s3_rresp(reload_r_rresp), .s3_rlast(reload_r_rlast),
.s3_rvalid(reload_r_rvalid), .s3_rready(reload_r_rready),
.s4_araddr(dr_araddr[29:0]), .s4_arburst(dr_arburst), .s4_arid(7'd7),
.s4_arlen(dr_arlen), .s4_arsize(dr_arsize), .s4_arvalid(dr_arvalid), .s4_arready(dr_arready),
.s4_rdata(dr_rdata), .s4_rresp(dr_rresp), .s4_rlast(dr_rlast), .s4_rvalid(dr_rvalid), .s4_rready(dr_rready),
.m_araddr(emif_ar_araddr), .m_arburst(emif_ar_arburst), .m_arid(emif_ar_arid),
.m_arlen(emif_ar_arlen), .m_arsize(emif_ar_arsize), .m_arvalid(emif_ar_arvalid),
.m_arready(emif_ar_arready),
@@ -2413,6 +2773,14 @@ module de25_nano_psmct32_raster_demo_top (
.color_o(demo_tile_reload_color), .z_o(demo_tile_reload_z),
.reload_ready(demo_tile_reload_ready)
);
`elsif GS_SH3_LPDDR_FB_Z
// Ch357 — read-port-3 carries the persistent-Z READ stream from gs_lpddr_zc_emit (zr_*).
assign reload_ar_araddr=zr_araddr[29:0]; assign reload_ar_arburst=zr_arburst; assign reload_ar_arid=7'd6;
assign reload_ar_arlen=zr_arlen; assign reload_ar_arsize=zr_arsize; assign reload_ar_arvalid=zr_arvalid;
assign zr_arready=reload_ar_arready;
assign zr_rdata=reload_r_rdata; assign zr_rresp=reload_r_rresp; assign zr_rlast=reload_r_rlast;
assign zr_rvalid=reload_r_rvalid; assign reload_r_rready=zr_rready;
assign reload_color_beats_w=32'd0; assign reload_z_beats_w=32'd0; assign reload_rd_errs_w=32'd0;
`else
// no reload engine — tie read-port-3 inert (arvalid=0, rready=1 drains) + counters 0.
assign reload_ar_araddr=30'd0; assign reload_ar_arburst=2'b01; assign reload_ar_arid=7'd6;
@@ -2434,7 +2802,7 @@ module de25_nano_psmct32_raster_demo_top (
.gs_clk(design_clk), .gs_rst_n(core_rst_n), .enable(1'b1),
.arm(lpddr_arm_w), .canary(lpddr_canary_w), .fb_base(lpddr_fb_base_w), .ctrl_commit(lpddr_ctrl_commit_w),
.px_emit(demo_flush_emit && (demo_flush_psm == 6'h02)),
.px_addr(demo_flush_addr), .px_pix16(demo_flush_pix16),
.px_addr(demo_flush_addr), .px_pix32({16'd0, demo_flush_pix16}), .flush(1'b0),
.axi_clk(CLOCK2_50), .axi_rst_n(~ninit_done),
.awaddr(f2s_awaddr_w), .awlen(f2s_awlen_w), .awsize(f2s_awsize_w),
.awburst(f2s_awburst_w), .awid(f2s_awid_w), .awvalid(f2s_awvalid_w),
@@ -2682,6 +3050,15 @@ module de25_nano_psmct32_raster_demo_top (
.emif_hps_emif_ref_clk_0_clk (LPDDR4A_REFCLK_p)
);
// Ch357 — persistent-Z observability into the bridge (host fail-closed gates). Under GS_SH3_LPDDR_FB_Z the
// zc_emit preclear ack + drop counter; tied 0 for every other profile (the status bits read 0 / clear_done 0).
`ifdef GS_SH3_LPDDR_FB_Z
wire bridge_clear_done = zc_clear_done; // emif domain (from gs_lpddr_zc_emit) -> 0x02C[7]
wire [31:0] bridge_frag_drops = zc_g_drops; // design_clk domain -> 0x0EC snapshot + 0x02C[8] sticky
`else
wire bridge_clear_done = 1'b0;
wire [31:0] bridge_frag_drops = 32'd0;
`endif
// The minimal null AXI4 slave for the hps2fpga bridge. Same port
// signature as `splash_hps_bridge` so a future "real" ps2 bridge
// can be dropped in without re-plumbing the top.
@@ -2714,6 +3091,9 @@ module de25_nano_psmct32_raster_demo_top (
.lpddr_bresp_err_i(lpddr_bresp_err_w),
.lpddr_fifo_ovf_i (lpddr_fifo_ovf_w),
.lpddr_idle_i (lpddr_idle_w),
.frame_drained_i (sh3_frame_drained), // Ch353 — 0x02C[6] host-visible ordered drain ack (0 for non-SH3)
.clear_done_i (bridge_clear_done), // Ch357 — 0x02C[7] persistent-Z preclear done (0 for non-Z)
.frag_drops_i (bridge_frag_drops), // Ch357 — 0x0EC snapshot + 0x02C[8] sticky drop flag (0 for non-Z)
.lpddr_rd_addr_o (lpddr_rd_addr_w),
.lpddr_rd_pulse_o (lpddr_rd_pulse_w),
.lpddr_rd_data_i (lpddr_rd_data_w),
@@ -2849,7 +3229,16 @@ module de25_nano_psmct32_raster_demo_top (
.feeder_go_tgl_o (feeder_go_tgl_w),
.feeder_ready_i (feeder_ready_w),
.feeder_records_i (feeder_records_w),
.feeder_waits_i (feeder_waits_w)
.feeder_waits_i (feeder_waits_w),
.clut_stage_waddr_o (clut_stage_waddr_w),
.clut_stage_wdata_o (clut_stage_wdata_w),
.clut_stage_we_o (clut_stage_we_w),
.clut_commit_tgl_o (clut_commit_tgl_w),
.clut_expected_crc_o (clut_expected_crc_w),
.clut_busy_i (runtime_clut_busy_w),
.clut_done_tgl_i (runtime_clut_done_tgl_w),
.clut_crc_i (runtime_clut_crc_w)
);
`else
@@ -2886,6 +3275,11 @@ module de25_nano_psmct32_raster_demo_top (
assign feeder_go_tgl_w = 1'b0;
assign feeder_stg_waddr_w = 12'd0;
assign feeder_stg_wdata_w = 64'd0;
assign clut_stage_waddr_w = 8'd0;
assign clut_stage_wdata_w = 32'd0;
assign clut_stage_we_w = 1'b0;
assign clut_commit_tgl_w = 1'b0;
assign clut_expected_crc_w = 32'd0;
`endif // USE_QSYS_TOP
// Suppress unused-input warnings on lint (CLOCK0_50 / CLOCK1_50
+84 -9
View File
@@ -195,12 +195,13 @@ module top_psmct32_raster_demo_bram
// Default 0 generate-guards all perspective logic out (zero cost); only the
// GS_PERSP_DEMO board profile sets it 1.
parameter bit PERSPECTIVE_CORRECT = 1'b0,
parameter bit SUBPIXEL_XY = 1'b0,
parameter int PERSP_RECIP_IDX_BITS = 8, // Ch351 — perspective reciprocal LUT width (far-W -> 11)
parameter int GRAD_DIV_CYCLES = 1, // Ch352 — triangle-setup divide settle cycles (board fits -> 4)
parameter bit GRAD_SEQ_DIVIDER = 1'b0, // Ch352 — sequential gradient divider (board fits -> 1)
// Ch344 — TEXTURED + source-over ALPHA SPRITE path (forwarded to gs_stub). Default 0 -> byte-identical.
parameter bit SPRITE_TEX_ALPHA = 1'b0,
parameter bit SPRITE_TEX_ALPHA_CLUT = 1'b0, // Ch347 — admit PSMT8 (CLUT) textures into the alpha-sprite path
parameter bit SPRITE_TEX_ALPHA_CLUT = 1'b0, // Ch408 — admit PSMT8/PSMT4 CLUT textures into alpha sprites
parameter bit CLUT_CSM1_ENABLE = 1'b0, // Ch350 — CSM1 16x16 CT32 grid CLUT load (SH3 indexed env path)
// Ch302 — COMBINED textured+alpha+depth probe (forwarded to gs_stub). When 1,
// a TME+ABE+ZTE triangle runs the multi-beat per-pixel FSM (Zread->Ztest->
@@ -237,6 +238,11 @@ module top_psmct32_raster_demo_bram
// dead logic, byte-identical for every existing profile.
parameter bit FEEDER_ENABLE = 1'b0,
parameter int FEEDER_STG_WORDS = 256,
// Ch353 — auto-start the FIRST feeder render at C_SETUP (default, byte-identical). Set 0 ONLY for
// the GS_SH3_LPDDR_FB profile: the disarmed boot render would otherwise leave a pending EOF that
// falsely asserts frame_drained the instant the host later arms the writer. With AUTOSTART=0,
// C_SETUP transitions straight to C_READY (no render) and every render waits for an explicit GO.
parameter bit FEEDER_AUTOSTART = 1'b1,
// Ch315 — primitive FIFO / per-tile bin depth (capacity). Power-of-2, default
// 4 = byte-identical. GS_TILE_CAP_DEMO sets 8 to scale capacity past the old 4.
parameter int TILE_FIFO_DEPTH = 4,
@@ -366,7 +372,16 @@ module top_psmct32_raster_demo_bram
output logic [31:0] flush_addr_o, // linear FB byte address
output logic [15:0] flush_pix16_o,
output logic [31:0] flush_color32_o, // Ch323 — full 32-bit flushed color (for spill round-trip capture)
output logic [3:0] flush_be_o, // FRAME.FBMSK-compressed byte enables
output logic [5:0] flush_psm_o,
output logic [11:0] flush_x_o, // Ch357 — fragment x/y/z for the external LPDDR-Z ROP (perspective path)
output logic [11:0] flush_y_o,
output logic [31:0] flush_z_o,
output logic flush_abe_o,
output logic [16:0] flush_alpha_o,
output logic flush_ztest_o,
output logic [1:0] flush_ztst_o,
output logic flush_zmsk_o,
// Ch323 — tile Z-FLUSH stream (TILE_SPILL_ENABLE; the de25 routes it to an LPDDR Z-backing
// writer). z_flush_addr_o is Z-backing-relative (pixel_index*4, 32-bit Z). Inert/0 unless
// spilling; unused (left open) by every existing consumer.
@@ -416,7 +431,15 @@ module top_psmct32_raster_demo_bram
input logic feeder_go_i, // retrigger pulse (honoured only in C_READY)
output logic feeder_ready_o, // control FSM is in C_READY (a new list may start)
output logic [15:0] feeder_records_o, // primitives emitted by the current list
output logic [31:0] feeder_waits_o // cycles the feeder paused under fifo_full
output logic [31:0] feeder_waits_o, // cycles the feeder paused under fifo_full
// Ch367 -- runtime palette residency. The design-clock staging copier
// owns this writer while busy; the legacy TEX0/VRAM loader remains the
// sole writer at every other time.
input logic runtime_clut_wr_en_i,
input logic [7:0] runtime_clut_wr_idx_i,
input logic [31:0] runtime_clut_wr_data_i,
input logic runtime_clut_busy_i
);
localparam int RAM_ADDR_W = $clog2(RAM_SIZE_BYTES);
@@ -805,7 +828,12 @@ module top_psmct32_raster_demo_bram
fdr_start <= 1'b0;
unique case (cst)
C_SETUP: if (dma_done_seen && !xfer_busy) begin // bootlet setup + TRX/IMAGE done
fdr_start <= 1'b1; grid_ran <= 1'b0; cst <= C_RUN; end
if (FEEDER_AUTOSTART) begin
fdr_start <= 1'b1; grid_ran <= 1'b0; cst <= C_RUN; // auto boot render
end else begin
grid_ran <= 1'b0; cst <= C_READY; // Ch353: no boot render — await explicit GO
end
end
C_RUN: begin if (gs_scene_busy) grid_ran <= 1'b1;
if (fdr_done) cst <= C_DRAIN; end
// Ch337 — gate on gs_scene_busy (NOT gs_raster_active): a >FIFO_DEPTH scene
@@ -883,15 +911,39 @@ module top_psmct32_raster_demo_bram
logic raster_pixel_emit;
logic [63:0] raster_pixel_color_q;
logic [31:0] raster_pixel_fb_addr_q;
logic [11:0] raster_pixel_x_q, raster_pixel_y_q;
logic [31:0] raster_pixel_z_q;
logic [3:0] raster_pixel_be_q;
logic [31:0] raster_pixel_mask_q;
logic [63:0] gs_frame_1_q;
logic [5:0] raster_pixel_psm_q;
logic raster_pixel_abe_q;
logic [16:0] raster_pixel_alpha_q;
logic raster_pixel_ztest_q;
logic [1:0] raster_pixel_ztst_q;
logic raster_pixel_zmsk_q;
// Ch318 — expose the flush stream for an external LPDDR AXI writer (de25 top).
assign flush_emit_o = raster_pixel_emit;
assign flush_addr_o = raster_pixel_fb_addr_q;
assign flush_pix16_o = raster_pixel_color_q[15:0];
assign flush_color32_o = raster_pixel_color_q[31:0]; // Ch323 — full color for spill capture
// The production LPDDR ROP supports the byte-granular FBMSK subset used
// by the captured SH3 frame. A set mask byte preserves that destination
// byte; partial-bit mask bytes are rejected by fixture generation.
wire [3:0] frame_byte_be = {gs_frame_1_q[63:56] == 8'h00,
gs_frame_1_q[55:48] == 8'h00,
gs_frame_1_q[47:40] == 8'h00,
gs_frame_1_q[39:32] == 8'h00};
assign flush_be_o = raster_pixel_be_q & frame_byte_be;
assign flush_x_o = raster_pixel_x_q; // Ch357 — fragment coords + Z for the LPDDR-Z ROP
assign flush_y_o = raster_pixel_y_q;
assign flush_z_o = raster_pixel_z_q;
assign flush_abe_o = raster_pixel_abe_q;
assign flush_alpha_o = raster_pixel_alpha_q;
assign flush_ztest_o = raster_pixel_ztest_q;
assign flush_ztst_o = raster_pixel_ztst_q;
assign flush_zmsk_o = raster_pixel_zmsk_q;
assign flush_psm_o = raster_pixel_psm_q;
// Ch295 — texture-sampler read port out of gs_stub. Wired to
@@ -921,6 +973,11 @@ module top_psmct32_raster_demo_bram
// that consumes it as clut_load_busy to hold the FIFO pop while the
// VRAM->CLUT load runs). Driven by clut_loader_stub.load_busy below.
logic clut_ld_busy;
// Existing unit tops leave the new runtime ports open. Treat only a
// driven-one as active so those legacy fixtures stay byte-identical.
wire runtime_clut_busy = (runtime_clut_busy_i === 1'b1);
wire runtime_clut_wr_en = (runtime_clut_wr_en_i === 1'b1);
wire clut_busy = clut_ld_busy | runtime_clut_busy;
// Ch296 — gs_stub TEX0_1 decode taps that feed clut_loader_stub: the
// 1-cycle commit pulse + CBP/CPSM/CSM/CSA/CLD that decide when (and
@@ -960,6 +1017,7 @@ module top_psmct32_raster_demo_bram
.FB_RD_REGISTERED (1'b1),
.Z_RD_REGISTERED (1'b1),
.PERSPECTIVE_CORRECT(PERSPECTIVE_CORRECT), // Ch301 — forwarded board param (default 0)
.SUBPIXEL_XY (SUBPIXEL_XY), // Ch416 — native GS 12.4 coverage (default 0)
.PERSP_RECIP_IDX_BITS(PERSP_RECIP_IDX_BITS), // Ch351 — perspective reciprocal LUT width (far-W -> 11)
.GRAD_DIV_CYCLES(GRAD_DIV_CYCLES), // Ch352 — triangle-setup divide settle cycles (board -> 4)
.GRAD_SEQ_DIVIDER(GRAD_SEQ_DIVIDER), // Ch352 — sequential gradient divider (board -> 1)
@@ -997,7 +1055,7 @@ module top_psmct32_raster_demo_bram
.pmode_q(pmode_q), .dispfb1_q(dispfb1_q), .display1_q(display1_q),
.prim_q(), .rgbaq_q(),
.xyz2_q(), .xyzf2_q(),
.frame_1_q(), .zbuf_1_q(),
.frame_1_q(gs_frame_1_q), .zbuf_1_q(),
.tex0_1_q(), .tex0_1_cbp_q(gs_tex0_cbp), .tex0_1_cpsm_q(gs_tex0_cpsm),
.tex0_1_csm_q(gs_tex0_csm), .tex0_1_csa_q(gs_tex0_csa),
.tex0_1_cld_q(gs_tex0_cld), .tex0_1_wr_q(gs_tex0_wr),
@@ -1018,12 +1076,18 @@ module top_psmct32_raster_demo_bram
.pixel_fbp_q(), .pixel_fbw_q(), .pixel_psm_q(), .pixel_fb_addr_q(),
.raster_pixel_emit(raster_pixel_emit),
.raster_pixel_emit_count(),
.raster_pixel_x_q(), .raster_pixel_y_q(),
.raster_pixel_x_q(raster_pixel_x_q), .raster_pixel_y_q(raster_pixel_y_q),
.raster_pixel_color_q(raster_pixel_color_q),
.raster_pixel_fb_addr_q(raster_pixel_fb_addr_q),
.raster_pixel_z_q(raster_pixel_z_q),
.raster_pixel_be_q(raster_pixel_be_q),
.raster_pixel_mask_q(raster_pixel_mask_q),
.raster_pixel_psm_q(raster_pixel_psm_q),
.raster_pixel_abe_q(raster_pixel_abe_q),
.raster_pixel_alpha_q(raster_pixel_alpha_q),
.raster_pixel_ztest_q(raster_pixel_ztest_q),
.raster_pixel_ztst_q(raster_pixel_ztst_q),
.raster_pixel_zmsk_q(raster_pixel_zmsk_q),
.z_flush_emit_o(z_flush_emit_o), // Ch323 — tile Z-flush stream (de25 Z-writer)
.z_flush_addr_o(z_flush_addr_o),
.z_flush_data_o(z_flush_data_o),
@@ -1047,7 +1111,7 @@ module top_psmct32_raster_demo_bram
.tex_rd_data(gs_tex_rd_data),
.clut_rd_idx (gs_clut_rd_idx),
.clut_rd_data(gs_clut_rd_data),
.clut_load_busy(clut_ld_busy), // hold pop while VRAM->CLUT load runs
.clut_load_busy(clut_busy), // hold pop while either CLUT writer runs
.fb_rd_en (gs_fb_rd_en),
.fb_rd_addr(gs_fb_rd_addr),
.fb_rd_data(gs_fb_rd_data),
@@ -1422,11 +1486,22 @@ module top_psmct32_raster_demo_bram
end
end
wire clut_wr_en = runtime_clut_wr_en ? 1'b1 : clut_ld_wr_en_q;
wire [7:0] clut_wr_idx = runtime_clut_wr_en ? runtime_clut_wr_idx_i : clut_ld_wr_idx_q;
wire [31:0] clut_wr_data = runtime_clut_wr_en ? runtime_clut_wr_data_i : clut_ld_wr_data_c;
// synthesis translate_off
always_ff @(posedge clk) begin
if (rst_n && runtime_clut_wr_en && clut_ld_wr_en_q)
$error("runtime CLUT copy collided with TEX0 CLUT loader");
end
// synthesis translate_on
clut_stub u_clut (
.clk(clk), .rst_n(rst_n),
.write_en (clut_ld_wr_en_q),
.write_idx(clut_ld_wr_idx_q),
.write_data(clut_ld_wr_data_c), // LIVE registered-read data
.write_en (clut_wr_en),
.write_idx(clut_wr_idx),
.write_data(clut_wr_data),
// pcrtc scanout read port — unused in this textured-board top
// (PCRTC clut_enable=0). Tie its index to 0.
.read_idx (8'd0),
+172 -7
View File
@@ -129,8 +129,159 @@ $(mac "FEEDER_STG_INIT_FILE=\\\"$MEMDIR/feeder_sh3_persp.mem\\\"")"
$(mac GS_LPDDR_TEX=1)
$(mac "FEEDER_STG_INIT_FILE=\\\"$MEMDIR/feeder_sh3_real.mem\\\"")"
;;
sh3_lpddr_fb)
# Ch353 — the SH3 real draw on an LPDDR-ONLY direct PSMCT32 framebuffer (the FULL 256x334 bounding box, NOT
# the Ch352 256x120 BRAM crop). Same feeder-driven SH3 config as sh3_real_draw (LPDDR texture cache @0x200000
# + real CSM1 CLUT + Ch351 widened perspective), but the bram-top runs with FB_LPDDR_ONLY (the PSMCT32 writer
# commits the frame to LPDDR @ base 0 instead of the BRAM mirror) and FEEDER_AUTOSTART=0 (no boot render). The
# line-buffer scanout serves the LPDDR FB, HARD-gated on the ordered frame_drained ack (no timeout). VRAM stays
# 128 KiB / CBP=480 (Codex option A). The static QSF EMIF/GS_LPDDR4B_FB/USE_QSYS_TOP already provide the EMIF +
# writer + line-buffer scanout. Proven end-to-end by tb_top_psmct32_sh3_lpddr_fb_seq (the mandatory fit gate).
# BOARD ORDER (Codex host sequence): (1) wait setup/CLUT + feeder ready (0x0D8[0]); (2) HPS PRECLEAR the LPDDR
# FB 0x000000..0x0537FF via the write-probe; (3) upload the 512x512 PSMT8 texture to LPDDR @0x200000; (4) fill +
# checksum-verify the texture cache (0x054/0x070=fbdeaa32); (5) set FB base 0 (0x01C=0), canary off, ARM the
# writer (0x018[0]); (6) pulse feeder GO (0x0E8); (7) confirm drain (poll LPDDR_STATUS 0x02C[0] idle + LPDDR_BYTES
# 0x030 != 0); (8) scanout auto-enables from frame_drained — raise video_src (0x018[2]=1); scanout_lb is forced.
# Fixtures SH3-derived -> LOCAL/gitignored: run `python3 tools/gs_make_sh3_real_draw_fixture.py --full-frame` FIRST.
FIXTURE="$(fix bios_sh3_full payload_sh3_full)"
SCENE="$(mac GS_SH3_LPDDR_FB=1)
$(mac GS_LPDDR_TEX=1)
$(mac "FEEDER_STG_INIT_FILE=\\\"$MEMDIR/feeder_sh3_full.mem\\\"")"
;;
sh3_lpddr_fb_multi)
# Ch354 Brick 1 — MULTI-DRAW: 3 authentic SH3 draws (idx89548/89761/89974) that SHARE one texture/CLUT,
# composited into ONE LPDDR PSMCT32 framebuffer (256x338, the union bbox). Same LPDDR-FB path as sh3_lpddr_fb
# (FEEDER_AUTOSTART=0 + render-epoch EOF + frame_drained hard gate + line-buffer scanout) but GS_SH3_LPDDR_FB_MULTI
# raises FEEDER_STG_WORDS 768->2048 (204-tri combined list) and SCANOUT_LB_NROWS 334->338. Accumulation is
# bit-exact (combined RTL == RTL-composed singles, 0/86528 px); per-draw <=1-texel fidelity is draw-dependent
# (97.5/95.6/93.2%, the existing perspective-sampling limit, NOT an acceptance gate). Proven by
# tb_top_psmct32_sh3_multidraw. GS_SH3_LPDDR_FB + GS_LPDDR4B_FB + GS_LPDDR_TEX all apply (the profile sets them).
# BOARD ORDER: identical to sh3_lpddr_fb, but preclear the LARGER FB range:
# sudo ./ps2_sh3_tex_upload sh3_real_tex_lpddr.mem --lpddr-fb --fb-rows 338 (preclears 0..0x54800).
# Fixtures SH3-derived -> LOCAL/gitignored: run `python3 tools/gs_make_sh3_multidraw_fixture.py <dump>
# --draw-list 89548,89761,89974 --tag multi --emit` FIRST.
FIXTURE="$(fix bios_sh3_multi payload_sh3_multi)"
SCENE="$(mac GS_SH3_LPDDR_FB=1)
$(mac GS_SH3_LPDDR_FB_MULTI=1)
$(mac GS_LPDDR_TEX=1)
$(mac "FEEDER_STG_INIT_FILE=\\\"$MEMDIR/feeder_sh3_multi.mem\\\"")"
;;
sh3_lpddr_fb_multitex)
# Ch355 Brick 1 — MULTI-TEXTURE: 2 SH3 draws with DIFFERENT textures/CLUTs (A=idx19562 tbp=11264/CBP_A,
# B=idx89761 tbp=9216/CBP_B) composited into ONE 320x381 LPDDR FB (FBW=5, stride 1280, 0x77100, 15240 beats/frame)
# via SCENE-LEVEL texture rebind + RUNTIME staged-list retriggering. GS_SH3_LPDDR_FB_MULTITEX raises
# FEEDER_STG_WORDS->2048 + SCANOUT_LB to 320x381. The bootlet (bios/payload_sh3_mt) uploads BOTH relocated CLUTs
# (CBP_A=480, CBP_B=484); the HOST streams each list at runtime (NO FEEDER_STG_INIT_FILE — feeder starts empty).
# Accumulation bit-exact + Q-canonicalized (feeder canonicalizes homogeneous coords to positive Q; NOT native
# signed-Q). Proven by tb_top_psmct32_sh3_multitex.
# BOARD RUN (runtime two-group flow, host streams the lists + rebinds the cache):
# sudo ./ps2_sh3_multitex sh3_mtA_tex_lpddr.mem sh3_mtB_tex_lpddr.mem feeder_sh3_mtA.mem feeder_sh3_mtB.mem
# Fixtures SH3-derived -> LOCAL/gitignored: run `python3 tools/gs_make_sh3_multitex_fixture.py <dump> --emit` FIRST.
FIXTURE="$(fix bios_sh3_mt payload_sh3_mt)"
SCENE="$(mac GS_SH3_LPDDR_FB=1)
$(mac GS_SH3_LPDDR_FB_MULTITEX=1)
$(mac GS_LPDDR_TEX=1)"
;;
sh3_lpddr_fb_sched)
# Ch356 — N-TEXTURE SCHEDULER: N (default 3) authentic SH3 draws with DIFFERENT textures/CLUTs
# (E0=idx11671 tbp=10240/CBP480, E1=idx19562 tbp=11264/CBP484, E2=idx89761 tbp=9216/CBP488) composited into ONE
# 384x381 LPDDR FB (FBW=6, stride 1536, 0x8ee00, 18288 beats/frame) via a DATA-DRIVEN scheduler over epoch
# descriptors (scene-level texture rebind + runtime staged-list retriggering). GS_SH3_LPDDR_FB_SCHED raises
# FEEDER_STG_WORDS->2048 + SCANOUT_LB to 384x381. The bootlet (bios/payload_sh3_sched) preloads ALL N relocated
# CLUTs; the HOST streams each list at runtime (NO FEEDER_STG_INIT_FILE). Accumulation proven 100% BIT-FOR-BIT
# (compose_sched.py) + Q-canonicalized; tb_top_psmct32_sh3_sched (+ _compose gate).
# BOARD RUN (data-driven; host reads the descriptor table + streams each list, rebinds the cache per epoch):
# sudo ./ps2_sh3_sched sh3_sched_epochs.txt (all fixtures in the cwd)
# Fixtures SH3-derived -> LOCAL/gitignored: run `python3 tools/gs_make_sh3_scheduler_fixture.py <dump> --emit` FIRST.
FIXTURE="$(fix bios_sh3_sched payload_sh3_sched)"
SCENE="$(mac GS_SH3_LPDDR_FB=1)
$(mac GS_SH3_LPDDR_FB_SCHED=1)
$(mac GS_LPDDR_TEX=1)"
;;
sh3_lpddr_fb_640)
# Ch357 — NATIVE 640x480 LPDDR framebuffer: the Ch356 N-texture scheduler on a full-frame 640x480 FB
# (FBW=10, stride 2560, size 0x12C000, 80 beats/row, 38400 beats/frame). Draws land at their AUTHENTIC screen
# coordinates (NO union-origin translation). Scheduler/host UNCHANGED from Ch356; only the FB geometry differs.
# GS_SH3_LPDDR_FB_640 sets SCANOUT_LB 640x480 + FEEDER_STG_WORDS=2048. Bootlet (bios/payload_sh3_s640) preloads
# all N relocated CLUTs; the HOST streams each list at runtime (NO FEEDER_STG_INIT_FILE). FB[0..0x12C000) disjoint
# from texture region [0x200000..). Composition proven 100% BIT-FOR-BIT (compose_sched.py); tb_top_psmct32_sh3_sched640.
# BOARD RUN (same data-driven host, different descriptor table):
# sudo ./ps2_sh3_sched sh3_s640_epochs.txt
# Fixtures SH3-derived -> LOCAL/gitignored: run `python3 tools/gs_make_sh3_scheduler_fixture.py <dump> --fb640 --emit` FIRST.
FIXTURE="$(fix bios_sh3_s640 payload_sh3_s640)"
SCENE="$(mac GS_SH3_LPDDR_FB=1)
$(mac GS_SH3_LPDDR_FB_640=1)
$(mac GS_LPDDR_TEX=1)"
;;
sh3_lpddr_fb_z)
# Ch357 — PERSISTENT AUTHENTIC Z: the N-texture scheduler with a REAL per-fragment PSMZ16S depth test on an
# LPDDR-persistent Z buffer. The plain color writer is replaced by gs_lpddr_zc_emit: per fragment it clamp16-reads
# /GEQUAL-compares/updates a packed-16b Z line in LPDDR (Z base 0x140000) and emits color ONLY on Z-pass. Z PERSISTS
# across the 3 scheduler epochs (precleared exactly once by clear_start at ARM; texture rebind does NOT clear it).
# The three AXI streams share the LPDDR through the existing arbiters (color->wr_arb s0, Z-write->wr_arb s2,
# Z-read->rd_arb s3). Strong-reject acceptance fixture (256x210, 18.31% reject: far-late fragments MUST be depth-
# rejected). GS_SH3_LPDDR_FB_Z sets SCANOUT_LB 256x210 (6720 beats/frame). Bootlet (bios/payload_sh3_zsched) preloads
# all 3 relocated CLUTs; the HOST streams each list at runtime (NO FEEDER_STG_INIT_FILE). ROP + arbitration + REAL-
# RASTER integration all sim-proven: tb_top_psmct32_sh3_zrop (ROP golden), tb_top_psmct32_sh3_zarb (arbitration),
# tb_top_psmct32_sh3_zint (real raster, ZERO fragment drops, Z+color bit-exact vs the clamp16 scoreboard).
# BOARD ORDER (Codex host sequence): (1) wait setup/CLUT + feeder ready; (2) HPS PRECLEAR color 0x000000..0x0347FF
# AND Z 0x140000..0x15A3FF via the write-probe; (3) per epoch upload the 512x512 PSMT8 texture @0x200000 + fill/verify
# the cache; (4) ARM the writer (0x018[0]) -> clear_start preclears the Z buffer; WAIT clear_done before the first GO;
# (5) per epoch stream the list + pulse GO (0x0E8) + confirm a FRESH frame_drained; Z persists across epochs; (6) after
# the LAST drain raise video_src (0x018[2]=1) — scanout_lb (256x210) is forced. Read zc_g_drops (must be 0).
# Fixtures SH3-derived -> LOCAL/gitignored: run `python3 tools/gs_make_sh3_scheduler_fixture.py <dump> --authz
# --tag zsched --emit` FIRST (strong-reject draw list, authentic per-vertex Z).
FIXTURE="$(fix bios_sh3_zsched payload_sh3_zsched)"
SCENE="$(mac GS_SH3_LPDDR_FB=1)
$(mac GS_SH3_LPDDR_FB_Z=1)
$(mac GS_LPDDR_TEX=1)"
;;
sh3_lpddr_fb_z640)
# Ch358 — PERSISTENT AUTHENTIC Z at NATIVE 640x480: the SAME strong-reject persistent-Z scene as sh3_lpddr_fb_z
# (draws 8634/12757/145742, gs_lpddr_zc_emit, clamp16/GEQUAL, Z persists across epochs) but on the full-frame
# native FB at AUTHENTIC screen coordinates (FBW=10, stride 2560, size 0x12C000, 38400 beats/frame; draws land at
# x[407..619] y[97..306]). GS_SH3_LPDDR_FB_Z + GS_SH3_LPDDR_FB_640 together select SCANOUT_LB 640x480 AND
# ZFB_PXW/H=640x480 (Z clear = 19200 beats). Map (gs_lpddr_map_pkg, disjoint by construction): color 0x0..0x12C000,
# Z 0x140000..0x1D6000, tex 0x200000. Bootlet (bios/payload_sh3_zs640) preloads all 3 relocated CLUTs; the HOST
# streams each list at runtime (NO FEEDER_STG_INIT_FILE). Sim gates: tb_top_psmct32_sh3_zs640_cap (capture +
# fidelity oracle) and tb_top_psmct32_sh3_zint640 (real raster -> zc_emit -> REAL arbiters -> shared LPDDR,
# ZERO drops, Z+color scoreboard-exact). Board proof = make -C sim sh3_zs640_board_compare (byte-for-byte vs
# zint640 + replay-color 100%).
# BOARD RUN (host unchanged — geometry comes from the epochs META):
# sudo ./ps2_sh3_sched --zbuf sh3_zs640_epochs.txt --dump-fb sh3_zs640_board_fb.mem
# Fixtures SH3-derived -> LOCAL/gitignored: run `make -C sim sh3_zs640_fixture` FIRST.
FIXTURE="$(fix bios_sh3_zs640 payload_sh3_zs640)"
SCENE="$(mac GS_SH3_LPDDR_FB=1)
$(mac GS_SH3_LPDDR_FB_Z=1)
$(mac GS_SH3_LPDDR_FB_640=1)
$(mac GS_LPDDR_TEX=1)"
;;
sh3_lpddr_fb_z640c6)
# Ch359 — shared-texture residency scene: six authentic SH3 draws grouped into two
# 204-triangle epochs at native 640x480 persistent Z. The bootlet preloads the C6
# relocated CLUT at CBP=13888; the host uploads/fills the shared texture once, then
# verifies it remains resident for the reuse epoch.
FIXTURE="$(fix bios_sh3_zs640c6 payload_sh3_zs640c6)"
SCENE="$(mac GS_SH3_LPDDR_FB=1)
$(mac GS_SH3_LPDDR_FB_Z=1)
$(mac GS_SH3_LPDDR_FB_640=1)
$(mac GS_LPDDR_TEX=1)"
;;
sh3_lpddr_fb_z640_runtime_clut)
# Ch367 -- runtime palette residency proof. Same native 640x480
# persistent-Z geometry as Ch358. Keep the proven nonempty C6 setup
# bootlet as a synthesis-retention anchor; the runtime test lists use
# TEX0.CLD=0 and the HPS stages/checks every palette before each GO.
# A zero-transfer bootlet lets Quartus prune the otherwise runtime-fed
# texture/raster RAMs, so it is not a valid hardware proof vehicle.
FIXTURE="$(fix bios_sh3_zs640c6 payload_sh3_zs640c6)"
SCENE="$(mac GS_SH3_LPDDR_FB=1)
$(mac GS_SH3_LPDDR_FB_Z=1)
$(mac GS_SH3_LPDDR_FB_640=1)
$(mac GS_LPDDR_TEX=1)"
;;
--list|"")
echo "Profiles: lpddr_tex (Ch322 texture) lpddr128_scanout (Ch321 scanout) tile_spill (Ch323 spill/reload) feeder (Ch330 command-list) feeder_persp (Ch342 perspective) feeder_cube (Ch343 authentic cube) texalpha (Ch344 textured-alpha sprite) feeder_sprite (Ch345a runtime sprite) sh3_clut (Ch347 authentic SH3 PSMT8+CLUT) sh3_persp (Ch348 SH3 PSMT8+CLUT perspective) sh3_real_draw (Ch350/351/352 SH3 real-draw crop, LPDDR tex + CSM1 + widened persp)"
echo "Profiles: lpddr_tex (Ch322 texture) lpddr128_scanout (Ch321 scanout) tile_spill (Ch323 spill/reload) feeder (Ch330 command-list) feeder_persp (Ch342 perspective) feeder_cube (Ch343 authentic cube) texalpha (Ch344 textured-alpha sprite) feeder_sprite (Ch345a runtime sprite) sh3_clut (Ch347 authentic SH3 PSMT8+CLUT) sh3_persp (Ch348 SH3 PSMT8+CLUT perspective) sh3_real_draw (Ch350/351/352 SH3 real-draw crop, LPDDR tex + CSM1 + widened persp) sh3_lpddr_fb (Ch353 SH3 full 256x334 on an LPDDR-only PSMCT32 FB + line-buffer scanout, frame_drained-gated) sh3_lpddr_fb_multi (Ch354 3 SH3 draws -> 256x338) sh3_lpddr_fb_multitex (Ch355 2 DIFFERENT-texture draws -> 320x381, runtime-staged) sh3_lpddr_fb_sched (Ch356 N-texture data-driven scheduler -> 384x381) sh3_lpddr_fb_640 (Ch357 native 640x480 full-frame) sh3_lpddr_fb_z (Ch357 PERSISTENT PSMZ16S Z, 256x210 strong-reject) sh3_lpddr_fb_z640 (Ch358 PERSISTENT Z at NATIVE 640x480) sh3_lpddr_fb_z640c6 (Ch359 shared-texture residency) sh3_lpddr_fb_z640_runtime_clut (Ch367 runtime palette residency)"
echo "Active profile:"
sed -n '/DE25_PROFILE_SCENE/,/DE25_PROFILE_SCENE/p' "$QSF" | grep VERILOG_MACRO || echo " (no scene macros)"
sed -n '/DE25_PROFILE_FIXTURE/,/DE25_PROFILE_FIXTURE/p' "$QSF" | grep IMAGE_FILE
@@ -139,24 +290,38 @@ $(mac "FEEDER_STG_INIT_FILE=\\\"$MEMDIR/feeder_sh3_real.mem\\\"")"
*) echo "unknown profile: $PROFILE (try --list)"; exit 2 ;;
esac
# Replace the content between each marker pair (awk: print markers, swap the body).
swap_block() { # $1=tag $2=replacement-text (reads stdin QSF, writes stdout)
awk -v tag="$1" -v repl="$2" '
$0 ~ (">>> " tag " ") { print; print repl; inblk=1; next }
# Replace the content between each marker pair. Read the replacement from a file
# rather than `awk -v`: awk consumes backslash-escaped quotes in Verilog macros.
swap_block() { # $1=tag $2=replacement-file (reads stdin QSF, writes stdout)
awk -v tag="$1" -v repl_file="$2" '
$0 ~ (">>> " tag " ") {
print
while ((getline line < repl_file) > 0) print line
close(repl_file)
inblk=1
next
}
$0 ~ ("<<< " tag " <<<") { inblk=0 }
!inblk { print }
'
}
fixture_tmp="$(mktemp)"
scene_tmp="$(mktemp)"
tmp="$(mktemp)"
swap_block DE25_PROFILE_FIXTURE "$FIXTURE" < "$QSF" \
| swap_block DE25_PROFILE_SCENE "$SCENE" > "$tmp"
trap 'rm -f "$fixture_tmp" "$scene_tmp" "$tmp"' EXIT HUP INT TERM
printf '%s\n' "$FIXTURE" > "$fixture_tmp"
printf '%s\n' "$SCENE" > "$scene_tmp"
swap_block DE25_PROFILE_FIXTURE "$fixture_tmp" < "$QSF" \
| swap_block DE25_PROFILE_SCENE "$scene_tmp" > "$tmp"
# sanity: both marker pairs must still be present (don't clobber the QSF on a bad run)
for tag in DE25_PROFILE_FIXTURE DE25_PROFILE_SCENE; do
[ "$(grep -c "$tag" "$tmp")" -eq 2 ] || { echo "ERROR: marker '$tag' missing after edit — QSF unchanged"; rm -f "$tmp"; exit 1; }
done
mv "$tmp" "$QSF"
trap - EXIT HUP INT TERM
rm -f "$fixture_tmp" "$scene_tmp"
echo "Selected profile: $PROFILE"
echo " fixture: $(echo "$FIXTURE" | grep -o '[a-z0-9_]*\.mem' | tr '\n' ' ')"
echo " scene : $(echo "$SCENE" | grep -o 'GS_[A-Z0-9_]*=1' | tr '\n' ' ')"
+2565 -5
View File
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
# Ch365 epoch descriptor table (8 epochs, dump order). host/board: preload CLUT table via bootlet;
# per epoch: upload tex -> LPDDR 0x200000, fill+verify CRC, stream list, GO, fresh drain.
# reuse=1 (Ch359): the texture cache is ALREADY resident from a prior epoch — the host must SKIP the
# upload and fill, and instead VERIFY the resident CRC register still equals crc (fail-closed residency).
META n_epochs 8 fbpxw 640 fbh 480 fbwords 307200 lpddr_tex 0x200000 tex_words 65536 n_beats 8192
# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse
0 114590 0 500 sh3_zs640b240_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640b240.mem 1843 204 0
1 115229 0 500 sh3_zs640b241_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640b241.mem 1843 204 1
2 115925 0 500 sh3_zs640b242_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640b242.mem 1843 204 1
3 117599 0 500 sh3_zs640b243_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640b243.mem 1843 204 1
4 118982 0 500 sh3_zs640b244_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640b244.mem 1843 204 1
5 126542 0 500 sh3_zs640b245_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640b245.mem 1843 204 1
6 127394 0 500 sh3_zs640b246_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640b246.mem 1843 204 1
7 128033 0 500 sh3_zs640b247_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640b247.mem 1843 204 1
@@ -0,0 +1,10 @@
# Ch359 epoch descriptor table (4 epochs, dump order). host/board: preload CLUT table via bootlet;
# per epoch: upload tex -> LPDDR 0x200000, fill+verify CRC, stream list, GO, fresh drain.
# reuse=1 (Ch359): the texture cache is ALREADY resident from a prior epoch — the host must SKIP the
# upload and fill, and instead VERIFY the resident CRC register still equals crc (fail-closed residency).
META n_epochs 4 fbpxw 640 fbh 480 fbwords 307200 lpddr_tex 0x200000 tex_words 65536 n_beats 8192
# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse
0 119471 8192 480 sh3_zs640c120_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c120.mem 1843 204 0
1 120110 8192 480 sh3_zs640c121_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c121.mem 1843 204 1
2 120749 8192 480 sh3_zs640c122_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c122.mem 1843 204 1
3 121388 8192 480 sh3_zs640c123_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c123.mem 1843 204 1
@@ -0,0 +1,12 @@
# Ch359 epoch descriptor table (6 epochs, dump order). CLUT=bootlet preload;
# per epoch: upload tex -> LPDDR 0x200000, fill+verify CRC, stream list, GO, fresh drain.
# reuse=1 (Ch359): the texture cache is ALREADY resident from a prior epoch — the host must SKIP the
# upload and fill, and instead VERIFY the resident CRC register still equals crc (fail-closed residency).
META n_epochs 6 fbpxw 640 fbh 480 fbwords 307200 lpddr_tex 0x200000 tex_words 65536 n_beats 8192
# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse pal_file pal_sum32
0 119471 8192 480 sh3_zs640c180_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c180.mem 565 62 0 - 0x00000000
1 120110 8192 480 sh3_zs640c180_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c181.mem 520 57 1 - 0x00000000
2 120749 8192 480 sh3_zs640c180_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c182.mem 718 79 1 - 0x00000000
3 121388 8192 480 sh3_zs640c180_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c183.mem 538 59 1 - 0x00000000
4 122027 8192 480 sh3_zs640c180_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c184.mem 520 57 1 - 0x00000000
5 129389 8192 480 sh3_zs640c180_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c185.mem 412 45 1 - 0x00000000
@@ -0,0 +1,14 @@
# Ch359 epoch descriptor table (8 epochs, dump order). host/board: preload CLUT table via bootlet;
# per epoch: upload tex -> LPDDR 0x200000, fill+verify CRC, stream list, GO, fresh drain.
# reuse=1 (Ch359): the texture cache is ALREADY resident from a prior epoch — the host must SKIP the
# upload and fill, and instead VERIFY the resident CRC register still equals crc (fail-closed residency).
META n_epochs 8 fbpxw 640 fbh 480 fbwords 307200 lpddr_tex 0x200000 tex_words 65536 n_beats 8192
# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse
0 119471 8192 480 sh3_zs640c240_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c240.mem 1843 204 0
1 120110 8192 480 sh3_zs640c241_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c241.mem 1843 204 1
2 120749 8192 480 sh3_zs640c242_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c242.mem 1843 204 1
3 121388 8192 480 sh3_zs640c243_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c243.mem 1843 204 1
4 122027 8192 480 sh3_zs640c244_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c244.mem 1843 204 1
5 129389 8192 480 sh3_zs640c245_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c245.mem 1843 204 1
6 130241 8192 480 sh3_zs640c246_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c246.mem 1843 204 1
7 130880 8192 480 sh3_zs640c247_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c247.mem 1843 204 1
@@ -0,0 +1,14 @@
# Ch366 epoch descriptor table (8 epochs, dump order). host/board: preload CLUT table via bootlet;
# per epoch: upload tex -> LPDDR 0x200000, fill+verify CRC, stream list, GO, fresh drain.
# reuse=1 (Ch359): the texture cache is ALREADY resident from a prior epoch — the host must SKIP the
# upload and fill, and instead VERIFY the resident CRC register still equals crc (fail-closed residency).
META n_epochs 8 fbpxw 640 fbh 480 fbwords 307200 lpddr_tex 0x200000 tex_words 65536 n_beats 8192
# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse
0 119471 8192 504 sh3_zs640c24c0_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c24c0.mem 1843 204 0
1 120110 8192 504 sh3_zs640c24c1_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c24c1.mem 1843 204 1
2 120749 8192 504 sh3_zs640c24c2_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c24c2.mem 1843 204 1
3 121388 8192 504 sh3_zs640c24c3_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c24c3.mem 1843 204 1
4 122027 8192 504 sh3_zs640c24c4_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c24c4.mem 1843 204 1
5 129389 8192 504 sh3_zs640c24c5_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c24c5.mem 1843 204 1
6 130241 8192 504 sh3_zs640c24c6_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c24c6.mem 1843 204 1
7 130880 8192 504 sh3_zs640c24c7_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640c24c7.mem 1843 204 1
@@ -0,0 +1,18 @@
# Ch364 epoch descriptor table (12 epochs, dump order). host/board: preload CLUT table via bootlet;
# per epoch: upload tex -> LPDDR 0x200000, fill+verify CRC, stream list, GO, fresh drain.
# reuse=1 (Ch359): the texture cache is ALREADY resident from a prior epoch — the host must SKIP the
# upload and fill, and instead VERIFY the resident CRC register still equals crc (fail-closed residency).
META n_epochs 12 fbpxw 640 fbh 480 fbwords 307200 lpddr_tex 0x200000 tex_words 65536 n_beats 8192
# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse
0 5328 11264 480 sh3_zs640m280_tex_lpddr.mem 0x200000 262144 0x55070afa feeder_sh3_zs640m280.mem 493 54 0
1 11671 10240 484 sh3_zs640m281_tex_lpddr.mem 0x200000 262144 0x26a88b77 feeder_sh3_zs640m281.mem 619 68 0
2 77112 10240 488 sh3_zs640m282_tex_lpddr.mem 0x200000 262144 0xa727234d feeder_sh3_zs640m282.mem 619 68 0
3 89164 9216 492 sh3_zs640m283_tex_lpddr.mem 0x200000 262144 0xfbdeaa32 feeder_sh3_zs640m283.mem 619 68 0
4 119471 8192 496 sh3_zs640m284_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640m284.mem 1843 204 0
5 120110 8192 496 sh3_zs640m285_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640m285.mem 1843 204 1
6 120749 8192 496 sh3_zs640m286_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640m286.mem 1843 204 1
7 121388 8192 496 sh3_zs640m287_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640m287.mem 1843 204 1
8 122027 8192 496 sh3_zs640m288_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640m288.mem 1843 204 1
9 129389 8192 496 sh3_zs640m289_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640m289.mem 1843 204 1
10 130241 8192 496 sh3_zs640m2810_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640m2810.mem 1843 204 1
11 130880 8192 496 sh3_zs640m2811_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640m2811.mem 1843 204 1
@@ -0,0 +1,11 @@
# Ch357 epoch descriptor table (5 epochs, dump order). host/board: preload CLUT table via bootlet;
# per epoch: upload tex -> LPDDR 0x200000, fill+verify CRC, stream list, GO, fresh drain.
# reuse=1 (Ch359): the texture cache is ALREADY resident from a prior epoch — the host must SKIP the
# upload and fill, and instead VERIFY the resident CRC register still equals crc (fail-closed residency).
META n_epochs 5 fbpxw 640 fbh 480 fbwords 307200 lpddr_tex 0x200000 tex_words 65536 n_beats 8192
# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse
0 5328 11264 480 sh3_zs640mt50_tex_lpddr.mem 0x200000 262144 0x55070afa feeder_sh3_zs640mt50.mem 493 54 0
1 11671 10240 484 sh3_zs640mt51_tex_lpddr.mem 0x200000 262144 0x26a88b77 feeder_sh3_zs640mt51.mem 619 68 0
2 77112 10240 488 sh3_zs640mt52_tex_lpddr.mem 0x200000 262144 0xa727234d feeder_sh3_zs640mt52.mem 619 68 0
3 89164 9216 492 sh3_zs640mt53_tex_lpddr.mem 0x200000 262144 0xfbdeaa32 feeder_sh3_zs640mt53.mem 619 68 0
4 119471 8192 496 sh3_zs640mt54_tex_lpddr.mem 0x200000 262144 0xcf4cae27 feeder_sh3_zs640mt54.mem 619 68 0
@@ -0,0 +1,9 @@
# Ch416 epoch descriptor table (3 epochs, dump order). CLUT=bootlet preload;
# per epoch: upload tex -> LPDDR 0x200000, fill+verify CRC, stream list, GO, fresh drain.
# reuse=1 (Ch359): the texture cache is ALREADY resident from a prior epoch — the host must SKIP the
# upload and fill, and instead VERIFY the resident CRC register still equals crc (fail-closed residency).
META n_epochs 3 fbpxw 640 fbh 480 fbwords 307200 lpddr_tex 0x200000 tex_words 65536 n_beats 8192
# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse pal_file pal_sum32
0 8634 11264 480 sh3_zspxy30_tex_lpddr.mem 0x200000 262144 0x55070afa feeder_sh3_zspxy30.mem 259 28 0 - 0x00000000
1 12757 10240 484 sh3_zspxy31_tex_lpddr.mem 0x200000 262144 0x26a88b77 feeder_sh3_zspxy31.mem 97 10 0 - 0x00000000
2 145742 12288 488 sh3_zspxy32_tex_lpddr.mem 0x200000 262144 0x87415c50 feeder_sh3_zspxy32.mem 97 10 0 - 0x00000000
@@ -0,0 +1,11 @@
# Ch395 epoch descriptor table (5 epochs, dump order). CLUT=runtime HPS staging;
# per epoch: upload tex -> LPDDR 0x200000, fill+verify CRC, stream list, GO, fresh drain.
# reuse=1 (Ch359): the texture cache is ALREADY resident from a prior epoch — the host must SKIP the
# upload and fill, and instead VERIFY the resident CRC register still equals crc (fail-closed residency).
META n_epochs 5 fbpxw 640 fbh 480 fbwords 307200 lpddr_tex 0x200000 tex_words 65536 n_beats 8192
# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse pal_file pal_sum32
0 5328 11264 500 sh3_zsrt139a10_tex_lpddr.mem 0x200000 262144 0x55070afa feeder_sh3_zsrt139a10.mem 493 54 0 sh3_zsrt139a10_pal.mem 0x32ed74c4
1 11671 10240 500 sh3_zsrt139a11_tex_lpddr.mem 0x200000 262144 0x26a88b77 feeder_sh3_zsrt139a11.mem 1060 117 0 sh3_zsrt139a11_pal.mem 0x3cd88c98
2 89548 9216 500 sh3_zsrt139a12_tex_lpddr.mem 0x200000 262144 0xfbdeaa32 feeder_sh3_zsrt139a12.mem 1231 136 0 sh3_zsrt139a12_pal.mem 0x5dfce0a2
3 89974 9216 500 sh3_zsrt139a13_tex_lpddr.mem 0x200000 262144 0xfbdeaa32 feeder_sh3_zsrt139a13.mem 1231 136 1 sh3_zsrt139a13_pal.mem 0x5dfce0a2
4 90400 9216 500 sh3_zsrt139a14_tex_lpddr.mem 0x200000 262144 0xfbdeaa32 feeder_sh3_zsrt139a14.mem 1231 136 1 sh3_zsrt139a14_pal.mem 0x5dfce0a2
@@ -0,0 +1,12 @@
# Ch396 epoch descriptor table (6 epochs, dump order). CLUT=runtime HPS staging;
# per epoch: upload tex -> LPDDR 0x200000, fill+verify CRC, stream list, GO, fresh drain.
# reuse=1 (Ch359): the texture cache is ALREADY resident from a prior epoch — the host must SKIP the
# upload and fill, and instead VERIFY the resident CRC register still equals crc (fail-closed residency).
META n_epochs 6 fbpxw 640 fbh 480 fbwords 307200 lpddr_tex 0x200000 tex_words 65536 n_beats 8192
# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse pal_file pal_sum32
0 5328 11264 500 sh3_zsrt139a20_tex_lpddr.mem 0x200000 262144 0x55070afa feeder_sh3_zsrt139a20.mem 493 54 0 sh3_zsrt139a20_pal.mem 0x32ed74c4
1 11671 10240 500 sh3_zsrt139a21_tex_lpddr.mem 0x200000 262144 0x26a88b77 feeder_sh3_zsrt139a21.mem 1060 117 0 sh3_zsrt139a21_pal.mem 0x3cd88c98
2 89164 9216 500 sh3_zsrt139a22_tex_lpddr.mem 0x200000 262144 0xfbdeaa32 feeder_sh3_zsrt139a22.mem 1105 122 0 sh3_zsrt139a22_pal.mem 0x5dfce0a2
3 89548 9216 500 sh3_zsrt139a23_tex_lpddr.mem 0x200000 262144 0xfbdeaa32 feeder_sh3_zsrt139a23.mem 1231 136 1 sh3_zsrt139a23_pal.mem 0x5dfce0a2
4 89974 9216 500 sh3_zsrt139a24_tex_lpddr.mem 0x200000 262144 0xfbdeaa32 feeder_sh3_zsrt139a24.mem 1231 136 1 sh3_zsrt139a24_pal.mem 0x5dfce0a2
5 90400 9216 500 sh3_zsrt139a25_tex_lpddr.mem 0x200000 262144 0xfbdeaa32 feeder_sh3_zsrt139a25.mem 1231 136 1 sh3_zsrt139a25_pal.mem 0x5dfce0a2
@@ -0,0 +1,9 @@
# Ch397 epoch descriptor table (3 epochs, dump order). CLUT=runtime HPS staging;
# per epoch: upload tex -> LPDDR 0x200000, fill+verify CRC, stream list, GO, fresh drain.
# reuse=1 (Ch359): the texture cache is ALREADY resident from a prior epoch — the host must SKIP the
# upload and fill, and instead VERIFY the resident CRC register still equals crc (fail-closed residency).
META n_epochs 3 fbpxw 640 fbh 480 fbwords 307200 lpddr_tex 0x200000 tex_words 65536 n_beats 8192
# k idx tbp cbp_reloc tex_file lpddr size crc list_file words records reuse pal_file pal_sum32
0 5328 11264 480 sh3_zsrt139a30_tex_lpddr.mem 0x200000 262144 0x55070afa feeder_sh3_zsrt139a30.mem 493 54 0 sh3_zsrt139a30_pal.mem 0x32ed74c4
1 11671 10240 484 sh3_zsrt139a31_tex_lpddr.mem 0x200000 262144 0x26a88b77 feeder_sh3_zsrt139a31.mem 619 68 0 sh3_zsrt139a31_pal.mem 0x3cd88c98
2 32391 13824 488 sh3_zsrt139a32_tex_lpddr.mem 0x200000 262144 0x5b7ffb3d feeder_sh3_zsrt139a32.mem 97 10 0 sh3_zsrt139a32_pal.mem 0x566e7c38

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