Cycle-accurate 6502 emulator as coroutine in Rust
5 months ago
- #rust
- #coroutines
- #emulation
- A simple CPU emulator typically uses a loop with a switch statement to interpret opcodes, abstracting away timing details.
- Timing abstraction can cause synchronization issues in systems with timing-sensitive components, like the NES CPU and PPU.
- The solution is to implement the CPU as a state machine that can be stepped one cycle at a time, ensuring accurate synchronization.
- This project demonstrates using Rust coroutines to define CPU emulation semantics, compiling it into a state machine for lockstep execution with the PPU.
- The CPU state includes registers and an I/O data bus, with coroutines yielding I/O events for reads and writes.
- Macros like `fetch!` and `write!` handle I/O events, simplifying the implementation of instruction semantics.
- Example code shows running the CPU and PPU together in lockstep, with the CPU stepping every third cycle to match the PPU's clock rate.
- Performance is sufficient, with the emulator running at ~470 MHz on modern hardware, far exceeding the NES's 1.79 MHz requirement.
- The project includes extensive testing with Harte and Klaus test suites to ensure correctness.
- Built with Rust nightly, the project may be unstable due to compiler changes, but it works with version 1.93.0-nightly.