cdc.rsannotatedcdc.rssource800 lines · 28.0 KB · raw
1//! Sanyo LC8951 CD-ROM decoder & error correction chip, which Sega CD documentation refers to as
2//! the CDC
3
4use crate::ScdCpu;
5use crate::memory;
6use crate::rf5c164::Rf5c164;
7use crate::wordram::{WordRam, WordRamMode};
8use bincode::{Decode, Encode};
9use jgenesis_common::boxedarray::BoxedByteArray;
10use jgenesis_common::debug::{DebugBytesView, DebugMemoryView};
11use jgenesis_common::num::{GetBit, U16Ext};
12
13// The register address is supposedly 4 bits, but internally it's actually 5 bits
14// Values $10-$1F are effectively unused
15pub const REGISTER_ADDRESS_MASK: u8 = 0x1F;
16
17const BUFFER_RAM_LEN: usize = 16 * 1024;
18const BUFFER_RAM_ADDRESS_MASK: u16 = (1 << 14) - 1;
19
20const DATA_TRACK_HEADER_LEN: u16 = 12;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
23pub enum DeviceDestination {
24    None(u8),
25    MainCpuRegister,
26    SubCpuRegister,
27    PrgRam,
28    WordRam,
29    Pcm,
30}
31
32impl DeviceDestination {
33    pub fn to_bits(self) -> u8 {
34        match self {
35            Self::None(bits) => bits,
36            Self::MainCpuRegister => 0b010,
37            Self::SubCpuRegister => 0b011,
38            Self::Pcm => 0b100,
39            Self::PrgRam => 0b101,
40            Self::WordRam => 0b111,
41        }
42    }
43
44    pub fn from_bits(bits: u8) -> Self {
45        match bits & 0x07 {
46            0b010 => Self::MainCpuRegister,
47            0b011 => Self::SubCpuRegister,
48            0b100 => Self::Pcm,
49            0b101 => Self::PrgRam,
50            0b111 => Self::WordRam,
51            bits @ (0b000 | 0b001 | 0b110) => {
52                // Prohibited patterns; unset destination
53                Self::None(bits)
54            }
55            _ => unreachable!("value & 0x07 is always <= 0x07"),
56        }
57    }
58
59    fn is_dma(self) -> bool {
60        matches!(self, Self::Pcm | Self::PrgRam | Self::WordRam)
61    }
62
63    fn is_host_data(self) -> bool {
64        matches!(self, Self::MainCpuRegister | Self::SubCpuRegister)
65    }
66}
67
68impl Default for DeviceDestination {
69    fn default() -> Self {
70        Self::None(0b000)
71    }
72}
73
74pub struct RchipDmaArgs<'a> {
75    pub word_ram: &'a mut WordRam,
76    pub prg_ram: &'a mut [u16; memory::PRG_RAM_LEN_WORDS],
77    pub prg_ram_accessible: bool,
78    pub pcm: &'a mut Rf5c164,
79}
80
81impl RchipDmaArgs<'_> {
82    pub fn reborrow(&mut self) -> RchipDmaArgs<'_> {
83        RchipDmaArgs {
84            word_ram: &mut *self.word_ram,
85            prg_ram: &mut *self.prg_ram,
86            prg_ram_accessible: self.prg_ram_accessible,
87            pcm: &mut *self.pcm,
88        }
89    }
90}
91
92// The LC8951, which the documentation describes as a "Real-Time Error Correction and Host Interface
93// Processor".
94//
95// Sega CD documentation refers to this chip as the CDC.
96#[derive(Debug, Clone, Encode, Decode)]
97pub struct Rchip {
98    buffer_ram: BoxedByteArray<BUFFER_RAM_LEN>,
99    device_destination: DeviceDestination,
100    host_data_buffer: Option<u16>,
101    register_address: u8,
102    dma_address: u32,
103    decoder_enabled: bool,
104    decoder_writes_enabled: bool,
105    decoded_first_written_block: bool,
106    decoded_last_75hz_cycle: bool,
107    cycles_44100hz_since_decode: u32,
108    data_out_enabled: bool,
109    data_transfer_in_progress: bool,
110    end_of_data_transfer: bool,
111    subheader_data_enabled: bool,
112    header_data: [u8; 4],
113    subheader_data: [u8; 4],
114    write_address: u16,
115    block_pointer: u16,
116    data_byte_counter: u16,
117    data_address_counter: u16,
118    transfer_end_interrupt_enabled: bool,
119    transfer_end_interrupt_pending: bool,
120    decoder_interrupt_enabled: bool,
121    decoder_interrupt_pending: bool,
122    // There needs to be a separate flag specifically for sub CPU INT5 because some games will fail
123    // to boot if the sub CPU acknowledging a level 5 interrupt does not clear INT5; these include
124    // Snatcher, Batman Returns, and Robo Aleste
125    scd_interrupt_flag: bool,
126}
127
128impl Rchip {
129    pub(super) fn new() -> Self {
130        Self {
131            buffer_ram: BoxedByteArray::new(),
132            device_destination: DeviceDestination::default(),
133            host_data_buffer: None,
134            register_address: 0,
135            dma_address: 0,
136            decoder_enabled: false,
137            decoder_writes_enabled: false,
138            decoded_first_written_block: false,
139            decoded_last_75hz_cycle: false,
140            cycles_44100hz_since_decode: 0,
141            data_out_enabled: false,
142            data_transfer_in_progress: false,
143            end_of_data_transfer: true,
144            subheader_data_enabled: false,
145            header_data: [0; 4],
146            subheader_data: [0; 4],
147            write_address: 0,
148            block_pointer: 0,
149            data_byte_counter: 0,
150            data_address_counter: 0,
151            transfer_end_interrupt_enabled: true,
152            transfer_end_interrupt_pending: false,
153            decoder_interrupt_enabled: true,
154            decoder_interrupt_pending: false,
155            scd_interrupt_flag: false,
156        }
157    }
158
159    pub fn device_destination(&self) -> DeviceDestination {
160        self.device_destination
161    }
162
163    pub fn set_device_destination(&mut self, device_destination: DeviceDestination) {
164        // Abort any in-progress data transfer and reset DMA controller
165        self.dma_address = 0;
166
167        // Writing device destination always clears EDT
168        self.end_of_data_transfer = false;
169
170        log::trace!("CDC device destination set to {device_destination:?}");
171
172        self.device_destination = device_destination;
173    }
174
175    pub fn read_host_data(&mut self, cpu: ScdCpu) -> u16 {
176        if !self.data_transfer_in_progress
177            || (cpu == ScdCpu::Main
178                && self.device_destination != DeviceDestination::MainCpuRegister)
179            || (cpu == ScdCpu::Sub && self.device_destination != DeviceDestination::SubCpuRegister)
180        {
181            // Invalid host data read; return whatever is currently in the buffer but don't refill it
182            return self.host_data_buffer.unwrap_or(0);
183        }
184
185        log::trace!("Host data read by {cpu:?}");
186
187        let Some(host_data) = self.host_data_buffer.take() else {
188            log::trace!("  Host data buffer is empty");
189            return 0x0000;
190        };
191
192        if self.end_of_data_transfer {
193            log::trace!("  Host data transfer has ended");
194            self.data_transfer_in_progress = false;
195        } else {
196            self.populate_host_data_buffer();
197        }
198
199        log::trace!("  Returning {host_data:04X}");
200
201        host_data
202    }
203
204    pub fn write_host_data(&mut self, cpu: ScdCpu) {
205        log::trace!("Host data write by {cpu:?}");
206
207        // Writing to the host data register effectively skips the word
208        if !self.data_transfer_in_progress
209            || (cpu == ScdCpu::Main
210                && self.device_destination != DeviceDestination::MainCpuRegister)
211            || (cpu == ScdCpu::Sub && self.device_destination != DeviceDestination::SubCpuRegister)
212        {
213            return;
214        }
215
216        if self.end_of_data_transfer {
217            log::trace!("  Host data transfer has ended");
218            self.data_transfer_in_progress = false;
219        } else {
220            self.populate_host_data_buffer();
221        }
222    }
223
224    fn populate_host_data_buffer(&mut self) {
225        let msb_addr = self.data_address_counter;
226        let lsb_addr = (self.data_address_counter + 1) & BUFFER_RAM_ADDRESS_MASK;
227        let host_data = u16::from_be_bytes([
228            self.buffer_ram[msb_addr as usize],
229            self.buffer_ram[lsb_addr as usize],
230        ]);
231        self.host_data_buffer = Some(host_data);
232        self.data_address_counter = (self.data_address_counter + 2) & BUFFER_RAM_ADDRESS_MASK;
233
234        let (new_byte_counter, overflowed) = self.data_byte_counter.overflowing_sub(2);
235        self.data_byte_counter = new_byte_counter;
236        if overflowed {
237            self.end_dma_transfer();
238        }
239
240        log::trace!(
241            "Host data read performed; data={host_data:04X}, DBC={new_byte_counter:04X}, ended={overflowed}"
242        );
243    }
244
245    fn end_dma_transfer(&mut self) {
246        self.end_of_data_transfer = true;
247        self.set_transfer_end_interrupt_flag();
248    }
249
250    pub fn register_address(&self) -> u8 {
251        self.register_address
252    }
253
254    pub fn set_register_address(&mut self, register_address: u8) {
255        self.register_address = register_address;
256    }
257
258    pub fn read_register(&mut self) -> u8 {
259        let value = match self.register_address {
260            0 => {
261                // COMIN (Command Input)
262                log::trace!("COMIN read");
263
264                // Not used by Sega CD; return a dummy value
265                0xFF
266            }
267            1 => {
268                // IFSTAT (Host Interface Status)
269                // Hardcode CMDI, STBSY, STEN, and bit 4 (unused) to 1
270                log::trace!("IFSTAT read");
271
272                // TODO do DTBSY and DTEN need to be different values?
273                0x95 | (u8::from(!self.transfer_end_interrupt_pending) << 6)
274                    | (u8::from(!self.decoder_interrupt_pending) << 5)
275                    | (u8::from(!self.data_transfer_in_progress) << 3)
276                    | (u8::from(!self.data_transfer_in_progress) << 1)
277            }
278            2 => {
279                // DBCL (Data Byte Counter, Low Byte)
280                log::trace!("DBCL read");
281                self.data_byte_counter as u8
282            }
283            3 => {
284                // DBCH (Data Byte Counter, High Byte)
285                log::trace!("DBCH read");
286
287                // DBC is only a 12-bit counter; the high 4 bytes of DBCH always read as DTEI
288                let dtei = u8::from(self.transfer_end_interrupt_pending);
289                let dbc_high_bits = ((self.data_byte_counter >> 8) & 0x0F) as u8;
290                (dtei << 7) | (dtei << 6) | (dtei << 5) | (dtei << 4) | dbc_high_bits
291            }
292            4..=7 => {
293                // HEAD0-3 (Header/Subheader Data)
294
295                let idx = self.register_address - 4;
296                log::trace!("HEAD{idx} read");
297
298                if self.subheader_data_enabled {
299                    self.subheader_data[idx as usize]
300                } else {
301                    self.header_data[idx as usize]
302                }
303            }
304            8 => {
305                // PTL (Block Pointer, Low Byte)
306                log::trace!("PTL read");
307
308                self.block_pointer.lsb()
309            }
310            9 => {
311                // PTH (Block Pointer, High Byte)
312                log::trace!("PTH read");
313
314                self.block_pointer.msb()
315            }
316            10 => {
317                // WAL (Write Address, Low Byte)
318                log::trace!("WAL read");
319                self.write_address.lsb()
320            }
321            11 => {
322                // WAH (Write Address, High Byte)
323                log::trace!("WAH read");
324                self.write_address.msb()
325            }
326            12 => {
327                // STAT0 (Status 0)
328                log::trace!("STAT0 read");
329
330                // Hardcode CRCOK to 1 and all other bits (various error conditions) to 0
331                0x80
332            }
333            13 => {
334                // STAT1 (Status 1)
335                log::trace!("STAT1 read");
336
337                // Error flags for header/subheader data registers; hardcode all to 0
338                0x00
339            }
340            14 => {
341                // STAT2 (Status 2)
342                log::trace!("STAT2 read");
343
344                // TODO figure out what to put here
345                0x00
346            }
347            15 => {
348                // STAT3 (Status 3)
349                log::trace!("STAT3 read");
350
351                // In actual hardware VALST remains low for a short amount of time after the
352                // decoder interrupt is generated, but the BIOS shouldn't read STAT3 multiple
353                // times per interrupt
354                let value = u8::from(!self.decoder_interrupt_pending) << 7;
355
356                // Reading STAT3 clears the decoder interrupt
357                self.decoder_interrupt_pending = false;
358
359                // Hardcode WLONG and CBLK to 0; bits 4-0 are unused
360                value
361            }
362            16..=31 => {
363                // Invalid addresses
364                0xFF
365            }
366            _ => panic!("CDC register address should always be <= 15"),
367        };
368
369        self.increment_register_address();
370
371        value
372    }
373
374    pub fn write_register(&mut self, value: u8) {
375        match self.register_address {
376            0 => {
377                // SBOUT (Status Byte Output)
378                log::trace!("SBOUT write: {value:02X}");
379
380                // Not used by Sega CD; do nothing
381            }
382            1 => {
383                // IFCTRL (Host Interface Control)
384                log::trace!("IFCTRL write: {value:02X}");
385
386                self.write_ifctrl(value);
387            }
388            2 => {
389                // DBCL (Data Byte Counter, Low Byte)
390                log::trace!("DBCL write: {value:02X}");
391
392                self.data_byte_counter.set_lsb(value);
393
394                log::trace!("  DBC: {:04X}", self.data_byte_counter);
395            }
396            3 => {
397                // DBCH (Data Byte Counter, High Byte)
398                log::trace!("DBCH write: {value:02X}");
399
400                // DBC is only a 12-bit counter; mask out the highest 4 bits
401                self.data_byte_counter.set_msb(value & 0x0F);
402
403                log::trace!("  DBC: {:04X}", self.data_byte_counter);
404            }
405            4 => {
406                // DACL (Data Address Counter, Low Byte)
407                log::trace!("DACL write: {value:02X}");
408
409                self.data_address_counter.set_lsb(value);
410
411                log::trace!("  DAC: {:04X}", self.data_address_counter);
412            }
413            5 => {
414                // DACH (Data Address Counter, High Byte)
415                log::trace!("DACH write: {value:02X}");
416
417                self.data_address_counter.set_msb(value);
418                self.data_address_counter &= BUFFER_RAM_ADDRESS_MASK;
419
420                log::trace!("  DAC: {:04X}", self.data_address_counter);
421            }
422            6 => {
423                // DTTRG (Data Transfer Trigger)
424                log::trace!("DTTRG write");
425
426                // Writing any value to this register initiates a data transfer if DOUTEN=1
427                self.data_transfer_in_progress = self.data_out_enabled;
428                self.end_of_data_transfer = !self.data_transfer_in_progress;
429                if self.data_transfer_in_progress && self.device_destination.is_host_data() {
430                    self.populate_host_data_buffer();
431                }
432            }
433            7 => {
434                // DTACK (Data Transfer End Acknowledge)
435                log::trace!("DTACK write");
436
437                // Writing any value to this register clears the DTEI interrupt
438                self.transfer_end_interrupt_pending = false;
439            }
440            8 => {
441                // WAL (Write Address, Low Byte)
442                log::trace!("WAL write: {value:02X}");
443
444                self.write_address.set_lsb(value);
445
446                log::trace!("  WA: {:04X}", self.write_address);
447            }
448            9 => {
449                // WAH (Write Address, High Byte)
450                log::trace!("WAH write: {value:02X}");
451
452                self.write_address.set_msb(value);
453                self.write_address &= BUFFER_RAM_ADDRESS_MASK;
454
455                log::trace!("  WA: {:04X}", self.write_address);
456            }
457            10 => {
458                // CTRL0 (Control 0)
459                // Intentionally ignore all bits except DECEN and WRRQ; the other bits are related
460                // to error detection and correction settings
461                log::trace!("CTRL0 write: {value:02X}");
462
463                self.write_ctrl0(value);
464            }
465            11 => {
466                // CTRL1 (Control 1)
467                log::trace!("CTRL1 write: {value:02X}");
468
469                self.write_ctrl1(value);
470            }
471            12 => {
472                // PTL (Block Pointer, Low Byte)
473                log::trace!("PTL write: {value:02X}");
474
475                self.block_pointer.set_lsb(value);
476
477                log::trace!("  PT: {:04X}", self.block_pointer);
478            }
479            13 => {
480                // PTH (Block Pointer, High Byte)
481                log::trace!("PTH write: {value:02X}");
482
483                self.block_pointer.set_msb(value);
484                self.block_pointer &= BUFFER_RAM_ADDRESS_MASK;
485
486                log::trace!("  PT: {:04X}", self.block_pointer);
487            }
488            15 => {
489                // RESET
490                log::trace!("RESET write");
491                self.reset();
492            }
493            14 | 16..=31 => {
494                // Unused, do nothing
495            }
496            _ => panic!("CDC register address should always be <= 15"),
497        }
498
499        self.increment_register_address();
500    }
501
502    fn write_ifctrl(&mut self, value: u8) {
503        // Intentionally ignoring CMDIEN, CMDBK, DTWAI, STWAI, SOUTEN bits
504
505        let prev_dtei_enabled = self.transfer_end_interrupt_enabled;
506        let prev_deci_enabled = self.decoder_interrupt_enabled;
507
508        self.transfer_end_interrupt_enabled = value.bit(6);
509        self.decoder_interrupt_enabled = value.bit(5);
510
511        if (!prev_dtei_enabled
512            && self.transfer_end_interrupt_enabled
513            && self.transfer_end_interrupt_pending)
514            || (!prev_deci_enabled
515                && self.decoder_interrupt_enabled
516                && self.decoder_interrupt_pending)
517        {
518            self.scd_interrupt_flag = true;
519        }
520
521        self.data_out_enabled = value.bit(1);
522        if !self.data_out_enabled {
523            // Abort any in-progress data transfer
524            self.data_transfer_in_progress = false;
525            self.end_of_data_transfer = true;
526        }
527
528        log::trace!("  DTEIEN: {}", self.transfer_end_interrupt_enabled);
529        log::trace!("  DECIEN: {}", self.decoder_interrupt_enabled);
530        log::trace!("  DOUTEN: {}", self.data_out_enabled);
531    }
532
533    fn write_ctrl0(&mut self, value: u8) {
534        self.decoder_enabled = value.bit(7);
535        self.decoder_writes_enabled = value.bit(2);
536
537        // Disabling the decoder also disables any pending interrupt
538        if !self.decoder_enabled {
539            self.decoder_interrupt_pending = false;
540        }
541
542        if !self.decoder_enabled || !self.decoder_writes_enabled {
543            self.decoded_first_written_block = false;
544        }
545
546        log::trace!("  DECEN: {}", self.decoder_enabled);
547        log::trace!("  WRRQ: {}", self.decoder_writes_enabled);
548    }
549
550    fn write_ctrl1(&mut self, value: u8) {
551        self.subheader_data_enabled = value.bit(0);
552        log::trace!("  SHDREN: {}", self.subheader_data_enabled);
553    }
554
555    fn increment_register_address(&mut self) {
556        // Register address automatically increments on each access when it is not 0
557        if self.register_address != 0 {
558            self.register_address = (self.register_address + 1) & REGISTER_ADDRESS_MASK;
559        }
560    }
561
562    pub fn dma_address(&self) -> u32 {
563        self.dma_address
564    }
565
566    pub fn set_dma_address(&mut self, dma_address: u32) {
567        log::trace!("CDC DMA address set to {dma_address:X}");
568        self.dma_address = dma_address;
569    }
570
571    pub fn data_ready(&self) -> bool {
572        self.data_transfer_in_progress
573    }
574
575    pub fn end_of_data_transfer(&self) -> bool {
576        self.end_of_data_transfer
577    }
578
579    pub fn interrupt_pending(&self) -> bool {
580        self.scd_interrupt_flag
581    }
582
583    pub(super) fn decode_block(&mut self, sector_buffer: &[u8; cdrom::BYTES_PER_SECTOR as usize]) {
584        if !self.decoder_enabled {
585            return;
586        }
587
588        self.decoded_last_75hz_cycle = true;
589
590        // Header data and subheader data are always read from bytes 12-15 and 16-19 respectively
591        self.header_data.copy_from_slice(&sector_buffer[12..16]);
592        self.subheader_data.copy_from_slice(&sector_buffer[16..20]);
593
594        self.set_decoder_interrupt_flag();
595
596        if self.decoder_writes_enabled {
597            for &byte in sector_buffer {
598                self.buffer_ram[self.write_address as usize] = byte;
599                self.write_address = (self.write_address + 1) & BUFFER_RAM_ADDRESS_MASK;
600            }
601
602            if self.decoded_first_written_block {
603                self.block_pointer =
604                    (self.block_pointer + cdrom::BYTES_PER_SECTOR as u16) & BUFFER_RAM_ADDRESS_MASK;
605            } else {
606                // Decoded blocks start at the header, skipping the 12-byte sync
607                self.block_pointer =
608                    (self.block_pointer + DATA_TRACK_HEADER_LEN) & BUFFER_RAM_ADDRESS_MASK;
609
610                self.decoded_first_written_block = true;
611            }
612
613            log::trace!(
614                "Performed decoder write; write address = {:04X}, block pointer = {:04X}",
615                self.write_address,
616                self.block_pointer
617            );
618        }
619    }
620
621    fn set_decoder_interrupt_flag(&mut self) {
622        // Decoder interrupt always triggers INT5, even if not acknowledged in CDC
623        self.decoder_interrupt_pending = true;
624        if self.decoder_interrupt_enabled
625            && (!self.transfer_end_interrupt_enabled || !self.transfer_end_interrupt_pending)
626        {
627            self.scd_interrupt_flag = true;
628        }
629    }
630
631    fn set_transfer_end_interrupt_flag(&mut self) {
632        // Transfer end interrupt only triggers INT5 if the previous interrupt was acknowledged in CDC
633        if self.transfer_end_interrupt_enabled
634            && !self.transfer_end_interrupt_pending
635            && (!self.decoder_interrupt_enabled || !self.decoder_interrupt_pending)
636        {
637            self.scd_interrupt_flag = true;
638        }
639        self.transfer_end_interrupt_pending = true;
640    }
641
642    pub fn clock_44100hz(&mut self, dma_args: RchipDmaArgs<'_>) {
643        if self.data_transfer_in_progress && self.device_destination.is_dma() {
644            self.progress_dma(dma_args);
645        }
646
647        // Based on mcd-verificator, DECI automatically clears about 40% of the way through a 75Hz frame
648        self.cycles_44100hz_since_decode += 1;
649        if self.cycles_44100hz_since_decode == 44100 / 75 * 4 / 10 {
650            self.decoder_interrupt_pending = false;
651        }
652    }
653
654    pub fn clock_75hz(&mut self) {
655        if !self.decoded_last_75hz_cycle && self.decoder_enabled {
656            // The decoder interrupt triggers every 75Hz cycle if enabled, even if no new sector
657            // was received from the CDD. In actual hardware I think it repeatedly decodes the
658            // last received block
659            self.set_decoder_interrupt_flag();
660        }
661        self.decoded_last_75hz_cycle = false;
662        self.cycles_44100hz_since_decode = 0;
663    }
664
665    fn progress_dma(
666        &mut self,
667        RchipDmaArgs { word_ram, prg_ram, prg_ram_accessible, pcm }: RchipDmaArgs<'_>,
668    ) {
669        if self.device_destination == DeviceDestination::PrgRam && !prg_ram_accessible {
670            log::trace!("CDC DMA to PRG RAM is halted because sub CPU is removed from the bus");
671            return;
672        }
673
674        if self.device_destination == DeviceDestination::WordRam && word_ram.is_sub_access_blocked()
675        {
676            log::trace!("CDC DMA is halted because sub CPU does not have access to word RAM");
677            return;
678        }
679
680        let dma_address_mask = match self.device_destination {
681            // All 19 bits of DMA address are used for PRG RAM
682            DeviceDestination::PrgRam => (1 << 19) - 1,
683            // DMA address is 18 bits in 2M mode (256KB), 17 bits in 1M mode (128KB)
684            DeviceDestination::WordRam => match word_ram.mode() {
685                WordRamMode::TwoM => (1 << 18) - 1,
686                WordRamMode::OneM => (1 << 17) - 1,
687            },
688            // PCM address is 13 bits in the register, but it's effectively a 12-bit address
689            DeviceDestination::Pcm => (1 << 12) - 1,
690            _ => panic!("Invalid DMA destination: {:?}", self.device_destination),
691        };
692
693        log::trace!(
694            "Progressing DMA transfer to {:?} starting at {:06X}; {} bytes remaining",
695            self.device_destination,
696            self.dma_address,
697            self.data_byte_counter + 1
698        );
699
700        match self.device_destination {
701            DeviceDestination::PrgRam | DeviceDestination::WordRam => {
702                let mut dma_address = self.dma_address & dma_address_mask;
703
704                // Transfers to PRG RAM and word RAM are word-size; transfer 2 bytes at a time
705                // 64 is arbitrary and makes the transfer finish quickly
706                for _ in 0..64 {
707                    if self.data_byte_counter == 0 {
708                        // DMA length is odd; skip the last byte because transfers are word-size
709                        log::trace!("DMA transfer complete");
710
711                        self.data_byte_counter = 0xFFFF;
712                        self.data_transfer_in_progress = false;
713                        self.end_dma_transfer();
714
715                        break;
716                    }
717
718                    let msb = self.buffer_ram[self.data_address_counter as usize];
719                    let lsb = self.buffer_ram
720                        [((self.data_address_counter + 1) & BUFFER_RAM_ADDRESS_MASK) as usize];
721
722                    match self.device_destination {
723                        DeviceDestination::PrgRam => {
724                            prg_ram[(dma_address >> 1) as usize] = u16::from_be_bytes([msb, lsb]);
725                        }
726                        DeviceDestination::WordRam => {
727                            word_ram.dma_write(dma_address, msb);
728                            word_ram.dma_write((dma_address + 1) & dma_address_mask, lsb);
729                        }
730                        _ => unreachable!("nested matches"),
731                    }
732
733                    self.data_address_counter =
734                        (self.data_address_counter + 2) & BUFFER_RAM_ADDRESS_MASK;
735                    dma_address = (dma_address + 2) & dma_address_mask;
736
737                    let (new_byte_counter, overflowed) = self.data_byte_counter.overflowing_sub(2);
738                    self.data_byte_counter = new_byte_counter;
739                    if overflowed {
740                        log::trace!("DMA transfer complete");
741
742                        self.data_transfer_in_progress = false;
743                        self.end_dma_transfer();
744
745                        break;
746                    }
747                }
748
749                self.dma_address = dma_address;
750            }
751            DeviceDestination::Pcm => {
752                // PCM DMA confusingly shifts the effective address bits down by 1, treating the register
753                // as A11-A2 instead of A12-A3
754                let mut dma_address = (self.dma_address >> 1) & dma_address_mask;
755
756                // Transfers to PCM RAM are byte-size
757                // 128 is arbitrary and makes the transfer finish quickly
758                for _ in 0..128 {
759                    let byte = self.buffer_ram[self.data_address_counter as usize];
760                    pcm.dma_write(dma_address, byte);
761
762                    self.data_address_counter =
763                        (self.data_address_counter + 1) & BUFFER_RAM_ADDRESS_MASK;
764                    dma_address = (dma_address + 1) & dma_address_mask;
765
766                    let (new_byte_counter, overflowed) = self.data_byte_counter.overflowing_sub(1);
767                    self.data_byte_counter = new_byte_counter;
768                    if overflowed {
769                        log::trace!("DMA transfer complete");
770
771                        self.data_transfer_in_progress = false;
772                        self.end_dma_transfer();
773
774                        break;
775                    }
776                }
777
778                self.dma_address = dma_address << 1;
779            }
780            _ => unreachable!("device destination was checked earlier in the method"),
781        }
782    }
783
784    pub fn reset(&mut self) {
785        // Clear all values from IFCTRL, CTRL0, and CTRL1, as well as interrupt flags
786        self.write_ifctrl(0x00);
787        self.write_ctrl0(0x00);
788        self.write_ctrl1(0x00);
789        self.transfer_end_interrupt_pending = false;
790        self.decoder_interrupt_pending = false;
791    }
792
793    pub fn acknowledge_interrupt(&mut self) {
794        self.scd_interrupt_flag = false;
795    }
796
797    pub fn debug_ram_view(&mut self) -> impl DebugMemoryView {
798        DebugBytesView(self.buffer_ram.as_mut_slice())
799    }
800}