use std::fmt;
use crate::chips::{Rom, ROM_SIZE};
const ADDR_BOUND: u32 = 0x100000;
const SYSTEM_RAM: usize = 0x40000;
const IO_CHANNEL_RAM: usize = 0x60000;
const CASSETTE_BASIC_ROMS: usize = 4;
#[derive(Clone, Copy)]
pub(super) struct EffectiveAddress(u16);
impl fmt::Debug for EffectiveAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("EffectiveAddress")
.field(&format_args!("{:#06x}", self.0))
.finish()
}
}
impl From<u16> for EffectiveAddress {
fn from(offset: u16) -> Self {
EffectiveAddress(offset)
}
}
impl EffectiveAddress {
pub(super) fn to_inner(self) -> u16 {
self.0
}
}
#[derive(Clone, Copy)]
pub(super) struct PhysicalAddress(u32);
impl fmt::Debug for PhysicalAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("PhysicalAddress")
.field(&format_args!("{:#07x}", self.0))
.finish()
}
}
impl fmt::Display for PhysicalAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:#07x}", self.0)
}
}
impl PhysicalAddress {
pub(super) fn new(segment: u16, offset: EffectiveAddress) -> Self {
let addr = ((segment as u32) << 4) + (offset.0 as u32);
assert!(addr < ADDR_BOUND);
PhysicalAddress(addr)
}
}
impl IntoIterator for PhysicalAddress {
type Item = Self;
type IntoIter = PhysicalAddressIterator;
fn into_iter(self) -> Self::IntoIter {
PhysicalAddressIterator { next: Some(self) }
}
}
pub(super) struct PhysicalAddressIterator {
next: Option<PhysicalAddress>,
}
impl Iterator for PhysicalAddressIterator {
type Item = PhysicalAddress;
fn next(&mut self) -> Option<Self::Item> {
let ret = self.next;
self.next = self
.next
.and_then(|addr| addr.0.checked_add(1))
.map(PhysicalAddress);
ret
}
}
fn map_memory<R>(
addr: PhysicalAddress,
system_ram: impl FnOnce(usize) -> R,
io_channel_ram: impl FnOnce(usize) -> R,
cassette_basic: impl FnOnce(usize) -> R,
bios_rom: impl FnOnce(usize) -> R,
) -> R {
const SYSTEM_RAM_START: u32 = 0;
const SYSTEM_RAM_END: u32 = SYSTEM_RAM_START + (SYSTEM_RAM as u32) - 1;
const IO_CHANNEL_RAM_START: u32 = SYSTEM_RAM_END + 1;
const IO_CHANNEL_RAM_END: u32 = IO_CHANNEL_RAM_START + (IO_CHANNEL_RAM as u32) - 1;
const OPTIONAL_MB_ROM_START: u32 = CASSETTE_BASIC_START - (ROM_SIZE as u32);
const OPTIONAL_MB_ROM_END: u32 = CASSETTE_BASIC_START - 1;
const CASSETTE_BASIC_START: u32 = BIOS_ROM_START - (CASSETTE_BASIC_ROMS * ROM_SIZE) as u32;
const CASSETTE_BASIC_END: u32 = BIOS_ROM_START - 1;
const BIOS_ROM_START: u32 = ADDR_BOUND - (ROM_SIZE as u32);
const BIOS_ROM_END: u32 = ADDR_BOUND - 1;
match addr.0 {
SYSTEM_RAM_START..=SYSTEM_RAM_END => system_ram(addr.0 as usize),
IO_CHANNEL_RAM_START..=IO_CHANNEL_RAM_END => {
io_channel_ram((addr.0 - IO_CHANNEL_RAM_START) as usize)
}
0xB0000..=0xB3FFF => panic!("Monochrome"),
0xB8000..=0xBBFFF => panic!("Color/Graphics"),
0xA0000..=0xBFFFF => panic!("128K Reserved"),
0xC0000..=0xC7FFF => panic!("Reserved for any video card's BIOS expansion ROM"),
0xC8000..=0xCBFFF => panic!("Fixed Disk Control"),
0xCC000..=0xEFFFF => panic!("ROM Expansion and Control"),
0xF0000..=0xF3FFF => panic!("Reserved"),
OPTIONAL_MB_ROM_START..=OPTIONAL_MB_ROM_END => panic!("Optional motherboard ROM"),
CASSETTE_BASIC_START..=CASSETTE_BASIC_END => {
cassette_basic((addr.0 - CASSETTE_BASIC_START) as usize)
}
BIOS_ROM_START..=BIOS_ROM_END => bios_rom((addr.0 - BIOS_ROM_START) as usize),
ADDR_BOUND.. => unreachable!(),
}
}
pub(super) struct Memory {
system_ram: [u8; SYSTEM_RAM],
io_channel_ram: [u8; IO_CHANNEL_RAM],
cassette_basic: Option<[Rom; CASSETTE_BASIC_ROMS]>,
bios_rom: Rom,
}
impl Memory {
pub(super) fn new(bios_rom: Rom, cassette_basic: Option<[Rom; CASSETTE_BASIC_ROMS]>) -> Self {
Self {
system_ram: [0; SYSTEM_RAM],
io_channel_ram: [0; IO_CHANNEL_RAM],
cassette_basic,
bios_rom,
}
}
#[cfg(test)]
pub(super) fn without_bios() -> Self {
Self::new(Rom::new([0; ROM_SIZE]), None)
}
pub(super) fn view(&self, addr: PhysicalAddress) -> &[u8] {
map_memory(
addr,
|offset| &self.system_ram[offset..],
|offset| &self.io_channel_ram[offset..],
|offset| {
let rom_idx = offset / ROM_SIZE;
let rom_offset = offset % ROM_SIZE;
self.cassette_basic.as_ref().expect("Cassette BASIC")[rom_idx].view(rom_offset)
},
|offset| self.bios_rom.view(offset),
)
}
pub(super) fn read(&self, addr: PhysicalAddress) -> u8 {
map_memory(
addr,
|offset| self.system_ram[offset],
|offset| self.io_channel_ram[offset],
|offset| {
let rom_idx = offset / ROM_SIZE;
let rom_offset = offset % ROM_SIZE;
self.cassette_basic.as_ref().expect("Cassette BASIC")[rom_idx].read(rom_offset)
},
|offset| self.bios_rom.read(offset),
)
}
pub(super) fn write(&mut self, addr: PhysicalAddress, data: u8) {
map_memory(
addr,
|offset| self.system_ram[offset] = data,
|offset| self.io_channel_ram[offset] = data,
|_| panic!("Cannot write to Cassette BASIC"),
|_| panic!("Cannot write to BIOS"),
)
}
}