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}