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}