m68k-rs
A safe, pure Rust implementation of the Motorola 68000 family CPU emulator.
Strong for both low-level hardware-accurate emulation and high-level emulation (HLE).
Features
- Complete CPU family support: M68000, M68010, M68020, M68030, M68040, and variants (EC/LC)
- Zero dependencies: Pure Rust with no external runtime dependencies
- Memory-safe core: The interpreter — instruction semantics, decode, exceptions, MMU, FPU — is 100% safe Rust. The optional fast paths (fastmem batch execution and the trace JIT) use a small, contract-documented
unsafeperimeter, fenced by step-vs-batch equivalence tests - FPU emulation: Full 68881/68882/68040 floating-point unit support
- MMU emulation: 68030/68040 PMMU with table walks and transparent translation
- HLE-ready: Built-in trap interception for High-Level Emulation
- Extensively tested: Validated against multiple industry-standard test suites
Quick Start
Add to your Cargo.toml:
[]
= "0.2"
Basic Usage
use ;
// Implement your memory bus
High-Level Emulation (HLE)
Intercept traps for OS emulation or debugger integration with CPU/bus access:
use ;
;
// All methods in HleHandler are optional (default return is false).
// Return `true` to indicate the HLE handled the trap (suppressing the hardware exception).
// Return `false` to let the CPU take the standard hardware exception.
Choosing an Approach
| Method | Best For | Behavior on Trap |
|---|---|---|
step() |
Debuggers, Analyzers, Custom Control | Returns a StepResult variant (e.g., AlineTrap). The CPU does not take the exception automatically. If you do nothing, it acts like a NOP. You must manually call cpu.take_exception(...) if you want standard behavior. |
step_with_hle_handler() |
OS Emulation (Mac/Amiga/Atari) | Calls your HleHandler callback. If it returns true, execution continues. If it returns false, the CPU automatically triggers the standard hardware exception (stacks frame, jumps to vector). |
Use step() when you need full control over the execution loop or are building a debugger that needs to pause on every event.
Use step_with_hle_handler() when implementing a high-level emulator (like a Macintosh or Amiga emulator) where you want to patch specific system calls but otherwise let the guest OS run normally.
Supported CPU Types
| CPU | Description |
|---|---|
M68000 |
Original 68000 (24-bit address bus) |
M68010 |
68010 with virtual memory support |
M68EC020 |
68020 embedded controller (no MMU) |
M68020 |
Full 68020 with 32-bit address bus |
M68EC030 |
68030 embedded controller (no MMU) |
M68030 |
Full 68030 with on-chip MMU |
M68EC040 |
68040 embedded controller (no FPU/MMU) |
M68LC040 |
68040 lite (no FPU) |
M68040 |
Full 68040 with FPU and MMU |
SCC68070 |
Philips SCC68070 variant |
Validation & Testing
This emulator has been rigorously validated against multiple industry-standard test suites to ensure correctness:
SingleStepTests (m68000)
The SingleStepTests project provides exhaustive per-instruction test vectors derived from real hardware and cycle-accurate emulators. Our test suite runs all 101 instruction categories with thousands of test cases each, covering:
- All addressing modes and operand sizes
- Edge cases for condition codes (CCR/SR)
- BCD arithmetic (ABCD, SBCD, NBCD)
- Multiply/divide overflow handling
- Exception frame generation
- Cycle counts: 99.92% of the 261,894 fixture cases match the real-hardware clock counts exactly (261,695 of 261,894), and the suite enforces this on every run. The only exception is one 2-clock quirk in CHK's negative-bound trap path (~200 cases, documented in the source). For reference, this is tighter than Musashi, which for example charges
ADD.L Dn,Dnat 6 cycles where the M68000UM and real hardware measure 8.
Musashi Reference Implementation
We validate against Musashi, the gold-standard M68000 emulator used in MAME and countless other projects. Our integration tests:
- Execute complete Musashi test binaries
- Verify register state, memory contents, and exception handling
- Cover 68000 through 68040 instruction sets
Cross-CPU Verification
Additional test suites verify behavior across CPU generations:
- 68040 FPU tests: Floating-point transcendental functions, rounding modes
- MMU translation tests: Table walks, TTR matching, fault handling
- Privilege tests: User/supervisor mode transitions, TRAP behavior
- Exception tests: Double-fault detection, address error frames
Test Coverage
tests/
├── singlestep_m68000_v1_tests.rs # 101 instruction test files
├── musashi_tests.rs # Musashi integration suite
├── cross_cpu_tests.rs # Multi-generation verification
├── m68040_tests.rs # 68040-specific features
├── mmu_fault_tests.rs # MMU and exception handling
├── hle_interception_tests.rs # Trap handler API tests
└── fixtures/
├── m68000/ # SingleStepTests submodule
└── Musashi/ # Musashi reference submodule
Architecture
m68k/
├── core/ # CPU core, registers, execution loop
├── dasm/ # Disassembler
├── fpu/ # 68881/68882/68040 FPU emulation
└── mmu/ # 68030/68040 PMMU emulation
Key Types
| Type | Description |
|---|---|
CpuCore |
Main CPU state and execution |
CpuType |
CPU model selection enum |
AddressBus |
Trait for memory/IO implementation |
HleHandler |
Trait for HLE interception |
StepResult |
Instruction execution result |
CpuCore::is_stopped() |
STOP state check |
CpuCore::is_halted() |
Double-fault halt check |
Performance
Correctness comes first — including cycle accuracy, see above — but m68k-rs is also fast. Two execution paths are provided:
execute/step— the cycle-exact interpreter, backed by an opcode-indexed decode table and a trace JIT (Cranelift) that compiles hot backward-branch loops and runs them natively with exact cycle accounting.run_batch— an instruction-budgeted fast path for HLE embedders. Buses that expose a contiguous RAM window (AddressBus::fast_mem, implemented byLinearMemoryBus) additionally get direct-RAM memory operands and JIT-compiled loops that include memory operations, with self-modifying code detected exactly.
Measured head-to-head against Musashi (the C core used by MAME) running identical 68000 workloads over the same flat memory, with final register/memory state cross-checked between the cores (one x86-64 machine snapshot; the harness lives on a separate benchmarking branch):
| workload | Musashi | m68k-rs execute |
m68k-rs run_batch |
|---|---|---|---|
| tight ALU loop (reg mix) | 142 MIPS | 778 MIPS | 757 MIPS |
| loop ADDQ/BRA | 175 | 504 | 472 |
| loop TST/BNE | 193 | 536 | 543 |
| memcpy inner loop | 106 | 48 | 291 |
| linear ADDQ.L | 120 | 105 | 107 |
| call/return | 146 | 74 | 80 |
Hot loops — where emulated programs spend their time — run 2.7–5.5× faster than Musashi; straight-line and call-heavy code currently sits at 0.5–0.9×. At these rates the interpreter emulates a 68000 clocked at roughly 700–950 MHz, cycle-exact — a stock 7.6 MHz Amiga 500 or Sega Genesis runs at ~100× real-time per core.
License
MIT License - see LICENSE for details.
Contributing
Contributions are welcome! Please ensure:
- All tests pass:
cargo test - No clippy warnings:
cargo clippy -- -D warnings - Code is formatted:
cargo fmt
Acknowledgments
- Musashi - Reference implementation and test fixtures
- SingleStepTests - Exhaustive instruction test vectors
- The M68000 Programmer's Reference Manual