Files
retroDE_ps2/docs/ch304_closeout.md
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.0 KiB

Ch304 closeout — syscall 0x6B HLE; +604 retires; next blocker is DSUBU (not a wrapper syscall)

Status: Closed. Verdict from re-running qbert.elf: elf_first_unsupported_opcode (pc=0x00110A60 instr=0x0062102F) — SPECIAL funct 0x2F = DSUBU (dsubu $v0, $v1, $v0). qbert advanced 28,813 → 29,417 retires (+604).

Ch303's prediction — partially confirmed, with a twist

Ch303 predicted the next blocker would be one of the remaining Table1 wrappers (0x76, 0x44, or 0xFFFF_FFBD). Instead, clearing 0x6B let qbert run 604 more retires into code that hits a new opcode (DSUBU), NOT the next wrapper syscall.

This is consistent with Ch303's autopsy — it doesn't contradict it. The wrapper table is real and bounded; qbert just doesn't walk straight down it. After the 0x6B call returns (its caller at 0x00111B00 ignoring the return, exactly as Ch303 found), qbert's control flow proceeds into a different code path that needs DSUBU before it would reach 0x76/0x44/-67.

Implication for Ch305: the "batch the remaining wrappers" plan is deferred, not cancelled. Those wrappers (0x76, 0x44, -67) will surface only when qbert's path actually reaches them. Ch305 is now a DSUBU opcode chapter, not a wrapper batch.

The Ch303 autopsy still paid off: when 0x76/0x44/-67 do surface, we already know they're return-ignored wrappers and can clear them instantly. We just don't pre-add them speculatively.

What landed — rtl/ee/ee_core_stub.sv

11th narrow $v0=0 case in the Ch273 dispatcher:

32'h0000_006B: begin
    regfile[2]   <= 32'd0;
    gpr128[2]    <= 128'd0;
    pc           <= pc + 32'd4;
    retire_pulse <= 1'b1;
    state        <= S_IFETCH_REQ;
end

Ch303 proved the caller at 0x00111B00 ignores the return ($v0=0 is safe).

TB + observer

  • tb_ee_core_syscall_hle.sv: 0x6B subcase (now 11 known syscalls
    • unknown-halt).
  • tb_ee_core_elf_runner.sv: 0x6B observer (count + first/last args). qbert run shows:
    syscall_0x6B = seen=1 count=1 first_pc=0x00111d64
      first_args=(0x00000005, 0, 0xffffffff, 0x00137568) → $v0=0
    
    count=1, exactly the channel-5 args Ch303's autopsy predicted. Single call, return ignored, qbert moved on.

qbert progression

Chapter Blocker retire_count
Post-Ch302 (0x13) syscall $v1=0x6B at 0x00111D64 28,813
Post-Ch304 (0x6B) DSUBU (0x0062102F) at 0x00110A60 29,417 (+604)

The +604 jump is the largest syscall-HLE-driven advance since the Ch293/Ch297 inflections — clearing the channel-5 init sequence let qbert run a substantial stretch of follow-on code.

Ch305 framing — DSUBU (SPECIAL funct 0x2F)

Instr 0x0062102F decodes:

  • opcode 0x00 (SPECIAL)
  • rs = 3 ($v1), rt = 2 ($v0), rd = 2 ($v0), sa = 0
  • funct = 0x2F = DSUBU (Doubleword Subtract Unsigned)

DSUBU is the 64-bit subtract — exact sibling of Ch272's DADDU (funct 0x2D). Our 32-bit-scalar model treats it as SUBU on the low 32 bits (the same approximation DADDU uses). With the gpr128 shadow, we could optionally do a full 64-bit subtract into the low doubleword, but the established DADDU precedent is low-32 SUBU + zero-extend mirror.

Mechanical recipe (mirror Ch272 DADDU, ~4 edits):

  1. localparam FUNC_DSUBU = 6'h2F.
  2. is_dsubu decode flag.
  3. Add to is_rtype_alu (and nop_class exclusion via that).
  4. Writeback arm: is_sub || is_subu || is_dsuburs_val - rt_val (extend the existing SUBU arm).
  5. Focused TB: exact qbert encoding 0x0062102F asserted + normal subtract + wraparound.

Regression 177 → 178.

Files changed

  • rtl/ee/ee_core_stub.sv — 1 new HLE case (~20 LOC with comment).
  • sim/tb/integration/tb_ee_core_syscall_hle.sv — 0x6B subcase.
  • sim/tb/integration/tb_ee_core_elf_runner.sv — 0x6B observer + SUMMARY.

No new TB; regression unchanged at 177/177.

Pattern note

The Ch303 autopsy's value is now clear in retrospect: it told us 0x6B's return is ignored (so $v0=0 was safe to add immediately, no risk), AND it pre-identified the remaining wrappers so we won't be surprised when they appear. The fact that DSUBU came first instead just means the autopsy's "bounded set" is a future certainty, not an immediate sequence.

Regression

177/177 PASS. (Honest note: I briefly misread this regression as "interrupted" because it was still running when I spot-checked its partial log at 135 lines and saw no live make process in that instant — it then completed cleanly at 177/177. The Ch304 0x6B change is also independently validated by the focused tb_ee_core_syscall_hle and the qbert run.)

Process note for the playbook (still valid): I started Ch305's ee_core_stub.sv edits while this Ch304 make run was still in its per-TB build phase. It happened to be harmless here only because the DSUBU additions were syntactically valid SystemVerilog — a half-finished edit (e.g. mid-always_comb) would have made the regression's later iverilog builds fail spuriously. Rule: wait for the regression-complete notification before editing shared RTL for the next chapter.