rustynes_cpu/
lib.rs

1//! RustyNES CPU - Cycle-accurate 6502 emulation
2//!
3//! This crate provides a cycle-accurate implementation of the MOS 6502 CPU
4//! as used in the Nintendo Entertainment System (NES). It includes:
5//!
6//! - All 256 opcodes (151 official + 105 unofficial)
7//! - Cycle-accurate timing
8//! - Complete interrupt handling (NMI, IRQ, BRK, RESET)
9//! - All addressing modes
10//! - Zero unsafe code
11//!
12//! # Example
13//!
14//! ```no_run
15//! use rustynes_cpu::{Cpu, Bus};
16//!
17//! // Implement the Bus trait for your system
18//! struct MyBus {
19//!     memory: [u8; 0x10000],
20//! }
21//!
22//! impl Bus for MyBus {
23//!     fn read(&mut self, addr: u16) -> u8 {
24//!         self.memory[addr as usize]
25//!     }
26//!
27//!     fn write(&mut self, addr: u16, value: u8) {
28//!         self.memory[addr as usize] = value;
29//!     }
30//! }
31//!
32//! fn main() {
33//!     let mut cpu = Cpu::new();
34//!     let mut bus = MyBus { memory: [0; 0x10000] };
35//!
36//!     // Set RESET vector to 0x8000
37//!     bus.memory[0xFFFC] = 0x00;
38//!     bus.memory[0xFFFD] = 0x80;
39//!
40//!     // Reset CPU
41//!     cpu.reset(&mut bus);
42//!
43//!     // Execute instructions
44//!     loop {
45//!         let cycles = cpu.step(&mut bus);
46//!         // Execute cycles CPU cycles worth of other system components
47//!     }
48//! }
49//! ```
50//!
51//! # Accuracy
52//!
53//! This implementation is designed to pass:
54//! - nestest.nes golden log
55//! - blargg's cpu_timing_test6
56//! - All TASVideos accuracy tests
57//!
58//! # Architecture
59//!
60//! - **Modular Design**: CPU, PPU, APU, and mappers are separate crates
61//! - **Strong Typing**: Newtype pattern for addresses and flags
62//! - **Safe Code**: Zero unsafe blocks (except for FFI in other crates)
63//! - **Trait-Based**: `Bus` trait allows flexible memory systems
64//!
65//! # Feature Flags
66//!
67//! Currently no optional features. All functionality is included by default.
68
69// Lints are configured in the workspace Cargo.toml
70// This ensures consistent settings across all crates
71
72mod addressing;
73mod bus;
74mod cpu;
75pub mod ines;
76mod instructions;
77mod opcodes;
78pub mod state;
79mod status;
80pub mod trace;
81
82// Public exports
83pub use addressing::AddressingMode;
84pub use bus::{Bus, CpuBus};
85pub use cpu::Cpu;
86pub use ines::{INesHeader, INesRom};
87pub use status::StatusFlags;
88pub use trace::CpuTracer;
89
90// Re-export for convenience
91pub use opcodes::OPCODE_TABLE;
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    struct TestBus {
98        memory: Vec<u8>,
99    }
100
101    impl TestBus {
102        fn new() -> Self {
103            Self {
104                memory: vec![0; 0x10000],
105            }
106        }
107
108        fn load_program(&mut self, start: u16, program: &[u8]) {
109            let start = start as usize;
110            self.memory[start..start + program.len()].copy_from_slice(program);
111        }
112    }
113
114    impl Bus for TestBus {
115        fn read(&mut self, addr: u16) -> u8 {
116            self.memory[addr as usize]
117        }
118
119        fn write(&mut self, addr: u16, value: u8) {
120            self.memory[addr as usize] = value;
121        }
122    }
123
124    #[test]
125    fn test_lda_immediate() {
126        let mut cpu = Cpu::new();
127        let mut bus = TestBus::new();
128
129        // LDA #$42
130        bus.load_program(0x8000, &[0xA9, 0x42]);
131        bus.memory[0xFFFC] = 0x00;
132        bus.memory[0xFFFD] = 0x80;
133
134        cpu.reset(&mut bus);
135        assert_eq!(cpu.pc, 0x8000);
136
137        let cycles = cpu.step(&mut bus);
138        assert_eq!(cycles, 2);
139        assert_eq!(cpu.a, 0x42);
140        assert_eq!(cpu.pc, 0x8002);
141    }
142
143    #[test]
144    fn test_tax_transfer() {
145        let mut cpu = Cpu::new();
146        let mut bus = TestBus::new();
147
148        // LDA #$42, TAX
149        bus.load_program(0x8000, &[0xA9, 0x42, 0xAA]);
150        bus.memory[0xFFFC] = 0x00;
151        bus.memory[0xFFFD] = 0x80;
152
153        cpu.reset(&mut bus);
154
155        cpu.step(&mut bus); // LDA
156        assert_eq!(cpu.a, 0x42);
157
158        cpu.step(&mut bus); // TAX
159        assert_eq!(cpu.x, 0x42);
160    }
161
162    #[test]
163    fn test_adc_carry() {
164        let mut cpu = Cpu::new();
165        let mut bus = TestBus::new();
166
167        // LDA #$FF, ADC #$01
168        bus.load_program(0x8000, &[0xA9, 0xFF, 0x69, 0x01]);
169        bus.memory[0xFFFC] = 0x00;
170        bus.memory[0xFFFD] = 0x80;
171
172        cpu.reset(&mut bus);
173
174        cpu.step(&mut bus); // LDA #$FF
175        assert_eq!(cpu.a, 0xFF);
176
177        cpu.step(&mut bus); // ADC #$01
178        assert_eq!(cpu.a, 0x00); // Wraps to 0
179        assert!(cpu.status.contains(StatusFlags::ZERO));
180        assert!(cpu.status.contains(StatusFlags::CARRY));
181    }
182
183    #[test]
184    fn test_branch_not_taken() {
185        let mut cpu = Cpu::new();
186        let mut bus = TestBus::new();
187
188        // LDA #$00, BNE +2
189        bus.load_program(0x8000, &[0xA9, 0x00, 0xD0, 0x02]);
190        bus.memory[0xFFFC] = 0x00;
191        bus.memory[0xFFFD] = 0x80;
192
193        cpu.reset(&mut bus);
194
195        cpu.step(&mut bus); // LDA #$00 (sets Z flag)
196        let cycles = cpu.step(&mut bus); // BNE (not taken because Z=1)
197
198        assert_eq!(cycles, 2); // Branch not taken
199        assert_eq!(cpu.pc, 0x8004);
200    }
201
202    #[test]
203    fn test_branch_taken_same_page() {
204        let mut cpu = Cpu::new();
205        let mut bus = TestBus::new();
206
207        // LDA #$01, BNE +2
208        bus.load_program(0x8000, &[0xA9, 0x01, 0xD0, 0x02]);
209        bus.memory[0xFFFC] = 0x00;
210        bus.memory[0xFFFD] = 0x80;
211
212        cpu.reset(&mut bus);
213
214        cpu.step(&mut bus); // LDA #$01 (clears Z flag)
215        let cycles = cpu.step(&mut bus); // BNE (taken, same page)
216
217        assert_eq!(cycles, 3); // Branch taken, same page
218        assert_eq!(cpu.pc, 0x8006);
219    }
220
221    #[test]
222    fn test_jsr_rts() {
223        let mut cpu = Cpu::new();
224        let mut bus = TestBus::new();
225
226        // JSR $8010, ..., RTS at $8010
227        bus.load_program(0x8000, &[0x20, 0x10, 0x80]);
228        bus.memory[0x8010] = 0x60; // RTS
229        bus.memory[0xFFFC] = 0x00;
230        bus.memory[0xFFFD] = 0x80;
231
232        cpu.reset(&mut bus);
233
234        let old_sp = cpu.sp;
235        cpu.step(&mut bus); // JSR
236        assert_eq!(cpu.pc, 0x8010);
237        assert_eq!(cpu.sp, old_sp.wrapping_sub(2)); // Pushed 2 bytes
238
239        cpu.step(&mut bus); // RTS
240        assert_eq!(cpu.pc, 0x8003); // Returns to next instruction
241        assert_eq!(cpu.sp, old_sp); // SP restored
242    }
243
244    #[test]
245    fn test_unofficial_lax() {
246        let mut cpu = Cpu::new();
247        let mut bus = TestBus::new();
248
249        // LAX $42 (Zero Page)
250        bus.load_program(0x8000, &[0xA7, 0x42]);
251        bus.memory[0x0042] = 0x55;
252        bus.memory[0xFFFC] = 0x00;
253        bus.memory[0xFFFD] = 0x80;
254
255        cpu.reset(&mut bus);
256
257        let cycles = cpu.step(&mut bus);
258        assert_eq!(cycles, 3);
259        assert_eq!(cpu.a, 0x55);
260        assert_eq!(cpu.x, 0x55);
261    }
262
263    #[test]
264    fn test_stack_operations() {
265        let mut cpu = Cpu::new();
266        let mut bus = TestBus::new();
267
268        // LDA #$42, PHA, LDA #$00, PLA
269        bus.load_program(0x8000, &[0xA9, 0x42, 0x48, 0xA9, 0x00, 0x68]);
270        bus.memory[0xFFFC] = 0x00;
271        bus.memory[0xFFFD] = 0x80;
272
273        cpu.reset(&mut bus);
274
275        cpu.step(&mut bus); // LDA #$42
276        let sp = cpu.sp;
277        cpu.step(&mut bus); // PHA
278        assert_eq!(cpu.sp, sp.wrapping_sub(1));
279
280        cpu.step(&mut bus); // LDA #$00
281        assert_eq!(cpu.a, 0x00);
282
283        cpu.step(&mut bus); // PLA
284        assert_eq!(cpu.a, 0x42);
285        assert_eq!(cpu.sp, sp);
286    }
287}