SNES general-purpose DMA and HBlank DMA code
9const CHANNELS: usize = 8;
Bus B (8-bit) is mapped to $2100-$21FF in Bus A (24-bit)
12const BUS_B_BASE_ADDRESS: u32 = 0x002100;
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 15enum HDmaState { 16 Idle, 17 ReloadPending, 18 Pending, 19 Transfer { channel: u8 }, 20} 21 22#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 23enum GpDmaState { 24 Idle, 25 Pending, 26 Transfer { channel: u8, bytes_copied: u16 }, 27} 28 29#[derive(Debug, Clone, Encode, Decode)] 30pub struct DmaUnit { 31 hdma: HDmaState, 32 gpdma: GpDmaState, 33 hdma_do_transfer: [bool; CHANNELS], 34 last_scanline_mclk: u64, 35 dma_active: bool, 36 dma_start_mclk: u64, 37} 38 39impl DmaUnit { 40 pub fn new() -> Self { 41 Self { 42 hdma: HDmaState::Idle, 43 gpdma: GpDmaState::Idle, 44 hdma_do_transfer: [false; CHANNELS], 45 last_scanline_mclk: 0, 46 dma_active: false, 47 dma_start_mclk: 0, 48 } 49 } 50 51 pub fn tick( 52 &mut self, 53 bus: &mut Bus<'_>, 54 total_master_cycles: u64, 55 next_cpu_cycle_mclk: impl Fn(&Bus<'_>) -> u64 + Copy, 56 ) -> DmaStatus { 57 let last_scanline_mclk = self.last_scanline_mclk; 58 self.last_scanline_mclk = bus.ppu.scanline_master_cycles(); 59 60 // HDMA takes priority over GPDMA if both are active 61 if let Some(status) = 62 self.tick_hdma(bus, last_scanline_mclk, total_master_cycles, next_cpu_cycle_mclk) 63 { 64 return status; 65 } 66 67 self.tick_gpdma(bus, total_master_cycles, next_cpu_cycle_mclk) 68 } 69 70 fn tick_hdma( 71 &mut self, 72 bus: &mut Bus<'_>, 73 last_scanline_mclk: u64, 74 total_master_cycles: u64, 75 next_cpu_cycle_mclk: impl Fn(&Bus<'_>) -> u64, 76 ) -> Option<DmaStatus> { 77 match self.hdma { 78 HDmaState::Idle => { 79 if Self::check_hdma_reload(bus, last_scanline_mclk) { 80 if self.dma_active { 81 return self.hdma_reload(bus, total_master_cycles, next_cpu_cycle_mclk); 82 } 83 self.hdma = HDmaState::ReloadPending; 84 } else if Self::check_hdma_start(bus, last_scanline_mclk) { 85 if self.dma_active { 86 return self.hdma_start(bus, total_master_cycles); 87 } 88 self.hdma = HDmaState::Pending; 89 } 90 91 None 92 } 93 HDmaState::ReloadPending => { 94 self.hdma_reload(bus, total_master_cycles, next_cpu_cycle_mclk) 95 } 96 HDmaState::Pending => self.hdma_start(bus, total_master_cycles), 97 HDmaState::Transfer { channel } => Some(self.hdma_progress_transfer( 98 channel.into(), 99 bus, 100 total_master_cycles, 101 next_cpu_cycle_mclk, 102 )), 103 } 104 } 105 106 fn check_hdma_reload(bus: &mut Bus<'_>, last_scanline_mclk: u64) -> bool { 107 // HDMA reload begins at H=4 V=0 108 const RELOAD_SCANLINE_MCLK: u64 = 4 * 4; 109 110 let scanline_mclk = bus.ppu.scanline_master_cycles(); 111 bus.ppu.scanline() == 0 112 && scanline_mclk >= RELOAD_SCANLINE_MCLK 113 && (last_scanline_mclk < RELOAD_SCANLINE_MCLK || last_scanline_mclk > scanline_mclk) 114 } 115 116 fn check_hdma_start(bus: &mut Bus<'_>, last_scanline_mclk: u64) -> bool { 117 // HDMA begins at H=276, V in 0-224 118 const START_SCANLINE_MCLK: u64 = 276 * 4; 119 120 if !Self::any_hdma_active(bus) { 121 return false; 122 } 123 124 !bus.ppu.vblank_flag() 125 && bus.ppu.scanline_master_cycles() >= START_SCANLINE_MCLK 126 && last_scanline_mclk < START_SCANLINE_MCLK 127 } 128 129 fn hdma_channel_active(bus: &Bus<'_>, channel: usize) -> bool { 130 // HDMA channels go inactive for the rest of the frame when line counter is loaded with 0 131 bus.cpu_registers.active_hdma_channels[channel] 132 && bus.cpu_registers.hdma_line_counter[channel] != 0 133 } 134 135 fn any_hdma_active(bus: &mut Bus<'_>) -> bool { 136 (0..CHANNELS).any(|channel| Self::hdma_channel_active(bus, channel)) 137 } 138 139 fn hdma_reload( 140 &mut self, 141 bus: &mut Bus<'_>, 142 total_master_cycles: u64, 143 next_cpu_cycle_mclk: impl Fn(&Bus<'_>) -> u64, 144 ) -> Option<DmaStatus> { 145 if !bus.cpu_registers.active_hdma_channels.into_iter().any(|b| b) { 146 // Reset all do_transfer flags at reload time when no HDMA channels are active 147 // Super Ghouls 'N Ghosts depends on this because it enables HDMA mid-frame 148 self.hdma_do_transfer.fill(false); 149 self.hdma = HDmaState::Idle; 150 return None; 151 } 152 153 // 8-cycle overhead for HDMA reload if any channels are active 154 let mut cycles = 8 + self.start_dma_if_inactive(total_master_cycles); 155 156 for channel in 0..CHANNELS { 157 if !bus.cpu_registers.active_hdma_channels[channel] { 158 self.hdma_do_transfer[channel] = false; 159 continue; 160 } 161 162 log::trace!("Reloading HDMA channel {channel}"); 163 164 // Active HDMA cancels any active GPDMA on the same channel 165 bus.cpu_registers.active_gpdma_channels[channel] = false; 166 167 bus.cpu_registers.hdma_table_current_address[channel] = 168 bus.cpu_registers.gpdma_current_address[channel]; 169 170 // 8 cycles for each active channel, +16 if in indirect mode 171 cycles += 8 + self.hdma_reload_line_counter(bus, channel); 172 } 173 174 self.hdma = HDmaState::Idle; 175 cycles += self.end_dma_if_done(bus, total_master_cycles + cycles, next_cpu_cycle_mclk); 176 177 Some(DmaStatus::InProgress { master_cycles_elapsed: cycles }) 178 } 179 180 #[must_use] 181 fn hdma_reload_line_counter(&mut self, bus: &mut Bus<'_>, channel: usize) -> u64 { 182 log::trace!("Reloading HDMA line counter for channel {channel}"); 183 184 let bank = bus.cpu_registers.dma_bank[channel]; 185 let mut current_addr = bus.cpu_registers.hdma_table_current_address[channel]; 186 187 log::trace!(" HDMA table bank={bank:02X}, current address={current_addr:04X}"); 188 189 let line_counter = bus.read(u24(bank, current_addr)); 190 bus.cpu_registers.hdma_line_counter[channel] = line_counter; 191 current_addr = current_addr.wrapping_add(1); 192 193 log::trace!(" HDMA line counter: {line_counter:02X}"); 194 195 let mut cycles = 0; 196 if bus.cpu_registers.hdma_addressing_mode[channel] == HdmaAddressingMode::Indirect { 197 cycles = 16; 198 199 let address_lsb = bus.read(u24(bank, current_addr)); 200 current_addr = current_addr.wrapping_add(1); 201 let address_msb = bus.read(u24(bank, current_addr)); 202 current_addr = current_addr.wrapping_add(1); 203 204 // Same register is used for GPDMA byte counter and HDMA indirect address 205 let address = u16::from_le_bytes([address_lsb, address_msb]); 206 bus.cpu_registers.gpdma_byte_counter[channel] = address; 207 208 log::trace!( 209 " HDMA indirect bank = {:02X}, indirect address: {address:04X}", 210 bus.cpu_registers.hdma_indirect_bank[channel] 211 ); 212 } 213 214 bus.cpu_registers.hdma_table_current_address[channel] = current_addr; 215 self.hdma_do_transfer[channel] = true; 216 217 cycles 218 } 219 220 fn hdma_start(&mut self, bus: &mut Bus<'_>, total_master_cycles: u64) -> Option<DmaStatus> { 221 if !Self::any_hdma_active(bus) { 222 self.hdma = HDmaState::Idle; 223 return None; 224 } 225 226 // 8-cycle overhead at HDMA start if any channels are active 227 let cycles = 8 + self.start_dma_if_inactive(total_master_cycles); 228 self.hdma = HDmaState::Transfer { channel: 0 }; 229 230 Some(DmaStatus::InProgress { master_cycles_elapsed: cycles }) 231 } 232 233 fn hdma_progress_transfer( 234 &mut self, 235 mut channel: usize, 236 bus: &mut Bus<'_>, 237 total_master_cycles: u64, 238 next_cpu_cycle_mclk: impl Fn(&Bus<'_>) -> u64, 239 ) -> DmaStatus { 240 while channel < CHANNELS { 241 if !Self::hdma_channel_active(bus, channel) { 242 channel += 1; 243 continue; 244 } 245 246 // Active HDMA cancels any active GPDMA on the same channel 247 bus.cpu_registers.active_gpdma_channels[channel] = false; 248 249 if !self.hdma_do_transfer[channel] { 250 channel += 1; 251 continue; 252 } 253 254 // 8 cycles per byte copied (up to 4 bytes / 32 cycles) 255 let cycles = hdma_copy_unit(bus, channel); 256 self.hdma = HDmaState::Transfer { channel: (channel + 1) as u8 }; 257 return DmaStatus::InProgress { master_cycles_elapsed: cycles }; 258 } 259 260 self.hdma_finish(bus, total_master_cycles, next_cpu_cycle_mclk) 261 } 262 263 fn hdma_finish( 264 &mut self, 265 bus: &mut Bus<'_>, 266 total_master_cycles: u64, 267 next_cpu_cycle_mclk: impl Fn(&Bus<'_>) -> u64, 268 ) -> DmaStatus { 269 let mut cycles = 0; 270 271 for channel in 0..CHANNELS { 272 if !Self::hdma_channel_active(bus, channel) { 273 continue; 274 } 275 276 let line_counter = bus.cpu_registers.hdma_line_counter[channel].wrapping_sub(1); 277 bus.cpu_registers.hdma_line_counter[channel] = line_counter; 278 279 // Highest bit of line counter functions as a repeat flag 280 self.hdma_do_transfer[channel] = line_counter.bit(7); 281 282 // 8 cycles per active channel 283 // +16 for each channel in indirect mode that reloads its line counter 284 cycles += 8; 285 286 // Line counter reloads when lowest 7 bits are 0 287 if line_counter & 0x7F == 0 { 288 cycles += self.hdma_reload_line_counter(bus, channel); 289 } 290 } 291 292 self.hdma = HDmaState::Idle; 293 cycles += self.end_dma_if_done(bus, total_master_cycles + cycles, next_cpu_cycle_mclk); 294 295 debug_assert_ne!(cycles, 0); 296 DmaStatus::InProgress { master_cycles_elapsed: cycles } 297 } 298 299 fn tick_gpdma( 300 &mut self, 301 bus: &mut Bus<'_>, 302 total_master_cycles: u64, 303 next_cpu_cycle_mclk: impl Fn(&Bus<'_>) -> u64, 304 ) -> DmaStatus { 305 match self.gpdma { 306 GpDmaState::Idle => { 307 if bus.cpu_registers.active_gpdma_channels.into_iter().any(|b| b) { 308 self.gpdma = GpDmaState::Pending; 309 } 310 DmaStatus::None 311 } 312 GpDmaState::Pending => self.gpdma_start(bus, total_master_cycles), 313 GpDmaState::Transfer { channel, bytes_copied } => self.gpdma_progress_transfer( 314 channel.into(), 315 bytes_copied, 316 bus, 317 total_master_cycles, 318 next_cpu_cycle_mclk, 319 ), 320 } 321 } 322 323 fn gpdma_start(&mut self, bus: &mut Bus<'_>, total_master_cycles: u64) -> DmaStatus { 324 if !bus.cpu_registers.active_gpdma_channels.into_iter().any(|b| b) { 325 self.gpdma = GpDmaState::Idle; 326 return DmaStatus::None; 327 } 328 329 if log::log_enabled!(log::Level::Trace) { 330 gpdma_start_log(bus); 331 } 332 333 // 8-cycle overhead when starting GPDMA 334 let cycles = 8 + self.start_dma_if_inactive(total_master_cycles); 335 self.gpdma = GpDmaState::Transfer { channel: 0, bytes_copied: 0 }; 336 DmaStatus::InProgress { master_cycles_elapsed: cycles } 337 } 338 339 fn gpdma_progress_transfer( 340 &mut self, 341 mut channel: usize, 342 mut bytes_copied: u16, 343 bus: &mut Bus<'_>, 344 total_master_cycles: u64, 345 next_cpu_cycle_mclk: impl Fn(&Bus<'_>) -> u64, 346 ) -> DmaStatus { 347 while channel < CHANNELS { 348 if !bus.cpu_registers.active_gpdma_channels[channel] { 349 channel += 1; 350 bytes_copied = 0; 351 continue; 352 } 353 354 // 8 cycles per byte 355 let mut cycles = 8; 356 357 if bytes_copied == 0 { 358 // 8-cycle overhead per active channel 359 cycles += 8; 360 361 // Notify coprocessor when GPDMA starts a new channel; needed by S-DD1 and SA-1 362 let start_address = u24( 363 bus.cpu_registers.dma_bank[channel], 364 bus.cpu_registers.gpdma_current_address[channel], 365 ); 366 bus.memory.notify_dma_start(channel as u8, start_address); 367 } 368 369 gpdma_copy_byte(bus, channel, bytes_copied); 370 371 if bus.cpu_registers.gpdma_byte_counter[channel] == 0 { 372 bus.cpu_registers.active_gpdma_channels[channel] = false; 373 channel += 1; 374 bytes_copied = 0; 375 } else { 376 bytes_copied = bytes_copied.wrapping_add(1); 377 } 378 379 self.gpdma = GpDmaState::Transfer { channel: channel as u8, bytes_copied }; 380 return DmaStatus::InProgress { master_cycles_elapsed: cycles }; 381 } 382 383 bus.memory.notify_dma_end(); 384 385 self.gpdma = GpDmaState::Idle; 386 let cycles = self.end_dma_if_done(bus, total_master_cycles, next_cpu_cycle_mclk); 387 DmaStatus::InProgress { master_cycles_elapsed: cycles } 388 } 389 390 #[must_use] 391 fn start_dma_if_inactive(&mut self, total_master_cycles: u64) -> u64 { 392 if self.dma_active { 393 return 0; 394 } 395 396 self.dma_active = true; 397 self.dma_start_mclk = total_master_cycles; 398 399 // DMA can only begin at a multiple of 8 cycles since power-on 400 8 - (total_master_cycles % 8) 401 } 402 403 #[must_use] 404 fn end_dma_if_done( 405 &mut self, 406 bus: &mut Bus<'_>, 407 total_master_cycles: u64, 408 next_cpu_cycle_mclk: impl Fn(&Bus<'_>) -> u64, 409 ) -> u64 { 410 if self.hdma != HDmaState::Idle || self.gpdma != GpDmaState::Idle { 411 return 0; 412 } 413 414 self.dma_active = false; 415 416 // After DMA ends, must wait until a whole number of CPU cycles have elapsed since DMA began 417 // CPU clock timing is based on the cycle that will execute after DMA ends 418 let dma_elapsed_mclk = total_master_cycles - self.dma_start_mclk; 419 let next_cpu_cycle_mclk = next_cpu_cycle_mclk(bus); 420 next_cpu_cycle_mclk - (dma_elapsed_mclk % next_cpu_cycle_mclk) 421 } 422} 423 424#[derive(Debug, Clone, Copy, PartialEq, Eq)] 425pub enum DmaStatus { 426 None, 427 InProgress { master_cycles_elapsed: u64 }, 428} 429 430fn u24(bank: u8, offset: u16) -> u32 { 431 (u32::from(bank) << 16) | u32::from(offset) 432}
Returns number of cycles (8 * bytes copied)
435fn hdma_copy_unit(bus: &mut Bus<'_>, channel: usize) -> u64 { 436 let (bus_a_bank, mut bus_a_offset) = match bus.cpu_registers.hdma_addressing_mode[channel] { 437 HdmaAddressingMode::Direct => ( 438 bus.cpu_registers.dma_bank[channel], 439 bus.cpu_registers.hdma_table_current_address[channel], 440 ), 441 HdmaAddressingMode::Indirect => ( 442 bus.cpu_registers.hdma_indirect_bank[channel], 443 bus.cpu_registers.gpdma_byte_counter[channel], 444 ), 445 }; 446 447 let bus_b_address = bus.cpu_registers.dma_bus_b_address[channel]; 448 let direction = bus.cpu_registers.dma_direction[channel]; 449 450 log::trace!( 451 "HDMA channel {channel}: ABank={bus_a_bank:02X}, AAddr={bus_a_offset:04X}, BAddr={bus_b_address:02X}, Unit={}, Direction={direction:?}, AddrMode={:?}", 452 bus.cpu_registers.dma_transfer_unit[channel], 453 bus.cpu_registers.hdma_addressing_mode[channel] 454 ); 455 456 let bytes_copied = match bus.cpu_registers.dma_transfer_unit[channel] { 457 0 => { 458 // 1 byte, 1 register 459 hdma_copy_byte(direction, bus, bus_a_bank, &mut bus_a_offset, bus_b_address); 460 461 1 462 } 463 1 => { 464 // 2 bytes, 2 registers 465 hdma_copy_byte(direction, bus, bus_a_bank, &mut bus_a_offset, bus_b_address); 466 hdma_copy_byte( 467 direction, 468 bus, 469 bus_a_bank, 470 &mut bus_a_offset, 471 bus_b_address.wrapping_add(1), 472 ); 473 474 2 475 } 476 2 | 6 => { 477 // 2 bytes, 1 register 478 hdma_copy_byte(direction, bus, bus_a_bank, &mut bus_a_offset, bus_b_address); 479 hdma_copy_byte(direction, bus, bus_a_bank, &mut bus_a_offset, bus_b_address); 480 481 2 482 } 483 3 | 7 => { 484 // 4 bytes, 2 registers (serial) 485 hdma_copy_byte(direction, bus, bus_a_bank, &mut bus_a_offset, bus_b_address); 486 hdma_copy_byte(direction, bus, bus_a_bank, &mut bus_a_offset, bus_b_address); 487 hdma_copy_byte( 488 direction, 489 bus, 490 bus_a_bank, 491 &mut bus_a_offset, 492 bus_b_address.wrapping_add(1), 493 ); 494 hdma_copy_byte( 495 direction, 496 bus, 497 bus_a_bank, 498 &mut bus_a_offset, 499 bus_b_address.wrapping_add(1), 500 ); 501 502 4 503 } 504 4 => { 505 // 4 bytes, 4 registers 506 for i in 0..4 { 507 hdma_copy_byte( 508 direction, 509 bus, 510 bus_a_bank, 511 &mut bus_a_offset, 512 bus_b_address.wrapping_add(i), 513 ); 514 } 515 516 4 517 } 518 5 => { 519 // 4 bytes, 2 registers (alternating) 520 for _ in 0..2 { 521 hdma_copy_byte(direction, bus, bus_a_bank, &mut bus_a_offset, bus_b_address); 522 hdma_copy_byte( 523 direction, 524 bus, 525 bus_a_bank, 526 &mut bus_a_offset, 527 bus_b_address.wrapping_add(1), 528 ); 529 } 530 531 4 532 } 533 _ => panic!("invalid DMA transfer unit: {}", bus.cpu_registers.dma_transfer_unit[channel]), 534 }; 535 536 log::trace!(" Copied {bytes_copied} bytes, new AAddress: {bus_a_offset:04X}"); 537 538 // Write back incremented bus A address 539 match bus.cpu_registers.hdma_addressing_mode[channel] { 540 HdmaAddressingMode::Direct => { 541 bus.cpu_registers.hdma_table_current_address[channel] = bus_a_offset; 542 } 543 HdmaAddressingMode::Indirect => { 544 bus.cpu_registers.gpdma_byte_counter[channel] = bus_a_offset; 545 } 546 } 547 548 // Each byte copy takes 8 cycles 549 8 * bytes_copied 550}
552fn hdma_copy_byte( 553 direction: DmaDirection, 554 bus: &mut Bus<'_>, 555 bus_a_bank: u8, 556 bus_a_offset: &mut u16, 557 bus_b_address: u8, 558) { 559 let bus_a_full_address = u24(bus_a_bank, *bus_a_offset); 560 *bus_a_offset = bus_a_offset.wrapping_add(1); 561 562 let bus_b_full_address = BUS_B_BASE_ADDRESS | u32::from(bus_b_address); 563 564 match direction { 565 DmaDirection::AtoB => { 566 let byte = dma_read_bus_a(bus, bus_a_full_address); 567 bus.apply_write(bus_b_full_address, byte); 568 } 569 DmaDirection::BtoA => { 570 let byte = bus.read(bus_b_full_address); 571 dma_write_bus_a(bus, bus_a_full_address, byte); 572 } 573 } 574} 575 576fn dma_read_bus_a(bus: &mut Bus<'_>, bus_a_address: u32) -> u8 { 577 let bank = (bus_a_address >> 16) & 0xFF; 578 let offset = bus_a_address & 0xFFFF; 579 match (bank, offset) { 580 // DMA cannot read bus B or DMA registers through bus A 581 // Krusty's Super Fun House depends on this or else it will write incorrect BG color 582 // palettes to CGRAM 583 (0x00..=0x3F | 0x80..=0xBF, 0x2100..=0x21FF | 0x4300..=0x43FF) => bus.memory.cpu_open_bus(), 584 _ => bus.read(bus_a_address), 585 } 586} 587 588fn dma_write_bus_a(bus: &mut Bus<'_>, bus_a_address: u32, value: u8) { 589 let bank = (bus_a_address >> 16) & 0xFF; 590 let offset = bus_a_address & 0xFFFF; 591 match (bank, offset) { 592 // DMA cannot write to bus B or DMA registers through bus A 593 (0x00..=0x3F | 0x80..=0xBF, 0x2100..=0x21FF | 0x4300..=0x43FF) => {} 594 _ => bus.apply_write(bus_a_address, value), 595 } 596} 597 598fn gpdma_copy_byte(bus: &mut Bus<'_>, channel: usize, bytes_copied: u16) { 599 let bus_a_bank = bus.cpu_registers.dma_bank[channel]; 600 let bus_a_address = bus.cpu_registers.gpdma_current_address[channel]; 601 let bus_a_full_address = (u32::from(bus_a_bank) << 16) | u32::from(bus_a_address); 602 603 // Transfer units (0-7): 604 // 0: 1 byte, 1 register 605 // 1: 2 bytes, 2 registers 606 // 2: 2 bytes, 1 register (functionally same as 0 for GPDMA) 607 // 3: 4 bytes, 2 registers (xx, xx, xx+1, xx+1) 608 // 4: 4 bytes, 4 registers 609 // 5: 4 bytes, 2 registers alternating (xx, xx+1, xx, xx+1) (functionally same as 1 for GPDMA) 610 // 6: Same as 2 611 // 7: Same as 3 612 let transfer_unit = bus.cpu_registers.dma_transfer_unit[channel]; 613 let bus_b_adjustment = match transfer_unit { 614 0 | 2 | 6 => 0, 615 1 | 5 => (bytes_copied & 0x01) as u8, 616 3 | 7 => ((bytes_copied >> 1) & 0x01) as u8, 617 4 => (bytes_copied & 0x03) as u8, 618 _ => panic!("invalid transfer unit: {transfer_unit}"), 619 }; 620 621 let bus_b_address = BUS_B_BASE_ADDRESS 622 | u32::from(bus.cpu_registers.dma_bus_b_address[channel].wrapping_add(bus_b_adjustment)); 623 624 match bus.cpu_registers.dma_direction[channel] { 625 DmaDirection::AtoB => { 626 let byte = dma_read_bus_a(bus, bus_a_full_address); 627 bus.apply_write(bus_b_address, byte); 628 } 629 DmaDirection::BtoA => { 630 let byte = bus.read(bus_b_address); 631 dma_write_bus_a(bus, bus_a_full_address, byte); 632 } 633 } 634 635 match bus.cpu_registers.dma_increment_mode[channel] { 636 DmaIncrementMode::Fixed0 | DmaIncrementMode::Fixed1 => {} 637 DmaIncrementMode::Increment => { 638 bus.cpu_registers.gpdma_current_address[channel] = bus_a_address.wrapping_add(1); 639 } 640 DmaIncrementMode::Decrement => { 641 bus.cpu_registers.gpdma_current_address[channel] = bus_a_address.wrapping_sub(1); 642 } 643 } 644 645 bus.cpu_registers.gpdma_byte_counter[channel] = 646 bus.cpu_registers.gpdma_byte_counter[channel].wrapping_sub(1); 647} 648 649fn gpdma_start_log(bus: &Bus<'_>) { 650 log::trace!("GPDMA started"); 651 for (i, active) in bus.cpu_registers.active_gpdma_channels.iter().copied().enumerate() { 652 if !active { 653 continue; 654 } 655 656 log::trace!(" Channel {i} bus A bank: {:02X}", bus.cpu_registers.dma_bank[i]); 657 log::trace!( 658 " Channel {i} bus A address: {:04X}", 659 bus.cpu_registers.gpdma_current_address[i] 660 ); 661 log::trace!(" Channel {i} bus B address: {:02X}", bus.cpu_registers.dma_bus_b_address[i]); 662 log::trace!(" Channel {i} byte counter: {:04X}", bus.cpu_registers.gpdma_byte_counter[i]); 663 log::trace!(" Channel {i} direction: {:?}", bus.cpu_registers.dma_direction[i]); 664 log::trace!(" Channel {i} transfer unit: {}", bus.cpu_registers.dma_transfer_unit[i]); 665 log::trace!(" Channel {i} increment mode: {:?}", bus.cpu_registers.dma_increment_mode[i]); 666 } 667}