1use crate::api::{SegaCdAudioOutput, SegaCdLoadResult}; 2use crate::cddrive::cdc::{DeviceDestination, Rchip}; 3use crate::cddrive::cdd::{CdDrive, CdModel}; 4use crate::cddrive::{CdController, cdc}; 5use crate::font::FontRegisters; 6use crate::graphics::GraphicsCoprocessor; 7use crate::memory::{BACKUP_RAM_LEN, Bios, PRG_RAM_LEN_WORDS, RAM_CARTRIDGE_LEN, SegaCdRegisters}; 8use crate::rf5c164::Rf5c164; 9use crate::{ScdCpu, WordRam, api, backupram}; 10use bincode::{Decode, Encode}; 11use cdrom::reader::CdRom; 12use genesis_config::{GenesisEmulatorConfig, GenesisRegion}; 13use jgenesis_common::boxedarray::{BoxedByteArray, BoxedWordArray}; 14use jgenesis_common::num::{GetBit, U16Ext}; 15use jgenesis_proc_macros::PartialClone; 16use m68000_emu::BusInterface; 17use m68000_emu::debug::DummyM68000Debugger; 18use std::mem; 19 20pub mod debug; 21 22// RAM cartridge size byte is N in the formula 8KB * 2^N 23// N=4 signals 128KB 24const RAM_CARTRIDGE_SIZE_BYTE: u8 = 0x04; 25 26const TIMER_DIVIDER: u64 = 1536; 27 28const SUB_REGISTER_ADDRESS_MASK: u32 = 0x1FF; 29 30#[derive(Debug, Clone, Copy, Encode, Decode)] 31enum BufferedWrite { 32 Byte(u8), 33 Word(u16), 34} 35 36#[derive(Debug, Encode, Decode, PartialClone)] 37pub struct SegaCdBus { 38 pub graphics_coprocessor: GraphicsCoprocessor, 39 pub pcm: Rf5c164, 40 #[partial_clone(default)] 41 bios: Bios, 42 #[partial_clone(partial)] 43 disc_drive: CdController, 44 prg_ram: BoxedWordArray<PRG_RAM_LEN_WORDS>, 45 word_ram: WordRam, 46 backup_ram: BoxedByteArray<BACKUP_RAM_LEN>, 47 enable_ram_cartridge: bool, 48 ram_cartridge: BoxedByteArray<RAM_CARTRIDGE_LEN>, 49 ram_cartridge_writes_enabled: bool, 50 backup_ram_dirty: bool, 51 registers: SegaCdRegisters, 52 font_registers: FontRegisters, 53 disc_region: GenesisRegion, 54 forced_region: Option<GenesisRegion>, 55 timer_divider: u64, 56 buffered_sub_register_writes: Vec<(u32, BufferedWrite)>, 57} 58 59impl SegaCdBus { 60 pub fn new( 61 bios: Vec<u8>, 62 mut disc: Option<CdRom>, 63 initial_backup_ram: Option<Vec<u8>>, 64 initial_ram_cartridge: Option<Vec<u8>>, 65 config: &GenesisEmulatorConfig, 66 ) -> SegaCdLoadResult<Self> { 67 let (backup_ram, ram_cartridge) = backupram::load_initial_backup_ram( 68 initial_backup_ram.as_ref(), 69 initial_ram_cartridge.as_ref(), 70 ); 71 72 let disc_region = match &mut disc { 73 Some(disc) => api::parse_disc_region(disc)?, 74 None => { 75 // Default to US if no disc provided 76 GenesisRegion::Americas 77 } 78 }; 79 80 log::info!("Region parsed from disc header: {disc_region:?}"); 81 82 let cd_model = guess_cd_model(&bios); 83 log::info!("Detected CD model {cd_model:?} based on BIOS ROM"); 84 85 Ok(Self { 86 graphics_coprocessor: GraphicsCoprocessor::new(), 87 pcm: Rf5c164::new(&config.sega_cd), 88 bios: Bios(bytes_to_words_be(bios).into_boxed_slice()), 89 disc_drive: CdController::new(disc, cd_model, &config.sega_cd), 90 prg_ram: BoxedWordArray::new(), 91 word_ram: WordRam::new(), 92 backup_ram: backup_ram.into(), 93 enable_ram_cartridge: config.sega_cd.enable_ram_cartridge, 94 ram_cartridge: ram_cartridge.into(), 95 ram_cartridge_writes_enabled: true, 96 backup_ram_dirty: false, 97 registers: SegaCdRegisters::new(), 98 font_registers: FontRegisters::new(), 99 disc_region, 100 forced_region: config.forced_region, 101 timer_divider: TIMER_DIVIDER, 102 buffered_sub_register_writes: Vec::with_capacity(5), 103 }) 104 } 105 106 #[inline] 107 pub fn tick_components( 108 &mut self, 109 mclk_cycles: u64, 110 pcm_cycles: u64, 111 audio_output: &mut impl SegaCdAudioOutput, 112 ) -> SegaCdLoadResult<()> { 113 // CDC DMA can only write to PRG RAM while the sub CPU is on the bus 114 let prg_ram_accessible = !(self.registers.sub_cpu_busreq || self.registers.sub_cpu_reset); 115 self.disc_drive.tick( 116 mclk_cycles, 117 &mut self.word_ram, 118 &mut self.prg_ram, 119 prg_ram_accessible, 120 &mut self.pcm, 121 |sample_l, sample_r| audio_output.collect_cd((sample_l, sample_r)), 122 )?; 123 124 self.tick_timers(mclk_cycles); 125 126 if !self.word_ram.is_sub_access_blocked() { 127 self.graphics_coprocessor.tick( 128 mclk_cycles, 129 &mut self.word_ram, 130 self.registers.graphics_interrupt_enabled, 131 ); 132 } 133 134 self.pcm.tick(pcm_cycles, |sample| audio_output.collect_pcm(sample)); 135 136 Ok(()) 137 } 138 139 fn tick_timers(&mut self, mut mclk_cycles: u64) { 140 while mclk_cycles >= self.timer_divider { 141 self.clock_timers(); 142 mclk_cycles -= self.timer_divider; 143 self.timer_divider = TIMER_DIVIDER; 144 } 145 self.timer_divider -= mclk_cycles; 146 } 147 148 fn clock_timers(&mut self) { 149 if self.registers.timer_counter == 1 { 150 self.registers.timer_interrupt_pending = true; 151 self.registers.timer_counter = 0; 152 } else if self.registers.timer_counter == 0 { 153 self.registers.timer_counter = self.registers.timer_interval; 154 } else { 155 self.registers.timer_counter -= 1; 156 } 157 158 self.registers.stopwatch_counter = (self.registers.stopwatch_counter + 1) & 0x0FFF; 159 } 160 161 // $000000-$1FFFFF: BIOS at $000000-$01FFFF, PRG RAM at $020000-$03FFFF, mirrored repeatedly 162 pub fn main_read_bios_prg_ram(&self, address: u32) -> u16 { 163 if address & 0x20000 == 0 { 164 // BIOS ROM 165 // HINT vector ($000070-$000073) should read out the register value 166 match address & 0x1FFFF { 167 0x70 | 0x71 => 0xFFFF, 168 0x72 | 0x73 => self.registers.h_interrupt_vector, 169 bios_addr => self.bios[(bios_addr >> 1) as usize], 170 } 171 } else { 172 // PRG RAM 173 let prg_ram_addr = self.registers.main_prg_ram_addr(address); 174 self.prg_ram[(prg_ram_addr >> 1) as usize] 175 } 176 } 177 178 // $000000-$1FFFFF: BIOS at $000000-$01FFFF, PRG RAM at $020000-$03FFFF, mirrored repeatedly 179 pub fn main_write_bios_prg_ram<const WORD: bool>(&mut self, address: u32, value: u16) { 180 if address & 0x20000 != 0 { 181 // PRG RAM 182 let prg_ram_addr = self.registers.main_prg_ram_addr(address); 183 self.write_prg_ram::<WORD>(prg_ram_addr, value, ScdCpu::Main); 184 } // else BIOS ROM, ignore 185 } 186 187 fn write_prg_ram<const WORD: bool>(&mut self, address: u32, value: u16, cpu: ScdCpu) { 188 if cpu == ScdCpu::Main && !(self.registers.sub_cpu_busreq || self.registers.sub_cpu_reset) { 189 // The Genesis hardware cannot write to PRG RAM while the sub CPU is on the bus. 190 // Dungeon Explorer depends on this or the Z80 will trash PRG RAM while the sub CPU is using it 191 log::trace!( 192 "Main CPU write to PRG RAM without removing sub CPU from bus: {address:06X} {value:02X}" 193 ); 194 return; 195 } 196 197 // PRG RAM write protection applies in multiples of $200 198 let write_protection_boundary = u32::from(self.registers.prg_ram_write_protect) * 0x200; 199 200 // PRG RAM write protection only applies to the Sub CPU. 201 // The JP V2.00 BIOS freezes if Main CPU writes to PRG RAM are not always allowed through 202 if cpu == ScdCpu::Main || address >= write_protection_boundary { 203 if WORD { 204 self.prg_ram[(address >> 1) as usize] = value; 205 } else if !address.bit(0) { 206 self.prg_ram[(address >> 1) as usize].set_msb(value as u8); 207 } else { 208 self.prg_ram[(address >> 1) as usize].set_lsb(value as u8); 209 } 210 } 211 } 212 213 // $200000-$3FFFFF: Word RAM 214 pub fn main_read_word_ram(&self, address: u32) -> u8 { 215 self.word_ram.main_cpu_read_ram(address) 216 } 217 218 // $200000-$3FFFFF: Word RAM 219 pub fn main_write_word_ram(&mut self, address: u32, value: u8) { 220 self.word_ram.main_cpu_write_ram(address, value); 221 } 222 223 // $400000-$7FFFFF: RAM cartridge 224 pub fn read_ram_cartridge(&self, address: u32) -> u8 { 225 if !self.enable_ram_cartridge { 226 return 0xFF; 227 } 228 229 if !address.bit(0) { 230 // RAM cartridge is mapped to odd addresses only 231 return 0x00; 232 } 233 234 match address { 235 0x400000..=0x4FFFFF => { 236 // RAM cartridge size 237 RAM_CARTRIDGE_SIZE_BYTE 238 } 239 0x500000..=0x5FFFFF => { 240 // Unused 241 0x00 242 } 243 0x600000..=0x6FFFFF => { 244 // RAM cartridge data, mirrored every 256KB 245 self.ram_cartridge[((address & 0x3FFFF) >> 1) as usize] 246 } 247 0x700000..=0x7FFFFF => { 248 // RAM cartridge writes enabled bit 249 self.ram_cartridge_writes_enabled.into() 250 } 251 _ => panic!("Invalid RAM cartridge address: {address:06X}"), 252 } 253 } 254 255 // $400000-$7FFFFF: RAM cartridge 256 pub fn write_ram_cartridge(&mut self, address: u32, value: u8) { 257 if !self.enable_ram_cartridge { 258 return; 259 } 260 261 if !address.bit(0) { 262 // RAM cartridge is mapped to odd addresses only 263 return; 264 } 265 266 match address { 267 0x400000..=0x5FFFFF => { 268 // Unused or not writable; do nothing 269 } 270 0x600000..=0x6FFFFF => { 271 // RAM cartridge data 272 if self.ram_cartridge_writes_enabled { 273 self.ram_cartridge[((address & 0x3FFFF) >> 1) as usize] = value; 274 self.backup_ram_dirty = true; 275 } 276 } 277 0x700000..=0x7FFFFF => { 278 // RAM cartridge writes enabled bit 279 self.ram_cartridge_writes_enabled = value.bit(0); 280 } 281 _ => panic!("Invalid RAM cartridge address: {address:06X}"), 282 } 283 } 284 285 // $A12000-$A1202F: Sega CD gate array registers 286 pub fn main_read_register<const WORD: bool>(&mut self, address: u32) -> u16 { 287 log::trace!("Main CPU register {} read: {address:06X}", if WORD { "word" } else { "byte" }); 288 289 let word = match address { 290 0xA12000 | 0xA12001 => { 291 // Initialization / reset 292 (u16::from(self.registers.software_interrupt_enabled) << 15) 293 | (u16::from(self.registers.software_interrupt_pending) << 8) 294 | (u16::from(self.registers.sub_cpu_busreq) << 1) 295 | u16::from(!self.registers.sub_cpu_reset) 296 } 297 0xA12002 | 0xA12003 => { 298 // Memory mode / write protect 299 (u16::from(self.registers.prg_ram_write_protect) << 8) 300 | (u16::from(self.registers.prg_ram_bank) << 6) 301 | u16::from(self.word_ram.read_control()) 302 } 303 0xA12004 | 0xA12005 => { 304 log::trace!(" CDC mode read (main CPU)"); 305 let cdc = self.cdc(); 306 let end_of_data_transfer = cdc.end_of_data_transfer(); 307 let data_ready = cdc.data_ready(); 308 let dd_bits = cdc.device_destination().to_bits(); 309 310 (u16::from(end_of_data_transfer) << 15) 311 | (u16::from(data_ready) << 14) 312 | (u16::from(dd_bits) << 8) 313 } 314 0xA12006 | 0xA12007 => { 315 // HINT vector 316 self.registers.h_interrupt_vector 317 } 318 0xA12008 | 0xA12009 => { 319 // CDC host data 320 self.cdc_mut().read_host_data(ScdCpu::Main) 321 } 322 0xA1200C | 0xA1200D => { 323 // Stopwatch 324 self.registers.stopwatch_counter 325 } 326 0xA1200E | 0xA1200F => { 327 // Communication flags 328 u16::from_be_bytes([ 329 self.registers.main_cpu_communication_flags, 330 self.registers.sub_cpu_communication_flags, 331 ]) 332 } 333 0xA12010..=0xA1201F => { 334 // Communication command buffers 335 self.registers.communication_commands[((address & 0xF) >> 1) as usize] 336 } 337 0xA12020..=0xA1202F => { 338 // Communication status buffers 339 self.registers.communication_statuses[((address & 0xF) >> 1) as usize] 340 } 341 _ => 0, 342 }; 343 344 if WORD { word } else { word.be_byte(address & 1).into() } 345 } 346 347 // $A12000-$A1202F: Sega CD gate array registers 348 pub fn main_write_register<const WORD: bool>(&mut self, address: u32, value: u16) { 349 if WORD { 350 log::trace!("Main CPU register word write: {address:06X} {value:04X}"); 351 } else { 352 log::trace!("Main CPU register byte write: {address:06X} {:02X}", value & 0xFF); 353 } 354 355 let value_msb = if WORD { value.msb() } else { value as u8 }; 356 let value_lsb = value.lsb(); 357 358 match address { 359 0xA12000 | 0xA12001 => { 360 // Initialization / reset 361 if WORD || !address.bit(0) { 362 self.registers.software_interrupt_pending = value_msb.bit(0); 363 364 log::trace!( 365 " INT2 pending write: {}", 366 self.registers.software_interrupt_pending 367 ); 368 } 369 370 if WORD || address.bit(0) { 371 self.registers.sub_cpu_busreq = value_lsb.bit(1); 372 self.registers.sub_cpu_reset = !value_lsb.bit(0); 373 374 log::trace!(" Sub CPU BUSREQ: {}", self.registers.sub_cpu_busreq); 375 log::trace!(" Sub CPU RESET: {}", self.registers.sub_cpu_reset); 376 } 377 } 378 0xA12002 | 0xA12003 => { 379 // Memory mode / write protect 380 if WORD || !address.bit(0) { 381 self.registers.prg_ram_write_protect = value_msb; 382 log::trace!(" PRG RAM protect write: {value:02X}"); 383 } 384 385 if WORD || address.bit(0) { 386 self.registers.prg_ram_bank = value_lsb >> 6; 387 self.word_ram.main_cpu_write_control(value_lsb); 388 389 log::trace!(" PRG RAM bank: {}", self.registers.prg_ram_bank); 390 } 391 } 392 0xA12006 | 0xA12007 => { 393 // HINT vector 394 // Byte-size writes copy the byte into both halves 395 self.registers.h_interrupt_vector = 396 if WORD { value } else { u16::from_ne_bytes([value as u8; 2]) }; 397 } 398 0xA12008 | 0xA12009 => { 399 // CDC host data 400 self.cdc_mut().write_host_data(ScdCpu::Main); 401 } 402 0xA1200E | 0xA1200F => { 403 // Communication flags; only main CPU flags are writable 404 // Byte-size writes always write the flags regardless of address 405 self.registers.main_cpu_communication_flags = value_msb; 406 } 407 0xA12010..=0xA1201F => { 408 // Communication command buffers 409 let idx = (address & 0xF) >> 1; 410 let command = &mut self.registers.communication_commands[idx as usize]; 411 if WORD { 412 *command = value; 413 } else if !address.bit(0) { 414 command.set_msb(value as u8); 415 } else { 416 command.set_lsb(value as u8); 417 } 418 } 419 _ => {} 420 } 421 } 422 423 #[allow(clippy::match_same_arms)] 424 fn sub_read_register<const WORD: bool>(&mut self, address: u32) -> u16 { 425 log::trace!("Sub CPU register {} read: {address:06X}", if WORD { "word" } else { "byte" }); 426 427 let word = match address & SUB_REGISTER_ADDRESS_MASK { 428 0x000 | 0x001 => { 429 // LED / reset 430 // TODO version in bits 7-4 431 // Bit 0 (CD drive operable) hardcoded to 1 432 (u16::from(self.registers.led_green) << 9) 433 | (u16::from(self.registers.led_red) << 8) 434 | 1 435 } 436 0x002 | 0x003 => { 437 // PRG RAM write protect / memory mode 438 (u16::from(self.registers.prg_ram_write_protect) << 8) 439 | (u16::from(self.word_ram.priority_mode().to_bits()) << 3) 440 | u16::from(self.word_ram.read_control()) 441 } 442 0x004 | 0x005 => { 443 // CDC mode / register address 444 log::trace!(" CDC mode read (sub CPU)"); 445 446 let cdc = self.cdc(); 447 let end_of_data_transfer = cdc.end_of_data_transfer(); 448 let data_ready = cdc.data_ready(); 449 let dd_bits = cdc.device_destination().to_bits(); 450 451 (u16::from(end_of_data_transfer) << 15) 452 | (u16::from(data_ready) << 14) 453 | (u16::from(dd_bits) << 8) 454 | u16::from(cdc.register_address()) 455 } 456 0x006 | 0x007 if WORD || address.bit(0) => { 457 // CDC register data 458 self.cdc_mut().read_register().into() 459 } 460 0x008 | 0x009 => { 461 // CDC host data 462 self.cdc_mut().read_host_data(ScdCpu::Sub) 463 } 464 0x00A | 0x00B => { 465 // CDC DMA address (bits 18-3) 466 (self.cdc().dma_address() >> 3) as u16 467 } 468 0x00C | 0x00D => { 469 // Stopwatch 470 self.registers.stopwatch_counter 471 } 472 0x00E | 0x00F => { 473 // Communication flags 474 u16::from_be_bytes([ 475 self.registers.main_cpu_communication_flags, 476 self.registers.sub_cpu_communication_flags, 477 ]) 478 } 479 0x010..=0x01F => { 480 // Communication command buffers 481 self.registers.communication_commands[((address & 0xF) >> 1) as usize] 482 } 483 0x020..=0x02F => { 484 // Communication status buffers 485 self.registers.communication_statuses[((address & 0xF) >> 1) as usize] 486 } 487 0x030 | 0x031 if WORD || address.bit(0) => { 488 // Timer 489 self.registers.timer_interval.into() 490 } 491 0x032 | 0x033 if WORD || address.bit(0) => { 492 // Interrupt mask control 493 (u16::from(self.registers.subcode_interrupt_enabled) << 6) 494 | (u16::from(self.registers.cdc_interrupt_enabled) << 5) 495 | (u16::from(self.registers.cdd_interrupt_enabled) << 4) 496 | (u16::from(self.registers.timer_interrupt_enabled) << 3) 497 | (u16::from(self.registers.software_interrupt_enabled) << 2) 498 | (u16::from(self.registers.graphics_interrupt_enabled) << 1) 499 } 500 0x034 | 0x035 => { 501 // CDD fader, only bit 15 (fader processing) is readable and it's fine to always 502 // set it to 0 503 0 504 } 505 0x036 | 0x037 => { 506 // CDD control 507 (u16::from(!self.cdd().playing_audio()) << 8) 508 | (u16::from(self.registers.cdd_host_clock_on) << 2) 509 } 510 0x038..=0x041 => { 511 // CDD status 512 let relative_addr = ((address - 8) & 0xE) as usize; 513 let cdd_status = self.cdd().status(); 514 u16::from_be_bytes([cdd_status[relative_addr], cdd_status[relative_addr + 1]]) 515 } 516 0x042..=0x04B => { 517 // CDD command 518 let relative_addr = ((address - 2) & 0xE) as usize; 519 let cdd_command = &self.registers.cdd_command; 520 u16::from_be_bytes([cdd_command[relative_addr], cdd_command[relative_addr + 1]]) 521 } 522 0x04C | 0x04D if WORD || address.bit(0) => { 523 // Font color 524 self.font_registers.read_color().into() 525 } 526 0x04E | 0x04F => { 527 // Font bits 528 self.font_registers.font_bits() 529 } 530 0x050..=0x057 => { 531 // Font data 532 self.font_registers.read_font_data(address) 533 } 534 0x058..=0x067 => { 535 // Graphics coprocessor 536 self.graphics_coprocessor.read_register(address) 537 } 538 _ => 0, 539 }; 540 541 if WORD { word } else { word.be_byte(address & 1).into() } 542 } 543 544 fn sub_write_register<const WORD: bool>(&mut self, address: u32, value: u16) { 545 if WORD { 546 log::trace!("Sub CPU register word write: {address:06X} {value:04X}"); 547 } else { 548 log::trace!("Sub CPU register byte write: {address:06X} {:02X}", value & 0xFF); 549 } 550 551 let value_msb = if WORD { value.msb() } else { value as u8 }; 552 let value_lsb = value.lsb(); 553 554 match address & SUB_REGISTER_ADDRESS_MASK { 555 0x000 | 0x001 => { 556 // LED / reset 557 if WORD || !address.bit(0) { 558 self.registers.led_green = value_msb.bit(1); 559 self.registers.led_red = value_msb.bit(0); 560 } 561 562 if WORD || address.bit(0) { 563 log::trace!(" CDD reset write: {value_lsb:02X}"); 564 565 if !value_lsb.bit(0) { 566 // TODO official documentation says that this reset takes about 100ms - unclear what happens during that time 567 self.cdd_mut().reset(); 568 } 569 } 570 } 571 0x002 | 0x003 => { 572 // Memory mode 573 self.word_ram.sub_cpu_write_control(value_lsb); 574 } 575 0x004 | 0x005 => { 576 // CDC mode / register address 577 if WORD || !address.bit(0) { 578 log::trace!(" CDC mode write: {value_msb:02X}"); 579 let device_destination = DeviceDestination::from_bits(value_msb & 7); 580 self.cdc_mut().set_device_destination(device_destination); 581 } 582 583 if WORD || address.bit(0) { 584 log::trace!(" CDC register address write: {value_lsb:02X}"); 585 let register_address = value_lsb & cdc::REGISTER_ADDRESS_MASK; 586 self.cdc_mut().set_register_address(register_address); 587 } 588 } 589 0x006 | 0x007 if WORD || address.bit(0) => { 590 // CDC register data 591 log::trace!(" CDC register data write: {value_lsb:02X}"); 592 self.cdc_mut().write_register(value_lsb); 593 } 594 0x008 | 0x009 => { 595 // CDC host data 596 self.cdc_mut().write_host_data(ScdCpu::Sub); 597 } 598 0x00A | 0x00B => { 599 // CDC DMA address (bits 18-3) 600 let word = if WORD { value } else { u16::from_ne_bytes([value as u8; 2]) }; 601 let dma_address = u32::from(word) << 3; 602 self.cdc_mut().set_dma_address(dma_address); 603 } 604 0x00C | 0x00D => { 605 // Stopwatch (12 bits) 606 let word = if WORD { value } else { u16::from_ne_bytes([value as u8; 2]) }; 607 self.registers.stopwatch_counter = word & 0xFFF; 608 } 609 0x00E | 0x00F => { 610 // Communication flags 611 // Only low byte (sub CPU) is writable, but byte-size writes always write the flags 612 self.registers.sub_cpu_communication_flags = value as u8; 613 } 614 0x020..=0x02F => { 615 // Communication status buffers 616 let idx = (address & 0xF) >> 1; 617 let status = &mut self.registers.communication_statuses[idx as usize]; 618 if WORD { 619 *status = value; 620 } else if !address.bit(0) { 621 status.set_msb(value as u8); 622 } else { 623 status.set_lsb(value as u8); 624 } 625 } 626 0x030 | 0x031 => { 627 // Timer 628 self.registers.timer_interval = value as u8; 629 self.registers.timer_counter = value as u8; 630 } 631 0x032 | 0x033 if WORD || address.bit(0) => { 632 // Interrupt mask control 633 self.registers.subcode_interrupt_enabled = value.bit(6); 634 self.registers.cdc_interrupt_enabled = value.bit(5); 635 self.registers.cdd_interrupt_enabled = value.bit(4); 636 self.registers.timer_interrupt_enabled = value.bit(3); 637 self.registers.software_interrupt_enabled = value.bit(2); 638 self.registers.graphics_interrupt_enabled = value.bit(1); 639 640 // Disabling the graphics interrupt should clear any pending interrupt 641 if !self.registers.graphics_interrupt_enabled { 642 self.graphics_coprocessor.acknowledge_interrupt(); 643 } 644 645 log::trace!(" Interrupt mask write: {value_lsb:08b}"); 646 } 647 0x034 | 0x035 => { 648 // CDD fader 649 let word = if WORD { value } else { u16::from_ne_bytes([value as u8; 2]) }; 650 self.cdd_mut().set_fader_volume(word); 651 652 log::trace!(" CDD fader write: {value:04X}"); 653 } 654 0x036 | 0x037 if WORD || address.bit(0) => { 655 // CDD control 656 self.registers.cdd_host_clock_on = value.bit(2); 657 log::trace!(" CDD control write: {value:02X}"); 658 } 659 0x042..=0x04B => { 660 // CDD command 661 let relative_addr = ((address - 2) & 0xF) as usize; 662 663 if WORD { 664 self.registers.cdd_command[relative_addr] = value.msb(); 665 self.registers.cdd_command[relative_addr + 1] = value.lsb(); 666 } else { 667 self.registers.cdd_command[relative_addr] = value as u8; 668 } 669 670 // Writes to the last byte trigger a CDD command send 671 if (WORD && relative_addr == 8) || (!WORD && relative_addr == 9) { 672 self.disc_drive.cdd_mut().send_command(self.registers.cdd_command); 673 } 674 } 675 0x04C | 0x04D => { 676 // Font color 677 self.font_registers.write_color(value as u8); 678 } 679 0x04E | 0x04F => { 680 // Font bits 681 if WORD { 682 self.font_registers.write_font_bits(value); 683 } else if !address.bit(0) { 684 self.font_registers.write_font_bits_msb(value as u8); 685 } else { 686 self.font_registers.write_font_bits_lsb(value as u8); 687 } 688 } 689 0x058..=0x067 => { 690 // Graphics coprocessor 691 if WORD { 692 self.graphics_coprocessor.write_register_word(address, value); 693 } else { 694 self.graphics_coprocessor.write_register_byte(address, value as u8); 695 } 696 } 697 _ => {} 698 } 699 } 700 701 fn sub_read<const WORD: bool>(&mut self, address: u32) -> u16 { 702 // Only A0-A19 are connected for the sub CPU: 703 // https://gendev.spritesmind.net/forum/viewtopic.php?p=18935#p18935 704 let address = address & 0xFFFFF; 705 match address { 706 0x00000..=0x7FFFF => { 707 // PRG RAM 708 let word = self.prg_ram[(address >> 1) as usize]; 709 if WORD { word } else { word.be_byte(address & 1).into() } 710 } 711 0x80000..=0xDFFFF => { 712 // Word RAM 713 if WORD { 714 let msb = self.word_ram.sub_cpu_read_ram(address); 715 let lsb = self.word_ram.sub_cpu_read_ram(address + 1); 716 u16::from_be_bytes([msb, lsb]) 717 } else { 718 self.word_ram.sub_cpu_read_ram(address).into() 719 } 720 } 721 0xE0000..=0xEFFFF => { 722 // Backup RAM (odd addresses) 723 // Canonically located at $E0000-$E3FFF, mirrored up to $EFFFF 724 if WORD || address.bit(0) { 725 let backup_ram_addr = (address & 0x3FFF) >> 1; 726 self.backup_ram[backup_ram_addr as usize].into() 727 } else { 728 0 729 } 730 } 731 0xF0000..=0xF7FFF => { 732 // PCM sound chip (odd addresses) 733 // Canonically located at $F0000-$F3FFF, mirrored at $F4000-$F7FFF 734 if WORD || address.bit(0) { 735 self.pcm.read((address & 0x3FFF) >> 1).into() 736 } else { 737 0 738 } 739 } 740 0xF8000..=0xFFFFF => { 741 // Sub CPU registers 742 // Canonically located at $F8000-$F81FF, mirrored up to $FFFFF 743 self.sub_read_register::<WORD>(address) 744 } 745 _ => unreachable!("Value & 0xFFFFF is always <= 0xFFFFF"), 746 } 747 } 748 749 fn sub_write<const WORD: bool>(&mut self, address: u32, value: u16) { 750 // Only A0-A19 are connected for the sub CPU: 751 // https://gendev.spritesmind.net/forum/viewtopic.php?p=18935#p18935 752 let address = address & 0xFFFFF; 753 match address { 754 0x00000..=0x7FFFF => { 755 // PRG RAM 756 self.write_prg_ram::<WORD>(address, value, ScdCpu::Sub); 757 } 758 0x80000..=0xDFFFF => { 759 // Word RAM 760 if WORD { 761 self.word_ram.sub_cpu_write_ram(address, value.msb()); 762 self.word_ram.sub_cpu_write_ram(address + 1, value.lsb()); 763 } else { 764 self.word_ram.sub_cpu_write_ram(address, value as u8); 765 } 766 } 767 0xE0000..=0xEFFFF => { 768 // Backup RAM (odd addresses) 769 // Canonically located at $E0000-$E3FFF, mirrored up to $EFFFF 770 if WORD || address.bit(0) { 771 let backup_ram_addr = (address & 0x3FFF) >> 1; 772 self.backup_ram[backup_ram_addr as usize] = value as u8; 773 self.backup_ram_dirty = true; 774 } 775 } 776 0xF0000..=0xF7FFF => { 777 // PCM sound chip (odd addresses) 778 // Canonically located at $F0000-$F3FFF, mirrored at $F4000-$F7FFF 779 if WORD || address.bit(0) { 780 self.pcm.write((address & 0x3FFF) >> 1, value as u8); 781 } 782 } 783 0xF8000..=0xFFFFF => { 784 // Sub CPU registers 785 // Canonically located at $F8000-$F81FF, mirrored up to $FFFFF 786 let register_addr = address & SUB_REGISTER_ADDRESS_MASK; 787 if matches!(register_addr, 0x002 | 0x003) { 788 // Hack: Buffer writes to the word RAM control register until the next sub CPU instruction 789 // Fixes possible crashing in Silpheed due to a race condition in its word RAM handoff code 790 self.buffered_sub_register_writes.push(( 791 address, 792 if WORD { 793 BufferedWrite::Word(value) 794 } else { 795 BufferedWrite::Byte(value as u8) 796 }, 797 )); 798 } else { 799 self.sub_write_register::<WORD>(address, value); 800 } 801 } 802 _ => unreachable!("value & 0xFFFFF is always <= 0xFFFFF"), 803 } 804 } 805 806 pub fn flush_buffered_sub_writes(&mut self) { 807 if self.buffered_sub_register_writes.is_empty() { 808 return; 809 } 810 811 let mut writes = mem::take(&mut self.buffered_sub_register_writes); 812 for &(address, value) in &writes { 813 match value { 814 BufferedWrite::Byte(byte) => { 815 self.sub_write_register::<false>(address, byte.into()); 816 } 817 BufferedWrite::Word(word) => { 818 self.sub_write_register::<true>(address, word); 819 } 820 } 821 } 822 823 writes.clear(); 824 self.buffered_sub_register_writes = writes; 825 } 826 827 pub(crate) fn word_ram(&self) -> &WordRam { 828 &self.word_ram 829 } 830 831 fn cdc(&self) -> &Rchip { 832 self.disc_drive.cdc() 833 } 834 835 fn cdc_mut(&mut self) -> &mut Rchip { 836 self.disc_drive.cdc_mut() 837 } 838 839 fn cdd(&self) -> &CdDrive { 840 self.disc_drive.cdd() 841 } 842 843 fn cdd_mut(&mut self) -> &mut CdDrive { 844 self.disc_drive.cdd_mut() 845 } 846 847 pub fn reload_config(&mut self, config: &GenesisEmulatorConfig) { 848 self.forced_region = config.forced_region; 849 self.enable_ram_cartridge = config.sega_cd.enable_ram_cartridge; 850 self.cdd_mut().reload_config(&config.sega_cd); 851 self.pcm.reload_config(&config.sega_cd); 852 } 853 854 pub fn reset(&mut self) { 855 self.disc_drive.reset(); 856 self.registers = SegaCdRegisters::new(); 857 self.pcm.disable(); 858 } 859 860 pub fn region(&self) -> GenesisRegion { 861 self.forced_region.unwrap_or(self.disc_region) 862 } 863 864 pub fn disc_title(&mut self) -> SegaCdLoadResult<Option<String>> { 865 self.disc_drive.disc_title(self.region()) 866 } 867 868 pub fn has_six_button_incompatible_game(&mut self) -> SegaCdLoadResult<bool> { 869 self.disc_drive.cdd_mut().has_six_button_incompatible_game() 870 } 871 872 pub fn take_backup_ram_dirty(&mut self) -> bool { 873 mem::take(&mut self.backup_ram_dirty) 874 } 875 876 pub fn backup_ram(&self) -> &[u8] { 877 self.backup_ram.as_slice() 878 } 879 880 pub fn ram_cartridge(&self) -> &[u8] { 881 self.ram_cartridge.as_slice() 882 } 883 884 pub fn take_bios_and_disc(mut self) -> (Vec<u16>, Option<CdRom>) { 885 let bios_rom = self.bios.0.into_vec(); 886 let disc = self.disc_drive.take_disc(); 887 888 (bios_rom, disc) 889 } 890 891 pub fn take_bios_and_disc_from(&mut self, other: &mut Self) { 892 self.bios.0 = mem::take(&mut other.bios.0); 893 self.disc_drive.take_disc_from(&mut other.disc_drive); 894 } 895 896 pub fn change_disc(&mut self, disc: CdRom) { 897 self.cdd_mut().change_disc(disc); 898 } 899 900 pub fn remove_disc(&mut self) { 901 self.cdd_mut().remove_disc(); 902 } 903} 904 905impl BusInterface for SegaCdBus { 906 type DebugView<'a> 907 = DummyM68000Debugger 908 where 909 Self: 'a; 910 911 fn read_byte(&mut self, address: u32) -> u8 { 912 self.sub_read::<false>(address) as u8 913 } 914 915 fn read_word(&mut self, address: u32) -> u16 { 916 self.sub_read::<true>(address) 917 } 918 919 fn write_byte(&mut self, address: u32, value: u8) { 920 self.sub_write::<false>(address, value.into()); 921 } 922 923 fn write_word(&mut self, address: u32, value: u16) { 924 self.sub_write::<true>(address, value); 925 } 926 927 #[allow(clippy::bool_to_int_with_if)] 928 fn interrupt_level(&self) -> u8 { 929 if self.registers.cdc_interrupt_enabled && self.cdc().interrupt_pending() { 930 // INT5: CDC interrupt 931 5 932 } else if self.registers.cdd_interrupt_enabled && self.cdd().interrupt_pending() { 933 // INT4: CDD interrupt 934 4 935 } else if self.registers.timer_interrupt_enabled && self.registers.timer_interrupt_pending { 936 // INT3: Timer interrupt 937 3 938 } else if self.registers.software_interrupt_enabled 939 && self.registers.software_interrupt_pending 940 { 941 // INT2: Software interrupt from main CPU 942 2 943 } else if self.registers.graphics_interrupt_enabled 944 && self.graphics_coprocessor.interrupt_pending() 945 { 946 // INT1: Graphics interrupt 947 1 948 } else { 949 0 950 } 951 } 952 953 fn acknowledge_interrupt(&mut self, interrupt_level: u8) { 954 // Unlike the Genesis VDP, the Sega CD does appear to acknowledge the correct interrupt 955 // when the sub CPU acknowledges an interrupt. Not doing this causes some mcd-verificator 956 // tests to fail 957 match interrupt_level { 958 5 => { 959 self.cdc_mut().acknowledge_interrupt(); 960 } 961 4 => { 962 self.cdd_mut().acknowledge_interrupt(); 963 } 964 3 => { 965 self.registers.timer_interrupt_pending = false; 966 } 967 2 => { 968 self.registers.software_interrupt_pending = false; 969 } 970 1 => { 971 self.graphics_coprocessor.acknowledge_interrupt(); 972 } 973 _ => {} 974 } 975 } 976 977 fn halt(&self) -> bool { 978 self.registers.sub_cpu_busreq 979 } 980 981 fn reset(&self) -> bool { 982 self.registers.sub_cpu_reset 983 } 984} 985 986fn guess_cd_model(bios: &[u8]) -> CdModel { 987 // Official BIOS versions have the version number at the end of the serial number, e.g.: 988 // "BR 000003-1.10" (Model 1 V1.10) 989 if &bios[0x18A..0x18C] == b"1." { CdModel::One } else { CdModel::Two } 990} 991 992fn bytes_to_words_be(bytes: Vec<u8>) -> Vec<u16> { 993 bytes.as_chunks::<2>().0.iter().map(|&chunk| u16::from_be_bytes(chunk)).collect() 994}