z80emu/z80/
flavours.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//! See: <https://faqwiki.zxnet.co.uk/wiki/Z80#Differences_between_NMOS_and_CMOS_Z80s>
8#[cfg(feature = "serde")]
9use serde::{Serialize, Deserialize};
10
11use super::{Z80, any::Z80Any, CpuFlags};
12
13/// A trait for implementing exceptions to the undocumented Z80 behaviour.
14///
15/// It's been [reported] that depending on the CPU technology (NMOS, CMOS) and the manufacturer (Zilog, NEC, other clones)
16/// there are certain differences of undocumented behaviour and mainly affects the way the Flags' undocumented
17/// bits 3 and 5 are being modified.
18///
19/// [reported]: https://faqwiki.zxnet.co.uk/wiki/Z80#Differences_between_NMOS_and_CMOS_Z80s
20pub trait Flavour: Clone + Copy + Default + PartialEq + Eq {
21    /// The value being actually put on the data bus while executing the undocumented instruction `OUT (C),(HL)`.
22    const CONSTANT_OUT_DATA: u8;
23    /// Should be `true` if the IFF2 is being reset early when accepting an interrupt, while an instruction
24    /// is being executed, so `LD A,I` or `LD A,R` may report modified IFF2 value.
25    const ACCEPTING_INT_RESETS_IFF2_EARLY: bool;
26    /// Returns the string identifier of this flavour.
27    fn tag() -> &'static str;
28    /// The way MEMPTR is being updated for: `LD (nnnn),A`, `LD (BC),A`, `LD (DE),A` and `OUT (nn),A`
29    /// is being controlled by this function. The current Accumulator value is being passed as `msb` and
30    /// the lower 8-bits of the current destination address is being provided as `lsb`.
31    /// The function should return the (MSB, LSB) value to set the MEMPTR with.
32    fn memptr_mix(msb: u8, lsb: u8) -> (u8, u8);
33    /// This method is being called each time before an instructions is being executed or an interrupt is being
34    /// accepted, including NMI. It might modify some state and is being used together with [Flavour::flags_modified]
35    /// and [Flavour::get_q] to prepare a value applied to bits 3 and 5 of the Flags for the SCF/CCF instructions.
36    fn begin_instruction(&mut self);
37    /// This method is being called each time an instructions modifies the Flags register.
38    fn flags_modified(&mut self);
39    /// Bits 3 and 5 of the returned value will be copied to the Flags register.
40    fn get_q(&self, acc:u8, flags: CpuFlags) -> u8;
41    /// Converts a [Z80] struct of this flavour into an [Z80Any] enum.
42    fn cpu_into_any(cpu: Z80<Self>) -> Z80Any;
43    /// Returns the contained [`Z80<Self>`][Z80] value, consuming the `cpu` value.
44    ///
45    /// # Panics
46    /// Panics if the `cpu_any` value is not a variant corresponding to this `Flavour`.
47    fn unwrap_cpu_any(cpu_any: Z80Any) -> Z80<Self>;
48    /// Should reset the state. Called by [crate::Cpu::reset]. The default implementation resets the state to default.
49    // #[inline(always)]
50    fn reset(&mut self) {
51        *self = Default::default();
52    }
53}
54
55/// The struct implements a [Flavour] that emulates the Zilog Z80 NMOS version.
56#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
57#[cfg_attr(feature = "serde", serde(default, rename_all(serialize = "camelCase")))]
58#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
59pub struct NMOS {
60    #[cfg_attr(feature = "serde", serde(alias = "flagsModified"))]
61    flags_modified: bool,
62    #[cfg_attr(feature = "serde", serde(alias = "lastFlagsModified"))]
63    last_flags_modified: bool
64}
65
66/// The struct implements a [Flavour] that emulates the Zilog Z80 CMOS version.
67#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
68#[cfg_attr(feature = "serde", serde(into = "NMOS", from = "NMOS"))]
69#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
70pub struct CMOS;
71
72/// The struct implements a [Flavour] that (supposedly) emulates the KP1858BM1 or T34BM1 clones of the Z80.
73///
74/// It differs from the NMOS implementation in the way [Flavour::memptr_mix] works.
75/// In this implementation the returned MSB is always 0.
76/// The [Flavour::ACCEPTING_INT_RESETS_IFF2_EARLY] value is `false`.
77#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
78#[cfg_attr(feature = "serde", serde(default, rename_all(serialize = "camelCase")))]
79#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
80pub struct BM1 {
81    #[cfg_attr(feature = "serde", serde(alias = "flagsModified"))]
82    flags_modified: bool,
83    #[cfg_attr(feature = "serde", serde(alias = "lastFlagsModified"))]
84    last_flags_modified: bool
85}
86
87impl Flavour for NMOS {
88    const CONSTANT_OUT_DATA: u8 = 0;
89    const ACCEPTING_INT_RESETS_IFF2_EARLY: bool = true;
90
91    fn tag() -> &'static str {
92        "NMOS"
93    }
94    #[inline(always)]
95    fn memptr_mix(msb: u8, lsb: u8) -> (u8, u8) {
96        (msb, lsb.wrapping_add(1))
97    }
98    #[inline(always)]
99    fn begin_instruction(&mut self) {
100        self.last_flags_modified = self.flags_modified;
101        self.flags_modified = false;
102    }
103    #[inline(always)]
104    fn flags_modified(&mut self) {
105        self.flags_modified = true;
106    }
107    #[inline(always)]
108    fn get_q(&self, acc: u8, flags: CpuFlags) -> u8 {
109        if self.last_flags_modified {
110            acc
111        }
112        else {
113            acc | flags.bits()
114        }
115    }
116
117    fn cpu_into_any(cpu: Z80<Self>) -> Z80Any {
118        Z80Any::NMOS(cpu)
119    }
120
121    fn unwrap_cpu_any(cpu_any: Z80Any) -> Z80<Self> {
122        cpu_any.unwrap_nmos()
123    }
124}
125
126impl Flavour for CMOS {
127    const CONSTANT_OUT_DATA: u8 = u8::max_value();
128    const ACCEPTING_INT_RESETS_IFF2_EARLY: bool = false;
129
130    fn tag() -> &'static str {
131        "CMOS"
132    }
133    #[inline(always)]
134    fn memptr_mix(msb: u8, lsb: u8) -> (u8, u8) {
135        (msb, lsb.wrapping_add(1))
136    }
137    #[inline(always)]
138    fn begin_instruction(&mut self) {}
139    #[inline(always)]
140    fn flags_modified(&mut self) {}
141    #[inline(always)]
142    fn get_q(&self, acc: u8, _flags: CpuFlags) -> u8 { acc }
143
144    fn cpu_into_any(cpu: Z80<Self>) -> Z80Any {
145        Z80Any::CMOS(cpu)
146    }
147
148    fn unwrap_cpu_any(cpu_any: Z80Any) -> Z80<Self> {
149        cpu_any.unwrap_cmos()
150    }
151}
152
153impl Flavour for BM1 {
154    const CONSTANT_OUT_DATA: u8 = 0;
155    const ACCEPTING_INT_RESETS_IFF2_EARLY: bool = false;
156
157    fn tag() -> &'static str {
158        "BM1"
159    }
160    #[inline(always)]
161    fn memptr_mix(_msb: u8, lsb: u8) -> (u8, u8) {
162        (0, lsb.wrapping_add(1))
163    }
164    #[inline(always)]
165    fn begin_instruction(&mut self) {
166        self.last_flags_modified = self.flags_modified;
167        self.flags_modified = false;
168    }
169    #[inline(always)]
170    fn flags_modified(&mut self) {
171        self.flags_modified = true;
172    }
173    #[inline(always)]
174    fn get_q(&self, acc: u8, flags: CpuFlags) -> u8 {
175        if self.last_flags_modified {
176            acc
177        }
178        else {
179            acc | flags.bits()
180        }
181    }
182
183    fn cpu_into_any(cpu: Z80<Self>) -> Z80Any {
184        Z80Any::BM1(cpu)
185    }
186
187    fn unwrap_cpu_any(cpu_any: Z80Any) -> Z80<Self> {
188        cpu_any.unwrap_bm1()
189    }
190}
191
192/// This conversion is lossy. When CMOS [Flavour] is converted back information is lost.
193impl From<NMOS> for CMOS {
194    fn from(_: NMOS) -> Self {
195        CMOS
196    }
197}
198
199impl From<CMOS> for NMOS {
200    fn from(_: CMOS) -> Self {
201        NMOS::default()
202    }
203}
204
205/// This conversion is lossy. When CMOS [Flavour] is converted back information is lost.
206impl From<BM1> for CMOS {
207    fn from(_: BM1) -> Self {
208        CMOS
209    }
210}
211
212impl From<CMOS> for BM1 {
213    fn from(_: CMOS) -> Self {
214        BM1::default()
215    }
216}
217
218impl From<NMOS> for BM1 {
219    fn from(NMOS{flags_modified, last_flags_modified}: NMOS) -> Self {
220        BM1 {flags_modified, last_flags_modified}
221    }
222}
223
224impl From<BM1> for NMOS {
225    fn from(BM1{flags_modified, last_flags_modified}: BM1) -> Self {
226        NMOS {flags_modified, last_flags_modified}
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    #[test]
235    fn flavour_works() {
236        assert_eq!(NMOS::memptr_mix(1, 1), (1, 2));
237        assert_eq!(NMOS::memptr_mix(1, 255), (1, 0));
238        assert_eq!(CMOS::memptr_mix(1, 1), (1, 2));
239        assert_eq!(CMOS::memptr_mix(1, 255), (1, 0));
240        assert_eq!(BM1::memptr_mix(1, 1), (0, 2));
241        assert_eq!(BM1::memptr_mix(1, 255), (0, 0));
242
243        let flags = CpuFlags::all();
244
245        let mut flav = NMOS::default();
246        assert!(!flav.last_flags_modified);
247        assert!(!flav.flags_modified);
248        assert_eq!(flav.get_q(0, flags), 0xFF);
249        flav.begin_instruction();
250        assert!(!flav.last_flags_modified);
251        assert!(!flav.flags_modified);
252        assert_eq!(flav.get_q(0, flags), 0xFF);
253        flav.flags_modified();
254        assert!(!flav.last_flags_modified);
255        assert!(flav.flags_modified);
256        assert_eq!(flav.get_q(0, flags), 0xFF);
257        flav.begin_instruction();
258        assert!(flav.last_flags_modified);
259        assert!(!flav.flags_modified);
260        assert_eq!(flav.get_q(0, flags), 0);
261
262        let mut flav = BM1::default();
263        assert!(!flav.last_flags_modified);
264        assert!(!flav.flags_modified);
265        assert_eq!(flav.get_q(0, flags), 0xFF);
266        flav.begin_instruction();
267        assert!(!flav.last_flags_modified);
268        assert!(!flav.flags_modified);
269        assert_eq!(flav.get_q(0, flags), 0xFF);
270        flav.flags_modified();
271        assert!(!flav.last_flags_modified);
272        assert!(flav.flags_modified);
273        assert_eq!(flav.get_q(0, flags), 0xFF);
274        flav.begin_instruction();
275        assert!(flav.last_flags_modified);
276        assert!(!flav.flags_modified);
277        assert_eq!(flav.get_q(0, flags), 0);
278
279        let mut flav = CMOS::default();
280        assert_eq!(flav.get_q(0, flags), 0);
281        flav.begin_instruction();
282        assert_eq!(flav.get_q(1, flags), 1);
283        flav.flags_modified();
284        assert_eq!(flav.get_q(2, flags), 2);
285        flav.begin_instruction();
286        assert_eq!(flav.get_q(3, flags), 3);
287    }
288
289    #[test]
290    fn flavour_conversion() {
291        let cmos: Z80<CMOS> = Z80::<BM1>::default().into_flavour();
292        let nmos = Z80::<NMOS>::from_flavour(cmos);
293        let bm1 = nmos.into_flavour::<BM1>();
294        assert_eq!(bm1, Z80::<BM1>::default());
295        let cmos1: Z80<CMOS> = Z80::<CMOS>::default();
296        let cmos2: Z80<CMOS> = Z80::<CMOS>::default();
297        assert_eq!(Z80Any::from(cmos1), Z80Any::CMOS(cmos2));
298        let nmos1: Z80<NMOS> = Z80::<NMOS>::default();
299        let nmos2: Z80<NMOS> = Z80::<NMOS>::default();
300        assert_eq!(Z80Any::from(nmos1), Z80Any::NMOS(nmos2));
301        let bm1_1: Z80<BM1> = Z80::<BM1>::default();
302        let bm1_2: Z80<BM1> = Z80::<BM1>::default();
303        assert_eq!(Z80Any::from(bm1_1), Z80Any::BM1(bm1_2));
304    }
305
306    #[cfg(feature = "serde")]
307    #[test]
308    fn flavour_serde() {
309        assert_eq!(NMOS::tag(), "NMOS");
310        assert_eq!(CMOS::tag(), "CMOS");
311        assert_eq!(BM1::tag(), "BM1");
312        assert_eq!(serde_json::to_string(&NMOS::default()).unwrap(), r#"{"flagsModified":false,"lastFlagsModified":false}"#);
313        assert_eq!(serde_json::to_string(&CMOS::default()).unwrap(), r#"{"flagsModified":false,"lastFlagsModified":false}"#);
314        assert_eq!(serde_json::to_string(&BM1::default()).unwrap(), r#"{"flagsModified":false,"lastFlagsModified":false}"#);
315        let flav: NMOS = serde_json::from_str(r#"{"flags_modified":false,"last_flags_modified":false}"#).unwrap();
316        assert!(flav == NMOS::default());
317        let flav: NMOS = serde_json::from_str(r#"{}"#).unwrap();
318        assert!(flav == NMOS::default());
319        let flav: NMOS = serde_json::from_str(r#"{"flagsModified":true,"lastFlagsModified":true}"#).unwrap();
320        assert!(flav == NMOS { flags_modified: true, last_flags_modified: true});
321        let flav: CMOS = serde_json::from_str(r#"{"flags_modified":false,"last_flags_modified":false}"#).unwrap();
322        assert!(flav == CMOS::default());
323        let flav: CMOS = serde_json::from_str(r#"{}"#).unwrap();
324        assert!(flav == CMOS::default());
325        let flav: CMOS = serde_json::from_str(r#"{"flagsModified":true,"lastFlagsModified":true}"#).unwrap();
326        assert!(flav == CMOS);
327        let flav: BM1 = serde_json::from_str(r#"{"flags_modified":false,"last_flags_modified":false}"#).unwrap();
328        assert!(flav == BM1::default());
329        let flav: BM1 = serde_json::from_str(r#"{}"#).unwrap();
330        assert!(flav == BM1::default());
331        let flav: BM1 = serde_json::from_str(r#"{"flagsModified":true,"lastFlagsModified":true}"#).unwrap();
332        assert!(flav == BM1 { flags_modified: true, last_flags_modified: true});
333    }
334}