bus: Major refactor: auto-impl implicit casting for all numerics

This commit is contained in:
2023-04-17 05:12:37 -05:00
parent 7d25a9f5f1
commit 95d4751cdd
8 changed files with 226 additions and 216 deletions

405
src/cpu/bus.rs Normal file
View File

@@ -0,0 +1,405 @@
// (c) 2023 John A. Breaux
// This code is licensed under MIT license (see LICENSE.txt for details)
//! The Bus connects the CPU to Memory
//!
//! This is more of a memory management unit + some utils for reading/writing
use crate::error::{Error::MissingRegion, Result};
use std::{
fmt::{Debug, Display, Formatter},
ops::Range,
slice::SliceIndex,
};
/// Creates a new bus, growing the backing memory as needed
/// # Examples
/// ```rust
/// # use chirp::*;
/// let mut bus = bus! {
/// Stack [0x0000..0x0800] = b"ABCDEF",
/// Program [0x0800..0x1000] = include_bytes!("bus.rs"),
/// };
/// ```
#[macro_export]
macro_rules! bus {
($($name:path $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => {
$crate::cpu::bus::Bus::default()
$(
.add_region_owned($name, $range)
$(
.load_region_owned($name, $data)
)?
)*
};
}
pub mod read;
pub use read::{Get, ReadWrite};
// Traits Read and Write are here purely to make implementing other things more bearable
impl Get<u8> for Bus {
/// Gets a slice of [Bus] memory
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let bus = Bus::new()
/// .add_region_owned(Program, 0..10);
/// assert!([0;10].as_slice() == bus.get(0..10).unwrap());
///# Ok(())
///# }
/// ```
#[inline(always)]
fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[u8]>>::Output>
where
I: SliceIndex<[u8]>,
{
self.memory.get(index)
}
/// Gets a mutable slice of [Bus] memory
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let mut bus = Bus::new()
/// .add_region_owned(Program, 0..10);
/// assert!([0;10].as_slice() == bus.get_mut(0..10).unwrap());
///# Ok(())
///# }
/// ```
#[inline(always)]
fn get_mut<I>(&mut self, index: I) -> Option<&mut <I as SliceIndex<[u8]>>::Output>
where
I: SliceIndex<[u8]>,
{
self.memory.get_mut(index)
}
}
/// Represents a named region in memory
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Region {
/// Character ROM (but writable!)
Charset,
/// Program memory
Program,
/// Screen buffer
Screen,
/// Stack space
Stack,
#[doc(hidden)]
/// Total number of named regions
Count,
}
impl Display for Region {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Region::Charset => "Charset",
Region::Program => "Program",
Region::Screen => "Screen",
Region::Stack => "Stack",
_ => "",
}
)
}
}
/// Stores memory in a series of named regions with ranges
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Bus {
memory: Vec<u8>,
region: [Option<Range<usize>>; Region::Count as usize],
}
impl Bus {
// TODO: make bus::new() give a properly set up bus with a default memory map
/// Constructs a new bus
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let bus = Bus::new();
/// assert!(bus.is_empty());
///# Ok(())
///# }
/// ```
pub fn new() -> Self {
Bus::default()
}
/// Gets the length of the bus' backing memory
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let bus = Bus::new()
/// .add_region_owned(Program, 0..1234);
/// assert_eq!(1234, bus.len());
///# Ok(())
///# }
/// ```
pub fn len(&self) -> usize {
self.memory.len()
}
/// Returns true if the backing memory contains no elements
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let bus = Bus::new();
/// assert!(bus.is_empty());
///# Ok(())
///# }
/// ```
pub fn is_empty(&self) -> bool {
self.memory.is_empty()
}
/// Grows the Bus backing memory to at least size bytes, but does not truncate
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let mut bus = Bus::new();
/// bus.with_size(1234);
/// assert_eq!(1234, bus.len());
/// bus.with_size(0);
/// assert_eq!(1234, bus.len());
///# Ok(())
///# }
/// ```
pub fn with_size(&mut self, size: usize) {
if self.len() < size {
self.memory.resize(size, 0);
}
}
/// Adds a new names range ([Region]) to an owned [Bus]
pub fn add_region_owned(mut self, name: Region, range: Range<usize>) -> Self {
self.add_region(name, range);
self
}
/// Adds a new named range ([Region]) to a [Bus]
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let mut bus = Bus::new();
/// bus.add_region(Program, 0..1234);
/// assert_eq!(1234, bus.len());
///# Ok(())
///# }
/// ```
pub fn add_region(&mut self, name: Region, range: Range<usize>) -> &mut Self {
self.with_size(range.end);
if let Some(region) = self.region.get_mut(name as usize) {
*region = Some(range);
}
self
}
/// Updates an existing [Region]
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let mut bus = Bus::new().add_region_owned(Program, 0..1234);
/// bus.set_region(Program, 1234..2345);
/// assert_eq!(2345, bus.len());
///# Ok(())
///# }
/// ```
pub fn set_region(&mut self, name: Region, range: Range<usize>) -> &mut Self {
self.with_size(range.end);
if let Some(region) = self.region.get_mut(name as usize) {
*region = Some(range);
}
self
}
/// Loads data into a [Region] on an *owned* [Bus], for use during initialization
pub fn load_region_owned(mut self, name: Region, data: &[u8]) -> Self {
self.load_region(name, data).ok();
self
}
/// Loads data into a named [Region]
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let bus = Bus::new()
/// .add_region_owned(Program, 0..1234)
/// .load_region(Program, b"Hello, world!")?;
///# // TODO: Test if region actually contains "Hello, world!"
///# Ok(())
///# }
/// ```
pub fn load_region(&mut self, name: Region, data: &[u8]) -> Result<&mut Self> {
use std::io::Write;
if let Some(mut region) = self.get_region_mut(name) {
assert_eq!(region.write(data)?, data.len());
}
Ok(self)
}
/// Fills a [Region] with zeroes
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let bus = Bus::new()
/// .add_region_owned(Program, 0..1234)
/// .clear_region(Program);
///# // TODO: test if region actually clear
///# Ok(())
///# }
/// ```
/// If the region doesn't exist, that's okay.
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let bus = Bus::new()
/// .add_region_owned(Program, 0..1234)
/// .clear_region(Screen);
///# // TODO: test if region actually clear
///# Ok(())
///# }
/// ```
pub fn clear_region(&mut self, name: Region) -> &mut Self {
if let Some(region) = self.get_region_mut(name) {
region.fill(0)
}
self
}
/// Gets a slice of a named [Region] of memory
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let bus = Bus::new()
/// .add_region_owned(Program, 0..10);
/// assert!([0;10].as_slice() == bus.get_region(Program).unwrap());
///# Ok(())
///# }
/// ```
#[inline(always)]
pub fn get_region(&self, name: Region) -> Option<&[u8]> {
debug_assert!(self.region.get(name as usize).is_some());
self.get(self.region.get(name as usize)?.clone()?)
}
/// Gets a mutable slice of a named region of memory
/// # Examples
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let mut bus = Bus::new()
/// .add_region_owned(Program, 0..10);
/// assert!([0;10].as_slice() == bus.get_region_mut(Program).unwrap());
///# Ok(())
///# }
/// ```
#[inline(always)]
pub fn get_region_mut(&mut self, name: Region) -> Option<&mut [u8]> {
debug_assert!(self.region.get(name as usize).is_some());
self.get_mut(self.region.get(name as usize)?.clone()?)
}
/// Prints the region of memory called `Screen` at 1bpp using box characters
/// # Examples
///
/// [Bus::print_screen] will print the screen
/// ```rust
///# use chirp::*;
///# fn main() -> Result<()> {
/// let bus = Bus::new()
/// .add_region_owned(Screen, 0x000..0x100);
/// bus.print_screen()?;
///# Ok(())
///# }
/// ```
/// If there is no Screen region, it will return Err([MissingRegion])
/// ```rust,should_panic
///# use chirp::*;
///# fn main() -> Result<()> {
/// let mut bus = Bus::new()
/// .add_region_owned(Program, 0..10);
/// bus.print_screen()?;
///# Ok(())
///# }
/// ```
pub fn print_screen(&self) -> Result<()> {
const REGION: Region = Region::Screen;
if let Some(screen) = self.get_region(REGION) {
let len_log2 = screen.len().ilog2() / 2;
#[allow(unused_variables)]
let (width, height) = (2u32.pow(len_log2 - 1), 2u32.pow(len_log2 + 1) - 1);
// draw with the drawille library, if available
#[cfg(feature = "drawille")]
{
use drawille::Canvas;
let mut canvas = Canvas::new(dbg!(width * 8), dbg!(height));
let width = width * 8;
screen
.iter()
.enumerate()
.flat_map(|(bytei, byte)| {
(0..8).enumerate().filter_map(move |(biti, bit)| {
if (byte << bit) & 0x80 != 0 {
Some(bytei * 8 + biti)
} else {
None
}
})
})
.for_each(|index| canvas.set(index as u32 % (width), index as u32 / (width)));
println!("{}", canvas.frame());
}
#[cfg(not(feature = "drawille"))]
for (index, byte) in screen.iter().enumerate() {
if index % width as usize == 0 {
print!("{index:03x}|");
}
print!(
"{}",
format!("{byte:08b}").replace('0', " ").replace('1', "")
);
if index % width as usize == width as usize - 1 {
println!("|");
}
}
} else {
return Err(MissingRegion { region: REGION });
}
Ok(())
}
}
#[cfg(target_feature = "rhexdump")]
impl Display for Bus {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
use rhexdump::Rhexdump;
let mut rhx = Rhexdump::default();
rhx.set_bytes_per_group(2)
.expect("2 <= MAX_BYTES_PER_GROUP (8)");
rhx.display_duplicate_lines(false);
for (&name, range) in &self.region {
writeln!(
f,
"[{name}]\n{}\n",
rhx.hexdump(&self.memory[range.clone()])
)?
}
write!(f, "")
}
}

100
src/cpu/bus/read.rs Normal file
View File

@@ -0,0 +1,100 @@
// (c) 2023 John A. Breaux
// This code is licensed under MIT license (see LICENSE.txt for details)
//! Trait for getting a generic integer for a structure.
#[allow(unused_imports)]
use core::mem::size_of;
use std::{fmt::Debug, slice::SliceIndex};
/// Gets a `&[T]` at [SliceIndex] `I`.
///
/// This is similar to the [SliceIndex] method `.get(...)`, however implementing this trait
/// for [u8] will auto-impl [Read]<(i8, u8, i16, u16 ... i128, u128)>
pub trait Get<T> {
/// Gets the slice of Self at [SliceIndex] I
fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output>
where
I: SliceIndex<[T]>;
/// Gets a mutable slice of Self at [SliceIndex] I
fn get_mut<I>(&mut self, index: I) -> Option<&mut <I as SliceIndex<[T]>>::Output>
where
I: SliceIndex<[T]>;
}
/// Read a T from address `addr`
pub trait ReadWrite<T>: FallibleReadWrite<T> {
/// Reads a T from address `addr`
///
/// # May Panic
///
/// If the type is not [Default], this will panic on error.
fn read(&self, addr: impl Into<usize>) -> T {
self.read_fallible(addr).unwrap_or_else(|e| panic!("{e:?}"))
}
/// Write a T to address `addr`
fn write(&mut self, addr: impl Into<usize>, data: T) {
self.write_fallible(addr, data)
.unwrap_or_else(|e| panic!("{e:?}"));
}
}
/// Read a T from address `addr`, and return the value as a [Result]
pub trait FallibleReadWrite<T>: Get<u8> {
/// The [Err] type returned by [read_fallible]
type Error: Debug;
/// Read a T from address `addr`, returning the value as a [Result]
fn read_fallible(&self, addr: impl Into<usize>) -> Result<T, Self::Error>;
/// Write a T to address `addr`, returning the value as a [Result]
fn write_fallible(&mut self, addr: impl Into<usize>, data: T) -> Result<(), Self::Error>;
}
/// Implements Read and Write for the provided types
///
/// Relies on inherent methods of Rust numeric types:
/// - `Self::from_be_bytes`
/// - `Self::to_be_bytes`
macro_rules! impl_rw {
($($t:ty) ,* $(,)?) => {
$(
impl<T: Get<u8> + FallibleReadWrite<$t>> ReadWrite<$t> for T {
#[inline(always)]
fn read(&self, addr: impl Into<usize>) -> $t {
self.read_fallible(addr).ok().unwrap_or_default()
}
#[inline(always)]
fn write(&mut self, addr: impl Into<usize>, data: $t) {
self.write_fallible(addr, data).ok();
}
}
impl<T: Get<u8>> FallibleReadWrite<$t> for T {
type Error = $crate::error::Error;
#[inline(always)]
fn read_fallible(&self, addr: impl Into<usize>) -> $crate::error::Result<$t> {
let addr: usize = addr.into();
let range = addr..addr + core::mem::size_of::<$t>();
if let Some(bytes) = self.get(range.clone()) {
// Chip-8 is a big-endian system
Ok(<$t>::from_be_bytes(bytes.try_into()?))
} else {
Err($crate::error::Error::InvalidAddressRange{range})
}
}
#[inline(always)]
fn write_fallible(&mut self, addr: impl Into<usize>, data: $t) -> std::result::Result<(), Self::Error> {
let addr: usize = addr.into();
if let Some(slice) = self.get_mut(addr..addr + core::mem::size_of::<$t>()) {
// Chip-8 is a big-endian system
data.to_be_bytes().as_mut().swap_with_slice(slice);
}
Ok(())
}
}
)*
};
}
impl_rw!(i8, i16, i32, i64, i128);
impl_rw!(u8, u16, u32, u64, u128);
impl_rw!(f32, f64);

View File

@@ -74,7 +74,7 @@ impl CPU {
}
/// |`00ee`| Returns from subroutine
#[inline(always)]
pub(super) fn ret(&mut self, bus: &impl Read<u16>) {
pub(super) fn ret(&mut self, bus: &impl ReadWrite<u16>) {
self.sp = self.sp.wrapping_add(2);
self.pc = bus.read(self.sp);
}
@@ -97,7 +97,7 @@ impl CPU {
impl CPU {
/// |`2aaa`| Pushes pc onto the stack, then jumps to a
#[inline(always)]
pub(super) fn call(&mut self, a: Adr, bus: &mut impl Write<u16>) {
pub(super) fn call(&mut self, a: Adr, bus: &mut impl ReadWrite<u16>) {
bus.write(self.sp, self.pc);
self.sp = self.sp.wrapping_sub(2);
self.pc = a;
@@ -518,7 +518,7 @@ impl CPU {
/// |`00fb`| Scroll the screen right
#[inline(always)]
pub(super) fn scroll_right(&mut self, bus: &mut (impl Read<u128> + Write<u128>)) {
pub(super) fn scroll_right(&mut self, bus: &mut (impl ReadWrite<u128> + ReadWrite<u128>)) {
// Get a line from the bus
for i in (0..16 * 64).step_by(16) {
//let line: u128 = bus.read(self.screen + i) >> 4;
@@ -527,7 +527,7 @@ impl CPU {
}
/// |`00fc`| Scroll the screen right
#[inline(always)]
pub(super) fn scroll_left(&mut self, bus: &mut (impl Read<u128> + Write<u128>)) {
pub(super) fn scroll_left(&mut self, bus: &mut (impl ReadWrite<u128> + ReadWrite<u128>)) {
// Get a line from the bus
for i in (0..16 * 64).step_by(16) {
let line: u128 = (bus.read(self.screen + i) & !(0xf << 124)) << 4;
@@ -559,7 +559,7 @@ impl CPU {
let w_bytes = w / 8;
if let Some(sprite) = bus.get(self.i as usize..(self.i + 32) as usize) {
let sprite = sprite.to_owned();
for (line, sprite) in sprite.chunks(2).enumerate() {
for (line, sprite) in sprite.chunks_exact(2).enumerate() {
let sprite = u16::from_be_bytes(
sprite
.try_into()

View File

@@ -15,7 +15,7 @@
use super::*;
use crate::{
bus,
bus::{Bus, Region::*},
cpu::bus::{Bus, Region::*},
};
mod decode;
@@ -758,7 +758,7 @@ mod io {
// Debug mode is 5x slower
cpu.flags.debug = false;
// Load the test program
bus = bus.load_region(Program, test.program);
bus = bus.load_region_owned(Program, test.program);
// Run the test program for the specified number of steps
while cpu.cycle() < test.steps {
cpu.multistep(&mut bus, test.steps - cpu.cycle())
@@ -1151,7 +1151,7 @@ mod behavior {
// The bus extends from 0x0..0x1000
cpu.pc = 0xfff;
match cpu.tick(&mut bus) {
Err(Error::InvalidBusRange { range }) => {
Err(Error::InvalidAddressRange { range }) => {
eprintln!("InvalidBusRange {{ {range:04x?} }}")
}
other => unreachable!("{other:04x?}"),