Skip to content

RustyN64 v0.2.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 07:33
19563f6

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 -- --ignored

413 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,
    so f32::NAN raises 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, fs and ft resolve differently: the low bit of fs is ignored, the low bit
    of ft is not. The manual declines to define odd registers here, so the ROM's measured table is
    the oracle (C-21).
  • A PageMask pair stores only its higher bit, and a TLB tag is masked by PageMask rather
    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 for KSEG0.

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 test does not mean a subsystem works — docs/STATUS.md is 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/SWR under Status.RE are implemented, but DMFC0/DMTC0 are deliberately
    excluded from the 64-bit reservation pending evidence.
  • Several timing constants remain unmeasuredM (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.