diff --git a/src/bus.rs b/src/bus.rs index 209d086..0094f10 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -2,6 +2,7 @@ // 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::Result; @@ -12,7 +13,7 @@ use std::{ slice::SliceIndex, }; -/// Creates a new bus, instantiating BusConnectable devices +/// Creates a new bus, growing the backing memory as needed /// # Examples /// ```rust /// # use chirp::prelude::*; @@ -24,7 +25,7 @@ use std::{ #[macro_export] macro_rules! bus { ($($name:path $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => { - $crate::bus::Bus::new() + $crate::bus::Bus::default() $( .add_region($name, $range) $( @@ -35,7 +36,7 @@ macro_rules! bus { } // Traits Read and Write are here purely to make implementing other things more bearable -/// Do whatever `Read` means to you +/// Read a T from address `addr` pub trait Read { /// Read a T from address `addr` fn read(&self, addr: impl Into) -> T; @@ -47,6 +48,7 @@ pub trait Write { fn write(&mut self, addr: impl Into, data: T); } +/// Represents a named region in memory #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Region { Charset, @@ -70,7 +72,7 @@ impl Display for Region { } } -/// Store memory in a series of named regions with ranges +/// Stores memory in a series of named regions with ranges #[derive(Clone, Debug, Default, PartialEq)] pub struct Bus { memory: Vec, @@ -78,29 +80,94 @@ pub struct Bus { } impl Bus { - /// Construct a new bus + // TODO: make bus::new() give a properly set up bus with a default memory map + /// Constructs a new bus + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# 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::prelude::*; + ///# fn main() -> Result<()> { + /// let bus = Bus::new() + /// .add_region(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::prelude::*; + ///# 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::prelude::*; + ///# 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 named range (Region) to the bus + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let bus = Bus::new().add_region(Program, 0..1234); + /// assert_eq!(1234, bus.len()); + ///# Ok(()) + ///# } + /// ``` pub fn add_region(mut self, name: Region, range: Range) -> Self { self.with_size(range.end); self.region.insert(name, range); self } + /// Loads data into a named region + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let bus = Bus::new() + /// .add_region(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 { use std::io::Write; if let Some(mut region) = self.get_region_mut(name) { @@ -108,34 +175,114 @@ impl Bus { } self } + /// Fills a named region with zeroes + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let bus = Bus::new() + /// .add_region(Program, 0..1234) + /// .clear_region(Program); + ///# // 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 bus memory + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# 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::prelude::*; + ///# 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::prelude::*; + ///# fn main() -> Result<()> { + /// let bus = Bus::new() + /// .add_region(Program, 0..10); + /// assert!([0;10].as_slice() == bus.get_region(Program).unwrap()); + ///# Ok(()) + ///# } + /// ``` pub fn get_region(&self, name: Region) -> Option<&[u8]> { self.get(self.region.get(&name)?.clone()) } - /// Gets a mutable slice to a named region of memory + + /// Gets a mutable slice of a named region of memory + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut bus = Bus::new() + /// .add_region(Program, 0..10); + /// assert!([0;10].as_slice() == bus.get_region_mut(Program).unwrap()); + ///# Ok(()) + ///# } + /// ``` pub fn get_region_mut(&mut self, name: Region) -> Option<&mut [u8]> { self.get_mut(self.region.get(&name)?.clone()) } + + /// Prints the region of memory called `Screen` at 1bpp using box characters + /// # Examples + /// + /// [Bus::print_screen] will print the screen + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let bus = Bus::new() + /// .add_region(Screen, 0x000..0x100); + /// bus.print_screen()?; + ///# Ok(()) + ///# } + /// ``` + /// If there is no Screen region, it will return Err(Error::MissingRegion) + /// ```rust,should_panic + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut bus = Bus::new() + /// .add_region(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) { @@ -161,6 +308,7 @@ 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) @@ -168,6 +316,7 @@ impl Read for Bus { } 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) { @@ -179,6 +328,7 @@ impl Read for Bus { } 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) { @@ -188,6 +338,7 @@ impl Write for Bus { } 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) { diff --git a/src/cpu.rs b/src/cpu.rs index 5efa4d2..0b14fa1 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -29,7 +29,7 @@ pub struct Quirks { pub draw_wait: bool, /// DMA instructions `Fx55`/`Fx65` should change I to I + x + 1 pub dma_inc: bool, - /// Indexed jump instructions should go to ADR + v[N] where N is high nibble of adr + /// Indexed jump instructions should go to ADR + v`N` where `N` is high nibble of adr pub stupid_jumps: bool, } @@ -73,19 +73,16 @@ pub struct ControlFlags { } impl ControlFlags { + /// Toggles debug mode pub fn debug(&mut self) { self.debug = !self.debug } + /// Toggles pause pub fn pause(&mut self) { self.pause = !self.pause } } -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Keys { - keys: [bool; 16], -} - /// Represents the internal state of the CPU interpreter #[derive(Clone, Debug, PartialEq)] pub struct CPU { @@ -111,11 +108,21 @@ pub struct CPU { // public interface impl CPU { + // TODO: implement From<&bus> for CPU /// Constructs a new CPU, taking all configurable parameters /// # Examples /// ```rust /// # use chirp::prelude::*; - /// let mut cpu = CPU::new(0xf00, 0x50, 0x200, 0xefe, Disassemble::default(), vec![], ControlFlags::default()); + /// let cpu = CPU::new( + /// 0xf00, // screen location + /// 0x50, // font location + /// 0x200, // start of program + /// 0xefe, // top of stack + /// Disassemble::default(), + /// vec![], // Breakpoints + /// ControlFlags::default() + /// ); + /// dbg!(cpu); /// ``` pub fn new( screen: Adr, @@ -138,13 +145,36 @@ impl CPU { } } - /// Press a key + /// Presses a key + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut cpu = CPU::default(); + /// cpu.press(0x7); + /// cpu.press(0xF); + ///# Ok(()) + ///# } + /// ``` pub fn press(&mut self, key: usize) { if (0..16).contains(&key) { self.keys[key] = true; } } - /// Release a key + + /// Releases a key + /// + /// If keypause is enabled, this disables keypause and records the last released key. + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut cpu = CPU::default(); + /// cpu.press(0x7); + /// cpu.release(0x7); + ///# Ok(()) + ///# } + /// ``` pub fn release(&mut self, key: usize) { if (0..16).contains(&key) { self.keys[key] = false; @@ -155,7 +185,7 @@ impl CPU { } } - /// Set a general purpose register in the CPU + /// Sets a general purpose register in the CPU /// # Examples /// ```rust /// # use chirp::prelude::*; @@ -171,16 +201,62 @@ impl CPU { } } - /// Get the program counter + /// Gets the program counter + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut cpu = CPU::default(); + /// assert_eq!(0x200, cpu.pc()); + ///# Ok(()) + ///# } + /// ``` pub fn pc(&self) -> Adr { self.pc } + /// Gets the number of cycles the CPU has executed + /// + /// If cpu.flags.monotonic is Some, the cycle count will be + /// updated even when the CPU is in drawpause or keypause + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut cpu = CPU::default(); + /// assert_eq!(0x0, cpu.cycle()); + ///# Ok(()) + ///# } + /// ``` pub fn cycle(&self) -> usize { self.cycle } - /// Soft resets the CPU, releasing keypause and reinitializing the program counter to 0x200 + /// Soft resets the CPU, releasing keypause and + /// reinitializing the program counter to 0x200 + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut cpu = CPU::new( + /// 0xf00, + /// 0x50, + /// 0x340, + /// 0xefe, + /// Disassemble::default(), + /// vec![], + /// ControlFlags::default() + /// ); + /// cpu.flags.keypause = true; + /// cpu.flags.vbi_wait = true; + /// assert_eq!(0x340, cpu.pc()); + /// cpu.soft_reset(); + /// assert_eq!(0x200, cpu.pc()); + /// assert_eq!(false, cpu.flags.keypause); + /// assert_eq!(false, cpu.flags.vbi_wait); + ///# Ok(()) + ///# } + /// ``` pub fn soft_reset(&mut self) { self.pc = 0x200; self.flags.keypause = false; @@ -188,6 +264,7 @@ impl CPU { } /// Set a breakpoint + // TODO: Unit test this pub fn set_break(&mut self, point: Adr) -> &mut Self { if !self.breakpoints.contains(&point) { self.breakpoints.push(point) @@ -196,6 +273,7 @@ impl CPU { } /// Unset a breakpoint + // TODO: Unit test this pub fn unset_break(&mut self, point: Adr) -> &mut Self { fn linear_find(needle: Adr, haystack: &Vec) -> Option { for (i, v) in haystack.iter().enumerate() { @@ -211,8 +289,28 @@ impl CPU { self } - /// Unpauses the emulator for a single tick + /// Unpauses the emulator for a single tick, + /// even if cpu.flags.pause is set. + /// /// NOTE: does not synchronize with delay timers + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut cpu = CPU::default(); + /// let mut bus = bus!{ + /// Program [0x0200..0x0f00] = &[ + /// 0x00, 0xe0, // cls + /// 0x22, 0x02, // jump 0x202 (pc) + /// ], + /// Screen [0x0f00..0x1000], + /// }; + /// cpu.singlestep(&mut bus); + /// assert_eq!(0x202, cpu.pc()); + /// assert_eq!(1, cpu.cycle()); + ///# Ok(()) + ///# } + /// ``` pub fn singlestep(&mut self, bus: &mut Bus) -> &mut Self { self.flags.pause = false; self.tick(bus); @@ -222,7 +320,26 @@ impl CPU { } /// Unpauses the emulator for `steps` ticks + /// /// Ticks the timers every `rate` ticks + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut cpu = CPU::default(); + /// let mut bus = bus!{ + /// Program [0x0200..0x0f00] = &[ + /// 0x00, 0xe0, // cls + /// 0x22, 0x02, // jump 0x202 (pc) + /// ], + /// Screen [0x0f00..0x1000], + /// }; + /// cpu.multistep(&mut bus, 0x20); + /// assert_eq!(0x202, cpu.pc()); + /// assert_eq!(0x20, cpu.cycle()); + ///# Ok(()) + ///# } + /// ``` pub fn multistep(&mut self, bus: &mut Bus, steps: usize) -> &mut Self { for _ in 0..steps { self.tick(bus); @@ -231,10 +348,15 @@ impl CPU { self } - /// Signals the start of a vertical blank + /// Simulates vertical blanking /// - /// - Ticks the sound and delay timers + /// If monotonic timing is `enabled`: + /// - Ticks the sound and delay timers according to CPU cycle count /// - Disables framepause + /// If monotonic timing is `disabled`: + /// - Subtracts the elapsed time in fractions of a frame + /// from st/dt + /// - Disables framepause if the duration exceeds that of a frame pub fn vertical_blank(&mut self) -> &mut Self { if self.flags.pause { return self; @@ -263,7 +385,44 @@ impl CPU { self } - /// Runs a single instruction + /// Executes a single instruction + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut cpu = CPU::default(); + /// let mut bus = bus!{ + /// Program [0x0200..0x0f00] = &[ + /// 0x00, 0xe0, // cls + /// 0x22, 0x02, // jump 0x202 (pc) + /// ], + /// Screen [0x0f00..0x1000], + /// }; + /// cpu.tick(&mut bus); + /// assert_eq!(0x202, cpu.pc()); + /// assert_eq!(1, cpu.cycle()); + ///# Ok(()) + ///# } + /// ``` + /// # Panics + /// Will panic if an invalid instruction is executed + /// ```rust,should_panic + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut cpu = CPU::default(); + ///# cpu.flags.debug = true; // enable live disassembly + ///# cpu.flags.monotonic = Some(8); // enable monotonic/test timing + /// let mut bus = bus!{ + /// Program [0x0200..0x0f00] = &[ + /// 0xff, 0xff, // invalid! + /// 0x22, 0x02, // jump 0x202 (pc) + /// ], + /// Screen [0x0f00..0x1000], + /// }; + /// cpu.multistep(&mut bus, 0x10); // panics! + ///# Ok(()) + ///# } + /// ``` pub fn tick(&mut self, bus: &mut Bus) -> &mut Self { // Do nothing if paused if self.flags.pause || self.flags.vbi_wait || self.flags.keypause { @@ -418,6 +577,25 @@ impl CPU { self } + /// Dumps the current state of all CPU registers, and the cycle count + /// # Examples + /// ```rust + ///# use chirp::prelude::*; + ///# fn main() -> Result<()> { + /// let mut cpu = CPU::default(); + /// cpu.dump(); + ///# Ok(()) + ///# } + /// ``` + /// outputs + /// ```text + /// PC: 0200, SP: 0efe, I: 0000 + /// v0: 00 v1: 00 v2: 00 v3: 00 + /// v4: 00 v5: 00 v6: 00 v7: 00 + /// v8: 00 v9: 00 vA: 00 vB: 00 + /// vC: 00 vD: 00 vE: 00 vF: 00 + /// DLY: 0, SND: 0, CYC: 0 + /// ``` pub fn dump(&self) { //let dumpstyle = owo_colors::Style::new().bright_black(); std::println!( @@ -454,6 +632,13 @@ impl Default for CPU { /// | font |`0x0050` | Location of font memory. /// | pc |`0x0200` | Start location. Generally 0x200 or 0x600. /// | sp |`0x0efe` | Initial top of stack. + /// + /// + /// # Examples + /// ```rust + /// use chirp::prelude::*; + /// let mut cpu = CPU::default(); + /// ``` fn default() -> Self { CPU { screen: 0xf00,