Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# Decision 0000: Trace Format
|
||||
|
||||
Status: `Locked`
|
||||
|
||||
## Context
|
||||
|
||||
Trace format is a required Phase 0 decision because every subsystem contract
|
||||
already depends on debug visibility, and those traces are much more useful if
|
||||
they share a known structure.
|
||||
|
||||
The project needs one format that is:
|
||||
|
||||
- simple enough to emit from early RTL stubs,
|
||||
- simple enough to generate from emulator-side instrumentation,
|
||||
- human-readable during bring-up,
|
||||
- structured enough for automated diffs later.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. One shared tabular text format for everything.
|
||||
2. Subsystem-specific text formats with no shared envelope.
|
||||
3. Binary trace format from day one.
|
||||
4. Common text envelope plus subsystem-specific payload fields.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a `common text envelope plus subsystem-specific payload fields` format for
|
||||
Phase 0 and Phase 1.
|
||||
|
||||
The common envelope should include:
|
||||
|
||||
- cycle or monotonic timestamp,
|
||||
- subsystem id,
|
||||
- event type,
|
||||
- schema/version id,
|
||||
- payload fields encoded as self-describing key/value pairs or a stable
|
||||
column layout documented per subsystem.
|
||||
|
||||
Golden-reference traces should be normalized into the same envelope before
|
||||
comparison. The trace files under `sim/traces/` remain text during early bring-up.
|
||||
|
||||
Binary traces are deferred unless text traces become a demonstrated bottleneck.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Early traces stay readable in code review and terminal workflows.
|
||||
- RTL stubs can emit useful traces before any heavy tooling exists.
|
||||
- Emulator-side tooling has a clear normalization target.
|
||||
- Different subsystems may still define different payload fields, but they must
|
||||
fit inside the same outer structure.
|
||||
- If performance later requires binary traces, the project can add them behind
|
||||
the same logical schema rather than reinventing the event model.
|
||||
|
||||
## Inputs to use when locking
|
||||
|
||||
- `docs/contracts/validation.md`
|
||||
- per-subsystem "required debug visibility" sections in `docs/contracts/`
|
||||
- `sim/traces/README.md`
|
||||
|
||||
## Follow-up
|
||||
|
||||
- The stub-module plan should name the initial envelope fields explicitly.
|
||||
- Subsystem contracts may later gain a short "trace payload schema" section once
|
||||
the first stubs are specified.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Decision 0001: Project Posture
|
||||
|
||||
Status: `Locked`
|
||||
|
||||
## Context
|
||||
|
||||
The project needed to choose between:
|
||||
|
||||
- native full-system on current hardware,
|
||||
- staged subset on current hardware,
|
||||
- hybrid architecture,
|
||||
- future-hardware target.
|
||||
|
||||
This decision sets the planning posture for all early contracts and milestones.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. Native full-system on current hardware.
|
||||
2. Staged subset on current hardware.
|
||||
3. Hybrid architecture with significant host-side execution.
|
||||
4. Future-hardware target.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt `staged subset on current hardware`.
|
||||
|
||||
This means:
|
||||
|
||||
- the project targets the current retroDE platform,
|
||||
- the architectural path remains that of a real PS2 core,
|
||||
- coverage will be incomplete for an extended period,
|
||||
- early phases prioritize observable bring-up over broad software
|
||||
compatibility.
|
||||
|
||||
The intended interpretation is:
|
||||
|
||||
`real PS2 architectural path, incomplete coverage`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Early milestones may validate only parts of the machine.
|
||||
- Some subsystems can remain stubbed or reduced while others become real.
|
||||
- The project preserves continuity with the rest of the retroDE family by
|
||||
staying on current hardware.
|
||||
- The project explicitly does not promise full-title compatibility on the
|
||||
current platform.
|
||||
- Contracts and milestones should optimize for progressive integration instead
|
||||
of "all-or-nothing" completeness.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Decision 0002: BIOS Policy
|
||||
|
||||
Status: `Locked`
|
||||
|
||||
## Context
|
||||
|
||||
The project needed a firmware strategy that balanced authenticity with
|
||||
bring-up practicality.
|
||||
|
||||
Main approaches considered:
|
||||
|
||||
- real BIOS only,
|
||||
- HLE-only BIOS behavior,
|
||||
- real BIOS with narrowly-scoped debug stubs.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. Real BIOS only.
|
||||
2. HLE-only BIOS strategy.
|
||||
3. Real BIOS plus narrow debug stubs.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt `real BIOS plus narrow debug stubs`.
|
||||
|
||||
Policy details:
|
||||
|
||||
- Real user-supplied BIOS images remain the primary firmware path.
|
||||
- Debug stubs are allowed only where they materially shorten early bring-up.
|
||||
- Stubs must be narrow, explicit, and temporary.
|
||||
|
||||
Every stub must be tracked in a decision record or equivalent design note with:
|
||||
|
||||
- owner,
|
||||
- purpose,
|
||||
- scope boundary,
|
||||
- removal condition.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The project stays anchored to real PS2 boot behavior.
|
||||
- Early bring-up may proceed without waiting for every subsystem to be complete.
|
||||
- There is a maintenance cost: stub behavior must not silently become the
|
||||
architecture.
|
||||
- The repository must never include Sony BIOS images.
|
||||
- The stub-module plan must call out which stubs are in play for each phase.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Decision 0003: Golden Reference Strategy
|
||||
|
||||
Status: `Locked`
|
||||
|
||||
## Context
|
||||
|
||||
The project needs software references for:
|
||||
|
||||
- early boot and subsystem trace comparison,
|
||||
- broader behavior cross-checking,
|
||||
- dispute resolution when implementations differ.
|
||||
|
||||
The main candidates were DobieStation, PCSX2, Play!, or a custom-purpose
|
||||
reference.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. DobieStation as sole primary reference.
|
||||
2. PCSX2 as sole primary reference.
|
||||
3. Multiple references with role separation.
|
||||
4. Play! as primary reference.
|
||||
5. Purpose-built minimal reference.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt `multiple references with role separation`.
|
||||
|
||||
Role split:
|
||||
|
||||
- DobieStation: early boot and subsystem-oriented trace comparison.
|
||||
- PCSX2: breadth and behavior cross-checking.
|
||||
- Sony documentation and authoritative hardware references: tiebreak when
|
||||
available.
|
||||
|
||||
This project does not treat PCSX2 as a mere fallback; it is a peer reference
|
||||
used for a different purpose.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Validation infrastructure must be able to normalize traces from more than one
|
||||
reference source.
|
||||
- Some disagreements will require manual triage rather than simple "reference
|
||||
wins" logic.
|
||||
- The project gets a smaller, more coherent early trace oracle while retaining
|
||||
access to broader real-world emulator behavior.
|
||||
- The validation contract and stub-module plan should name which reference is
|
||||
used for each milestone.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Decision 0004: First Visible Milestone
|
||||
|
||||
Status: `Locked`
|
||||
|
||||
## Context
|
||||
|
||||
The project needed a first milestone that would produce fast, meaningful signal
|
||||
without conflating platform-video integration with EE/BIOS bring-up.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. BIOS fetch only.
|
||||
2. GS-stub test pattern only.
|
||||
3. Minimal homebrew EE graphics.
|
||||
4. Split first milestone into two parallel proofs.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a `split first milestone` with two parallel proofs.
|
||||
|
||||
### Milestone A: platform/video proof
|
||||
|
||||
- GS-stub test pattern through the platform video path.
|
||||
|
||||
Purpose:
|
||||
|
||||
- validate display plumbing,
|
||||
- validate retroDE integration,
|
||||
- isolate platform-output bugs from CPU/boot bugs.
|
||||
|
||||
### Milestone B: core/boot proof
|
||||
|
||||
- EE BIOS fetch and early trace match against DobieStation.
|
||||
|
||||
Purpose:
|
||||
|
||||
- validate memory visibility, reset vectors, and EE-side early execution,
|
||||
- isolate core bring-up bugs from display-path bugs.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Early work can proceed in parallel on platform-video and core-boot tracks.
|
||||
- "First success" is no longer overloaded into one giant milestone.
|
||||
- Minimal homebrew EE graphics is deferred to a later integration milestone.
|
||||
- The stub-module plan should explicitly map which stubs and traces are needed
|
||||
for Milestone A versus Milestone B.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Decision 0005: Phase 0 Source of Truth
|
||||
|
||||
Status: `Locked`
|
||||
|
||||
## Context
|
||||
|
||||
`docs/phase0_checklist.md` began as an options menu. Once the Phase 0 decisions
|
||||
were locked, the project needed one clear rule for which document is
|
||||
authoritative.
|
||||
|
||||
## Decision
|
||||
|
||||
Decision records under `docs/decisions/` are the source of truth for locked
|
||||
Phase 0 choices.
|
||||
|
||||
`docs/phase0_checklist.md` remains useful, but only as a progress and navigation
|
||||
document. When a checklist item is locked, it should point to the corresponding
|
||||
decision record.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The checklist can stay concise.
|
||||
- Locked decisions are not duplicated as prose in multiple places.
|
||||
- Future changes to posture, BIOS policy, milestone definition, or validation
|
||||
policy should update the decision records first.
|
||||
@@ -0,0 +1,113 @@
|
||||
# Decision 0006: VRAM Roadmap
|
||||
|
||||
Status: `In progress` — Ch251.4 near-term rescue applied, longer-term work
|
||||
queued.
|
||||
|
||||
## Context
|
||||
|
||||
The Ch251 hardware demo build (`de25_nano_psmct32_raster_demo_top`) failed the
|
||||
Quartus Fitter on Agilex 5 with **516 / 358 M20K** (144%). The Fitter resource
|
||||
report attributed ~410 M20Ks to two replicated `vram_bram_stub` banks:
|
||||
|
||||
```
|
||||
u_demo|u_vram|mem_rtl_0 Logical Size: 4194304 bits M20K blocks: 204.800
|
||||
u_demo|u_vram|mem_rtl_1 Logical Size: 4194304 bits M20K blocks: 204.800
|
||||
```
|
||||
|
||||
Root cause: `vram_bram_stub` exposes **1 write + 2 independent read ports**.
|
||||
An M20K block has at most two physical ports total (and at most one write
|
||||
port). To honour 1W + 2R, Quartus replicates the entire storage so each read
|
||||
port gets its own simple-dual-port BRAM, with the write fanned to both copies.
|
||||
True dual-port would not have rescued this — TDP still gives only 2 physical
|
||||
ports, not 3.
|
||||
|
||||
The two read ports serve distinct clients:
|
||||
|
||||
- **read** — PCRTC scanout (every pixel)
|
||||
- **read2** — PSMT4 RMW old-byte read on the rasterizer write path
|
||||
|
||||
The Ch251 build draws PSMCT32 sprites only. The PSMT4 RMW pipe is wired but
|
||||
never fires (`is_t4_emit` stays low), so read2 is dead weight on hardware.
|
||||
|
||||
## Decision (Near-Term — Ch251.4)
|
||||
|
||||
Add a parameter `ENABLE_READ2` to `vram_bram_stub`:
|
||||
|
||||
- Default `1` keeps every simulation TB and every PSMT4-exercising path
|
||||
byte-identical.
|
||||
- Hardware top (`de25_nano_psmct32_raster_demo_top`) overrides to `0`. When
|
||||
disabled, the read2 always_ff branch contains **no reference** to `mem`,
|
||||
so Quartus infers a single 1W+1R simple-dual-port BRAM (~205 M20Ks at
|
||||
512 KiB) instead of two replicas (~410 M20Ks).
|
||||
|
||||
This is a **scoped hardware-demo build profile**, not a general fix. It is
|
||||
correct only as long as the hardware build is PSMCT32 (or any non-PSMT4
|
||||
format). Any future hardware build that exercises PSMT4 RMW must either
|
||||
re-enable read2 (and accept the M20K cost) or first land the long-term
|
||||
architecture below.
|
||||
|
||||
## Decision (Long-Term)
|
||||
|
||||
Before the GS path expands beyond PSMCT32 on hardware (PSMT4 RMW, broader
|
||||
format coverage, or a larger framebuffer), replace the replicated-multi-read
|
||||
VRAM with one of:
|
||||
|
||||
1. **Arbitrated TDP VRAM scheduler** — one TDP backing memory. Port A serves
|
||||
PCRTC reads with priority; port B serves the writer / RMW path. PSMT4 RMW
|
||||
becomes multi-cycle and may stall raster writes. This is the most correct
|
||||
long-term FPGA shape.
|
||||
|
||||
2. **Line-buffer scanout** — PCRTC reads short bursts into a small line
|
||||
FIFO/line-buffer once per scanline, freeing the VRAM ports for writes for
|
||||
the rest of the line. More complex but closer to a scalable video
|
||||
architecture.
|
||||
|
||||
3. **Bank/tile partitioning** — split VRAM by banks so different clients
|
||||
typically hit different banks. Still needs conflict handling. Useful as a
|
||||
later optimization, not as the first replacement.
|
||||
|
||||
Eventually larger memory surfaces (≥ a few MiB of true PS2 VRAM, or the
|
||||
32 MiB main RAM) will need SDRAM/HPS/DDR-backed storage with tiled BRAM
|
||||
caches; the all-M20K convenience model does not scale.
|
||||
|
||||
## Triggers — when to revisit (Ch252)
|
||||
|
||||
Re-open this decision and land one of the long-term options above when
|
||||
**any** of the following becomes true on a hardware build:
|
||||
|
||||
1. **PSMT4 RMW returns to the rasterizer write path on hardware.** Any
|
||||
GS draw flow that consults `is_t4_emit` needs the second VRAM read
|
||||
port live, which re-introduces the replication cost.
|
||||
|
||||
2. **More than one VRAM read client during scanout.** The current
|
||||
profile is one read client (PCRTC). A second simultaneous read
|
||||
consumer — texture cache fetch, CLUT sampler from VRAM, secondary
|
||||
display window, anything that races PCRTC for read bandwidth —
|
||||
recreates the 1W+nR shape that forced Quartus replication in the
|
||||
first place.
|
||||
|
||||
3. **VRAM_BYTES grows beyond the current 512 KiB profile.** 512 KiB
|
||||
already costs ~205 M20Ks per replica at Agilex 5 packing. Any
|
||||
expansion (larger framebuffer, multi-format scratch space, texture
|
||||
storage) at the current replicated shape exceeds the device budget.
|
||||
|
||||
A simulation/elaboration tripwire in `vram_bram_stub.sv` fires
|
||||
(`$display` + `$fatal`) when `ENABLE_READ2 = 1` **and**
|
||||
`BYTES >= 262_144` (256 KiB). 256 KiB is not magical — it is the
|
||||
threshold above which replicated VRAM becomes a board-level
|
||||
architectural decision rather than a casual parameter flip. The
|
||||
tripwire is a loud canary in lint / sim; the **real protection is the
|
||||
board-top parameter profile**.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Ch251 ships on hardware with the read2-strip build profile. The
|
||||
bring-up runbook documents the override so anyone reading it later sees
|
||||
the explicit trade-off.
|
||||
- Simulation regressions stay byte-identical (default `ENABLE_READ2 = 1`).
|
||||
- Any chapter that re-enables PSMT4 on hardware **must** land an arbitrated
|
||||
/ line-buffered VRAM design first. Surfacing this as a decision record
|
||||
keeps it from quietly slipping when scope expands.
|
||||
- The Ch251 failure was a warning shot about VRAM strategy, not a fundamental
|
||||
blocker on the PS2 core. Actual 512 KiB framebuffer storage is ~205 M20Ks;
|
||||
the over-budget portion was the second full copy.
|
||||
@@ -0,0 +1,271 @@
|
||||
# 0007 — EE Core Reality Checkpoint (Ch306)
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-05-28
|
||||
Chapter: Ch306 (strategic recon / design — no RTL)
|
||||
Supersedes: nothing. Companion to 0006-vram-roadmap.md.
|
||||
Authors: lead architect, with Codex co-review.
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
`rtl/ee/ee_core_stub.sv` (2155 lines) is **a behavioral compatibility oracle, not a CPU.**
|
||||
|
||||
It is an interpreter-style multicycle FSM that has been grown chapter-by-chapter (Ch67 → Ch305) to boot `qbert.elf` ~1.49M instructions deep by adding, one blocker at a time, exactly the opcodes, syscall HLE cases, MMIO stubs, and testbench-side pokes that the next blocker demanded. It has been extraordinarily productive *as a discovery instrument*: it told us precisely which 67 instruction behaviors a real PS2 game touches during boot, which syscalls the EE kernel must service, and which MMIO regions matter. That is its value, and that value is real.
|
||||
|
||||
But it is now load-bearing in a way it was never designed to be. The owner and Codex have called the key question correctly: **we are about to confuse the oracle for the deliverable.** The stub mixes three layers that real hardware keeps strictly separate (CPU / BIOS-kernel / async-hardware), and several of the things that make qbert "boot" are fabrications — a `$v0=1` longjmp fib (Ch215) that *created* the BIOS treadmill we then chased for 50 chapters, an `$a0`-aware bit-17 syscall return (Ch294/0x7A) that fakes an interrupt that never fired, and a testbench poke (Ch299) that writes `1` into qbert's private global from inside the TB.
|
||||
|
||||
**Go / No-Go on a synthesizable R5900 subset: GO, with caveats.**
|
||||
|
||||
A deliberately-scoped multicycle R5900 subset (fetch/decode, 32-bit ALU, load/store, branches + delay slots, HI/LO, and the existing gpr128/MMI subset) is **straightforwardly synthesizable on Agilex 5 (DE25-Nano)**. There are no language-level blockers in the current RTL, the microarchitecture is a clean synchronous `always_ff` FSM with handshaked memory ports, and ~63 of the 67 decoded behaviors graduate essentially as-is. The path is **bounded and validatable**. The danger is not technical intractability — it is *layer confusion*: letting oracle hacks leak into the real core.
|
||||
|
||||
This document splits the work into two explicit, permanently-separate tracks and defines the graduation path.
|
||||
|
||||
- **Track A — EE Behavioral Oracle**: keep `ee_core_stub` as a *discovery-only* instrument. Its output is a living opcode/syscall/MMIO checklist. It is never the CPU.
|
||||
- **Track B — Synthesizable EE Core**: a new, clean core built to the checklist Track A produces, validated against the existing ~50 focused EE TBs (re-pointed for full-width semantics).
|
||||
|
||||
---
|
||||
|
||||
## 2. The Three-Layer Separation
|
||||
|
||||
Real PS2 hardware keeps three things in three places: the **CPU** executes instructions; the **BIOS/kernel ROM** services syscalls and implements `longjmp`/`_ReturnFromException`; **async hardware** (INTC / DMAC / GS / VBLANK / SIF) produces the events and flags that kernel code polls. The stub collapses all three into one FSM. The table below re-classifies every feature in the inventories by where it actually belongs.
|
||||
|
||||
| Stub feature | Layer | Graduates to Track B CPU? | Where it really belongs |
|
||||
|---|---|---|---|
|
||||
| SPECIAL ALU/shift/HILO set (SLL…SRAV, ADD…SLTU, MFHI/MFLO, MULTU, DIVU) | (a) CPU-architectural | **Yes** | CPU core |
|
||||
| Immediate ALU (ADDI…LUI), branches (BEQ/BNE/BLEZ/BGTZ + REGIMM BLTZ/BGEZ + BEQL/BNEL), jumps (J/JAL/JR/JALR) | (a) CPU-architectural | **Yes** | CPU core |
|
||||
| Loads/stores (LB/LH/LW/LBU/LHU + multi-beat LD/LQ/SD/SQ), SB/SH/SW | (a) CPU-architectural | **Yes** | CPU core |
|
||||
| MMI subset (PCPYLD/PSUBB/PNOR/PAND/PCPYUD/PCPYH) + gpr128 shadow | (a) CPU-architectural | **Yes** (if MMI in scope) | CPU core |
|
||||
| COP0 MFC0/MTC0/RFE/EI, SYNC, CACHE | (a) CPU-architectural (partial) | **Yes** (needs widening) | CPU core; RFE↔ERET to reconcile |
|
||||
| SYSCALL **exception-entry mechanism** (EPC / Cause.ExcCode=Sys / vector) | (a) CPU-architectural | **Yes** (the *mechanism* only) | CPU core |
|
||||
| SYSCALL **$v1 case table** (0x3C EndOfHeap, 0x3D InitMainThread, 0x40, 0x64 FlushCache, 0x6B, 0x77, 0x78, 0x79, 0x13, 0x17, 0x16, 0x12) | (b) BIOS/kernel HLE | **No** | PS2 BIOS ROM, or a dedicated EE-kernel HLE companion module between CPU and memory map |
|
||||
| Ch199 `_ReturnFromException(2)` RFE-on-syscall-8 shortcut | (b) BIOS/kernel HLE | **No** | BIOS kernel exception-return path (ROM). The status-stack pop is architectural; *selecting it by syscall number* is kernel behavior |
|
||||
| Ch215 `jmp_buf` restore FSM (hardcoded base `0xA000B1E0`, 12-slot libc layout, forced `$v0=1`) | (b) BIOS/kernel HLE | **No** | BIOS ROM `longjmp()`. **This `$v0=1` fib is the documented source of the Ch215 treadmill (Ch269).** It is a workaround, not behavior |
|
||||
| Syscall 0x7A `$a0`-aware bit-17 readiness return | (c) async-hardware stand-in | **No** | INTC/DMAC-completion/event delivery (real interrupt fires the flag). Labeled "Not architectural truth" |
|
||||
| Ch299 TB-side library-ready poke (`useg_shadow_mem[0x4CA70]=1` on qbert-specific arg guard) | (c) async-hardware stand-in | **No** | Memory side effect of the RegisterLibraryEntries (0x77) kernel callback. **Most fragile, ship-blocking hack in the inventory** |
|
||||
| Syscall 0x12/0x16 (Add/EnableDmacHandler) registration | (b) BIOS/kernel HLE → (c) | **No** | Kernel handler table; the *enable* arms real INTC/DMAC dispatch (unbuilt hardware) |
|
||||
| Syscall default-case halt (`retired_flag_halt` → S_HALT, expose $v1/$a0-$a3) | (c) TB-only scaffolding | **No** | Diagnostic only; real CPU vectors to kernel |
|
||||
| Trace port cluster (`ev_*` + `retired_*` shadows) | (c) TB-only scaffolding | **No** (strip) | Test instrumentation; no hardware counterpart |
|
||||
| Per-syscall runner observers (snapshots, tuple tables, $a0 counters) | (c) TB-only scaffolding | **No** | Passive measurement; correct to live in the TB |
|
||||
| BIOS reset-vector LUI/ORI/JR trampoline + ELF `$readmemh` loader | (c) TB-only scaffolding | **No** | Real BIOS boot + program loader |
|
||||
|
||||
**The crisp rule:** the CPU core contains *faithful instructions and the exception-entry mechanism, and nothing else.* Every syscall service moves to a BIOS/HLE companion. Every fabricated flag moves to the async-hardware layer (and until that hardware exists, it stays in the oracle/TB — never in the real core).
|
||||
|
||||
---
|
||||
|
||||
## 3. Track A — EE Behavioral Oracle
|
||||
|
||||
**Role: discovery only. This is `ee_core_stub` as it exists today, plus the ELF runner harness.**
|
||||
|
||||
Track A continues exactly as Ch67→Ch305 did: when a new game/BIOS path blocks, Track A finds out *why* and *what is missing*, cheaply, by adding the minimum stub behavior to push past the blocker. It is allowed to lie (the `$v0=1` fib, the bit-17 fake, the TB poke) because its job is to *map the territory*, not to be the territory.
|
||||
|
||||
**Output: a living checklist.** Track A's deliverable is not silicon — it is three growing lists:
|
||||
|
||||
1. **Opcode checklist** — every instruction a real workload touches, with required fidelity (see §6).
|
||||
2. **Syscall checklist** — every EE kernel service number, its observed arg shape, and its required return contract.
|
||||
3. **MMIO checklist** — every device region touched (DMAC global/per-channel, INTC, timers, GIF, SIF), with the access pattern.
|
||||
|
||||
These lists are the *specification* Track B builds to. Every entry on them is evidence-backed by a real boot trace, which is worth more than any datasheet table because it tells us what *actually matters* for the games we run.
|
||||
|
||||
**The one inviolable rule:** Track A output must **never be mistaken for the CPU.** Specifically:
|
||||
- An oracle hack (`$v0=1`, bit-17, TB poke) is a *flag that hardware is missing*, not a feature to copy. When Track B implements the real mechanism, the corresponding oracle hack must be **backed out**, and a TB must prove the real mechanism produces the same observable result the hack faked.
|
||||
- Any conclusion drawn "after the Ch215 shim fires" must be labeled "under jmp_buf fallback semantics" (per the Ch269 finding). Track A conclusions downstream of a known fib are suspect by construction.
|
||||
|
||||
---
|
||||
|
||||
## 4. Track B — Synthesizable EE Core
|
||||
|
||||
**A new, clean RTL core (`rtl/ee/ee_core.sv`, distinct from `ee_core_stub.sv`), built deliberately to the Track A checklist.**
|
||||
|
||||
### 4.1 The first synthesizable subset (concrete)
|
||||
|
||||
Scope the first Track B core to exactly what qbert boot proves is needed, and no more:
|
||||
|
||||
- **Fetch / decode / retire**: handshaked instruction fetch over the existing BIU/memory-map ports; fully combinational decode (the `is_*` assign pile is fine).
|
||||
- **32-bit integer ALU**: SLL/SRL/SRA/SLLV/SRLV/SRAV, ADD/ADDU/SUB/SUBU, AND/OR/XOR/NOR, SLT/SLTU, all immediate forms (ADDI/ADDIU/SLTI/SLTIU/ANDI/ORI/LUI). **Add the Arithmetic Overflow trap** for ADD/SUB/ADDI (the stub defers it; a real core must trap, Cause.ExcCode=12).
|
||||
- **HI/LO**: MFHI/MFLO/MTHI/MTLO, MULTU (infers DSP), and DIVU **as a multi-cycle iterative divider FSM** (not the combinational `/`+`%` — see §4.3).
|
||||
- **Load/store**: LB/LH/LW/LBU/LHU/SB/SH/SW with AdEL/AdES alignment exceptions, plus multi-beat LD/LQ/SD/SQ via the proven `sq_beat` counter pattern.
|
||||
- **Branches + delay slots**: BEQ/BNE/BLEZ/BGTZ, REGIMM BLTZ/BGEZ, branch-likely BEQL/BNEL (squash semantics), jumps J/JAL/JR/JALR. Keep the `branch_pending` latch model.
|
||||
- **128-bit GPR + MMI subset**: `gpr128[0:31]` and PCPYLD/PSUBB/PNOR/PAND/PCPYUD/PCPYH. **Gate this behind a parameter** (`EE_ENABLE_MMI`) so a minimal build can fall back to a 32×32 regfile and save ~4096 FFs.
|
||||
- **COP0**: MFC0/MTC0 for the 5 modeled regs + the proper **exception-entry mechanism** (EPC save, Cause.ExcCode, BEV vectoring) and **ERET** (reconciled against the stub's R3000-style RFE — R5900 uses EXL/ERL/EPC). SYNC and CACHE are faithful no-ops on a cacheless in-order core.
|
||||
|
||||
**Explicitly out of the first subset:** the syscall $v1 table (moves to a BIOS/HLE companion fed by the real SYSCALL exception), COP0 64-bit upper lanes beyond what MMI needs, FPU/COP1, VU0/VU1 macro-mode, and full TLB. These are later chapters or separate tracks.
|
||||
|
||||
### 4.2 Recommended microarchitecture: **start multicycle/interpreter-style, pipeline later**
|
||||
|
||||
Keep the current 8-state FSM shape (S_IFETCH_REQ → S_IFETCH_WAIT → S_EXECUTE → optional S_MEM_*; drop the two Ch215 shim states). **Reasons:**
|
||||
|
||||
1. **It already synthesizes cleanly.** The synthesizability assessment is unambiguous: clean synchronous `always_ff`, handshaked ports, no latches (both `unique case` blocks carry defaults), constant-bound loops. There are *no language-level blockers*.
|
||||
2. **It is the correct altitude for first-silicon correctness.** A multicycle core has no hazards, no forwarding, no branch prediction — delay slots are a single `branch_pending` latch. This is the smallest correct design, and correctness-first is the only sane order when the goal is "prove a real R5900 RTL works."
|
||||
3. **Pipelining is a pure-performance follow-on**, addable once the multicycle core passes the full TB suite and boots qbert. The R5900 is a dual-issue in-order pipeline; that is a *known, bounded* later effort, not a prerequisite for graduation.
|
||||
4. **It matches the proven `iop_core_stub` shape**, so the platform integration patterns already exist.
|
||||
|
||||
Minimum ~4 cycles/instruction is acceptable for bring-up. The DE25-Nano has the headroom.
|
||||
|
||||
### 4.3 What must be stripped / gated for synthesis
|
||||
|
||||
From the synthesizability assessment, ranked:
|
||||
|
||||
- **STRIP_HW_DIVIDER=1 is mandatory** for any fit. The inferred combinational divider is the documented ~32 ns STA critical path (Ch162). Track B must replace it with a **multi-cycle sequential divider FSM** if DIVU semantics are needed (they are — qbert uses it).
|
||||
- **Strip the trace port cluster** (`ev_valid/ev_subsys/ev_event/ev_arg0-3/ev_flags` + the `retired_*` shadow registers + the divu/multu trace arms). These are pure observability (~4×64 + 32 + several 32-bit FFs of dead weight) that force the synthesizer to keep otherwise-dead arg-computation logic. Replace with a thin, optional debug-readout port if needed.
|
||||
- **Gate the gpr128 shadow** (`EE_ENABLE_MMI`). 32×128 = 4096 FFs is the dominant flop cost and Quartus will build it in ALMs (async multi-port read), not M20K. Keep only if MMI/quadword is in scope.
|
||||
- **The CH215 jmp_buf FSM and the EE_SYSCALL_HLE dispatcher do not enter Track B at all.** In the stub they are param-gated OFF; in Track B they are simply absent — they move to the BIOS/HLE companion.
|
||||
- `unique case`, constant-bound for-loops: **keep** (not blockers; defaults prevent latches).
|
||||
|
||||
---
|
||||
|
||||
## 5. Validation Strategy
|
||||
|
||||
**The existing ~50 focused `tb_ee_core_*` benches + the qbert boot path ARE Track B's compliance harness.** This is the single strongest asset we have, and it directly answers the owner's worry.
|
||||
|
||||
### 5.1 Why the existing suite transfers
|
||||
|
||||
The compliance inventory confirms **all 50 focused TBs are reusable** with only mechanical adaptation. The uniform pattern is port-driven: each TB hand-assembles a tiny program into the BIOS/bootstrap slots, lets the DUT fetch/decode/execute *through the public memory-map ports* to a PASS-syscall halt, then checks results. **Step 2 (execution) is already fully port-driven — there are no internal pokes to make the core run.** Many TBs embed an in-program BNE/BEQ-to-FAIL self-check, so the expected architectural behavior is encoded in the program itself and is checkable purely from observable halt-PC/RAM. There is a strict 1:1 opcode→TB discipline (Ch271–Ch293), so **there are no implemented-but-untested opcodes.**
|
||||
|
||||
### 5.2 The two required adaptations (both mechanical, both bounded)
|
||||
|
||||
1. **Hierarchical-peek → architectural readout.** Most TBs read the *post-halt* result via `u_core.regfile[...]` (and `u_ee_ram.mem[]`/`u_bios.mem[]` for stores). Against a renamed/synthesized core these peeks break. Fix: change each test program to **store its result register to a known RAM/MMIO address** and read it back through the map port. This is a per-TB swap that does not change the encoded expected behavior. Store-class TBs (memops, sb, sh, sd, sq, lq, ld) already verify partly through `u_ee_ram.mem[]` and are closest to a real memory boundary.
|
||||
|
||||
2. **Stub-accurate golden values → architecture-accurate golden values.** Several TBs deliberately encode *simplified* semantics: DADDU/DSUBU/DSLL as low-32 only, and (per stale comments — actually now full-128 via gpr128) the SQ/SD/LQ/LD width expectations. Against a true 64/128-bit Track B core, the low-32 expectations would FAIL and **must be upgraded to full-width**. The TBs are reusable as scaffolding and as behavior encodings; their golden values need a width pass.
|
||||
|
||||
### 5.3 Known coverage gaps to close (new TBs for Track B)
|
||||
|
||||
- **gpr128 invariant**: add a dedicated TB asserting `gpr128[i][31:0] === regfile[i]` directly (today only transitive via PCPYUD/etc.).
|
||||
- **COP0 exception state**: EPC save/restore, ERET, Cause.ExcCode encoding — no focused TB today beyond BEV and Count. This is the *most important* new TB, because the SYSCALL exception-entry mechanism is the CPU's only legitimate connection to the kernel.
|
||||
- **Arithmetic Overflow trap** for ADD/SUB/ADDI (stub defers it; Track B implements it).
|
||||
- **DI positive semantics** (today only a negative/still-trapping companion in tb_ee_core_ei).
|
||||
|
||||
### 5.4 Directly addressing the owner's worry
|
||||
|
||||
> *"Are we even able to verify a real R5900 RTL would work / model the hardware to finalize?"*
|
||||
|
||||
**Yes — and we are unusually well-positioned to, for three concrete reasons:**
|
||||
|
||||
1. **We have a behavioral golden model.** Track A (the stub) is, for the scoped subset, a working executable specification. Track B can be **co-simulated against Track A instruction-by-instruction**: run the same program through both, compare retire-by-retire (PC, GPR writeback, memory effects). Divergence is an immediate, localized bug report. This is the gold-standard CPU-verification methodology (lockstep against a reference model), and we already own the reference model.
|
||||
|
||||
2. **We have an evidence-backed requirements list.** We are not guessing what an R5900 needs — qbert's 1.49M-instruction boot trace *tells us* exactly the opcode/syscall/MMIO surface that matters. Track B's "done" is defined by a real workload, not a datasheet wishlist.
|
||||
|
||||
3. **We have a port-driven, near-complete compliance suite** (§5.1) that runs entirely through the public bus interface — i.e., it validates the core the same way the rest of the system will use it.
|
||||
|
||||
**The honest qualifier:** "verify a *real R5900*" means verify the *scoped subset we implement*, in lockstep against the oracle and the TB suite, booting the workloads we target. It does **not** mean bit-exact cycle-accuracy against Sony silicon (multiply/divide latency, dual-issue timing, cache timing are not modeled and are out of scope for first-silicon). For a "boots and runs the game correctly" goal — which is the project goal — that scope is sufficient and verifiable. For a "cycle-perfect deterministic netplay" goal it is not, and we should not pretend otherwise.
|
||||
|
||||
---
|
||||
|
||||
## 6. Master Opcode / Feature Checklist
|
||||
|
||||
This is the deliverable Codex asked for: every decoded behavior, its fidelity, whether it is synthesizable, and whether it graduates to the Track B CPU core.
|
||||
|
||||
| Mnemonic | Encoding | Fidelity | Synth | Graduates |
|
||||
|---|---|---|---|---|
|
||||
| SLL | SPECIAL 0x00 | faithful | yes | **Yes** |
|
||||
| SRL | SPECIAL 0x02 | faithful | yes | **Yes** |
|
||||
| SRA | SPECIAL 0x03 | faithful | yes | **Yes** |
|
||||
| SLLV | SPECIAL 0x04 | faithful | yes | **Yes** |
|
||||
| SRLV | SPECIAL 0x06 | faithful | yes | **Yes** |
|
||||
| SRAV | SPECIAL 0x07 | faithful | yes | **Yes** |
|
||||
| JR | SPECIAL 0x08 | faithful | yes | **Yes** |
|
||||
| JALR | SPECIAL 0x09 | faithful | yes | **Yes** |
|
||||
| SYSCALL | SPECIAL 0x0C | hle_or_shim | needs_work | **No** (only the exception-entry mechanism graduates; the $v1 table is kernel HLE) |
|
||||
| SYNC | SPECIAL 0x0F | faithful | yes | **Yes** |
|
||||
| MFHI | SPECIAL 0x10 | faithful | yes | **Yes** |
|
||||
| MFLO | SPECIAL 0x12 | faithful | yes | **Yes** |
|
||||
| MULTU | SPECIAL 0x19 | faithful | yes | **Yes** (infers DSP; latency not modeled) |
|
||||
| DIVU | SPECIAL 0x1B | faithful | needs_work | **Yes** (needs multi-cycle iterative divider; STRIP_HW_DIVIDER for fit) |
|
||||
| DSLL | SPECIAL 0x38 | low32_approx | yes | **Yes** (needs full 64-bit shifter + DSLL32) |
|
||||
| ADD | SPECIAL 0x20 | faithful | yes | **Yes** (needs overflow trap, ExcCode 12) |
|
||||
| ADDU | SPECIAL 0x21 | faithful | yes | **Yes** |
|
||||
| DADDU | SPECIAL 0x2D | low32_approx | yes | **Yes** (needs full 64-bit adder) |
|
||||
| SUB | SPECIAL 0x22 | faithful | yes | **Yes** (needs overflow trap) |
|
||||
| SUBU | SPECIAL 0x23 | faithful | yes | **Yes** |
|
||||
| DSUBU | SPECIAL 0x2F | low32_approx | yes | **Yes** (needs full 64-bit subtract) |
|
||||
| AND | SPECIAL 0x24 | faithful | yes | **Yes** |
|
||||
| OR | SPECIAL 0x25 | faithful | yes | **Yes** |
|
||||
| XOR | SPECIAL 0x26 | faithful | yes | **Yes** |
|
||||
| NOR | SPECIAL 0x27 | faithful | yes | **Yes** |
|
||||
| SLT | SPECIAL 0x2A | faithful | yes | **Yes** |
|
||||
| SLTU | SPECIAL 0x2B | faithful | yes | **Yes** |
|
||||
| BLTZ | REGIMM rt=0x00 | faithful | yes | **Yes** (BLTZAL link variant not modeled) |
|
||||
| BGEZ | REGIMM rt=0x01 | faithful | yes | **Yes** (BGEZAL link variant not modeled) |
|
||||
| J | 0x02 | faithful | yes | **Yes** |
|
||||
| JAL | 0x03 | faithful | yes | **Yes** |
|
||||
| BEQ | 0x04 | faithful | yes | **Yes** |
|
||||
| BNE | 0x05 | faithful | yes | **Yes** |
|
||||
| BLEZ | 0x06 | faithful | yes | **Yes** |
|
||||
| BGTZ | 0x07 | faithful | yes | **Yes** |
|
||||
| ADDI | 0x08 | faithful | yes | **Yes** (needs overflow trap) |
|
||||
| ADDIU | 0x09 | faithful | yes | **Yes** |
|
||||
| SLTI | 0x0A | faithful | yes | **Yes** |
|
||||
| SLTIU | 0x0B | faithful | yes | **Yes** |
|
||||
| ANDI | 0x0C | faithful | yes | **Yes** |
|
||||
| ORI | 0x0D | faithful | yes | **Yes** |
|
||||
| LUI | 0x0F | faithful | yes | **Yes** |
|
||||
| MFC0 | COP0 rs=0x00 | low32_approx | yes | **Yes** (only 5 regs modeled; Count at full clock vs half) |
|
||||
| MTC0 | COP0 rs=0x04 | low32_approx | yes | **Yes** (partial Status/Cause fields; Count write dropped) |
|
||||
| RFE | COP0/CO funct 0x10 | faithful | yes | **Yes** (reconcile vs R5900 ERET) |
|
||||
| EI | COP0/CO 0x42000038 | low32_approx | yes | **Yes** (should set Status.EIE; companion DI still traps) |
|
||||
| LB | 0x20 | faithful | yes | **Yes** |
|
||||
| LH | 0x21 | faithful | yes | **Yes** |
|
||||
| LW | 0x23 | faithful | yes | **Yes** |
|
||||
| LBU | 0x24 | faithful | yes | **Yes** |
|
||||
| LHU | 0x25 | faithful | yes | **Yes** |
|
||||
| LD | 0x37 | faithful | yes | **Yes** (full 64-bit via gpr128) |
|
||||
| LQ | 0x1E | faithful | yes | **Yes** (full 128-bit via gpr128) |
|
||||
| SB | 0x28 | faithful | yes | **Yes** |
|
||||
| SH | 0x29 | faithful | yes | **Yes** |
|
||||
| SW | 0x2B | faithful | yes | **Yes** |
|
||||
| SD | 0x3F | faithful | yes | **Yes** (full 64-bit via gpr128; stale "beat1=0" comments) |
|
||||
| SQ | 0x1F | faithful | yes | **Yes** (full 128-bit via gpr128; stale "beats 1-3=0" comments) |
|
||||
| CACHE | 0x2F | hle_or_shim | yes | **Yes** (no-op correct for cacheless model) |
|
||||
| PCPYLD | MMI2 sa 0x0E | faithful | yes | **Yes** (full-128) |
|
||||
| PSUBB | MMI0 sa 0x09 | faithful | yes | **Yes** (full-128, no cross-byte borrow) |
|
||||
| PNOR | MMI3 sa 0x13 | faithful | yes | **Yes** (full-128) |
|
||||
| PAND | MMI2 sa 0x12 | faithful | yes | **Yes** (full-128) |
|
||||
| PCPYUD | MMI3 sa 0x0E | faithful | yes | **Yes** (reads upper 64; drove gpr128) |
|
||||
| PCPYH | MMI3 sa 0x1B | faithful | yes | **Yes** (full-128 halfword broadcast) |
|
||||
| BEQL | 0x14 | faithful | yes | **Yes** (branch-likely squash) |
|
||||
| BNEL | 0x15 | faithful | yes | **Yes** (branch-likely squash) |
|
||||
| NOP | 0x00000000 | faithful | yes | **Yes** |
|
||||
|
||||
**Tally: 63 of 67 decoded behaviors graduate to the Track B CPU core.** The 4 that do not: SYSCALL (only its exception-entry mechanism graduates; the $v1 table is kernel HLE) — and that is the only true non-graduate, since CACHE graduates as an accepted no-op. The genuinely-approximate-but-graduating ops are DADDU/DSUBU/DSLL (need full 64-bit datapath) and MFC0/MTC0/EI (need fuller COP0 coverage). **The MMI/128-bit infrastructure is the strongest, most faithful part of the stub and is genuinely synthesizable.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Go / No-Go + Recommended Next Chapters
|
||||
|
||||
**Does a scoped R5900 subset fit Agilex 5 and pass the TBs? — YES, with the §4.3 caveats honored.**
|
||||
|
||||
- **Fit**: Agilex 5 has hundreds of K ALMs. The dominant cost is gpr128 (~4096 FFs) — wasteful but not fatal, and gateable. MULTU infers DSP (fine). The divider must be stripped/replaced. The trace cluster should be stripped. No structural blocker.
|
||||
- **TBs**: passes in simulation against the stub as-is; a stripped/gated synthesis config (no trace, divider replaced, HLE absent) needs the §5.2 adaptations (architectural readout + full-width golden values) and the §5.3 new TBs. Hence **go_with_caveats**, not unqualified go.
|
||||
|
||||
### Track A — next chapters (discovery only)
|
||||
|
||||
- **Ch307**: Autopsy the next qbert wait loop (post-Ch294/0x7A unblock; the steady-state hot-PC, e.g. the suspected `0x00106154` region). Classify the gate (memory flag? MMIO poll? handler-fire?) the same way Ch294 did. **No RTL** — produce the checklist entry, not a hack, unless a one-shot stub is the cheapest way to see the *next* blocker.
|
||||
- **Ch308 (A)**: Begin backing out fabrications into the async-hardware layer: replace the Ch299 TB poke with the real RegisterLibraryEntries (0x77) memory side effect, modeled in the HLE companion, and prove qbert still progresses. This *de-risks* Track B by validating the real mechanism in the cheap environment first.
|
||||
- **Ch309 (A)**: Capture a full lockstep retire-trace export from the oracle for a fixed qbert prefix, to serve as Track B's co-sim golden reference (§5.4.1).
|
||||
|
||||
### Track B — next chapters (the real core)
|
||||
|
||||
- **Ch308 (B)**: Scaffold `rtl/ee/ee_core.sv` — a clean multicycle skeleton: fetch/decode/retire FSM + 32-bit ALU (SLL…SLTU, ADDI…LUI) + HI/LO + branches/delay slots. **Validate immediately against the existing ALU/shift/branch TBs** (tb_ee_core_shift, _varshift, _rtype_logic, _rtype_addu, _add_sub, _slt, _slti, _branch_zero, _jal, _jalr) re-pointed to architectural readout. No MMI, no MMIO, no syscalls yet.
|
||||
- **Ch309 (B)**: Add load/store (LB…SW + multi-beat LD/LQ/SD/SQ) with AdEL/AdES, and the multi-cycle DIVU FSM (replacing the combinational divider). Validate against _memops, _lb/_lbu/_lh/_lhu/_sb/_sh, _ld/_lq/_sd/_sq, _align/_align_exc, _divu_mflo, _multu_mflo.
|
||||
- **Ch310 (B)**: Add the COP0 exception-entry mechanism (EPC/Cause.ExcCode/BEV vectoring) + ERET, plus the gated gpr128/MMI subset. Add the **new** TBs: gpr128 invariant, COP0 exception state, overflow trap. Wire the SYSCALL exception to vector into the BIOS/HLE companion (not an internal $v1 switch). First lockstep co-sim run against the Ch309(A) golden trace.
|
||||
|
||||
---
|
||||
|
||||
## 8. Risks / Rabbit-Holes to Avoid
|
||||
|
||||
**Be honest about what could make this unrecoverable — and what is merely hard.**
|
||||
|
||||
1. **THE primary risk: conflating oracle hacks into the real core.** This is the single thing that turns a bounded project into an unrecoverable one. If the `$v0=1` fib, the bit-17 fake, or a syscall stub leaks into `ee_core.sv`, Track B becomes a second oracle wearing a CPU costume — and we will chase phantom "blockers" (the Ch264–Ch268 thunk-chain hunt is the cautionary tale: 5 chapters chasing a treadmill that was *our own shim*, per Ch269). **Mitigation: the §2 rule is non-negotiable — the CPU core contains faithful instructions + exception entry, full stop. Every backed-out hack gets a TB proving the real mechanism reproduces the faked result.**
|
||||
|
||||
2. **GS fillrate and VU0/VU1 parallelism are separate mountains — do not let them contaminate the EE-core decision.** The EE *integer/MMI core* is tractable and is what this document scopes. The GS (rasterizer fillrate, VRAM bandwidth — see 0006-vram-roadmap) and the VUs (two SIMD vector coprocessors with their own microcode, macro/micro mode, and tight EE coupling) are each *larger* than the EE core and have their own roadmaps. **Risk: scope creep that bundles "boot qbert's CPU code" with "render qbert's graphics." Keep them separate; the EE core graduating does NOT imply the frame renders.** FPU/COP1 is a smaller but real adjacent piece, also deferred.
|
||||
|
||||
3. **Cycle-accuracy ambition.** If the goal silently drifts from "boots and runs correctly" to "cycle-perfect," the project becomes unbounded (multiply/divide latency, dual-issue scheduling, cache timing, bus contention). **Mitigation: §5.4 names the scope explicitly. First-silicon is behavioral correctness, not timing fidelity.**
|
||||
|
||||
4. **The divider critical path.** Known, measured (~32 ns, Ch162), and already gated. The only risk is *forgetting* to replace it with a sequential FSM when DIVU semantics are required. Tracked as a Ch309(B) deliverable.
|
||||
|
||||
5. **TB golden-value drift.** Several TBs encode stub-accurate (low-32 / truncated) golden values. If Track B is validated against *unmodified* stub TBs, a correct full-width core will FAIL spuriously, or worse, a buggy core will PASS against a too-lax expectation. **Mitigation: the §5.2 width pass is a prerequisite, not an afterthought.**
|
||||
|
||||
6. **Hierarchical-peek brittleness.** Not unrecoverable, but if ignored it blocks the entire compliance suite from running against the new core. Mechanical (§5.2.1) but must be budgeted.
|
||||
|
||||
**Bottom line: the EE core itself is tractable, bounded, and validatable.** We have a golden behavioral model, an evidence-backed requirements list, and a near-complete port-driven compliance suite — three assets most from-scratch CPU projects never have. The path is not a rabbit hole *provided* we hold the layer separation. The unrecoverable scenarios all share one root cause — letting the oracle and the CPU be the same artifact. This document exists to make sure they never are.
|
||||
@@ -0,0 +1,201 @@
|
||||
# 0008 — GS Tiled-VRAM Feasibility Baseline + Test #2 Spec
|
||||
|
||||
Status: Accepted (baseline); Test #2 = spec only, not implemented
|
||||
Date: 2026-05-28
|
||||
Chapter: Phase-3 hardware de-risk (LPDDR4B bandwidth spike) → GS architecture pivot
|
||||
Supersedes: nothing. Companion to 0006-vram-roadmap.md and 0007-ee-core-reality-checkpoint.md.
|
||||
Authors: lead architect, with Codex co-review and a parallel outside-perspective review.
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
The single missing number that gated the whole "is a faithful-enough PS2 GS
|
||||
physically possible on this board?" question has been **measured on real
|
||||
silicon**, and it clears the first gate cleanly.
|
||||
|
||||
A standalone HPS-coexistent diagnostic core (`de25_lpddr4_bw`, an ao486-cloned
|
||||
shell + a saturating AXI4 traffic generator on the FPGA-side LPDDR4B EMIF)
|
||||
sustained, over a 256 MiB sequential stream at the EMIF user clock (310 MHz
|
||||
exactly):
|
||||
|
||||
| phase | cycles | sustained |
|
||||
|------|------|------|
|
||||
| write | 9,786,835 | **8.50 GB/s** |
|
||||
| read | 9,913,927 | **8.39 GB/s** |
|
||||
|
||||
- **~86%** of the 256-bit fabric port (≈27.4 of 32 bytes/cycle).
|
||||
- **~79%** of the ~10.7 GB/s LPDDR4 PHY peak. (Both ceilings, one consistent result.)
|
||||
- **Read ≈ write** at `MAX_OUTSTANDING=16` → the bus is **bandwidth-bound, not
|
||||
latency-bound**. Nothing to sweep; the number is trustworthy as-is.
|
||||
|
||||
**Verdict on the bandwidth gate: GREEN.** Before measuring, the working
|
||||
assumption was "probably impossible." 8.4 GB/s sustained changes the tone to
|
||||
**"feasible *if* the GS is architected around tiling from day one."** The board
|
||||
is not killed by LPDDR bandwidth. Full-4 MB-VRAM-in-M20K remains off the table
|
||||
(0006); the tiled-VRAM path is no longer fantasy.
|
||||
|
||||
**The gate still standing is texture + locality**, not raw sequential
|
||||
bandwidth. Test #1 measured framebuffer-shaped sequential traffic. It cannot
|
||||
see random texture reads, CLUT indirection, or the tile-reload churn that
|
||||
primitive disorder and alpha-overdraw produce. **Test #2** — a tiled-raster
|
||||
microbenchmark driven by *real game traffic* — is the measurement that finally
|
||||
answers "faithful-enough GS on this board: yes or no." This document specs it;
|
||||
it does not implement it.
|
||||
|
||||
---
|
||||
|
||||
## 2. Test #1 — the measured baseline (authoritative)
|
||||
|
||||
- **Memory under test:** FPGA-side LPDDR4B, 1 GB, 32-bit, 2666 MT/s (DE25-Nano
|
||||
Rev B), via the same `EMIF_Qsys` hard-IP ao486 ships. User port: **256-bit
|
||||
AXI4 @ 310 MHz** (IOPLL ×62/10 off the 50 MHz reference — exact).
|
||||
- **Theoretical ceilings:** ~9.92 GB/s if you count the 256-bit (32-byte) port
|
||||
at 310 MHz; ~10.7 GB/s from the DRAM PHY (32-bit × 2666 MT/s). These are the
|
||||
same physical limit viewed two ways. (Historical note: an earlier "78 GB/s"
|
||||
was a bits-treated-as-bytes error — do not resurrect it.)
|
||||
- **Method:** saturating sequential write phase then read phase over 256 MiB,
|
||||
4 KiB AXI-legal bursts (128 beats × 32 B; see note), up to 16 in flight, raw
|
||||
emif_clk cycle counts exposed — GB/s computed off-chip so no Fmax assumption
|
||||
is baked in.
|
||||
- **Conclusion:** sequential tile-stream bandwidth is **viable**; no need to
|
||||
sweep outstanding-count; result internally consistent against both ceilings.
|
||||
- **Caveat (explicit):** does **not** model random texture reads, CLUT, Z, or
|
||||
alpha-blend / framebuffer RMW behavior. That is Test #2.
|
||||
|
||||
> Bring-up footnote (durable lesson): the first board run flagged a bresp
|
||||
> error because the bursts were 8 KiB (AWLEN=255), which violates the AXI4
|
||||
> 4 KiB-boundary rule; the EMIF NAK'd them with SLVERR. Fixed to 4 KiB bursts
|
||||
> (AWLEN=127). **Any future AXI master in this family — including the GS
|
||||
> tiled-VRAM DMA — must cap bursts at 4 KiB.** See [[reference-lpddr4-bw-spike]].
|
||||
|
||||
---
|
||||
|
||||
## 3. What LPDDR4 actually carries per frame in a tiled design
|
||||
|
||||
Tiling moves framebuffer/Z **read-modify-write** on-chip (M20K), so the three
|
||||
things crossing the DDR boundary per frame are wildly unequal:
|
||||
|
||||
1. **Framebuffer writeout (tile flush to DDR): trivial.** 640×480×4 B ≈ 1.2
|
||||
MB/frame → ~70 MB/s @ 60 fps. Noise against 8.4 GB/s. Ignore it.
|
||||
2. **Texture fetch: the dominant unknown.** Textures are too large to sit in
|
||||
M20K beside the framebuffer tile, so they stream from DDR. Locality-driven.
|
||||
3. **Tile reload from primitive disorder.** When primitives don't arrive in
|
||||
tile order, a tile gets evicted and re-fetched. Also locality-driven.
|
||||
|
||||
Items 2 and 3 are why a synthetic test would *lie* and the emulation traces are
|
||||
the only honest source of truth: both depend on real access patterns and
|
||||
working-set shape, not on raw throughput.
|
||||
|
||||
---
|
||||
|
||||
## 4. The PS2-specific tilt: palettized textures
|
||||
|
||||
PS2 textures were overwhelmingly **palettized — 4-bit and 8-bit indexed through
|
||||
CLUT**, not 32-bit RGBA. That is a **quarter to an eighth** the per-texel DRAM
|
||||
traffic of the naive 32-bit assumption. Budgeting texture bandwidth as if every
|
||||
texel were 32-bit would massively overestimate the wall.
|
||||
|
||||
**Prerequisite measurement before any Test #2 RTL:** a **texture-format
|
||||
histogram** — what fraction of texel fetches are 4-bit / 8-bit / 16-bit /
|
||||
32-bit, plus the **overdraw factor** on busy scenes. That histogram sizes the
|
||||
entire texture-bandwidth question before a line of RTL is written.
|
||||
|
||||
> **REALITY CHECK (2026-05-28, post-review):** an outside review assumed this
|
||||
> histogram could be "extracted from the trace library / the 301 chapters."
|
||||
> **It cannot — no real-game GS trace corpus exists in-repo.** A full-disk
|
||||
> search confirmed every GS/texture artifact here is synthetic (hand-authored
|
||||
> testbench sprites, `bake.py` test cards), and the two live-emulator capture
|
||||
> harnesses (DobieStation, PCSX2) are parked/blocked (`sim/golden/README.md`,
|
||||
> `third_party/*/NOTES.md`). The 301 chapters are EE-opcode/BIOS work, not GS
|
||||
> captures. So this number must be **captured fresh, not extracted.** Building
|
||||
> Test #2 against an assumed distribution would be the GS-side repeat of the
|
||||
> Ch215 oracle-confusion trap. The realistic source is **PCSX2 GS dumps**
|
||||
> (`.gs`/`.gs.xz` — a built-in PCSX2 GS-debugger feature that records real
|
||||
> GIF + privileged-register traffic, incl. TEX0/CLUT, replayable offline);
|
||||
> a prebuilt PCSX2 binary sidesteps the in-repo CMake-deps block. The
|
||||
> prerequisite to the prerequisite is therefore **acquiring real GS dumps**
|
||||
> (needs PCSX2 + games the owner owns), then a software `.gs` parser (no RTL).
|
||||
|
||||
---
|
||||
|
||||
## 5. Test #2 — Tiled-Raster Microbenchmark Spec
|
||||
|
||||
Goal: measure **sustained DDR bandwidth and tile-reload rate under real PS2
|
||||
workloads**, in a tiled rasterizer fragment (tile color/Z resident in M20K, RMW
|
||||
on-chip, tile + texture streamed to/from LPDDR4B).
|
||||
|
||||
### 5.1 Two trace-data prerequisites (do these FIRST — they scope the build)
|
||||
1. **Texture-format histogram** (§4): texel-fetch distribution by bit-depth +
|
||||
overdraw factor, from real game traffic.
|
||||
2. **Worst-case stimulus selection** (§7): identify the single most
|
||||
alpha-blended / overdraw-punishing in-game scene in the trace library — the
|
||||
design must clear *peak*, not mean.
|
||||
|
||||
### 5.2 Workload knobs (sweep matrix)
|
||||
- **Tile size:** 32×32 and 64×32 pixels (start).
|
||||
- **Color format:** PSMCT32 first; later PSMCT16 / PSMT8.
|
||||
- **Z buffer:** on / off.
|
||||
- **Alpha blend:** on / off.
|
||||
- **Texture mode:** solid color · small cached texture · streaming texture ·
|
||||
CLUT texture.
|
||||
- **Primitive mix:** fullscreen sprites · many small sprites · triangles.
|
||||
|
||||
### 5.3 Metrics (per configuration)
|
||||
- tiles/sec, pixels/sec
|
||||
- **bytes/pixel external** (the locality number that matters)
|
||||
- LPDDR4 read GB/s and write GB/s (reuse the Test #1 counter approach)
|
||||
- M20K footprint (tile color + Z + any texture cache)
|
||||
- tile-reload rate (evictions/frame under the real primitive order)
|
||||
|
||||
### 5.4 Stimulus
|
||||
Driven by **representative GS primitive + texture traffic pulled from the
|
||||
emulation history** — specifically the worst-case scene from §5.1(2). **Not** a
|
||||
boot screen or menu: those are bandwidth-trivial and will hand back a gorgeous
|
||||
green result that collapses in-game.
|
||||
|
||||
---
|
||||
|
||||
## 6. Permanent architecture this implies
|
||||
|
||||
The GS that survives this board almost certainly is:
|
||||
|
||||
- **On-chip tile color + Z buffers** (M20K), RMW resolved on-chip.
|
||||
- **LPDDR4B as backing VRAM** (no full 4 MB VRAM in M20K — consistent with 0006).
|
||||
- **Texture cache or texture-tile streamer** feeding the rasterizer from DDR.
|
||||
- **Scanout** either from the tiled framebuffer cache or a resolved linear buffer.
|
||||
|
||||
DSP budget is not the constraint (the shipped raster demo used 4/376 DSP).
|
||||
Bandwidth and on-chip working-set are.
|
||||
|
||||
---
|
||||
|
||||
## 7. Methodological guardrails
|
||||
|
||||
- **Traces are truth.** Texture/locality numbers cannot be synthesized honestly;
|
||||
pull them from real game traffic.
|
||||
- **Test the peak, not the mean.** The torture case is alpha-blended overdraw
|
||||
(smoke, fog, transparency, particles) — simultaneously worst for tile RMW and
|
||||
often texture-heavy. Find the worst frame and make *that* the stimulus.
|
||||
- **Don't over-trust the green.** Test #1 green ≠ faithful-GS feasible. Only
|
||||
Test #2 under real game traffic produces the integer that answers the question.
|
||||
|
||||
---
|
||||
|
||||
## 8. Status / Next
|
||||
|
||||
- **Bandwidth gate: GREEN** (this doc, §1–2). New feasibility baseline.
|
||||
- **Strategic pivot endorsed by Codex + outside review:** the next serious work
|
||||
moves from qbert opcode-growth (Track A oracle, 0007) toward **GS tiled-VRAM
|
||||
architecture feasibility** — because that path now has a plausible physical
|
||||
foundation.
|
||||
- **Immediate next step (no RTL): ACQUIRE real GS traffic first** — the trace
|
||||
corpus does not exist (see §4 reality check). Capture PCSX2 GS dumps from
|
||||
real games (owner-supplied, prebuilt PCSX2), then write a software `.gs`
|
||||
parser to produce the texture-format histogram + locate the worst-case
|
||||
alpha-overdraw frame (§5.1). Only then is the Test #2 stimulus honest.
|
||||
- **Then:** build the Test #2 microbenchmark to this spec; its sustained number
|
||||
under real game traffic is the final yes/no on faithful-enough GS on this board.
|
||||
- **Chapter numbering note:** "Ch306" is already this repo's EE-core reality
|
||||
checkpoint (0007). This GS line is a later chapter (Ch307+); the label, not
|
||||
the substance, is what differs from Codex's framing.
|
||||
@@ -0,0 +1,63 @@
|
||||
# 0009 — Combined textured + alpha + depth: per-pixel memory-op schedule
|
||||
|
||||
**Status:** proven in sim (Ch302), board-pending. Local BRAM probe; NOT yet tiled VRAM.
|
||||
|
||||
## Why this exists
|
||||
|
||||
Before designing tiled/LPDDR-backed VRAM we need the exact per-pixel read/write
|
||||
schedule a primitive that is simultaneously **textured + alpha-blended +
|
||||
depth-tested** demands. Until Ch302 those three GS features were *mutually
|
||||
exclusive* (each the sole `read2` consumer for its primitive). Ch302 lifts that —
|
||||
behind the default-off `COMBINED_TAZ` param — with an explicit walker-stalling
|
||||
multi-beat FSM in `gs_stub`, so the schedule is observable and asserted.
|
||||
|
||||
Speed was explicitly NOT a goal; the correct, observable schedule is.
|
||||
|
||||
## The per-pixel schedule (single read2 port, single write port)
|
||||
|
||||
Z-test is issued FIRST so a hidden pixel costs one read and nothing else:
|
||||
|
||||
| Beat | read2 (1-cyc registered) | compute | write port |
|
||||
|------|--------------------------|---------|------------|
|
||||
| 0 `CB_Z` | issue **stored-Z** read (`z_rd_en`) | — | — |
|
||||
| 1 `CB_ZW` | (issue **texel** read iff Z passes) | Z-test (GEQUAL): frag_z vs stored_z. **FAIL → stop** (no texel/dest read, no write; advance) | — |
|
||||
| 2 `CB_T` | issue **dest-color** read (`fb_rd_en`) | latch texel as Cs + As (=texel α) | — |
|
||||
| 3 `CB_FB` | — | blend `Cv=((Cs−Cd)·As)>>7+Cd` | **write color** (blended) → FB |
|
||||
| 4 `CB_ZWR` | — | — | **write Z** → Z-buffer (skip if ZMSK); then advance walker |
|
||||
|
||||
The three reads land on the single read2 port in **separate cycles**, so the
|
||||
existing read2 priority mux + its mutual-exclusion `$error` asserts are untouched
|
||||
(one consumer per cycle). The two writes serialize on the single write port
|
||||
(color beat 3, Z beat 4). The walker does not advance to the next candidate
|
||||
pixel until BOTH writes complete.
|
||||
|
||||
## The concrete requirement for tiled VRAM
|
||||
|
||||
- **hidden pixel: 1 read, 0 writes** (stored-Z only).
|
||||
- **visible pixel: 3 reads + 2 writes** — stored-Z, texel, dest-color reads;
|
||||
color + Z writes.
|
||||
|
||||
So tile-local memory must serve **up to 3 reads + 2 writes per pixel**. The
|
||||
options this makes concrete (no longer hand-wavy):
|
||||
- a **2-read-port** tile RAM (e.g. texel + Z in parallel, dest folded in) + a
|
||||
write path, OR
|
||||
- a **3-phase read schedule** on fewer ports (what this probe does, serialized),
|
||||
trading throughput for ports, OR
|
||||
- tile-local banking that absorbs the dest read-modify-write locally.
|
||||
|
||||
Z-first ordering means the texel/dest bandwidth is only spent on visible pixels —
|
||||
a real saving the tiled design should preserve.
|
||||
|
||||
## Verification (tb_top_psmct32_combined_demo)
|
||||
|
||||
A green Z-writing background + one TME+ABE+ZTE triangle whose interpolated Z
|
||||
crosses the background Z (top half passes, bottom fails). A **memory-op tracer**
|
||||
records, per pixel, the read enables + write addresses and asserts the SEQUENCE
|
||||
(not just final pixels):
|
||||
- depth-FAIL: z-read=1, texel-read=0, dest-read=0, color-write=0, Z-write=0 → pixel stays background green.
|
||||
- depth-PASS: z-read=1, texel-read=1, dest-read=1, color-write=1, Z-write=1 → blend(texel, green); texel RGB and green dest both present.
|
||||
Result: 35 PASS / 7 FAIL / 160 outside, errors=0. Param=0 keeps all prior demos byte-identical.
|
||||
|
||||
## Out of scope (deliberately)
|
||||
Perspective (affine only — perspective proven separately, Ch301), alpha-test /
|
||||
texture-alpha discard, non-PSMCT32 dest, and throughput (multi-beat is fine here).
|
||||
@@ -0,0 +1,454 @@
|
||||
# 0010 — On-chip tile-local renderer core (first tiled-VRAM rung)
|
||||
|
||||
**Status:** proven in sim (Ch303), board-pending. One 16×16 tile, on-chip color+Z,
|
||||
flush to VRAM. Texture still BRAM-VRAM. NO LPDDR, NO multi-tile binning yet.
|
||||
|
||||
## Why
|
||||
|
||||
doc 0009 established the per-pixel requirement for a combined textured+alpha+depth
|
||||
primitive: **hidden = 1 read / 0 writes; visible = 3 reads / 2 writes.** doc 0008 §6
|
||||
sets the target architecture: on-chip tile color+Z (RMW resolved on-chip), LPDDR as
|
||||
backing VRAM, texture streamed/cached. This rung builds the **first piece**: the
|
||||
on-chip tile color+Z scratchpad with flush, so the combined RMW happens on-chip and
|
||||
only the texture fetch + the tile flush cross to VRAM. (Codex framing: build the
|
||||
tile-local core first; stage LPDDR integration later.)
|
||||
|
||||
## What was built
|
||||
|
||||
- **`gs_tile_ram`** (rtl/gif_gs/gs_tile_ram.sv): generic 1W1R on-chip tile RAM,
|
||||
registered read (1-cycle, matching the VRAM read2 contract). Instantiated twice
|
||||
in gs_stub (gated by `TILE_LOCAL`): `u_tile_color` (256×32) + `u_tile_z` (256×32)
|
||||
— one 16×16 tile, ~2 KiB total.
|
||||
- **gs_stub `TILE_LOCAL` mode** (default 0 → byte-identical): a combined TME+ABE+ZTE
|
||||
triangle renders into the tile via a CLEAR → RENDER → FLUSH sequence overlaid on
|
||||
the existing R_IDLE/R_SCAN/R_DRAIN FSM.
|
||||
|
||||
## The tile memory schedule (the deliverable)
|
||||
|
||||
```
|
||||
CLEAR : 256 cycles → write tile_color = TILE_CLEAR_COLOR, tile_z = TILE_CLEAR_Z
|
||||
(every entry initialized; the "background")
|
||||
|
||||
RENDER (per inside pixel, tile index = {y[3:0], x[3:0]}):
|
||||
beat0 read tile_z
|
||||
beat1 Z-test (GEQUAL: frag_z vs tile_z). FAIL → STOP (no texture read,
|
||||
no tile_color read, no tile_color/tile_z write). PASS → read texture (VRAM)
|
||||
beat2 texel ready (Cs/As) ; read tile_color (dest)
|
||||
beat3 blend ; WRITE tile_color
|
||||
beat4 WRITE tile_z (skip on ZMSK)
|
||||
|
||||
FLUSH : 256 cycles → read tile_color[idx] (registered) → framebuffer write
|
||||
(raster_pixel_emit → VRAM at the linear FB address). ~70 MB/s class
|
||||
per doc 0008 — trivial.
|
||||
```
|
||||
|
||||
In tile terms:
|
||||
- **hidden pixel:** tile_z read only. No texture, no tile_color read/write, no tile_z write.
|
||||
- **visible pixel:** tile_z read + texture read + tile_color read + tile_color write + tile_z write.
|
||||
- **flush:** tile_color read → framebuffer write, ×256.
|
||||
|
||||
Texture stays on the VRAM read2 path (unchanged). Only color/Z moved on-chip.
|
||||
|
||||
## Verification (tb_top_psmct32_tile_demo)
|
||||
|
||||
Combined triangle (interpolated Z crossing the clear Z) over a CLEAR'd green tile.
|
||||
A tracer on the tile-RAM ports + the emit port asserts the schedule:
|
||||
- CLEAR wrote 256 color + 256 Z entries.
|
||||
- hidden (depth-fail) pixels: no texture read, no tile_color write, no tile_z write.
|
||||
- visible (depth-pass) pixels: texture read + tile_color write + tile_z write; rendered
|
||||
color = blend(texel, clear-green); occluded/outside = clear green.
|
||||
- FLUSH emitted 256 framebuffer writes; final scanout matches the Ch302 image.
|
||||
Result: clear 256/256, flush 256, 35 visible / 7 hidden, errors=0. `TILE_LOCAL=0`
|
||||
keeps every prior demo byte-identical.
|
||||
|
||||
## External LPDDR bandwidth model (documented, not yet exercised)
|
||||
|
||||
Per doc 0008: framebuffer flush is **trivial** (640×480×4 B ≈ 1.2 MB/frame ≈ 70 MB/s
|
||||
@60fps, noise vs the measured 8.4 GB/s). Texture fetch + tile-reload from primitive
|
||||
disorder are the real DDR consumers and are **locality-driven** — to be measured
|
||||
against real GS traces (doc 0008 §4–5), not synthesized. This rung does NOT touch
|
||||
LPDDR: texture is BRAM-VRAM, one tile, no eviction.
|
||||
|
||||
## Ch304 — 2×2 multi-tile grid (extension)
|
||||
|
||||
The single-tile core generalizes to a `TILE_COLS×TILE_ROWS` grid with minimal
|
||||
change, because (a) `tile_idx = {y[3:0],x[3:0]}` is already the tile-local address
|
||||
for any 16-aligned tile, and (b) attribute interpolation is screen-space → seams
|
||||
are continuous by construction. Added: an outer tile loop (the popped primitive +
|
||||
solved gradients persist across all tiles), per-tile walker-bbox clip to
|
||||
`primitive_bbox ∩ tile` (skip render if no overlap → tile shows clear color), and
|
||||
a flush FB-address offset by the tile origin. `TILE_COLS=TILE_ROWS=1` is byte-
|
||||
identical to the single-tile path. Codex scope: fixed primitive list (one
|
||||
primitive re-rendered per tile), re-test-against-each-tile, NO external bin memory.
|
||||
|
||||
Proven (tb_top_psmct32_tile2x2_demo): one triangle spanning a 2×2 grid (32×32,
|
||||
crossing x=16 & y=16) — all 4 tiles clear independently (256 each), 1024 flush
|
||||
emits, and the **whole 32×32 scanout matches a single screen-space reference**
|
||||
(718/718, 67 seam-region pixels continuous) → no visible seams. This is the
|
||||
re-test-each-primitive-against-each-tile architecture; a real binning engine /
|
||||
command buffer is a later optimization, not needed for the architectural proof.
|
||||
|
||||
## Ch305 — MULTI-PRIMITIVE tiled scene (extension)
|
||||
|
||||
Generalizes the grid from re-rendering ONE primitive per tile to compositing a
|
||||
LIST of primitives per tile, in order, so later primitives depth-test/alpha-blend
|
||||
over earlier ones within each tile. Gated by `TILE_MULTIPRIM` (default 0 →
|
||||
byte-identical) + `TILE_PRIM_COUNT` (batch size). The primitive FIFO IS the list
|
||||
store (its slots already hold each primitive's pre-solved gradients), so re-reading
|
||||
a slot is free. Per tile: CLEAR → load+render prim 0 → (pipeline-flush) → load+render
|
||||
prim 1 → … → FLUSH. The grid starts only once the whole batch is buffered
|
||||
(`fifo_count >= TILE_PRIM_COUNT && all_grad_done`) — the demo-honest stand-in for a
|
||||
future GIF-EOP/kick. Empty-clip primitives are skipped per tile; the whole FIFO is
|
||||
drained at grid end (streaming/partial-drain is future work). The inter-primitive
|
||||
advance waits for the per-pixel pipeline to fully flush (`comb_pipe_empty`), not just
|
||||
the walker reaching R_DRAIN, so a primitive's in-flight color/Z writes commit before
|
||||
the next primitive's `ras_*` load.
|
||||
|
||||
Proven (tb_top_psmct32_tile_multiprim_demo): 3 combined prims over the 2×2 grid —
|
||||
opaque blue bg (Z=0x5000), opaque red (Z=0x6000, in front), translucent white
|
||||
(Z=0x5800, blends but is OCCLUDED by red where 0x5800 < 0x6000). The whole 32×32
|
||||
scanout matches a software integer-Z-buffer + source-over replay (514/514), with all
|
||||
interaction regions exercised (blue 24 / red 48 / light-blue 26 / occlusion 19 /
|
||||
green 416) and seam continuity (50 seam matches). This is the architecture a real
|
||||
command-stream replay needs; a per-tile bin buffer is a later optimization.
|
||||
|
||||
DEBUG NOTES (two non-obvious bugs surfaced): (1) the per-primitive clip wires indexed
|
||||
a FIFO array through a function inside continuous `assign`s — iverilog-12 mis-reads
|
||||
that as 0 (silent hang, sim-time frozen); fixed by computing them in `always_comb`
|
||||
(legal SV, Quartus-clean; sim-only workaround). (2) The first failing image was a
|
||||
FIXTURE bug, not RTL: three solid 4×4 textures placed 0x100 apart aliased, because a
|
||||
PSMCT32 texture with TBW=1 has a 0x100-byte row stride so a 4-tall texture spans
|
||||
0x400 bytes; spacing them 0x400 apart (TBP0 32/36/40) fixed it. The depth/RMW path
|
||||
was correct all along.
|
||||
|
||||
## Ch306 — GS SCISSOR clipping (extension)
|
||||
|
||||
Bakes the GS SCISSOR_1 rectangle into the tile-traversal walker bounds (param
|
||||
`SCISSOR_ENABLE`, default 0 → byte-identical). Because the scissor is a rectangle and
|
||||
the walker scans a rectangle, the effective draw region = primitive bbox ∩ tile bbox ∩
|
||||
scissor rect is itself rectangular — so the scissor is just intersected into the
|
||||
walker bbox at all clip sites (single-prim `clip_*`, multiprim `always_comb`, and the
|
||||
`mp_next_nonempty` empty-test). NO per-pixel scissor test: pixels outside the scissor
|
||||
are never visited, so color and Z writes are both suppressed for free. SCISSOR_1 (GIF
|
||||
reg 0x40) is parsed into a GLOBAL `scissor_1_q` (reset full-range); decoded fields →
|
||||
12-bit `eff_sc*` gated by SCISSOR_ENABLE (0/0xFFF when off → max/min no-op). Per-
|
||||
primitive (FIFO-snapshot) scissor is a future extension if a command stream varies it.
|
||||
|
||||
Proven (tb_top_psmct32_tile_scissor_demo): the Ch305 3-prim scene + SCISSOR_1
|
||||
[9..22]×[6..20] (crossing both seams) — 514/514 match, clipped=39 (would-be-scene
|
||||
pixels outside the rect are clear green), inside=59 (kept scene matches the unclipped
|
||||
ref), exact boundary (edgePairs=6), seam=50. Regression 209→210, byte-identical.
|
||||
|
||||
## Ch307 — texture WRAP modes (REPEAT + CLAMP) (extension)
|
||||
|
||||
Adds GS texture wrap (CLAMP_1 WMS/WMT: REPEAT/CLAMP) for u/v, inside `gs_texture_unit`
|
||||
(param `TEX_WRAP_ENABLE`, default 0 → pass-through byte-identical). Applied to u/v
|
||||
BEFORE texel-address gen, so it covers the linear and swizzle paths and all callers at
|
||||
one point. REPEAT = `u & (2^TW - 1)`; CLAMP = `min(u, 2^TW-1)`. gs_stub parses CLAMP_1
|
||||
(reg 0x48) and snapshots wrap mode + TW/TH per primitive (FIFO, like ras_tbw), so
|
||||
REPEAT and CLAMP primitives coexist in one scene. Codex sequencing: wrap/clamp before
|
||||
bilinear, because it determines which edge neighbours a future bilinear filter samples.
|
||||
|
||||
Proven: a standalone sampler TB (tb_gs_texture_wrap) covers PSMCT32 + PSMT8 +
|
||||
PSMT4-swizzle (wrap happens before swizzle); the board TB (tb_top_psmct32_tile_wrap_demo,
|
||||
557/557) renders two textured tris sampling a striped 4×4 texture with UV 0..8 — REPEAT
|
||||
tiles 2× (two white stripes), CLAMP sticks (one white stripe + edge-stretched blue).
|
||||
Regression 210→212, byte-identical. (Fixture lesson: the first NON-solid tile texture
|
||||
exposed an upload-giftag REGS nibble-count bug that solid textures had masked.)
|
||||
|
||||
## Ch308 — PSMCT16 tile color buffer (extension)
|
||||
|
||||
The on-chip tile COLOR RAM can be PSMCT16 (RGB5A1, 16-bit) instead of PSMCT32, via
|
||||
param `TILE_COLOR_PSMCT16` (default 0 → byte-identical). It HALVES the color tile RAM
|
||||
(`TILE_COLOR_W` = 16; Z RAM stays 32-bit) — the first answer to "can tile color be
|
||||
narrower than the 32-bit blend width when the frame format allows it?" (yes). The RMW
|
||||
packs ABGR8888→pix16 on write/clear, unpacks pix16→ABGR (bit-replicate) for the blend
|
||||
dest, and the FLUSH emits PSMCT16 framebuffer writes (mirroring the proven S2 PSMCT16
|
||||
emit; vram_normalize keys the halfword off byte_addr[1]). Scanout reads PSMCT16 via
|
||||
DISPFB1.PSM=0x02.
|
||||
|
||||
CONSTRAINT discovered: the combined tile path's primitive eligibility requires
|
||||
FRAME.PSM==PSMCT32 (the combined RMW was built PSMCT32-only). So the PSMCT16-ness lives
|
||||
in the tile RAM + flush + DISPFB, NOT in FRAME.PSM — the demo keeps FRAME PSMCT32 (so
|
||||
the prims classify as combined) while DISPFB + tile + flush are PSMCT16. A fully-PSMCT16
|
||||
FRAME would need the combined gate relaxed to accept PSMCT16 dest (future work).
|
||||
|
||||
Proven (tb_top_psmct32_tile_psmct16_demo, 514/514): the Ch305 scene in PSMCT16, matched
|
||||
against a software reference that applies the SAME per-step 5-bit quantization
|
||||
(q(c)=(c&0xF8)|(c>>5)) the on-chip RMW does — each primitive blends over the quantized
|
||||
dest. Clear green 0x80→0x84, light-blue 0x7F→0x7B, pure blue/red unchanged, red
|
||||
occlusion intact. Regression 212→213, byte-identical.
|
||||
|
||||
## Ch309 — generic GS ALPHA blend modes (extension)
|
||||
|
||||
Generalizes the combined blender from the single hardcoded source-over to the GS
|
||||
selector machinery `Cv = clamp(((A-B)*C)>>7 + D)` (A/B/C/D from ALPHA_1, FIX=[39:32]),
|
||||
param `ALPHA_MODES_ENABLE` (default 0 → source-over, byte-identical). gs_alpha_blend
|
||||
gains a_sel/b_sel/c_sel/d_sel/ad/fix inputs + a generic datapath; gs_stub FIFO-snapshots
|
||||
the per-primitive selectors+FIX and wires them to u_comb_blend. The combined-eligibility
|
||||
gate `close_combined` (which hardcoded source-over) is relaxed to accept any ABE
|
||||
primitive when ALPHA_MODES_ENABLE — the generic blender handles any config. (Same class
|
||||
of "eligibility gate too strict" as Ch308's PSMCT32-FRAME requirement: when you add a
|
||||
per-pixel mode to the combined path, check the datapath AND close_combined.)
|
||||
|
||||
Proven (tb_top_psmct32_tile_alpha_demo, 514/514): the Ch305 scene with P1 ADDITIVE
|
||||
(A=Cs,B=0,C=FIX=0x80,D=Cd → Cs+Cd) → magenta over the blue bg (glow/particle add), while
|
||||
P0/P2 stay source-over (light-blue intact) and P2 is still depth-occluded by P1. Two
|
||||
blend modes coexist. Regression 213→214, byte-identical.
|
||||
|
||||
## Ch310 — bilinear texture filtering (extension, 2-phase)
|
||||
|
||||
4-tap bilinear (PSMCT32), staged per Codex. PHASE 1: a multi-beat bilinear sampler in
|
||||
gs_texture_unit (param BILINEAR_ENABLE, default 0): reads the 4 neighbours (each via the
|
||||
Ch307 wrap), lerps by fractional U/V; schedule = 4·(1+RD_LATENCY)+1 ≈ 9 cyc/sample (the
|
||||
architectural number for the future texture cache). Proven by a standalone TB
|
||||
(tb_gs_texture_bilinear) — all 6 cases exact (center=nearest, halfway=4-tap avg, clamp
|
||||
edge no-OOB, repeat edge wraps, nearest unchanged). PHASE 2: integrated into the COMBINED
|
||||
tile path — TEX1.MMAG (GIF 0x14 bit5) per-primitive selects nearest vs linear; a runtime
|
||||
`filter_lin` input gates the 4-tap; the affine interp gains a frac sibling
|
||||
(interp_affine_uv_frac → step[15:12]); a new CB_TWAIT beat stalls the per-pixel FSM on the
|
||||
LEVEL !tex_busy until the ~9-cycle sample completes (the FSM steps half-rate on z_advance,
|
||||
so a level wait can't miss the 1-cycle out_valid), then CB_T latches the HELD filtered texel.
|
||||
Depth/Z/blend/tile-RMW unchanged; bilinear did NOT touch close_combined (the prim is still
|
||||
source-over ABE). Proven (tb_top_psmct32_tile_bilinear_demo): a magnified 4×4 blue/white
|
||||
checker, nearest tri blocky (0 midtones) vs bilinear tri smoothed (all midtones), same
|
||||
coverage (stall dropped nothing). Regression 215→216, byte-identical.
|
||||
|
||||
## Ch311 — per-tile BIN BUFFER (extension)
|
||||
|
||||
Replaces Ch305's render-time re-test (mp_next_nonempty: each tile re-scans all prims) with
|
||||
a real precomputed bin buffer (param BIN_BUFFER_ENABLE, default 0). A new TP_BIN phase runs
|
||||
a (prim,tile) double-loop counter FSM (prim_count×NTILES cycles) that tests each prim's
|
||||
bbox∩tile∩scissor (the same overlap math) and appends the prim index to bin_prim[tile][] /
|
||||
bin_n[tile], in ascending draw order. The render then walks each tile's bin (CLEAR-done loads
|
||||
bin slot 0; RENDER-drain steps through bin_n; FLUSH at end) — no re-scan. Equivalent image
|
||||
to the re-test path (same overlap test + order). This is the primitive-ROUTING machinery for
|
||||
command-stream replay; the grid stays 2×2 (prove the mechanism, scale later). Proven
|
||||
(tb_top_psmct32_tile_bin_demo): bins read back exactly (t0={0,1} t1={0,1} t2={0} t3={0,2} for
|
||||
an all-tiles/2-tiles/1-tile prim trio) + image 594/594 vs the re-test reference. Regression
|
||||
216→217, byte-identical.
|
||||
|
||||
## Next (staged, per Codex)
|
||||
1. Multiple tiles / tile grid (primitive→tile binning). [DONE: Ch304 grid, Ch305 list, Ch311 bin buffer]
|
||||
Scissor/window clipping. [DONE: Ch306]
|
||||
Texture clamp/repeat. [DONE: Ch307]
|
||||
PSMCT16 tile color. [DONE: Ch308]
|
||||
ALPHA mode expansion. [DONE: Ch309]
|
||||
Bilinear filtering. [DONE: Ch310 — sampler + combined-path integration]
|
||||
Larger grid sweep. [DONE: Ch312 — 2x2→4x4 (16 tiles, 64x64) via the bin buffer; no new RTL logic, COLS/ROWS/NTILES already parameterized]
|
||||
|
||||
## Ch312 — 4x4 grid (extension)
|
||||
|
||||
Scales the tiled renderer to a 4×4 grid (16 tiles, 16×16 each = 64×64) by setting
|
||||
TILE_COLS=TILE_ROWS=4 — NO new RTL logic, since the grid loop + bin buffer (NTILES,
|
||||
CUR_T_W/BIN_T_W via $clog2) were already parameterized. 64×64 PSMCT32 FB fills 16 KiB so
|
||||
the demo uses VRAM 32 KiB (textures @ 0x4000). Proven (tb_top_psmct32_tile_bin4x4_demo):
|
||||
3 prims (P0 4-tile / P1 6-tile cross-seam / P2 1-tile, + 6 empty tiles), all 16 bin_n
|
||||
read back exactly (1100 1211 0111 0001, empty=0), t5={0,1} order preserved, image 3240/3240
|
||||
vs the re-test reference, seam continuity across x=16/32/48 + y=16/32. Regression 217→218,
|
||||
byte-identical. The fit (owner) gives the resource-scaling answer: bin storage grows 4×
|
||||
(60→240 register bits, still tiny) — a hard ALM/register jump would signal the register-bins
|
||||
should go BRAM/MLAB-backed before larger scenes.
|
||||
|
||||
## Ch313 — full PSMCT16 framebuffer mode (extension)
|
||||
|
||||
Relaxes the `close_combined` eligibility gate so the combined/tiled path accepts a
|
||||
PSMCT16 dest (`frame_1_q[29:24]==6'h02`) — but ONLY when `TILE_COLOR_PSMCT16=1`, so a
|
||||
PSMCT16 FRAME never pairs with a PSMCT32 flush. This was the LAST place forcing a
|
||||
PSMCT32 FRAME: the tile color RAM, the dest-color unpack for blending, and the flush
|
||||
emit (be=`4'b0011`, psm=`0x02`, `<<1` byte addr) were ALL already PSMCT16 from Ch308,
|
||||
keyed off `TILE_COLOR_PSMCT16` and independent of `FRAME.PSM`. So Ch308's PSMCT32-FRAME
|
||||
workaround is gone — render/flush/scanout are now consistently RGB5A1. One-term RTL
|
||||
change; at `TILE_COLOR_PSMCT16=0` (default) the new disjunct is constant-0 and the gate
|
||||
collapses to the original PSMCT32-only test (byte-identical). Demo = the Ch312 4×4
|
||||
(64×64) scene with `FRAME.PSM=PSMCT16` + DISPFB PSMCT16. A 64×64 PSMCT16 FB is 8 KiB —
|
||||
HALF the 16 KiB PSMCT32 FB — so the demo runs in **16 KiB VRAM vs Ch312's 32 KiB**: the
|
||||
direct framebuffer-memory saving that motivates the LPDDR-backed FB phase. Proven
|
||||
(tb_top_psmct32_tile_psmct16fb_demo): flush 4096/4096 carry psm=0x02 + be=`0011`, ZERO
|
||||
PSMCT32 flushes (whole FB is 16-bit); image 3240/3240 vs a re-test reference replayed
|
||||
with per-step RGB5A1 quantization `q5(c)=(c&0xF8)|(c>>5)` (EXACT); 2875 matched pixels
|
||||
differ from the would-be PSMCT32 value (proves the FB is genuinely RGB5A1, not PSMCT32);
|
||||
bin_n/scissor/depth identical to Ch312 (1100 1211 0111 0001, t5={0,1}, seam 464).
|
||||
FIT-CLEARED + VISUALLY VERIFIED on Agilex 5 (2026-06-01): vs Ch312 (4×4 PSMCT32, 32 KiB),
|
||||
RAM blocks 45→29 (−16), block-mem 688,128→421,888 (−256 Kbit), ALMs −159, regs −555 — the
|
||||
PSMCT16 FB recovered ALL of Ch312's 4×-scale-up memory cost, landing back on the Ch311 2×2
|
||||
PSMCT32 baseline of 29 RAM blocks. A 4× grid in PSMCT16 costs ZERO extra framebuffer memory
|
||||
vs the 2×2 PSMCT32 grid: hard proof the framebuffer (not bins/logic) is the on-chip memory
|
||||
consumer and pixel format trades directly against it. Board image matches (blue/red/teal
|
||||
tris + green, RGB5A1-quantized, no seams).
|
||||
|
||||
## Ch314 — bilinear for palettized (PSMT8/PSMT4) textures (extension)
|
||||
|
||||
Extends bilinear to INDEXED textures with the CLUT-BEFORE-INTERPOLATE rule: each of the 4
|
||||
taps fetches an index, CLUTs it to RGBA, then the 4 COLORS interpolate (NOT the indices —
|
||||
that would round to one palette entry). The sampler core is ~6 lines: the bilinear FSM tap
|
||||
capture changes from `tap[beat] <= tex_rd_data` to `tap[beat] <= near_color` (already
|
||||
`(PSMT8||PSMT4)?clut_rd_data:tex_rd_data`), so PSMCT32 is byte-identical and indexed taps
|
||||
capture the CLUT'd color. New param PALETTE_BILINEAR (default 0) widens `do_lin` to admit
|
||||
PSMT8(0x13)/PSMT4(0x14). The per-tap addr-gen (linear/swizzle + wrap/clamp) already runs
|
||||
BEFORE the CLUT lookup, so "swizzle-before-CLUT" + edge wrap/clamp are free. For the BOARD
|
||||
demo (bilinear lives only in the combined path), `close_combined`'s texture-PSM gate also
|
||||
widens to admit PSMT8/PSMT4 when PALETTE_BILINEAR; the shared gs_texture_unit already had
|
||||
the CLUT port wired and CLUT is a combinational 3rd port (no read2-arbitration change).
|
||||
Proven: tb_gs_texture_bilinear (unit) CASE7 PSMT8 red↔blue halfway → 0xFF7F007F (purple,
|
||||
neither endpoint = colors interpolated), CASE10 PSMT4 nibble across a byte boundary, CASE11
|
||||
repeat / CASE12 clamp edges + no OOB, CASE1-6 PSMCT32 byte-identical;
|
||||
tb_top_psmct32_tile_palbilinear_demo (board, combined path + CLUT load) nearMid=0 /
|
||||
bilMid=58 — the on-board CLUT-before-interp proof. Regression 219→220 byte-identical, board
|
||||
elab EXIT 0. FIT-CLEARED + VISUALLY VERIFIED on Agilex 5 (2026-06-01): LEFT tri blocky
|
||||
blue/white indexed checker, RIGHT tri smoothed blue↔white midtones (CLUT-before-interp on
|
||||
silicon). RESOURCE DELTA vs Ch310 (2×2 PSMCT32 bilinear, 16KB): ALMs 30,229→30,101 (flat),
|
||||
DSP 122→122 (0), block-mem 425,984→425,984 (0), RAM blocks 29→29 (0) — palettized bilinear
|
||||
is essentially FREE: zero extra DSP (reuses the same lerp multipliers), zero extra memory
|
||||
(reuses the CLUT port from Ch296), the CLUT-before-interp restructure is just a mux on the
|
||||
tap-capture path.
|
||||
|
||||
## Ch315 — primitive/bin capacity scaling (extension)
|
||||
|
||||
Parameterizes the primitive FIFO depth (was a hardcoded `FIFO_DEPTH=4`) as `TILE_FIFO_DEPTH`
|
||||
(default 4 → byte-identical; power-of-2). In the bin-buffer renderer this depth sizes BOTH
|
||||
the prim-list capacity N AND the per-tile bin depth M (bins are `[NTILES][FIFO_DEPTH]`),
|
||||
so they're coupled (M=N: a tile's bin can hold every queued prim). Adds sim-visible
|
||||
diagnostics (`raster_overflow_count_r`, `bin_occ_max_r`, defensive `bin_overflow_r`).
|
||||
ARCHITECTURAL ANSWER to "where do register bins stop being reasonable": the dominant cost
|
||||
is the ~40 `fifo_*` per-prim attribute arrays (hundreds of register bits/slot); the bins
|
||||
add only `NTILES*FIFO_CNT_W` index bits per depth (~48 bits/depth at 4×4 — negligible), so
|
||||
register bins stay cheap far past the FIFO's practical limit. OVERFLOW nuance: the batched
|
||||
tile path triggers at `TILE_PRIM_COUNT` and drains the FIFO, so excess prims are CLAMPED
|
||||
(visible as capped bin occupancy), not push-dropped — `raster_overflow` (the streaming
|
||||
push-while-full flag, now counted) doesn't fire in the batched path; and `TILE_PRIM_COUNT`
|
||||
must be `<= FIFO_DEPTH`. Proven: tb_top_psmct32_tile_cap_demo (depth 8, 7 prims) — bin t0
|
||||
holds 6 (occ_max=6 > old 4), draw order {0..5}, image 3873/3873, no overflow;
|
||||
tb_top_psmct32_tile_cap_overflow_demo (depth 4, same payload) — occ_max CLAMPS to 4
|
||||
(capacity ceiling) and still renders all 16 tiles gracefully. Regression 220→222
|
||||
byte-identical, board elab EXIT 0. (Demo puts the deep bin in t0 to dodge an orthogonal
|
||||
latent bug: empty tiles preceding the first non-empty tile flush black — to be fixed
|
||||
separately.) FIT-CLEARED + VISUALLY VERIFIED on Agilex 5 (2026-06-01). RESOURCE SLOPE
|
||||
(depth-8 vs Ch312 depth-4): ALMs 29,682→32,072 (+2,390), regs 33,356→37,486 (+4,130),
|
||||
block-mem + RAM-blocks UNCHANGED (688,128 / 45). So +4 FIFO slots = ~1,033 regs + ~600 ALMs
|
||||
PER primitive slot, ZERO block RAM — dominated by the ~40 fifo_* attribute arrays; the bins
|
||||
add ~80 regs/slot (negligible). The bins never stop being reasonable; the per-prim attribute
|
||||
FIFO is the ALM-bound scaling wall (~16-prim headroom at this grid). Beyond that, move the
|
||||
per-prim attribute storage (not the bins) to block RAM.
|
||||
|
||||
## Ch316 — leading-empty-tile traversal fix (correctness)
|
||||
|
||||
Fixes the latent bug found in Ch315: tiles that are EMPTY and PRECEDE the first non-empty
|
||||
tile flushed BLACK instead of the clear colour. ROOT CAUSE: the per-tile flush row-stride
|
||||
is `flush_pixel_index_w = flush_y*(ras_fbw<<6)+flush_x` (gs_stub ~line 3408), and `ras_fbw`
|
||||
(FRAME width) was loaded ONLY by `mp_load_prim` (on primitive load). A leading-empty tile
|
||||
loads no prim, so it used the reset `ras_fbw=0` → stride 0 → every row collapsed onto row
|
||||
0's FB addresses → the tile's real screen rows kept the FB-init value (black). Empties AFTER
|
||||
a render inherited that render's ras_fbw, hence were fine — the exact asymmetry observed.
|
||||
FIX: in the `mp_grid_start` branch (~line 5588) load `ras_fbp/ras_fbw/ras_psm/ras_bpp_shift`
|
||||
from the batch's oldest FIFO entry (`fifo_*[fifo_rptr]`) at GRID-RENDER START, so the flush
|
||||
address is valid for EVERY tile. A batch shares one FRAME, so this equals what `mp_load_prim`
|
||||
sets at render → byte-identical for any batch whose first tile is non-empty. Proven:
|
||||
tb_top_psmct32_tile_late_demo (1 prim only in t15, t0..t14 empty) — ZERO black pixels, all
|
||||
empty tiles green-cleared, bin_n[15]=1 (renderer reached the last tile, no premature done),
|
||||
image 3990/3990; Ch315 cap_demo still 3873/3873. Regression 222→223 byte-identical, board
|
||||
elab EXIT 0 (GS_TILE_LATE_DEMO). Root-caused with a direct VRAM probe (FB at empty tiles
|
||||
0x0 → 0xFF008000 after the fix). FIT-CLEARED + VISUALLY VERIFIED on Agilex 5 (2026-06-01):
|
||||
whole 64×64 green (all 15 leading empties clear correctly) + one blue triangle in t15;
|
||||
resources on the Ch312 baseline (ALMs 29,801, regs 32,442, block-mem/RAM unchanged) — the
|
||||
fix adds zero storage (loads 4 existing ras_* regs at grid start), pure control-flow.
|
||||
|
||||
## Ch317 — LPDDR-backed framebuffer, tile-flush only (sim write/readback proof)
|
||||
|
||||
First external-framebuffer step, deliberately tight: ONLY the PSMCT16 tile FLUSH is
|
||||
redirected to an LPDDR framebuffer; tile color/Z + texture stay on-chip. The proven LPDDR
|
||||
path (doc 0008, 8.4 GB/s, 256-bit AXI4 → EMIF hard-IP) lives in a SEPARATE diagnostic core,
|
||||
not the GS top — so this rung proves the write/readback path against a behavioral LPDDR
|
||||
MODEL (no board fit; wiring the real EMIF master + LPDDR scanout into the GS top is the next
|
||||
rung). New module `gs_lpddr_fb_writer.sv`: a staging FIFO + burst engine (coalesces a
|
||||
contiguous +2 run into one burst, 4 KiB cap per the doc 0008 AXI lesson) + byte-addressed
|
||||
backing FB + bandwidth/over-underflow counters. Consumes the existing flush stream
|
||||
(`raster_pixel_fb_addr_q` is already the linear `fb_base+(y*pitch+x)*2`). Integrated into
|
||||
the bram top generate-guarded by `LPDDR_FB_ENABLE` (default 0 → not instantiated,
|
||||
byte-identical), as a transitional ADDITIVE mirror (BRAM FB still feeds scanout; LPDDR is
|
||||
the readback-proof target). Proven: tb_gs_lpddr_fb_writer (256-px tile → 512 B / 16 bursts;
|
||||
2049-px run → 2 bursts via the 4 KiB cap; enable=0 inert) and tb_top_psmct32_tile_lpddrfb_demo
|
||||
(Ch313 PSMCT16 scene → LPDDR FB == BRAM FB for all 4096 px; 8192 bytes; 256× 32-B bursts; no
|
||||
over/underflow; ~0.20 GB/s @100 MHz model). Regression 223→225 byte-identical, board elab
|
||||
EXIT 0 (writer pruned at default). FIX worth noting: a `PTR_W'(FIFO_DEPTH)` truncation read
|
||||
the FIFO empty-as-full; use `count[PTR_W]`.
|
||||
|
||||
## Ch318 — LPDDR framebuffer write path on hardware (RTL sim-proven + fit-ready; board gated)
|
||||
|
||||
Connects the Ch317 write path to the real fabric→LPDDR port. qsys_top exposes an
|
||||
`f2sdram` AXI4-256 port (was tied off); the GS runs on design_clk, f2sdram on CLOCK2_50 —
|
||||
genuinely async. New `gs_async_fifo` (gray-code CDC) + `gs_lpddr_axi_master` (thin wrapper,
|
||||
per Codex — does NOT touch the proven writer): GS-domain packer (16 px → one 256-bit
|
||||
tile-row beat {addr,data,strb}) → async FIFO → f2sdram AXI burst FSM (single-beat INCR,
|
||||
AWSIZE=5, AWLEN=0, full WSTRB). A HARD `write_enable` gate (packer + awvalid/wvalid + FSM)
|
||||
makes an LPDDR write impossible unless explicitly enabled — Linux-safety. de25 top exposes
|
||||
the PSMCT16 flush stream and, under `ifdef GS_LPDDR_FB`, drives the f2sdram write channel
|
||||
(default = legacy inert tie-off → byte-identical; with the macro, write_enable=0 + FB_BASE=0
|
||||
placeholder, so the fitted core boots inert). Proven: tb_gs_lpddr_axi_master (gate-off →
|
||||
zero AXI activity; gate-on → 16 INCR beats, 0 protocol/bresp/FIFO errors, slave readback ==
|
||||
source, under AW/W/B backpressure + async clocks); de25 elaborates EXIT 0 both ways;
|
||||
regression byte-identical. fifo_full gotcha: use `count[PTR_W]` (a PTR_W-wide literal
|
||||
truncates DEPTH→0). The BOARD run is GATED on a Linux-safe LPDDR address (owner: /proc/iomem
|
||||
→ reserved region → FB_BASE → raise write_enable → write 8 KiB → HPS devmem readback/hash).
|
||||
HW acceptance = write/readback + fitter snapshot. Ch319 = LPDDR scanout.
|
||||
|
||||
## Ch319 — LPDDR4B framebuffer write + HPS-bridge readback (SILICON-VERIFIED)
|
||||
|
||||
The f2sdram/HPS-DRAM path of Ch318 was CLOSED as platform-rejected (BRESP 256/256 on the
|
||||
secure reserved region — /dev/mem reads of 0x80000000 crash the board). The GS framebuffer
|
||||
pivots to **FPGA-private LPDDR4B** via the EMIF_Qsys IP (cloned from de25_lpddr4_bw/ao486,
|
||||
same device): emif_clk ~310 MHz, emif_reset_n = cal-ready. Reuse the Ch318 writer chain
|
||||
(`gs_lpddr_axi_master` + `gs_async_fifo` + counters), just retargeted onto the EMIF AXI
|
||||
write port instead of f2sdram. New `gs_lpddr_rd_probe.sv` lets the HPS read any FB word back
|
||||
over the bridge (`LPDDR_RDADDR` @ 0x03C: write byte-addr → poll `LPDDR_STATUS[3]` rd_pending
|
||||
→ read the 32-bit word); the `lpddr_dump` tool walks this to pull a whole frame to a PPM.
|
||||
**Crucially the FB is FPGA-private, NOT Linux RAM** — so verification is via the bridge probe
|
||||
+ bridge COUNTERS (bytes/bursts/bresp_err), never /dev/mem. SILICON-VERIFIED: write 8 KiB →
|
||||
bridge readback hash matches the source (md5 3b12baffc00bb6419fa66272c75b2cc7), BRESP_ERRS=0.
|
||||
|
||||
## Ch320 — LPDDR4B scanout to HDMI (SILICON-VERIFIED)
|
||||
|
||||
Display the LPDDR4B framebuffer on HDMI. `gs_lpddr_scanout.sv`: a whole-frame cache (an 8 KiB
|
||||
M20K copy of the frame, NOT an ao486-style line buffer) filled from LPDDR via single-beat
|
||||
reads (arlen=0 — the ONLY AXI read pattern proven on this EMIF; multi-beat bursts garble),
|
||||
indexed by the PCRTC `vram_read_addr`. `gs_lpddr_rd_arb.sv`: a 2:1 read arbiter sharing the
|
||||
EMIF read channel (port0 scanout = priority, port1 Ch319 probe). de25 top muxes the video
|
||||
source (BRAM default / LPDDR scanout) on `LPDDR_CTRL[2]` video_src, gated by the PCRTC
|
||||
display-window (`pix_window_o`). SILICON-VERIFIED at 64×64: scanout pixel-identical to BRAM.
|
||||
**Bug found+fixed on silicon:** the scanout ignored the PCRTC display window → 10 sheared
|
||||
tiles; fixed by exposing `pix_window_o` and gating the scanout mux. The whole-frame cache
|
||||
DOES NOT SCALE — see Ch321: at 1024 beats (32 KiB) it never finishes loading on this EMIF.
|
||||
|
||||
## Ch321 — larger FB (128×128 PSMCT16) + LINE-BUFFER scanout (SILICON-VERIFIED) — ACCEPTED ARCHITECTURE
|
||||
|
||||
Two bricks. **Brick 1 (render):** new 128×128 PSMCT16 fixture (32 KiB frame) +
|
||||
`GS_TILE_LPDDR128_DEMO` profile (VRAM grown 8→64 KiB so a 32 KiB frame fits, TILE grid 8×8 of
|
||||
16×16 tiles). **Brick 2 (scanout) — the real deliverable:** `gs_lpddr_scanout_lb.sv`, a
|
||||
double-buffered LINE-BUFFER reader that holds just TWO scanlines (displays row L from buf[L&1]
|
||||
while prefetching the next row), O(width) on-chip not O(width×height). **DECISION: the
|
||||
whole-frame cache is REFERENCE/FALLBACK only, NOT the architecture** — a cache that "fits"
|
||||
still MIRRORS the FB in M20K, defeating the move to LPDDR, and empirically it won't even load
|
||||
a 32 KiB frame on this EMIF (frame-cache `0x4` → cache_valid never sets, blank). The
|
||||
line-buffer is THE scanout path going forward. SILICON-VERIFIED: render BURSTS=0x400/BRESP=0;
|
||||
line-buffer `LPDDR_CTRL=0xC` → STATUS line_valid=1/rd_errs=0, HDMI matches the lpddr_dump PPM
|
||||
pixel-for-pixel (no col-1 band — the sim TB's residual 1px/line was confirmed a checker
|
||||
leading-edge artifact, not hardware). **Three real HARDWARE bugs fixed** (the first board
|
||||
attempt garbled): (1) multi-beat burst → single-beat reads (arlen=0); (2) miss-prone request
|
||||
toggle → free-running sequential prefetcher; (3) vsync-mid-read AXI abort → deferred reset
|
||||
(`fs_pending`, never abort an in-flight read). Fit clean: 31,683 ALMs (68%), 117 RAM (33%).
|
||||
|
||||
## Next (per Codex)
|
||||
The framebuffer now lives off-chip (write + line-buffer scanout, silicon-proven). Make
|
||||
TEXTURE storage external next, correctness-first, before any performance sizing:
|
||||
1. **Ch322 — LPDDR-backed texture fetch/cache (correctness-first).** One known texture in
|
||||
LPDDR4B; a small read-only texture cache behind the sampler; BRAM texture path stays as
|
||||
fallback. Acceptance: LPDDR-textured image == BRAM-textured image, rd_errs=0, counters
|
||||
prove LPDDR fills happened. NOTE (prereq-check finding): the nearest-path sampler assumes
|
||||
FIXED 1-cycle texel latency (no stall on the default path — CB_TWAIT only exists for
|
||||
bilinear/combined), so a naive demand-miss stall would corrupt output. Resolve via
|
||||
prefetch-warm (fill the cache fully before raster → every read a 1-cycle hit) OR add a
|
||||
sampler/walker stall — see the Ch322 framing.
|
||||
2. Framebuffer/Z backing to LPDDR with tile flush/reload.
|
||||
3. Command-stream ingestion (defer until both FB and texture memory are off-chip).
|
||||
Only after a real-trace texture-format histogram (doc 0008 §4) is performance LPDDR sizing
|
||||
honest; Ch322 is correctness-only and does NOT pretend to know real-game cache sizing.
|
||||
@@ -0,0 +1,101 @@
|
||||
# 0011 — GS dump ingestion (Ch340): parse, census, translate a supported subset
|
||||
|
||||
Status: **ACCEPTED — Ch340 CLOSED (2026-06-21)** as a parser + census / fail-closed victory. Brick 5
|
||||
(authentic on-silicon render) is **explicitly waived** because the authentic dump contains zero
|
||||
supported segments — that is the census doing its job, not a failure.
|
||||
|
||||
## Closeout (honest framing, per Codex)
|
||||
- Authentic `cubes_demo` GS dump (MIT homebrew, content-clean) parsed **deterministically, 0 malformed**.
|
||||
- Container format pinned from PCSX2 source and validated byte-exact; byte-exact synthetic parser test
|
||||
passes (`tools/test_gs.sh`).
|
||||
- Primitive reconstruction (GS vertex-kick model) works: **648 triangles + 540 sprites**.
|
||||
- Support census classified **every** primitive; histograms + reasons emitted to
|
||||
`captures/gs/reports/cubes.census.txt` (aggregate only).
|
||||
- Translator **failed closed with no scene**: every authentic triangle is textured (`TME=1`, no real-
|
||||
texture path) and sprites are unsupported. Nothing was approximated.
|
||||
- Core trust-boundary goal achieved: **authentic GS traffic enters the pipeline and unsupported
|
||||
content is reported, not faked.** The translator→`ps2_feeder`→staging path is proven on the
|
||||
supported synthetic fixture; authentic silicon render is deferred to the census-derived blocker.
|
||||
- Mechanical top blocker → **Ch341: textured-triangle ingestion** (real texture state/upload/bind).
|
||||
Do NOT hunt another dump for a convenient flat segment; do NOT substitute synthetic silicon for
|
||||
authentic Brick 5.
|
||||
|
||||
Original design follows (the boundary it set still holds).
|
||||
|
||||
## Goal & trust boundary
|
||||
Authentic GS traffic enters the proven host pipeline, is decoded **deterministically**, and a
|
||||
**strictly-supported subset** reaches pixels with **no hidden approximation**. Ch340's honest victory
|
||||
is that property — NOT a full game frame rendering. Real captures will expose unsupported textures,
|
||||
transfers, blend/state, and primitive modes; those are **reported**, never approximated.
|
||||
|
||||
## Pipeline
|
||||
```
|
||||
.gs[.xz] ──(container parser)──► raw packets ──(GIF/GS decoder)──► normalized event stream
|
||||
│ │
|
||||
│ (local, gitignored) ▼
|
||||
└────────────────────────────────────────────────► support census + histograms (reports/)
|
||||
│
|
||||
supported subset ──(translator)─┴─► ps2_feeder scene file
|
||||
(Ch339 encoder streams it)
|
||||
```
|
||||
The translator emits the Ch339 text scene grammar (`tri`/`trig`/`tritile`/`rect`/`go`); it does NOT
|
||||
re-implement the staging format. `ps2_feeder -f scene.txt` renders it on the existing bitstream.
|
||||
|
||||
## Normalized event stream (schema v1, versioned)
|
||||
A flat, ordered list; every event carries `byte_off`, `frame_idx`, `event_idx`. Event kinds:
|
||||
- `FRAME_BOUNDARY {field}` — VSync packet (frame delimiter).
|
||||
- `GIFTAG {path, nloop, eop, pre, prim, flg, nreg, regs}` — decoded GIF tag header.
|
||||
- `GSREG {addr, name, value}` — a GS register write (from A+D, REGLIST, or PACKED expansion).
|
||||
- `IMAGE {qwc, dst_fmt}` — an IMAGE-mode (HWREG/texture/FB upload) transfer; payload bytes summarized,
|
||||
NOT inlined into committable output.
|
||||
- `READFIFO {qwc}` — local→host transfer.
|
||||
- `MALFORMED {reason}` — structural decode failure at this offset.
|
||||
|
||||
`GSREG.name` covers the register set we already encode in `bake.py`/`ps2_feeder`: PRIM, RGBAQ, ST, UV,
|
||||
XYZ2/XYZ3, XYZF2/XYZF3, TEX0_1/2, CLAMP, FOG, TEX1/2, FRAME_1/2, ZBUF_1/2, TEST_1/2, ALPHA_1/2,
|
||||
SCISSOR, PRMODE/PRMODECONT, BITBLTBUF, TRXPOS, TRXREG, TRXDIR, etc. Unknown addrs → `GSREG` with
|
||||
`name="UNKNOWN_0xNN"` (reported, not dropped).
|
||||
|
||||
## Support census (every event classified)
|
||||
- **translated** — emitted into the ps2_feeder scene (the supported subset, below).
|
||||
- **ignored (justified)** — safely skippable with a stated reason (e.g. FOG with FGE off; a redundant
|
||||
state write; a NOP). The justification is explicit per category.
|
||||
- **unsupported** — a real effect we cannot faithfully reproduce yet (textured prim, sprite with a
|
||||
real texture, blend mode ≠ the proven source-over, PSM we don't render, Z format, dest-alpha test,
|
||||
scissor we don't honor, TRIANGLE_FAN/STRIP we haven't reduced, lines/points, IMAGE texture upload).
|
||||
Recorded with frame/event/byte offset + the exact reason. **Never approximated.**
|
||||
- **malformed** — structural failure (bad GIF tag, truncated payload, length mismatch).
|
||||
|
||||
Reports (committable, no game content): per-dump JSON/text with frame count, a GS-register-write
|
||||
histogram, a primitive-mode histogram, and the full unsupported/malformed list with offsets+reasons.
|
||||
|
||||
## Supported subset that reaches pixels (Ch340 v1)
|
||||
Matches what the feeder + `ps2_feeder` already render faithfully on silicon:
|
||||
- `PRIM` = TRIANGLE (prim type 3), with `IIP` flat or gouraud (per-vertex RGBAQ).
|
||||
- Vertices via `XYZ2`/`XYZ3` (Z honored — Ch338 cross-batch Z is correct).
|
||||
- Color via `RGBAQ` (MODULATE through the unity texel — matches the proven path).
|
||||
- `FRAME_1`/`ZBUF_1`/`TEST_1`(GEQUAL)/`ALPHA_1`(the proven source-over) within the supported envelope.
|
||||
A draw segment qualifies only if EVERY primitive in it is in this subset and the state matches the
|
||||
proven envelope. Sprites→rect and triangle-strip→triangle reduction are candidate Ch341 work, decided
|
||||
from the census, not pre-built.
|
||||
|
||||
## Acceptance (Codex)
|
||||
1. Byte-exact parser tests on a small **synthetic** `.gs` fixture (`captures/gs/synthetic/`, authored
|
||||
once the real container format is confirmed).
|
||||
2. One authentic dump parses **deterministically** (same events every run).
|
||||
3. Frame / register / primitive histograms emitted.
|
||||
4. Unsupported events carry frame/event/byte offset + reason.
|
||||
5. ≥1 carefully chosen supported frame/segment translates to a ps2_feeder scene and **renders on
|
||||
silicon**.
|
||||
6. Translation failures **stop before board access**.
|
||||
|
||||
## Bricks (gated on the dump)
|
||||
1. Inspect the real dump bytes; confirm the container framing (header, compression, packet types).
|
||||
Build the container parser + a matching synthetic fixture; byte-exact parser test.
|
||||
2. GIF/GS decoder → normalized events (GIF tag + A+D/REGLIST/PACKED register expansion). Unit-tested
|
||||
against hand-built GIF packets (the encode side already exists in `bake.py`).
|
||||
3. Census + histograms over the normalized stream; emit reports.
|
||||
4. Translator: supported-subset events → ps2_feeder scene file; everything else → census. Fail closed.
|
||||
5. Pick a supported segment from the census; render via `ps2_feeder -f`; confirm on silicon.
|
||||
|
||||
Ch341 is then chosen from the census's highest-impact blocker, not guessed now.
|
||||
@@ -0,0 +1,86 @@
|
||||
# 0012 — Ch347: CLUT (PSMT8) textured-alpha sprites
|
||||
|
||||
Status: planned (synthetic brick buildable now; authentic acceptance gated on a real capture)
|
||||
Date: 2026-06-23
|
||||
|
||||
## Goal
|
||||
|
||||
Extend the Ch344/Ch345a textured-alpha SPRITE path from PSMCT32-only to **PSMT8 indexed (CLUT) textures**:
|
||||
`TEX0.PSM=0x13` → fetch 8-bit index from VRAM → CLUT → ABGR texel → MODULATE → source-over alpha. This is
|
||||
the first "real game" GS feature beyond the homebrew corpus (which is anomalously all-PSMCT32); PS2 titles
|
||||
lean on palettized textures to fit VRAM, so a richer free corpus (Ch347 target: a ScummVM-freeware capture,
|
||||
Beneath a Steel Sky) forces CLUT. Scope is **PSMT8 only** — PSMT4 (nibble/RMW) deferred unless census forces it.
|
||||
|
||||
## Key finding: the CLUT machinery is ~95% already built (search-before-reimplement)
|
||||
|
||||
The platform already has, and PROVES for textured TRI/SPRITE **DECAL** (Ch296/297/299/314):
|
||||
- `clut_stub.sv` — 256×32 CLUT RAM, **two** combinational read ports; one is already dedicated to the
|
||||
texture sampler (`tex_read_idx`→`tex_read_data`).
|
||||
- `clut_loader_stub.sv` — VRAM→CLUT load FSM, CLD-mode policy, PSMCT32/PSMCT16 unpack, `load_busy` guards read2.
|
||||
- `gs_texel_addr.sv` PSMT8 path — 1 byte/texel linear byte address; `gs_swizzle_psmt8_stub.sv` for swizzle.
|
||||
- `gs_texture_unit.sv` (Ch296) — byte-lane extract from the 32-bit word + CLUT lookup; output is `.tex_color`.
|
||||
- gs_stub already decodes TEX0 CLUT fields (CBP/CPSM/CSM/CSA/CLD) and the textured-DECAL gate already
|
||||
admits PSM 0x13/0x14.
|
||||
|
||||
Critically: the Ch344 half-rate sprite datapath captures **`s1_tex_color`**, and `s1_tex_color` IS the
|
||||
`gs_texture_unit` output (gs_stub.sv:4352) — i.e. already CLUT-decoded for PSMT8. So the CLUT decode happens
|
||||
upstream of the half-rate capture.
|
||||
|
||||
## What actually needs doing
|
||||
|
||||
1. **Relax the textured-alpha SPRITE eligibility gate** (`new_tex_abe_active`, gs_stub.sv ~:5114):
|
||||
`(tex0_psm==6'h00)` → `(tex0_psm==6'h00 || tex0_psm==6'h13)` (PSMT8). PSMT4 (0x14) left out for v1.
|
||||
2. **Validate the timing** — the one real risk. PSMT8 adds a byte-lane SELECT; under `TEX_RD_REGISTERED=1`
|
||||
(the board config) the selector is realigned (`SEL_DELAY`). The Ch344 half-rate capture (ta_tex_q/ta_tex_q1,
|
||||
the 1-deep texel delay) was tuned to PSMCT32's registered-read latency. We must prove the CLUT-decoded
|
||||
texel is still valid at the frozen-beat capture for PSMT8 — a COMBINATIONAL-read TB would be a FALSE GREEN
|
||||
(this exact trap bit Ch344). Use a **registered-read** TB.
|
||||
3. **CLUT precondition**: a TEX0_1 write with CLD≠0 must fire (loading clut_stub) before the sprite draws —
|
||||
same precondition as the proven indexed-DECAL path; declared, asserted in the TB.
|
||||
|
||||
## Pre-fit synthetic TB (buildable NOW — no capture needed), proving Codex's 5 points
|
||||
|
||||
`tb_gs_psmt8_alpha_sprite` (registered-read model, SPRITE_TEX_ALPHA=1, TEX_RD_REGISTERED=1):
|
||||
1. index fetch hits the right byte (PSMT8 linear address → correct VRAM byte lane);
|
||||
2. CLUT maps index → ABGR (program clut_stub via a CLD≠0 TEX0 / loader);
|
||||
3. the **texel's** alpha (from the CLUT entry) drives source-over against the dest;
|
||||
4. **no read2 collision** regression (texel read on primary beat, dest on frozen beat, CLUT lookup is
|
||||
combinational — assert no overlap, incl. vs `load_busy`);
|
||||
5. the **PSMCT32** sprite path stays green (cross-check the existing tb_gs_textured_alpha_sprite + regression).
|
||||
|
||||
Acceptance for the synthetic brick: TB passes + full regression + quartus_syn 0-err. This banks the hardware
|
||||
without claiming authentic content.
|
||||
|
||||
## Synthetic ≠ authentic — two separate labels (Codex)
|
||||
|
||||
The datapath proof (`tb_gs_psmt8_alpha_sprite`) proves index→CLUT→ABGR→source-over works. It is NOT authentic
|
||||
CLUT *ingestion*. Authentic PSMT8 additionally requires the emitted TEX0's CLUT-side fields to select a CLUT
|
||||
that is actually loaded and resident:
|
||||
- **Screening (DONE, Ch346):** `gs_texture_residency.py` now decodes CBP/CPSM/CSM/CSA/CLD and, for indexed-PSM
|
||||
(0x13/0x14) candidates, REQUIRES a resident CLUT upload at CBP before the draw (epoch-tracked, same as the
|
||||
texture) — else REJECT. It also flags CLD=0 (no load trigger -> possibly-stale palette). So `residency_ok()`
|
||||
won't green-light a PSMT8 candidate whose palette isn't resident.
|
||||
- **Emission (capture-step TODO):** the feeder/translator must carry the CLUT-side TEX0 fields. Today
|
||||
`ps2_feeder.c`'s `tex0 TBP TBW TW TH TFX` grammar packs ONLY texture-side fields — it needs CBP/CPSM/CSM/CSA/
|
||||
CLD added (and the fixture must upload the palette to CBP + a CLD!=0 TEX0 so clut_loader_stub fires). Build
|
||||
this around the exact Ch346-selected candidate, not speculatively.
|
||||
|
||||
## Board-fit guardrail (Codex guardrail 1) — RESOLVED
|
||||
|
||||
The "missing HDMI IO_STANDARD" the synth smoke reported was a FALSE alarm: the assignments are present + correct
|
||||
in the QSF (with an `-entity` qualifier); the scaffold check's regexes were EOL-anchored and didn't tolerate the
|
||||
qualifier. Fixed 3 checks in sim/Makefile (VIRTUAL_PIN + HDMI/ADV7513 IO_STANDARD). The QSF carries the full
|
||||
77-source list (incl. osd/qsys platform modules under USE_QSYS_TOP) so the owner's board fit is unaffected.
|
||||
NOTE: `quartus_syn_only` itself is a reduced smoke (files.f, 115 entries) that OMITS the platform modules, so it
|
||||
can't fully elaborate the de25 top — a pre-existing smoke-scope limitation, not a board-fit blocker. Quartus
|
||||
analyzed the Ch347 gs_stub change clean (the 7 elaboration errors are all unrelated platform entities).
|
||||
|
||||
## Authentic acceptance (gated on the capture — do NOT commit the target until it exists)
|
||||
|
||||
1. Capture a Beneath a Steel Sky (ScummVM-freeware) GS dump.
|
||||
2. `gs_texture_residency.py` (Ch346) picks a RESIDENT, plausible PSMT8 candidate WITH a resident CLUT —
|
||||
**prefer a no-wrap footprint** so we don't repeat the Ch345b wrap-mode ambiguity.
|
||||
3. Extend `ps2_feeder.c`/translator with CLUT-side TEX0 fields + palette upload; emit the scene; software
|
||||
reference pixel-diffs; then board fit (after confirming the board profile's clut_load_busy wiring).
|
||||
|
||||
Provenance: all dump-derived content stays LOCAL/gitignored, same discipline as the cube/sprite fixtures.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Design Decisions
|
||||
|
||||
This directory is for short decision records once the team starts locking items.
|
||||
|
||||
Suggested format:
|
||||
|
||||
- title
|
||||
- date
|
||||
- status
|
||||
- context
|
||||
- options considered
|
||||
- decision
|
||||
- consequences
|
||||
|
||||
Locked so far:
|
||||
|
||||
- `0000-trace-format.md`
|
||||
- `0001-posture.md`
|
||||
- `0002-bios-policy.md`
|
||||
- `0003-golden-reference.md`
|
||||
- `0004-first-visible-milestone.md`
|
||||
- `0005-phase0-source-of-truth.md`
|
||||
Reference in New Issue
Block a user