z80emu/host.rs
1/*
2 z80emu: ZiLOG Z80 microprocessor emulation library.
3 Copyright (C) 2019-2024 Rafal Michalski
4
5 For the full copyright notice, see the lib.rs file.
6*/
7/*! This module contains traits that should be implemented by the builder of the host computer.
8
9To complete the emulation of the Z80-based computer the following trait interfaces need to be implemented:
10
11* [Clock] - to determine how the CPU cycles are being counted and when certain events take place.
12* [Io] - to establish a communication with the I/O devices.
13* [Memory] - to build a bridge between the CPU and the host's memory.
14
15When the [Cpu][crate::cpu::Cpu] is executing instructions, it calls [Clock]'s methods to increase the cycle counter,
16only hinting which cycle the currently executed instruction is at.
17It is however up to the [Clock] implementation to actually increase the counter. Some of the [Clock]'s methods return
18timestamps which in turn are being used when accessing peripherals or memory to mark certain actions on the time axis.
19
20Please see [crate::host::cycles] module for the description of each emulated cycle.
21*/
22#[cfg(feature = "std")] use std::error;
23#[cfg(feature = "serde")] use serde::{Serialize, Deserialize};
24use core::fmt;
25#[allow(unused_imports)]
26use core::num::{NonZeroU8, NonZeroU16, Wrapping};
27use core::ops::{Add, AddAssign, Deref, DerefMut};
28
29use super::opconsts::RST_38H_OPCODE;
30
31/** This module defines constants indicating the number of T-states of each of the emulated [Cpu] cycle,
32to help [Clock] implementations count those cycles.
33
34Below are the diagrams showing timings for all CPU cycles. The diagrams include the information when the
35[Io::is_irq] is being called, compared with the moment when the `INT` line is being sampled by the real CPU.
36
37# M1 (opcode fetch)
38
39M1 is indicated by a call to [Clock::add_m1]. The function should return a timestamp that is being passed
40to the [Memory::read_opcode] function and should increase the internal counter by at least [M1_CYCLE_TS].
41```text
42 T1 T2 T3 T4
43 _ _ _ _ _
44 | |_| |_| |_| |_| |_|
45 0 ^ M1_CYCLE_TS
46A0-15 |---pc--|---ir--|
47D0-7 -op-|
48MREQ --_______--___----
49RD --______----------
50M1 -________---------
51RFSH ----------_______-
52WAIT ......--..........
53 <===============> Memory::read_opcode
54 ^ Io::is_irq ¹
55INT ............___.. ¹
56```
57
58# Memory Read or Write.
59
60This cycle is indicated by a call to [Clock::add_mreq]. The function should return a timestamp that is
61being passed to one of: [Memory::read_mem], [Memory::read_mem16] or [Memory::write_mem] functions and should
62increase the internal counter by at least [MEMRW_CYCLE_TS].
63```text
64 T1 T2 T3
65 _ _ _ _
66 | |_| |_| |_| |_|
67 0 ^ MEMRW_CYCLE_TS
68A0-15 |--address--|
69D0-7 ---data-|
70MREQ --_________--
71RD --_________-- (read)
72WR ------_____-- (write)
73WAIT .....--......
74 <===========> Memory::read_mem/write_mem
75 ^ Io::is_irq ¹
76INT ........___.. ¹
77```
78
79# Input/Output.
80
81This cycle is indicated by a call to [Clock::add_io]. The function should return a timestamp that is
82being passed to one of: [Io::read_io] or [Io::write_io] functions and should increase the internal counter by
83at least [IO_CYCLE_TS].
84The aforementioned [Io] methods can in turn insert an additional number of wait states (visible here as `TW`)
85into this cycle.
86```text
87 T1 T2 TW T3
88 _ _ _ _ _
89 | |_| |_| |_| |_| |_|
90 0 ^ IO_CYCLE_TS
91 ^ IO_IORQ_LOW_TS
92A0-15 |-----port------|
93D0-7 |----data---|
94IORQ -----__________--
95RD -----__________-- (read)
96WR -----__________-- (write)
97WAIT .........__......
98 <===============> Io::read_io/write_io
99 ^ Io::is_irq ¹
100INT ............___.. ¹
101```
102
103# Interrupt Request/Acknowledge.
104
105This cycle is indicated by a call to [Clock::add_irq]. The function should return a timestamp that is
106being passed to the [Io::irq_data] function and should increase the internal counter by at least [IRQ_ACK_CYCLE_TS].
107The [Io::irq_data] method can in turn insert an additional number of wait states (visible here as `TW`)
108into this cycle.
109```text
110 T1 T2 TW TW T3 T4
111 _ _ _ _ _ _
112 | |_| |_| |_| |_| |_| |_|
113 0 ^ IRQ_ACK_CYCLE_TS
114 ^ INT_IORQ_LOW_TS
115A0-15 |------pc-------|---ir--|
116D0-7 |--data-|
117M1 -________________--------
118MREQ -------------------_____-
119IORQ -----------______--------
120WAIT .............--..........
121 <=======================> Io::irq_data
122```
123
124# RETI
125
126This is not a cycle but rather a special case for an instruction which is being used by the Z80 peripherals
127to detect the end of the interrupt service routine. The currently active device in the daisy chain, with
128the `IEI` line high, can deactivate its `IEO` line and let the device with lower priority take control over
129the `INT` line. The diagram shows when the [Io::reti] is being called while executing the `RETI` instruction.
130```text
131 T1 T2 T3 T4 T1 T2 T3 T4 T1 T2 T3 T1 T2 T3
132 _ _ _ _ _ _ _ _ _ _ _ _ _ _
133 | |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_|
134Clock |>>> add_m1 >>>>|>>> add_m1 >>>>|> add_mreq |> add_mreq |
135A0-15 |---pc--|---ir--|--pc+1-|---ir--|-----sp----|----sp+1---|
136D0-7 -ED-| | -4D-| ----lo--| ----hi--|
137 ^ Io::reti(pc+2) ^ Io::is_irq
138INT ....................................................___..
139```
140¹ The INT line is being probed with the rising edge of the final clock at the end of every instruction
141when the interrupts are enabled, except after the `EI` instruction and prefixes: `0xDD`, `0xFD`.
142
143 [Cpu]: crate::cpu::Cpu
144**/
145pub mod cycles {
146 /// The minimum number of T-states for an `M1` cycle: opcode fetch, non-maskable interrupt and a `HALT` cycle.
147 pub const M1_CYCLE_TS: u8 = 4;
148 /// The minimum number of T-states for a memory read or write cycle.
149 pub const MEMRW_CYCLE_TS: u8 = 3;
150 /// The minimum number of T-states for an `I/O` cycle before the `IORQ` goes low
151 /// and the earliest moment the value might be available on the bus.
152 pub const IO_IORQ_LOW_TS: u8 = 1;
153 /// The minimum number of T-states for an `I/O` cycle.
154 pub const IO_CYCLE_TS: u8 = 4;
155 /// The minimum number of T-states in a maskable interrupt request/acknowledge cycle before the `IORQ` goes low
156 /// and the earliest moment the value might be put on the bus.
157 pub const INT_IORQ_LOW_TS: u8 = 2;
158 /// The number of T-states in a maskable interrupt request/acknowledge cycle.
159 pub const IRQ_ACK_CYCLE_TS: u8 = 6;
160}
161
162use cycles::*;
163
164/// This trait defines an interface to the system clock from the [Cpu] emulation perspective.
165///
166/// An implementation of this trait is responsible for counting T-states during various [Cpu] cycles.
167///
168/// It is however up to the implementation to determine how the counter is being represented and updated.
169///
170/// This trait can be used to emulate the [Cpu] contention by increasing the counter more than the required
171/// [number of T-states][cycles].
172///
173/// [Cpu]: crate::cpu::Cpu
174pub trait Clock {
175 /// This type is being used for an arbitrary representation of the `limit` argument when executing
176 /// instructions. See [Cpu::execute_with_limit](crate::cpu::Cpu::execute_with_limit) for an explanation.
177 type Limit: Sized + Copy;
178 /// This type is being used for timestamping the interactions between [Cpu], [Io] and [Memory]
179 /// implementations.
180 ///
181 /// The implementations of `Clock`, [Io][Io::Timestamp] and [Memory][Memory::Timestamp] should
182 /// have the same exact type assigned as their `Timestamp` associated type in order for the
183 /// [Cpu] to execute instructions.
184 ///
185 /// Values of this type are returned by some of the methods in this trait.
186 ///
187 /// [Cpu]: crate::cpu::Cpu
188 type Timestamp: Sized;
189 /// Should return `true` if the [Clock] has reached the given `limit` otherwise should return `false`.
190 fn is_past_limit(&self, limit: Self::Limit) -> bool;
191 /// This method should increase the counter by at least [IRQ_ACK_CYCLE_TS] `6` T-states.
192 /// The method should return the timestamp that may be passed to [Io::irq_data].
193 /// It's being used at the beginning of the maskable interrupt request/acknowledge cycle.
194 /// The `pc` is a value of the program counter when the interrupt was accepted.
195 fn add_irq(&mut self, pc: u16) -> Self::Timestamp;
196 /// This method should increase the counter by at least the value given in `add_ts`.
197 /// It's being used by internal operations of the [Cpu](crate::cpu::Cpu) without any external access.
198 /// The `address` given here is whatever was put on the address bus before.
199 // fn add_no_mreq<const ADD_TS: u8>(&mut self, address: u16);
200 fn add_no_mreq(&mut self, address: u16, add_ts: NonZeroU8);
201 /// This method should increase the counter by at least [M1_CYCLE_TS] `4`
202 /// and should return the timestamp that may be passed to [Memory::read_opcode].
203 /// This method is also being used when the non-maskable interrupt is being accepted and while the
204 /// `Cpu` is wasting cycles in the `halted` state.
205 fn add_m1(&mut self, address: u16) -> Self::Timestamp;
206 /// This method should increase the counter by at least the value given in [MEMRW_CYCLE_TS] `3`
207 /// and should return the timestamp that may be passed to [Memory::read_mem],
208 // [Memory::read_mem16] or [Memory::write_mem].
209 fn add_mreq(&mut self, address: u16) -> Self::Timestamp;
210 /// This method should increase the counter by at least [IO_CYCLE_TS] `4` T-states
211 /// and should return the timestamp that may be passed to [Io::read_io] or [Io::write_io].
212 fn add_io(&mut self, port: u16) -> Self::Timestamp;
213 /// This method should increase the counter by the value given in `wait_states`.
214 /// A call to one of [Io::read_io], [Io::write_io] or [Io::irq_data] may request such additional
215 /// number of wait states to be added.
216 fn add_wait_states(&mut self, bus: u16, wait_states: NonZeroU16);
217 /// Should return the current state of the [Clock] as a timestamp.
218 fn as_timestamp(&self) -> Self::Timestamp;
219}
220
221/// This trait defines an interface for handling data provided to `IN` or from `OUT` instruction
222/// families and maskable interrupts.
223///
224/// Please see also [cycles].
225#[allow(unused_variables)]
226#[allow(clippy::wrong_self_convention)]
227pub trait Io {
228 /// This type is being used for timestamping I/O operations. See also [Clock::Timestamp].
229 type Timestamp: Sized;
230 /// A value of this type is returned when a break is being requested by [Io::write_io].
231 type WrIoBreak;
232 /// A value of this type is returned when a break is being requested by [Io::reti].
233 type RetiBreak;
234 /// Called during the `IO_IORQ` cycle when executing one of the `IN` instructions.
235 ///
236 /// Should return a single byte from the emulated device at the given `port` and the optional
237 /// number of wait states to be inserted during the `IO_IORQ` cycle.
238 ///
239 /// The `timestamp` given here has been previously returned from [Clock::add_io].
240 ///
241 /// This method is being used by the [Cpu](crate::cpu::Cpu) to read data from the I/O port.
242 ///
243 /// The default implementation returns `(u8::MAX, None)`.
244 fn read_io(&mut self, port: u16, timestamp: Self::Timestamp) -> (u8, Option<NonZeroU16>) {
245 (u8::max_value(), None)
246 }
247 /// Called during the `IO_IORQ` cycle when executing one of the `OUT` instructions.
248 ///
249 /// Should write a single `data` byte to the device at the given `port`.
250 ///
251 /// The `timestamp` given here has been previously returned from [Clock::add_io].
252 ///
253 /// Returning `Some(Self::WrIoBreak)` from this method is a request to break the execution after
254 /// the currently executed instruction completes.
255 /// See [Cpu::execute_with_limit](crate::cpu::Cpu::execute_with_limit).
256 ///
257 /// The returned tuple's second argument is an optional number of wait states to be inserted
258 /// during the `IO_IORQ` cycle.
259 ///
260 /// This method is being used by the [Cpu](crate::cpu::Cpu) to write data to the I/O port.
261 ///
262 /// The default implementation returns `(None, None)`.
263 fn write_io(&mut self, port: u16, data: u8, timestamp: Self::Timestamp) -> (Option<Self::WrIoBreak>, Option<NonZeroU16>) {
264 (None, None)
265 }
266 /// Should return `true` if the interrupt request signal - the `INT` line - is active.
267 ///
268 /// The `timestamp` given here has been previously returned from the [Clock::as_timestamp] method.
269 ///
270 /// Look at the diagrams in the [cycles] module for the information when exactly, in the execution cycle,
271 /// this method is being called.
272 ///
273 /// The default implementation returns `false`.
274 fn is_irq(&mut self, timestamp: Self::Timestamp) -> bool {
275 false
276 }
277 /// Called during the `INT_IORQ` cycle when the maskable interrupt has been requested and accepted.
278 ///
279 /// Depending on the interrupt mode this method should return a tuple with its first argument being:
280 /// * [IM 0][crate::InterruptMode::Mode0]: an opcode of a command to execute.
281 /// * [IM 1][crate::InterruptMode::Mode1]: ignored by `CPU`.
282 /// * [IM 2][crate::InterruptMode::Mode2]: the lower half of an address of the vector jump table entry.
283 /// The upper half of this address is being taken from the value of the register `I`.
284 ///
285 /// In reality, during the `INT_IORQ` cycle, a byte is being sampled from the data bus where an external
286 /// device places it while requesting an interrupt.
287 ///
288 /// The returned tuple's second argument is an optional number of wait states to be inserted
289 /// during the `INT_IORQ` cycle.
290 ///
291 /// The default implementation returns `(RST_38H_OPCODE, None)` equalizing mode 0 to mode 1.
292 fn irq_data(&mut self, pc: u16, timestamp: Self::Timestamp) -> (u8, Option<NonZeroU16>) {
293 (RST_38H_OPCODE, None)
294 }
295 /// When `RETI` instruction is being executed this method is being called to update the device
296 /// implementation instance, so another interrupt signal can be set up if necessary.
297 ///
298 /// This method is being called in the middle of the instruction execution, before the returning
299 /// address is popped from the machine stack.
300 ///
301 /// The given `address` is pointing immediately after the `RETI` instruction opcode.
302 /// The given `timestamp` is taken after the `RETI` instruction opcode was read but before popping
303 /// the return value from the stack. After calling this method the [Clock] counter will be
304 /// further increased `2 x` by the [MEMRW][MEMRW_CYCLE_TS] cycles.
305 ///
306 /// Returning `Some(Self::RetiBreak)` from this method is a request to break the execution after
307 /// the execution of `RETI` completes. See [Cpu::execute_with_limit](crate::cpu::Cpu::execute_with_limit).
308 ///
309 /// The default implementation returns `None`.
310 fn reti(&mut self, address: u16, timestamp: Self::Timestamp) -> Option<Self::RetiBreak> {
311 None
312 }
313}
314
315/// This trait defines an interface for accessing the host's memory.
316///
317/// Please also see [cycles] module.
318#[allow(unused_variables)]
319pub trait Memory {
320 /// This type is being used for timestamping memory operations. See also [Clock::Timestamp].
321 type Timestamp: Sized;
322 /// Should return a single byte read from the memory present at the given `address`.
323 ///
324 /// This method is being used by the [Cpu](crate::cpu::Cpu) to read data from memory
325 /// during the `MEMRW` read cycle.
326 ///
327 /// The `timestamp` given here has been previously returned from [Clock::add_mreq].
328 ///
329 /// For reading data during `M1` cycles [Memory::read_opcode] is being used instead.
330 ///
331 /// The default implementation forwards call to [Memory::read_debug].
332 fn read_mem(&self, address: u16, ts: Self::Timestamp) -> u8 {
333 self.read_debug(address)
334 }
335 /// Should return the unaligned, 2 consecutive bytes from the memory present at the given
336 /// `address` as a 16-bit unsigned integer in a `LE` (least significant byte first) order.
337 ///
338 /// This method is being used by the [Cpu](crate::cpu::Cpu) to read 16 bit values from memory
339 /// during the `MEMRW` read cycle.
340 ///
341 /// The real CPU splits this read but we are cutting corners here slightly.
342 ///
343 /// The `timestamp` given here has been previously returned from [Clock::add_mreq].
344 ///
345 /// The default implementation composes a 16-bit value from two calls to [Memory::read_debug].
346 fn read_mem16(&self, address: u16, ts: Self::Timestamp) -> u16 {
347 u16::from_le_bytes([self.read_debug(address), self.read_debug(address.wrapping_add(1))])
348 }
349 /// Should return a single byte read from the memory present at the given `pc` address.
350 ///
351 /// This method is being used by the [Cpu](crate::cpu::Cpu) to read instruction opcodes
352 /// from memory during the `M1` cycle and can be used e.g. for implementing ROM/instruction
353 /// traps.
354 ///
355 /// Other, non `M1` related [Cpu](crate::cpu::Cpu) read operations, are performed
356 /// via [Memory::read_mem] and [Memory::read_mem16] methods.
357 ///
358 /// * `pc` contains an address in memory from which the opcode should be read.
359 /// * `ir` contains a memory refresh value that the real CPU would put on the bus during
360 /// the memory refresh cycle.
361 /// * `timestamp` has been previously returned from [Clock::add_m1].
362 ///
363 /// The default implementation forwards call to [Memory::read_debug].
364 fn read_opcode(&mut self, pc: u16, ir: u16, ts: Self::Timestamp) -> u8 {
365 self.read_debug(pc)
366 }
367 /// Should write a byte `value` into the memory at the given `address`.
368 ///
369 /// This method is being used by the [Cpu](crate::cpu::Cpu) to write data into memory
370 /// during the `MEMRW` write cycle.
371 ///
372 /// The `timestamp` given here has been previously returned from [Clock::add_mreq].
373 ///
374 /// The default implementation is a no-op.
375 fn write_mem(&mut self, address: u16, value: u8, ts: Self::Timestamp) {}
376 /// Should return a single byte read from the memory present at the given `address`.
377 ///
378 /// This method is being used by the [Cpu](crate::cpu::Cpu) debugger to get a relative
379 /// jump argument of a conditional instruction when a condition is not satisfied.
380 /// If the debugger is not being used, this call should be optimized out by the compiler
381 /// unless your implementation performs a volatile read here.
382 ///
383 /// The default implementation returns `u8::MAX`.
384 fn read_debug(&self, address: u16) -> u8 {
385 u8::max_value()
386 }
387}
388
389/// An enum representing the execution break cause returned by various methods of the [Cpu](crate::cpu::Cpu)
390/// trait.
391#[derive(Debug)]
392pub enum BreakCause<O, R> {
393 /// The `HALT` instruction was executed.
394 Halt,
395 /// [Io::write_io] requested a break while one of the `OUT` family instructions was executed.
396 WriteIo(O),
397 /// [Io::reti] requested a break while `RETI` was executed.
398 Reti(R)
399}
400
401/// The type returned from some of the [Cpu](crate::cpu::Cpu) trait methods.
402pub type Result<O, R> = core::result::Result<(), BreakCause<O, R>>;
403
404impl<O, R> From<BreakCause<O, R>> for &str {
405 fn from(cause: BreakCause<O, R>) -> &'static str {
406 (&cause).into()
407 }
408}
409
410impl<O, R> From<&BreakCause<O, R>> for &str {
411 fn from(cause: &BreakCause<O, R>) -> &'static str {
412 match cause {
413 BreakCause::Halt => "a HALT instruction was executed",
414 BreakCause::WriteIo(_) => "an I/O write operation has requested a break",
415 BreakCause::Reti(_) => "a break was requested at the end of an interrupt service routine",
416 }
417 }
418}
419
420impl<O, R> fmt::Display for BreakCause<O, R> {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 Into::<&str>::into(self).fmt(f)
423 }
424}
425
426#[cfg(feature = "std")]
427impl<O, R> error::Error for BreakCause<O, R> where O: fmt::Debug, R: fmt::Debug {
428 fn description(&self) -> &str {
429 self.into()
430 }
431
432 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
433 None
434 }
435}
436
437/// A simple T-states counter wrapping at 2^bitsize of T.
438/// You may refer to it as a template for implementing [Clock] trait methods.
439#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
440#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
441pub struct TsCounter<T: Copy>(pub Wrapping<T>);
442
443impl<T> Clock for TsCounter<T>
444where T: Copy + PartialEq + PartialOrd + core::convert::From<u8> + core::convert::From<u16>,
445 Wrapping<T>: AddAssign + Add<Output=Wrapping<T>>
446{
447 type Limit = T;
448 type Timestamp = T;
449
450 /// Returns `true` if `self` as `T` >= `limit`. Otherwise returns `false`.
451 #[inline(always)]
452 fn is_past_limit(&self, limit: Self::Limit) -> bool {
453 (self.0).0 >= limit
454 }
455 /// Returns `self` after adding [INT_IORQ_LOW_TS] as `T`.
456 /// Updates `self` by adding [IRQ_ACK_CYCLE_TS] to the previous value of `self`.
457 #[inline]
458 fn add_irq(&mut self, _addr: u16) -> T {
459 let ts = (self.0 + Wrapping(INT_IORQ_LOW_TS.into())).0;
460 self.0 += Wrapping(IRQ_ACK_CYCLE_TS.into());
461 ts
462 }
463 /// Updates `self` by adding `add_ts` T-states to the previous value of `self`.
464 #[inline(always)]
465 fn add_no_mreq(&mut self, _addr: u16, add_ts: NonZeroU8) {
466 // fn add_no_mreq<const ADD_TS: u8>(&mut self, _addr: u16) {
467 self.0 += Wrapping(add_ts.get().into());
468 }
469 /// Returns `self` after adding [IO_IORQ_LOW_TS] as `T`.
470 /// Updates `self` by adding [IO_CYCLE_TS] to the previous value of `self`.
471 #[inline]
472 fn add_io(&mut self, _port: u16) -> T {
473 let ts = (self.0 + Wrapping(IO_IORQ_LOW_TS.into())).0;
474 self.0 += Wrapping(IO_CYCLE_TS.into());
475 ts
476 }
477 /// Updates `self` by adding [MEMRW_CYCLE_TS] to the previous value of `self`.
478 /// Returns `self` after updating as `T`.
479 #[inline(always)]
480 fn add_mreq(&mut self, _addr: u16) -> T {
481 self.0 += Wrapping(MEMRW_CYCLE_TS.into());
482 (self.0).0
483 }
484 /// Updates `self` by adding [M1_CYCLE_TS] to the previous value of `self`.
485 /// Returns `self` after updating as `T`.
486 #[inline(always)]
487 fn add_m1(&mut self, _addr: u16) -> T {
488 self.0 += Wrapping(M1_CYCLE_TS.into());
489 (self.0).0
490 }
491 /// Updates `self` by adding `wait_states` T-states to the previous value of `self`.
492 #[inline]
493 fn add_wait_states(&mut self, _bus: u16, wait_states: NonZeroU16) {
494 self.0 += Wrapping(wait_states.get().into())
495 }
496 /// Returns a copy of `self` as `T`.
497 #[inline(always)]
498 fn as_timestamp(&self) -> T {
499 (self.0).0
500 }
501}
502
503impl<T: Copy> From<T> for TsCounter<T> {
504 fn from(tsc: T) -> Self {
505 TsCounter(Wrapping(tsc))
506 }
507}
508
509impl<T: Copy> From<Wrapping<T>> for TsCounter<T> {
510 fn from(tsc: Wrapping<T>) -> Self {
511 TsCounter(tsc)
512 }
513}
514
515impl<T: Copy> Deref for TsCounter<T> {
516 type Target = Wrapping<T>;
517
518 fn deref(&self) -> &Self::Target {
519 &self.0
520 }
521}
522
523impl<T: Copy> DerefMut for TsCounter<T> {
524 fn deref_mut(&mut self) -> &mut Self::Target {
525 &mut self.0
526 }
527}
528
529#[cfg(test)]
530mod tests {
531 #[allow(unused_imports)]
532 use super::*;
533
534 #[test]
535 fn default_host_impl() {
536 #[derive(Default)]
537 struct Ctrl;
538 impl Io for Ctrl {
539 type Timestamp = i32;
540 type WrIoBreak = ();
541 type RetiBreak = ();
542 }
543 impl Memory for Ctrl {
544 type Timestamp = i32;
545 }
546 let mut ctrl = Ctrl::default();
547 for val in 0..=u16::max_value() {
548 assert_eq!(ctrl.read_io(val, 0), (0xFF, None));
549 assert_eq!(ctrl.write_io(val, 0, 0), (None, None));
550 assert_eq!(ctrl.is_irq(0), false);
551 assert_eq!(ctrl.irq_data(val, 0), (RST_38H_OPCODE, None));
552 assert_eq!(ctrl.reti(val, 0), None);
553 assert_eq!(ctrl.read_mem(val, 0), 0xFF);
554 assert_eq!(ctrl.read_mem16(val, 0), 0xFFFF);
555 assert_eq!(ctrl.read_opcode(val, val, 0), 0xFF);
556 assert_eq!(ctrl.read_debug(val), 0xFF);
557 }
558 }
559
560 #[cfg(feature = "std")]
561 #[test]
562 fn break_cause() {
563 type BrkCause = BreakCause<(),()>;
564 assert_eq!(BrkCause::Halt.to_string(), "a HALT instruction was executed");
565 assert_eq!(BrkCause::WriteIo(()).to_string(), "an I/O write operation has requested a break");
566 assert_eq!(BrkCause::Reti(()).to_string(), "a break was requested at the end of an interrupt service routine");
567 }
568
569 #[test]
570 fn ts_counter() {
571 let mut tc = TsCounter::<i32>::default();
572 assert!(tc.is_past_limit(0));
573 assert!(!tc.is_past_limit(1));
574 tc.0.0 = 1;
575 assert!(!tc.is_past_limit(2));
576 assert!(tc.is_past_limit(1));
577 assert!(tc.is_past_limit(0));
578 tc.0 = Wrapping(0).into();
579 assert_eq!(tc.add_irq(0), INT_IORQ_LOW_TS as i32);
580 assert_eq!(tc.as_timestamp(), IRQ_ACK_CYCLE_TS as i32);
581 tc = 0.into();
582 tc.add_no_mreq(0, NonZeroU8::new(3).unwrap());
583 assert_eq!(*tc, Wrapping(3i32));
584 *tc = Wrapping(0).into();
585 assert_eq!(tc.add_io(0), IO_IORQ_LOW_TS as i32);
586 assert_eq!(tc.as_timestamp(), IO_CYCLE_TS as i32);
587 tc = 0.into();
588 assert_eq!(tc.add_mreq(0), MEMRW_CYCLE_TS as i32);
589 assert_eq!(tc.as_timestamp(), MEMRW_CYCLE_TS as i32);
590 tc.0 = Wrapping(0).into();
591 assert_eq!(tc.add_m1(0), M1_CYCLE_TS as i32);
592 assert_eq!(tc.as_timestamp(), M1_CYCLE_TS as i32);
593 tc = 0.into();
594 tc.add_wait_states(0, NonZeroU16::new(7).unwrap());
595 assert_eq!(tc.as_timestamp(), 7i32);
596 }
597
598 #[cfg(feature = "serde")]
599 #[test]
600 fn tscounter_serde() {
601 let tsc: TsCounter<i32> = serde_json::from_str("0").unwrap();
602 assert_eq!(tsc, TsCounter::default());
603 let tsc = TsCounter::from(-32);
604 let sertsc = serde_json::to_string(&tsc).unwrap();
605 assert_eq!(sertsc, "-32");
606 let tsc_de: TsCounter<i32> = serde_json::from_str(&sertsc).unwrap();
607 assert_eq!(tsc, tsc_de);
608 }
609}