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