32X public interface and main loop
3pub mod debug;
5use crate::api::debug::Sega32XDebugger; 6use crate::bootrom::M68kVectors; 7use crate::bus::debug::DebugSh2Bus; 8use crate::bus::{Sega32XBus, Sh2Bus}; 9use crate::registers::Access; 10use crate::{GenesisVdp, WhichCpu, bootrom}; 11use bincode::{Decode, Encode}; 12use genesis_components::cartridge::Cartridge; 13use genesis_components::vdp::BorderSize; 14use genesis_config::GenesisEmulatorConfig; 15use jgenesis_common::frontend::{FrameSize, Renderer, TimingMode}; 16use jgenesis_common::num::{GetBit, U16Ext}; 17use jgenesis_proc_macros::PartialClone; 18use sh2_emu::Sh2; 19#[cfg(feature = "test-apis")] 20use sh2_emu::bus::{AccessContext, BusInterface, OpSize}; 21#[cfg(feature = "test-apis")] 22use sh2_emu::debug::Sh2Debugger; 23use std::cmp; 24use std::fmt::Debug; 25use std::num::NonZeroU64; 26 27const M68K_DIVIDER: u64 = genesis_config::NATIVE_M68K_DIVIDER; 28const SH2_MULTIPLIER: u64 = crate::SH2_CLOCK_MULTIPLIER;
Prefer to execute SH-2 instructions in longer chunks when possible for better performance
31pub(crate) const SH2_EXECUTION_SLICE_LEN: u64 = 50;
33pub trait Sega32XAudioOutput { 34 fn collect_pwm(&mut self, sample: (f64, f64)); 35 36 fn update_pwm_source_frequency(&mut self, frequency: f64); 37} 38 39#[derive(Debug, Clone, Copy)] 40pub struct GenesisVdpInfo { 41 pub scanline: u16, 42 pub scanline_mclk: u64, 43 pub frame_size: FrameSize, 44 pub border_size: BorderSize, 45 pub scanlines_in_current_frame: u16, 46} 47 48#[derive(Debug, Clone, Encode, Decode, PartialClone)] 49pub struct Sega32X { 50 sh2_master: Sh2, 51 sh2_slave: Sh2, 52 mclk_counter: u64, 53 global_cycles: u64, 54 master_cycles: u64, 55 slave_cycles: u64, 56 sh2_clock_multiplier: Option<NonZeroU64>, 57 #[partial_clone(partial)] 58 bus: Sega32XBus, 59 m68k_vectors: Box<M68kVectors>, 60 timing_mode: TimingMode, 61 config: GenesisEmulatorConfig, 62} 63 64impl Sega32X { 65 #[must_use] 66 pub fn new( 67 timing_mode: TimingMode, 68 cartridge: Option<Cartridge>, 69 config: &GenesisEmulatorConfig, 70 ) -> Self { 71 Self { 72 sh2_master: Sh2::new("Master".into()), 73 sh2_slave: Sh2::new("Slave".into()), 74 mclk_counter: 0, 75 global_cycles: 0, 76 master_cycles: 0, 77 slave_cycles: 0, 78 sh2_clock_multiplier: none_if_default_multiplier(config.sega_32x.sh2_clock_multiplier), 79 bus: Sega32XBus::new(timing_mode, cartridge, &config.sega_32x), 80 m68k_vectors: Box::new(bootrom::new_m68k_vectors()), 81 timing_mode, 82 config: config.clone(), 83 } 84 } 85 86 #[inline] 87 pub fn tick<const DEBUG: bool>( 88 &mut self, 89 mut total_mclk_cycles: u64, 90 vdp_info: GenesisVdpInfo, 91 audio_output: &mut impl Sega32XAudioOutput, 92 debugger: &mut impl Sega32XDebugger, 93 ) { 94 while total_mclk_cycles > 0 { 95 // If an SH-2 is stalled on a cartridge ROM access, it unstalls when RV=0 96 self.bus.stalled_on_cartridge_access &= self.bus.registers.dma.rom_to_vram; 97 98 let h_interrupt_enabled = self.bus.registers.either_h_interrupt_enabled(); 99 let mclk_till_next_vdp_event = 100 self.bus.vdp.mclk_cycles_until_next_event(h_interrupt_enabled); 101 debug_assert_ne!(mclk_till_next_vdp_event, 0); 102 103 let mclk_cycles = cmp::min(mclk_till_next_vdp_event, total_mclk_cycles); 104 total_mclk_cycles -= mclk_cycles; 105 106 self.mclk_counter += mclk_cycles; 107 let (elapsed_sh2_cycles, elapsed_pwm_cycles) = match self.sh2_clock_multiplier { 108 Some(multiplier) => { 109 let multiplier = multiplier.get(); 110 let elapsed_sh2_cycles = self.mclk_counter / M68K_DIVIDER * multiplier; 111 let elapsed_pwm_cycles = elapsed_sh2_cycles / multiplier * SH2_MULTIPLIER; 112 self.mclk_counter -= elapsed_sh2_cycles * M68K_DIVIDER / multiplier; 113 114 (elapsed_sh2_cycles, elapsed_pwm_cycles) 115 } 116 None => { 117 let elapsed_sh2_cycles = self.mclk_counter / M68K_DIVIDER * SH2_MULTIPLIER; 118 self.mclk_counter -= elapsed_sh2_cycles * M68K_DIVIDER / SH2_MULTIPLIER; 119 120 (elapsed_sh2_cycles, elapsed_sh2_cycles) 121 } 122 }; 123 124 self.global_cycles += elapsed_sh2_cycles; 125 126 // SH-2s 127 self.execute_cpus::<DEBUG>(debugger); 128 129 // SH-2/SH7604 peripherals (WDT, SCI) 130 let mut peripherals_bus = Sh2Bus::create(&mut self.bus, WhichCpu::Master, 0, 0, None); 131 self.sh2_master.tick_peripherals(elapsed_pwm_cycles, &mut *peripherals_bus); 132 133 peripherals_bus.which = WhichCpu::Slave; 134 self.sh2_slave.tick_peripherals(elapsed_pwm_cycles, &mut *peripherals_bus); 135 136 // 32X VDP 137 self.bus.vdp.tick(mclk_cycles, &mut self.bus.registers, vdp_info); 138 139 // PWM chip 140 self.bus.pwm.tick(elapsed_pwm_cycles, &mut self.bus.registers, audio_output); 141 } 142 143 debug_assert_eq!(self.bus.vdp.scanline(), vdp_info.scanline); 144 debug_assert_eq!(self.bus.vdp.scanline_mclk(), vdp_info.scanline_mclk); 145 } 146 147 fn execute_cpus<const DEBUG: bool>(&mut self, debugger: &mut impl Sega32XDebugger) { 148 if self.bus.stalled_on_cartridge_access { 149 self.slave_cycles = self.global_cycles; 150 self.master_cycles = self.global_cycles; 151 return; 152 } 153 154 // Slave SH-2 155 let mut slave_bus = Sh2Bus::create( 156 &mut self.bus, 157 WhichCpu::Slave, 158 self.slave_cycles, 159 self.global_cycles, 160 Some((&mut self.sh2_master, &mut self.master_cycles)), 161 ); 162 163 self.slave_cycles = if DEBUG { 164 let mut debug_bus = DebugSh2Bus::create(slave_bus, debugger); 165 while debug_bus.cycle_counter() < debug_bus.cycle_limit() { 166 self.sh2_slave.execute(SH2_EXECUTION_SLICE_LEN, &mut *debug_bus); 167 } 168 debug_bus.cycle_counter() 169 } else { 170 while slave_bus.cycle_counter < slave_bus.cycle_limit { 171 self.sh2_slave.execute(SH2_EXECUTION_SLICE_LEN, &mut *slave_bus); 172 } 173 slave_bus.cycle_counter 174 }; 175 176 // Master SH-2 177 let mut master_bus = Sh2Bus::create( 178 &mut self.bus, 179 WhichCpu::Master, 180 self.master_cycles, 181 self.global_cycles, 182 Some((&mut self.sh2_slave, &mut self.slave_cycles)), 183 ); 184 self.master_cycles = if DEBUG { 185 let mut debug_bus = DebugSh2Bus::create(master_bus, debugger); 186 while debug_bus.cycle_counter() < debug_bus.cycle_limit() { 187 self.sh2_master.execute(SH2_EXECUTION_SLICE_LEN, &mut *debug_bus); 188 } 189 debug_bus.cycle_counter() 190 } else { 191 while master_bus.cycle_counter < master_bus.cycle_limit { 192 self.sh2_master.execute(SH2_EXECUTION_SLICE_LEN, &mut *master_bus); 193 } 194 master_bus.cycle_counter 195 }; 196 } 197 198 #[cfg(feature = "test-apis")] 199 pub fn simulate_bus_interactions<const DEBUG: bool>( 200 &mut self, 201 debugger: &mut impl Sega32XDebugger, 202 which: WhichCpu, 203 reads: &[u32], 204 writes: &[(u32, u32)], 205 ) { 206 self.global_cycles = 0; 207 self.master_cycles = 0; 208 self.slave_cycles = 0; 209 210 let (sh2, other_sh2, other_cycles) = match which { 211 WhichCpu::Master => (&mut self.sh2_master, &mut self.sh2_slave, &mut self.slave_cycles), 212 WhichCpu::Slave => (&mut self.sh2_slave, &mut self.sh2_master, &mut self.master_cycles), 213 }; 214 215 let mut bus = Sh2Bus::create(&mut self.bus, which, 0, 50, Some((other_sh2, other_cycles))); 216 bus.cycle_limit = 100; 217 218 if DEBUG { 219 let mut debug_bus = DebugSh2Bus::create(bus, debugger); 220 Self::simulate_bus_interactions_inner(&mut *debug_bus, reads, writes, sh2); 221 } else { 222 Self::simulate_bus_interactions_inner(&mut *bus, reads, writes, sh2); 223 } 224 } 225 226 #[cfg(feature = "test-apis")] 227 fn simulate_bus_interactions_inner<Bus: BusInterface>( 228 bus: &mut Bus, 229 reads: &[u32], 230 writes: &[(u32, u32)], 231 sh2: &mut Sh2, 232 ) { 233 for &address in reads { 234 if let Some(mut debug_view) = bus.debug_view() { 235 debug_view.check_read::<{ OpSize::WORD }>(address, sh2); 236 debug_view.apply_read::<{ OpSize::WORD }>(address, AccessContext::Fetch, sh2); 237 } else { 238 bus.read::<{ OpSize::WORD }>(address, AccessContext::Fetch); 239 } 240 } 241 242 for &(address, value) in writes { 243 if let Some(mut debug_view) = bus.debug_view() { 244 debug_view.check_write::<{ OpSize::WORD }>(address, value, sh2); 245 debug_view.apply_write::<{ OpSize::WORD }>( 246 address, 247 value, 248 AccessContext::Fetch, 249 sh2, 250 ); 251 } else { 252 bus.write::<{ OpSize::WORD }>(address, value, AccessContext::Fetch); 253 } 254 } 255 } 256 257 pub fn reload_config(&mut self, config: &GenesisEmulatorConfig) { 258 self.config = config.clone(); 259 260 self.sh2_clock_multiplier = 261 none_if_default_multiplier(config.sega_32x.sh2_clock_multiplier); 262 self.bus.vdp.reload_config(&config.sega_32x); 263 264 if let Some(cartridge) = &mut self.bus.cartridge { 265 cartridge.reload_config(config); 266 } 267 } 268 269 pub fn reset(&mut self) { 270 self.bus.registers.reset(); 271 } 272 273 // ADEN bit in $A15100 274 #[must_use] 275 #[inline] 276 pub fn adapter_enabled(&self) -> bool { 277 self.bus.registers.adapter_enabled 278 } 279 280 // RV bit in $A15106 281 #[must_use] 282 #[inline] 283 pub fn rom_to_vram_dma(&self) -> bool { 284 self.bus.registers.dma.rom_to_vram 285 } 286 287 #[must_use] 288 #[inline] 289 pub fn cartridge(&self) -> Option<&Cartridge> { 290 self.bus.cartridge.as_ref() 291 } 292 293 #[must_use] 294 #[inline] 295 pub fn cartridge_mut(&mut self) -> Option<&mut Cartridge> { 296 self.bus.cartridge.as_mut() 297 } 298 299 // Reads from $000000-$3FFFFF while the 32X adapter is enabled 300 #[must_use] 301 #[inline] 302 #[allow(clippy::missing_panics_doc)] 303 pub fn m68k_read_cartridge<const WORD: bool>(&mut self, address: u32, open_bus: u16) -> u16 { 304 if address < 0x000100 { 305 // 68K vector ROM + HINT vector 306 return if WORD { 307 let address = address as usize; 308 u16::from_be_bytes(self.m68k_vectors[address..address + 2].try_into().unwrap()) 309 } else { 310 self.m68k_vectors[address as usize].into() 311 }; 312 } 313 314 let open_bus_read = 315 move || if WORD { open_bus } else { open_bus.be_byte(address & 1).into() }; 316 317 if !self.rom_to_vram_dma() { 318 // $000100-$3FFFFF is not accessible on the Genesis side while RV=0 319 log::warn!("68000 invalid cartridge read while RV=0: {address:06X}"); 320 return open_bus_read(); 321 } 322 323 let Some(cartridge) = &mut self.bus.cartridge else { 324 return open_bus_read(); 325 }; 326 327 cartridge.read::<WORD>(address, open_bus) 328 } 329 330 // Writes to $000000-$3FFFFF while the 32X adapter is enabled 331 #[inline] 332 pub fn m68k_write_cartridge<const WORD: bool>(&mut self, address: u32, value: u16) { 333 if (0x70..0x74).contains(&address) { 334 // HINT vector is R/W 335 if WORD { 336 self.m68k_vectors[address as usize] = value.msb(); 337 self.m68k_vectors[(address + 1) as usize] = value.lsb(); 338 } else { 339 self.m68k_vectors[address as usize] = value as u8; 340 } 341 return; 342 } 343 344 if !self.rom_to_vram_dma() { 345 // $000100-$3FFFFF is not accessible on the Genesis side while RV=0 346 log::warn!("68000 invalid cartridge write while RV=0: {address:06X} {value:04X}"); 347 return; 348 } 349 350 let Some(cartridge) = &mut self.bus.cartridge else { return }; 351 352 cartridge.write::<WORD>(address, value); 353 } 354 355 // Reads from $800000-$9FFFFF while the 32X adapter is enabled 356 #[must_use] 357 #[inline] 358 pub fn m68k_read_memory<const WORD: bool>(&mut self, address: u32, open_bus: u16) -> u16 { 359 match address { 360 0x840000..=0x87FFFF => { 361 // Frame buffer 362 match self.bus.registers.vdp_access { 363 Access::M68k => { 364 let word = self.bus.vdp.read_frame_buffer(address); 365 if WORD { word } else { word.be_byte(address & 1).into() } 366 } 367 Access::Sh2 => { 368 log::warn!("Frame buffer 68000 read with FM=1: {address:06X}"); 369 0xFFFF 370 } 371 } 372 } 373 0x880000..=0x8FFFFF 374 if !self.rom_to_vram_dma() 375 && let Some(cartridge) = &mut self.bus.cartridge => 376 { 377 // First 512KB of cartridge 378 let rom_addr = address & 0x7FFFF; 379 cartridge.read::<WORD>(rom_addr, open_bus) 380 } 381 0x900000..=0x9FFFFF 382 if !self.rom_to_vram_dma() 383 && let Some(cartridge) = &mut self.bus.cartridge => 384 { 385 // Mappable 1MB cartridge bank 386 let rom_addr = 387 (u32::from(self.bus.registers.m68k_rom_bank) << 20) | (address & 0xFFFFF); 388 cartridge.read::<WORD>(rom_addr, open_bus) 389 } 390 _ => { 391 if (0x880000..=0x9FFFFF).contains(&address) && self.rom_to_vram_dma() { 392 log::warn!("68000 invalid cartridge read while RV=1: {address:06X}"); 393 } 394 395 if WORD { open_bus } else { open_bus.be_byte(address & 1).into() } 396 } 397 } 398 } 399 400 // Writes to $800000-$9FFFFF while the 32X adapter is enabled 401 #[inline] 402 pub fn m68k_write_memory<const WORD: bool>(&mut self, address: u32, value: u16) { 403 match address { 404 0x840000..=0x85FFFF => { 405 // Frame buffer 406 match self.bus.registers.vdp_access { 407 Access::M68k => { 408 if WORD { 409 self.bus.vdp.write_frame_buffer_word(address, value); 410 } else { 411 self.bus.vdp.write_frame_buffer_byte(address, value as u8); 412 } 413 } 414 Access::Sh2 => { 415 log::warn!("Frame buffer 68000 write with FM=1: {address:06X} {value:04X}"); 416 } 417 } 418 } 419 0x860000..=0x87FFFF => { 420 // Frame buffer overwrite image 421 match self.bus.registers.vdp_access { 422 Access::M68k => { 423 if WORD { 424 self.bus.vdp.frame_buffer_overwrite_word(address, value); 425 } else { 426 // No difference between normal and overwrite writes for byte-size 427 self.bus.vdp.write_frame_buffer_byte(address, value as u8); 428 } 429 } 430 Access::Sh2 => { 431 log::warn!("Frame buffer 68000 write with FM=1: {address:06X} {value:04X}"); 432 } 433 } 434 } 435 0x880000..=0x8FFFFF 436 if !self.rom_to_vram_dma() 437 && let Some(cartridge) = &mut self.bus.cartridge => 438 { 439 // First 512KB of cartridge 440 let rom_addr = address & 0x7FFFF; 441 cartridge.write::<WORD>(rom_addr, value); 442 } 443 0x900000..=0x9FFFFF 444 if !self.rom_to_vram_dma() 445 && let Some(cartridge) = &mut self.bus.cartridge => 446 { 447 // Mappable 1MB cartridge bank 448 let rom_addr = 449 (u32::from(self.bus.registers.m68k_rom_bank) << 20) | (address & 0xFFFFF); 450 cartridge.write::<WORD>(rom_addr, value); 451 } 452 _ => { 453 if (0x880000..=0x9FFFFF).contains(&address) && self.rom_to_vram_dma() { 454 log::warn!( 455 "68000 invalid cartridge write while RV=1: {address:06X} {value:04X}" 456 ); 457 } 458 } 459 } 460 } 461 462 // Reads from $A15000-$A15FFF (32X registers and 32X CRAM) 463 #[must_use] 464 #[inline] 465 pub fn m68k_read_register<const WORD: bool>(&mut self, address: u32, open_bus: u16) -> u16 { 466 let word = match address { 467 0xA15100..=0xA1512F => { 468 // 32X system registers 469 self.bus.registers.m68k_read(address & !1) 470 } 471 0xA15130..=0xA1513F => { 472 // PWM 473 self.bus.pwm.read_register(address & !1) 474 } 475 0xA15180..=0xA1518F => { 476 // 32X VDP 477 match self.bus.registers.vdp_access { 478 Access::M68k => self.bus.vdp.read_register(address & !1), 479 Access::Sh2 => { 480 log::warn!("68000 VDP register read while FM=1: {address:06X}"); 481 !0 482 } 483 } 484 } 485 0xA15200..=0xA153FF => { 486 // 32X CRAM 487 match self.bus.registers.vdp_access { 488 Access::M68k => self.bus.vdp.read_cram(address & !1), 489 Access::Sh2 => { 490 log::warn!("68000 CRAM read while FM=1: {address:06X}"); 491 !0 492 } 493 } 494 } 495 _ => open_bus, 496 }; 497 498 if WORD { word } else { word.be_byte(address & 1).into() } 499 } 500 501 // Writes to $A15000-$A15FFF (32X registers and 32X CRAM) 502 #[inline] 503 pub fn m68k_write_register<const WORD: bool>(&mut self, address: u32, value: u16) { 504 match address { 505 0xA15100..=0xA1512F => { 506 // 32X system registers 507 if WORD { 508 self.bus.registers.m68k_write(address, value); 509 } else { 510 self.bus.registers.m68k_write_byte(address, value as u8); 511 } 512 } 513 0xA15130..=0xA1513F => { 514 // PWM 515 if WORD { 516 self.bus.pwm.m68k_write_register(address, value); 517 } else { 518 let mut word = self.bus.pwm.read_register(address & !1); 519 if !address.bit(0) { 520 word.set_msb(value as u8); 521 } else { 522 word.set_lsb(value as u8); 523 } 524 self.bus.pwm.m68k_write_register(address & !1, word); 525 } 526 } 527 0xA15180..=0xA1518F => { 528 // 32X VDP 529 match self.bus.registers.vdp_access { 530 Access::M68k => { 531 if WORD { 532 self.bus.vdp.write_register(address, value); 533 } else { 534 self.bus.vdp.write_register_byte(address, value as u8); 535 } 536 } 537 Access::Sh2 => { 538 log::warn!( 539 "68000 VDP register write while FM=1: {address:06X} {value:04X}" 540 ); 541 } 542 } 543 } 544 0xA15200..=0xA153FF => { 545 // 32X CRAM 546 match self.bus.registers.vdp_access { 547 Access::M68k => { 548 if WORD { 549 self.bus.vdp.write_cram(address, value); 550 } else { 551 let mut word = self.bus.vdp.read_cram(address & !1); 552 if !address.bit(0) { 553 word.set_msb(value as u8); 554 } else { 555 word.set_lsb(value as u8); 556 } 557 self.bus.vdp.write_cram(address & !1, word); 558 } 559 } 560 Access::Sh2 => { 561 log::warn!("68000 CRAM write while FM=1: {address:06X} {value:04X}"); 562 } 563 } 564 } 565 _ => {} 566 } 567 } 568 569 pub fn composite_frame(&mut self, genesis_vdp: &mut GenesisVdp) { 570 self.bus.vdp.composite_frame(genesis_vdp); 571 }
Errors
Propagates any rendering errors.
576 pub fn render_frame<R: Renderer>( 577 &mut self, 578 genesis_vdp: &GenesisVdp, 579 renderer: &mut R, 580 ) -> Result<(), R::Err> { 581 let frame_size = genesis_vdp.frame_size(); 582 let aspect_ratio = self.config.aspect_ratio.to_pixel_aspect_ratio( 583 self.timing_mode, 584 frame_size, 585 self.config.to_gen_par_params(), 586 ); 587 self.bus.vdp.render_frame(genesis_vdp, aspect_ratio, renderer) 588 } 589}