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:
2026-06-29 20:10:50 -04:00
commit ec82764bef
2462 changed files with 2174303 additions and 0 deletions
@@ -0,0 +1,467 @@
# top_psmct32_raster_demo on DE25-Nano (current as of Ch165)
This directory is the **DE25-Nano synthesis project** for
[`rtl/top/de25_nano_psmct32_raster_demo_top.sv`](../../../rtl/top/de25_nano_psmct32_raster_demo_top.sv)
(the Ch149 board wrapper, retargeted in **Ch159** to instantiate
the BRAM-backed `top_psmct32_raster_demo_bram` instead of the
Ch146 legacy `top_psmct32_raster_demo`). The directory grew
across these chapters:
| Chapter | Added |
|---------|----------------------------------------------------------------------------------------------------|
| Ch148 | `files.f` (RTL filelist, Ch123 dep tree only) + this README. |
| Ch149 | (RTL only — `de25_nano_psmct32_raster_demo_top.sv` lives in `rtl/top/`). |
| Ch150 | `de25_nano_psmct32_raster_demo_top.qsf` + `.sdc` — minimal Quartus scaffold. |
| Ch151 | (RTL only — `de25_nano_pll_stub.sv` + PLL/lock-gated-reset rework of board top). |
| Ch152 | `build_quartus.sh` + `parse_reports.py` — first real Quartus compile (fit FAILED). |
| Ch159 | Board-top swap to `top_psmct32_raster_demo_bram` (Ch155-Ch158 BRAM/normalize/PCRTC stack); fit + STA succeed. `baseline_ch152/` snapshots the prior fit-failed reports for diff. |
| Ch160 | SDC retarget from 50 MHz → 30 MHz (down-clock profile); `build_quartus.sh` runs `quartus_asm` on clean STA → first `.sof` bitstream produced. `baseline_ch159/` snapshots the 50 MHz timing-miss reports. |
| Ch161 | Real Quartus IOPLL `.ip` commit (50 MHz refclk → 30 MHz outclk_0) + `USE_PLL_IP=1` macro + `ip/` symlink in build_quartus.sh; SDC restored to 20 ns CLOCK2_50 (the IP's auto-generated SDC handles the post-PLL clock). The `.sof` now genuinely runs at 30 MHz on hardware. `baseline_ch160/` snapshots the SDC-profile-only state. |
| Ch162 | `STRIP_HW_DIVIDER` parameter on `ee_core_stub` removes the auto-inferred 32-bit DIVU divider from the synth path on hardware builds (default off in sim preserves every existing TB). Fmax 30.74 → **33.6 MHz** (+9.4 %); fit ALMs 892, registers 734. New critical path: PCRTC magnification divider in `gs_pcrtc_stub` (`hwin_rel / hmag_factor`). `baseline_ch161/` snapshots the pre-strip state. |
| Ch163 | `STRIP_PCRTC_MAG_DIV` parameter on `gs_pcrtc_stub` strips the PCRTC magnification dividers (constant divisor 1 when MAGH=MAGV=0). Fmax 33.6 → **81.83 MHz** at 30 MHz target (+143 %), then PLL `.ip` retuned 30 MHz → **50 MHz** outclk_0; STA closes at 50 MHz with +7.500 ns setup slack and Fmax 80.0 MHz. **First .sof that genuinely runs at 50 MHz on the DE25-Nano.** `baseline_ch162/` + `baseline_ch163_30mhz/` snapshots the milestones. |
| Ch164 | First video-PHY shim — pins HDMI_TX_CLK + HDMI_TX_D[23:0] + HS/VS/DE on the DE25-Nano ADV7513 (pinout sourced from `retroDE_nes`). Wrapper drives them combinationally from VIDEO_* (R in MSBs of HDMI_TX_D). HDMI_TX_CLK = design_clk = 50 MHz post-PLL. ADV7513 I²C wake-up FSM still deferred (Ch165) so a real monitor stays dark — but pixels are now off-chip on the HDMI connector. STA stays clean (+7.536 ns slack); pins 17 → 45. `baseline_ch163_50mhz/` snapshots the pre-shim state. |
| Ch165 | ADV7513 I²C wake-up FSM (Terasic-derived; ported from `retroDE_splash/rtl/platform/`). Adds 4 control pins (`HDMI_I2C_SCL/SDA` open-drain bus + `HDMI_TX_INT` interrupt + `HDMI_MCLK` audio reference); `LED[3] = ~hdmi_init_done`. The 38-entry LUT walks ADV7513 register writes (power-up + HPD override + AVI InfoFrame + HDMI mode select), turning the chip from standby into "transmitting RGB on the HDMI port". Pins 45 → 49; STA setup slack +7.198 ns; `.sof` clean. **First .sof that should drive a real HDMI monitor.** `baseline_ch164/` snapshots the pre-wake-up state. |
## Ch163 strip-PCRTC-divider + 50 MHz close state
The full journey from Ch152's fit failure to a real 50 MHz
bitstream:
| Metric | Ch152 (50 MHz) | Ch159 (50 MHz) | Ch161 (real PLL @ 30 MHz) | Ch162 (strip EE div, 30 MHz) | **Ch163 (strip both, 50 MHz)** |
|-----------------------------------|-------------------|-----------------------|---------------------------|-------------------------------------|--------------------------------------|
| Fit status | **FAILED** | Successful (30,364) | Successful (30,898) | Successful (30,006) | **Successful (27,543)** |
| Fit RAM blocks | 6 | 14 | 14 | 14 | 14 |
| Fit PLLs | 0 | 0 | 1 | 1 | **1 (50 MHz outclk0)** |
| Setup slack worst (design domain) | (did not run) | 6.950 ns | +0.565 ns @ 30 MHz | +3.567 ns @ 30 MHz | **+7.500 ns @ 50 MHz** |
| **Fmax (design domain)** | (did not run) | 37.11 MHz | 30.74 MHz | 33.6 MHz | **80.0 MHz** |
| `.sof` produced | (skipped) | (skipped) | yes — 30 MHz on hardware | yes — Fmax 33.6 MHz / 30 MHz target | **yes — 50 MHz on hardware** |
Ch163 lands in two stages:
- **Stage A**: with `STRIP_PCRTC_MAG_DIV=1` on the board top
but the PLL still at 30 MHz output, Fmax jumps from 33.6 MHz
(Ch162) to **81.83 MHz** at the 30 MHz target — +143 %, well
past 50 MHz.
- **Stage B**: retune `pll.ip` from 30 MHz → 50 MHz output
(`gui_output_clock_frequency0` = 50.0; `gui_output_clock_frequency_ps0`
= 20000.0), `quartus_ipgenerate` regenerates the .qip / synth
files. CLOCK2_50 stays at the physical 50 MHz period in the
SDC; the IOPLL's auto-generated SDC declares the new
outclk_0. Quartus rebuild → STA closes at 50 MHz with
**+7.500 ns** of setup slack and Fmax **80.0 MHz**.
The Stage B `.sof` is the first bitstream that genuinely runs
at 50 MHz on the DE25-Nano. The Ch161 PLL hardware-real
contract carries through; the IOPLL takes 50 MHz CLOCK2_50 in
and emits 50 MHz outclk_0, so the chip-internal clock
distribution still goes through the dedicated IOPLL clock
network even at the 1:1 frequency relation.
## Ch162 strip-divider state (vs Ch161 / Ch160 / Ch159 / Ch152 baselines)
| Metric | Ch152 (50 MHz) | Ch159 (50 MHz) | Ch160 (30 MHz profile) | Ch161 (real PLL @ 30 MHz) | Ch162 (real PLL + strip divider) |
|-----------------------------------|-------------------|-----------------------|---------------------------|-----------------------------------|-------------------------------------|
| Fit status | **FAILED** | Successful (30,364) | Successful (31,056) | Successful (30,898) | Successful (30,006) |
| Fit registers | 121,176 | 39,085 | 37,381 | 37,352 | 36,618 |
| Fit RAM blocks | 6 | 14 | 14 | 14 | 14 |
| Fit PLLs | 0 | 0 | 0 | 1 | 1 |
| Setup slack worst (design domain) | (did not run) | 6.950 ns | +0.805 ns | +0.565 ns @ `iopll_0_outclk0` | **+3.567 ns** @ `iopll_0_outclk0` |
| **Fmax (design domain)** | (did not run) | 37.11 MHz | 30.74 MHz | 30.74 MHz | **33.6 MHz** (+9.4 %) |
| `.sof` produced | (skipped) | (skipped) | yes (profile only) | yes — 30 MHz on hardware | **yes — 33.6 MHz Fmax / 30 MHz target** |
Stripping the EE-core 32-bit DIVU divider freed +3 ns of setup
margin and 892 ALMs / 734 registers. The new STA worst path
moves to the **PCRTC magnification divider** in
[`gs_pcrtc_stub.sv`](../../../rtl/gif_gs/gs_pcrtc_stub.sv)
(`vram_x_unshift = hwin_rel / hmag_factor` and the matching y
form). **Ch163 gates that divider** via `STRIP_PCRTC_MAG_DIV`
and retunes the PLL to 50 MHz — see the Ch163 section above
for the milestone numbers.
## Ch161 real-PLL state (vs Ch160 / Ch159 / Ch152 baselines)
| Metric | Ch152 (50 MHz) | Ch159 (50 MHz) | Ch160 (30 MHz profile) | Ch161 (real PLL @ 30 MHz) |
|-----------------------------------|-------------------|---------------------------|---------------------------|-----------------------------------|
| Fit status | **FAILED** | Successful (30,364) | Successful (31,056) | Successful (30,898) |
| Fit registers | 121,176 | 39,085 | 37,381 | 37,352 |
| Fit RAM blocks | 6 | 14 | 14 | 14 |
| **Fit PLLs** | 0 | 0 | 0 | **1** (real IOPLL) |
| Setup slack worst (design domain) | (did not run) | 6.950 ns | +0.805 ns | **+0.565 ns** @ `iopll_0_outclk0` |
| Fmax (design domain) | (did not run) | 37.11 MHz | 30.74 MHz | **30.74 MHz** |
| `.sof` produced | (skipped) | (skipped) | yes (profile only) | **yes — 30 MHz on hardware** |
The Ch161 `.sof` is the first bitstream that genuinely runs at
the constrained frequency on the real DE25-Nano: the IOPLL
takes the 50 MHz CLOCK2_50 input and divides to 30 MHz inside
the chip, so the entire design downstream of `u_pll.outclk_0`
operates at 30 MHz. Critical path was the EE core's auto-
generated 64-bit DIVU divider — **closed in Ch162** via
`STRIP_HW_DIVIDER`. New critical path (Ch162 onward): the PCRTC
magnification divider in `gs_pcrtc_stub.sv` (`hwin_rel /
hmag_factor`); see the Ch162 section above.
## Ch160 down-clock + first .sof state (vs Ch159 / Ch152 baselines)
| Metric | Ch152 (50 MHz) | Ch159 (50 MHz) | Ch160 (30 MHz) |
|-------------------------------|---------------------------|---------------------------|---------------------------|
| Fit status | **FAILED** (155k / 331 %) | Successful (30,364 / 65 %) | Successful (31,056 / 66 %) |
| STA setup slack worst | (did not run) | 6.950 ns | **+0.805 ns** |
| Fmax | (did not run) | 37.11 MHz | 30.74 MHz |
| `quartus_asm` (.sof produced) | (skipped — fit failed) | (skipped — STA missed) | **Successful** |
The Ch160 SDC profile retargets CLOCK2_50 from 20.000 ns
(50 MHz) to 33.333 ns (30 MHz) so the fitter has positive
slack on every report. `quartus_asm` now runs on every clean
build, so a real `.sof` bitstream lands in
[`output_files/de25_nano_psmct32_raster_demo_top.sof`](output_files/de25_nano_psmct32_raster_demo_top.sof).
Worst-case path is the EE core's auto-generated 64-bit divider
(actually the Ch43 DIVU divider, dead code in the PSMCT32 demo
since the bootlet doesn't execute DIVU); **closed in Ch162**
via `STRIP_HW_DIVIDER`. Programming the Ch160 `.sof` onto a
real board where CLOCK2_50 is still wired straight through
gives a 50 MHz chip
clock that may setup-violate the divider path; Ch161+'s
PLL-IP commit fixes that.
## Ch159 fit-success state (vs Ch152 baseline)
`make quartus_compile` runs the full syn/fit/sta flow. With
the Ch159 BRAM-backed top:
| Metric | Ch152 (vram_stub) | Ch159 (vram_bram_stub) |
|-----------------------------------|-------------------------------|-------------------------------|
| Synthesis status | Successful | Successful |
| Synthesis ALMs estimate | 199,103 / 46,800 (425% over) | **22,704 / 46,800 (49%)** |
| Synthesis registers | 101,457 | **36,008** |
| **Fit status** | **FAILED** (155k / 331% over) | **Successful** (30,364 / 65%) |
| Fit registers | 121,176 | **39,085** |
| Fit RAM blocks | 6 / 358 | **14 / 358** |
| **STA status** | **DID NOT RUN** | **Successful** (12 warnings) |
| Setup slack worst (CLOCK2_50) | n/a | 6.950 ns |
| Fmax | n/a | 37.11 MHz |
The headline numbers: **synth ALMs 88.6 %, fit registers
67.7 %, +8 RAM blocks** (the `vram_bram_stub` 8 KiB dual-port
shape that Ch154 exp_c forecast). Fit and STA both run through
to completion. Setup slack is negative at the 50 MHz CLOCK2_50
constraint — Fmax is currently 37.11 MHz, so timing closure
(PLL down-clock or critical-path pipelining) is the **Ch160+**
surface; Ch159 deliberately stops at "fits and reaches STA."
The full chapter narrative lives in
[`docs/contracts/gif_gs.md`](../../../docs/contracts/gif_gs.md)
under "Board-top swap to BRAM wrapper + Quartus fit recovery
(Ch159)". The Ch152 baseline reports are preserved under
[`baseline_ch152/`](baseline_ch152/) for diff and audit.
Ch150's QSF/SDC pin clock + reset + LED I/O for a real Quartus
build, but **`VIDEO_R/G/B/HSYNC/VSYNC/DE` are intentionally left
as virtual pins** (`VIRTUAL_PIN ON` in the QSF) — they will not
toggle real package pins until the PHY shim chapter (Ch151+)
maps them to a video output. Without virtualization, Quartus
would auto-place an unassigned top-level output on an arbitrary
package pin.
## Files
| File | Purpose |
|---------------------------------------------------|----------------------------------------------------------------|
| `files.f` | Synthesis filelist — Ch123 dep tree + Ch149 board wrapper. |
| `de25_nano_psmct32_raster_demo_top.qsf` | **Ch150** — Quartus pin assignments + IO standards + macros. |
| `de25_nano_psmct32_raster_demo_top.sdc` | **Ch150** — clock + reset-sync + IO false-path constraints. |
| `README.md` | This file. |
The Ch150 `.qsf` + `.sdc` together are PHY-light: clock / reset /
LED pins are pinned and constrained, but video pins
(`VIDEO_R/G/B/HSYNC/VSYNC/DE`) are **not** pin-assigned. The PHY
shim chapter (Ch151+) maps them to a real connector.
To validate the Quartus scaffold (.qsf + .sdc + filelist +
fixtures) without launching Quartus:
```
make -C sim top_psmct32_raster_demo_quartus_scaffold_check
```
## Top module
**Set Quartus's top-level entity to `de25_nano_psmct32_raster_demo_top`**
(in [`rtl/top/de25_nano_psmct32_raster_demo_top.sv`](../../../rtl/top/de25_nano_psmct32_raster_demo_top.sv)).
This is the Ch149 board-shaped wrapper, retargeted in **Ch159**
to instantiate `top_psmct32_raster_demo_bram` (the BRAM-backed
inner module from Ch155+, carrying every Ch155-Ch158 fix). It
adds the DE25-Nano-specific plumbing — Terasic-canonical port
names, reset-release sequencer, `core_go` pulse generator, and
active-low LED status mapping.
The legacy Ch146 inner module `top_psmct32_raster_demo` is kept
on the project file list for back-compat with sim TBs that
still target it; on Quartus only the actually-instantiated top
is elaborated. **Do NOT set the top entity to either of the
inner modules** (`top_psmct32_raster_demo` or
`top_psmct32_raster_demo_bram`) — that bypasses every board
adapter and exposes the inner module's `clk` / `rst_n` /
`core_go` / `r/g/b/hsync/vsync/de` / status outputs directly,
which is useful for sim and lint but not for an FPGA build.
### Ch149 board wrapper ports (`de25_nano_psmct32_raster_demo_top`)
| Port | Direction | Width | Role |
|-------------------------------------|-----------|-------|------------------------------------------------------------|
| `CLOCK0_50` / `CLOCK1_50` / `CLOCK2_50` | input | 1 ea. | DE25-Nano 50 MHz oscillators. Only `CLOCK2_50` is used. |
| `KEY[1:0]` | input | 2 | Active-LOW push buttons. `KEY[0]` = soft reset. |
| `SW[3:0]` | input | 4 | DIP switches; placeholder, unused. |
| `LED[7:0]` | output | 8 | Active-LOW. `LED[2:0]` = status, `LED[7:3]` = OFF. |
| `VIDEO_R/G/B` | output | 8 ea. | Raw 8-bit RGB; PHY shim deferred to next chapter. |
| `VIDEO_HSYNC/VSYNC/DE` | output | 1 ea. | Raw video timing; PHY shim deferred. |
LED mapping (active-low; signal asserted lights its LED):
| LED | Polarity-corrected source |
|-----------|---------------------------|
| `LED[0]` | `~core_halt` |
| `LED[1]` | `~dma_done_seen` |
| `LED[2]` | `~frame_seen` |
| `LED[7:3]`| tied HIGH (OFF) |
### Inner module ports (`top_psmct32_raster_demo_bram`, Ch155+)
For sim / lint use only. The board wrapper above adapts these
to the DE25-Nano signal names; a custom integration could
re-adapt them for a different board. The legacy Ch146 inner
module `top_psmct32_raster_demo` exposes the same external
port shape (just with a `vram_stub`-backed implementation
instead of `vram_bram_stub`).
| Port | Direction | Width | Role |
|-----------------|-----------|-------|---------------------------------------------------------------|
| `clk` | input | 1 | Single clock domain (see "Clock plan" below). |
| `rst_n` | input | 1 | Active-low synchronous reset (see "Reset plan"). |
| `core_go` | input | 1 | Pulsed high to start the EE bootlet (see "core_go strategy"). |
| `r/g/b` | output | 8 ea. | 8-bit RGB scanout (PCRTC). |
| `hsync/vsync/de`| output | 1 ea. | Standard video timing. |
| `core_halt` | output | 1 | High once the EE has SYSCALL'd. |
| `dma_done_seen` | output | 1 | Sticky: DMAC ch2 fired EV_DMA_DONE. |
| `frame_seen` | output | 1 | Sticky: PCRTC end-of-frame fired ≥1 frame. |
## Required preprocessor macros
Both must be set on the synthesis tool (NOT as module generics —
they're `\`define` macros per the Ch146 iverilog-12 string-
parameter forwarding workaround):
| Macro | Value |
|----------------------------------------------------|---------------------------------------------|
| `TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE` | Absolute path to `bios.mem` |
| `TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE` | Absolute path to `payload.mem` |
For Quartus: see the `set_global_assignment -name VERILOG_MACRO`
example in [`docs/contracts/gif_gs.md`](../../../docs/contracts/gif_gs.md)
under "Synthesis-facing macros".
## Required `.mem` fixtures
Both files must exist before synthesis (Quartus's `$readmemh`
runs at elaboration; if the file is missing you get a silent
zero-init that produces no payload at all):
| File | Size | Produced by |
|--------------------------------------------|-----------------------|-------------|
| `sim/data/top_psmct32_raster_demo/bios.mem` | 1024 32-bit words | `bake.py` |
| `sim/data/top_psmct32_raster_demo/payload.mem` | 256 128-bit qwords | `bake.py` |
To produce them:
```
make -C sim top_psmct32_raster_demo_mem
```
To verify both files exist + match expected sizes (and the
synth filelist resolves cleanly):
```
make -C sim top_psmct32_raster_demo_synth_check
```
## DE25-Nano board topology (current as of Ch151)
Live wiring (no longer "assumptions" — the board top
implements it):
### Clock plan
- DE25-Nano's `CLOCK2_50` (50 MHz, PIN_BF23) is the only board
oscillator the design uses. CLOCK0_50 / CLOCK1_50 are pin-
assigned in the QSF for completeness but the wrapper ties
them off internally.
- **Ch151**: CLOCK2_50 → `de25_nano_pll_stub` (sim default,
pass-through) or Quartus IOPLL `pll` (synth, when
`USE_PLL_IP` is defined) → `design_clk`. Everything in the
design (EE core, GIF, GS, PCRTC, `core_go` sequencer, all
three status sticky-bits) is clocked on `design_clk`.
- The PCRTC's H/V counters are parameter-driven (default 16×8)
and do NOT model real CRTC timing — a first hardware build
can run at any sane clock. The PLL chapter (Ch152+) will
commit a real `outclk_0` frequency.
- No clock gating, no derived clocks, no CDCs in the design.
### Reset plan
- **Ch151**: the reset bridge async-asserts on
`(ninit_done | ~pll_locked)` and synchronously deasserts on
`design_clk` through a 2-stage shift register. Both
conditions must clear before the design leaves reset:
1. `ninit_done` falls when FPGA initialization completes
(real Terasic `reset_release` IP under
`\`ifdef USE_TERASIC_RESET_RELEASE_IP`, else inline
16-cycle stub).
2. `pll_locked` rises when the PLL acquires lock (32-cycle
stub warm-up; real IP timing depends on the configured
output frequency).
- `KEY[0]` (active-low push button) is sampled synchronously
through the same 2-stage register — async-assert is reserved
for the FPGA-init / PLL-lock signals.
- The Ch146 wrapper's `rst_n` input is active-low and sampled
inside `always_ff @(posedge clk) if (!rst_n)` — i.e.
**synchronous** reset despite active-low polarity.
### `core_go` strategy
`ee_core_stub.go_i` is sampled **level-sensitively** in the
core's `S_IDLE` state (see [`rtl/ee/ee_core_stub.sv:812-813`](../../../rtl/ee/ee_core_stub.sv#L812-L813)
— `if (go_i) state <= S_IFETCH_REQ`). There is no edge
synchronizer; the core enters `S_IDLE` after reset and
transitions to `S_IFETCH_REQ` on the first cycle `go_i` is
high. Two equivalent hardware paths:
1. **Tie `core_go` high** at the top level. After `rst_n`
deasserts, the core enters `S_IDLE`, sees `go_i == 1`, and
immediately starts fetching from `0xBFC0_0000`. Simplest
wiring.
2. **Reset-release sequencer**: a small board-level FSM that
waits N cycles after `rst_n` deasserts (giving the PLL +
BRAM init time to settle) then drives `core_go` high. Held-
high or one-cycle pulse both work — only the rising-into-
S_IDLE matters.
Option 2 is the **recommended hardware path** because the
deliberate post-reset wait avoids fetching during the BRAM
`$readmemh` settle window. A board-level synchronizer also
gives a clean place to debounce the user push-button if
`core_go` is also user-controllable.
### Video output path
- DE25-Nano video output is **TBD** in this chapter — the next
chapter will wire the top's `r/g/b/hsync/vsync/de` to the
board's chosen video PHY. Likely candidates:
- **HDMI** via on-board ADV7513 (or equivalent). Needs an I²C
config sequence, which adds a small bring-up state machine.
- **VGA** via on-board ADV7123 / passive DAC. Simpler — just
pin-wire the 8-bit RGB + sync signals.
- **Direct GPIO** for an external PMOD video adapter
(simplest path for a first bring-up).
- The Ch146 wrapper's PCRTC outputs are 8-bit RGB at one cycle
per pixel — compatible with all three options after a clock-
rate adjustment.
### LEDs / status
The Ch146 wrapper exposes three sticky status bits the DE25-
Nano user LEDs can show:
| LED | Signal | Meaning |
|----------------|------------------|----------------------------------------|
| User LED 0 | `core_halt` | EE has SYSCALL'd → bootlet finished. |
| User LED 1 | `dma_done_seen` | DMAC ch2 completed transfer. |
| User LED 2 | `frame_seen` | PCRTC has scanned out ≥1 frame. |
All three latch high within ~10 ms of `rst_n` deasserting on a
typical 50 MHz clock. The actual order is **`frame_seen` first**
(the PCRTC starts scanning empty frames as soon as reset
deasserts), then `core_halt` (after the EE bootlet runs to
SYSCALL), then `dma_done_seen` (after the DMAC channel-2
transfer completes). The Ch146 wrapper's `frame_seen` is a
"PCRTC alive" indicator — it doesn't gate on whether the frame
holds rendered content. If you see `core_halt` low, the bootlet
hung; if `dma_done_seen` is low, the DMAC didn't deliver the
GIF payload; if `frame_seen` is low, the PCRTC isn't scanning
out (PLL not locked or reset stuck asserted).
## What's still NOT in this chapter
Landed in Ch165Ch167:
- **Accelerated I²C bring-up TB** — `sim/tb/top/tb_hdmi_i2c_wake_smoke.sv`
instantiates `I2C_HDMI_Config` with `CLK_Freq=2 / I2C_Freq=1` so
the LUT walks in microseconds, asserts (1) the 38-entry walk reaches
`LUT_SIZE-1`, (2) `READY` rises, (3) `HDMI_TX_INT` retriggers the
walk, (4) SDA never resolves to `'x` (no driver conflict), (5)
Ch166 NACK watchdog stays low on the healthy bus and rises
(sticky) when `mI2C_ACK` is forced HIGH, and **(6, Ch167) every
one of the 38 transactions on the wire matches the FSM-intent
payload byte-for-byte**. The byte-sequence lock uses a pullup +
phase-aware slave-ACK bus model so released SDA bits resolve to
1; a decoder samples SDA on each SCL rising edge between START
and STOP and assembles each transaction as a 24-bit
`{dev_addr, reg, data}` tuple, which is then compared against
`u_dut.mI2C_DATA[23:0]` snapshots taken on `mI2C_GO` rising
edges. Every transaction's dev_addr is asserted to be 8'h72
(ADV7513 write address). At the production divider the LUT
walk takes ~125 ms (controller-clock period ~100 µs × 33
phases/byte × 38 bytes), so observing it inside the 5 ms board
TB is impractical.
Deferred to Ch168+:
- Proper `set_output_delay` constraints for HDMI_TX_* once
the ADV7513 setup/hold window is locked from the bring-up
datasheet pass — replaces Ch164's `set_false_path -to`.
- Make the rendered pattern bigger than Ch123's 16×8 SPRITE
so there's something visible to admire on a real screen.
- xfer-side T4 coverage TB (open from Ch157+).
- `useg_shadow_mem` BRAM-shape forensics.
(Ch161 made the PLL hardware-real at 30 MHz. Ch162 retired
the EE-core hardware-divider critical path via
`STRIP_HW_DIVIDER`. Ch163 retired the PCRTC magnification
divider via `STRIP_PCRTC_MAG_DIV` and retuned the PLL to
50 MHz outclk_0 — the `.sof` now genuinely runs at 50 MHz
on the DE25-Nano. Ch164 added the HDMI pin shim — pixels
reach the DE25-Nano HDMI connector pins. Ch165 ported the
ADV7513 wake-up FSM from Terasic's reference design — the
chip is now configured to transmit RGB on the HDMI port,
so the .sof should drive a real monitor for the first time.)
The point of Ch149 was that the **design is now board-shaped** —
DE25-Nano signal names, Terasic-style reset-release sequencer,
and active-low LED status mapping. Ch161 made the **PLL
hardware-real**; Ch162+ makes the **timing closure
hardware-real** at 50 MHz.
## Ch149 additions
| Artifact | Purpose |
|--------------------------------------------------|----------------------------------------------------------|
| `rtl/top/de25_nano_psmct32_raster_demo_top.sv` | DE25-Nano board wrapper: CLOCK0/1/2_50 + KEY[1:0] + SW[3:0] + LED[7:0] + raw video pins. |
| `sim/tb/top/tb_de25_nano_psmct32_raster_demo_top.sv` | Smoke TB: drives CLOCK2_50 + KEY[0] release, asserts core_go pulses exactly once, all 3 status LEDs latch, VIDEO_DE rises. |
The board top instantiates `top_psmct32_raster_demo_bram` (Ch155+
BRAM-backed inner module, retargeted in **Ch159** from the Ch146
legacy `top_psmct32_raster_demo`) and adds:
- **`ninit_done` source**. Default-off `\`ifdef
USE_TERASIC_RESET_RELEASE_IP` swaps in Terasic's `reset_release`
IP (from
`DE25_Nano_ResourceCD/Demonstration/FPGA/Board_Info_RTL/reset_release/`).
When the macro is undefined (sim default), an inline 16-cycle
counter mimics the IP's "high until BRAM init completes" shape.
- **Reset synchronizer**. `KEY[0]` (active-low button) and
`ninit_done` feed an async-assert/sync-deassert 2-stage shift
register clocked by CLOCK2_50, mirroring the retroDE_nes
pattern. Output is the design's `core_rst_n`.
- **`core_go` sequencer**. After `core_rst_n` deasserts, waits 16
cycles then drives `core_go` high for one cycle. Matches the
recommended hardware path documented above (the EE core's
`go_i` is sampled level-sensitively in S_IDLE, so a single
pulse is sufficient).
- **LED polarity**. DE25-Nano LEDs are active-LOW (LED HIGH =
OFF); the three status outputs are inverted before driving
the pins. `LED[7:3]` are tied HIGH (OFF).