Skip to content

Commit

Permalink
ral1243: memory implementation clean up and test
Browse files Browse the repository at this point in the history
  • Loading branch information
royaltm committed Feb 18, 2024
1 parent fcfe310 commit 9c3866c
Showing 1 changed file with 135 additions and 46 deletions.
181 changes: 135 additions & 46 deletions examples/ral1243/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@ use super::clock::Ts;
use log::{error, warn, info, debug, trace, Level};

const MAXRAMSIZE: usize = 0x10000 - BANK2 as usize;
const ROMSIZE: usize = 0x2000;
const BANK_MASK: u16 = 0xE000;
const BANK_SIZE: u16 = 0x2000;
const BANK_MASK: u16 = BANK_SIZE - 1;
const BANK_SELECT: u16 = !BANK_MASK;
const BANK0: u16 = 0x0000;
const BANK1: u16 = 0x2000;
const BANK2: u16 = 0x4000;
const BANK1: u16 = BANK0 + BANK_SIZE;
const BANK2: u16 = BANK1 + BANK_SIZE;
const ROMSIZE: usize = BANK_SIZE as usize;

pub type Rom = Box<[u8;ROMSIZE]>;

pub struct Memory {
rom: Rom,
bank1: Option<NonNull<u8>>,
bank1: Option<NonNull<[u8;ROMSIZE]>>,
ram0: Box<[u8;ROMSIZE]>,
ram1: Box<[u8]>,
exroms: Vec<Rom>,
Expand All @@ -35,7 +37,7 @@ pub struct Memory {

#[inline(always)]
unsafe fn read_unaligned16(ptr: *const u8) -> u16 {
(ptr as *const u16).read_unaligned().to_le()
ptr.cast::<u16>().read_unaligned().to_le()
}

impl Memory {
Expand All @@ -49,10 +51,17 @@ impl Memory {
let bank1 = None;
assert!(ramsize <= MAXRAMSIZE);
let ram1 = vec![0;ramsize].into_boxed_slice();
Memory { rom, bank1, ram0, ram1, exroms: vec![], exrom_select: 0, bank1_is_ram: false }
Memory { rom, bank1, ram0, ram1, exroms: Vec::new(), exrom_select: 0, bank1_is_ram: false }
}

pub fn make_rom(rom_in: &[u8]) -> Box<[u8;ROMSIZE]> {
pub fn eject_exroms(&mut self) -> Vec<Rom> {
if !self.bank1_is_ram {
self.bank1 = None;
}
core::mem::take(&mut self.exroms)
}

pub fn make_rom(rom_in: &[u8]) -> Rom {
assert!(rom_in.len() <= ROMSIZE);
let mut rom = Box::new([u8::max_value();ROMSIZE]);
rom[0..rom_in.len()].copy_from_slice(rom_in);
Expand Down Expand Up @@ -87,46 +96,34 @@ impl Memory {

#[inline(always)]
fn read_rom(&self, addr: u16) -> u8 {
unsafe {
*self.rom.as_ptr().add((addr & BANK1 - 1) as usize)
}
self.rom[(addr & BANK_MASK) as usize]
}

#[inline(always)]
fn read_bank1(&self, addr: u16) -> u8 {
match self.bank1 {
// Safe as long as ram0 and exroms are not moved out or replaced in Memory
Some(bank1) => unsafe {
*bank1.as_ptr().add((addr & BANK1 - 1) as usize)
},
bank1.as_ref()[(addr & BANK_MASK) as usize]
}
None => u8::max_value()
}
}

#[inline(always)]
fn read_ram1(&self, addr: u16) -> u8 {
match self.ram1.get((addr - BANK2) as usize) {
match self.ram1.get(addr.wrapping_sub(BANK2) as usize) {
Some(p) => *p,
None => u8::max_value()
}
}

#[inline(always)]
fn write_ram0(&mut self, addr: u16, data: u8) {
unsafe { *self.ram0.as_mut_ptr().add((addr & BANK1 - 1) as usize) = data }
}

#[inline(always)]
fn write_ram1(&mut self, addr: u16, data: u8) {
if let Some(p) = self.ram1.get_mut((addr - BANK2) as usize) {
*p = data;
}
}

#[inline(always)]
fn exrom_to_bank1(&mut self) {
self.bank1_is_ram = false;
if let Some(bank) = self.exroms.get_mut(self.exrom_select as usize) {
// trace!("swapped bank; {}", self.exrom_select);
self.bank1 = NonNull::new(bank.as_mut_ptr());
self.bank1 = NonNull::new(bank.as_mut());
}
else {
// trace!("swapped empty bank");
Expand Down Expand Up @@ -162,18 +159,17 @@ impl z80emu::Memory for Memory {

#[inline(always)]
fn read_opcode(&mut self, pc: u16, _ir: u16, _ts: Ts) -> u8 {
let pcbank = pc & BANK_MASK;
let pcbank = pc & BANK_SELECT;
if pcbank == BANK0 {
if !self.bank1_is_ram {
self.bank1_is_ram = true;
self.bank1 = NonNull::new(self.ram0.as_mut_ptr());
self.bank1 = NonNull::new(self.ram0.as_mut());
// trace!("swapped ram bank: {:04x}", pc);
}
self.read_rom(pc)
}
else {
if self.bank1_is_ram {
self.bank1_is_ram = false;
self.exrom_to_bank1();
// trace!("swapped rom bank: {:04x}", pc);
}
Expand All @@ -193,41 +189,134 @@ impl z80emu::Memory for Memory {

#[inline(always)]
fn read_mem16(&self, addr: u16, _ts: Ts) -> u16 {
let addr1 = addr.wrapping_add(1);
unsafe {
match (addr & BANK_MASK, addr1 & BANK_MASK) {
(BANK0, BANK0) => read_unaligned16(self.rom.as_ptr().add(addr as usize)),
(BANK1, BANK1) => match self.bank1 {
Some(bank1) => read_unaligned16(bank1.as_ptr().add((addr & BANK1 - 1) as usize)),
match addr & BANK_SELECT {
BANK0 => if addr != BANK_MASK {
// Safe: (addr & BANK_MASK) == addr
unsafe { read_unaligned16(self.rom.as_ptr().add(addr as usize)) }
}
else {
u16::from_le_bytes([self.read_rom(BANK_MASK), self.read_bank1(BANK1)])
}
BANK1 => if addr != (BANK1|BANK_MASK) {
match self.bank1 {
// Safe as long as ram0 and exroms are not moved out or replaced in Memory
Some(bank1) => unsafe {
read_unaligned16(bank1.as_ptr().cast::<u8>().add((addr & BANK_MASK) as usize))
}
None => u16::max_value()
}
_ if addr >= BANK2 && ((addr - BANK2 + 1) as usize) < self.ram1.len() => {
read_unaligned16(self.ram1.as_ptr().add((addr - BANK2) as usize))
}
else {
u16::from_le_bytes([self.read_bank1(BANK1|BANK_MASK), self.read_ram1(BANK2)])
}
_ => { /* addr is now >= BANK2 */
let offs = usize::from(addr - BANK2);
if offs + 1 < self.ram1.len() {
// Safe: the length is checked against the 2nd byte address.
unsafe { read_unaligned16(self.ram1.as_ptr().add(offs)) }
}
_ => {
u16::from_le_bytes([self.read_debug(addr), self.read_debug(addr1)])
else {
u16::from_le_bytes([
self.read_ram1(addr),
if addr == u16::MAX {
self.read_rom(0)
}
else {
u8::max_value()
}])
}
}
}
}

#[inline(always)]
fn write_mem(&mut self, addr: u16, data: u8, _ts: Ts) {
match addr & BANK_MASK {
BANK0 => {},
match addr & BANK_SELECT {
BANK0 => {}
BANK1 => if self.bank1_is_ram {
self.write_ram0(addr, data)
},
_ => self.write_ram1(addr, data)
self.ram0[(addr & BANK_MASK) as usize] = data
}
_ => if let Some(p) = self.ram1.get_mut(addr.wrapping_sub(BANK2) as usize) {
*p = data;
}
}
}

#[inline(always)]
fn read_debug(&self, addr: u16) -> u8 {
match addr & BANK_MASK {
match addr & BANK_SELECT {
BANK0 => self.read_rom(addr),
BANK1 => self.read_bank1(addr),
_ => self.read_ram1(addr)
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use z80emu::Memory as _;

#[test]
fn memory_read16_works() {
let mut rom_in = Vec::new();
for i in 0..Memory::ROMSIZE {
rom_in.push(u8::try_from(i & 0xFF).unwrap());
}
let mut mem = Memory::new(rom_in.as_slice(), 48);
/* force RAM0 into bank 1 */
mem.read_opcode(0, 0, 0);
for addr in BANK1..=u16::MAX {
mem.write_mem(addr, u8::try_from(addr & 0xFF).unwrap(), 0);
}
for addr in 0..=u16::MAX {
let addr1 = addr.wrapping_add(1);
let lo = mem.read_debug(addr);
let hi = mem.read_debug(addr1);
let r16 = mem.read_mem16(addr, 0);
let t16 = u16::from_le_bytes([lo, hi]);
let x16 = u16::from_le_bytes([
u8::try_from(addr & 0xFF).unwrap(),
u8::try_from(addr1 & 0xFF).unwrap()
]);
assert_eq!(r16, t16);
assert_eq!(r16, x16);
}

let mut mem = Memory::new(rom_in.as_slice(), 0);
/* force RAM0 into bank 1 */
mem.read_opcode(0, 0, 0);
for addr in BANK1..=u16::MAX {
mem.write_mem(addr, u8::try_from(addr & 0xFF).unwrap(), 0);
}
for addr in 0..BANK2-1 {
let addr1 = addr.wrapping_add(1);
let lo = mem.read_debug(addr);
let hi = mem.read_debug(addr1);
let r16 = mem.read_mem16(addr, 0);
let t16 = u16::from_le_bytes([lo, hi]);
let x16 = u16::from_le_bytes([
u8::try_from(addr & 0xFF).unwrap(),
u8::try_from(addr1 & 0xFF).unwrap()
]);
assert_eq!(r16, t16);
assert_eq!(r16, x16);
}
assert_eq!(mem.read_debug(BANK2-1), 0xFF);
assert_eq!(mem.read_debug(BANK2), 0xFF);
assert_eq!(mem.read_mem16(BANK2-1, 0), 0xFFFF);
for addr in BANK2..u16::MAX {
let addr1 = addr.wrapping_add(1);
let lo = mem.read_debug(addr);
let hi = mem.read_debug(addr1);
let r16 = mem.read_mem16(addr, 0);
let t16 = u16::from_le_bytes([lo, hi]);
let x16 = u16::from_le_bytes([u8::MAX, u8::MAX]);
assert_eq!(r16, t16);
assert_eq!(r16, x16);
}
assert_eq!(mem.read_debug(u16::MAX), 0xFF);
assert_eq!(mem.read_debug(0), 0);
assert_eq!(mem.read_mem16(u16::MAX, 0), 0x00FF);
}
}

0 comments on commit 9c3866c

Please sign in to comment.