1use crate::HardwareMode; 2use crate::cartridge::Cartridge; 3use crate::memory::Memory; 4use crate::ppu::{Ppu, PpuMode}; 5use bincode::{Decode, Encode}; 6use jgenesis_common::num::{GetBit, U16Ext}; 7 8const OAM_DMA_M_CYCLES: u8 = 160; 9 10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 11enum VramDmaState { 12 #[default] 13 Idle, 14 GpDmaActive, 15 HDmaActive { 16 hblank_bytes_remaining: u8, 17 }, 18 HDmaPending, 19} 20 21#[derive(Debug, Clone, Encode, Decode)] 22pub struct DmaUnit { 23 oam_dma_source_address: u16, 24 oam_dma_m_cycles_remaining: u8, 25 oam_dma_pending: bool, 26 oam_dma_running: bool, 27 vram_dma_source_address: u16, 28 vram_dma_destination_address: u16, 29 vram_dma_length: u16, 30 vram_dma_state: VramDmaState, 31 last_ppu_mode: PpuMode, 32} 33 34impl DmaUnit { 35 pub fn new(hardware_mode: HardwareMode) -> Self { 36 Self { 37 oam_dma_source_address: match hardware_mode { 38 HardwareMode::Dmg => 0xFF00, 39 HardwareMode::Cgb => 0x0000, 40 }, 41 oam_dma_m_cycles_remaining: 0, 42 oam_dma_pending: false, 43 oam_dma_running: false, 44 vram_dma_source_address: 0, 45 vram_dma_destination_address: 0, 46 vram_dma_length: 0, 47 vram_dma_state: VramDmaState::default(), 48 last_ppu_mode: PpuMode::VBlank, 49 } 50 } 51 52 pub fn read_dma_register(&self) -> u8 { 53 self.oam_dma_source_address.msb() 54 } 55 56 pub fn write_dma_register(&mut self, value: u8) { 57 self.oam_dma_source_address = u16::from_le_bytes([0x00, value]); 58 59 // Writing to DMA register initiates OAM DMA, with a 1 M-cycle delay 60 self.oam_dma_pending = true; 61 62 log::trace!("DMA written: {value:02X}"); 63 log::trace!(" OAM DMA source address: {:04X}", self.oam_dma_source_address); 64 } 65 66 pub fn oam_dma_tick_m_cycle(&mut self, cartridge: &Cartridge, memory: &Memory, ppu: &mut Ppu) { 67 if self.oam_dma_pending { 68 self.oam_dma_m_cycles_remaining = OAM_DMA_M_CYCLES; 69 self.oam_dma_pending = false; 70 return; 71 } 72 73 if self.oam_dma_m_cycles_remaining == 0 { 74 self.oam_dma_running = false; 75 return; 76 } 77 78 self.oam_dma_running = true; 79 80 let source_addr = self.oam_dma_source_address; 81 let byte = match source_addr { 82 0x0000..=0x7FFF => cartridge.read_rom(source_addr), 83 0x8000..=0x9FFF => ppu.read_vram(source_addr), 84 0xA000..=0xBFFF => cartridge.read_ram(source_addr), 85 0xC000..=0xDFFF => memory.read_main_ram(source_addr), 86 // OAM, I/O registers, and HRAM are not readable from OAM DMA 87 0xE000..=0xFFFF => 0xFF, 88 }; 89 ppu.write_oam_for_dma(source_addr, byte); 90 91 log::trace!( 92 "Copied {byte:02X} to OAM from {source_addr:04X} to $FE{:02X}", 93 source_addr.lsb() 94 ); 95 96 self.oam_dma_source_address += 1; 97 self.oam_dma_m_cycles_remaining -= 1; 98 } 99 100 pub fn vram_dma_copy_byte(&mut self, cartridge: &Cartridge, memory: &Memory, ppu: &mut Ppu) { 101 let last_ppu_mode = self.last_ppu_mode; 102 self.last_ppu_mode = ppu.mode(); 103 104 match self.vram_dma_state { 105 VramDmaState::Idle => return, 106 VramDmaState::HDmaPending => { 107 if last_ppu_mode != PpuMode::HBlank && ppu.mode() == PpuMode::HBlank { 108 // Just reached HBlank; halt the CPU and copy 16 bytes 109 self.vram_dma_state = VramDmaState::HDmaActive { hblank_bytes_remaining: 16 }; 110 } else { 111 // HDMA is still running but the PPU is not in HBlank yet; wait 112 return; 113 } 114 } 115 _ => {} 116 } 117 118 let source_addr = self.vram_dma_source_address; 119 let byte = match self.vram_dma_source_address { 120 0x0000..=0x7FFF => cartridge.read_rom(source_addr), 121 0xA000..=0xBFFF => cartridge.read_ram(source_addr), 122 0xC000..=0xDFFF => memory.read_main_ram(source_addr), 123 // VRAM, OAM, I/O registers, and HRAM are not accessible from VRAM DMA 124 0x8000..=0x9FFF | 0xE000..=0xFFFF => 0xFF, 125 }; 126 127 ppu.write_vram(self.vram_dma_destination_address, byte); 128 129 self.vram_dma_source_address = self.vram_dma_source_address.wrapping_add(1); 130 self.vram_dma_destination_address = self.vram_dma_destination_address.wrapping_add(1); 131 self.vram_dma_length -= 1; 132 133 if let VramDmaState::HDmaActive { hblank_bytes_remaining } = &mut self.vram_dma_state { 134 *hblank_bytes_remaining -= 1; 135 if *hblank_bytes_remaining == 0 { 136 // Finished copying the 16-byte chunk; wait until next HBlank period 137 self.vram_dma_state = VramDmaState::HDmaPending; 138 } 139 } 140 141 // End VRAM DMA when length reaches 0 or destination address overflows to 0 142 if self.vram_dma_length == 0 || self.vram_dma_destination_address == 0x0000 { 143 log::trace!("VRAM DMA complete"); 144 145 self.vram_dma_state = VramDmaState::Idle; 146 } 147 } 148 149 pub fn oam_dma_in_progress(&self) -> bool { 150 self.oam_dma_running 151 } 152 153 pub fn vram_dma_active(&self) -> bool { 154 matches!(self.vram_dma_state, VramDmaState::GpDmaActive | VramDmaState::HDmaActive { .. }) 155 } 156 157 pub fn write_hdma1(&mut self, value: u8) { 158 // HDMA1: VRAM DMA source address, MSB 159 self.vram_dma_source_address.set_msb(value); 160 161 log::trace!("HDMA1 write, VRAM DMA source address MSB: {value:02X}"); 162 } 163 164 pub fn write_hdma2(&mut self, value: u8) { 165 // HDMA2: VRAM DMA source address, LSB 166 // Ignore lowest 4 bits 167 self.vram_dma_source_address.set_lsb(value & 0xF0); 168 169 log::trace!("HDMA2 write, VRAM DMA source address LSB: {value:02X}"); 170 } 171 172 pub fn write_hdma3(&mut self, value: u8) { 173 // HDMA3: VRAM DMA destination address, MSB 174 // Highest 3 bits are not used directly since destination is always VRAM, but they are used 175 // for an overflow check 176 self.vram_dma_destination_address.set_msb(value); 177 178 log::trace!("HDMA3 write, VRAM DMA destination address MSB: {value:02X}"); 179 } 180 181 pub fn write_hdma4(&mut self, value: u8) { 182 // HDMA4: VRAM DMA destination address, MSB 183 // Ignore lowest 4 bits 184 self.vram_dma_destination_address.set_lsb(value & 0xF0); 185 186 log::trace!("HDMA4 write, VRAM DMA destination address LSB: {value:02X}"); 187 } 188 189 pub fn read_hdma5(&self) -> u8 { 190 let length_bits = ((self.vram_dma_length / 16) as u8).wrapping_sub(1) & 0x7F; 191 let status_bit = u8::from(self.vram_dma_state == VramDmaState::Idle); 192 length_bits | (status_bit << 7) 193 } 194 195 pub fn write_hdma5(&mut self, value: u8, ppu_mode: PpuMode) { 196 // HDMA5: VRAM DMA length/mode + initiate VRAM DMA 197 let dma_length = 16 * u16::from((value & 0x7F) + 1); 198 199 if self.vram_dma_state != VramDmaState::Idle { 200 // HDMA5 writes can alter the length of an in-progress HDMA 201 // NASCAR 2000 (with bit 7) 202 // liji32/samesuite/dma/hdma_lcd_off and 203 // liji32/samesuite/dma/hdma_mode0 (without bit 7) 204 // depend on this behavior 205 self.vram_dma_length = dma_length; 206 207 if !value.bit(7) { 208 // Writing HDMA5 with bit 7 clear while an HDMA is in progress immediately cancels it 209 self.vram_dma_state = VramDmaState::Idle; 210 } 211 212 return; 213 } 214 215 self.vram_dma_length = dma_length; 216 217 self.vram_dma_state = if value.bit(7) { 218 // HDMA 219 if ppu_mode == PpuMode::HBlank { 220 VramDmaState::HDmaActive { hblank_bytes_remaining: 16 } 221 } else { 222 VramDmaState::HDmaPending 223 } 224 } else { 225 // GPDMA 226 VramDmaState::GpDmaActive 227 }; 228 229 log::trace!("HDMA5 write, VRAM DMA initiated: {value:02X}"); 230 log::trace!(" VRAM DMA length: {:04X}", self.vram_dma_length); 231 log::trace!(" VRAM DMA state: {:?}", self.vram_dma_state); 232 } 233}