1//! Cycle counting and wait state tracking for the Genesis hardware 2 3use bincode::{Decode, Encode}; 4use genesis_components::vdp; 5use std::num::{NonZeroU32, NonZeroU64}; 6use std::{cmp, mem}; 7 8pub const Z80_DIVIDER: u64 = 15; 9pub const YM2612_DIVIDER: u64 = 7 * 6; 10pub const PSG_DIVIDER: u64 = 15; 11 12// Sync the YM2612 at least once per scanline 13pub const MAX_YM2612_LAG_MCLK: u64 = vdp::MCLK_CYCLES_PER_SCANLINE; 14 15pub const REFRESH_INTERVAL: u32 = 128; 16 17#[derive(Debug, Clone, Copy, Encode, Decode)] 18pub struct CycleCounters { 19 // Store divider as both u64 and u32 for better codegen when doing u32 division 20 pub m68k_divider: NonZeroU64, 21 pub m68k_divider_u32: NonZeroU32, 22 pub max_wait_cpu_cycles: u32, 23 pub m68k_wait_cpu_cycles: u32, 24 pub m68k_wait_counter: u8, 25 pub m68k_mclk_cycles: u64, 26 pub z80_mclk_cycles: u64, 27 pub ym2612_mclk_cycles: u64, 28 pub last_ym2612_drain_mclk: u64, 29 pub psg_mclk_cycles: u64, 30 pub m68k_refresh_counter: u32, 31 pub vdp_owns_bus: bool, 32 pub z80_halt: bool, 33} 34 35fn max_wait_cpu_cycles(m68k_divider: NonZeroU64) -> u32 { 36 // Executing for too many cycles at a time breaks assumptions in the VDP code, checked via an 37 // assert in Vdp::tick() 38 const MAX_WAIT_MCLK_CYCLES: u32 = 1225; 39 40 MAX_WAIT_MCLK_CYCLES / m68k_divider.get() as u32 41} 42 43impl CycleCounters { 44 #[inline] 45 #[must_use] 46 #[allow(clippy::missing_panics_doc)] 47 pub fn new(m68k_divider: NonZeroU64) -> Self { 48 let m68k_divider_u32 = NonZeroU32::new(m68k_divider.get() as u32).unwrap(); 49 let max_wait_cpu_cycles = max_wait_cpu_cycles(m68k_divider); 50 51 Self { 52 m68k_divider, 53 m68k_divider_u32, 54 max_wait_cpu_cycles, 55 m68k_wait_cpu_cycles: 0, 56 m68k_wait_counter: 0, 57 m68k_mclk_cycles: 0, 58 z80_mclk_cycles: 0, 59 ym2612_mclk_cycles: 0, 60 last_ym2612_drain_mclk: 0, 61 psg_mclk_cycles: 0, 62 m68k_refresh_counter: 0, 63 vdp_owns_bus: false, 64 z80_halt: false, 65 } 66 } 67 68 #[inline] 69 #[allow(clippy::missing_panics_doc)] 70 pub fn update_m68k_divider(&mut self, m68k_divider: NonZeroU64) { 71 self.m68k_divider = m68k_divider; 72 self.m68k_divider_u32 = NonZeroU32::new(m68k_divider.get() as u32).unwrap(); 73 self.max_wait_cpu_cycles = max_wait_cpu_cycles(m68k_divider); 74 75 self.m68k_wait_cpu_cycles = cmp::min(self.m68k_wait_cpu_cycles, self.max_wait_cpu_cycles); 76 } 77 78 #[inline] 79 pub fn take_m68k_wait_cpu_cycles(&mut self) -> u32 { 80 if self.m68k_wait_cpu_cycles > self.max_wait_cpu_cycles { 81 self.m68k_wait_cpu_cycles -= self.max_wait_cpu_cycles; 82 return self.max_wait_cpu_cycles; 83 } 84 85 mem::take(&mut self.m68k_wait_cpu_cycles) 86 } 87 88 #[inline] 89 #[must_use] 90 pub fn record_68k_instruction( 91 &mut self, 92 m68k_pc: u32, 93 m68k_cycles: u32, 94 m68k_wait: bool, 95 vdp_owns_bus: bool, 96 ) -> u64 { 97 const REGION_WAIT_CYCLES: [u32; 8] = [ 98 2, // $000000-$1FFFFF 99 2, // $200000-$3FFFFF 100 2, // $400000-$5FFFFF 101 2, // $600000-$7FFFFF 102 0, // $800000-$9FFFFF 103 0, // $A00000-$BFFFFF 104 0, // $C00000-$DFFFFF 105 3, // $E00000-$FFFFFF 106 ]; 107 108 // Track memory refresh delay, which stalls the 68000 for roughly 2 out of every 130 CPU cycles 109 // when executing from ROM (at least on the standalone Genesis) 110 // Clue and Super Airwolf depend on this or they will have graphical glitches, Clue in the 111 // main menu and Super Airwolf in the intro 112 // 113 // TODO this implementation is only approximate, e.g. ROM and RAM refreshes are not aligned 114 // and it's possible for the CPU to trigger both independently. A more accurate implementation 115 // requires more accurate 68000 timing, in particular tracking the exact cycles on which 116 // memory accesses occur 117 if vdp_owns_bus { 118 // Not sure this is accurate, but it's required for stable images in Direct Color DMA demos 119 self.m68k_refresh_counter = 0; 120 } else { 121 self.m68k_refresh_counter += m68k_cycles; 122 if self.m68k_refresh_counter >= REFRESH_INTERVAL { 123 if !m68k_wait { 124 let wait_cycles = REGION_WAIT_CYCLES[((m68k_pc >> 21) & 7) as usize]; 125 self.m68k_wait_cpu_cycles += wait_cycles; 126 } 127 self.m68k_refresh_counter %= REFRESH_INTERVAL; 128 } 129 } 130 131 let mclk_cycles = u64::from(m68k_cycles) * self.m68k_divider.get(); 132 self.increment_mclk_counters(mclk_cycles, vdp_owns_bus); 133 134 mclk_cycles 135 } 136 137 #[inline] 138 pub fn increment_mclk_counters(&mut self, mclk_cycles: u64, vdp_owns_bus: bool) { 139 self.vdp_owns_bus = vdp_owns_bus; 140 self.z80_halt &= vdp_owns_bus; 141 142 self.m68k_mclk_cycles += mclk_cycles; 143 if self.z80_halt { 144 self.z80_mclk_cycles += mclk_cycles; 145 } 146 } 147 148 #[inline] 149 pub fn record_z80_68k_bus_access(&mut self) { 150 // Each time the Z80 accesses the 68K bus, the Z80 is stalled for on average 3 Z80 cycles 151 // and the 68K is stalled for on average slightly less than 9.7 68K cycles (based on test ROM) 152 self.z80_mclk_cycles += 3 * Z80_DIVIDER; 153 154 // The Z80 should halt if it accesses the 68K bus during a VDP DMA or while the 68K is 155 // stalled on a VDP FIFO write 156 self.z80_halt |= self.vdp_owns_bus; 157 158 if !self.vdp_owns_bus { 159 // Not sure if it's accurate for this to be conditional, but adding this delay after 160 // a VDP DMA breaks some effects in Overdrive 161 self.m68k_wait_cpu_cycles += 9 + u32::from(self.m68k_wait_counter < 7); 162 self.m68k_wait_counter += 1; 163 if self.m68k_wait_counter == 10 { 164 self.m68k_wait_counter = 0; 165 } 166 } 167 } 168 169 #[inline] 170 pub fn record_68k_z80_bus_access(&mut self) { 171 // Each time the 68K accesses the Z80 bus, the 68K is stalled for 1 CPU cycle 172 // Pac-Man 2: The New Adventures depends on this for its audio code to work correctly 173 self.m68k_wait_cpu_cycles += 1; 174 } 175 176 #[inline] 177 #[must_use] 178 pub fn should_tick_z80(&self) -> bool { 179 self.z80_mclk_cycles + Z80_DIVIDER <= self.m68k_mclk_cycles 180 } 181 182 #[inline] 183 pub fn z80_cycle(&mut self) { 184 self.z80_mclk_cycles += Z80_DIVIDER; 185 } 186 187 #[inline] 188 #[must_use] 189 pub fn has_ym2612_ticks(&mut self) -> bool { 190 self.ym2612_mclk_cycles + YM2612_DIVIDER <= self.z80_mclk_cycles 191 } 192 193 #[inline] 194 #[must_use] 195 pub fn take_ym2612_ticks(&mut self) -> u32 { 196 let ticks = (self.z80_mclk_cycles - self.ym2612_mclk_cycles) / YM2612_DIVIDER; 197 self.ym2612_mclk_cycles += ticks * YM2612_DIVIDER; 198 ticks as u32 199 } 200 201 #[inline] 202 #[must_use] 203 pub fn should_tick_psg(&self) -> bool { 204 self.psg_mclk_cycles + PSG_DIVIDER <= self.m68k_mclk_cycles 205 } 206 207 #[inline] 208 pub fn psg_cycle(&mut self) { 209 self.psg_mclk_cycles += PSG_DIVIDER; 210 } 211 212 #[inline] 213 pub fn ym2612_sync_needed(&self) -> bool { 214 self.last_ym2612_drain_mclk + MAX_YM2612_LAG_MCLK <= self.z80_mclk_cycles 215 } 216}