api.rsannotatedapi.rssource596 lines · 21.2 KB · raw

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}
591fn none_if_default_multiplier(multiplier: NonZeroU64) -> Option<NonZeroU64> {
592    match multiplier.get() {
593        SH2_MULTIPLIER => None,
594        _ => Some(multiplier),
595    }
596}