diff --git a/src/bin/chirp-minifb/ui.rs b/src/bin/chirp-minifb/ui.rs index b623721..f578cd4 100644 --- a/src/bin/chirp-minifb/ui.rs +++ b/src/bin/chirp-minifb/ui.rs @@ -11,7 +11,7 @@ use std::{ }; use chirp::{ - bus::{Bus, Region}, + cpu::bus::{Bus, Region}, error::Result, Chip8, }; diff --git a/src/cpu.rs b/src/cpu.rs index 23749ae..7872fa3 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -6,22 +6,22 @@ #[cfg(test)] mod tests; +pub mod bus; pub mod disassembler; pub mod flags; +pub mod init; pub mod instruction; pub mod mode; pub mod quirks; use self::{ + bus::{Bus, Get, ReadWrite, Region}, disassembler::{Dis, Disassembler, Insn}, flags::Flags, mode::Mode, quirks::Quirks, }; -use crate::{ - bus::{Bus, Read, Region, Write}, - error::{Error, Result}, -}; +use crate::error::{Error, Result}; use imperative_rs::InstructionSet; use owo_colors::OwoColorize; use rand::random; @@ -86,7 +86,7 @@ impl CPU { /// 0xefe, // top of stack /// Dis::default(), /// vec![], // Breakpoints - /// ControlFlags::default() + /// Flags::default() /// ); /// dbg!(cpu); /// ``` @@ -143,8 +143,8 @@ impl CPU { /// Releases a key, and reports whether the key's state changed. /// If key is outside range `0..=0xF`, returns [Error::InvalidKey]. /// - /// If [ControlFlags::keypause] was enabled, it is disabled, - /// and the [ControlFlags::lastkey] is recorded. + /// If [Flags::keypause] was enabled, it is disabled, + /// and the [Flags::lastkey] is recorded. /// # Examples /// ```rust /// # use chirp::*; @@ -280,7 +280,7 @@ impl CPU { /// 0xefe, /// Dis::default(), /// vec![], - /// ControlFlags::default() + /// Flags::default() /// ); /// cpu.flags.keypause = true; /// cpu.flags.draw_wait = true; @@ -487,14 +487,13 @@ impl CPU { .try_into() .expect("`slice` should be exactly 2 bytes.") } else { - return Err(Error::InvalidBusRange { + return Err(Error::InvalidAddressRange { range: self.pc as usize..self.pc as usize + 2, }); }; // Print opcode disassembly: if self.flags.debug { - println!("{:?}", self.timers.insn.elapsed().bright_black()); self.timers.insn = Instant::now(); std::print!( "{:3} {:03x}: {:<36}", @@ -514,6 +513,10 @@ impl CPU { }); } + if self.flags.debug { + println!("{:?}", self.timers.insn.elapsed().bright_black()); + } + // process breakpoints if !self.breakpoints.is_empty() && self.breakpoints.contains(&self.pc) { self.flags.pause = true; diff --git a/src/bus.rs b/src/cpu/bus.rs similarity index 62% rename from src/bus.rs rename to src/cpu/bus.rs index 8efb185..4c33e2d 100644 --- a/src/bus.rs +++ b/src/cpu/bus.rs @@ -24,27 +24,58 @@ use std::{ #[macro_export] macro_rules! bus { ($($name:path $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => { - $crate::bus::Bus::default() + $crate::cpu::bus::Bus::default() $( - .add_region($name, $range) + .add_region_owned($name, $range) $( - .load_region($name, $data) + .load_region_owned($name, $data) )? )* }; } -// Traits Read and Write are here purely to make implementing other things more bearable -/// Read a T from address `addr` -pub trait Read { - /// Read a T from address `addr` - fn read(&self, addr: impl Into) -> T; -} +pub mod read; +pub use read::{Get, ReadWrite}; -/// Write "some data" to the Bus -pub trait Write { - /// Write a T to address `addr` - fn write(&mut self, addr: impl Into, data: T); +// Traits Read and Write are here purely to make implementing other things more bearable +impl Get 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(&self, index: I) -> Option<&>::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(&mut self, index: I) -> Option<&mut >::Output> + where + I: SliceIndex<[u8]>, + { + self.memory.get_mut(index) + } } /// Represents a named region in memory @@ -70,10 +101,10 @@ impl Display for Region { f, "{}", match self { - Region::Charset => "charset", - Region::Program => "program", - Region::Screen => "screen", - Region::Stack => "stack", + Region::Charset => "Charset", + Region::Program => "Program", + Region::Screen => "Screen", + Region::Stack => "Stack", _ => "", } ) @@ -109,7 +140,7 @@ impl Bus { ///# use chirp::*; ///# fn main() -> Result<()> { /// let bus = Bus::new() - /// .add_region(Program, 0..1234); + /// .add_region_owned(Program, 0..1234); /// assert_eq!(1234, bus.len()); ///# Ok(()) ///# } @@ -149,30 +180,40 @@ impl Bus { self.memory.resize(size, 0); } } - /// Adds a new named range (Region) to the bus + + /// Adds a new names range ([Region]) to an owned [Bus] + pub fn add_region_owned(mut self, name: Region, range: Range) -> Self { + self.add_region(name, range); + self + } + + /// Adds a new named range ([Region]) to a [Bus] /// # Examples /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let bus = Bus::new().add_region(Program, 0..1234); + /// 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) -> Self { + pub fn add_region(&mut self, name: Region, range: Range) -> &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 named range (Region) + + /// Updates an existing [Region] /// # Examples /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let bus = Bus::new().add_region(Program, 0..1234); - /// assert_eq!(1234, bus.len()); + /// let mut bus = Bus::new().add_region_owned(Program, 0..1234); + /// bus.set_region(Program, 1234..2345); + /// assert_eq!(2345, bus.len()); ///# Ok(()) ///# } /// ``` @@ -183,32 +224,40 @@ impl Bus { } self } - /// Loads data into a named region + + /// 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(Program, 0..1234) - /// .load_region(Program, b"Hello, world!"); + /// .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]) -> Self { + 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) { - region.write(data).ok(); // TODO: THIS SUCKS + assert_eq!(region.write(data)?, data.len()); } - self + Ok(self) } - /// Fills a named region with zeroes + + /// Fills a [Region] with zeroes /// # Examples /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { /// let bus = Bus::new() - /// .add_region(Program, 0..1234) + /// .add_region_owned(Program, 0..1234) /// .clear_region(Program); ///# // TODO: test if region actually clear ///# Ok(()) @@ -219,7 +268,7 @@ impl Bus { ///# use chirp::*; ///# fn main() -> Result<()> { /// let bus = Bus::new() - /// .add_region(Program, 0..1234) + /// .add_region_owned(Program, 0..1234) /// .clear_region(Screen); ///# // TODO: test if region actually clear ///# Ok(()) @@ -232,54 +281,20 @@ impl Bus { self } - /// Gets a slice of bus memory + /// Gets a slice of a named [Region] of memory /// # Examples /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { /// let bus = Bus::new() - /// .add_region(Program, 0..10); - /// assert!([0;10].as_slice() == bus.get(0..10).unwrap()); - ///# Ok(()) - ///# } - /// ``` - pub fn get(&self, index: I) -> Option<&>::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(Program, 0..10); - /// assert!([0;10].as_slice() == bus.get_mut(0..10).unwrap()); - ///# Ok(()) - ///# } - /// ``` - pub fn get_mut(&mut self, index: I) -> Option<&mut >::Output> - where - I: SliceIndex<[u8]>, - { - self.memory.get_mut(index) - } - - /// Gets a slice of a named region of memory - /// # Examples - /// ```rust - ///# use chirp::*; - ///# fn main() -> Result<()> { - /// let bus = Bus::new() - /// .add_region(Program, 0..10); + /// .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()?) } @@ -289,12 +304,14 @@ impl Bus { ///# use chirp::*; ///# fn main() -> Result<()> { /// let mut bus = Bus::new() - /// .add_region(Program, 0..10); + /// .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()?) } @@ -306,7 +323,7 @@ impl Bus { ///# use chirp::*; ///# fn main() -> Result<()> { /// let bus = Bus::new() - /// .add_region(Screen, 0x000..0x100); + /// .add_region_owned(Screen, 0x000..0x100); /// bus.print_screen()?; ///# Ok(()) ///# } @@ -316,7 +333,7 @@ impl Bus { ///# use chirp::*; ///# fn main() -> Result<()> { /// let mut bus = Bus::new() - /// .add_region(Program, 0..10); + /// .add_region_owned(Program, 0..10); /// bus.print_screen()?; ///# Ok(()) ///# } @@ -326,27 +343,24 @@ impl Bus { 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)); + 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(width * 8, height); + let mut canvas = Canvas::new(dbg!(width * 8), dbg!(height)); let width = width * 8; screen .iter() .enumerate() .flat_map(|(bytei, byte)| { - (0..8) - .into_iter() - .enumerate() - .filter_map(move |(biti, bit)| { - if (byte << bit) & 0x80 != 0 { - Some(bytei * 8 + biti) - } else { - None - } - }) + (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()); @@ -371,112 +385,6 @@ impl Bus { } } -impl Read for Bus { - /// Read a u8 from address `addr` - fn read(&self, addr: impl Into) -> u8 { - let addr: usize = addr.into(); - *self.memory.get(addr).unwrap_or(&0xc5) - } -} - -impl Read for Bus { - /// Read a u16 from address `addr` - fn read(&self, addr: impl Into) -> u16 { - let addr: usize = addr.into(); - if let Some(bytes) = self.memory.get(addr..addr + 2) { - u16::from_be_bytes(bytes.try_into().expect("Should get 2 bytes")) - } else { - 0xc5c5 - } - } -} - -impl Read for Bus { - /// Read a u16 from address `addr` - fn read(&self, addr: impl Into) -> u32 { - let addr: usize = addr.into(); - if let Some(bytes) = self.memory.get(addr..addr + 4) { - u32::from_be_bytes(bytes.try_into().expect("Should get 4 bytes")) - } else { - 0xc5c5 - } - } -} - -impl Read for Bus { - /// Read a u16 from address `addr` - fn read(&self, addr: impl Into) -> u64 { - let addr: usize = addr.into(); - if let Some(bytes) = self.memory.get(addr..addr + 8) { - u64::from_be_bytes(bytes.try_into().expect("Should get 8 bytes")) - } else { - 0xc5c5 - } - } -} - -impl Read for Bus { - /// Read a u16 from address `addr` - fn read(&self, addr: impl Into) -> u128 { - let addr: usize = addr.into(); - if let Some(bytes) = self.memory.get(addr..addr + 16) { - u128::from_be_bytes(bytes.try_into().expect("Should get 16 bytes")) - } else { - 0xc5c5 - } - } -} - -impl Write for Bus { - /// Write a u8 to address `addr` - fn write(&mut self, addr: impl Into, data: u8) { - let addr: usize = addr.into(); - if let Some(byte) = self.get_mut(addr) { - *byte = data; - } - } -} - -impl Write for Bus { - /// Write a u16 to address `addr` - fn write(&mut self, addr: impl Into, data: u16) { - let addr: usize = addr.into(); - if let Some(slice) = self.get_mut(addr..addr + 2) { - data.to_be_bytes().as_mut().swap_with_slice(slice); - } - } -} - -impl Write for Bus { - /// Write a u16 to address `addr` - fn write(&mut self, addr: impl Into, data: u32) { - let addr: usize = addr.into(); - if let Some(slice) = self.get_mut(addr..addr + 4) { - data.to_be_bytes().as_mut().swap_with_slice(slice); - } - } -} - -impl Write for Bus { - /// Write a u16 to address `addr` - fn write(&mut self, addr: impl Into, data: u64) { - let addr: usize = addr.into(); - if let Some(slice) = self.get_mut(addr..addr + 8) { - data.to_be_bytes().as_mut().swap_with_slice(slice); - } - } -} - -impl Write for Bus { - /// Write a u16 to address `addr` - fn write(&mut self, addr: impl Into, data: u128) { - let addr: usize = addr.into(); - if let Some(slice) = self.get_mut(addr..addr + 16) { - data.to_be_bytes().as_mut().swap_with_slice(slice); - } - } -} - #[cfg(target_feature = "rhexdump")] impl Display for Bus { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { diff --git a/src/cpu/bus/read.rs b/src/cpu/bus/read.rs new file mode 100644 index 0000000..ea20481 --- /dev/null +++ b/src/cpu/bus/read.rs @@ -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 { + /// Gets the slice of Self at [SliceIndex] I + fn get(&self, index: I) -> Option<&>::Output> + where + I: SliceIndex<[T]>; + + /// Gets a mutable slice of Self at [SliceIndex] I + fn get_mut(&mut self, index: I) -> Option<&mut >::Output> + where + I: SliceIndex<[T]>; +} + +/// Read a T from address `addr` +pub trait ReadWrite: FallibleReadWrite { + /// 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) -> T { + self.read_fallible(addr).unwrap_or_else(|e| panic!("{e:?}")) + } + /// Write a T to address `addr` + fn write(&mut self, addr: impl Into, 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: Get { + /// 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) -> Result; + /// Write a T to address `addr`, returning the value as a [Result] + fn write_fallible(&mut self, addr: impl Into, 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 + FallibleReadWrite<$t>> ReadWrite<$t> for T { + #[inline(always)] + fn read(&self, addr: impl Into) -> $t { + self.read_fallible(addr).ok().unwrap_or_default() + } + #[inline(always)] + fn write(&mut self, addr: impl Into, data: $t) { + self.write_fallible(addr, data).ok(); + } + } + impl> FallibleReadWrite<$t> for T { + type Error = $crate::error::Error; + #[inline(always)] + fn read_fallible(&self, addr: impl Into) -> $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, 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); diff --git a/src/cpu/instruction.rs b/src/cpu/instruction.rs index 99386fc..14caaae 100644 --- a/src/cpu/instruction.rs +++ b/src/cpu/instruction.rs @@ -74,7 +74,7 @@ impl CPU { } /// |`00ee`| Returns from subroutine #[inline(always)] - pub(super) fn ret(&mut self, bus: &impl Read) { + pub(super) fn ret(&mut self, bus: &impl ReadWrite) { 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) { + pub(super) fn call(&mut self, a: Adr, bus: &mut impl ReadWrite) { 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 + Write)) { + pub(super) fn scroll_right(&mut self, bus: &mut (impl ReadWrite + ReadWrite)) { // 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 + Write)) { + pub(super) fn scroll_left(&mut self, bus: &mut (impl ReadWrite + ReadWrite)) { // 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() diff --git a/src/cpu/tests.rs b/src/cpu/tests.rs index 2bde028..1eb89b5 100644 --- a/src/cpu/tests.rs +++ b/src/cpu/tests.rs @@ -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?}"), diff --git a/src/error.rs b/src/error.rs index 2212e89..51542e5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,7 @@ use std::ops::Range; -use crate::bus::Region; +use crate::cpu::bus::Region; use thiserror::Error; /// Result type, equivalent to [std::result::Result] @@ -36,7 +36,7 @@ pub enum Error { }, /// Tried to fetch [Range] from bus, received nothing #[error("Invalid range {range:04x?} for bus")] - InvalidBusRange { + InvalidAddressRange { /// The offending [Range] range: Range, }, diff --git a/src/lib.rs b/src/lib.rs index ec24a46..c1daaea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,13 +8,12 @@ //! //! Hopefully, though, you'll find some use in it. -pub mod bus; pub mod cpu; pub mod error; // Common imports for Chirp -pub use bus::{Bus, Read, Region::*, Write}; pub use cpu::{ + bus::{Bus, Get, ReadWrite, Region::*}, disassembler::{Dis, Disassembler}, flags::Flags, mode::Mode, @@ -29,5 +28,5 @@ pub struct Chip8 { /// Contains the registers, flags, and operating state for a single Chip-8 pub cpu: cpu::CPU, /// Contains the memory of a chip-8 - pub bus: bus::Bus, + pub bus: cpu::bus::Bus, }