263 lines
7.5 KiB
Rust
263 lines
7.5 KiB
Rust
#![allow(clippy::unreadable_literal)]
|
|
|
|
use crate::bus::*;
|
|
use crate::err::BusError;
|
|
|
|
use std::fmt::Debug;
|
|
use std::fmt::Error;
|
|
use std::fmt::Formatter;
|
|
use std::ops::Range;
|
|
use std::ops::{Index, IndexMut};
|
|
use std::vec::Vec;
|
|
|
|
pub struct Mem {
|
|
address_range: Range<usize>,
|
|
len: usize,
|
|
ram: Vec<u8>,
|
|
is_read_only: bool,
|
|
}
|
|
|
|
/// Memory is a Device with a single address range.
|
|
impl Mem {
|
|
pub fn new(start_address: usize, len: usize, is_read_only: bool) -> Mem {
|
|
Mem {
|
|
address_range: start_address..start_address + len,
|
|
len,
|
|
ram: vec![0; len],
|
|
is_read_only,
|
|
}
|
|
}
|
|
|
|
pub fn as_slice(&self, range: Range<usize>) -> &[u8] {
|
|
&self.ram[range]
|
|
}
|
|
}
|
|
|
|
impl Debug for Mem {
|
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
|
write!(f, "Memory [0x{:x}..0x{:x}]", self.address_range.start, self.address_range().end)
|
|
}
|
|
}
|
|
|
|
impl Device for Mem {
|
|
fn address_range(&self) -> &Range<usize> {
|
|
&self.address_range
|
|
}
|
|
|
|
fn name(&self) -> &str {
|
|
if self.is_read_only {
|
|
"ROM"
|
|
} else {
|
|
"RAM"
|
|
}
|
|
}
|
|
|
|
fn is_read_only(&self) -> bool {
|
|
self.is_read_only
|
|
}
|
|
|
|
/// Read from memory at the specified absolute address.
|
|
fn read_byte(&mut self, address: usize, _: AccessCode) -> Result<u8, BusError> {
|
|
let offset = address.wrapping_sub(self.address_range().start);
|
|
|
|
if address >= self.address_range().end {
|
|
Err(BusError::Range)
|
|
} else {
|
|
Ok(self.ram[offset])
|
|
}
|
|
}
|
|
|
|
fn read_half(&mut self, address: usize, _: AccessCode) -> Result<u16, BusError> {
|
|
let offset = address.wrapping_sub(self.address_range().start);
|
|
|
|
if address >= self.address_range().end {
|
|
Err(BusError::Range)
|
|
} else {
|
|
Ok(
|
|
// Byte-swap
|
|
u16::from(self.ram[offset]).wrapping_shl(8) | u16::from(self.ram[offset + 1]),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn read_word(&mut self, address: usize, _: AccessCode) -> Result<u32, BusError> {
|
|
let offset = address.wrapping_sub(self.address_range().start);
|
|
|
|
if address >= self.address_range().end {
|
|
Err(BusError::Range)
|
|
} else {
|
|
Ok(
|
|
// Byte-swap
|
|
u32::from(self.ram[offset]).wrapping_shl(24)
|
|
| u32::from(self.ram[offset + 1]).wrapping_shl(16)
|
|
| u32::from(self.ram[offset + 2]).wrapping_shl(8)
|
|
| u32::from(self.ram[offset + 3]),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Write to memory at the specified absolute address.
|
|
fn write_byte(&mut self, address: usize, val: u8, _: AccessCode) -> Result<(), BusError> {
|
|
if self.is_read_only {
|
|
return Err(BusError::Write(address));
|
|
}
|
|
|
|
let offset = address.wrapping_sub(self.address_range().start);
|
|
|
|
if address >= self.address_range().end {
|
|
Err(BusError::Range)
|
|
} else {
|
|
self.ram[offset] = val;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn write_half(&mut self, address: usize, val: u16, _: AccessCode) -> Result<(), BusError> {
|
|
if self.is_read_only {
|
|
return Err(BusError::Write(address));
|
|
}
|
|
|
|
let offset = address.wrapping_sub(self.address_range().start);
|
|
|
|
if address >= self.address_range().end {
|
|
Err(BusError::Range)
|
|
} else {
|
|
self.ram[offset] = (val.wrapping_shr(8) & 0xff) as u8;
|
|
self.ram[offset + 1] = (val & 0xff) as u8;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn write_word(&mut self, address: usize, val: u32, _: AccessCode) -> Result<(), BusError> {
|
|
if self.is_read_only {
|
|
return Err(BusError::Write(address));
|
|
}
|
|
|
|
let offset = address.wrapping_sub(self.address_range().start);
|
|
|
|
if address >= self.address_range().end {
|
|
Err(BusError::Range)
|
|
} else {
|
|
self.ram[offset] = (val.wrapping_shr(24) & 0xff) as u8;
|
|
self.ram[offset + 1] = (val.wrapping_shr(16) & 0xff) as u8;
|
|
self.ram[offset + 2] = (val.wrapping_shr(8) & 0xff) as u8;
|
|
self.ram[offset + 3] = (val & 0xff) as u8;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Load a block of bytes into memory at the specified absolute
|
|
/// address. Note that "load" can load into read-only memory.
|
|
fn load(&mut self, address: usize, program: &[u8]) -> Result<(), BusError> {
|
|
let offset = address.wrapping_sub(self.address_range().start);
|
|
|
|
if program.len() > self.len {
|
|
Err(BusError::Range)
|
|
} else {
|
|
for (i, byte) in program.iter().enumerate() {
|
|
self.ram[offset.wrapping_add(i)] = *byte;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Index<usize> for Mem {
|
|
type Output = u8;
|
|
|
|
fn index(&self, idx: usize) -> &u8 {
|
|
&self.ram[idx]
|
|
}
|
|
}
|
|
|
|
impl IndexMut<usize> for Mem {
|
|
fn index_mut(&'_ mut self, idx: usize) -> &'_ mut u8 {
|
|
&mut self.ram[idx]
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn cannot_write_to_read_only_memory() {
|
|
let mut mem = Mem::new(0, 0x1000, true);
|
|
assert!(mem.write_byte(0, 0x1f, AccessCode::Write).is_err());
|
|
assert!(mem.write_half(0, 0x1f1f, AccessCode::Write).is_err());
|
|
assert!(mem.write_word(0, 0x1f1f1f1f, AccessCode::Write).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn loads_program_if_it_fits() {
|
|
let mut mem = Mem::new(0, 3, false);
|
|
let program: [u8; 3] = [0x0a, 0x30, 0x1f];
|
|
let result = mem.load(0, &program);
|
|
assert!(result.is_ok());
|
|
|
|
// Did it actually load?
|
|
assert_eq!(mem[0], 0x0a);
|
|
assert_eq!(mem[1], 0x30);
|
|
assert_eq!(mem[2], 0x1f);
|
|
}
|
|
|
|
#[test]
|
|
fn fails_to_load_program_if_it_doesnt_fit() {
|
|
let mut mem = Mem::new(0, 3, false);
|
|
let program: [u8; 4] = [0x0a, 0x30, 0x1f, 0x1b];
|
|
let result = mem.load(0, &program);
|
|
assert!(result.is_err());
|
|
|
|
// Did it actually fail to load?
|
|
assert_eq!(mem[0], 0);
|
|
assert_eq!(mem[1], 0);
|
|
assert_eq!(mem[2], 0);
|
|
}
|
|
|
|
#[test]
|
|
fn can_write_and_read_memory() {
|
|
let mut mem = Mem::new(0, 2, false);
|
|
|
|
let mut read_result = mem.read_byte(0, AccessCode::AddressFetch);
|
|
assert!(read_result.is_ok());
|
|
assert_eq!(0, read_result.unwrap());
|
|
|
|
let mut write_result = mem.write_byte(0, 0x01, AccessCode::Write);
|
|
assert!(write_result.is_ok());
|
|
|
|
read_result = mem.read_byte(0, AccessCode::AddressFetch);
|
|
assert!(read_result.is_ok());
|
|
assert_eq!(1, read_result.unwrap());
|
|
|
|
write_result = mem.write_byte(1, 0x02, AccessCode::Write);
|
|
assert!(write_result.is_ok());
|
|
|
|
read_result = mem.read_byte(1, AccessCode::AddressFetch);
|
|
assert!(read_result.is_ok());
|
|
assert_eq!(2, read_result.unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn fails_to_read_or_write_memory_when_out_of_bounds() {
|
|
let mut mem = Mem::new(0, 2, false);
|
|
|
|
let write_result = mem.write_byte(2, 0x03, AccessCode::Write);
|
|
assert!(write_result.is_err());
|
|
|
|
let read_result = mem.read_byte(2, AccessCode::AddressFetch);
|
|
assert!(read_result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn memory_access_uses_absolute_addresses() {
|
|
let mut mem = Mem::new(0x300, 2, false);
|
|
|
|
let write_result = mem.write_byte(0x300, 0xfe, AccessCode::Write);
|
|
assert!(write_result.is_ok());
|
|
|
|
let read_result = mem.read_byte(0x300, AccessCode::AddressFetch);
|
|
assert!(read_result.is_ok());
|
|
assert_eq!(0xfe, read_result.unwrap());
|
|
}
|
|
}
|