spectrusty_core/chip/
flags.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*/
8use core::fmt;
9use core::convert::TryFrom;
10use core::str::FromStr;
11
12#[cfg(feature = "snapshot")]
13use serde::{Serialize, Deserialize};
14
15use bitflags::bitflags;
16
17/// This enum determines the EAR input (bit 6) read from the 0xFE port when there is no EAR input feed.
18#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ReadEarMode {
21    /// Issue 3 keyboard behavior - EAR input is 1 when EAR output is 1
22    Issue3,
23    /// Issue 2 keyboard behavior - EAR input is 1 when either EAR or MIC output is 1
24    Issue2,
25    /// Always clear - EAR input is always 0
26    Clear,
27    /// Always set - EAR input is always 1
28    Set
29}
30
31#[derive(Clone, Debug)]
32pub struct ParseReadEarModeError;
33
34bitflags! {
35    /// This type represents packed EAR and MIC output data.
36    #[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
37    #[cfg_attr(feature = "snapshot", serde(try_from = "u8", into = "u8"))]
38    #[derive(Default)]
39    pub struct EarMic: u8 {
40        const MIC    = 0b01;
41        const EAR    = 0b10;
42        const EARMIC = 0b11;
43    }
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
47pub struct TryFromU8EarMicError(pub u8);
48
49bitflags! {
50    /// ZX Spectrum's ULA port flags.
51    #[derive(Default)]
52    pub struct UlaPortFlags: u8 {
53        const BORDER_MASK   = 0b0000_0111;
54        const EAR_MIC_MASK  = 0b0001_1000;
55        const MIC_OUT       = 0b0000_1000;
56        const EAR_OUT       = 0b0001_0000;
57        const KEYBOARD_MASK = 0b0001_1111;
58        const EAR_IN        = 0b0100_0000;
59        const UNUSED_MASK   = 0b1010_0000;
60    }
61}
62
63bitflags! {
64    /// ZX Spectrum 128K / +2 memory control flags and +2A / +3 primary control flags.
65    #[derive(Default)]
66    pub struct Ula128MemFlags: u8 {
67        const RAM_BANK_MASK = 0b00_0111;
68        const SCREEN_BANK   = 0b00_1000;
69        const ROM_BANK      = 0b01_0000;
70        const LOCK_MMU      = 0b10_0000;
71    }
72}
73
74bitflags! {
75    /// ZX Spectrum +2A / +3 secondary control flags.
76    #[derive(Default)]
77    pub struct Ula3CtrlFlags: u8 {
78        const EXT_PAGING       = 0b0_0001;
79        const PAGE_LAYOUT_MASK = 0b0_0110;
80        const ROM_BANK_HI      = 0b0_0100;
81        const DISC_MOTOR       = 0b0_1000;
82        const PRINTER_STROBE   = 0b1_0000;
83        const PAGE_LAYOUT0     = 0b0_0000;
84        const PAGE_LAYOUT1     = 0b0_0010;
85        const PAGE_LAYOUT2     = 0b0_0100;
86        const PAGE_LAYOUT3     = 0b0_0110;
87    }
88}
89
90#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
91#[cfg_attr(feature = "snapshot", serde(try_from = "u8", into = "u8"))]
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93#[repr(u8)]
94pub enum Ula3Paging {
95    Banks0123 = 0,
96    Banks4567 = 1,
97    Banks4563 = 2,
98    Banks4763 = 3,
99}
100
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct TryFromU8Ula3PagingError(pub u8);
103
104bitflags! {
105    /// Timex TC2048/TC2068/TS2068 screen and memory control flags.
106    #[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
107    #[cfg_attr(feature = "snapshot", serde(from = "u8", into = "u8"))]
108    #[derive(Default)]
109    pub struct ScldCtrlFlags: u8 {
110        const SCREEN_MODE_MASK   = 0b0000_0111;
111        const SCREEN_SOURCE_MASK = 0b0000_0011;
112        const SCREEN_SECONDARY   = 0b0000_0001;
113        const SCREEN_HI_ATTRS    = 0b0000_0010;
114        const SCREEN_HI_RES      = 0b0000_0100;
115        const HIRES_COLOR_MASK   = 0b0011_1000;
116        const INTR_DISABLED      = 0b0100_0000;
117        const MAP_EX_ROM         = 0b1000_0000;
118    }
119}
120
121bitflags! {
122    /// ULAplus register port flags.
123    #[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
124    #[cfg_attr(feature = "snapshot", serde(from = "u8", into = "u8"))]
125    #[derive(Default)]
126    pub struct UlaPlusRegFlags: u8 {
127        const SCREEN_MODE_MASK   = 0b0000_0111;
128        const SCREEN_HI_ATTRS    = 0b0000_0010;
129        const SCREEN_HI_RES      = 0b0000_0100;
130        const HIRES_COLOR_MASK   = 0b0011_1000;
131        const PALETTE_MASK       = 0b0011_1111;
132        const GROUP_MASK         = 0b1100_0000;
133        const MODE_GROUP         = 0b0100_0000;
134        const PALETTE_GROUP      = 0b0000_0000;
135    }
136}
137
138bitflags! {
139    /// ULAplus data port flags when register port is set to [UlaPlusRegFlags::MODE_GROUP].
140    #[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
141    #[cfg_attr(feature = "snapshot", serde(try_from = "u8", into = "u8"))]
142    #[derive(Default)]
143    pub struct ColorMode: u8 {
144        const PALETTE   = 0b001;
145        const GRAYSCALE = 0b010;
146    }
147}
148
149#[derive(Clone, Debug, PartialEq, Eq)]
150pub struct TryFromU8ColorModeError(pub u8);
151
152/****************************** ReadEarMode ******************************/
153
154impl From<ReadEarMode> for &str {
155    fn from(mode: ReadEarMode) -> Self {
156        match mode {
157            ReadEarMode::Issue3 => "Issue 3",
158            ReadEarMode::Issue2 => "Issue 2",
159            ReadEarMode::Clear  => "Clear",
160            ReadEarMode::Set    => "Set"
161        }
162    }
163}
164
165impl fmt::Display for ReadEarMode {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        <&str>::from(*self).fmt(f)
168    }
169}
170
171impl std::error::Error for ParseReadEarModeError {}
172
173impl fmt::Display for ParseReadEarModeError {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        write!(f, "cannot parse `ReadEarMode`: unrecognized string")
176    }
177}
178
179impl FromStr for ReadEarMode {
180    type Err = ParseReadEarModeError;
181    fn from_str(mode: &str) -> core::result::Result<Self, Self::Err> {
182        if mode.eq_ignore_ascii_case("issue 3") {
183            Ok(ReadEarMode::Issue3)
184        }
185        else if mode.eq_ignore_ascii_case("issue 2") {
186            Ok(ReadEarMode::Issue2)
187        }
188        else if mode.eq_ignore_ascii_case("clear") {
189            Ok(ReadEarMode::Clear)
190        }
191        else if mode.eq_ignore_ascii_case("set") {
192            Ok(ReadEarMode::Set)
193        }
194        else {
195            Err(ParseReadEarModeError)
196        }
197    }
198}
199
200/****************************** EarMic ******************************/
201
202impl std::error::Error for TryFromU8EarMicError {}
203
204impl fmt::Display for TryFromU8EarMicError {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        write!(f, "converted integer (0x{:x}) contains extraneous bits for `EarMic`", self.0)
207    }
208}
209
210impl TryFrom<u8> for EarMic {
211    type Error = TryFromU8EarMicError;
212    fn try_from(earmic: u8) -> core::result::Result<Self, Self::Error> {
213        EarMic::from_bits(earmic).ok_or(TryFromU8EarMicError(earmic))
214    }
215}
216
217impl From<EarMic> for u8 {
218    fn from(earmic: EarMic) -> u8 {
219        earmic.bits()
220    }
221}
222
223/****************************** UlaPortFlags ******************************/
224
225impl From<UlaPortFlags> for EarMic {
226    #[inline]
227    fn from(flags: UlaPortFlags) -> Self {
228        EarMic::from_bits_truncate((flags & UlaPortFlags::EAR_MIC_MASK).bits() >> 3)
229    }
230}
231
232/****************************** Ula128MemFlags ******************************/
233
234impl Ula128MemFlags {
235    /// Returns modified flags with the last memory page RAM bank index set to `bank`.
236    pub fn with_last_ram_page_bank(self, bank: usize) -> Self {
237        (self & !Ula128MemFlags::RAM_BANK_MASK) |
238        (Ula128MemFlags::from_bits_truncate(bank as u8) & Ula128MemFlags::RAM_BANK_MASK)
239    }
240    /// Returns a RAM bank index mapped at the last memory page.
241    pub fn last_ram_page_bank(self) -> usize {
242        (self & Ula128MemFlags::RAM_BANK_MASK).bits().into()
243    }
244    /// Returns a ROM bank index mapped at the first memory page.
245    pub fn rom_page_bank(self) -> usize {
246        self.intersects(Ula128MemFlags::ROM_BANK).into()
247    }
248    /// Returns `true` if a shadow screen bank bit is 1. Otherwise returns `false`.
249    pub fn is_shadow_screen(self) -> bool {
250        self.intersects(Ula128MemFlags::SCREEN_BANK)
251    }
252    /// Returns `true` if a mmu lock bit is 1. Otherwise returns `false`.
253    pub fn is_mmu_locked(self) -> bool {
254        self.intersects(Ula128MemFlags::LOCK_MMU)
255    }
256}
257
258/****************************** Ula3CtrlFlags ******************************/
259
260impl Ula3CtrlFlags {
261    /// Returns modified flags with the special paging enabled and its layout set to the specified value.
262    pub fn with_special_paging(self, paging: Ula3Paging) -> Self {
263        ((self | Ula3CtrlFlags::EXT_PAGING) & !Ula3CtrlFlags::PAGE_LAYOUT_MASK)
264        | match paging {
265            Ula3Paging::Banks0123 => Ula3CtrlFlags::PAGE_LAYOUT0,
266            Ula3Paging::Banks4567 => Ula3CtrlFlags::PAGE_LAYOUT1,
267            Ula3Paging::Banks4563 => Ula3CtrlFlags::PAGE_LAYOUT2,
268            Ula3Paging::Banks4763 => Ula3CtrlFlags::PAGE_LAYOUT3,
269        }
270    }
271    /// Returns modified flags with the special paging disabled and with the rom bank high flag set from
272    /// the `rom_bank` bit1.
273    pub fn with_rom_page_bank_hi(mut self, rom_bank: usize) -> Self {
274        self.remove(Ula3CtrlFlags::EXT_PAGING);
275        self.set(Ula3CtrlFlags::ROM_BANK_HI, rom_bank & 2 != 0);
276        self
277    }
278    /// Returns `true` if a special paging is enabled. Otherwise returns `false`.
279    pub fn has_special_paging(self) -> bool {
280        self.intersects(Ula3CtrlFlags::EXT_PAGING)
281    }
282    /// Returns a special paging layout if it is enabled.
283    pub fn special_paging(self) -> Option<Ula3Paging> {
284        if self.has_special_paging() {
285            Some(match self & Ula3CtrlFlags::PAGE_LAYOUT_MASK {
286                Ula3CtrlFlags::PAGE_LAYOUT0 => Ula3Paging::Banks0123,
287                Ula3CtrlFlags::PAGE_LAYOUT1 => Ula3Paging::Banks4567,
288                Ula3CtrlFlags::PAGE_LAYOUT2 => Ula3Paging::Banks4563,
289                Ula3CtrlFlags::PAGE_LAYOUT3 => Ula3Paging::Banks4763,
290                _ => unreachable!()
291            })
292        }
293        else {
294            None
295        }
296    }
297    /// Returns a bit 1 value of rom bank index mapped at the first RAM page.
298    ///
299    /// The complete rom page bank index can be obtained by bitwise ORing the returned value with
300    /// the result from [Ula128MemFlags::rom_page_bank].
301    pub fn rom_page_bank_hi(self) -> usize {
302        ((self & Ula3CtrlFlags::ROM_BANK_HI).bits() >> 1).into()
303    }
304    /// Returns `true` if a disc motor bit is 1. Otherwise returns `false`.
305    pub fn is_disc_motor_on(self) -> bool {
306        self.intersects(Ula3CtrlFlags::DISC_MOTOR)
307    }
308    /// Returns `true` if a printer strobe bit is 1. Otherwise returns `false`.
309    pub fn is_printer_strobe_on(self) -> bool {
310        self.intersects(Ula3CtrlFlags::PRINTER_STROBE)
311    }
312}
313
314/****************************** Ula3Paging ******************************/
315
316impl From<Ula3Paging> for u8 {
317    fn from(paging: Ula3Paging) -> u8 {
318        paging as u8
319    }
320}
321
322impl std::error::Error for TryFromU8Ula3PagingError {}
323
324impl fmt::Display for TryFromU8Ula3PagingError {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        write!(f, "converted integer ({}) out of range for `Ula3Paging`", self.0)
327    }
328}
329
330impl TryFrom<u8> for Ula3Paging {
331    type Error = TryFromU8Ula3PagingError;
332    fn try_from(bank: u8) -> core::result::Result<Self, Self::Error> {
333        Ok(match bank {
334            0 => Ula3Paging::Banks0123,
335            1 => Ula3Paging::Banks4567,
336            2 => Ula3Paging::Banks4563,
337            3 => Ula3Paging::Banks4763,
338            _ => return Err(TryFromU8Ula3PagingError(bank))
339        })
340    }
341}
342
343impl Ula3Paging {
344    const RAM_BANKS0: [u8;4] = [0, 1, 2, 3];
345    const RAM_BANKS1: [u8;4] = [4, 5, 6, 7];
346    const RAM_BANKS2: [u8;4] = [4, 5, 6, 3];
347    const RAM_BANKS3: [u8;4] = [4, 7, 6, 3];
348    /// Returns a reference to an array of RAM bank indexes for a current paging variant layout.
349    pub fn ram_banks(self) -> &'static [u8] {
350        match self {
351            Ula3Paging::Banks0123 => &Self::RAM_BANKS0,
352            Ula3Paging::Banks4567 => &Self::RAM_BANKS1,
353            Ula3Paging::Banks4563 => &Self::RAM_BANKS2,
354            Ula3Paging::Banks4763 => &Self::RAM_BANKS3,
355        }
356    }
357    /// Returns an iterator of tuples of RAM bank indexes with memory page indexes for a current
358    /// paging variant layout.
359    pub fn ram_banks_with_pages_iter(self) -> impl Iterator<Item=(usize, u8)> {
360        self.ram_banks().iter().map(|&bank| bank as usize).zip(0..4)
361    }
362}
363
364/****************************** ScldCtrlFlags ******************************/
365
366impl ScldCtrlFlags {
367    /// Returns scren mode with hi-res colors from `flags` as ULAplus register flags in mode group.
368    #[inline]
369    pub fn into_plus_mode(self) -> UlaPlusRegFlags {
370        UlaPlusRegFlags::from_bits_truncate(
371            (self & (ScldCtrlFlags::SCREEN_MODE_MASK
372                     |ScldCtrlFlags::HIRES_COLOR_MASK)
373            ).bits()
374        )|UlaPlusRegFlags::MODE_GROUP
375    }
376    /// Returns a color index from high resolution color mask: [0, 7].
377    #[inline]
378    pub fn hires_color_index(self) -> u8 {
379        (self & ScldCtrlFlags::HIRES_COLOR_MASK).bits() >> 3
380    }
381    /// Returns `true` if a map ex rom bit is 1. Otherwise returns `false`.
382    #[inline]
383    pub fn is_map_ex_rom(self) -> bool {
384        self.intersects(ScldCtrlFlags::MAP_EX_ROM)
385    }
386    /// Returns `true` if a interrupt disabled bit is 1. Otherwise returns `false`.
387    #[inline]
388    pub fn is_intr_disabled(self) -> bool {
389        self.intersects(ScldCtrlFlags::INTR_DISABLED)
390    }
391    /// Returns `true` if a screen high resolution bit is 1. Otherwise returns `false`.
392    #[inline]
393    pub fn is_screen_hi_res(self) -> bool {
394        self.intersects(ScldCtrlFlags::SCREEN_HI_RES)
395    }
396    /// Returns `true` if a screen high color bit is 1. Otherwise returns `false`.
397    #[inline]
398    pub fn is_screen_hi_attrs(self) -> bool {
399        self.intersects(ScldCtrlFlags::SCREEN_HI_ATTRS)
400    }
401    /// Returns `true` if a screen secondary bit is 1. Otherwise returns `false`.
402    #[inline]
403    pub fn is_screen_secondary(self) -> bool {
404        self.intersects(ScldCtrlFlags::SCREEN_SECONDARY)
405    }
406}
407
408impl From<ScldCtrlFlags> for u8 {
409    #[inline]
410    fn from(flags: ScldCtrlFlags) -> u8 {
411        flags.bits()
412    }
413}
414
415impl From<u8> for ScldCtrlFlags {
416    #[inline]
417    fn from(flags: u8) -> ScldCtrlFlags {
418        ScldCtrlFlags::from_bits_truncate(flags)
419    }
420}
421
422/****************************** UlaPlusRegFlags ******************************/
423
424impl UlaPlusRegFlags {
425    /// Returns an `index` [0, 63] to the palette entry if `self` selects a palette group.
426    pub fn palette_group(self) -> Option<u8> {
427        if (self & UlaPlusRegFlags::GROUP_MASK) == UlaPlusRegFlags::PALETTE_GROUP {
428            return Some((self & UlaPlusRegFlags::PALETTE_MASK).bits())
429        }
430        None
431    }
432
433    /// Returns an `true` if `self` selects a mode group.
434    pub fn is_mode_group(self) -> bool {
435        (self & UlaPlusRegFlags::GROUP_MASK) == UlaPlusRegFlags::MODE_GROUP
436    }
437    /// Returns a color index from high resolution color mask: [0, 7].
438    #[inline]
439    pub fn hires_color_index(self) -> u8 {
440        (self & UlaPlusRegFlags::HIRES_COLOR_MASK).bits() >> 3
441    }
442    /// Returns `true` if a screen high resolution bit is 1. Otherwise returns `false`.
443    #[inline]
444    pub fn is_screen_hi_res(self) -> bool {
445        self.intersects(UlaPlusRegFlags::SCREEN_HI_RES)
446    }
447}
448
449impl From<UlaPlusRegFlags> for u8 {
450    #[inline]
451    fn from(flags: UlaPlusRegFlags) -> u8 {
452        flags.bits()
453    }
454}
455
456impl From<u8> for UlaPlusRegFlags {
457    #[inline]
458    fn from(flags: u8) -> UlaPlusRegFlags {
459        UlaPlusRegFlags::from_bits_truncate(flags)
460    }
461}
462
463/****************************** ColorMode ******************************/
464
465impl std::error::Error for TryFromU8ColorModeError {}
466
467impl fmt::Display for TryFromU8ColorModeError {
468    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469        write!(f, "converted integer (0x{:x}) contains extraneous bits for `ColorMode`", self.0)
470    }
471}
472
473impl TryFrom<u8> for ColorMode {
474    type Error = TryFromU8ColorModeError;
475    fn try_from(color_mode: u8) -> core::result::Result<Self, Self::Error> {
476        ColorMode::from_bits(color_mode).ok_or(TryFromU8ColorModeError(color_mode))
477    }
478}
479
480impl From<ColorMode> for u8 {
481    fn from(color_mode: ColorMode) -> u8 {
482        color_mode.bits()
483    }
484}