spectrusty_core/
chip.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8//! Chipset emulation building blocks.
9use core::num::NonZeroU32;
10use core::time::Duration;
11#[cfg(not(target_arch = "wasm32"))]
12use std::time::Instant;
13
14use z80emu::{CpuDebug, Cpu, host::Result};
15
16use crate::bus::BusDevice;
17use crate::clock::FTs;
18use crate::memory::{ZxMemory, MemoryExtension};
19
20mod flags;
21pub use flags::*;
22
23/// A trait for directly accessing an emulated memory implementation and memory extensions.
24pub trait MemoryAccess {
25    type Memory: ZxMemory;
26    type MemoryExt: MemoryExtension;
27    /// Returns a read-only reference to the memory extension.
28    fn memory_ext_ref(&self) -> &Self::MemoryExt;
29    /// Returns a mutable reference to the memory extension.
30    fn memory_ext_mut(&mut self) -> &mut Self::MemoryExt;
31    /// Returns a reference to the memory.
32    fn memory_ref(&self) -> &Self::Memory;    
33    /// Returns a mutable reference to the memory.
34    fn memory_mut(&mut self) -> &mut Self::Memory;
35    /// Returns mutable references to both the memory and the memory extension.
36    fn memory_with_ext_mut(&mut self) -> (&mut Self::Memory, &mut Self::MemoryExt);
37}
38
39/// The trait for reading and modifying the state of frame and cycle counters.
40pub trait FrameState {
41    /// Returns the value of the current execution frame counter. The [FrameState] implementation should
42    /// count passing frames infinitely wrapping at 2^64.
43    fn current_frame(&self) -> u64;
44    /// Sets the frame counter to the specified value.
45    fn set_frame_counter(&mut self, fc: u64);
46    /// Returns a normalized frame counter and a T-state counter as a tuple.
47    ///
48    /// T-states are counted from 0 at the start of each frame.
49    /// This method never returns the T-state counter value below 0 or past the frame counter limit.
50    fn frame_tstate(&self) -> (u64, FTs);
51    /// Returns the current value of the T-state counter.
52    /// 
53    /// Unlike in [FrameState::frame_tstate], values return by this method can sometimes be negative as well as
54    /// exceeding the maximum number of T-states per frame.
55    fn current_tstate(&self) -> FTs;
56    /// Sets the T-state counter to the specified value modulo `<Self as Video>::FRAME_TSTATES_COUNT`.
57    fn set_frame_tstate(&mut self, ts: FTs);
58    /// Returns `true` if the value of the current T-state counter has reached a certain arbitrary limit which
59    /// is very close to the maximum number of T-states per frame.
60    fn is_frame_over(&self) -> bool;
61}
62
63/// This trait provides the interface for running the emulation and accessing instances of peripheral devices.
64///
65/// It's being implemented by the emulators of core chipsets.
66pub trait ControlUnit {
67    /// A type of a chain of emulated peripheral devices should be declared here.
68    ///
69    /// This determines which devices, the emulated computer will be able to interact with.
70    ///
71    /// An associated constant: [BusDevice::Timestamp] must match the associated type declared for
72    /// implementations of [z80emu] traits such as [Io::Timestamp] or [Memory::Timestamp].
73    ///
74    /// [z80emu]: crate::z80emu::host
75    /// [Io::Timestamp]: crate::z80emu::Io::Timestamp
76    /// [Memory::Timestamp]: crate::z80emu::Memory::Timestamp
77    type BusDevice: BusDevice;
78    /// Returns a mutable reference to the instance of the first bus device in the device chain.
79    fn bus_device_mut(&mut self) -> &mut Self::BusDevice;
80    /// Returns a reference to the the instance of the first bus device in the device chain.
81    fn bus_device_ref(&self) -> &Self::BusDevice;
82    /// Destructs self and returns the instance of the bus device.
83    fn into_bus_device(self) -> Self::BusDevice;
84    /// Performs a system reset.
85    ///
86    /// When `hard` is:
87    /// * `true` emulates a **RESET** signal being active for the `cpu` and all bus devices.
88    /// * `false` executes a `RST 0` instruction on the `cpu` but without forwarding the clock counter.
89    ///
90    /// In any case, this operation is always instant.
91    fn reset<C: Cpu>(&mut self, cpu: &mut C, hard: bool);
92    /// Triggers a non-maskable interrupt. Returns `true` if **NMI** was accepted.
93    ///
94    /// Returns `false` when the `cpu` has just executed an `EI` instruction or one of `0xDD`, `0xFD` prefixes.
95    /// In this instance, calling this method is a no-op, and it returns `false`.
96    ///
97    /// For more details see [z80emu::Cpu::nmi][crate::z80emu::Cpu::nmi].
98    fn nmi<C: Cpu>(&mut self, cpu: &mut C) -> bool;
99    /// Conditionally prepares the internal state for the next frame and executes instructions on the `cpu`
100    /// as fast as possible, until the near end of that frame.
101    fn execute_next_frame<C: Cpu>(&mut self, cpu: &mut C);
102    /// Conditionally prepares the internal state for the next frame, advances the frame counter, and wraps
103    /// the T-state counter if it is near the end of a frame.
104    ///
105    /// This method should be called after all side effects (e.g. video and audio rendering) have been taken care of.
106    /// Usually, implementations will clear internal data from a previous frame.
107    ///
108    /// Both [ControlUnit::execute_next_frame] and [ControlUnit::execute_single_step] invoke this method internally,
109    /// so the only reason to call this method from the emulator program would be to make sure internal buffers
110    /// are empty before feeding the implementation with external data that will be consumed by devices during
111    /// the next frame.
112    fn ensure_next_frame(&mut self);
113    /// Executes a single instruction on the `cpu` with the option to pass a debugging function.
114    ///
115    /// If the T-state counter value is near the end of a frame, prepares the internal state for the next frame
116    /// before executing the next instruction.
117    fn execute_single_step<C: Cpu,
118                           F: FnOnce(CpuDebug)>(
119            &mut self,
120            cpu: &mut C,
121            debug: Option<F>
122    ) -> Result<(), ()>;
123}
124
125/// A trait for reading the MIC line output.
126pub trait MicOut<'a> {
127    type PulseIter: Iterator<Item=NonZeroU32> + 'a;
128    /// Returns a frame buffered MIC output as a pulse iterator.
129    fn mic_out_pulse_iter(&'a self) -> Self::PulseIter;
130}
131
132/// A trait for feeding the EAR line input.
133pub trait EarIn {
134    /// Sets `EAR IN` bit state after the provided interval in ∆ T-states counted from the last recorded change.
135    fn set_ear_in(&mut self, ear_in: bool, delta_fts: u32);
136    /// Feeds the `EAR IN` buffer with changes.
137    ///
138    /// The provided iterator should yield time intervals measured in T-state ∆ differences after which the state
139    /// of the `EAR IN` bit should be toggled.
140    ///
141    /// `max_frames_threshold` may be optionally provided as a number of frames to limit the buffered changes.
142    /// This is useful if the given iterator provides data largely exceeding the duration of a single frame.
143    fn feed_ear_in<I: Iterator<Item=NonZeroU32>>(&mut self, fts_deltas: I, max_frames_threshold: Option<usize>);
144    /// Removes all buffered so far `EAR IN` changes.
145    ///
146    /// Changes are usually consumed only when a call is made to [crate::chip::ControlUnit::ensure_next_frame].
147    /// Provide the current value of `EAR IN` bit as `ear_in`.
148    ///
149    /// This may be useful when tape data is already buffered but the user decided to stop the tape playback
150    /// immediately.
151    fn purge_ear_in_changes(&mut self, ear_in: bool);
152    /// Returns the counter of how many times the EAR input line was read since the beginning of the current frame.
153    ///
154    /// This can be used to help to implement the autoloading of tape data.
155    fn read_ear_in_count(&self) -> u32;
156    /// Returns the current mode.
157    fn read_ear_mode(&self) -> ReadEarMode {
158        ReadEarMode::Clear
159    }
160    /// Changes the current mode.
161    fn set_read_ear_mode(&mut self, _mode: ReadEarMode) {}
162}
163
164/// A helper trait for accessing parameters of well-known host configurations.
165pub trait HostConfig {
166    /// The number of CPU cycles (T-states) per second.
167    const CPU_HZ: u32;
168    /// The number of CPU cycles (T-states) in a single execution frame.
169    const FRAME_TSTATES: FTs;
170    /// Returns the CPU rate (T-states / second) after multiplying it by the `multiplier`.
171    #[inline]
172    fn effective_cpu_rate(multiplier: f64) -> f64 {
173        Self::CPU_HZ as f64 * multiplier
174    }
175    /// Returns the duration of a single execution frame in nanoseconds after multiplying
176    /// the CPU rate by the `multiplier`.
177    #[inline]
178    fn effective_frame_duration_nanos(multiplier: f64) -> u32 {
179        let cpu_rate = Self::effective_cpu_rate(multiplier).round() as u32;
180        nanos_from_frame_tc_cpu_hz(Self::FRAME_TSTATES as u32, cpu_rate) as u32
181    }
182    /// Returns the duration of a single execution frame after multiplying the CPU rate by
183    /// the `multiplier`.
184    #[inline]
185    fn effective_frame_duration(multiplier: f64) -> Duration {
186        let cpu_rate = Self::effective_cpu_rate(multiplier).round() as u32;
187        duration_from_frame_tc_cpu_hz(Self::FRAME_TSTATES as u32, cpu_rate)
188    }
189    /// Returns the duration of a single execution frame in nanoseconds.
190    #[inline]
191    fn frame_duration_nanos() -> u32 {
192        nanos_from_frame_tc_cpu_hz(Self::FRAME_TSTATES as u32, Self::CPU_HZ) as u32
193    }
194    /// Returns the duration of a single execution frame.
195    #[inline]
196    fn frame_duration() -> Duration {
197        duration_from_frame_tc_cpu_hz(Self::FRAME_TSTATES as u32, Self::CPU_HZ)
198    }
199}
200
201/// A generic trait, useful for chipset implementations based on other underlying implementations.
202pub trait InnerAccess {
203    type Inner;
204    /// Returns a reference to the inner chipset.
205    fn inner_ref(&self) -> &Self::Inner;
206    /// Returns a mutable reference to the inner chipset.
207    fn inner_mut(&mut self) -> &mut Self::Inner;
208    /// Destructs `self` and returns the inner chipset.
209    fn into_inner(self) -> Self::Inner;
210}
211
212// pub const fn duration_from_frame_time(frame_time: f64) -> std::time::Duration {
213//     const NANOS_PER_SEC: f64 = 1_000_000_000.0;
214//     let nanos: u64 = (frame_time * NANOS_PER_SEC) as u64;
215//     std::time::Duration::from_nanos(nanos)
216// }
217
218/// Returns the number of nanoseconds from the number of T-states in a single frame and a CPU clock rate.
219pub const fn nanos_from_frame_tc_cpu_hz(frame_ts_count: u32, cpu_hz: u32) -> u64 {
220    const NANOS_PER_SEC: u64 = 1_000_000_000;
221    frame_ts_count as u64 * NANOS_PER_SEC / cpu_hz as u64
222}
223
224/// Returns a duration from the number of T-states in a single frame and a CPU clock rate.
225pub const fn duration_from_frame_tc_cpu_hz(frame_ts_count: u32, cpu_hz: u32) -> Duration {
226    let nanos = nanos_from_frame_tc_cpu_hz(frame_ts_count, cpu_hz);
227    Duration::from_nanos(nanos)
228}
229
230/// A tool for synchronizing emulation with a running thread.
231#[cfg(not(target_arch = "wasm32"))]
232pub struct ThreadSyncTimer {
233    /// The start time of a current synchronization period.
234    pub time: Instant,
235    /// The desired duration of a single synchronization period.
236    pub frame_duration: Duration,
237}
238
239#[cfg(not(target_arch = "wasm32"))]
240// #[allow(clippy::new_without_default)]
241impl ThreadSyncTimer {
242    /// Pass the real time duration of a desired synchronization period (usually a duration of a video frame).
243    pub fn new(frame_duration_nanos: u32) -> Self {
244        let frame_duration = Duration::from_nanos(frame_duration_nanos as u64);
245        ThreadSyncTimer { time: Instant::now(), frame_duration }
246    }
247    /// Sets [ThreadSyncTimer::frame_duration] from the provided `frame_duration_nanos`.
248    pub fn set_frame_duration(&mut self, frame_duration_nanos: u32) {
249        self.frame_duration = Duration::from_nanos(frame_duration_nanos as u64);
250    }
251    /// Restarts the synchronization period. Useful e.g. for resuming paused emulation.
252    pub fn restart(&mut self) -> Instant {
253        core::mem::replace(&mut self.time, Instant::now())
254    }
255    /// Calculates the difference between the desired duration of a synchronization period
256    /// and the real time that has passed from the start of the current period and levels
257    /// the difference by calling [std::thread::sleep].
258    ///
259    /// This method may be called at the end of each iteration of an emulation loop to
260    /// synchronize the running thread with a desired iteration period.
261    ///
262    /// Returns `Ok` if the thread is in sync with the emulation. In this instance the value
263    /// of [ThreadSyncTimer::frame_duration] is being added to [ThreadSyncTimer::time] to mark
264    /// the beginning of a new period.
265    ///
266    /// Returns `Err(missed_periods)` if the elapsed time exceeds the desired period duration.
267    /// In this intance the start of a new period is set to [Instant::now].
268    pub fn synchronize_thread_to_frame(&mut self) -> core::result::Result<(), u32> {
269        let frame_duration = self.frame_duration;
270        let now = Instant::now();
271        let elapsed = now.duration_since(self.time);
272        if let Some(duration) = frame_duration.checked_sub(elapsed) {
273            std::thread::sleep(duration);
274            self.time += frame_duration;
275            Ok(())
276        }
277        else {
278            let missed_frames = (elapsed.as_secs_f64() / frame_duration.as_secs_f64()).trunc() as u32;
279            self.time = now;
280            Err(missed_frames)
281        }
282    }
283    /// Returns `Some(excess_duration)` if the time elapsed from the beginning of the current
284    /// period exceeds or is equal to the desired duration of a synchronization period.
285    /// Otherwise returns `None`.
286    ///
287    /// The value returned is the excess time that has elapsed above the desired duration.
288    /// If the time elapsed equals to the `frame_duration` the returned value equals to zero.
289    ///
290    /// In case `Some` variant is returned the value of [ThreadSyncTimer::frame_duration] is
291    /// being added to [ThreadSyncTimer::time] to mark the beginning of a new period.
292    pub fn check_frame_elapsed(&mut self) -> Option<Duration> {
293        let frame_duration = self.frame_duration;
294        let elapsed = self.time.elapsed();
295        if let Some(duration) = elapsed.checked_sub(frame_duration) {
296            self.time += frame_duration;
297            return Some(duration)
298        }
299        None
300    }
301}
302
303#[cfg(target_arch = "wasm32")]
304pub struct AnimationFrameSyncTimer {
305    pub time: f64,
306    pub frame_duration_millis: f64,
307}
308
309#[cfg(target_arch = "wasm32")]
310const NANOS_PER_MILLISEC: f64 = 1_000_000.0;
311
312#[cfg(target_arch = "wasm32")]
313impl AnimationFrameSyncTimer {
314    const MAX_FRAME_LAG_THRESHOLD: u32 = 10;
315    /// Pass the `DOMHighResTimeStamp` as a first and the real time duration of a desired synchronization
316    /// period (usually a duration of a video frame) as a second argument.
317    pub fn new(time: f64, frame_duration_nanos: u32) -> Self {
318        let frame_duration_millis = frame_duration_nanos as f64 / NANOS_PER_MILLISEC;
319        AnimationFrameSyncTimer { time, frame_duration_millis }
320    }
321     /// Sets [AnimationFrameSyncTimer::frame_duration_millis] from the provided `frame_duration_nanos`.
322    pub fn set_frame_duration(&mut self, frame_duration_nanos: u32) {
323        self.frame_duration_millis = frame_duration_nanos as f64 / NANOS_PER_MILLISEC;
324    }
325   /// Restarts the synchronizaiton period. Useful for e.g. resuming paused emulation.
326    ///
327    /// Pass the value of `DOMHighResTimeStamp` as the argument.
328    pub fn restart(&mut self, time: f64) -> f64 {
329        core::mem::replace(&mut self.time, time)
330    }
331    /// Returns `Ok(number_of_frames)` required to be rendered to synchronize with the native animation frame rate.
332    ///
333    /// Pass the value of DOMHighResTimeStamp obtained from a callback argument invoked by `request_animation_frame`.
334    ///
335    /// Returns `Err(time)` if the time elapsed between this and previous call to this method is larger
336    /// than the duration of number of frames specified by [MAX_FRAME_LAG_THRESHOLD]. In this instance the
337    /// previous value of `time` is returned.
338    pub fn num_frames_to_synchronize(&mut self, time: f64) -> core::result::Result<u32, f64> {
339        let frame_duration_millis = self.frame_duration_millis;
340        let time_elapsed = time - self.time;
341        if time_elapsed > frame_duration_millis {
342            let nframes = (time_elapsed / frame_duration_millis).trunc() as u32;
343            if nframes <= Self::MAX_FRAME_LAG_THRESHOLD {
344                self.time = frame_duration_millis.mul_add(nframes as f64, self.time);
345                Ok(nframes)
346            }
347            else {
348                Err(self.restart(time))
349            }
350        }
351        else {
352            Ok(0)
353        }
354            
355    }
356    /// Returns `Some(excess_duration_millis)` if the `time` elapsed from the beginning of the current
357    /// period exceeds or is equal to the desired duration of a synchronization period.
358    /// Otherwise returns `None`.
359    ///
360    /// The value returned is the excess time that has elapsed above the desired duration.
361    /// If the time elapsed equals to the `frame_duration` the returned value equals to zero.
362    ///
363    /// In case `Some` variant is returned the value of [AnimationFrameSyncTimer::frame_duration] is
364    /// being added to [AnimationFrameSyncTimer::time] to mark the beginning of a new period.
365   pub fn check_frame_elapsed(&mut self, time: f64) -> Option<f64> {
366        let frame_duration_millis = self.frame_duration_millis;
367        let elapsed = time - self.time;
368        let excess_duration_millis = elapsed - frame_duration_millis;
369        if excess_duration_millis >= 0.0 {
370            self.time += frame_duration_millis;
371            return Some(excess_duration_millis)
372        }
373        None
374    }
375}
376
377impl<U, I> FrameState for U
378    where U: InnerAccess<Inner=I>,
379          I: FrameState
380{
381    fn current_frame(&self) -> u64 {
382        self.inner_ref().current_frame()
383    }
384
385    fn set_frame_counter(&mut self, fc: u64) {
386        self.inner_mut().set_frame_counter(fc)
387    }
388
389    fn frame_tstate(&self) -> (u64, FTs) {
390        self.inner_ref().frame_tstate()
391    }
392
393    fn current_tstate(&self) -> FTs {
394        self.inner_ref().current_tstate()
395    }
396
397    fn set_frame_tstate(&mut self, ts: FTs) {
398        self.inner_mut().set_frame_tstate(ts)
399    }
400
401    fn is_frame_over(&self) -> bool {
402        self.inner_ref().is_frame_over()
403    }
404}