mizu_core/
memory.rs

1mod dma;
2pub mod interrupts;
3
4use save_state::Savable;
5
6use std::cell::RefCell;
7use std::rc::Rc;
8
9pub use interrupts::{InterruptManager, InterruptType};
10
11use crate::apu::{Apu, AudioBuffers};
12use crate::cartridge::Cartridge;
13use crate::cpu::CpuBusProvider;
14use crate::joypad::{Joypad, JoypadButton};
15use crate::ppu::Ppu;
16use crate::serial::{Serial, SerialDevice};
17use crate::timer::Timer;
18use crate::GameBoyConfig;
19use dma::{BusType, Hdma, OamDma};
20use interrupts::Interrupts;
21
22#[derive(Default, Savable)]
23struct BootRom {
24    enabled: bool,
25    data: Vec<u8>,
26}
27
28#[derive(Clone, Copy, PartialEq, Debug, Savable)]
29enum Speed {
30    Normal,
31    Double,
32}
33
34impl Default for Speed {
35    fn default() -> Self {
36        Self::Normal
37    }
38}
39
40#[derive(Default, Savable)]
41struct SpeedController {
42    preparing_switch: bool,
43    current_speed: Speed,
44}
45
46impl SpeedController {
47    fn read_key1(&self) -> u8 {
48        0x7E | ((self.current_speed as u8) << 7) | self.preparing_switch as u8
49    }
50
51    fn write_key1(&mut self, data: u8) {
52        self.preparing_switch = data & 1 != 0;
53    }
54
55    fn preparing_switch(&self) -> bool {
56        self.preparing_switch
57    }
58
59    fn current_speed(&self) -> Speed {
60        self.current_speed
61    }
62
63    fn commit_speed_switch(&mut self) {
64        assert!(self.preparing_switch);
65        self.current_speed = match self.current_speed {
66            Speed::Normal => Speed::Double,
67            Speed::Double => Speed::Normal,
68        };
69        self.preparing_switch = false;
70    }
71}
72
73#[derive(Savable)]
74struct Wram {
75    data: [u8; 0x8000],
76    bank: u8,
77}
78
79impl Default for Wram {
80    fn default() -> Self {
81        Self {
82            data: [0; 0x8000],
83            bank: 1,
84        }
85    }
86}
87
88impl Wram {
89    fn read_wram0(&self, addr: u16) -> u8 {
90        self.data[addr as usize & 0xFFF]
91    }
92
93    fn read_wramx(&self, addr: u16) -> u8 {
94        self.data[(0x1000 * self.bank as usize) + (addr as usize & 0xFFF)]
95    }
96
97    fn write_wram0(&mut self, addr: u16, data: u8) {
98        self.data[addr as usize & 0xFFF] = data;
99    }
100
101    fn write_wramx(&mut self, addr: u16, data: u8) {
102        self.data[(0x1000 * self.bank as usize) + (addr as usize & 0xFFF)] = data;
103    }
104
105    fn set_wram_bank(&mut self, data: u8) {
106        self.bank = data & 7;
107        // bank cannot be 0
108        if self.bank == 0 {
109            self.bank = 1;
110        }
111    }
112
113    fn get_wram_bank(&self) -> u8 {
114        0xF8 | self.bank
115    }
116}
117
118#[derive(Savable)]
119struct Lock {
120    during_boot: bool,
121    is_dmg_mode: bool,
122    written_to: bool,
123}
124
125impl Default for Lock {
126    fn default() -> Self {
127        Self {
128            during_boot: true,
129            is_dmg_mode: false,
130            written_to: false,
131        }
132    }
133}
134
135impl Lock {
136    fn write(&mut self, data: u8) {
137        // TODO: check if this registers lock after bootrom or after
138        //  first write
139        if !self.written_to {
140            self.written_to = true;
141            self.is_dmg_mode = data & 0x4 != 0;
142        }
143    }
144
145    fn finish_boot(&mut self) {
146        self.during_boot = false;
147    }
148
149    /// The bootrom can write to both CGB and DMG registers during bootrom,
150    /// so this should return true if we are still in bootrom
151    fn is_cgb_mode(&self) -> bool {
152        !self.is_dmg_mode || self.during_boot
153    }
154}
155
156#[derive(Savable)]
157struct UnknownRegister {
158    data: u8,
159    mask: u8,
160}
161
162impl UnknownRegister {
163    fn new(mask: u8) -> Self {
164        Self { data: 0, mask }
165    }
166
167    fn read(&self) -> u8 {
168        (self.data & self.mask) | !self.mask
169    }
170
171    fn write(&mut self, data: u8) {
172        self.data = data & self.mask
173    }
174}
175
176// made this into a structure just to be easier to implement `Savable`
177#[derive(Savable)]
178struct UnknownRegisters {
179    registers: [UnknownRegister; 4],
180}
181
182impl UnknownRegisters {
183    pub fn new(masks: [u8; 4]) -> Self {
184        Self {
185            registers: [
186                UnknownRegister::new(masks[0]),
187                UnknownRegister::new(masks[1]),
188                UnknownRegister::new(masks[2]),
189                UnknownRegister::new(masks[3]),
190            ],
191        }
192    }
193}
194
195impl std::ops::Index<usize> for UnknownRegisters {
196    type Output = UnknownRegister;
197
198    fn index(&self, index: usize) -> &Self::Output {
199        &self.registers[index]
200    }
201}
202
203impl std::ops::IndexMut<usize> for UnknownRegisters {
204    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
205        &mut self.registers[index]
206    }
207}
208
209#[derive(Savable)]
210pub struct Bus {
211    cartridge: Cartridge,
212    ppu: Ppu,
213    wram: Wram,
214    interrupts: Interrupts,
215    timer: Timer,
216    joypad: Joypad,
217    serial: Serial,
218    oam_dma: OamDma,
219    hdma: Hdma,
220    apu: Apu,
221    hram: [u8; 127],
222    boot_rom: BootRom,
223    speed_controller: SpeedController,
224    lock: Lock,
225    unknown_registers: UnknownRegisters,
226
227    #[savable(skip)]
228    serial_device: Option<Rc<RefCell<dyn SerialDevice>>>,
229
230    stopped: bool,
231
232    /// Used to track how many ppu cycles have elapsed
233    /// when the frontend gets the elapsed value, its reset to 0
234    elapsed_ppu_cycles: u32,
235
236    config: GameBoyConfig,
237}
238
239impl Bus {
240    pub fn new_without_boot_rom(cartridge: Cartridge, config: GameBoyConfig) -> Self {
241        let cgb_mode = cartridge.is_cartridge_color();
242        let mut lock = Lock::default();
243
244        if !cgb_mode || config.is_dmg {
245            lock.write(4);
246        } else {
247            // TODO: change this to take the value from the cartridge addr 0x143
248            lock.write(0x80);
249        }
250
251        lock.finish_boot();
252
253        Self {
254            cartridge,
255            ppu: Ppu::new_skip_boot_rom(cgb_mode, config),
256            wram: Wram::default(),
257            interrupts: Interrupts::default(),
258            timer: Timer::new_skip_boot_rom(config),
259            joypad: Joypad::default(),
260            serial: Serial::new_skip_boot_rom(config),
261            oam_dma: OamDma::default(),
262            hdma: Hdma::default(),
263            apu: Apu::new_skip_boot_rom(config),
264            hram: [0; 127],
265            boot_rom: BootRom::default(),
266            speed_controller: SpeedController::default(),
267            lock,
268            unknown_registers: UnknownRegisters::new([0xFF, 0xFF, 0xFF, 0x70]),
269            serial_device: None,
270            stopped: false,
271
272            elapsed_ppu_cycles: 0,
273
274            config,
275        }
276    }
277
278    pub fn new_with_boot_rom(
279        cartridge: Cartridge,
280        boot_rom_data: Vec<u8>,
281        config: GameBoyConfig,
282    ) -> Self {
283        let mut s = Self::new_without_boot_rom(cartridge, config);
284        s.timer = Timer::default();
285        s.ppu = Ppu::new(config);
286        s.apu = Apu::new(config);
287        s.serial = Serial::new(config);
288        s.lock = Lock::default();
289
290        if config.is_dmg {
291            s.lock.write(4);
292            s.lock.finish_boot();
293        }
294
295        // should always pass as another check is done in `lib.rs`, but this is needed
296        // if the Bus was used elsewhere
297        assert_eq!(
298            boot_rom_data.len(),
299            config.boot_rom_len(),
300            "Bootrom length does not match"
301        );
302
303        s.boot_rom.data = boot_rom_data;
304        s.boot_rom.enabled = true;
305        s
306    }
307
308    pub fn cartridge(&self) -> &Cartridge {
309        &self.cartridge
310    }
311
312    pub fn screen_buffer(&self) -> &[u8] {
313        self.ppu.screen_buffer()
314    }
315
316    #[cfg(test)]
317    pub(crate) fn raw_screen_buffer(&self) -> &[u8] {
318        self.ppu.raw_screen_buffer()
319    }
320
321    pub fn audio_buffers(&mut self) -> AudioBuffers<'_> {
322        self.apu.get_buffers()
323    }
324
325    pub fn press_joypad(&mut self, button: JoypadButton) {
326        self.joypad.press_joypad(button);
327    }
328
329    pub fn release_joypad(&mut self, button: JoypadButton) {
330        self.joypad.release_joypad(button);
331    }
332
333    pub fn connect_device(&mut self, device: Rc<RefCell<dyn SerialDevice>>) {
334        self.serial_device = Some(device);
335    }
336
337    pub fn disconnect_device(&mut self) {
338        self.serial_device = None;
339    }
340
341    pub fn elapsed_ppu_cycles(&mut self) -> u32 {
342        std::mem::replace(&mut self.elapsed_ppu_cycles, 0)
343    }
344}
345
346impl Bus {
347    fn on_cpu_machine_cycle(&mut self) {
348        let double_speed = self.speed_controller.current_speed() == Speed::Double;
349
350        // will be 2 in normal speed and 1 in double speed
351        let cpu_clocks_added = ((!double_speed) as u8) + 1;
352
353        // will be 4 in normal speed and 2 in double speed
354        let t_clocks = cpu_clocks_added * 2;
355
356        // In order to not crash if overflowed (in case the user is not taking the value
357        // after every cpu exeution)
358        self.elapsed_ppu_cycles = self.elapsed_ppu_cycles.saturating_add(t_clocks as u32);
359
360        // we return after updating `elapsed_ppu_cycles` because frontend
361        // depend on it
362        if self.stopped {
363            if self.joypad.get_keys_pressed() != 0xF {
364                self.stopped = false;
365            }
366
367            return;
368        }
369
370        // The mapper is independent of CPU clock speed, and a full second
371        // for the mapper is 4194304/2 clocks
372        self.cartridge.clock_mapper();
373        if !double_speed {
374            self.cartridge.clock_mapper();
375        }
376
377        // PPU stays at the same speed even if CPU is in double speed
378        self.ppu.clock(&mut self.interrupts, t_clocks);
379
380        // APU stays at the same speed even if CPU is in double speed,
381        // the APU will handle clocking to stay in the same speed regardless
382        // of the CPU speed
383        self.apu.clock(double_speed, self.timer.read_div());
384
385        // HDMA stays at the same speed even if CPU is in double speed
386        if self.hdma.is_transferreing(&self.ppu) {
387            // this can be filled with 1 values in double mode and 2 in normal
388            // mode
389            let mut values = [0; 2];
390            let addr = self.hdma.get_next_src_address();
391            values[0] = self.read_not_ticked(addr, None);
392            let mut values_len = 1;
393            if !double_speed {
394                values_len = 2;
395                let addr = self.hdma.get_next_src_address();
396                values[1] = self.read_not_ticked(addr, None);
397            }
398
399            self.hdma
400                .transfer_clock(&mut self.ppu, &values[..values_len]);
401        }
402
403        // timer, DMA, and serial follow the CPU in speed and operates at double speed
404        // if CPU is in double speed
405        self.timer.clock_divider(&mut self.interrupts);
406        self.joypad.update_interrupts(&mut self.interrupts);
407
408        let serial_bit = self.serial.clock_for_bit(&mut self.interrupts);
409
410        // TODO: this design only support gameboy sending data as master clock.
411        //  Add support for gameboy as slave (maybe another gameboy as master).
412        if let Some(bit) = serial_bit {
413            if let Some(serial_device) = self.serial_device.as_mut() {
414                if let Ok(mut serial_device) = serial_device.try_borrow_mut() {
415                    let received_bit = serial_device.exchange_bit_external_clock(bit);
416                    self.serial.receive_bit(received_bit);
417                }
418            }
419        }
420
421        if self.oam_dma.in_transfer() {
422            let value = self.read_not_ticked(self.oam_dma.get_next_address(), None);
423            self.oam_dma.transfer_clock(&mut self.ppu, value);
424        }
425    }
426
427    pub(crate) fn read_not_ticked(&mut self, addr: u16, block_for_dma: Option<BusType>) -> u8 {
428        let dma_value = if block_for_dma.is_some() {
429            self.oam_dma.current_value()
430        } else {
431            0xFF
432        };
433
434        let page = (addr >> 8) as u8;
435        let offset = addr as u8;
436
437        match (page, block_for_dma) {
438            (0x00, _) | (0x02..=0x08, _) if self.boot_rom.enabled => {
439                self.boot_rom.data[addr as usize]
440            } // boot rom
441            (0x02..=0x08, _) if self.boot_rom.enabled && !self.config.is_dmg => {
442                self.boot_rom.data[addr as usize]
443            } // boot rom
444            (0x00..=0x7F, Some(BusType::External)) => dma_value, // external bus DMA conflict
445            (0x00..=0x3F, _) => self.cartridge.read_rom0(addr),  // rom0
446            (0x40..=0x7F, _) => self.cartridge.read_romx(addr),  // romx
447            (0x80..=0x9F, Some(BusType::Video)) => dma_value,    // video bus DMA conflict
448            (0x80..=0x9F, _) => self.ppu.read_vram(addr),        // ppu vram
449            (0xA0..=0xDF, Some(BusType::External)) if self.config.is_dmg => dma_value, // external bus DMA conflict
450            (0xA0..=0xBF, _) => self.cartridge.read_ram(addr),                         // sram
451            (0xC0..=0xCF, _) => self.wram.read_wram0(addr),                            // wram0
452            (0xD0..=0xDF, _) => self.wram.read_wramx(addr),                            // wramx
453            (0xE0..=0xFD, _) => self.read_not_ticked(0xC000 | (addr & 0x1FFF), block_for_dma), // echo
454            (0xFE, None) if offset <= 0x9F => self.ppu.read_oam(addr), // ppu oam
455            (0xFE, _) if offset >= 0xA0 => 0,                          // unused
456            (0xFF, _) => self.read_io(offset),                         // io registers
457            _ => 0xFF,
458        }
459    }
460
461    fn write_not_ticked(&mut self, addr: u16, data: u8, block_for_dma: Option<BusType>) {
462        let page = (addr >> 8) as u8;
463        let offset = addr as u8;
464
465        match (page, block_for_dma) {
466            (0x00..=0x7F, Some(BusType::External)) => {} // ignore writes
467            (0x00..=0x7F, _) => self.cartridge.write_to_bank_controller(addr, data), // cart
468            (0x80..=0x9F, Some(BusType::Video)) => {}    // ignore writes
469            (0x80..=0x9F, _) => self.ppu.write_vram(addr, data), // ppu vram
470            (0xA0..=0xDF, Some(BusType::External)) if self.config.is_dmg => {} // ignore writes
471            (0xA0..=0xBF, _) => self.cartridge.write_ram(addr, data), // sram
472            (0xC0..=0xCF, _) => self.wram.write_wram0(addr, data), // wram0
473            (0xD0..=0xDF, _) => self.wram.write_wramx(addr, data), // wramx
474            (0xE0..=0xFD, _) => {
475                self.write_not_ticked(0xC000 | (addr & 0x1FFF), data, block_for_dma)
476            } // echo
477            (0xFE, None) if offset <= 0x9F => {
478                self.ppu.write_oam(addr, data) // ppu oam
479            }
480            (0xFF, _) => self.write_io(offset, data), // io registers
481            _ => {}
482        }
483    }
484
485    fn read_io(&mut self, offset: u8) -> u8 {
486        let addr = 0xFF00 | (offset as u16);
487        match offset {
488            0x00 => self.joypad.read_joypad(),              // joypad
489            0x01 => self.serial.read_data(),                // serial
490            0x02 => self.serial.read_control(),             // serial
491            0x04 => self.timer.read_div(),                  // timer
492            0x05 => self.timer.read_timer_counter(),        // timer
493            0x06 => self.timer.read_timer_reload(),         // timer
494            0x07 => self.timer.read_control(),              // timer
495            0x0F => self.interrupts.read_interrupt_flags(), // interrupts flags
496            0x10..=0x3F => self.apu.read_register(addr),    // apu
497            0x40 => self.ppu.read_lcd_control(),            // ppu
498            0x41 => self.ppu.read_lcd_status(),             // ppu
499            0x42 => self.ppu.read_scroll_y(),               // ppu
500            0x43 => self.ppu.read_scroll_x(),               // ppu
501            0x44 => self.ppu.read_ly(),                     // ppu
502            0x45 => self.ppu.read_lyc(),                    // ppu
503            0x46 => self.oam_dma.read_register(),           // oam dma
504            0x47 => self.ppu.read_dmg_bg_palette(),         // ppu
505            0x48 => self.ppu.read_dmg_sprite_palettes(0),   // ppu
506            0x49 => self.ppu.read_dmg_sprite_palettes(1),   // ppu
507            0x4A => self.ppu.read_window_y(),               // ppu
508            0x4B => self.ppu.read_window_x(),               // ppu
509            0x4D if self.lock.is_cgb_mode() => self.speed_controller.read_key1(), // speed
510            0x4F if !self.config.is_dmg => self.ppu.read_vram_bank(), // vram bank
511            0x50 => 0xFF,                                   // boot rom stop
512            0x51..=0x55 if self.lock.is_cgb_mode() => self.hdma.read_register(addr), // hdma
513            0x56 if self.lock.is_cgb_mode() => {
514                // TODO: implement RP port
515                0xFF
516            }
517            0x68 if !self.config.is_dmg => self.ppu.read_cgb_bg_palettes_index(), // ppu
518            0x69 if !self.config.is_dmg => self.ppu.read_cgb_bg_palettes_data(),  // ppu
519            0x6A if !self.config.is_dmg => self.ppu.read_cgb_sprite_palettes_index(), // ppu
520            0x6B if self.lock.is_cgb_mode() => self.ppu.read_cgb_sprite_palettes_data(), // ppu
521            0x6C if !self.config.is_dmg => self.ppu.read_sprite_priority_mode(),
522            0x70 if self.lock.is_cgb_mode() => self.wram.get_wram_bank(), // wram bank
523            0x72 if !self.config.is_dmg => self.unknown_registers[0].read(), // unknown
524            0x73 if !self.config.is_dmg => self.unknown_registers[1].read(), // unknown
525            0x74 if self.lock.is_cgb_mode() => self.unknown_registers[2].read(), // unknown
526            0x75 if !self.config.is_dmg => self.unknown_registers[3].read(), // unknown
527            0x76 if !self.config.is_dmg => self.apu.read_pcm12(),         // apu pcm 12
528            0x77 if !self.config.is_dmg => self.apu.read_pcm34(),         // apu pcm 34
529            0x80..=0xFE => self.hram[addr as usize & 0x7F],               // hram
530            0xFF => self.interrupts.read_interrupt_enable(),              //interrupts enable
531            _ => 0xFF,
532        }
533    }
534
535    fn write_io(&mut self, offset: u8, data: u8) {
536        let addr = 0xFF00 | (offset as u16);
537
538        match offset {
539            0x00 => self.joypad.write_joypad(data),              // joypad
540            0x01 => self.serial.write_data(data),                // serial
541            0x02 => self.serial.write_control(data),             // serial
542            0x04 => self.timer.write_div(data),                  // timer
543            0x05 => self.timer.write_timer_counter(data),        // timer
544            0x06 => self.timer.write_timer_reload(data),         // timer
545            0x07 => self.timer.write_control(data),              // timer
546            0x0F => self.interrupts.write_interrupt_flags(data), // interrupts flags
547            0x10..=0x3F => self.apu.write_register(addr, data),  // apu
548            0x40 => self.ppu.write_lcd_control(data),            // ppu
549            0x41 => self.ppu.write_lcd_status(data),             // ppu
550            0x42 => self.ppu.write_scroll_y(data),               // ppu
551            0x43 => self.ppu.write_scroll_x(data),               // ppu
552            0x44 => self.ppu.write_ly(data),                     // ppu
553            0x45 => self.ppu.write_lyc(data),                    // ppu
554            0x46 => self.oam_dma.write_register(data),           // dma start
555            0x47 => self.ppu.write_dmg_bg_palette(data),         // ppu
556            0x48 => self.ppu.write_dmg_sprite_palettes(0, data), // ppu
557            0x49 => self.ppu.write_dmg_sprite_palettes(1, data), // ppu
558            0x4A => self.ppu.write_window_y(data),               // ppu
559            0x4B => self.ppu.write_window_x(data),               // ppu
560            0x4C if self.lock.is_cgb_mode() => self.lock.write(data), // DMG/CGB lock register
561            0x4D if self.lock.is_cgb_mode() => self.speed_controller.write_key1(data), // speed
562            0x4F if self.lock.is_cgb_mode() => self.ppu.write_vram_bank(data), // vram bank
563            0x50 => {
564                self.lock.finish_boot();
565                self.boot_rom.enabled = false;
566                self.ppu
567                    .update_cgb_mode(self.cartridge.is_cartridge_color());
568            } // boot rom stop
569            0x51..=0x55 if self.lock.is_cgb_mode() => self.hdma.write_register(addr, data), // hdma
570            0x56 => {
571                //TODO: implement RP port
572            }
573            0x68 if self.lock.is_cgb_mode() => self.ppu.write_cgb_bg_palettes_index(data), // ppu
574            0x69 if self.lock.is_cgb_mode() => self.ppu.write_cgb_bg_palettes_data(data),  // ppu
575            0x6A if self.lock.is_cgb_mode() => self.ppu.write_cgb_sprite_palettes_index(data), // ppu
576            0x6B if self.lock.is_cgb_mode() => self.ppu.write_cgb_sprite_palettes_data(data), // ppu
577            0x6C if self.lock.is_cgb_mode() => self.ppu.write_sprite_priority_mode(data),
578            0x70 if self.lock.is_cgb_mode() => self.wram.set_wram_bank(data), // wram bank
579            0x72 if !self.config.is_dmg => self.unknown_registers[0].write(data), // unknown
580            0x73 if !self.config.is_dmg => self.unknown_registers[1].write(data), // unknown
581            0x74 if self.lock.is_cgb_mode() => self.unknown_registers[2].write(data), // unknown
582            0x75 if !self.config.is_dmg => self.unknown_registers[3].write(data), // unknown
583            0x80..=0xFE => self.hram[addr as usize & 0x7F] = data,            // hram
584            0xFF => self.interrupts.write_interrupt_enable(data),             // interrupts enable
585            _ => {}
586        }
587    }
588}
589
590impl CpuBusProvider for Bus {
591    /// each time the cpu reads, clock the components on the bus
592    fn read(&mut self, addr: u16) -> u8 {
593        let result = self.read_no_oam_bug(addr);
594
595        if self.config.is_dmg && addr & 0xFF00 == 0xFE00 {
596            self.ppu.oam_bug_read();
597        }
598
599        result
600    }
601
602    /// each time the cpu writes, clock the components on the bus
603    fn write(&mut self, addr: u16, data: u8) {
604        self.write_not_ticked(addr, data, self.oam_dma.conflicting_bus());
605        self.on_cpu_machine_cycle();
606
607        if self.config.is_dmg && addr & 0xFF00 == 0xFE00 {
608            self.ppu.oam_bug_write();
609        }
610    }
611
612    // gets the interrupt type and remove it
613    fn take_next_interrupt(&mut self) -> Option<InterruptType> {
614        let int = self.interrupts.get_highest_interrupt();
615        if let Some(int) = int {
616            self.interrupts.acknowledge_interrupt(int);
617        }
618        int
619    }
620
621    fn peek_next_interrupt(&mut self) -> Option<InterruptType> {
622        self.interrupts.get_highest_interrupt()
623    }
624
625    fn is_hdma_running(&mut self) -> bool {
626        self.hdma.is_transferreing(&self.ppu)
627    }
628
629    fn enter_stop_mode(&mut self) {
630        if self.speed_controller.preparing_switch() {
631            assert!(!self.config.is_dmg, "Cannot switch speed in DMG");
632            self.speed_controller.commit_speed_switch();
633            self.timer.write_div(0);
634        } else {
635            self.stopped = true;
636            self.ppu.enter_stop_mode();
637            // TODO: is there any special stuff to do with the apu?
638            //  for CGB, sounds still play?
639            // self.apu.enter_stop_mode(); ?
640        }
641    }
642
643    fn stopped(&self) -> bool {
644        self.stopped
645    }
646
647    fn trigger_write_oam_bug(&mut self, addr: u16) {
648        if self.config.is_dmg && addr & 0xFF00 == 0xFE00 {
649            self.ppu.oam_bug_write();
650        }
651    }
652
653    fn trigger_read_write_oam_bug(&mut self, addr: u16) {
654        if self.config.is_dmg && addr & 0xFF00 == 0xFE00 {
655            self.ppu.oam_bug_read_write();
656        }
657    }
658
659    fn read_no_oam_bug(&mut self, addr: u16) -> u8 {
660        let result = self.read_not_ticked(addr, self.oam_dma.conflicting_bus());
661        self.on_cpu_machine_cycle();
662        result
663    }
664}