Skip to content

Add R4600 CPU support to cputest bench - #109

Merged
techomancer merged 7 commits into
mainfrom
claude/r4600-cputests
Sep 17, 2026
Merged

techomancer merged 7 commits into
mainfrom
claude/r4600-cputests

Conversation

@danifunker

Copy link
Copy Markdown
Collaborator

Not tested on real hardware, but I think we might be pretty close. This is to support the SGI Indy MiSTer core.

danifunker and others added 7 commits September 16, 2026 12:40
The sgiindy_MiSTer core presents as an R4600 (PRId/FIR 0x2020, 16 KB
caches with 32-byte lines). This is the patch its gates ran with: accept
imp 0x20 as R4000-class in identify(), and expect the R4600's identity and
cache geometry. Rebased onto the hardware-validated suite; the PRId/FIR
checks keep main's revision masking.

Superseded by a proper CPU_R4600 kind in the following commits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The suite refused imp 0x20 with rc=127. The sgiindy_MiSTer FPGA core
presents as an R4600 (IRIX 5.3 hard-codes a 32-byte D-cache line for it),
so it could not be measured against the hardware-validated expectations.

- harness: CPU_R4600 is its own kind (not folded into CPU_R4400), with
  is_r4600() and has_mips4(); skip messages name any CPU combination.
- identity/cache: PRId and FIR imp 0x20, 16 KB caches with 32-byte lines,
  two ways - from the IDT79R4600 data sheet.
- mips4/*: gated on has_mips4() rather than is_r4400(), so the R4600 must
  refuse MIPS IV like the R4400 (RI outside COP1, FPE inside it).
- fpu_denorm: asserted for the R4600 with the R4400 - the R5000's reported
  observations are byte-identical to the R4400's assertions in every case.
- mem/lwr_all_offsets: the two measured parts differ on a partial LWR, so
  an R4600 passes with either answer and the log names which.

No change for the R4400 or the R5000: every branch they take is the same.
docs/r4600.md tabulates where each R4600 answer comes from and what the
first real R4600 run would confirm.

On the sgiindy_MiSTer core (build 30b): cpu=R4600, 2096 passed / 54 failed,
12 failing tests - all in the classes the Indy runs corrected (NaN
polarity, COP1 unimplemented -> FPE, COP2 with CU2 set, cvt from long).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rocessor Unusable

Every test so far ran in Kernel mode, so nothing could see whether a CPU
enforces the CP0 privilege rule: CP0 instructions (and CACHE) are usable in
Kernel mode or with Status.CU0 set, otherwise Coprocessor Unusable with
Cause.CE = 0 (R4000 manual, chapter 5).

excep/cp0_unusable_user maps a scratch page at kuseg 0x00400000, ERETs
into it with KSU = User (then Supervisor), runs mfc0 / tlbp there, and
comes home through a syscall. A test-local exception hook (um_handler)
steps over faults in the mode they came from and keeps the first Cause; a
runaway guard returns after eight faults so a broken CPU cannot hang the
suite. A control run (syscall only) proves User mode was really entered.
excep/cp0_usable_cu0 checks that CU0 grants access.

Derived from the manual, not yet run on silicon. On the sgiindy_MiSTer
core before its fix: the control passed and all three accesses executed
silently (6 failed checks) - which is the finding. IRIS added the same
gate in src (rules/testing/cop0-requires-kernel-or-cu0.md).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sters

Disabled on 2026-09-12 because it changed KSEG0's cacheability while its
own spilled local was a dirty D-cache line: an uncached reload then read
pre-spill RAM, the restore wrote a wrong K0 and the CPU ran into unmapped
KUSEG. That hazard is real on hardware too (the Indy passed by luck).

Between the first MTC0 and the restore there is now no load, store or
call: orig, the counter and two failure bitmasks (one bit per K0 value:
the field did not stick / another Config bit moved) live in registers, and
results reach memory only after Config is back. Instruction fetch is the
one thing the attribute still affects, and the text was written back to
memory when the suite relocated itself.

On the sgiindy_MiSTer core (R4600 presentation, K0 = 2 uncached): PASS,
2167/0 over 242 tests. Not yet run on silicon.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A pipelined CPU finds a load's value a stage later than any other result,
so the instruction immediately behind a load is where forwarding breaks:
used at once, used one instruction later, as the next load's base (a
pointer chase), in a branch, stored and reloaded at once, overwritten by a
second load, narrow loads extended, and the load in a branch delay slot.
Each sequence runs with its lines out of the D-cache and in it, since those
are different paths through a core.

Written as the guard for making the sgiindy_MiSTer core stall only on a
real load-use hazard instead of on every load. Plain ISA semantics, true on
every part. Passes on that core as it is today (2187/0, 243 tests).

One trap recorded in the test: an asm block's "=r" output may be given the
same hard register as an input, so a sequence that writes its output before
reading an input compares against garbage - the first version of the branch
case did exactly that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… on silicon

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mem/load_then_trap: syscall, break, teq, add overflow and a reserved
instruction immediately after a load, once with the load's D-cache line
invalidated (the load is still filling when the trap executes) and once
cached. Exactly one exception, EPC on the trapping instruction, and the
loaded value in place when the handler resumes.

mem/load_then_more: an mfc0, a ddivu, a jr with its delay slot, a second
load and a store two instructions behind, and a store then load then
non-memory instruction (the D-cache READWAIT) - each after a load.

fpu/trap_behind_a_load: div.s by zero with Z enabled right behind an
integer load from a line the operand loads do not fill.

Operands are built inside the asm blocks: a loop-invariant input can share
a hard register with an "=r" output and arrive clobbered on pass 1 (the
first version of load_then_more's divide failed that way on every core).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@techomancer
techomancer merged commit 5133926 into main Sep 17, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants