siso/
memory.rs

1use std::fmt;
2
3use crate::chips::{Rom, ROM_SIZE};
4
5const ADDR_BOUND: u32 = 0x100000;
6
7const SYSTEM_RAM: usize = 0x40000;
8const IO_CHANNEL_RAM: usize = 0x60000;
9
10const CASSETTE_BASIC_ROMS: usize = 4;
11
12/// The 16-bit effective address of a memory operand.
13///
14/// This is the operand's distance in bytes from the beginning of the segment in which it
15/// resides.
16#[derive(Clone, Copy)]
17pub(super) struct EffectiveAddress(u16);
18
19impl fmt::Debug for EffectiveAddress {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        f.debug_tuple("EffectiveAddress")
22            .field(&format_args!("{:#06x}", self.0))
23            .finish()
24    }
25}
26
27impl From<u16> for EffectiveAddress {
28    fn from(offset: u16) -> Self {
29        EffectiveAddress(offset)
30    }
31}
32
33impl EffectiveAddress {
34    pub(super) fn to_inner(self) -> u16 {
35        self.0
36    }
37}
38
39/// The 20-bit physical address of a memory operand.
40#[derive(Clone, Copy)]
41pub(super) struct PhysicalAddress(u32);
42
43impl fmt::Debug for PhysicalAddress {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.debug_tuple("PhysicalAddress")
46            .field(&format_args!("{:#07x}", self.0))
47            .finish()
48    }
49}
50
51impl fmt::Display for PhysicalAddress {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(f, "{:#07x}", self.0)
54    }
55}
56
57impl PhysicalAddress {
58    /// Constructs a physical address from a segment base address and offset within the
59    /// segment.
60    pub(super) fn new(segment: u16, offset: EffectiveAddress) -> Self {
61        let addr = ((segment as u32) << 4) + (offset.0 as u32);
62        assert!(addr < ADDR_BOUND);
63        PhysicalAddress(addr)
64    }
65}
66
67impl IntoIterator for PhysicalAddress {
68    type Item = Self;
69    type IntoIter = PhysicalAddressIterator;
70
71    fn into_iter(self) -> Self::IntoIter {
72        PhysicalAddressIterator { next: Some(self) }
73    }
74}
75
76/// An iterator that produces a sequence of [`PhysicalAddress`]es.
77pub(super) struct PhysicalAddressIterator {
78    next: Option<PhysicalAddress>,
79}
80
81impl Iterator for PhysicalAddressIterator {
82    type Item = PhysicalAddress;
83
84    fn next(&mut self) -> Option<Self::Item> {
85        let ret = self.next;
86        self.next = self
87            .next
88            .and_then(|addr| addr.0.checked_add(1))
89            .map(PhysicalAddress);
90        ret
91    }
92}
93
94/// Memory map for the IBM 5150.
95///
96/// Sources:
97/// - IBM 5150 Technical Reference, Page 1-9
98/// - http://minuszerodegrees.net/5150/misc/5150%20-%20Memory%20Map%20of%20the%20640%20KB%20to%201%20MB%20Area.jpg
99fn map_memory<R>(
100    addr: PhysicalAddress,
101    system_ram: impl FnOnce(usize) -> R,
102    io_channel_ram: impl FnOnce(usize) -> R,
103    cassette_basic: impl FnOnce(usize) -> R,
104    bios_rom: impl FnOnce(usize) -> R,
105) -> R {
106    const SYSTEM_RAM_START: u32 = 0;
107    const SYSTEM_RAM_END: u32 = SYSTEM_RAM_START + (SYSTEM_RAM as u32) - 1;
108
109    const IO_CHANNEL_RAM_START: u32 = SYSTEM_RAM_END + 1;
110    const IO_CHANNEL_RAM_END: u32 = IO_CHANNEL_RAM_START + (IO_CHANNEL_RAM as u32) - 1;
111
112    const OPTIONAL_MB_ROM_START: u32 = CASSETTE_BASIC_START - (ROM_SIZE as u32);
113    const OPTIONAL_MB_ROM_END: u32 = CASSETTE_BASIC_START - 1;
114
115    const CASSETTE_BASIC_START: u32 = BIOS_ROM_START - (CASSETTE_BASIC_ROMS * ROM_SIZE) as u32;
116    const CASSETTE_BASIC_END: u32 = BIOS_ROM_START - 1;
117
118    const BIOS_ROM_START: u32 = ADDR_BOUND - (ROM_SIZE as u32);
119    const BIOS_ROM_END: u32 = ADDR_BOUND - 1;
120
121    match addr.0 {
122        SYSTEM_RAM_START..=SYSTEM_RAM_END => system_ram(addr.0 as usize),
123        IO_CHANNEL_RAM_START..=IO_CHANNEL_RAM_END => {
124            io_channel_ram((addr.0 - IO_CHANNEL_RAM_START) as usize)
125        }
126        0xB0000..=0xB3FFF => panic!("Monochrome"),
127        0xB8000..=0xBBFFF => panic!("Color/Graphics"),
128        0xA0000..=0xBFFFF => panic!("128K Reserved"),
129        0xC0000..=0xC7FFF => panic!("Reserved for any video card's BIOS expansion ROM"),
130        0xC8000..=0xCBFFF => panic!("Fixed Disk Control"),
131        0xCC000..=0xEFFFF => panic!("ROM Expansion and Control"),
132        0xF0000..=0xF3FFF => panic!("Reserved"),
133        OPTIONAL_MB_ROM_START..=OPTIONAL_MB_ROM_END => panic!("Optional motherboard ROM"),
134        CASSETTE_BASIC_START..=CASSETTE_BASIC_END => {
135            cassette_basic((addr.0 - CASSETTE_BASIC_START) as usize)
136        }
137        BIOS_ROM_START..=BIOS_ROM_END => bios_rom((addr.0 - BIOS_ROM_START) as usize),
138        ADDR_BOUND.. => unreachable!(),
139    }
140}
141
142/// The memory available in a system.
143pub(super) struct Memory {
144    /// Memory on the system board.
145    system_ram: [u8; SYSTEM_RAM],
146    /// Memory plugged into I/O ports.
147    ///
148    /// This is temporarily hard-coded until we have I/O support.
149    io_channel_ram: [u8; IO_CHANNEL_RAM],
150    /// Optional Cassette BASIC ROMs.
151    cassette_basic: Option<[Rom; CASSETTE_BASIC_ROMS]>,
152    /// Motherboard BIOS ROM.
153    bios_rom: Rom,
154}
155
156impl Memory {
157    /// Sets up the system memory.
158    pub(super) fn new(bios_rom: Rom, cassette_basic: Option<[Rom; CASSETTE_BASIC_ROMS]>) -> Self {
159        Self {
160            system_ram: [0; SYSTEM_RAM],
161            io_channel_ram: [0; IO_CHANNEL_RAM],
162            cassette_basic,
163            bios_rom,
164        }
165    }
166
167    /// Sets up the system memory without a BIOS ROM.
168    #[cfg(test)]
169    pub(super) fn without_bios() -> Self {
170        Self::new(Rom::new([0; ROM_SIZE]), None)
171    }
172
173    /// Obtains a view over the memory starting from the given address.
174    ///
175    /// The view ends at the end of the logical component into which the address points.
176    /// For example, an address in the main system RAM will return a slice that does not
177    /// include the I/O channel RAM or the BIOS.
178    ///
179    /// This is a temporary method for use with `iced-x86` for decoding instructions.
180    pub(super) fn view(&self, addr: PhysicalAddress) -> &[u8] {
181        map_memory(
182            addr,
183            |offset| &self.system_ram[offset..],
184            |offset| &self.io_channel_ram[offset..],
185            |offset| {
186                let rom_idx = offset / ROM_SIZE;
187                let rom_offset = offset % ROM_SIZE;
188                self.cassette_basic.as_ref().expect("Cassette BASIC")[rom_idx].view(rom_offset)
189            },
190            |offset| self.bios_rom.view(offset),
191        )
192    }
193
194    /// Reads the byte at the given memory address.
195    pub(super) fn read(&self, addr: PhysicalAddress) -> u8 {
196        map_memory(
197            addr,
198            |offset| self.system_ram[offset],
199            |offset| self.io_channel_ram[offset],
200            |offset| {
201                let rom_idx = offset / ROM_SIZE;
202                let rom_offset = offset % ROM_SIZE;
203                self.cassette_basic.as_ref().expect("Cassette BASIC")[rom_idx].read(rom_offset)
204            },
205            |offset| self.bios_rom.read(offset),
206        )
207    }
208
209    /// Writes the given data to the given memory address.
210    pub(super) fn write(&mut self, addr: PhysicalAddress, data: u8) {
211        map_memory(
212            addr,
213            |offset| self.system_ram[offset] = data,
214            |offset| self.io_channel_ram[offset] = data,
215            |_| panic!("Cannot write to Cassette BASIC"),
216            |_| panic!("Cannot write to BIOS"),
217        )
218    }
219}