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

5.6 KiB

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:

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).