bus.rsannotatedbus.rssource994 lines · 37.0 KB · raw
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}