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(§or_buffer[12..16]); 592 self.subheader_data.copy_from_slice(§or_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}