From 96b6038bbefe9a0b3dbfcb5a103a9ac7cf83bf0b Mon Sep 17 00:00:00 2001 From: John Breaux Date: Sat, 29 Apr 2023 23:32:14 -0500 Subject: [PATCH] Chirp: Bus Schism: Split into Mem (internal) and Screen (external) --- src/bin/chirp-imgui/emu.rs | 34 ++---- src/bin/chirp-imgui/main.rs | 1 - src/bin/chirp-minifb/main.rs | 25 ++--- src/bin/chirp-minifb/tests.rs | 4 +- src/bin/chirp-minifb/ui.rs | 73 +++++------- src/bin/chirp-shot-viewer/main.rs | 2 +- src/cpu.rs | 40 +++---- src/cpu/behavior.rs | 37 ++++--- src/cpu/{bus.rs => mem.rs} | 177 +++++++++--------------------- src/cpu/tests.rs | 78 ++++++------- src/cpu/tests/decode.rs | 8 +- src/error.rs | 2 +- src/lib.rs | 6 +- src/screen.rs | 132 ++++++++++++++++++++++ src/traits.rs | 3 +- src/traits/auto_cast.rs | 30 ++--- tests/chip8_test_suite.rs | 22 ++-- tests/integration.rs | 29 ++--- 18 files changed, 356 insertions(+), 347 deletions(-) rename src/cpu/{bus.rs => mem.rs} (59%) create mode 100644 src/screen.rs diff --git a/src/bin/chirp-imgui/emu.rs b/src/bin/chirp-imgui/emu.rs index 76a0007..26346c9 100644 --- a/src/bin/chirp-imgui/emu.rs +++ b/src/bin/chirp-imgui/emu.rs @@ -1,7 +1,7 @@ //! The emulator's state, including the screen data use super::{error, BACKGROUND, FOREGROUND}; -use chirp::{bus, Bus, Screen, CPU}; +use chirp::{Grab, Screen, CPU}; use pixels::Pixels; use std::path::{Path, PathBuf}; use winit::event::VirtualKeyCode; @@ -10,7 +10,7 @@ use winit_input_helper::WinitInputHelper; /// The state of the application #[derive(Debug)] pub struct Emulator { - screen: Bus, + screen: Screen, cpu: CPU, pub ipf: usize, pub rom: PathBuf, @@ -23,9 +23,7 @@ impl Emulator { /// # Panics /// Panics if the provided ROM does not exist pub fn new(ipf: usize, rom: impl AsRef) -> Self { - let screen = bus! { - Screen [0x000..0x100], - }; + let screen = Screen::default(); let mut cpu = CPU::default(); cpu.load_program(&rom).expect("Loaded file MUST exist."); Self { @@ -43,7 +41,7 @@ impl Emulator { } /// Rasterizes the screen into a [Pixels] buffer pub fn draw(&mut self, pixels: &mut Pixels) -> Result<(), error::Error> { - if let Some(screen) = self.screen.get_region(Screen) { + if let Some(screen) = self.screen.grab(..) { let len_log2 = screen.len().ilog2() / 2; #[allow(unused_variables)] let (width, height) = (2u32.pow(len_log2 + 2), 2u32.pow(len_log2 + 1)); @@ -102,7 +100,7 @@ impl Emulator { } /// Prints the screen (using the highest resolution available printer) to stdout pub fn print_screen(&self) -> Result<(), error::Error> { - self.screen.print_screen()?; + self.screen.print_screen(); Ok(()) } /// Dumps the raw screen bytes to a file named `{rom}_{cycle}.bin`, @@ -114,23 +112,9 @@ impl Emulator { self.cpu.cycle() )); path.set_extension("bin"); - if std::fs::write( - &path, - self.screen - .get_region(Screen) - .expect("Region::Screen should exist"), - ) - .is_ok() - { + if std::fs::write(&path, self.screen.as_slice()).is_ok() { eprintln!("Saved to {}", &path.display()); - } else if std::fs::write( - "screen_dump.bin", - self.screen - .get_region(Screen) - .expect("Region::Screen should exist"), - ) - .is_ok() - { + } else if std::fs::write("screen_dump.bin", self.screen.as_slice()).is_ok() { eprintln!("Saved to screen_dump.bin"); } else { eprintln!("Failed to dump screen to file.") @@ -171,14 +155,14 @@ impl Emulator { /// Soft-resets the CPU, keeping the program in memory pub fn soft_reset(&mut self) { self.cpu.reset(); - self.screen.clear_region(Screen); + self.screen.clear(); eprintln!("Soft Reset"); } /// Creates a new CPU with the current CPU's flags pub fn hard_reset(&mut self) { self.cpu.reset(); - self.screen.clear_region(Screen); + self.screen.clear(); // keep the flags let flags = self.cpu.flags.clone(); // instantiate a completely new CPU, and reload the ROM from disk diff --git a/src/bin/chirp-imgui/main.rs b/src/bin/chirp-imgui/main.rs index 97a353e..65c0d21 100644 --- a/src/bin/chirp-imgui/main.rs +++ b/src/bin/chirp-imgui/main.rs @@ -8,7 +8,6 @@ mod gui; use crate::emu::*; use crate::gui::*; -use core::panic; use pixels::{Pixels, SurfaceTexture}; use std::result::Result; use winit::dpi::LogicalSize; diff --git a/src/bin/chirp-minifb/main.rs b/src/bin/chirp-minifb/main.rs index 0e6741d..81eee87 100644 --- a/src/bin/chirp-minifb/main.rs +++ b/src/bin/chirp-minifb/main.rs @@ -12,7 +12,6 @@ use chirp::error::Error::BreakpointHit; use chirp::{error::Result, *}; use gumdrop::*; use owo_colors::OwoColorize; -use std::fs::read; use std::{ path::PathBuf, time::{Duration, Instant}, @@ -108,6 +107,11 @@ struct Arguments { pub frame_rate: u64, } +#[derive(Debug)] +pub struct Chip8 { + pub cpu: CPU, + pub screen: Screen, +} #[derive(Debug)] struct State { pub speed: usize, @@ -127,16 +131,8 @@ impl State { rate: options.frame_rate, perf: options.perf, ch8: Chip8 { - bus: bus! { - // Load the charset into ROM - Charset [0x0050..0x00A0] = include_bytes!("../../mem/charset.bin"), - // Load the ROM file into RAM - Program [0x0200..0x1000] = &read(&options.file)?, - // Create a screen - Screen [0x1000..0x1100], - }, cpu: CPU::new( - &options.file, + Some(&options.file), 0x50, 0x200, Dis::default(), @@ -148,6 +144,7 @@ impl State { ..Default::default() }, )?, + screen: Screen::default(), }, ui: UIBuilder::new(128, 64, &options.file).build()?, ft: Instant::now(), @@ -158,7 +155,7 @@ impl State { state.ch8.cpu.flags.quirks.draw_wait ^= options.drawsync; state.ch8.cpu.flags.quirks.shift ^= options.shift; state.ch8.cpu.flags.quirks.stupid_jumps ^= options.jumping; - state.ch8.bus.write(0x1feu16, options.data); + state.ch8.screen.write(0x1feu16, options.data); Ok(state) } fn keys(&mut self) -> Result { @@ -173,7 +170,7 @@ impl State { match self.step { Some(ticks) => { let time = Instant::now(); - self.ch8.cpu.multistep(&mut self.ch8.bus, ticks)?; + self.ch8.cpu.multistep(&mut self.ch8.screen, ticks)?; if self.perf { let time = time.elapsed(); let nspt = time.as_secs_f64() / ticks as f64; @@ -186,7 +183,7 @@ impl State { } } None => { - self.ch8.cpu.multistep(&mut self.ch8.bus, rate)?; + self.ch8.cpu.multistep(&mut self.ch8.screen, rate)?; } } } @@ -195,7 +192,7 @@ impl State { fn wait_for_next_frame(&mut self) { let rate = Duration::from_nanos(1_000_000_000 / self.rate + 1); std::thread::sleep(rate.saturating_sub(self.ft.elapsed())); - self.ft = self.ft + rate; + self.ft += rate; } } diff --git a/src/bin/chirp-minifb/tests.rs b/src/bin/chirp-minifb/tests.rs index 23104f7..a2289fd 100644 --- a/src/bin/chirp-minifb/tests.rs +++ b/src/bin/chirp-minifb/tests.rs @@ -1,6 +1,8 @@ //! Tests for chirp-minifb +#![allow(clippy::redundant_clone)] use super::ui::*; +use super::Chip8; use chirp::*; use std::{collections::hash_map::DefaultHasher, hash::Hash}; @@ -29,7 +31,7 @@ mod ui { fn new_chip8() -> Chip8 { Chip8 { cpu: CPU::default(), - bus: bus! {}, + screen: Screen::default(), } } #[test] diff --git a/src/bin/chirp-minifb/ui.rs b/src/bin/chirp-minifb/ui.rs index e6c7717..f043c76 100644 --- a/src/bin/chirp-minifb/ui.rs +++ b/src/bin/chirp-minifb/ui.rs @@ -4,18 +4,14 @@ //! Platform-specific IO/UI code, and some debug functionality. //! TODO: Destroy this all. +use super::Chip8; +use chirp::{error::Result, screen::Screen}; +use minifb::*; use std::{ path::{Path, PathBuf}, time::Instant, }; -use chirp::{ - cpu::bus::{Bus, Region}, - error::Result, - Chip8, -}; -use minifb::*; - #[derive(Clone, Debug)] pub struct UIBuilder { pub width: usize, @@ -105,27 +101,25 @@ impl FrameBuffer { format: Default::default(), } } - pub fn render(&mut self, window: &mut Window, bus: &Bus) -> Result<()> { - if let Some(screen) = bus.get_region(Region::Screen) { - // Resizing the buffer does not unmap memory. - // After the first use of high-res mode, this is pretty cheap - (self.width, self.height) = match screen.len() { - 256 => (64, 32), - 1024 => (128, 64), - _ => { - unimplemented!("Screen must be 64*32 or 128*64"); - } - }; - self.buffer.resize(self.width * self.height, 0); - for (idx, byte) in screen.iter().enumerate() { - for bit in 0..8 { - self.buffer[8 * idx + bit] = if byte & (1 << (7 - bit)) as u8 != 0 { - self.format.fg - } else { - self.format.bg - // .wrapping_add(0x001104 * (idx / self.width) as u32) - // .wrapping_add(0x141000 * (idx & 3) as u32) - } + pub fn render(&mut self, window: &mut Window, screen: &Screen) -> Result<()> { + // Resizing the buffer does not unmap memory. + // After the first use of high-res mode, this is pretty cheap + (self.width, self.height) = match screen.len() { + 256 => (64, 32), + 1024 => (128, 64), + _ => { + unimplemented!("Screen must be 64*32 or 128*64"); + } + }; + self.buffer.resize(self.width * self.height, 0); + for (idx, byte) in screen.as_slice().iter().enumerate() { + for bit in 0..8 { + self.buffer[8 * idx + bit] = if byte & (1 << (7 - bit)) as u8 != 0 { + self.format.fg + } else { + self.format.bg + // .wrapping_add(0x001104 * (idx / self.width) as u32) + // .wrapping_add(0x141000 * (idx & 3) as u32) } } } @@ -164,7 +158,7 @@ impl UI { } self.time = Instant::now(); // update framebuffer - self.fb.render(&mut self.window, &ch8.bus)?; + self.fb.render(&mut self.window, &ch8.screen)?; Ok(true) } @@ -182,7 +176,6 @@ impl UI { .into_iter() .filter(|key| !self.window.get_keys().contains(key)) }; - use crate::ui::Region::*; for key in get_keys_released() { if let Some(key) = identify_key(key) { ch8.cpu.release(key)?; @@ -193,7 +186,7 @@ impl UI { use Key::*; match key { F1 | Comma => ch8.cpu.dump(), - F2 | Period => ch8.bus.print_screen()?, + F2 | Period => ch8.screen.print_screen(), F3 => { debug_dump_screen(ch8, &self.rom).expect("Unable to write debug screen dump"); } @@ -217,7 +210,7 @@ impl UI { }), F6 | Enter => { eprintln!("Step"); - ch8.cpu.singlestep(&mut ch8.bus)?; + ch8.cpu.singlestep(&mut ch8.screen)?; } F7 => { eprintln!("Set breakpoint {:03x}.", ch8.cpu.pc()); @@ -230,7 +223,7 @@ impl UI { F9 | Delete => { eprintln!("Soft reset state.cpu {:03x}", ch8.cpu.pc()); ch8.cpu.soft_reset(); - ch8.bus.clear_region(Screen); + ch8.screen.clear(); } Escape => return Ok(false), key => { @@ -274,19 +267,9 @@ pub fn debug_dump_screen(ch8: &Chip8, rom: &Path) -> Result<()> { ch8.cpu.cycle() )); path.set_extension("bin"); - if let Ok(_) = std::fs::write( - &path, - ch8.bus - .get_region(Region::Screen) - .expect("Region::Screen should exist"), - ) { + if std::fs::write(&path, ch8.screen.as_slice()).is_ok() { eprintln!("Saved to {}", &path.display()); - } else if let Ok(_) = std::fs::write( - "screen_dump.bin", - ch8.bus - .get_region(Region::Screen) - .expect("Region::Screen should exist"), - ) { + } else if std::fs::write("screen_dump.bin", ch8.screen.as_slice()).is_ok() { eprintln!("Saved to screen_dump.bin"); } else { eprintln!("Failed to dump screen to file.") diff --git a/src/bin/chirp-shot-viewer/main.rs b/src/bin/chirp-shot-viewer/main.rs index 9b8f023..9a092b5 100644 --- a/src/bin/chirp-shot-viewer/main.rs +++ b/src/bin/chirp-shot-viewer/main.rs @@ -4,7 +4,7 @@ use std::{env::args, fs::read}; fn main() -> Result<()> { for screen in args().skip(1).inspect(|screen| println!("{screen}")) { let screen = read(screen)?; - bus! {Screen [0..screen.len()] = &screen}.print_screen()?; + Screen::from(screen).print_screen(); } Ok(()) } diff --git a/src/cpu.rs b/src/cpu.rs index 4189218..6b0a6b4 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -7,26 +7,27 @@ mod tests; mod behavior; -pub mod bus; pub mod flags; pub mod instruction; +#[macro_use] +pub mod mem; pub mod mode; pub mod quirks; use self::{ - bus::{Bus, Region::*}, flags::Flags, instruction::{ disassembler::{Dis, Disassembler}, Insn, }, + mem::{Mem, Region::*}, mode::Mode, quirks::Quirks, }; use crate::{ - bus, error::{Error, Result}, - traits::auto_cast::{AutoCast, Grab}, + screen::Screen, + traits::{AutoCast, Grab}, }; use imperative_rs::InstructionSet; use owo_colors::OwoColorize; @@ -44,7 +45,7 @@ pub struct CPU { /// chip-8. Includes [Quirks], target IPF, etc. pub flags: Flags, // memory map info - mem: Bus, + mem: Mem, font: Adr, // memory stack: Vec, @@ -90,7 +91,7 @@ impl CPU { flags: Flags, ) -> Result { const CHARSET: &[u8] = include_bytes!("mem/charset.bin"); - let mem = bus! { + let mem = mem! { Charset [font as usize..font as usize + CHARSET.len()] = CHARSET, Program [pc as usize..0x1000], }; @@ -133,7 +134,7 @@ impl CPU { } /// Grabs a reference to the [CPU]'s memory - pub fn introspect(&mut self) -> &Bus { + pub fn introspect(&mut self) -> &Mem { &self.mem } @@ -398,7 +399,7 @@ impl CPU { /// ```rust /// # use chirp::*; /// let mut cpu = CPU::default(); - /// let mut screen = bus!{ + /// let mut screen = mem!{ /// Screen [0x000..0x100], /// }; /// cpu.load_program_bytes(&[0x00, 0xe0, 0x22, 0x02]); @@ -406,7 +407,7 @@ impl CPU { /// assert_eq!(0x202, cpu.pc()); /// assert_eq!(1, cpu.cycle()); /// ``` - pub fn singlestep(&mut self, bus: &mut Bus) -> Result<&mut Self> { + pub fn singlestep(&mut self, bus: &mut Screen) -> Result<&mut Self> { self.flags.pause = false; self.tick(bus)?; self.flags.draw_wait = false; @@ -421,7 +422,7 @@ impl CPU { /// ```rust /// # use chirp::*; /// let mut cpu = CPU::default(); - /// let mut screen = bus!{ + /// let mut screen = mem!{ /// Screen [0x000..0x100], /// }; /// cpu.load_program_bytes(&[0x00, 0xe0, 0x22, 0x02]); @@ -430,7 +431,7 @@ impl CPU { /// assert_eq!(0x202, cpu.pc()); /// assert_eq!(0x20, cpu.cycle()); /// ``` - pub fn multistep(&mut self, screen: &mut Bus, steps: usize) -> Result<&mut Self> { + pub fn multistep(&mut self, screen: &mut Screen, steps: usize) -> Result<&mut Self> { //let speed = 1.0 / steps as f64; for _ in 0..steps { self.tick(screen)?; @@ -447,22 +448,21 @@ impl CPU { /// This result contains information about the breakpoint, but can be safely ignored. /// /// Returns [Error::UnimplementedInstruction] if the instruction at `pc` is unimplemented. + /// /// # Examples /// ```rust /// # use chirp::*; /// let mut cpu = CPU::default(); - /// let mut bus = bus!{ - /// Program [0x0200..0x0f00] = &[ - /// 0x00, 0xe0, // cls - /// 0x22, 0x02, // jump 0x202 (pc) - /// ], - /// Screen [0x0f00..0x1000], + /// let mut bus = mem!{ + /// Screen [0x000..0x100], /// }; + /// cpu.load_program_bytes(&[0x00, 0xe0, 0x22, 0x02]); /// cpu.tick(&mut bus) /// .expect("0x00e0 (cls) should be a valid opcode."); /// assert_eq!(0x202, cpu.pc()); /// assert_eq!(1, cpu.cycle()); /// ``` + /// Returns [Error::UnimplementedInstruction] if the instruction is not implemented. /// ```rust /// # use chirp::*; @@ -470,7 +470,7 @@ impl CPU { /// let mut cpu = CPU::default(); /// # cpu.flags.debug = true; // enable live disassembly /// # cpu.flags.monotonic = true; // enable monotonic/test timing - /// let mut bus = bus!{ + /// let mut bus = mem!{ /// Screen [0x0f00..0x1000], /// }; /// cpu.load_program_bytes(&[ @@ -480,7 +480,7 @@ impl CPU { /// dbg!(cpu.tick(&mut bus)) /// .expect_err("Should return Error::InvalidInstruction { 0xffff }"); /// ``` - pub fn tick(&mut self, screen: &mut Bus) -> Result<&mut Self> { + pub fn tick(&mut self, screen: &mut Screen) -> Result<&mut Self> { // Do nothing if paused if self.flags.is_paused() { // always tick in test mode @@ -608,7 +608,7 @@ impl Default for CPU { fn default() -> Self { CPU { stack: vec![], - mem: bus! { + mem: mem! { Charset [0x0050..0x00a0] = include_bytes!("mem/charset.bin"), Program [0x0200..0x1000], }, diff --git a/src/cpu/behavior.rs b/src/cpu/behavior.rs index 603e113..3341e4b 100644 --- a/src/cpu/behavior.rs +++ b/src/cpu/behavior.rs @@ -3,14 +3,15 @@ //! Contains implementations for each Chip-8 [Insn] -use super::{bus::Region, *}; +use super::*; +use crate::traits::Grab; use rand::random; impl CPU { /// Executes a single [Insn] #[rustfmt::skip] #[inline(always)] - pub(super) fn execute(&mut self, screen: &mut Bus, instruction: Insn) { + pub(super) fn execute(&mut self, screen: &mut Screen, instruction: Insn) { match instruction { // Core Chip-8 instructions Insn::cls => self.clear_screen(screen), @@ -73,8 +74,8 @@ impl CPU { /// |`00e0`| Clears the screen memory to 0 /// Corresponds to [Insn::cls] #[inline(always)] - pub(super) fn clear_screen(&mut self, bus: &mut Bus) { - bus.clear_region(Region::Screen); + pub(super) fn clear_screen(&mut self, screen: &mut Screen) { + screen.clear() } /// |`00ee`| Returns from subroutine /// Corresponds to [Insn::ret] @@ -99,7 +100,7 @@ impl CPU { /// /// Corresponds to [Insn::scd] #[inline(always)] - pub(super) fn scroll_down(&mut self, n: Nib, screen: &mut Bus) { + pub(super) fn scroll_down(&mut self, n: Nib, screen: &mut Screen) { match self.flags.draw_mode { true => { // Get a line from the bus @@ -148,18 +149,18 @@ impl CPU { /// Initialize lores mode /// /// Corresponds to [Insn::lores] - pub(super) fn init_lores(&mut self, screen: &mut Bus) { + pub(super) fn init_lores(&mut self, screen: &mut Screen) { self.flags.draw_mode = false; - screen.set_region(Region::Screen, 0..256); + screen.with_size(256); self.clear_screen(screen); } /// # |`00ff`| /// Initialize hires mode /// /// Corresponds to [Insn::hires] - pub(super) fn init_hires(&mut self, screen: &mut Bus) { + pub(super) fn init_hires(&mut self, screen: &mut Screen) { self.flags.draw_mode = true; - screen.set_region(Region::Screen, 0..1024); + screen.with_size(1024); self.clear_screen(screen); } } @@ -439,7 +440,7 @@ impl CPU { /// # Quirk /// On the original chip-8 interpreter, this will wait for a VBI #[inline(always)] - pub(super) fn draw(&mut self, x: Reg, y: Reg, n: Nib, screen: &mut Bus) { + pub(super) fn draw(&mut self, x: Reg, y: Reg, n: Nib, screen: &mut Screen) { if !self.flags.quirks.draw_wait { self.flags.draw_wait = true; } @@ -453,12 +454,20 @@ impl CPU { /// |`Dxyn`| Chip-8: Draws n-byte sprite to the screen at coordinates (vX, vY) #[inline(always)] - pub(super) fn draw_lores(&mut self, x: Reg, y: Reg, n: Nib, scr: &mut Bus) { + pub(super) fn draw_lores(&mut self, x: Reg, y: Reg, n: Nib, scr: &mut Screen) { self.draw_sprite(self.v[x] as u16 % 64, self.v[y] as u16 % 32, n, 64, 32, scr); } #[inline(always)] - pub(super) fn draw_sprite(&mut self, x: u16, y: u16, n: Nib, w: u16, h: u16, screen: &mut Bus) { + pub(super) fn draw_sprite( + &mut self, + x: u16, + y: u16, + n: Nib, + w: u16, + h: u16, + screen: &mut Screen, + ) { let w_bytes = w / 8; self.v[0xf] = 0; if let Some(sprite) = self.mem.grab(self.i as usize..(self.i + n as u16) as usize) { @@ -492,7 +501,7 @@ impl CPU { impl CPU { /// |`Dxyn`| Super-Chip extension high-resolution graphics mode #[inline(always)] - pub(super) fn draw_hires(&mut self, x: Reg, y: Reg, n: Nib, screen: &mut Bus) { + pub(super) fn draw_hires(&mut self, x: Reg, y: Reg, n: Nib, screen: &mut Screen) { if !self.flags.quirks.draw_wait { self.flags.draw_wait = true; } @@ -508,7 +517,7 @@ impl CPU { } /// Draws a 16x16 Super Chip sprite #[inline(always)] - pub(super) fn draw_schip_sprite(&mut self, x: u16, y: u16, w: u16, screen: &mut Bus) { + pub(super) fn draw_schip_sprite(&mut self, x: u16, y: u16, w: u16, screen: &mut Screen) { self.v[0xf] = 0; let w_bytes = w / 8; if let Some(sprite) = self.mem.grab(self.i as usize..(self.i + 32) as usize) { diff --git a/src/cpu/bus.rs b/src/cpu/mem.rs similarity index 59% rename from src/cpu/bus.rs rename to src/cpu/mem.rs index 03a4e49..5c14a8f 100644 --- a/src/cpu/bus.rs +++ b/src/cpu/mem.rs @@ -1,45 +1,43 @@ // (c) 2023 John A. Breaux // This code is licensed under MIT license (see LICENSE for details) -//! The Bus connects the CPU to Memory +//! The Mem represents the CPU's memory //! -//! This is more of a memory management unit + some utils for reading/writing +//! Contains some handy utils for reading and writing -use crate::error::{Error::MissingRegion, Result}; +use crate::{error::Result, traits::Grab}; use std::{ fmt::{Debug, Display, Formatter}, ops::Range, slice::SliceIndex, }; -/// Creates a new bus, growing the backing memory as needed +/// Creates a new [Mem], growing as needed /// # Examples /// ```rust /// # use chirp::*; -/// let mut bus = bus! { +/// let mut mem = mem! { /// Charset [0x0000..0x0800] = b"ABCDEF", -/// Program [0x0800..0xf000] = include_bytes!("bus.rs"), +/// Program [0x0800..0xf000] = include_bytes!("mem.rs"), /// }; /// ``` #[macro_export] -macro_rules! bus { +macro_rules! mem { ($($name:path $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => { - $crate::cpu::bus::Bus::default()$(.add_region_owned($name, $range)$(.load_region_owned($name, $data))?)* + $crate::cpu::mem::Mem::default()$(.add_region_owned($name, $range)$(.load_region_owned($name, $data))?)* }; } -pub use crate::traits::auto_cast::{AutoCast, Grab}; - // Traits Read and Write are here purely to make implementing other things more bearable -impl Grab for Bus { - /// Gets a slice of [Bus] memory +impl Grab for Mem { + /// Gets a slice of [Mem] memory /// # Examples /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let bus = Bus::new() + /// let mem = Mem::new() /// .add_region_owned(Program, 0..10); - /// assert!([0;10].as_slice() == bus.get(0..10).unwrap()); + /// assert!([0;10].as_slice() == mem.grab(0..10).unwrap()); ///# Ok(()) ///# } /// ``` @@ -51,14 +49,14 @@ impl Grab for Bus { self.memory.get(index) } - /// Gets a mutable slice of [Bus] memory + /// Gets a mutable slice of [Mem] memory /// # Examples /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let mut bus = Bus::new() + /// let mut mem = Mem::new() /// .add_region_owned(Program, 0..10); - /// assert!([0;10].as_slice() == bus.get_mut(0..10).unwrap()); + /// assert!([0;10].as_slice() == mem.grab_mut(0..10).unwrap()); ///# Ok(()) ///# } /// ``` @@ -80,8 +78,6 @@ pub enum Region { Charset, /// Program memory Program, - /// Screen buffer - Screen, #[doc(hidden)] /// Total number of named regions Count, @@ -95,7 +91,6 @@ impl Display for Region { match self { Region::Charset => "Charset", Region::Program => "Program", - Region::Screen => "Screen", _ => "", } ) @@ -105,35 +100,35 @@ impl Display for Region { /// Stores memory in a series of named regions with ranges #[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Bus { +pub struct Mem { memory: Vec, region: [Option>; 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 +impl Mem { + // TODO: make mem::new() give a properly set up mem with a default memory map + /// Constructs a new mem /// # Examples /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let bus = Bus::new(); - /// assert!(bus.is_empty()); + /// let mem = Mem::new(); + /// assert!(mem.is_empty()); ///# Ok(()) ///# } /// ``` pub fn new() -> Self { - Bus::default() + Mem::default() } - /// Gets the length of the bus' backing memory + /// Gets the length of the mem' backing memory /// # Examples /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let bus = Bus::new() + /// let mem = Mem::new() /// .add_region_owned(Program, 0..1234); - /// assert_eq!(1234, bus.len()); + /// assert_eq!(1234, mem.len()); ///# Ok(()) ///# } /// ``` @@ -146,47 +141,48 @@ impl Bus { /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let bus = Bus::new(); - /// assert!(bus.is_empty()); + /// let mem = Mem::new(); + /// assert!(mem.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 + + /// Grows the Mem 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()); + /// let mut mem = Mem::new(); + /// mem.with_size(1234); + /// assert_eq!(1234, mem.len()); + /// mem.with_size(0); + /// assert_eq!(1234, mem.len()); ///# Ok(()) ///# } /// ``` - pub fn with_size(&mut self, size: usize) { + 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] + /// Adds a new names range ([Region]) to an owned [Mem] 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] + /// Adds a new named range ([Region]) to a [Mem] /// # Examples /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let mut bus = Bus::new(); - /// bus.add_region(Program, 0..1234); - /// assert_eq!(1234, bus.len()); + /// let mut mem = Mem::new(); + /// mem.add_region(Program, 0..1234); + /// assert_eq!(1234, mem.len()); ///# Ok(()) ///# } /// ``` @@ -203,9 +199,9 @@ impl Bus { /// ```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()); + /// let mut mem = Mem::new().add_region_owned(Program, 0..1234); + /// mem.set_region(Program, 1234..2345); + /// assert_eq!(2345, mem.len()); ///# Ok(()) ///# } /// ``` @@ -217,7 +213,7 @@ impl Bus { self } - /// Loads data into a [Region] on an *owned* [Bus], for use during initialization + /// Loads data into a [Region] on an *owned* [Mem], for use during initialization pub fn load_region_owned(mut self, name: Region, data: &[u8]) -> Self { self.load_region(name, data).ok(); self @@ -228,7 +224,7 @@ impl Bus { /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let bus = Bus::new() + /// let mem = Mem::new() /// .add_region_owned(Program, 0..1234) /// .load_region(Program, b"Hello, world!")?; ///# // TODO: Test if region actually contains "Hello, world!" @@ -248,7 +244,7 @@ impl Bus { /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let bus = Bus::new() + /// let mem = Mem::new() /// .add_region_owned(Program, 0..1234) /// .clear_region(Program); ///# // TODO: test if region actually clear @@ -259,7 +255,7 @@ impl Bus { /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let bus = Bus::new() + /// let mem = Mem::new() /// .add_region_owned(Program, 0..1234) /// .clear_region(Screen); ///# // TODO: test if region actually clear @@ -278,9 +274,9 @@ impl Bus { /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let bus = Bus::new() + /// let mem = Mem::new() /// .add_region_owned(Program, 0..10); - /// assert!([0;10].as_slice() == bus.get_region(Program).unwrap()); + /// assert!([0;10].as_slice() == mem.get_region(Program).unwrap()); ///# Ok(()) ///# } /// ``` @@ -295,9 +291,9 @@ impl Bus { /// ```rust ///# use chirp::*; ///# fn main() -> Result<()> { - /// let mut bus = Bus::new() + /// let mut mem = Mem::new() /// .add_region_owned(Program, 0..10); - /// assert!([0;10].as_slice() == bus.get_region_mut(Program).unwrap()); + /// assert!([0;10].as_slice() == mem.get_region_mut(Program).unwrap()); ///# Ok(()) ///# } /// ``` @@ -306,79 +302,10 @@ impl Bus { debug_assert!(self.region.get(name as usize).is_some()); self.grab_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(width * 8, 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 { +impl Display for Mem { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { use rhexdump::Rhexdump; let mut rhx = Rhexdump::default(); diff --git a/src/cpu/tests.rs b/src/cpu/tests.rs index ba8c713..7e262f2 100644 --- a/src/cpu/tests.rs +++ b/src/cpu/tests.rs @@ -13,15 +13,12 @@ //! Some of these tests run >16M times, which is very silly use super::*; -use crate::{ - bus, - cpu::bus::{Bus, Region::*}, -}; +use crate::Screen; use rand::random; mod decode; -fn setup_environment() -> (CPU, Bus) { +fn setup_environment() -> (CPU, Screen) { let mut ch8 = ( CPU { flags: Flags { @@ -31,10 +28,7 @@ fn setup_environment() -> (CPU, Bus) { }, ..CPU::default() }, - bus! { - // Create a screen - Screen [0x000..0x100] = include_bytes!("../../chip8Archive/roms/1dcell.ch8"), - }, + Screen::default(), ); ch8.0 .load_program_bytes(include_bytes!("tests/roms/jumptest.ch8")) @@ -43,9 +37,7 @@ fn setup_environment() -> (CPU, Bus) { } fn print_screen(bytes: &[u8]) { - bus! {Screen [0..0x100] = bytes} - .print_screen() - .expect("Printing screen should not fail if Screen exists.") + Screen::from(bytes).print_screen() } /// Unused instructions @@ -57,9 +49,9 @@ mod unimplemented { $pub:vis test $name:ident { $($insn:literal),+$(,)? } );+ $(;)?) => { $( $(#[$attr])* #[test] $pub fn $name () {$( - let (mut cpu, mut bus) = setup_environment(); + let (mut cpu, mut screen) = setup_environment(); cpu.mem.write(0x200u16, $insn as u16); - cpu.tick(&mut bus) + cpu.tick(&mut screen) .expect_err(stringify!($insn is not an instruction)); )*} )+ }; @@ -95,10 +87,11 @@ mod sys { /// 00e0: Clears the screen memory to 0 #[test] fn clear_screen() { - let (mut cpu, mut bus) = setup_environment(); - cpu.clear_screen(&mut bus); - bus.get_region(Screen) - .expect("Expected screen, got None") + let (mut cpu, mut screen) = setup_environment(); + cpu.clear_screen(&mut screen); + screen + .grab(..) + .unwrap() .iter() .for_each(|byte| assert_eq!(*byte, 0)); } @@ -751,7 +744,7 @@ mod io { #[test] fn draw() { for test in SCREEN_TESTS { - let (mut cpu, mut bus) = setup_environment(); + let (mut cpu, mut screen) = setup_environment(); cpu.flags.quirks = test.quirks; // Debug mode is 5x slower cpu.flags.debug = false; @@ -759,18 +752,13 @@ mod io { cpu.mem.load_region(Program, test.program).unwrap(); // Run the test program for the specified number of steps while cpu.cycle() < test.steps { - cpu.multistep(&mut bus, 10.min(test.steps - cpu.cycle())) + cpu.multistep(&mut screen, 10.min(test.steps - cpu.cycle())) .expect("Draw tests should not contain undefined instructions"); } // Compare the screen to the reference screen buffer - bus.print_screen() - .expect("Printing screen should not fail if screen exists"); + screen.print_screen(); print_screen(test.screen); - assert_eq!( - bus.get_region(Screen) - .expect("Getting screen should not fail if screen exists"), - test.screen - ); + assert_eq!(screen.grab(..).unwrap(), test.screen); } } } @@ -1028,15 +1016,15 @@ mod io { // Perform DMA store cpu.i = addr as u16; cpu.store_dma(len); - // Check that bus grabbed the correct data - let bus = cpu + // Check that screen grabbed the correct data + let screen = cpu .mem .grab_mut(addr..addr + DATA.len()) .expect("Getting a mutable slice at addr 0x0456 should not fail"); - assert_eq!(bus[0..=len], DATA[0..=len]); - assert_eq!(bus[len + 1..], [0; 16][len + 1..]); + assert_eq!(screen[0..=len], DATA[0..=len]); + assert_eq!(screen[len + 1..], [0; 16][len + 1..]); // clear - bus.fill(0); + screen.fill(0); } } @@ -1074,11 +1062,11 @@ mod behavior { use std::time::Duration; #[test] fn delay() { - let (mut cpu, mut bus) = setup_environment(); + let (mut cpu, mut screen) = setup_environment(); cpu.flags.monotonic = false; cpu.delay = 10; for _ in 0..2 { - cpu.multistep(&mut bus, 8) + cpu.multistep(&mut screen, 8) .expect("Running valid instructions should always succeed"); std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0)); } @@ -1087,11 +1075,11 @@ mod behavior { } #[test] fn sound() { - let (mut cpu, mut bus) = setup_environment(); + let (mut cpu, mut screen) = setup_environment(); cpu.flags.monotonic = false; // disable monotonic timing cpu.sound = 10; for _ in 0..2 { - cpu.multistep(&mut bus, 8) + cpu.multistep(&mut screen, 8) .expect("Running valid instructions should always succeed"); std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0)); } @@ -1100,11 +1088,11 @@ mod behavior { } #[test] fn vbi_wait() { - let (mut cpu, mut bus) = setup_environment(); + let (mut cpu, mut screen) = setup_environment(); cpu.flags.monotonic = false; // disable monotonic timing cpu.flags.draw_wait = true; for _ in 0..2 { - cpu.multistep(&mut bus, 8) + cpu.multistep(&mut screen, 8) .expect("Running valid instructions should always succeed"); std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0)); } @@ -1118,9 +1106,9 @@ mod behavior { #[test] #[cfg_attr(feature = "unstable", no_coverage)] fn hit_break() { - let (mut cpu, mut bus) = setup_environment(); + let (mut cpu, mut screen) = setup_environment(); cpu.set_break(0x202); - match cpu.multistep(&mut bus, 10) { + match cpu.multistep(&mut screen, 10) { Err(crate::error::Error::BreakpointHit { addr, next }) => { assert_eq!(0x202, addr); // current address is 202 assert_eq!(0x1204, next); // next insn is `jmp 204` @@ -1133,9 +1121,9 @@ mod behavior { #[test] #[cfg_attr(feature = "unstable", no_coverage)] fn hit_break_singlestep() { - let (mut cpu, mut bus) = setup_environment(); + let (mut cpu, mut screen) = setup_environment(); cpu.set_break(0x202); - match cpu.singlestep(&mut bus) { + match cpu.singlestep(&mut screen) { Err(crate::error::Error::BreakpointHit { addr, next }) => { assert_eq!(0x202, addr); // current address is 202 assert_eq!(0x1204, next); // next insn is `jmp 204` @@ -1150,10 +1138,10 @@ mod behavior { #[test] #[cfg_attr(feature = "unstable", no_coverage)] fn invalid_pc() { - let (mut cpu, mut bus) = setup_environment(); - // The bus extends from 0x0..0x1000 + let (mut cpu, mut screen) = setup_environment(); + // The screen extends from 0x0..0x1000 cpu.pc = 0x1001; - match cpu.tick(&mut bus) { + match cpu.tick(&mut screen) { Err(Error::InvalidAddressRange { range }) => { eprintln!("InvalidAddressRange {{ {range:04x?} }}") } diff --git a/src/cpu/tests/decode.rs b/src/cpu/tests/decode.rs index 875d19f..85b6810 100644 --- a/src/cpu/tests/decode.rs +++ b/src/cpu/tests/decode.rs @@ -9,17 +9,15 @@ const INDX: &[u8; 16] = b"\0\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d /// runs one arbitrary operation on a brand new CPU /// returns the CPU for inspection fn run_single_op(op: &[u8]) -> CPU { - let (mut cpu, mut bus) = ( + let (mut cpu, mut screen) = ( CPU::default(), - bus! { - Screen[0x0..0x1000], - }, + Screen::default(), ); cpu.mem .load_region(Program, op).unwrap(); cpu.v = *INDX; cpu.flags.quirks = Quirks::from(false); - cpu.tick(&mut bus).unwrap(); // will panic if unimplemented + cpu.tick(&mut screen).unwrap(); // will panic if unimplemented cpu } diff --git a/src/error.rs b/src/error.rs index 1247841..6380238 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,7 +6,7 @@ pub mod any_range; use any_range::AnyRange; -use crate::cpu::bus::Region; +use crate::cpu::mem::Region; use thiserror::Error; /// Result type, equivalent to [std::result::Result] diff --git a/src/lib.rs b/src/lib.rs index 1c4763b..e114fe6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,11 +10,11 @@ pub mod cpu; pub mod error; +pub mod screen; pub mod traits; // Common imports for Chirp pub use cpu::{ - bus::{Bus, Region::*}, flags::Flags, instruction::disassembler::{Dis, Disassembler}, mode::Mode, @@ -22,8 +22,6 @@ pub use cpu::{ CPU, }; pub use error::{Error, Result}; -pub use traits::auto_cast::{AutoCast, Grab}; - /// Holds the state of a Chip-8 #[derive(Clone, Debug, Default, PartialEq)] pub struct Chip8 { @@ -32,3 +30,5 @@ pub struct Chip8 { /// Contains the memory of a chip-8 pub bus: cpu::bus::Bus, } +pub use screen::Screen; +pub use traits::{AutoCast, FallibleAutoCast, Grab}; diff --git a/src/screen.rs b/src/screen.rs new file mode 100644 index 0000000..91c132e --- /dev/null +++ b/src/screen.rs @@ -0,0 +1,132 @@ +//! Contains the raw screen bytes, and an iterator over individual bits of those bytes. + +use crate::traits::Grab; + +/// Stores the screen bytes +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Screen { + /// The screen bytes + bytes: Vec, +} + +impl> From for Screen { + fn from(value: T) -> Self { + Screen { + bytes: value.as_ref().into(), + } + } +} + +impl Default for Screen { + fn default() -> Self { + Self::new(256) + } +} + +impl Grab for Screen { + fn grab(&self, index: I) -> Option<&>::Output> + where + I: std::slice::SliceIndex<[u8]>, + { + self.bytes.get(index) + } + + fn grab_mut(&mut self, index: I) -> Option<&mut >::Output> + where + I: std::slice::SliceIndex<[u8]>, + { + self.bytes.get_mut(index) + } +} + +impl Screen { + /// Creates a new [Screen] + pub fn new(size: usize) -> Self { + Self { + bytes: vec![0; size], + } + } + + /// Returns true if the screen has 0 elements + pub fn is_empty(&self) -> bool { + self.bytes.is_empty() + } + + /// Gets the length of the [Screen] + pub fn len(&self) -> usize { + self.bytes.len() + } + + /// Gets a slice of the whole [Screen] + pub fn as_slice(&self) -> &[u8] { + self.bytes.as_slice() + } + + /// Clears the [Screen] to 0 + pub fn clear(&mut self) { + self.bytes.fill(0); + } + + /// Grows the [Screen] memory to at least size bytes, but does not truncate + /// # Examples + /// ```rust + ///# use chirp::screen::Screen; + /// let mut screen = Screen::new(256); + /// assert_eq!(1234, screen.len()); + /// screen.with_size(0); + /// assert_eq!(1234, screen.len()); + /// ``` + pub fn with_size(&mut self, size: usize) { + if self.len() < size { + self.bytes.resize(size, 0); + } + } + + /// Prints the [Screen] using either [drawille] or Box Drawing Characters + /// # Examples + /// [Screen::print_screen] will print the screen + /// ```rust + ///# use chirp::screen::Screen; + /// let screen = Screen::default(); + /// screen.print_screen(); + /// ``` + pub fn print_screen(&self) { + let len_log2 = self.bytes.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(width * 8, height); + let width = width * 8; + self.bytes + .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 self.bytes.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!("|"); + } + } + } +} diff --git a/src/traits.rs b/src/traits.rs index f333fac..b2b6830 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,4 +3,5 @@ //! Traits useful for Chirp -pub mod auto_cast; +mod auto_cast; +pub use auto_cast::{AutoCast, FallibleAutoCast, Grab}; diff --git a/src/traits/auto_cast.rs b/src/traits/auto_cast.rs index c113361..15189e5 100644 --- a/src/traits/auto_cast.rs +++ b/src/traits/auto_cast.rs @@ -3,26 +3,26 @@ //! Traits for automatically serializing and deserializing Rust primitive types. //! -//! Users of this module should impl [Get]`` for their type, which notably returns `&[u8]` and `&mut [u8]` +//! Users of this module should impl [Grab]`` for their type, which notably returns `&[u8]` and `&mut [u8]` #[allow(unused_imports)] use core::mem::size_of; use std::{fmt::Debug, slice::SliceIndex}; -/// Gets a `&[T]` at [SliceIndex] `I`. +/// Get Raw Bytes at [SliceIndex] `I`. /// -/// This is similar to the [SliceIndex] method `.get(...)`, however implementing this trait -/// for [u8] will auto-impl [ReadWrite]<([i8], [u8], [i16], [u16] ... [i128], [u128])> -pub trait Grab { +/// This is similar to the [SliceIndex] method `.get(...)`, however implementing this +/// trait will auto-impl [AutoCast]<([i8], [u8], [i16], [u16] ... [i128], [u128])> +pub trait Grab { /// Gets the slice of Self at [SliceIndex] I - fn grab(&self, index: I) -> Option<&>::Output> + fn grab(&self, index: I) -> Option<&>::Output> where - I: SliceIndex<[T]>; + I: SliceIndex<[u8]>; /// Gets a mutable slice of Self at [SliceIndex] I - fn grab_mut(&mut self, index: I) -> Option<&mut >::Output> + fn grab_mut(&mut self, index: I) -> Option<&mut >::Output> where - I: SliceIndex<[T]>; + I: SliceIndex<[u8]>; } /// Read or Write a T at address `addr` @@ -36,6 +36,10 @@ pub trait AutoCast: FallibleAutoCast { self.read_fallible(addr).unwrap_or_else(|e| panic!("{e:?}")) } /// Write a T to address `addr` + /// + /// # Will Panic + /// + /// This will panic on error. For a non-panicking implementation, do it yourself. fn write(&mut self, addr: impl Into, data: T) { self.write_fallible(addr, data) .unwrap_or_else(|e| panic!("{e:?}")); @@ -43,7 +47,7 @@ pub trait AutoCast: FallibleAutoCast { } /// Read a T from address `addr`, and return the value as a [Result] -pub trait FallibleAutoCast: Grab { +pub trait FallibleAutoCast: Grab { /// The [Err] type type Error: Debug; /// Read a T from address `addr`, returning the value as a [Result] @@ -59,8 +63,8 @@ pub trait FallibleAutoCast: Grab { /// - `Self::to_be_bytes` macro_rules! impl_rw {($($t:ty) ,* $(,)?) =>{ $( - #[doc = concat!("Read or Write [", stringify!($t), "] at address `addr`")] - impl + FallibleAutoCast<$t>> AutoCast<$t> for T { + #[doc = concat!("Read or Write [", stringify!($t), "] at address `addr`, *discarding errors*.\n\nThis will never panic.")] + impl> AutoCast<$t> for T { #[inline(always)] fn read(&self, addr: impl Into) -> $t { self.read_fallible(addr).ok().unwrap_or_default() @@ -70,7 +74,7 @@ macro_rules! impl_rw {($($t:ty) ,* $(,)?) =>{ self.write_fallible(addr, data).ok(); } } - impl> FallibleAutoCast<$t> for T { + impl FallibleAutoCast<$t> for T { type Error = $crate::error::Error; #[inline(always)] fn read_fallible(&self, addr: impl Into) -> $crate::error::Result<$t> { diff --git a/tests/chip8_test_suite.rs b/tests/chip8_test_suite.rs index d79e6b0..40e7f69 100644 --- a/tests/chip8_test_suite.rs +++ b/tests/chip8_test_suite.rs @@ -6,20 +6,14 @@ pub use chirp::*; -fn setup_environment() -> (CPU, Bus) { +fn setup_environment() -> (CPU, Screen) { let mut cpu = CPU::default(); cpu.flags = Flags { debug: true, pause: false, ..Default::default() }; - ( - cpu, - bus! { - // Create a screen, and fill it with garbage - Screen [0x000..0x100] = b"jsuadhgufywegrwsdyfogbbg4owgbrt", - }, - ) + (cpu, Screen::default()) } struct SuiteTest { @@ -28,23 +22,21 @@ struct SuiteTest { screen: &'static [u8], } -fn run_screentest(test: SuiteTest, mut cpu: CPU, mut bus: Bus) { +fn run_screentest(test: SuiteTest, mut cpu: CPU, mut screen: Screen) { // Set the test to run cpu.poke(0x1ffu16, test.test); cpu.load_program_bytes(test.data).unwrap(); // The test suite always initiates a keypause on test completion while !(cpu.flags.is_paused()) { - cpu.multistep(&mut bus, 10).unwrap(); + cpu.multistep(&mut screen, 10).unwrap(); if cpu.cycle() > 1000000 { panic!("test {} took too long", test.test) } } // Compare the screen to the reference screen buffer - bus.print_screen().unwrap(); - bus! {crate::cpu::bus::Region::Screen [0..256] = test.screen} - .print_screen() - .unwrap(); - assert_eq!(bus.get_region(Screen).unwrap(), test.screen); + screen.print_screen(); + Screen::from(test.screen).print_screen(); + assert_eq!(screen.grab(..).unwrap(), test.screen); } #[test] diff --git a/tests/integration.rs b/tests/integration.rs index e908060..5ba12df 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,52 +1,45 @@ //! Testing methods on Chirp's public API +use chirp::cpu::mem::Region::*; use chirp::*; use std::{collections::hash_map::DefaultHasher, hash::Hash}; -#[test] -#[allow(clippy::redundant_clone)] -fn chip8() { - let ch8 = Chip8::default(); // Default - let ch82 = ch8.clone(); // Clone - assert_eq!(ch8, ch82); // PartialEq - println!("{ch8:?}"); // Debug -} - mod bus { use super::*; mod region { + use super::*; // #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[test] fn copy() { - let r1 = Screen; + let r1 = Charset; let r2 = r1; assert_eq!(r1, r2); } #[test] #[allow(clippy::clone_on_copy)] fn clone() { - let r1 = Screen; + let r1 = Charset; let r2 = r1.clone(); assert_eq!(r1, r2); } #[test] fn display() { - println!("{Charset}{Program}{Screen}{Count}"); + println!("{Charset}{Program}{Count}"); } #[test] fn debug() { - println!("{Charset:?}{Program:?}{Screen:?}{Count:?}"); + println!("{Charset:?}{Program:?}{Count:?}"); } // lmao the things you do for test coverage #[test] fn eq() { - assert_eq!(Screen, Screen); + assert_eq!(Charset, Charset); assert_ne!(Charset, Program); } #[test] fn ord() { - assert_eq!(Screen, Charset.max(Program).max(Screen)); - assert!(Charset < Program && Program < Screen); + assert_eq!(Program, Charset.max(Program)); + assert!(Charset < Program); } #[test] fn hash() { @@ -59,7 +52,7 @@ mod bus { #[should_panic] fn bus_missing_region() { // Print the screen of a bus with no screen - bus! {}.print_screen().unwrap() + mem! {}.get_region(Charset).unwrap(); } } @@ -208,7 +201,7 @@ mod dis { #[test] fn error() { - let error = chirp::error::Error::MissingRegion { region: Screen }; + let error = chirp::error::Error::InvalidAddressRange { range: (..).into() }; // Print it with Display and Debug println!("{error} {error:?}"); }