RustyN64 v0.2.0
RustyN64 v0.2.0 "Interpreter"
The VR4300 works, and two independent oracles say so. Phase 1 is complete: the CPU executes
MIPS III — including the TLB, COP0, the FPU, the caches and the documented errata — and both of the
phase's exit criteria are met.
Unlike v0.1.0, which was tagged as a deliberately honest skeleton, this tag describes a subsystem
that genuinely runs. Every accuracy claim below is an oracle result with a committed runner, not
a self-assessment, and every one is reproducible from a clean checkout.
Accuracy statement
| Criterion | Result |
|---|---|
| n64-systemtest, CPU/COP0/TLB/COP1 categories | Failed: 0 — 0 failing of 917 tests started |
| CPU golden-log 0-diff vs ares | held — retired-instruction stream identical from the ELF entry |
cargo test -p rustyn64-test-harness --release --test systemtest -- --ignored
cargo test -p rustyn64-test-harness --release --test golden_log -- --ignored413 assertions still fail suite-wide. Every one is RSP/RCP, which is v0.3.0's criterion
(to-dos/VERSION-PLAN.md §v0.3.0), not this one. That distinction is deliberate and is enforced by
the runner, which matches on the categories to exclude so a new CPU-side category added upstream
lands inside the gate rather than being silently skipped.
CPU
Timebase and microarchitecture. One canonical 187.5 MHz master clock is the only incremented
counter; every other cycle position is a derived accessor, pinned by a residue-invariant test that
fails if any position becomes independent (ADR 0006, superseding ADR 0001). The CPU is a
cycle-accurate five-stage pipeline (IC/RF/EX/DC/WB) of four inter-stage latches advanced in
reverse stage order, which is what makes the latching implicit (ADR 0007). in_delay_slot rides
in the latch rather than in global state.
Instruction set. MIPS III including the 64-bit D* forms, LL/SC/LLD/SCD, the unaligned
LWL/LWR/LDL/LDR family, the trap family, and the branch-likely forms with delay-slot
nullification.
COP0, TLB and exceptions. The register file with correct 64-bit widths, Count/Compare timer
interrupts, the Status/Cause exception path, 32 dual TLB entries with variable page sizes and
ASID matching, and the refill/invalid/modified vectors. The micro-ITLB sits in front of the JTLB.
The primary caches. A 16 KiB instruction cache (32-byte lines) and an 8 KiB write-back data
cache (16-byte lines), both direct-mapped. Instruction fetch and every cached load and store run
through them; a cached store is a write-allocate that leaves the line dirty until an eviction or a
CACHE operation forces it out. All thirteen CACHE operations act.
COP1 runs on a soft-float core, not on Rust's f32/f64 operators. That is not gratuitous:
the native operators discard the exact pre-rounding result, so inexact/underflow cannot be
reported and FCSR.RM cannot be honoured. The module is verified bit-for-bit against those same
operators in round-to-nearest over ~100k cases — they are the independent oracle, which is why it
implements IEEE behaviour and leaves the VR4300's refusal to produce subnormals as a separate
layer.
Privilege and addressing. The segment map is a function of (address, mode, width), never the
address alone. KSEG0 does not exist in User mode, and it is the address-space check — not the TLB
— that stops a user program reaching it: an out-of-range address raises AdEL before the TLB is
consulted. XKPHYS, Status.RE reverse-endian, and the 64-bit-operation reservation in 32-bit
User/Supervisor mode are all implemented.
Hardware behaviours worth naming
Several of these look like bugs on every reading and are not. Each is recorded in
docs/accuracy-ledger.md with its evidence.
- NaN classification is INVERTED from IEEE-754:2008 — significand MSB set means signalling,
sof32::NANraises Invalid. Legacy MIPS convention (C-12). - The VR4300 has no subnormal datapath. A subnormal operand or result raises the unmaskable
unimplemented-operation cause, not a number — except that compares are exempt (C-13). - Under
FR = 0,fsandftresolve differently: the low bit offsis ignored, the low bit
offtis not. The manual declines to define odd registers here, so the ROM's measured table is
the oracle (C-21). - A
PageMaskpair stores only its higher bit, and a TLB tag is masked byPageMaskrather
than divided by the page size — a distinction invisible for all six legal page sizes. - Tininess is detected before rounding, so a directed rounding mode that lifts a tiny result back
into the normal range still raises underflow. - An address in 32-bit mode must be the sign extension of its low word.
0x0000_0000_8000_1000
is an address error, not shorthand forKSEG0.
Verification infrastructure
The n64-systemtest runner is committed (crates/rustyn64-test-harness/tests/systemtest.rs).
Previously each session rebuilt a throwaway, which made the published number unverifiable from the
repository. It witnesses execution before trusting a zero — an empty run produces zero failures just
as convincingly as a passing one.
The golden-log differ is wired to a committed reference trace (tests/golden/n64-systemtest.log)
captured from ares, carrying a provenance header naming the reference build, ROM hash and start PC.
The claim is stated narrowly and deliberately: given identical initial state, RustyN64 retires the
same instructions in the same order as the reference. This is the tandem-verification shape used
by RISC-V co-simulation harnesses — align two models at a boundary and treat only deltas as the
claim. It says nothing about boot or timing (C-26).
EMUX (xdetect/xlog/xioctl) is implemented behind Bus::emux_enabled, off by default
because hardware has none. The test harness opts in and gets a console needing no PI/SI/ISViewer
emulation (~9× faster) plus xioctl(EXIT) as a definite end-of-run signal (C-27).
Known limitations
- The RSP, RDP and AI are LLE-shaped stubs. No chip other than the CPU executes anything. A green
cargo testdoes not mean a subsystem works —docs/STATUS.mdis authoritative. - 413 n64-systemtest assertions fail, all RSP/RCP. Phase 2's criterion.
- Cache ↔ DMA coherency is not modelled. Cart/RSP DMA writes land in RDRAM behind the cache.
- The caches are indexed by physical address where hardware indexes virtually — a divergence in
both directions, bounded to the tested KSEG0 scope (D-6). LWL/LWR/SWL/SWRunderStatus.REare implemented, butDMFC0/DMTC0are deliberately
excluded from the 64-bit reservation pending evidence.- Several timing constants remain unmeasured —
M(memory access time), the exception-epilogue
cost, CP0I, RDRAM bank-state costs. They are listed in the accuracy ledger as unmeasured, never
fitted; no ROM-tuned constant has been admitted. - No save states, no netplay, no RetroAchievements. Later phases.
Compatibility
No save-state or public-API format exists yet, so nothing in this release can break one. ADR 0005's
sub-cycle bus-timing refactor remains the only anticipated MAJOR candidate, and only if Phase 7's
accuracy triage concludes it is warranted.