Game Boy bus / address mapping
3use crate::HardwareMode; 4use crate::apu::Apu; 5use crate::cartridge::Cartridge; 6use crate::cgb::{CgbRegisters, CpuSpeed}; 7use crate::dma::DmaUnit; 8use crate::inputs::InputState; 9use crate::interrupts::InterruptRegisters; 10use crate::memory::Memory; 11use crate::ppu::Ppu; 12use crate::serial::SerialPort; 13use crate::sm83::InterruptType; 14use crate::sm83::bus::BusInterface; 15use crate::timer::GbTimer;
17pub struct Bus<'a> { 18 pub hardware_mode: HardwareMode, 19 pub ppu: &'a mut Ppu, 20 pub apu: &'a mut Apu, 21 pub memory: &'a mut Memory, 22 pub serial_port: &'a mut SerialPort, 23 pub cartridge: &'a mut Cartridge, 24 pub interrupt_registers: &'a mut InterruptRegisters, 25 pub cgb_registers: &'a mut CgbRegisters, 26 pub timer: &'a mut GbTimer, 27 pub dma_unit: &'a mut DmaUnit, 28 pub input_state: &'a mut InputState, 29} 30 31fn cgb_only_read(bus: &Bus<'_>, read_fn: impl FnOnce(&Bus<'_>) -> u8) -> u8 { 32 match (bus.hardware_mode, bus.cgb_registers.dmg_compatibility) { 33 (HardwareMode::Dmg, _) | (HardwareMode::Cgb, true) => 0xFF, 34 (HardwareMode::Cgb, false) => read_fn(bus), 35 } 36} 37 38fn cgb_only_write(bus: &mut Bus<'_>, write_fn: impl FnOnce(&mut Bus<'_>)) { 39 // The CGB boot ROM depends on still being able to write to some CGB registers between writing 40 // to KEY0 (enable DMG compatibility mode) and writing to BANK (unmap boot ROM) 41 if bus.hardware_mode == HardwareMode::Cgb 42 && (!bus.cgb_registers.dmg_compatibility || bus.memory.boot_rom_mapped()) 43 { 44 write_fn(bus); 45 } 46} 47 48fn cgb_boot_rom_only_write(bus: &mut Bus<'_>, write_fn: impl FnOnce(&mut Bus<'_>)) { 49 if bus.hardware_mode == HardwareMode::Cgb && bus.memory.boot_rom_mapped() { 50 write_fn(bus); 51 } 52} 53 54impl Bus<'_> { 55 fn read_io_register(&self, address: u16) -> u8 { 56 log::trace!("I/O register read: {address:04X}"); 57 58 match address & 0x7F { 59 0x00 => self.input_state.read_joyp(), 60 0x01 => self.serial_port.read_data(), 61 0x02 => self.serial_port.read_control(), 62 0x04 => self.timer.read_div(), 63 0x05 => self.timer.read_tima(), 64 0x06 => self.timer.read_tma(), 65 0x07 => self.timer.read_tac(), 66 0x0F => self.interrupt_registers.read_if(), 67 0x10..=0x3F => self.apu.read_register(address), 68 0x40..=0x45 | 0x47..=0x4B => self.read_ppu_register(address), 69 0x4F | 0x68..=0x6B => cgb_only_read(self, |bus| bus.read_ppu_register(address)), 70 0x46 => self.dma_unit.read_dma_register(), 71 0x4D => cgb_only_read(self, |bus| bus.cgb_registers.read_key1()), 72 0x55 => cgb_only_read(self, |bus| bus.dma_unit.read_hdma5()), 73 0x6C => cgb_only_read(self, |bus| bus.cgb_registers.read_opri()), 74 0x70 => cgb_only_read(self, |bus| bus.memory.read_svbk()), 75 0x76 => cgb_only_read(self, |bus| bus.apu.read_pcm12()), 76 0x77 => cgb_only_read(self, |bus| bus.apu.read_pcm34()), 77 _ => { 78 log::debug!("Unexpected I/O register read: {address:04X}"); 79 0xFF 80 } 81 } 82 } 83 84 fn read_ppu_register(&self, address: u16) -> u8 { 85 self.ppu.read_register(address, *self.cgb_registers) 86 } 87 88 fn write_io_register(&mut self, address: u16, value: u8) { 89 log::trace!("I/O register write: {address:04X} {value:02X}"); 90 91 match address & 0x7F { 92 0x00 => self.input_state.write_joyp(value, self.interrupt_registers), 93 0x01 => self.serial_port.write_data(value), 94 0x02 => self.serial_port.write_control(value), 95 0x04 => self.timer.write_div(), 96 0x05 => self.timer.write_tima(value), 97 0x06 => self.timer.write_tma(value), 98 0x07 => self.timer.write_tac(value), 99 0x0F => self.interrupt_registers.write_if(value), 100 0x10..=0x3F => self.apu.write_register(address, value), 101 0x40..=0x45 | 0x47..=0x4B => self.write_ppu_register(address, value), 102 0x4F | 0x68..=0x6B => { 103 cgb_only_write(self, |bus| bus.write_ppu_register(address, value)); 104 } 105 0x46 => self.dma_unit.write_dma_register(value), 106 0x4C => cgb_boot_rom_only_write(self, |bus| bus.cgb_registers.write_key0(value)), 107 0x4D => cgb_only_write(self, |bus| bus.cgb_registers.write_key1(value)), 108 0x50 => self.memory.write_bank(value), 109 0x51 => cgb_only_write(self, |bus| bus.dma_unit.write_hdma1(value)), 110 0x52 => cgb_only_write(self, |bus| bus.dma_unit.write_hdma2(value)), 111 0x53 => cgb_only_write(self, |bus| bus.dma_unit.write_hdma3(value)), 112 0x54 => cgb_only_write(self, |bus| bus.dma_unit.write_hdma4(value)), 113 0x55 => cgb_only_write(self, |bus| bus.dma_unit.write_hdma5(value, bus.ppu.mode())), 114 0x6C => cgb_boot_rom_only_write(self, |bus| bus.cgb_registers.write_opri(value)), 115 0x70 => cgb_only_write(self, |bus| bus.memory.write_svbk(value)), 116 _ => { 117 log::debug!("Unexpected I/O register write: {address:04X} {value:02X}"); 118 } 119 } 120 } 121 122 fn write_ppu_register(&mut self, address: u16, value: u8) { 123 self.ppu.write_register(address, value, self.cgb_registers.speed, self.interrupt_registers); 124 } 125 126 fn tick_components(&mut self) { 127 loop { 128 self.timer.tick_m_cycle(self.interrupt_registers); 129 self.dma_unit.oam_dma_tick_m_cycle(self.cartridge, self.memory, self.ppu); 130 self.serial_port.tick(self.interrupt_registers); 131 132 if self.cgb_registers.speed == CpuSpeed::Double { 133 self.cgb_registers.double_speed_odd_cycle = 134 !self.cgb_registers.double_speed_odd_cycle; 135 if self.cgb_registers.double_speed_odd_cycle { 136 if self.dma_unit.vram_dma_active() { 137 continue; 138 } 139 return; 140 } 141 } 142 143 for _ in 0..2 { 144 self.dma_unit.vram_dma_copy_byte(self.cartridge, self.memory, self.ppu); 145 } 146 147 for _ in 0..4 { 148 self.ppu.tick_dot(*self.cgb_registers, self.dma_unit, self.interrupt_registers); 149 } 150 151 self.apu.tick_m_cycle(self.timer, self.cgb_registers.speed); 152 153 self.cartridge.tick_cpu(); 154 155 // Execute all non-CPU components indefinitely while CPU is halted due to VRAM DMA 156 // This allows HDMA to halt the CPU mid-instruction (fixes glitches in Toy Story Racer) 157 if !self.dma_unit.vram_dma_active() { 158 return; 159 } 160 } 161 } 162} 163 164impl BusInterface for Bus<'_> { 165 fn read(&mut self, address: u16) -> u8 { 166 self.tick_components(); 167 168 match address { 169 0x0000..=0x7FFF => self 170 .memory 171 .try_read_boot_rom(address) 172 .unwrap_or_else(|| self.cartridge.read_rom(address)), 173 0x8000..=0x9FFF => self.ppu.read_vram(address), 174 0xA000..=0xBFFF => self.cartridge.read_ram(address), 175 0xC000..=0xFDFF => self.memory.read_main_ram(address), 176 0xFE00..=0xFE9F => { 177 if !self.dma_unit.oam_dma_in_progress() { 178 self.ppu.read_oam(address) 179 } else { 180 0xFF 181 } 182 } 183 // Unusable memory 184 0xFEA0..=0xFEFF => 0xFF, 185 0xFF00..=0xFF7F => self.read_io_register(address), 186 0xFF80..=0xFFFE => self.memory.read_hram(address), 187 0xFFFF => self.interrupt_registers.read_ie(), 188 } 189 } 190 191 fn write(&mut self, address: u16, value: u8) { 192 self.tick_components(); 193 194 match address { 195 0x0000..=0x7FFF => self.cartridge.write_rom(address, value), 196 0x8000..=0x9FFF => self.ppu.write_vram(address, value), 197 0xA000..=0xBFFF => self.cartridge.write_ram(address, value), 198 0xC000..=0xFDFF => self.memory.write_main_ram(address, value), 199 0xFE00..=0xFE9F => { 200 if !self.dma_unit.oam_dma_in_progress() { 201 self.ppu.write_oam(address, value); 202 } 203 } 204 // Unusable memory 205 0xFEA0..=0xFEFF => {} 206 0xFF00..=0xFF7F => self.write_io_register(address, value), 207 0xFF80..=0xFFFE => self.memory.write_hram(address, value), 208 0xFFFF => self.interrupt_registers.write_ie(value), 209 } 210 } 211 212 fn idle(&mut self) { 213 self.tick_components(); 214 } 215 216 fn read_ie_register(&self) -> u8 { 217 self.interrupt_registers.read_ie() & 0x1F 218 } 219 220 fn read_if_register(&self) -> u8 { 221 self.interrupt_registers.read_if() & 0x1F 222 } 223 224 fn acknowledge_interrupt(&mut self, interrupt_type: InterruptType) { 225 self.interrupt_registers.clear_flag(interrupt_type); 226 } 227 228 fn halt(&self) -> bool { 229 self.dma_unit.vram_dma_active() 230 } 231 232 fn speed_switch_armed(&self) -> bool { 233 self.cgb_registers.speed_switch_armed 234 } 235 236 fn perform_speed_switch(&mut self) { 237 self.cgb_registers.perform_speed_switch(); 238 } 239}