rustynes_cpu/bus.rs
1//! Memory bus trait for CPU communication.
2//!
3//! The Bus trait defines the interface between the CPU and the rest of the system.
4//! All memory reads and writes go through this trait, allowing for flexible
5//! implementation of memory mapping, I/O registers, and hardware synchronization.
6
7/// Memory bus interface
8///
9/// Implementors of this trait provide memory access to the CPU.
10/// The CPU calls `read()` and `write()` for all memory operations.
11///
12/// # Examples
13///
14/// ## Simple RAM-only bus
15///
16/// ```
17/// use rustynes_cpu::Bus;
18///
19/// struct SimpleBus {
20/// ram: [u8; 0x10000],
21/// }
22///
23/// impl Bus for SimpleBus {
24/// fn read(&mut self, addr: u16) -> u8 {
25/// self.ram[addr as usize]
26/// }
27///
28/// fn write(&mut self, addr: u16, value: u8) {
29/// self.ram[addr as usize] = value;
30/// }
31/// }
32/// ```
33///
34/// ## NES bus with memory-mapped I/O
35///
36/// ```
37/// use rustynes_cpu::Bus;
38///
39/// # struct Ppu;
40/// # impl Ppu {
41/// # fn read_register(&mut self, _: u16) -> u8 { 0 }
42/// # fn write_register(&mut self, _: u16, _: u8) {}
43/// # }
44/// struct NesBus {
45/// ram: [u8; 0x800],
46/// ppu: Ppu,
47/// // ... other components
48/// }
49///
50/// impl Bus for NesBus {
51/// fn read(&mut self, addr: u16) -> u8 {
52/// match addr {
53/// 0x0000..=0x1FFF => {
54/// // 2KB internal RAM, mirrored 4 times
55/// self.ram[(addr & 0x07FF) as usize]
56/// }
57/// 0x2000..=0x3FFF => {
58/// // PPU registers, mirrored every 8 bytes
59/// self.ppu.read_register(addr & 0x0007)
60/// }
61/// // ... other address ranges
62/// _ => 0,
63/// }
64/// }
65///
66/// fn write(&mut self, addr: u16, value: u8) {
67/// match addr {
68/// 0x0000..=0x1FFF => {
69/// self.ram[(addr & 0x07FF) as usize] = value;
70/// }
71/// 0x2000..=0x3FFF => {
72/// self.ppu.write_register(addr & 0x0007, value);
73/// }
74/// // ... other address ranges
75/// _ => {}
76/// }
77/// }
78/// }
79/// ```
80pub trait Bus {
81 /// Read a byte from memory
82 ///
83 /// # Arguments
84 ///
85 /// * `addr` - 16-bit memory address to read from
86 ///
87 /// # Returns
88 ///
89 /// The 8-bit value at the specified address
90 ///
91 /// # Notes
92 ///
93 /// - This function may have side effects (e.g., reading from a hardware register)
94 /// - Open bus behavior: return last value on the bus for unmapped addresses
95 /// - For debugging, implement `peek()` instead
96 fn read(&mut self, addr: u16) -> u8;
97
98 /// Write a byte to memory
99 ///
100 /// # Arguments
101 ///
102 /// * `addr` - 16-bit memory address to write to
103 /// * `value` - 8-bit value to write
104 ///
105 /// # Notes
106 ///
107 /// - This function may have side effects (e.g., triggering DMA)
108 /// - Writes to ROM or unmapped areas should be ignored (or logged)
109 fn write(&mut self, addr: u16, value: u8);
110
111 /// Read a byte without side effects (for debugging/disassembly)
112 ///
113 /// Default implementation returns 0. Override for proper debugging support.
114 ///
115 /// # Arguments
116 ///
117 /// * `addr` - 16-bit memory address to peek at
118 ///
119 /// # Returns
120 ///
121 /// The 8-bit value at the specified address, without triggering side effects
122 ///
123 /// # Notes
124 ///
125 /// - This should NOT modify any state (e.g., don't clear IRQ flags)
126 /// - Used by debuggers and disassemblers
127 /// - Default implementation returns 0 for simplicity
128 #[inline]
129 fn peek(&self, addr: u16) -> u8 {
130 let _ = addr;
131 0
132 }
133
134 /// Read a 16-bit value in little-endian format
135 ///
136 /// Reads two consecutive bytes and combines them into a 16-bit value.
137 ///
138 /// # Arguments
139 ///
140 /// * `addr` - Address of the low byte
141 ///
142 /// # Returns
143 ///
144 /// 16-bit value: `(high << 8) | low`
145 ///
146 /// # Notes
147 ///
148 /// - Reads from `addr` (low byte) and `addr + 1` (high byte)
149 /// - Addition wraps: `0xFFFF + 1 = 0x0000`
150 ///
151 /// # Example
152 ///
153 /// ```
154 /// # use rustynes_cpu::Bus;
155 /// # struct TestBus { ram: [u8; 0x10000] }
156 /// # impl Bus for TestBus {
157 /// # fn read(&mut self, addr: u16) -> u8 { self.ram[addr as usize] }
158 /// # fn write(&mut self, addr: u16, value: u8) { self.ram[addr as usize] = value; }
159 /// # }
160 /// # let mut bus = TestBus { ram: [0; 0x10000] };
161 /// bus.write(0x1000, 0x34); // Low byte
162 /// bus.write(0x1001, 0x12); // High byte
163 /// assert_eq!(bus.read_u16(0x1000), 0x1234);
164 /// ```
165 #[inline]
166 fn read_u16(&mut self, addr: u16) -> u16 {
167 let lo = self.read(addr) as u16;
168 let hi = self.read(addr.wrapping_add(1)) as u16;
169 (hi << 8) | lo
170 }
171
172 /// Read a 16-bit value with page wrap (for JMP indirect bug)
173 ///
174 /// The 6502 has a bug in JMP indirect where if the low byte is $FF,
175 /// the high byte is read from $xx00 instead of $(xx+1)00.
176 ///
177 /// # Arguments
178 ///
179 /// * `addr` - Address of the low byte
180 ///
181 /// # Returns
182 ///
183 /// 16-bit value with page-wrap behavior
184 ///
185 /// # Example: JMP ($10FF) Bug
186 ///
187 /// ```
188 /// # use rustynes_cpu::Bus;
189 /// # struct TestBus { ram: [u8; 0x10000] }
190 /// # impl Bus for TestBus {
191 /// # fn read(&mut self, addr: u16) -> u8 { self.ram[addr as usize] }
192 /// # fn write(&mut self, addr: u16, value: u8) { self.ram[addr as usize] = value; }
193 /// # }
194 /// # let mut bus = TestBus { ram: [0; 0x10000] };
195 /// bus.write(0x10FF, 0x34); // Low byte at $10FF
196 /// bus.write(0x1100, 0x56); // Should be high byte (correct)
197 /// bus.write(0x1000, 0x12); // Actually read as high byte (bug!)
198 ///
199 /// // Normal read would give 0x5634
200 /// assert_eq!(bus.read_u16(0x10FF), 0x5634);
201 ///
202 /// // Page-wrap read gives 0x1234 (bug behavior)
203 /// assert_eq!(bus.read_u16_wrap(0x10FF), 0x1234);
204 /// ```
205 #[inline]
206 fn read_u16_wrap(&mut self, addr: u16) -> u16 {
207 let lo = self.read(addr) as u16;
208
209 // If low byte is at $xxFF, high byte wraps to $xx00
210 let hi_addr = if addr & 0xFF == 0xFF {
211 addr & 0xFF00
212 } else {
213 addr.wrapping_add(1)
214 };
215
216 let hi = self.read(hi_addr) as u16;
217 (hi << 8) | lo
218 }
219}
220
221/// Cycle-accurate bus interface for sub-cycle PPU/APU synchronization
222///
223/// This trait extends the basic Bus functionality with cycle callbacks that are
224/// invoked BEFORE each memory access. This enables accurate emulation of the
225/// NES timing where PPU advances 3 dots per CPU cycle and APU advances 1 cycle.
226///
227/// # Architecture
228///
229/// The key insight from accurate emulators (like Pinky) is that `on_cpu_cycle()`
230/// must be called at the START of each memory access, BEFORE the actual read/write.
231/// This ensures that when the CPU reads $2002 (PPUSTATUS), the PPU has already
232/// advanced to the correct state for that exact CPU cycle.
233///
234/// # Timing Model
235///
236/// ```text
237/// CPU Cycle: |-------- read --------|
238/// PPU Cycles: |--1--|--2--|--3--|
239/// ^ on_cpu_cycle() called here (before read)
240/// ```
241///
242/// # Examples
243///
244/// ## Implementing cycle-accurate bus for NES
245///
246/// ```
247/// use rustynes_cpu::CpuBus;
248///
249/// # struct Ppu { scanline: u16, dot: u16 }
250/// # impl Ppu {
251/// # fn step(&mut self) { self.dot += 1; }
252/// # fn read_register(&mut self, _: u16) -> u8 { 0 }
253/// # fn write_register(&mut self, _: u16, _: u8) {}
254/// # }
255/// # struct Apu;
256/// # impl Apu { fn step(&mut self) {} }
257/// struct CycleAccurateBus {
258/// ram: [u8; 0x800],
259/// ppu: Ppu,
260/// apu: Apu,
261/// }
262///
263/// impl CpuBus for CycleAccurateBus {
264/// fn read(&mut self, addr: u16) -> u8 {
265/// match addr {
266/// 0x0000..=0x1FFF => self.ram[(addr & 0x07FF) as usize],
267/// 0x2000..=0x3FFF => self.ppu.read_register(addr & 0x0007),
268/// _ => 0,
269/// }
270/// }
271///
272/// fn write(&mut self, addr: u16, value: u8) {
273/// match addr {
274/// 0x0000..=0x1FFF => self.ram[(addr & 0x07FF) as usize] = value,
275/// 0x2000..=0x3FFF => self.ppu.write_register(addr & 0x0007, value),
276/// _ => {}
277/// }
278/// }
279///
280/// fn on_cpu_cycle(&mut self) {
281/// // Step APU once per CPU cycle
282/// self.apu.step();
283///
284/// // Step PPU 3 times per CPU cycle (3:1 ratio)
285/// for _ in 0..3 {
286/// self.ppu.step();
287/// }
288/// }
289/// }
290/// ```
291///
292/// # Implementation Notes
293///
294/// - The CPU's `read_cycle()` and `write_cycle()` methods call `on_cpu_cycle()`
295/// before performing the actual memory access
296/// - This trait is essential for passing VBlank timing tests that require
297/// +/- 2 cycle accuracy when reading $2002 during VBlank transitions
298/// - Dummy reads for page boundary crossings also trigger `on_cpu_cycle()`
299pub trait CpuBus {
300 /// Read a byte from memory
301 ///
302 /// This is the raw memory read without cycle callback. For cycle-accurate
303 /// emulation, use CPU's `read_cycle()` method instead which calls
304 /// `on_cpu_cycle()` before this method.
305 ///
306 /// # Arguments
307 ///
308 /// * `addr` - 16-bit memory address to read from
309 ///
310 /// # Returns
311 ///
312 /// The 8-bit value at the specified address
313 fn read(&mut self, addr: u16) -> u8;
314
315 /// Write a byte to memory
316 ///
317 /// This is the raw memory write without cycle callback. For cycle-accurate
318 /// emulation, use CPU's `write_cycle()` method instead which calls
319 /// `on_cpu_cycle()` before this method.
320 ///
321 /// # Arguments
322 ///
323 /// * `addr` - 16-bit memory address to write to
324 /// * `value` - 8-bit value to write
325 fn write(&mut self, addr: u16, value: u8);
326
327 /// Called BEFORE each memory access to synchronize PPU/APU
328 ///
329 /// This callback is the heart of cycle-accurate emulation. It must:
330 /// 1. Step the APU once (1:1 ratio with CPU)
331 /// 2. Step the PPU three times (3:1 ratio with CPU)
332 /// 3. Handle any cycle-based events (IRQ timing, etc.)
333 ///
334 /// # Critical Timing
335 ///
336 /// This method is called BEFORE the memory access occurs. This is essential
337 /// for accurate $2002 reads during VBlank, where the PPU state must be
338 /// updated to the exact cycle before the CPU observes the flags.
339 ///
340 /// # Example
341 ///
342 /// ```ignore
343 /// fn on_cpu_cycle(&mut self) {
344 /// // Step APU
345 /// self.apu.step();
346 ///
347 /// // Step PPU 3 times
348 /// for _ in 0..3 {
349 /// self.ppu.step();
350 /// }
351 /// }
352 /// ```
353 fn on_cpu_cycle(&mut self);
354
355 /// Read a byte without side effects (for debugging/disassembly)
356 ///
357 /// Default implementation calls read(). Override for proper debugging support
358 /// where hardware register reads have side effects.
359 ///
360 /// # Notes
361 ///
362 /// - This should NOT call `on_cpu_cycle()`
363 /// - This should NOT modify any state (e.g., don't clear IRQ flags)
364 /// - Used by debuggers and disassemblers
365 #[inline]
366 fn peek(&self, addr: u16) -> u8 {
367 let _ = addr;
368 0
369 }
370
371 /// Read a 16-bit value in little-endian format
372 ///
373 /// Reads two consecutive bytes and combines them into a 16-bit value.
374 /// Note: This does NOT call on_cpu_cycle() - use CPU's cycle-aware methods.
375 ///
376 /// # Arguments
377 ///
378 /// * `addr` - Address of the low byte
379 ///
380 /// # Returns
381 ///
382 /// 16-bit value: `(high << 8) | low`
383 #[inline]
384 fn read_u16(&mut self, addr: u16) -> u16 {
385 let lo = self.read(addr) as u16;
386 let hi = self.read(addr.wrapping_add(1)) as u16;
387 (hi << 8) | lo
388 }
389
390 /// Read a 16-bit value with page wrap (for JMP indirect bug)
391 ///
392 /// The 6502 has a bug in JMP indirect where if the low byte is $FF,
393 /// the high byte is read from $xx00 instead of $(xx+1)00.
394 /// Note: This does NOT call on_cpu_cycle() - use CPU's cycle-aware methods.
395 #[inline]
396 fn read_u16_wrap(&mut self, addr: u16) -> u16 {
397 let lo = self.read(addr) as u16;
398
399 // If low byte is at $xxFF, high byte wraps to $xx00
400 let hi_addr = if addr & 0xFF == 0xFF {
401 addr & 0xFF00
402 } else {
403 addr.wrapping_add(1)
404 };
405
406 let hi = self.read(hi_addr) as u16;
407 (hi << 8) | lo
408 }
409}
410
411/// Blanket implementation: any CpuBus also implements Bus
412///
413/// This provides backward compatibility - code using the simpler Bus trait
414/// will continue to work. The CpuBus trait just adds the on_cpu_cycle() method.
415impl<T: CpuBus> Bus for T {
416 #[inline]
417 fn read(&mut self, addr: u16) -> u8 {
418 CpuBus::read(self, addr)
419 }
420
421 #[inline]
422 fn write(&mut self, addr: u16, value: u8) {
423 CpuBus::write(self, addr, value);
424 }
425
426 #[inline]
427 fn peek(&self, addr: u16) -> u8 {
428 CpuBus::peek(self, addr)
429 }
430
431 #[inline]
432 fn read_u16(&mut self, addr: u16) -> u16 {
433 CpuBus::read_u16(self, addr)
434 }
435
436 #[inline]
437 fn read_u16_wrap(&mut self, addr: u16) -> u16 {
438 CpuBus::read_u16_wrap(self, addr)
439 }
440}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445
446 struct TestBus {
447 ram: [u8; 0x10000],
448 }
449
450 impl Bus for TestBus {
451 fn read(&mut self, addr: u16) -> u8 {
452 self.ram[addr as usize]
453 }
454
455 fn write(&mut self, addr: u16, value: u8) {
456 self.ram[addr as usize] = value;
457 }
458
459 fn peek(&self, addr: u16) -> u8 {
460 self.ram[addr as usize]
461 }
462 }
463
464 #[test]
465 fn test_read_write() {
466 let mut bus = TestBus { ram: [0; 0x10000] };
467
468 bus.write(0x1234, 0x42);
469 assert_eq!(bus.read(0x1234), 0x42);
470 }
471
472 #[test]
473 fn test_read_u16() {
474 let mut bus = TestBus { ram: [0; 0x10000] };
475
476 bus.write(0x1000, 0x34);
477 bus.write(0x1001, 0x12);
478
479 assert_eq!(bus.read_u16(0x1000), 0x1234);
480 }
481
482 #[test]
483 fn test_read_u16_wrap_no_boundary() {
484 let mut bus = TestBus { ram: [0; 0x10000] };
485
486 bus.write(0x1080, 0x34);
487 bus.write(0x1081, 0x12);
488
489 // No page boundary, should behave normally
490 assert_eq!(bus.read_u16_wrap(0x1080), 0x1234);
491 }
492
493 #[test]
494 fn test_read_u16_wrap_page_boundary() {
495 let mut bus = TestBus { ram: [0; 0x10000] };
496
497 bus.write(0x10FF, 0x34); // Low byte
498 bus.write(0x1100, 0x56); // What high byte SHOULD be
499 bus.write(0x1000, 0x12); // What high byte ACTUALLY is (bug)
500
501 // Normal read crosses page correctly
502 assert_eq!(bus.read_u16(0x10FF), 0x5634);
503
504 // Wrap read triggers the bug
505 assert_eq!(bus.read_u16_wrap(0x10FF), 0x1234);
506 }
507
508 #[test]
509 fn test_peek_no_side_effects() {
510 let bus = TestBus {
511 ram: [0x42; 0x10000],
512 };
513
514 // Peek should not modify state
515 assert_eq!(bus.peek(0x1234), 0x42);
516 }
517}