bus.rs/cpu.rs: Improve doctest coverage
This commit is contained in:
		
							
								
								
									
										163
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										163
									
								
								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<T> { | ||||
|     /// Read a T from address `addr` | ||||
|     fn read(&self, addr: impl Into<usize>) -> T; | ||||
| @@ -47,6 +48,7 @@ pub trait Write<T> { | ||||
|     fn write(&mut self, addr: impl Into<usize>, 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<u8>, | ||||
| @@ -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<usize>) -> 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<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::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<I>(&mut self, index: I) -> Option<&mut <I as SliceIndex<[u8]>>::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<u8> for Bus { | ||||
|     /// Read a u8 from address `addr` | ||||
|     fn read(&self, addr: impl Into<usize>) -> u8 { | ||||
|         let addr: usize = addr.into(); | ||||
|         *self.memory.get(addr).unwrap_or(&0xc5) | ||||
| @@ -168,6 +316,7 @@ impl Read<u8> for Bus { | ||||
| } | ||||
|  | ||||
| impl Read<u16> for Bus { | ||||
|     /// Read a u16 from address `addr` | ||||
|     fn read(&self, addr: impl Into<usize>) -> u16 { | ||||
|         let addr: usize = addr.into(); | ||||
|         if let Some(bytes) = self.memory.get(addr..addr + 2) { | ||||
| @@ -179,6 +328,7 @@ impl Read<u16> for Bus { | ||||
| } | ||||
|  | ||||
| impl Write<u8> for Bus { | ||||
|     /// Write a u8 to address `addr` | ||||
|     fn write(&mut self, addr: impl Into<usize>, data: u8) { | ||||
|         let addr: usize = addr.into(); | ||||
|         if let Some(byte) = self.get_mut(addr) { | ||||
| @@ -188,6 +338,7 @@ impl Write<u8> for Bus { | ||||
| } | ||||
|  | ||||
| impl Write<u16> for Bus { | ||||
|     /// Write a u16 to address `addr` | ||||
|     fn write(&mut self, addr: impl Into<usize>, data: u16) { | ||||
|         let addr: usize = addr.into(); | ||||
|         if let Some(slice) = self.get_mut(addr..addr + 2) { | ||||
|   | ||||
							
								
								
									
										217
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										217
									
								
								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<Adr>) -> Option<usize> { | ||||
|             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, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user