spectrusty_core/
memory.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//! Memory API.
9use core::fmt;
10use core::ops::{Bound, Range, RangeBounds};
11use std::rc::Rc;
12use std::io::{self, Read};
13
14mod extension;
15#[cfg(feature = "snapshot")] pub mod arrays;
16#[cfg(feature = "snapshot")] pub mod serde;
17
18pub use extension::*;
19
20pub const MEM16K_SIZE : usize = 0x4000;
21pub const MEM32K_SIZE : usize = 2 * MEM16K_SIZE;
22pub const MEM48K_SIZE : usize = 3 * MEM16K_SIZE;
23pub const MEM64K_SIZE : usize = 4 * MEM16K_SIZE;
24pub const MEM128K_SIZE: usize = 8 * MEM16K_SIZE;
25pub const MEM8K_SIZE  : usize = MEM16K_SIZE / 2;
26pub const SCREEN_SIZE: u16 = 0x1B00;
27
28/// Represents a single screen memory.
29pub type ScreenArray = [u8;SCREEN_SIZE as usize];
30
31/// Represents an external ROM as a shared pointer to a slice of bytes.
32pub type ExRom = Rc<[u8]>;
33
34#[non_exhaustive]
35#[derive(Debug)]
36pub enum ZxMemoryError {
37    InvalidPageIndex,
38    InvalidBankIndex,
39    UnsupportedAddressRange,
40    UnsupportedExRomPaging,
41    InvalidExRomSize,
42    Io(io::Error)
43}
44
45impl std::error::Error for ZxMemoryError {}
46
47impl fmt::Display for ZxMemoryError {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        write!(f, "{}", match self {
50            ZxMemoryError::InvalidPageIndex => "Memory page index is out of range",
51            ZxMemoryError::InvalidBankIndex => "Memory bank index is out of range",
52            ZxMemoryError::UnsupportedAddressRange => "Address range is not supported",
53            ZxMemoryError::UnsupportedExRomPaging => "EX-ROM mapping is not supported",
54            ZxMemoryError::InvalidExRomSize => "EX-ROM size is smaller than the memory page size",
55            ZxMemoryError::Io(err) => return err.fmt(f)
56        })
57    }
58}
59
60impl From<ZxMemoryError> for io::Error {
61    fn from(err: ZxMemoryError) -> Self {
62        match err {
63            ZxMemoryError::Io(err) => err,
64            e => io::Error::new(io::ErrorKind::InvalidInput, e)
65        }
66    }
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70#[repr(u8)]
71pub enum MemoryKind {
72    Rom,
73    Ram
74}
75
76/// A type returned by [ZxMemory::page_index_at].
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub struct MemPageOffset {
79    /// A kind of memory bank switched in.
80    pub kind: MemoryKind,
81    /// A page index.
82    pub index: u8,
83    /// An offset into the page slice.
84    pub offset: u16
85}
86
87/// A type yielded by [ZxMemory::for_each_page_mut].
88#[derive(Debug, PartialEq, Eq)]
89pub enum PageMutSlice<'a> {
90    Rom(&'a mut [u8]),
91    Ram(&'a mut [u8])
92}
93
94impl<'a> PageMutSlice<'a> {
95    pub fn as_mut_slice(&mut self) -> &mut [u8] {
96        match self {
97            PageMutSlice::Rom(slice)|PageMutSlice::Ram(slice) => slice
98        }
99    }
100
101    pub fn into_mut_slice(self) -> &'a mut[u8] {
102        match self {
103            PageMutSlice::Rom(slice)|PageMutSlice::Ram(slice) => slice
104        }
105    }
106
107    pub fn is_rom(&self) -> bool {
108        matches!(self, PageMutSlice::Rom(..))
109    }
110
111    pub fn as_mut_rom(&mut self) -> Option<&mut [u8]> {
112        match self {
113            PageMutSlice::Rom(slice) => Some(slice),
114            _ => None
115        }
116    }
117
118    pub fn into_mut_rom(self) -> Option<&'a mut [u8]> {
119        match self {
120            PageMutSlice::Rom(slice) => Some(slice),
121            _ => None
122        }
123    }
124
125    pub fn is_ram(&self) -> bool {
126        matches!(self, PageMutSlice::Ram(..))
127    }
128
129    pub fn as_mut_ram(&mut self) -> Option<&mut [u8]> {
130        match self {
131            PageMutSlice::Ram(slice) => Some(slice),
132            _ => None
133        }
134    }
135
136    pub fn into_mut_ram(self) -> Option<&'a mut [u8]> {
137        match self {
138            PageMutSlice::Ram(slice) => Some(slice),
139            _ => None
140        }
141    }
142}
143
144/// A type returned by some of [ZxMemory] methods.
145pub type Result<T> = core::result::Result<T, ZxMemoryError>;
146
147/// A trait for interfacing ZX Spectrum's various memory types.
148pub trait ZxMemory {
149    /// This is just a hint. Actual page sizes may vary.
150    const PAGE_SIZE: usize = 0x4000;
151    /// The size of the whole ROM, not just one bank.
152    const ROM_SIZE: usize;
153    /// The last available memory address.
154    const RAMTOP: u16;
155    /// A maximum value allowed for a `page` argument.
156    const PAGES_MAX: u8;
157    /// A maximum value allowed for a `screen_bank` argument
158    const SCR_BANKS_MAX: usize;
159    /// A maximum value allowed for a `rom_bank` argument
160    const ROM_BANKS_MAX: usize;
161    /// A maximum value allowed for a `ram_bank` argument
162    const RAM_BANKS_MAX: usize;
163
164    /// Resets memory banks.
165    fn reset(&mut self);
166    /// If `addr` is above `RAMTOP` the function should return [std::u8::MAX].
167    fn read(&self, addr: u16) -> u8;
168    /// If `addr` is above `RAMTOP` the function should return [std::u16::MAX].
169    fn read16(&self, addr: u16) -> u16;
170    /// Reads a byte from screen memory at the given `addr`.
171    ///
172    /// The screen banks are different from memory banks.
173    /// E.g. for Spectrum 128k then screen bank `0` resides in a memory bank 5 and screen bank `1`
174    /// resides in a memory bank 7. For 16k/48k Spectrum, there is only one screen bank: `0`.
175    ///
176    /// `addr` is in screen address space (0 addresses the first byte of screen memory).
177    ///
178    /// # Panics
179    /// If `addr` is above the upper limit of screen memory address space the function should panic.
180    /// If `screen_bank` doesn't exist the function should also panic.
181    fn read_screen(&self, screen_bank: usize, addr: u16) -> u8;
182    /// If `addr` is above `RAMTOP` the function should do nothing.
183    fn write(&mut self, addr: u16, val: u8);
184    /// If addr is above `RAMTOP` the function should do nothing.
185    fn write16(&mut self, addr: u16, val: u16);
186    /// Provides a continuous view into the whole memory (all banks: ROM + RAM).
187    fn mem_ref(&self) -> &[u8];
188    /// Provides a continuous view into the whole memory (all banks: ROM + RAM).
189    fn mem_mut(&mut self) -> &mut[u8];
190    /// Returns a reference to the screen memory.
191    ///
192    /// See [ZxMemory::read_screen] for an explanation of `screen_bank` argument.
193    fn screen_ref(&self, screen_bank: usize) -> Result<&ScreenArray>;
194    /// Returns a mutable reference to the screen memory.
195    ///
196    /// See [ZxMemory::read_screen] for an explanation of `screen_bank` argument.
197    fn screen_mut(&mut self, screen_bank: usize) -> Result<&mut ScreenArray>;
198    /// Returns an enum describing what kind of memory is currently paged at the specified `page`.
199    ///
200    /// If an EX-ROM bank is currently mapped at the specified `page` a [MemoryKind::Rom] will be returned
201    /// in this instance.
202    ///
203    /// `page` should be less or equal to PAGES_MAX.
204    fn page_kind(&self, page: u8) -> Result<MemoryKind>;
205    /// Returns a tuple of an enum describing what kind of memory and which bank of that memory is
206    /// currently paged at the specified `page`.
207    ///
208    /// # Note
209    /// Unlike [ZxMemory::page_kind] this method ignores if an EX-ROM bank is currently mapped at the
210    /// specified `page`. In this instance, the returned value corresponds to a memory bank that would
211    /// be mapped at the `page` if the EX-ROM bank wasn't mapped.
212    ///
213    /// `page` should be less or equal to PAGES_MAX.
214    fn page_bank(&self, page: u8) -> Result<(MemoryKind, usize)>;
215    /// `page` should be less or equal to PAGES_MAX.
216    fn page_ref(&self, page: u8) -> Result<&[u8]>;
217    /// `page` should be less or equal to PAGES_MAX.
218    fn page_mut(&mut self, page: u8) -> Result<&mut[u8]>;
219    /// `rom_bank` should be less or equal to `ROM_BANKS_MAX`.
220    fn rom_bank_ref(&self, rom_bank: usize) -> Result<&[u8]>;
221    /// `rom_bank` should be less or equal to `ROM_BANKS_MAX`.
222    fn rom_bank_mut(&mut self, rom_bank: usize) -> Result<&mut[u8]>;
223    /// `ram_bank` should be less or equal to `RAM_BANKS_MAX`.
224    fn ram_bank_ref(&self, ram_bank: usize) -> Result<&[u8]>;
225    /// `ram_bank` should be less or equal to `RAM_BANKS_MAX`.
226    fn ram_bank_mut(&mut self, ram_bank: usize) -> Result<&mut[u8]>;
227    /// `rom_bank` should be less or equal to `ROM_BANKS_MAX` and `page` should be less or equal to PAGES_MAX.
228    fn map_rom_bank(&mut self, rom_bank: usize, page: u8) -> Result<()>;
229    /// Maps EX-ROM bank at the specified `page`.
230    ///
231    /// `exrom_bank` should be one of the attachable EX-ROMS and `page` should be less or equal to PAGES_MAX.
232    ///
233    /// Only one EX-ROM can be mapped at the same time. If an EX-ROM bank is already mapped when calling
234    /// this function it will be unmapped first, regardless of the page, the previous EX-ROM bank has been
235    /// mapped at.
236    ///
237    /// Not all types of memory support attaching external ROMs.
238    fn map_exrom(&mut self, _exrom_bank: ExRom, _page: u8) -> Result<()> {
239        Err(ZxMemoryError::UnsupportedExRomPaging)
240    }
241    /// Unmaps an external ROM if the currently mapped EX-ROM bank is the same as in the argument.
242    /// Otherwise does nothing.
243    fn unmap_exrom(&mut self, _exrom_bank: &ExRom) { }
244    /// Returns `true` if an EX-ROM bank is currently being mapped at the specified memory `page`.
245    fn is_exrom_at(&self, _page: u8) -> bool { false }
246    /// Returns `true` if a specified EX-ROM bank is currently being mapped.
247    fn has_mapped_exrom(&self, _exrom_bank: &ExRom) -> bool { false }
248    /// `ram_bank` should be less or equal to `RAM_BANKS_MAX` and `page` should be less or equal to PAGES_MAX.
249    fn map_ram_bank(&mut self, ram_bank: usize, page: u8) -> Result<()>;
250    /// Returns `Ok(MemPageOffset)` if address is equal to or less than [ZxMemory::RAMTOP].
251    fn page_index_at(&self, address: u16) -> Result<MemPageOffset> {
252        let index = (address / Self::PAGE_SIZE as u16) as u8;
253        let offset = address % Self::PAGE_SIZE as u16;
254        let kind = self.page_kind(index)?;
255        Ok(MemPageOffset {kind, index, offset})
256    }
257    /// Provides a continuous view into the ROM memory (all banks).
258    fn rom_ref(&self) -> &[u8] {
259        &self.mem_ref()[0..Self::ROM_SIZE]
260    }
261    /// Provides a continuous mutable view into the ROM memory (all banks).
262    fn rom_mut(&mut self) -> &mut [u8] {
263        &mut self.mem_mut()[0..Self::ROM_SIZE]
264    }
265    /// Provides a continuous view into RAM (all banks).
266    fn ram_ref(&self) -> &[u8] {
267        &self.mem_ref()[Self::ROM_SIZE..]
268    }
269    /// Provides a continuous mutable view into RAM (all banks).
270    fn ram_mut(&mut self) -> &mut [u8] {
271        &mut self.mem_mut()[Self::ROM_SIZE..]
272    }
273    /// The data read depends on how big ROM is [ZxMemory::ROM_SIZE].
274    /// Results in an error when the ROM data size is less than the `ROM_SIZE`.
275    fn load_into_rom<R: Read>(&mut self, mut rd: R) -> Result<()> {
276        let slice = self.rom_mut();
277        rd.read_exact(slice).map_err(ZxMemoryError::Io)
278    }
279    /// Results in an error when the ROM data size is less than the ROM bank's size.
280    fn load_into_rom_bank<R: Read>(&mut self, rom_bank: usize, mut rd: R) -> Result<()> {
281        let slice = self.rom_bank_mut(rom_bank)?;
282        rd.read_exact(slice).map_err(ZxMemoryError::Io)
283    }
284    /// Returns an iterator of memory page slice references intersecting with a given address range.
285    ///
286    /// # Errors
287    /// May return an [ZxMemoryError::UnsupportedAddressRange] error.
288    fn iter_pages<A: RangeBounds<u16>>(
289            &self,
290            address_range: A,
291        ) -> Result<MemPageRefIter<'_, Self>> {
292        let range = normalize_address_range(address_range, 0, Self::RAMTOP)
293                    .map_err(|_| ZxMemoryError::UnsupportedAddressRange)?;
294        let cursor = range.start;
295        let end = range.end;
296        Ok(MemPageRefIter { mem: self, cursor, end })
297    }
298    /// Iterates over mutable memory page slices [PageMutSlice] intersecting with a given address range
299    /// passing the slices to the closure `f`.
300    ///
301    /// # Errors
302    /// May return an [ZxMemoryError::UnsupportedAddressRange] error or an error returned by the provided closure.
303    fn for_each_page_mut<A: RangeBounds<u16>, F>(
304            &mut self,
305            address_range: A,
306            mut f: F
307        ) -> Result<()>
308        where for<'a> F: FnMut(PageMutSlice<'a>) -> Result<()>
309    {
310        let range = normalize_address_range(address_range, 0, Self::RAMTOP)
311                    .map_err(|_| ZxMemoryError::UnsupportedAddressRange)?;
312        let cursor = range.start;
313        let end = range.end;
314        let iter = MemPageMutIter { mem: self, cursor, end };
315        for page in iter {
316            f(page)?
317        }
318        Ok(())
319    }
320    /// Reads data into a paged-in memory area at the given address range.
321    fn load_into_mem<A: RangeBounds<u16>, R: Read>(&mut self, address_range: A, mut rd: R) -> Result<()> {
322        self.for_each_page_mut(address_range, |page| {
323            match page {
324                PageMutSlice::Rom(slice)|PageMutSlice::Ram(slice) => {
325                    rd.read_exact(slice).map_err(ZxMemoryError::Io)
326                }
327            }
328        })
329    }
330    /// Fills currently paged-in pages with the data produced by the closure F.
331    ///
332    /// Useful to fill RAM with random bytes.
333    ///
334    /// Provide the address range.
335    ///
336    /// *NOTE*: this will overwrite both ROM and RAM locations.
337    fn fill_mem<R, F>(&mut self, address_range: R, mut f: F) -> Result<()>
338    where R: RangeBounds<u16>, F: FnMut() -> u8
339    {
340        self.for_each_page_mut(address_range, |page| {
341            for p in page.into_mut_slice().iter_mut() {
342                *p = f()
343            }
344            Ok(())
345        })
346    }
347}
348
349pub struct MemPageRefIter<'a, Z: ?Sized> {
350    mem: &'a Z,
351    cursor: usize,
352    end: usize
353}
354
355impl<'a, Z: ZxMemory + ?Sized> Iterator for MemPageRefIter<'a, Z> {
356    type Item = &'a [u8];
357    fn next(&mut self) -> Option<Self::Item> {
358        let cursor = self.cursor;
359        let end = self.end;
360        if cursor < end {
361            let MemPageOffset { index, offset, .. } = self.mem.page_index_at(cursor as u16).unwrap();
362            let offset = offset as usize;
363            let page = self.mem.page_ref(index).unwrap();
364            let read_len = end - cursor;
365            let read_end = page.len().min(offset + read_len);
366            let read_page = &page[offset..read_end];
367            self.cursor += read_page.len();
368            Some(read_page)
369        }
370        else {
371            None
372        }
373    }
374}
375
376struct MemPageMutIter<'a, Z: ?Sized> {
377    mem: &'a mut Z,
378    cursor: usize,
379    end: usize
380}
381
382impl<'a, Z: ZxMemory + ?Sized> Iterator for MemPageMutIter<'a, Z> {
383    type Item = PageMutSlice<'a>;
384    fn next(&mut self) -> Option<Self::Item> {
385        let cursor = self.cursor;
386        let end = self.end;
387        if cursor < end {
388            let MemPageOffset { kind, index, offset } = self.mem.page_index_at(cursor as u16).unwrap();
389            let offset = offset as usize;
390            let page = self.mem.page_mut(index).unwrap();
391            // this may lead to yielding pages that view the same memory bank slices;
392            // so this struct isn't public, and it's only used internally when no
393            // two iterated pages are allowed to be accessed at once.
394            let page = unsafe { core::mem::transmute::<&mut[u8], &'a mut[u8]>(page) };
395            let read_len = end - cursor;
396            let read_end = page.len().min(offset + read_len);
397            let read_page = &mut page[offset..read_end];
398            self.cursor += read_page.len();
399            match kind {
400                MemoryKind::Rom => {
401                    Some(PageMutSlice::Rom(read_page))
402                },
403                MemoryKind::Ram => {
404                    Some(PageMutSlice::Ram(read_page))
405                }
406            }
407        }
408        else {
409            None
410        }
411    }
412}
413
414enum AddressRangeError {
415    StartBoundTooLow,
416    EndBoundTooHigh
417}
418
419fn normalize_address_range<R: RangeBounds<u16>>(
420        range: R,
421        min_inclusive: u16,
422        max_inclusive: u16
423    ) -> core::result::Result<Range<usize>, AddressRangeError>
424{
425    let start = match range.start_bound() {
426        Bound::Included(start) => if *start < min_inclusive {
427                return Err(AddressRangeError::StartBoundTooLow)
428            } else { *start as usize },
429        Bound::Excluded(start) => if *start < min_inclusive.saturating_sub(1) {
430                return Err(AddressRangeError::StartBoundTooLow)
431            } else { *start as usize + 1 },
432        Bound::Unbounded => min_inclusive as usize
433    };
434    let end = match range.end_bound() {
435        Bound::Included(end) => if *end > max_inclusive {
436                return Err(AddressRangeError::EndBoundTooHigh)
437            } else { *end as usize + 1},
438        Bound::Excluded(end) => if *end > max_inclusive.saturating_add(1) {
439                return Err(AddressRangeError::EndBoundTooHigh)
440            } else { *end as usize },
441        Bound::Unbounded => max_inclusive as usize + 1
442    };
443    Ok(start..end)
444}