dma.rsannotateddma.rssource667 lines · 22.9 KB · raw

SNES general-purpose DMA and HBlank DMA code

3use crate::bus::Bus;
4use crate::memory::{DmaDirection, DmaIncrementMode, HdmaAddressingMode};
5use bincode::{Decode, Encode};
6use jgenesis_common::num::GetBit;
7use wdc65816_emu::traits::BusInterface;
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}