# m68k-rs
A safe, pure Rust implementation of the Motorola 68000 family CPU emulator.
One core for transaction-accurate hardware emulation and high-throughput
high-level emulation (HLE).
[](https://github.com/benletchford/m68k-rs/actions/workflows/rust.yml)
[](https://crates.io/crates/m68k)
[](LICENSE)
## Features
- **Complete CPU family support**: M68000 through M68060, including EC/LC variants and the SCC68070
- **Two explicit execution contracts**: transaction-accurate cycle scheduling for hardware emulators, and an instruction-budgeted fast path for HLE
- **Bus-visible accuracy**: 68000/68010 two-word prefetch, model-specific access ordering, and internal clock synchronization through `AddressBus::sync`
- **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 `unsafe` perimeter, fenced by step-vs-batch equivalence tests
- **FPU emulation**: Software 80-bit extended precision, packed decimal, and model-specific 68881/68882/68040/68060 behavior
- **MMU emulation**: 68030/68040/68060 translation, ATCs, transparent translation, `PTEST`, fault frames, and writeback
- **HLE-ready**: Built-in trap interception for High-Level Emulation
- **Save-state ready**: Optional `serde` support serializes architectural state while rebuilding runtime caches on load
- **Extensively tested**: Validated against multiple industry-standard test suites
## Quick Start
Add to your `Cargo.toml`:
```toml
[dependencies]
m68k = "0.3"
```
### Basic Usage
```rust
use m68k::{CpuCore, CpuType, AddressBus, StepResult};
// Implement your memory bus
struct MyBus { memory: Vec<u8> }
impl AddressBus for MyBus {
fn read_byte(&mut self, addr: u32) -> u8 {
self.memory.get(addr as usize).copied().unwrap_or(0)
}
fn write_byte(&mut self, addr: u32, val: u8) {
if let Some(m) = self.memory.get_mut(addr as usize) { *m = val; }
}
fn read_word(&mut self, addr: u32) -> u16 {
u16::from_be_bytes([self.read_byte(addr), self.read_byte(addr + 1)])
}
fn write_word(&mut self, addr: u32, val: u16) {
let bytes = val.to_be_bytes();
self.write_byte(addr, bytes[0]);
self.write_byte(addr + 1, bytes[1]);
}
fn read_long(&mut self, addr: u32) -> u32 {
((self.read_word(addr) as u32) << 16) | self.read_word(addr + 2) as u32
}
fn write_long(&mut self, addr: u32, val: u32) {
self.write_word(addr, (val >> 16) as u16);
self.write_word(addr + 2, val as u16);
}
}
fn main() {
let mut cpu = CpuCore::new();
cpu.set_cpu_type(CpuType::M68000);
let mut bus = MyBus { memory: vec![0; 0x10000] };
// Set up vectors: SSP at 0x1000, PC at 0x400
bus.write_long(0, 0x1000);
bus.write_long(4, 0x400);
// Write a NOP instruction at 0x400
bus.write_word(0x400, 0x4E71);
cpu.reset(&mut bus);
loop {
match cpu.step(&mut bus) {
StepResult::Ok { cycles } => println!("Executed: {} cycles", cycles),
StepResult::Stopped => break,
StepResult::AlineTrap { opcode } => println!("A-line trap: {:04X}", opcode),
StepResult::FlineTrap { opcode } => println!("F-line trap: {:04X}", opcode),
StepResult::TrapInstruction { trap_num } => println!("TRAP #{}", trap_num),
StepResult::Breakpoint { bp_num } => println!("BKPT #{}", bp_num),
StepResult::IllegalInstruction { opcode } => println!("Illegal instruction: {:04X}", opcode),
}
}
}
```
### High-Level Emulation (HLE)
Intercept traps for OS emulation or debugger integration with CPU/bus access:
```rust
use m68k::{AddressBus, CpuCore, HleHandler};
struct MacToolbox;
// 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.
impl HleHandler for MacToolbox {
// Optional: Intercept A-line traps (0xAxxx)
fn handle_aline(
&mut self,
cpu: &mut CpuCore,
bus: &mut dyn AddressBus,
opcode: u16,
) -> bool {
println!("A-line trap: {:04X} at PC=0x{:08X}", opcode, cpu.pc);
// ... implement HLE logic ...
true // Handled: do NOT take the standard Line-A exception
}
// Optional: Intercept TRAP #n instructions
fn handle_trap(&mut self, _cpu: &mut CpuCore, _bus: &mut dyn AddressBus, trap: u8) -> bool {
// Example: Only intercept TRAP #0, let #1-15 go to real hardware vectors
if trap == 0 {
println!("OS Call (TRAP #0)");
true // Handled
} else {
false // Not handled: CPU will take exception vector 32+n
}
}
// Optional: Intercept F-line traps (coprocessor instructions)
fn handle_fline(&mut self, _cpu: &mut CpuCore, _bus: &mut dyn AddressBus, opcode: u16) -> bool {
println!("Generic Coprocessor instruction: {:04X}", opcode);
true // Handled
}
// Optional: Intercept BKPT #n instructions
fn handle_breakpoint(&mut self, _cpu: &mut CpuCore, _bus: &mut dyn AddressBus, bp: u8) -> bool {
println!("Breakpoint #{}", bp);
true // Handled
}
// Optional: Intercept ILLEGAL instructions (0x4AFC)
fn handle_illegal(&mut self, _cpu: &mut CpuCore, _bus: &mut dyn AddressBus, opcode: u16) -> bool {
println!("Illegal instruction: {:04X}", opcode);
false // Not handled: CPU will take illegal instruction exception
}
}
fn emulate(cpu: &mut CpuCore, bus: &mut impl AddressBus) {
let mut hle = MacToolbox;
let result = cpu.step_with_hle_handler(bus, &mut hle);
}
```
### Choosing an Approach
| **`step()`** | One instruction | Transaction-accurate `AddressBus` accesses and cycles | Surfaces A-line, F-line, `TRAP`, `BKPT`, and illegal instructions without taking their exception |
| **`step_with_hle_handler()`** | One instruction | Same precise path as `step()` | Offers traps to `HleHandler`; unhandled traps take the hardware exception |
| **`execute()`** | CPU cycles | Precise path; whole instructions may overshoot the requested cycles | Takes traps as hardware exceptions and returns consumed cycles |
| **`run_for_cycles()`** | CPU cycles | Precise path with actual cycle and instruction totals | Surfaces traps like `step()` and reports STOP separately |
| **`run_batch()`** | Instructions | Throughput path using decoded-op caching, optional direct RAM, and native hot-loop traces | Surfaces traps, STOP, watched PCs, or budget exhaustion |
Use **`step()`** for debugger-style control. Use **`run_for_cycles()`** when a
machine scheduler needs to advance the CPU by a clock budget without losing
bus ordering or trap state:
```rust
use m68k::CycleBatchExit;
let result = cpu.run_for_cycles(&mut bus, 512);
match result.exit {
CycleBatchExit::BudgetExhausted => {
// result.cycles may be greater than 512: instructions are never split.
}
CycleBatchExit::Stopped => {
// Wait for a serviceable interrupt.
}
event => {
// Handle a surfaced trap. The trapping instruction is not included
// in result.instructions and no exception-entry cycles were charged.
println!("{event:?}");
}
}
```
Use **`step_with_hle_handler()`** when patching selected guest OS calls while
allowing every unhandled trap to follow hardware behavior. Use
**`run_batch()`** for HLE workloads where host-call latency and instruction
throughput matter more than observing the physical prefetch bus.
## Supported CPU Types
| `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 |
| `M68060` | Superscalar 68060 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](https://github.com/SingleStepTests/m68000) project
provides exhaustive per-instruction fixtures from MAME's microcoded 68000 core.
The suite covers every supplied instruction file and 261,894 cases, including:
- 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 and transaction auditing**: all 261,894 cases match their fixture cycle and bus-access totals, with separate ignored audit tools for access sequence and per-access timing analysis
### Musashi Reference Implementation
We also run binaries from [Musashi](https://github.com/kstenerud/Musashi), a
widely deployed M68000 emulator. The integration tests:
- Execute complete Musashi test binaries
- Verify register state, memory contents, and exception handling
- Cover 68000 through 68040 instruction sets
- Explicitly exclude legacy cases whose undefined BCD flags or illegal
68000 encodings conflict with the hardware-oriented SingleStepTests model
### Cross-CPU Verification
Additional test suites verify behavior across CPU generations:
- **FPU tests**: 80-bit arithmetic, transcendental functions, packed decimal, memory operands, rounding modes, and save/restore
- **MMU translation tests**: 68030/68040/68060 table walks, ATCs, TTR matching, page crossings, fault frames, and writeback
- **Privilege tests**: User/supervisor mode transitions, TRAP behavior
- **Exception tests**: Per-model frames, resumable bus faults, double faults, and address errors
- **Execution-path differential tests**: Cold/warm cache state, self-modifying code, cycle batches, fast batches, and native traces
### Test Coverage
```
tests/
├── singlestep_m68000_v1_tests.rs # Exhaustive 68000 fixture suite
├── musashi_tests.rs # Musashi integration binaries
├── m68020_tests.rs ... # Generation-specific behavior
├── m68060_tests.rs # 68060 integer, FPU, and MMU behavior
├── fpu_accuracy.rs # Extended-precision differential tests
├── run_for_cycles_tests.rs # Precise cycle-batch boundary contract
├── run_batch_tests.rs # HLE fast-path equivalence and exits
└── fixtures/
├── m68000/ # External SingleStepTests checkout
└── Musashi/ # Musashi reference binaries
```
## Architecture
```
m68k/
├── core/ # CPU state, interpreter, timing, caches, and trace JIT
├── dasm/ # Disassembler
├── fpu/ # 80-bit FPU, packed decimal, and transcendental operations
└── mmu/ # 68030/68040/68060 translation and ATCs
```
### Key Types
| `CpuCore` | Main CPU state and execution APIs |
| `CpuType` | CPU model selection enum |
| `AddressBus` | Extensible memory, device, fetch-cache, timing, and fast-RAM contract |
| `LinearMemoryBus` / `FastMem` | Ready-made flat memory and optional direct-RAM window |
| `HleHandler` | Trap interception callbacks |
| `StepResult` | Single-instruction result |
| `CycleBatchResult` / `CycleBatchExit` | Precise cycle-scheduled execution result |
| `BatchResult` / `BatchExit` | High-throughput instruction-batch result |
| `CpuCore::is_stopped()` | STOP state check |
| `CpuCore::is_halted()` | Double-fault halt check |
## Performance
Accuracy and throughput are separate, deliberate contracts:
- **Precise execution** — `step`, `step_with_hle_handler`, `execute`, and
`run_for_cycles` use the interpreter and ordinary `AddressBus` calls. These
paths preserve bus-visible prefetch, access ordering, internal-clock
synchronization, fault state, and model-specific cycle accounting.
- **Throughput execution** — `run_batch` reuses decoded operations and compiles
eligible hot backward-branch traces with Cranelift on native targets. A bus
may expose a contiguous `FastMem` window to keep eligible RAM operands
inside the trace. Guarded exits and self-modifying-code checks fall back
without partially committing an instruction.
The two paths share instruction semantics and are continuously compared in
cold-cache, warm-cache, memory, control-flow, and fault tests. This keeps the
hardware contract simple for machine emulators while allowing HLE systems to
opt into lower dispatch overhead explicitly.
## License
MIT License - see [LICENSE](LICENSE) for details.
## Contributing
Contributions are welcome! Please ensure:
1. All tests pass: `cargo test`
2. No clippy warnings: `cargo clippy -- -D warnings`
3. Code is formatted: `cargo fmt`
## Acknowledgments
- [Musashi](https://github.com/kstenerud/Musashi) - Reference implementation and test fixtures
- [SingleStepTests](https://github.com/SingleStepTests/m68000) - Exhaustive instruction test vectors
- The M68000 Programmer's Reference Manual