Files
retroDE_ps2/docs/ch285_closeout.md
T
thejayman77 ec82764bef 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>
2026-06-29 20:10:50 -04:00

136 lines
5.6 KiB
Markdown

# Ch285 closeout — syscall 0x40 HLE; next blocker is R5900 EI (COP0 funct 0x38)
**Status:** Closed. **Verdict from re-running qbert.elf:**
`elf_first_unsupported_opcode (pc=0x001000FC instr=0x42000038)`
COP0/CO funct 0x38 = R5900 `EI` (Enable Interrupts), an EE-specific
extension to the MIPS COP0 CO sub-table. qbert advanced 27,091 →
**27,239 retires (+148)** — the biggest single-chapter jump since
Ch283. The PC dropped from 0x001113xx (deep into game code) back
down to 0x001000FC (early init), which means the syscall 0x40
return successfully unstuck qbert's setup phase and it took the next
hot block of work.
## What landed
A narrow HLE case for syscall `$v1 == 0x40` in `ee_core_stub.sv`'s
existing Ch273 dispatcher. Per Codex framing ("accept the
registration, return success, continue; don't over-trust the SDK
name"), the case returns `$v0 = 0` and resumes at `PC + 4`. Two
lines of new RTL surrounded by a comment block:
```sv
32'h0000_0040: begin
regfile[2] <= 32'd0;
gpr128[2] <= 128'd0;
pc <= pc + 32'd4;
retire_pulse <= 1'b1;
state <= S_IFETCH_REQ;
end
```
The standard PS2 kernel syscall table lists names in this slot like
`SetVCommonHandler` / `SetVTLBRefillHandler`. The observed call shape
(`$a0=0x001DFFC0` heap-ish, `$a1=0x0011C326` code-ptr-ish) is
consistent with a kernel-handler-install pattern. Real PS2 ROM
implementations of these calls return the previous handler pointer;
our stub returns 0 since (a) we don't store handler state, and (b)
qbert clearly doesn't use the return value as a function pointer
(it advanced 148 instructions past the call without re-trapping in
a wild jump).
If a future ELF needs the previous-handler return, this case can be
widened with $a0-keyed handler-pointer storage. Not warranted yet.
## TB — `tb_ee_core_syscall_hle.sv` extended
Existing TB extended with a 4th known case slot (`S_ORI_V1_40` /
`S_SYS_40` / `S_BNE_40` / `S_DS_40`) plus matching latch
(`v0_after_40` / `seen_40_return`) and the corresponding assert.
The display summary now reports `$v0_after_40` next to the other
three. Pattern identical to the existing 3C/3D/64 cases. The
unknown-syscall halt still terminates the test.
Result: `retired=21 halt=1 trap=0 errors=0 PASS`, with
`$v0_after_3C=0x001e0000 $v0_after_3D=0x00000000 $v0_after_64=0x00000000 $v0_after_40=0x00000000 $v1_at_halt=0x00007777`.
## qbert progression
| Chapter | Blocker | retire_count |
|---|---|---|
| Post-Ch283 (PCPYUD + gpr128) | LD at 0x00113378 | 27,067 |
| Post-Ch284 (LD) | SYSCALL $v1=0x40 at 0x00111D24 | 27,091 |
| **Post-Ch285 (syscall 0x40)** | **`0x42000038` (COP0 EI) at 0x001000FC** | **27,239** |
The PC walking *backward* from 0x001113xx to 0x001000FC is a
positive signal — qbert took the syscall return and looped or
called back into earlier code, hit the next blocker there. 148
retires is the largest single-chapter jump on the qbert track
since Ch283's architectural pivot.
## Ch286 framing
Instr `0x42000038`:
- bits 31..26: `010000` = opcode 0x10 (COP0)
- bits 25..21: `10000` = rs/sub = 0x10 (COP0_CO — "coprocessor
command")
- bits 5..0: `111000` = funct 0x38
R5900 `EI` (Enable Interrupts). EE-specific extension to the MIPS
COP0 CO sub-table (alongside `DI` at funct 0x39, plus the standard
RFE/ERET/TLBP/TLBR/TLBWI/TLBWR/WAIT). Minimal implementation: NOP-
class it (no model state mutated), PC += 4. We could optionally set
`status[16]` (EIE bit) if a future test depends on the COP0 Status
view, but qbert almost certainly doesn't poll Status after EI —
it's calling EI as standard init noise.
Concrete Ch286 scope:
1. `localparam FUNC_EI = 6'h38; localparam FUNC_DI = 6'h39;`
2. `is_ei = is_cop0 && (rs_idx == COP0_RS_CO) && (func == FUNC_EI)`
3. (`is_di` analogous, in case the next chapter trips DI)
4. Add `!is_ei` (and `!is_di`) to the `(is_cop0 && !is_mfc0 && !is_mtc0 && !is_rfe)` is_nop_class exclusion.
5. Default execute path retires (PC += 4 via normal `retire_advance`).
6. Focused TB: encode EI, execute, verify no trap + PC advances + retire fires.
5-ish RTL edits. Pure NOP-class extension; no register effects in
the model.
## Files changed
- `rtl/ee/ee_core_stub.sv` — 1 new case in the syscall HLE switch
(~10 LOC with comment).
- `sim/tb/integration/tb_ee_core_syscall_hle.sv` — 4 new BIOS
slots, 1 new latch group, 1 new assertion, 1 expanded display
line.
No new TB, no new Makefile target; regression count unchanged at
**172/172**.
## Pattern review (15 chapters)
| Ch | Blocker | Edits | Pattern |
|-----|--------------|-------|---------|
| 271 | SQ | 5 | NEW 4-beat write |
| 272 | DADDU | 4 | NEW ALU-low-32 |
| 273 | SYSCALL HLE | 2 | NEW gated dispatcher |
| 274 | BEQL | 6 | NEW branch+squash |
| 275 | SD | 7 | REUSE SQ counter |
| 276 | DSLL | 4 | REUSE DADDU |
| 277 | BNEL | 6 | REUSE BEQL squash |
| 278 | PCPYLD | 4 | NEW MMI narrow-decode |
| 279 | LQ | 5 | REUSE LW path |
| 280 | PSUBB | 5 | REUSE MMI narrow (byte-SIMD new) |
| 281 | PNOR | 5 | REUSE MMI narrow + NOR arm |
| 282 | PAND | 5 | REUSE MMI narrow + AND arm |
| 283 | PCPYUD + gpr128 | architectural | NEW 128-bit shadow |
| 284 | LD | 5 | REUSE Ch283 multi-beat path |
| **285** | **syscall 0x40** | **~1** | **REUSE Ch273 dispatcher** |
Highest-reuse chapter on record. The Ch273 dispatcher was designed
to be extended — each new $v1 is one switch case. The +148 retires
shows the cost-to-progress ratio remains favorable.
## Regression
**172/172 PASS** (unchanged from Ch284; no new TB added in this
chapter, the existing tb_ee_core_syscall_hle was extended in place).