Skip to main content

spectrusty/chip/
ula.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//! An emulator of Sinclair Uncommitted Logic Array chip for ZX Spectrum 16k/48k PAL/NTSC.
9#![macro_use]
10use core::fmt;
11
12mod audio;
13mod earmic;
14pub mod frame_cache;
15mod io;
16mod video;
17mod video_ntsc;
18mod plus;
19mod cpuext;
20#[cfg(feature = "formats")]
21mod screen;
22
23use core::num::Wrapping;
24
25#[allow(unused_imports)]
26use log::{error, warn, info, debug, trace};
27
28use crate::z80emu::{*, host::Result};
29#[cfg(feature = "snapshot")]
30use serde::{Serialize, Deserialize};
31
32use crate::bus::{BusDevice, VFNullDevice};
33use crate::chip::{
34    UlaControl, FrameState, ControlUnit, MemoryAccess, EarMic, ReadEarMode
35};
36use crate::video::{BorderColor, VideoFrame};
37use crate::memory::{ZxMemory, MemoryExtension, NoMemoryExtension};
38use crate::peripherals::ZXKeyboardMap;
39use crate::clock::{
40    FTs, VFrameTs, VFrameTsCounter, MemoryContention,
41    VideoTsData1, VideoTsData2, VideoTsData3
42};
43use frame_cache::UlaFrameCache;
44
45pub use cpuext::*;
46pub use video::UlaVideoFrame;
47pub use video_ntsc::UlaNTSCVidFrame;
48
49/// NTSC 16k/48k ULA (Uncommitted Logic Array).
50pub type UlaNTSC<M, B=VFNullDevice<UlaNTSCVidFrame>, X=NoMemoryExtension> = Ula<M, B, X, UlaNTSCVidFrame>;
51/// PAL 16k/48k ULA (Uncommitted Logic Array).
52pub type UlaPAL<M, B=VFNullDevice<UlaVideoFrame>, X=NoMemoryExtension> = Ula<M, B, X, UlaVideoFrame>;
53
54/// A struct implementing [MemoryContention] for addresses in the range: [0x4000, 0x7FFF] being contended.
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
56pub struct UlaMemoryContention;
57
58/// Generic 16k/48k Ferranti ULA (Uncommitted Logic Array).
59///
60/// * `M` - [ZxMemory]
61/// * `B` - [BusDevice]
62/// * `X` - [MemoryExtension]
63/// * `V` - [VideoFrame]
64///
65/// The type used for [`<B as BusDevice>::Timestamp`][BusDevice::Timestamp] should at least
66/// satisfy the condition: `From<VFrameTs<V>>`.
67#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
68#[cfg_attr(feature = "snapshot", serde(rename_all = "camelCase"))]
69#[derive(Clone)]
70pub struct Ula<M, B, X, V> {
71    pub(super) frames: Wrapping<u64>, // frame counter
72    #[cfg_attr(feature = "snapshot", serde(bound = "V: VideoFrame"))]
73    pub(super) tsc: VFrameTs<V>, // current T-state timestamp
74    pub(super) memory: M,
75    pub(super) bus: B,
76    // #[cfg_attr(feature = "snapshot", serde(default))]
77    pub(super) memext: X,
78    // keyboard
79    #[cfg_attr(feature = "snapshot", serde(skip))]
80    keyboard: ZXKeyboardMap,
81    read_ear_mode: ReadEarMode,
82    late_timings: bool,
83    // video related
84    #[cfg(feature = "boxed_frame_cache")]
85    #[cfg_attr(feature = "snapshot", serde(skip))]
86    pub(super) frame_cache: Box<UlaFrameCache<V>>,
87
88    #[cfg(not(feature = "boxed_frame_cache"))]
89    #[cfg_attr(feature = "snapshot", serde(skip))]
90    pub(super) frame_cache: UlaFrameCache<V>,
91
92    #[cfg_attr(feature = "snapshot", serde(skip))]
93    border_out_changes: Vec<VideoTsData3>, // frame timestamp with packed border on 3 bits
94    pub(super) border: BorderColor, // video frame start border color
95    pub(super) last_border: BorderColor, // last recorded change
96    // EAR, MIC
97    #[cfg_attr(feature = "snapshot", serde(skip))]
98    ear_in_changes: Vec<VideoTsData1>,  // frame timestamp with packed earin on 1 bit
99    prev_ear_in: bool, // EAR IN state before first change in ear_in_changes
100    ear_in_last_index: usize, // index into ear_in_changes of the last probed EAR IN
101    read_ear_in_count: Wrapping<u32>, // the number of EAR IN probes during the last frame
102    #[cfg_attr(feature = "snapshot", serde(skip))]
103    earmic_out_changes: Vec<VideoTsData2>, // frame timestamp with packed earmic on 2 bits
104    prev_earmic_ts: FTs, // previously recorded change timestamp
105    prev_earmic_data: EarMic, // previous frame last recorded data
106    last_earmic_data: EarMic, // last recorded data
107}
108
109impl MemoryContention for UlaMemoryContention {
110    #[inline(always)]
111    fn is_contended_address(self, address: u16) -> bool {
112        address & 0xC000 == 0x4000
113    }
114}
115
116impl<M, B, X, V: VideoFrame> FrameState for Ula<M, B, X, V> {
117    fn current_frame(&self) -> u64 {
118        self.frames.0
119    }
120
121    fn set_frame_counter(&mut self, fc: u64) {
122        self.frames = Wrapping(fc);
123    }
124
125    fn frame_tstate(&self) -> (u64, FTs) {
126        self.tsc.into_frame_tstates(self.frames.0)
127    }
128
129    fn current_tstate(&self) -> FTs {
130        self.tsc.into_tstates()
131    }
132
133    fn set_frame_tstate(&mut self, ts: FTs) {
134        let ts = ts.rem_euclid(V::FRAME_TSTATES_COUNT);
135        let tsc = VFrameTs::from_tstates(ts);
136        self.tsc = tsc
137    }
138
139    fn is_frame_over(&self) -> bool {
140        self.tsc.is_eof()
141    }
142}
143
144impl<M, B, X, V> UlaControl for Ula<M, B, X, V> {
145    fn has_late_timings(&self) -> bool {
146        self.late_timings
147    }
148
149    fn set_late_timings(&mut self, late_timings: bool) {
150        self.late_timings = late_timings;
151    }
152}
153
154impl<M, B, X, V> Default for Ula<M, B, X, V>
155where M: Default,
156      B: Default,
157      X: Default
158{
159    fn default() -> Self {
160        Ula {
161            frames: Wrapping(0),   // frame counter
162            tsc: VFrameTs::default(),
163            memory: M::default(),
164            bus: B::default(),
165            memext: X::default(),
166            // keyboard
167            keyboard: ZXKeyboardMap::empty(),
168            read_ear_mode: ReadEarMode::Issue3,
169            late_timings: false,
170            // video related
171            frame_cache: Default::default(),
172            border_out_changes: Vec::new(),
173            border: BorderColor::WHITE, // video frame start border color
174            last_border: BorderColor::WHITE, // last changed border color
175            // EAR, MIC
176            ear_in_changes:  Vec::new(),
177            prev_ear_in: false,
178            ear_in_last_index: 0,
179            read_ear_in_count: Wrapping(0),
180            earmic_out_changes: Vec::new(),
181            prev_earmic_ts: FTs::min_value(),
182            prev_earmic_data: EarMic::empty(),
183            last_earmic_data: EarMic::empty(),
184        }
185    }
186}
187
188impl<M, B, X, V> fmt::Debug for Ula<M, B, X, V>
189    where M: ZxMemory,
190          B: BusDevice,
191          X: MemoryExtension,
192          V: VideoFrame
193{
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        f.debug_struct("Ula")
196            .field("frames", &self.frames.0)
197            .field("tsc", &self.tsc)
198            .field("memory", &self.memory.mem_ref().len())
199            .field("bus", &self.bus)
200            .field("memext", &self.memext)
201            .field("keyboard", &self.keyboard)
202            .field("read_ear_mode", &self.read_ear_mode)
203            .field("late_timings", &self.late_timings)
204            .field("frame_cache", &self.frame_cache)
205            .field("border_out_changes", &self.border_out_changes.len())
206            .field("border", &self.border)
207            .field("last_border", &self.last_border)
208            .field("prev_ear_in", &self.prev_ear_in)
209            .field("ear_in_changes", &self.ear_in_changes.len())
210            .field("read_ear_in_count", &self.read_ear_in_count.0)
211            .field("earmic_out_changes", &self.earmic_out_changes.len())
212            .field("prev_earmic_data", &self.prev_earmic_data)
213            .field("last_earmic_data", &self.last_earmic_data)
214            .finish()
215    }
216}
217
218impl<M, B, X, V> MemoryAccess for Ula<M, B, X, V>
219    where M: ZxMemory, X: MemoryExtension
220{
221    type Memory = M;
222    type MemoryExt = X;
223
224    #[inline(always)]
225    fn memory_ext_ref(&self) -> &Self::MemoryExt {
226        &self.memext
227    }
228    #[inline(always)]
229    fn memory_ext_mut(&mut self) -> &mut Self::MemoryExt {
230        &mut self.memext
231    }
232    #[inline(always)]
233    fn memory_mut(&mut self) -> &mut Self::Memory {
234        &mut self.memory
235    }
236    #[inline(always)]
237    fn memory_ref(&self) -> &Self::Memory {
238        &self.memory
239    }
240
241    fn memory_with_ext_mut(&mut self) -> (&mut Self::Memory, &mut Self::MemoryExt) {
242        (&mut self.memory, &mut self.memext)
243    }
244}
245
246impl<M, B, X, V> ControlUnit for Ula<M, B, X, V>
247    where M: ZxMemory,
248          B: BusDevice,
249          B::Timestamp: From<VFrameTs<V>>,
250          X: MemoryExtension,
251          V: VideoFrame
252{
253    type BusDevice = B;
254
255    fn bus_device_mut(&mut self) -> &mut Self::BusDevice {
256        &mut self.bus
257    }
258
259    fn bus_device_ref(&self) -> &Self::BusDevice {
260        &self.bus
261    }
262
263    fn into_bus_device(self) -> Self::BusDevice {
264        self.bus
265    }
266
267    fn reset<C: Cpu>(&mut self, cpu: &mut C, hard: bool) {
268        if hard {
269            cpu.reset();
270            self.bus.reset(self.tsc.into());
271            self.memory.reset();
272        }
273        else {
274            const DEBUG: Option<CpuDebugFn> = None;
275            let mut vtsc = VFrameTsCounter::from_vframe_ts(VFrameTs::<V>::default(), UlaMemoryContention);
276            let _ = cpu.execute_instruction(self, &mut vtsc, DEBUG, opconsts::RST_00H_OPCODE);
277        }
278    }
279
280    fn nmi<C: Cpu>(&mut self, cpu: &mut C) -> bool {
281        self.ula_nmi(cpu)
282    }
283
284    fn execute_next_frame<C: Cpu>(&mut self, cpu: &mut C) {
285        while !self.ula_execute_next_frame_with_breaks(cpu) {}
286    }
287
288    fn ensure_next_frame(&mut self) {
289        self.ensure_next_frame_vtsc();
290    }
291
292    fn execute_single_step<C: Cpu, F: FnOnce(CpuDebug)>(
293            &mut self,
294            cpu: &mut C,
295            debug: Option<F>
296        ) -> Result<(),()>
297    {
298        self.ula_execute_single_step(cpu, debug)
299    }
300}
301
302impl<M, B, X, V> UlaControlExt for Ula<M, B, X, V>
303    where M: ZxMemory,
304          B: BusDevice,
305          B::Timestamp: From<VFrameTs<V>>,
306          V: VideoFrame
307{
308    fn prepare_next_frame<C: MemoryContention>(
309            &mut self,
310            mut vtsc: VFrameTsCounter<V, C>
311        ) -> VFrameTsCounter<V, C>
312    {
313        self.bus.next_frame(VFrameTs::<V>::EOF.into());
314        self.frames += Wrapping(1);
315        self.cleanup_video_frame_data();
316        self.cleanup_earmic_frame_data();
317        vtsc.wrap_frame();
318        self.tsc = vtsc.into();
319        vtsc
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use crate::memory::Memory64k;
326    use crate::video::Video;
327    use super::*;
328    type TestUla = UlaPAL::<Memory64k>;
329
330    #[test]
331    fn test_ula() {
332        assert_eq!(<TestUla as Video>::VideoFrame::FRAME_TSTATES_COUNT, 69888);
333        let ula = TestUla::default();
334        let clock = ula.current_video_clock();
335        for addr in 0x4000..0x8000 {
336            assert_eq!(clock.is_contended_address(addr), true);
337        }
338        for addr in (0x0000..0x4000).chain(0x8000..=0xFFFF) {
339            assert_eq!(clock.is_contended_address(addr), false);
340        }
341    }
342}