diff --git a/src/cpu.rs b/src/cpu.rs index e282b7e..3668995 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -522,6 +522,11 @@ impl CPU { } /// Executes a single instruction + /// + /// Returns [Error::BreakpointHit] if a breakpoint was hit after the instruction executed. + /// 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::*; @@ -552,11 +557,8 @@ impl CPU { /// ], /// Screen [0x0f00..0x1000], /// }; - /// match cpu.tick(&mut bus) { - /// Err(Error::UnimplementedInstruction {word}) - /// => assert_eq!(0xffff, word), - /// _ => panic!(), - /// } + /// dbg!(cpu.tick(&mut bus)) + /// .expect_err("Should return Error::InvalidInstruction { 0xffff }"); /// ``` pub fn tick(&mut self, bus: &mut Bus) -> Result<&mut Self> { // Do nothing if paused @@ -571,7 +573,9 @@ impl CPU { // fetch opcode let opcode: &[u8; 2] = if let Some(slice) = bus.get(self.pc as usize..self.pc as usize + 2) { - slice.try_into()? + slice + .try_into() + .expect("`slice` should be exactly 2 bytes.") } else { return Err(Error::InvalidBusRange { range: self.pc as usize..self.pc as usize + 2, @@ -603,6 +607,10 @@ impl CPU { // process breakpoints if !self.breakpoints.is_empty() && self.breakpoints.contains(&self.pc) { self.flags.pause = true; + return Err(Error::BreakpointHit { + addr: self.pc, + next: bus.read(self.pc), + }); } Ok(self) } diff --git a/src/cpu/tests.rs b/src/cpu/tests.rs index 9b82462..deed2c1 100644 --- a/src/cpu/tests.rs +++ b/src/cpu/tests.rs @@ -939,6 +939,7 @@ mod io { output: &'static [u8], } + /// Verify the character sprite addresses with the data they should return #[rustfmt::skip] const TESTS: [SpriteTest; 16] = [ SpriteTest { input: 0x0, output: &[0xf0, 0x90, 0x90, 0x90, 0xf0] }, @@ -1121,15 +1122,51 @@ mod behavior { } } mod breakpoint { + use super::*; #[test] + #[cfg_attr(feature = "unstable", no_coverage)] fn hit_break() { let (mut cpu, mut bus) = setup_environment(); cpu.set_break(0x202); - cpu.multistep(&mut bus, 10) - .expect("Running valid instructions should always succeed"); + match cpu.multistep(&mut bus, 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` + } + other => unreachable!("{:?}", other), + } + assert!(cpu.flags.pause); + assert_eq!(0x202, cpu.pc); + } + #[test] + #[cfg_attr(feature = "unstable", no_coverage)] + fn hit_break_singlestep() { + let (mut cpu, mut bus) = setup_environment(); + cpu.set_break(0x202); + match cpu.singlestep(&mut bus) { + Err(crate::error::Error::BreakpointHit { addr, next }) => { + assert_eq!(0x202, addr); // current address is 202 + assert_eq!(0x1204, next); // next insn is `jmp 204` + } + other => unreachable!("{:?}", other), + } assert!(cpu.flags.pause); assert_eq!(0x202, cpu.pc); } } + + #[test] + #[cfg_attr(feature = "unstable", no_coverage)] + fn invalid_pc() { + let (mut cpu, mut bus) = setup_environment(); + // The bus extends from 0x0..0x1000 + cpu.pc = 0xfff; + match cpu.tick(&mut bus) { + Err(Error::InvalidBusRange { range }) => { + eprintln!("InvalidBusRange {{ {range:04x?} }}") + } + other => unreachable!("{other:04x?}"), + } + } } diff --git a/src/error.rs b/src/error.rs index ccdddcf..bc35717 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,6 +14,14 @@ pub type Result = std::result::Result; /// Error type for Chirp. #[derive(Debug, Error)] pub enum Error { + /// Represents a breakpoint being hit + #[error("Breakpoint hit: {addr:03x} ({next:04x})")] + BreakpointHit { + /// The address of the breakpoint + addr: u16, + /// The instruction after the breakpoint + next: u16, + }, /// Represents an unimplemented operation #[error("Unrecognized opcode: {word:04x}")] UnimplementedInstruction {