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,188 @@
|
||||
# Ch293 closeout — syscall 0x7A HLE; **the opcode-trap era ENDS**
|
||||
|
||||
**Status:** Closed. **Verdict from re-running qbert.elf:**
|
||||
`elf_timeout_with_hot_pc (watchdog after 50000000 ns, 1661413
|
||||
retires, hot_pc=0x0011242C count=29/256)` — for the **first time**
|
||||
qbert is not hitting an opcode trap, an unmapped MMIO, or an
|
||||
unhandled syscall. It's running real code in a steady-state loop.
|
||||
|
||||
## The 60× retire jump
|
||||
|
||||
| Chapter | retire_count | delta | verdict |
|
||||
|---------|--------------|-------|---------|
|
||||
| Post-Ch292 (SYNC) | 27,968 | — | unhandled_syscall (0x7A) |
|
||||
| **Post-Ch293 (syscall 0x7A)** | **1,661,413** | **+1,633,445** | **timeout_with_hot_pc** |
|
||||
|
||||
That's a **60× advance** in a single chapter. The 27k retires it
|
||||
took us 23 chapters (Ch271..Ch292) to accumulate is now barely
|
||||
1.6% of where we are.
|
||||
|
||||
## What changed
|
||||
|
||||
The mechanical Ch289-pattern extension landed exactly:
|
||||
|
||||
### Dispatcher case — `rtl/ee/ee_core_stub.sv`
|
||||
|
||||
```sv
|
||||
32'h0000_007A: begin
|
||||
regfile[2] <= 32'd0;
|
||||
gpr128[2] <= 128'd0;
|
||||
pc <= pc + 32'd4;
|
||||
retire_pulse <= 1'b1;
|
||||
state <= S_IFETCH_REQ;
|
||||
end
|
||||
```
|
||||
|
||||
The 8th case in the Ch273 HLE switch. ~10 LOC.
|
||||
|
||||
### TB extension — `tb_ee_core_syscall_hle.sv`
|
||||
|
||||
Same mechanical pattern (slots / latch / assertion / display). The
|
||||
TB now covers eight known syscalls (3C / 3D / 40 / 64 / 78 / 12 /
|
||||
16 / 7A) plus the unknown-halt path.
|
||||
|
||||
### Runner observer — `tb_ee_core_elf_runner.sv`
|
||||
|
||||
Fourth observer in the library (after 0x78, 0x12, 0x16). The
|
||||
SUMMARY block now self-documents all four tracked syscalls.
|
||||
|
||||
## What the runner showed
|
||||
|
||||
```
|
||||
syscall_0x78 = seen=1 count=1 first_pc=0x00112aa4
|
||||
syscall_0x12 = seen=1 count=1 first_pc=0x00112a54
|
||||
syscall_0x16 = seen=1 count=1 first_pc=0x00112a74
|
||||
syscall_0x7A = seen=1 count=181494 first_pc=0x00110994 ← !!!
|
||||
```
|
||||
|
||||
**count=181,494** for syscall 0x7A. qbert is in a loop calling
|
||||
SyncDCache **on the order of every 9 retires**. At halt-time the
|
||||
observer's first-occurrence `$a0=0x80000000` but the live halt-
|
||||
time `$a0=0x00000004` — qbert is iterating sync ranges (likely
|
||||
"sync this address" with the address changing each iteration).
|
||||
|
||||
`hot_pc = 0x0011242C` (count 29/256) is the loop center. The
|
||||
181k SyncDCache calls suggest the loop body is something like:
|
||||
```
|
||||
loop:
|
||||
<modify data at addr>
|
||||
syscall SyncDCache(addr)
|
||||
advance addr
|
||||
branch back to loop
|
||||
```
|
||||
|
||||
Or:
|
||||
```
|
||||
loop:
|
||||
<poll some completion bit>
|
||||
syscall SyncDCache(stale_cache_line)
|
||||
branch back to loop if not done
|
||||
```
|
||||
|
||||
Without examining the disassembly at 0x0011242c we can't tell
|
||||
which. But the SUMMARY's "qbert reached entry and ran real code"
|
||||
language is now literally correct — this isn't the runner's
|
||||
boilerplate "expected verdict for synthetic" case; this is real
|
||||
qbert.elf execution.
|
||||
|
||||
## What this means for the project
|
||||
|
||||
**The opcode-trap whack-a-mole phase is over.** Ch271..Ch292
|
||||
exhaustively added every R5900 opcode and MMIO surface qbert
|
||||
needs to reach init quiescence. Ch293's tiny addition (syscall
|
||||
0x7A HLE) was the last brick.
|
||||
|
||||
The next blocker is not "implement opcode X" or "stub MMIO Y" —
|
||||
it's "qbert is waiting on something we haven't modelled." The
|
||||
possibilities, ranked by likelihood:
|
||||
|
||||
1. **DMAC interrupt delivery.** Ch290/291 registered + enabled a
|
||||
handler on DMAC channel 5 (SIF0). The handler at 0x00112AB0
|
||||
was never called because the model has no interrupt-delivery
|
||||
path from DMAC completion → COP0 Cause/Status → handler
|
||||
invocation. qbert may be polling for handler-side state that
|
||||
never updates.
|
||||
2. **VBLANK / VSYNC.** PS2 game loops typically wait for VBLANK
|
||||
to advance frame state. The model has no VBLANK generator yet
|
||||
(GS PCRTC stub doesn't emit the VSYNC interrupt).
|
||||
3. **A specific kernel-state poll.** qbert might be reading a
|
||||
global flag (e.g., a thread-control-block field) that some
|
||||
missing kernel service should update.
|
||||
4. **A combination** — most PS2 game main loops wait on multiple
|
||||
signals (VBLANK + DMAC-complete + thread-message).
|
||||
|
||||
## Ch294 framing — investigation, not mechanical extension
|
||||
|
||||
The opcode-by-opcode + syscall-by-syscall mechanical recipe that
|
||||
served Ch271..Ch293 is **no longer the right approach**. The next
|
||||
chapter needs to:
|
||||
|
||||
1. **Disassemble** the loop body at `hot_pc = 0x0011242C` (and
|
||||
immediately adjacent PCs in the ring) to understand what
|
||||
qbert is checking each iteration.
|
||||
2. **Trace** what memory addresses / MMIO addresses qbert reads
|
||||
in the loop. The runner already emits a per-retire trace; the
|
||||
trace file at `sim/traces/ee_core_elf_runner_core.trace`
|
||||
should show every read with EA + region.
|
||||
3. **Identify** the specific service that's missing — most
|
||||
likely an interrupt-delivery path or a VBLANK generator.
|
||||
|
||||
Concrete first investigation step: read the qbert.elf
|
||||
disassembly around 0x00112400..0x00112460 (~16 instructions
|
||||
covering the hot_pc and its likely branch targets). This will
|
||||
identify the exact wait condition.
|
||||
|
||||
Codex should frame Ch294 as an **investigation chapter** — like
|
||||
the Ch263..Ch269 BIOS-treadmill autopsies — not as another
|
||||
mechanical extension. The right output is a "what is qbert
|
||||
waiting on" answer + a concrete proposal for the minimal model
|
||||
change that breaks the wait.
|
||||
|
||||
## Cumulative HLE coverage at the inflection point
|
||||
|
||||
| $v1 | Probable name | Return | Chapter | qbert call count |
|
||||
|-----|---------------|--------|---------|------------------|
|
||||
| 0x3C | EndOfHeap | SYSCALL_HEAP_END | Ch273 | not observed |
|
||||
| 0x3D | InitMainThread | 0 | Ch273 | not observed |
|
||||
| 0x40 | SetV*Handler? | 0 | Ch285 | not observed |
|
||||
| 0x64 | FlushCache | 0 | Ch273 | not observed |
|
||||
| 0x78 | (kernel setup) | 0 | Ch289 | 1 |
|
||||
| 0x12 | AddDmacHandler? | 0 | Ch290 | 1 |
|
||||
| 0x16 | EnableDmacHandler? | 0 | Ch291 | 1 |
|
||||
| 0x7A | SyncDCache? | 0 | Ch293 | **181,494** |
|
||||
|
||||
The count=1 entries are init-time calls. count=181,494 is the
|
||||
"main loop is grinding" signal — and it's only that one syscall.
|
||||
Whatever the loop is, SyncDCache is its central operation.
|
||||
|
||||
## Files changed
|
||||
|
||||
- `rtl/ee/ee_core_stub.sv` — one new HLE case (~10 LOC).
|
||||
- `sim/tb/integration/tb_ee_core_syscall_hle.sv` — extended with
|
||||
syscall 0x7A.
|
||||
- `sim/tb/integration/tb_ee_core_elf_runner.sv` — syscall_0x7A
|
||||
observer + SUMMARY line.
|
||||
|
||||
No new TB, no new Makefile target; regression count unchanged at
|
||||
**176/176**.
|
||||
|
||||
## Pattern review (23 chapters)
|
||||
|
||||
| Phase | Chapters | Description |
|
||||
|-------|----------|-------------|
|
||||
| Opcode-blocker era | Ch271..Ch286 | New R5900 opcodes, one per chapter |
|
||||
| MMIO era | Ch287..Ch288 | DMAC ctrl + per-channel surfaces |
|
||||
| Syscall HLE era | Ch273, 285, 289, 290, 291, 293 | Six narrow $v0=0 extensions |
|
||||
| Narrow-NOP era | Ch286 (EI), Ch292 (SYNC) | Side-effect-free accepts |
|
||||
| **Investigation era** | **Ch294+** | **Find what qbert is waiting on** |
|
||||
|
||||
The "opcode + MMIO + syscall HLE" toolkit accumulated over the
|
||||
previous 23 chapters has now exhaustively covered everything
|
||||
qbert *demands* during its init phase. The remaining work is
|
||||
*model fidelity*: making the system actually deliver the
|
||||
asynchronous events (interrupts, VBLANK, scheduled threads) that
|
||||
real PS2 hardware provides.
|
||||
|
||||
## Regression
|
||||
|
||||
**176/176 PASS** (unchanged from Ch292; no new TB).
|
||||
Reference in New Issue
Block a user