-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture Overview
This page fixes the high-level architectural decisions for RustySNES. Subsystem docs (and the per-chip wiki pages) take these as given and elaborate one chip each.
Per Architecture-Decision-Records (ADR 0001), the whole system derives from a single 21.477270 MHz NTSC (21.281370 MHz PAL) master clock, and the scheduler steps every chip on its own divisor in lockstep rather than running one chip ahead and catching the others up later. This is what makes mid-instruction events — a mid-scanline scroll write, an HDMA firing at an exact dot, an H/V-IRQ at a precise counter position — visible to subsequent CPU code without per-quirk patches.
The SNES makes this harder than a fixed-ratio console: the 65C816 CPU cycle is variable (6, 8, or 12 master clocks depending on which address region is accessed and the FastROM bit), and dot/scanline lengths vary (1360/1364/1368 master clocks). See Lockstep-Scheduler for the full timing model.
The crate graph is strictly one-directional — no chip crate depends on another:
rustysnes-cpu # WDC 65C816 (5A22 core) — no PPU/APU/cart dependency
rustysnes-ppu # PPU1 (5C77) + PPU2 (5C78) — VRAM/CGRAM/OAM only
rustysnes-apu # SPC700 (S-SMP) + S-DSP + 64 KiB ARAM — independent
rustysnes-cart # LoROM/HiROM/ExHiROM/ExLoROM map + coprocessor families
\ | /
rustysnes-core # ties them together: Bus, scheduler, DMA/HDMA
|
rustysnes-{frontend, netplay, cheevos, script, test-harness}
rustysnes-core is the only crate that knows all four chip crates. Downstream consumers
(frontend, tooling) depend on rustysnes-core, never on a chip crate directly. This keeps each
chip independently fuzzable and benchmarkable, and means adding a cross-chip dependency is a
structural violation, not just a style nit.
rustysnes-netplay, rustysnes-cheevos, and rustysnes-script are currently 1-line stubs —
their crates exist to fix the workspace shape ahead of time, but no implementation has started
(see Roadmap and Deferred-Features).
rustysnes-core::Bus owns everything mutable: the PPU, the APU/SMP, the cart (with its
coprocessor/board logic), WRAM, the DMA/HDMA controller, controllers, and the open-bus latch.
The CPU borrows &mut Bus during tick(). This single choice avoids the borrow-checker fight
that "CPU holds the PPU, but the PPU also needs the CPU bus" creates — the PPU and SMP each see
a narrow trait for only what they need (VRAM/CGRAM/OAM access and ARAM access, respectively).
Each SNES coprocessor (DSP-1..4, Super FX/GSU, SA-1, S-DD1, SPC7110, CX4, OBC1, ST01x/ST018,
S-RTC) is a "mapper-equivalent" with its own bus window and clock, living behind a Board /
Coprocessor trait in rustysnes-cart with default-no-op hooks — the CPU and PPU never
special-case a board. Several of the DSP-family chips share one µPD77C25/µPD96050 LLE core, so
the cart crate implements that engine once. See Cartridge-and-Coprocessors.
Per ADR 0004, the same seed + ROM + input sequence yields a bit-identical framebuffer and audio stream. The asynchronous SPC700 clock domain is tracked with an integer relative-time accumulator (no floating point, no host wall-clock); real-hardware resonator drift and real-time-clock coprocessors are deliberately frozen out of the deterministic path. Rate control and run-ahead live entirely in the frontend, never in core synthesis — see Frontend-Architecture. This is required for save-state round-trips (ADR 0006), and is the foundation the (not-yet-started) netplay rollback and TAS features will build on.
The core is single-threaded. The frontend can optionally run emulation on a dedicated thread
(Arc<Mutex<EmuCore>>), while winit's event loop and the egui shell drive the present path —
the shell never holds the emulator lock inside the egui closure; menu interactions return an
action dispatched after the egui pass completes.
When a doc and a passing test ROM disagree, the ROM wins and the doc gets updated. The oracle
is two-layered: the SingleStepTests 65816 + spc700 JSON per-opcode suites, plus the committable
gilyon / undisbeliever ROM corpora, blargg's spc_* suite for audio, and additional video test
suites. See Testing-Strategy.
A cooperative-threaded coroutine scheduler (the higan/bsnes libco model) was the proven
reference for the async SPC700 domain, but was rejected as the literal implementation —
coroutines complicate bit-deterministic save-states. RustySNES borrows the model's
relative-time-accumulator technique for the SPC700 resync only, implemented single-threaded so
the whole core stays one deterministic state machine. See Lockstep-Scheduler for the
resync mechanics.
RustySNES is a cycle-accurate Super Nintendo / Super Famicom emulator written in pure Rust. Licensed under MIT OR Apache-2.0. | GitHub Repository | Web Demo | API Docs