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}