cpu.rs: Make quirks individually configurable
This commit is contained in:
		| @@ -1,54 +0,0 @@ | |||||||
| //! Connects a BusConnectible to the Bus |  | ||||||
|  |  | ||||||
| use super::BusConnectible; |  | ||||||
| use std::{ |  | ||||||
|     fmt::{Display, Formatter, Result}, |  | ||||||
|     ops::Range, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /// BusDevice performs address translation for BusConnectibles. |  | ||||||
| /// It is an implementation detail of Bus.connect() |  | ||||||
| #[derive(Debug)] |  | ||||||
| pub struct BusDevice { |  | ||||||
|     pub name: String, |  | ||||||
|     pub range: Range<u16>, |  | ||||||
|     device: Box<dyn BusConnectible>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl BusDevice { |  | ||||||
|     pub fn new(name: &str, range: Range<u16>, device: Box<dyn BusConnectible>) -> Self { |  | ||||||
|         BusDevice { |  | ||||||
|             name: name.to_string(), |  | ||||||
|             range, |  | ||||||
|             device, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     fn translate_address(&self, addr: u16) -> Option<u16> { |  | ||||||
|         let addr = addr.wrapping_sub(self.range.start); |  | ||||||
|         if addr < self.range.end { |  | ||||||
|             Some(addr) |  | ||||||
|         } else { |  | ||||||
|             None |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl BusConnectible for BusDevice { |  | ||||||
|     fn read_at(&self, addr: u16) -> Option<u8> { |  | ||||||
|         self.device.read_at(self.translate_address(addr)?) |  | ||||||
|     } |  | ||||||
|     fn write_to(&mut self, addr: u16, data: u8) { |  | ||||||
|         if let Some(addr) = self.translate_address(addr) { |  | ||||||
|             self.device.write_to(addr, data); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     fn get_mut(&mut self, addr: u16) -> Option<&mut u8> { |  | ||||||
|         return self.device.get_mut(addr); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Display for BusDevice { |  | ||||||
|     fn fmt(&self, f: &mut Formatter<'_>) -> Result { |  | ||||||
|         writeln!(f, "{} [{:04x?}]:\n{}", self.name, self.range, self.device) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,52 +0,0 @@ | |||||||
| //! Iterators for working with Busses |  | ||||||
|  |  | ||||||
| use super::{Bus, Read}; |  | ||||||
| use std::ops::Range; |  | ||||||
|  |  | ||||||
| pub trait IterMut<'a> { |  | ||||||
|     type Item; |  | ||||||
|     fn next(&'a mut self) -> Option<&'a mut Self::Item>; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub trait IntoIterMut<'a> { |  | ||||||
|     type Item; |  | ||||||
|  |  | ||||||
|     type IntoIter; |  | ||||||
|  |  | ||||||
|     fn into_iter(self) -> Self::IntoIter; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug)] |  | ||||||
| pub struct BusIterator<'a> { |  | ||||||
|     range: Range<u16>, |  | ||||||
|     addr: u16, |  | ||||||
|     bus: &'a Bus, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<'a> BusIterator<'a> { |  | ||||||
|     /// Creates a new BusIterator with a specified range |  | ||||||
|     pub fn new(range: Range<u16>, bus: &'a Bus) -> BusIterator<'a> { |  | ||||||
|         BusIterator { |  | ||||||
|             addr: range.start, |  | ||||||
|             range, |  | ||||||
|             bus, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     pub fn range(mut self, range: Range<u16>) -> Self { |  | ||||||
|         self.range = range; |  | ||||||
|         self |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<'a> Iterator for BusIterator<'a> { |  | ||||||
|     type Item = u8; |  | ||||||
|  |  | ||||||
|     fn next(&mut self) -> Option<Self::Item> { |  | ||||||
|         let mut res = None; |  | ||||||
|         if self.range.contains(&self.addr) { |  | ||||||
|             res = Some(self.bus.read(self.addr)); |  | ||||||
|             self.addr += 1; |  | ||||||
|         } |  | ||||||
|         res |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										282
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										282
									
								
								src/cpu.rs
									
									
									
									
									
								
							| @@ -15,12 +15,58 @@ type Reg = usize; | |||||||
| type Adr = u16; | type Adr = u16; | ||||||
| type Nib = u8; | type Nib = u8; | ||||||
|  |  | ||||||
|  | /// Controls the authenticity behavior of the CPU on a granular level. | ||||||
|  | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||||||
|  | pub struct Quirks { | ||||||
|  |     /// Binary ops in `8xy`(`1`, `2`, `3`) should set vF to 0 | ||||||
|  |     pub bin_ops: bool, | ||||||
|  |     /// Shift ops in `8xy`(`6`, `E`) should source from vY instead of vX | ||||||
|  |     pub shift: bool, | ||||||
|  |     /// Draw operations should pause execution until the next timer tick | ||||||
|  |     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 | ||||||
|  |     pub stupid_jumps: bool, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<bool> for Quirks { | ||||||
|  |     fn from(value: bool) -> Self { | ||||||
|  |         if value { | ||||||
|  |             Quirks { | ||||||
|  |                 bin_ops: true, | ||||||
|  |                 shift: true, | ||||||
|  |                 draw_wait: true, | ||||||
|  |                 dma_inc: true, | ||||||
|  |                 stupid_jumps: false, | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             Quirks { | ||||||
|  |                 bin_ops: false, | ||||||
|  |                 shift: false, | ||||||
|  |                 draw_wait: false, | ||||||
|  |                 dma_inc: false, | ||||||
|  |                 stupid_jumps: false, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Default for Quirks { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self::from(false) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] | #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||||||
| pub struct ControlFlags { | pub struct ControlFlags { | ||||||
|     pub debug: bool, |     pub debug: bool, | ||||||
|     pub pause: bool, |     pub pause: bool, | ||||||
|     pub keypause: bool, |     pub keypause: bool, | ||||||
|     pub authentic: bool, |     pub vbi_wait: bool, | ||||||
|  |     pub lastkey: Option<usize>, | ||||||
|  |     pub quirks: Quirks, | ||||||
|  |     pub monotonic: Option<usize>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl ControlFlags { | impl ControlFlags { | ||||||
| @@ -37,8 +83,10 @@ pub struct Keys { | |||||||
|     keys: [bool; 16], |     keys: [bool; 16], | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// Represents the internal state of the CPU interpreter | ||||||
| #[derive(Clone, Debug, PartialEq)] | #[derive(Clone, Debug, PartialEq)] | ||||||
| pub struct CPU { | pub struct CPU { | ||||||
|  |     pub flags: ControlFlags, | ||||||
|     // memory map info |     // memory map info | ||||||
|     screen: Adr, |     screen: Adr, | ||||||
|     font: Adr, |     font: Adr, | ||||||
| @@ -47,12 +95,12 @@ pub struct CPU { | |||||||
|     sp: Adr, |     sp: Adr, | ||||||
|     i: Adr, |     i: Adr, | ||||||
|     v: [u8; 16], |     v: [u8; 16], | ||||||
|     delay: u8, |     delay: f64, | ||||||
|     sound: u8, |     sound: f64, | ||||||
|     // I/O |     // I/O | ||||||
|     pub keys: [bool; 16], |     keys: [bool; 16], | ||||||
|     pub flags: ControlFlags, |  | ||||||
|     // Execution data |     // Execution data | ||||||
|  |     timer: Instant, | ||||||
|     cycle: usize, |     cycle: usize, | ||||||
|     breakpoints: Vec<Adr>, |     breakpoints: Vec<Adr>, | ||||||
|     disassembler: Disassemble, |     disassembler: Disassemble, | ||||||
| @@ -60,36 +108,6 @@ pub struct CPU { | |||||||
|  |  | ||||||
| // public interface | // public interface | ||||||
| impl CPU { | impl CPU { | ||||||
|     /// Press keys (where `keys` is a bitmap of the keys [F-0]) |  | ||||||
|     pub fn press(&mut self, key: usize) { |  | ||||||
|         if (0..16).contains(&key) { |  | ||||||
|             self.keys[key] = true; |  | ||||||
|             self.flags.keypause = false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     /// Release all keys |  | ||||||
|     pub fn release(&mut self) { |  | ||||||
|         for key in &mut self.keys { |  | ||||||
|             *key = false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Set a general purpose register in the CPU |  | ||||||
|     /// # Examples |  | ||||||
|     /// ```rust |  | ||||||
|     /// # use chirp::prelude::*; |  | ||||||
|     /// // Create a new CPU, and set v4 to 0x41 |  | ||||||
|     /// let mut cpu = CPU::default(); |  | ||||||
|     /// cpu.set_gpr(0x4, 0x41); |  | ||||||
|     /// // Dump the CPU registers |  | ||||||
|     /// cpu.dump(); |  | ||||||
|     /// ``` |  | ||||||
|     pub fn set_gpr(&mut self, gpr: Reg, value: u8) { |  | ||||||
|         if let Some(gpr) = self.v.get_mut(gpr) { |  | ||||||
|             *gpr = value; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Constructs a new CPU, taking all configurable parameters |     /// Constructs a new CPU, taking all configurable parameters | ||||||
|     /// # Examples |     /// # Examples | ||||||
|     /// ```rust |     /// ```rust | ||||||
| @@ -111,14 +129,42 @@ impl CPU { | |||||||
|             font, |             font, | ||||||
|             pc, |             pc, | ||||||
|             sp, |             sp, | ||||||
|             i: 0, |  | ||||||
|             v: [0; 16], |  | ||||||
|             delay: 0, |  | ||||||
|             sound: 0, |  | ||||||
|             cycle: 0, |  | ||||||
|             keys: [false; 16], |  | ||||||
|             breakpoints, |             breakpoints, | ||||||
|             flags, |             flags, | ||||||
|  |             ..Default::default() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Press a key | ||||||
|  |     pub fn press(&mut self, key: usize) { | ||||||
|  |         if (0..16).contains(&key) { | ||||||
|  |             self.keys[key] = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// Release a key | ||||||
|  |     pub fn release(&mut self, key: usize) { | ||||||
|  |         if (0..16).contains(&key) { | ||||||
|  |             self.keys[key] = false; | ||||||
|  |             if self.flags.keypause { | ||||||
|  |                 self.flags.lastkey = Some(key); | ||||||
|  |             } | ||||||
|  |             self.flags.keypause = false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Set a general purpose register in the CPU | ||||||
|  |     /// # Examples | ||||||
|  |     /// ```rust | ||||||
|  |     /// # use chirp::prelude::*; | ||||||
|  |     /// // Create a new CPU, and set v4 to 0x41 | ||||||
|  |     /// let mut cpu = CPU::default(); | ||||||
|  |     /// cpu.set_gpr(0x4, 0x41); | ||||||
|  |     /// // Dump the CPU registers | ||||||
|  |     /// cpu.dump(); | ||||||
|  |     /// ``` | ||||||
|  |     pub fn set_gpr(&mut self, gpr: Reg, value: u8) { | ||||||
|  |         if let Some(gpr) = self.v.get_mut(gpr) { | ||||||
|  |             *gpr = value; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -127,10 +173,15 @@ impl CPU { | |||||||
|         self.pc |         self.pc | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     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 | ||||||
|     pub fn soft_reset(&mut self) { |     pub fn soft_reset(&mut self) { | ||||||
|         self.pc = 0x200; |         self.pc = 0x200; | ||||||
|         self.flags.keypause = false; |         self.flags.keypause = false; | ||||||
|  |         self.flags.vbi_wait = false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Set a breakpoint |     /// Set a breakpoint | ||||||
| @@ -162,38 +213,64 @@ impl CPU { | |||||||
|     pub fn singlestep(&mut self, bus: &mut Bus) -> &mut Self { |     pub fn singlestep(&mut self, bus: &mut Bus) -> &mut Self { | ||||||
|         self.flags.pause = false; |         self.flags.pause = false; | ||||||
|         self.tick(bus); |         self.tick(bus); | ||||||
|  |         self.flags.vbi_wait = false; | ||||||
|         self.flags.pause = true; |         self.flags.pause = true; | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Unpauses the emulator for `steps` ticks |     /// Unpauses the emulator for `steps` ticks | ||||||
|     /// Ticks the timers every `rate` ticks |     /// Ticks the timers every `rate` ticks | ||||||
|     pub fn multistep(&mut self, bus: &mut Bus, steps: usize, rate: usize) -> &mut Self { |     pub fn multistep(&mut self, bus: &mut Bus, steps: usize) -> &mut Self { | ||||||
|         for _ in 0..steps { |         for _ in 0..steps { | ||||||
|             self.tick(bus); |             self.tick(bus); | ||||||
|             if rate != 0 && self.cycle % rate == rate - 1 { |             self.vertical_blank(); | ||||||
|                 self.tick_timer(); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Ticks the delay and sound timers |     /// Signals the start of a vertical blank | ||||||
|     pub fn tick_timer(&mut self) -> &mut Self { |     /// | ||||||
|  |     /// - Ticks the sound and delay timers | ||||||
|  |     /// - Disables framepause | ||||||
|  |     pub fn vertical_blank(&mut self) -> &mut Self { | ||||||
|         if self.flags.pause { |         if self.flags.pause { | ||||||
|             return self; |             return self; | ||||||
|         } |         } | ||||||
|         self.delay = self.delay.saturating_sub(1); |         // Use a monotonic counter when testing | ||||||
|         self.sound = self.sound.saturating_sub(1); |         if let Some(speed) = self.flags.monotonic { | ||||||
|  |             if self.flags.vbi_wait { | ||||||
|  |                 self.flags.vbi_wait = !(self.cycle % speed == 0); | ||||||
|  |             } | ||||||
|  |             self.delay -= 1.0 / speed as f64; | ||||||
|  |             self.sound -= 1.0 / speed as f64; | ||||||
|  |             return self; | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         let time = self.timer.elapsed().as_secs_f64() * 60.0; | ||||||
|  |         self.timer = Instant::now(); | ||||||
|  |         if time > 1.0 { | ||||||
|  |             self.flags.vbi_wait = false; | ||||||
|  |         } | ||||||
|  |         if self.delay > 0.0 { | ||||||
|  |             self.delay -= time; | ||||||
|  |         } | ||||||
|  |         if self.sound > 0.0 { | ||||||
|  |             self.sound -= time; | ||||||
|  |         } | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Runs a single instruction |     /// Runs a single instruction | ||||||
|     pub fn tick(&mut self, bus: &mut Bus) -> &mut Self { |     pub fn tick(&mut self, bus: &mut Bus) -> &mut Self { | ||||||
|         // Do nothing if paused |         // Do nothing if paused | ||||||
|         if self.flags.pause || self.flags.keypause { |         if self.flags.pause || self.flags.vbi_wait || self.flags.keypause { | ||||||
|  |             // always tick in test mode | ||||||
|  |             if self.flags.monotonic.is_some() { | ||||||
|  |                 self.cycle += 1; | ||||||
|  |             } | ||||||
|             return self; |             return self; | ||||||
|         } |         } | ||||||
|         let time = Instant::now(); |         self.cycle += 1; | ||||||
|         // fetch opcode |         // fetch opcode | ||||||
|         let opcode: u16 = bus.read(self.pc); |         let opcode: u16 = bus.read(self.pc); | ||||||
|         let pc = self.pc; |         let pc = self.pc; | ||||||
| @@ -318,9 +395,9 @@ impl CPU { | |||||||
|                 0x65 => self.load_dma(x, bus), |                 0x65 => self.load_dma(x, bus), | ||||||
|                 _ => self.unimplemented(opcode), |                 _ => self.unimplemented(opcode), | ||||||
|             }, |             }, | ||||||
|             _ => unimplemented!("Extracted nibble from byte, got >nibble?"), |             _ => unreachable!("Extracted nibble from byte, got >nibble?"), | ||||||
|         } |         } | ||||||
|         let elapsed = time.elapsed(); |         let elapsed = self.timer.elapsed(); | ||||||
|         // Print opcode disassembly: |         // Print opcode disassembly: | ||||||
|         if self.flags.debug { |         if self.flags.debug { | ||||||
|             std::println!( |             std::println!( | ||||||
| @@ -331,7 +408,6 @@ impl CPU { | |||||||
|                 elapsed.dimmed() |                 elapsed.dimmed() | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
|         self.cycle += 1; |  | ||||||
|         // process breakpoints |         // process breakpoints | ||||||
|         if self.breakpoints.contains(&self.pc) { |         if self.breakpoints.contains(&self.pc) { | ||||||
|             self.flags.pause = true; |             self.flags.pause = true; | ||||||
| @@ -346,8 +422,7 @@ impl CPU { | |||||||
|             self.pc, |             self.pc, | ||||||
|             self.sp, |             self.sp, | ||||||
|             self.i, |             self.i, | ||||||
|             self |             self.v | ||||||
|                 .v |  | ||||||
|                 .into_iter() |                 .into_iter() | ||||||
|                 .enumerate() |                 .enumerate() | ||||||
|                 .map(|(i, gpr)| { |                 .map(|(i, gpr)| { | ||||||
| @@ -384,21 +459,22 @@ impl Default for CPU { | |||||||
|             sp: 0xefe, |             sp: 0xefe, | ||||||
|             i: 0, |             i: 0, | ||||||
|             v: [0; 16], |             v: [0; 16], | ||||||
|             delay: 0, |             delay: 0.0, | ||||||
|             sound: 0, |             sound: 0.0, | ||||||
|             cycle: 0, |             cycle: 0, | ||||||
|             keys: [false; 16], |             keys: [false; 16], | ||||||
|             flags: ControlFlags { |             flags: ControlFlags { | ||||||
|                 debug: true, |                 debug: true, | ||||||
|                 ..Default::default() |                 ..Default::default() | ||||||
|             }, |             }, | ||||||
|  |             timer: Instant::now(), | ||||||
|             breakpoints: vec![], |             breakpoints: vec![], | ||||||
|             disassembler: Disassemble::default(), |             disassembler: Disassemble::default(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| // Below this point, comments may be duplicated per impl' block,  | // Below this point, comments may be duplicated per impl' block, | ||||||
| // since some opcodes handle multiple instructions. | // since some opcodes handle multiple instructions. | ||||||
|  |  | ||||||
| // | 0aaa | Issues a "System call" (ML routine) | // | 0aaa | Issues a "System call" (ML routine) | ||||||
| @@ -529,46 +605,40 @@ impl CPU { | |||||||
| // | 8xyE | X = X << 1                         | | // | 8xyE | X = X << 1                         | | ||||||
| impl CPU { | impl CPU { | ||||||
|     /// 8xy0: Loads the value of y into x |     /// 8xy0: Loads the value of y into x | ||||||
|     ///  |  | ||||||
|     /// # Authenticity |  | ||||||
|     /// The original chip-8 interpreter will clobber vF for any 8-series instruction |  | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn load(&mut self, x: Reg, y: Reg) { |     fn load(&mut self, x: Reg, y: Reg) { | ||||||
|         self.v[x] = self.v[y]; |         self.v[x] = self.v[y]; | ||||||
|         if self.flags.authentic { |  | ||||||
|             self.v[0xf] = 0; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|     /// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX |     /// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX | ||||||
|     ///  |     /// | ||||||
|     /// # Authenticity |     /// # Quirk | ||||||
|     /// The original chip-8 interpreter will clobber vF for any 8-series instruction |     /// The original chip-8 interpreter will clobber vF for any 8-series instruction | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn or(&mut self, x: Reg, y: Reg) { |     fn or(&mut self, x: Reg, y: Reg) { | ||||||
|         self.v[x] |= self.v[y]; |         self.v[x] |= self.v[y]; | ||||||
|         if self.flags.authentic { |         if self.flags.quirks.bin_ops { | ||||||
|             self.v[0xf] = 0; |             self.v[0xf] = 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     /// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX |     /// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX | ||||||
|     ///  |     /// | ||||||
|     /// # Authenticity |     /// # Quirk | ||||||
|     /// The original chip-8 interpreter will clobber vF for any 8-series instruction |     /// The original chip-8 interpreter will clobber vF for any 8-series instruction | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn and(&mut self, x: Reg, y: Reg) { |     fn and(&mut self, x: Reg, y: Reg) { | ||||||
|         self.v[x] &= self.v[y]; |         self.v[x] &= self.v[y]; | ||||||
|         if self.flags.authentic { |         if self.flags.quirks.bin_ops { | ||||||
|             self.v[0xf] = 0; |             self.v[0xf] = 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     /// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX |     /// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX | ||||||
|     ///  |     /// | ||||||
|     /// # Authenticity |     /// # Quirk | ||||||
|     /// The original chip-8 interpreter will clobber vF for any 8-series instruction |     /// The original chip-8 interpreter will clobber vF for any 8-series instruction | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn xor(&mut self, x: Reg, y: Reg) { |     fn xor(&mut self, x: Reg, y: Reg) { | ||||||
|         self.v[x] ^= self.v[y]; |         self.v[x] ^= self.v[y]; | ||||||
|         if self.flags.authentic { |         if self.flags.quirks.bin_ops { | ||||||
|             self.v[0xf] = 0; |             self.v[0xf] = 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -587,13 +657,12 @@ impl CPU { | |||||||
|         self.v[0xf] = (!carry).into(); |         self.v[0xf] = (!carry).into(); | ||||||
|     } |     } | ||||||
|     /// 8xy6: Performs bitwise right shift of vX |     /// 8xy6: Performs bitwise right shift of vX | ||||||
|     ///  |     /// | ||||||
|     /// # Authenticity |     /// # Quirk | ||||||
|     /// On the original chip-8 interpreter, this would perform the operation on vY |     /// On the original chip-8 interpreter, this shifts vY and stores the result in vX | ||||||
|     /// and store the result in vX. This behavior was left out, for now. |  | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn shift_right(&mut self, x: Reg, y: Reg) { |     fn shift_right(&mut self, x: Reg, y: Reg) { | ||||||
|         let src: Reg = if self.flags.authentic {y} else {x}; |         let src: Reg = if self.flags.quirks.shift { y } else { x }; | ||||||
|         let shift_out = self.v[src] & 1; |         let shift_out = self.v[src] & 1; | ||||||
|         self.v[x] = self.v[src] >> 1; |         self.v[x] = self.v[src] >> 1; | ||||||
|         self.v[0xf] = shift_out; |         self.v[0xf] = shift_out; | ||||||
| @@ -606,13 +675,13 @@ impl CPU { | |||||||
|         self.v[0xf] = (!carry).into(); |         self.v[0xf] = (!carry).into(); | ||||||
|     } |     } | ||||||
|     /// 8X_E: Performs bitwise left shift of vX |     /// 8X_E: Performs bitwise left shift of vX | ||||||
|     ///  |     /// | ||||||
|     /// # Authenticity |     /// # Quirk | ||||||
|     /// On the original chip-8 interpreter, this would perform the operation on vY |     /// On the original chip-8 interpreter, this would perform the operation on vY | ||||||
|     /// and store the result in vX. This behavior was left out, for now. |     /// and store the result in vX. This behavior was left out, for now. | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn shift_left(&mut self, x: Reg, y: Reg) { |     fn shift_left(&mut self, x: Reg, y: Reg) { | ||||||
|         let src: Reg = if self.flags.authentic {y} else {x}; |         let src: Reg = if self.flags.quirks.shift { y } else { x }; | ||||||
|         let shift_out: u8 = self.v[src] >> 7; |         let shift_out: u8 = self.v[src] >> 7; | ||||||
|         self.v[x] = self.v[src] << 1; |         self.v[x] = self.v[src] << 1; | ||||||
|         self.v[0xf] = shift_out; |         self.v[0xf] = shift_out; | ||||||
| @@ -646,9 +715,17 @@ impl CPU { | |||||||
| // | Baaa | Jump to &adr + v0 | // | Baaa | Jump to &adr + v0 | ||||||
| impl CPU { | impl CPU { | ||||||
|     /// Badr: Jump to &adr + v0 |     /// Badr: Jump to &adr + v0 | ||||||
|  |     /// | ||||||
|  |     /// Quirk: | ||||||
|  |     /// On the Super-Chip, this does stupid shit | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn jump_indexed(&mut self, a: Adr) { |     fn jump_indexed(&mut self, a: Adr) { | ||||||
|         self.pc = a.wrapping_add(self.v[0] as Adr); |         let reg = if self.flags.quirks.stupid_jumps { | ||||||
|  |             a as usize >> 8 | ||||||
|  |         } else { | ||||||
|  |             0 | ||||||
|  |         }; | ||||||
|  |         self.pc = a.wrapping_add(self.v[reg] as Adr); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -664,9 +741,14 @@ impl CPU { | |||||||
| // | Dxyn | Draws n-byte sprite to the screen at coordinates (vX, vY) | // | Dxyn | Draws n-byte sprite to the screen at coordinates (vX, vY) | ||||||
| impl CPU { | impl CPU { | ||||||
|     /// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY) |     /// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY) | ||||||
|     #[inline] |     /// | ||||||
|  |     /// # Quirk | ||||||
|  |     /// On the original chip-8 interpreter, this will wait for a VBI | ||||||
|     fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { |     fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { | ||||||
|         let (x, y) = (self.v[x] as u16, self.v[y] as u16); |         let (x, y) = (self.v[x] as u16 % 64, self.v[y] as u16 % 32); | ||||||
|  |         if self.flags.quirks.draw_wait { | ||||||
|  |             self.flags.vbi_wait = true; | ||||||
|  |         } | ||||||
|         self.v[0xf] = 0; |         self.v[0xf] = 0; | ||||||
|         for byte in 0..n as u16 { |         for byte in 0..n as u16 { | ||||||
|             if y + byte > 32 { |             if y + byte > 32 { | ||||||
| @@ -736,19 +818,15 @@ impl CPU { | |||||||
|     /// ``` |     /// ``` | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn load_delay_timer(&mut self, x: Reg) { |     fn load_delay_timer(&mut self, x: Reg) { | ||||||
|         self.v[x] = self.delay; |         self.v[x] = self.delay as u8; | ||||||
|     } |     } | ||||||
|     /// Fx0A: Wait for key, then vX = K |     /// Fx0A: Wait for key, then vX = K | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn wait_for_key(&mut self, x: Reg) { |     fn wait_for_key(&mut self, x: Reg) { | ||||||
|         let mut pressed = false; |         if let Some(key) = self.flags.lastkey { | ||||||
|         for bit in 0..16 { |             self.v[x] = key as u8; | ||||||
|             if self.keys[bit] { |             self.flags.lastkey = None; | ||||||
|                 self.v[x] = bit as u8; |         } else { | ||||||
|                 pressed = true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if !pressed { |  | ||||||
|             self.pc = self.pc.wrapping_sub(2); |             self.pc = self.pc.wrapping_sub(2); | ||||||
|             self.flags.keypause = true; |             self.flags.keypause = true; | ||||||
|         } |         } | ||||||
| @@ -759,7 +837,7 @@ impl CPU { | |||||||
|     /// ``` |     /// ``` | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn store_delay_timer(&mut self, x: Reg) { |     fn store_delay_timer(&mut self, x: Reg) { | ||||||
|         self.delay = self.v[x]; |         self.delay = self.v[x] as f64; | ||||||
|     } |     } | ||||||
|     /// Fx18: Load vX into ST |     /// Fx18: Load vX into ST | ||||||
|     /// ```py |     /// ```py | ||||||
| @@ -767,7 +845,7 @@ impl CPU { | |||||||
|     /// ``` |     /// ``` | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn store_sound_timer(&mut self, x: Reg) { |     fn store_sound_timer(&mut self, x: Reg) { | ||||||
|         self.sound = self.v[x]; |         self.sound = self.v[x] as f64; | ||||||
|     } |     } | ||||||
|     /// Fx1e: Add vX to I, |     /// Fx1e: Add vX to I, | ||||||
|     /// ```py |     /// ```py | ||||||
| @@ -794,8 +872,8 @@ impl CPU { | |||||||
|         bus.write(self.i, x / 100 % 10); |         bus.write(self.i, x / 100 % 10); | ||||||
|     } |     } | ||||||
|     /// Fx55: DMA Stor from I to registers 0..X |     /// Fx55: DMA Stor from I to registers 0..X | ||||||
|     ///  |     /// | ||||||
|     /// # Authenticity |     /// # Quirk | ||||||
|     /// The original chip-8 interpreter uses I to directly index memory, |     /// The original chip-8 interpreter uses I to directly index memory, | ||||||
|     /// with the side effect of leaving I as I+X+1 after the transfer is done. |     /// with the side effect of leaving I as I+X+1 after the transfer is done. | ||||||
|     #[inline] |     #[inline] | ||||||
| @@ -809,13 +887,13 @@ impl CPU { | |||||||
|         { |         { | ||||||
|             *value = self.v[reg] |             *value = self.v[reg] | ||||||
|         } |         } | ||||||
|         if self.flags.authentic { |         if self.flags.quirks.dma_inc { | ||||||
|             self.i += x as Adr + 1; |             self.i += x as Adr + 1; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     /// Fx65: DMA Load from I to registers 0..X |     /// Fx65: DMA Load from I to registers 0..X | ||||||
|     ///  |     /// | ||||||
|     /// # Authenticity |     /// # Quirk | ||||||
|     /// The original chip-8 interpreter uses I to directly index memory, |     /// The original chip-8 interpreter uses I to directly index memory, | ||||||
|     /// with the side effect of leaving I as I+X+1 after the transfer is done. |     /// with the side effect of leaving I as I+X+1 after the transfer is done. | ||||||
|     #[inline] |     #[inline] | ||||||
| @@ -829,7 +907,7 @@ impl CPU { | |||||||
|         { |         { | ||||||
|             self.v[reg] = *value; |             self.v[reg] = *value; | ||||||
|         } |         } | ||||||
|         if self.flags.authentic { |         if self.flags.quirks.dma_inc { | ||||||
|             self.i += x as Adr + 1; |             self.i += x as Adr + 1; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										186
									
								
								src/cpu/tests.rs
									
									
									
									
									
								
							
							
						
						
									
										186
									
								
								src/cpu/tests.rs
									
									
									
									
									
								
							| @@ -14,6 +14,7 @@ fn setup_environment() -> (CPU, Bus) { | |||||||
|             flags: ControlFlags { |             flags: ControlFlags { | ||||||
|                 debug: true, |                 debug: true, | ||||||
|                 pause: false, |                 pause: false, | ||||||
|  |                 monotonic: Some(8), | ||||||
|                 ..Default::default() |                 ..Default::default() | ||||||
|             }, |             }, | ||||||
|             ..CPU::default() |             ..CPU::default() | ||||||
| @@ -29,6 +30,10 @@ fn setup_environment() -> (CPU, Bus) { | |||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn print_screen(bytes: &[u8]) { | ||||||
|  |     bus! {Screen [0..0x100] = bytes}.print_screen().unwrap() | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Unused instructions | /// Unused instructions | ||||||
| /// | /// | ||||||
| /// TODO: Exhaustively test unused instructions | /// TODO: Exhaustively test unused instructions | ||||||
| @@ -72,7 +77,7 @@ mod sys { | |||||||
|         let sp_orig = cpu.sp; |         let sp_orig = cpu.sp; | ||||||
|         // Place the address on the stack |         // Place the address on the stack | ||||||
|         bus.write(cpu.sp.wrapping_add(2), test_addr); |         bus.write(cpu.sp.wrapping_add(2), test_addr); | ||||||
|          |  | ||||||
|         cpu.ret(&mut bus); |         cpu.ret(&mut bus); | ||||||
|  |  | ||||||
|         // Verify the current address is the address from the stack |         // Verify the current address is the address from the stack | ||||||
| @@ -251,7 +256,6 @@ mod math { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// 8xy0: Loads the value of y into x |     /// 8xy0: Loads the value of y into x | ||||||
|     // TODO: Test with authentic flag set |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn load() { |     fn load() { | ||||||
|         let (mut cpu, _) = setup_environment(); |         let (mut cpu, _) = setup_environment(); | ||||||
| @@ -275,10 +279,11 @@ mod math { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX |     /// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX | ||||||
|     // TODO: Test with authentic flag set |     // TODO: Test with bin_ops quirk flag set | ||||||
|     #[test] |     #[test] | ||||||
|     fn or() { |     fn or_inaccurate() { | ||||||
|         let (mut cpu, _) = setup_environment(); |         let (mut cpu, _) = setup_environment(); | ||||||
|  |         cpu.flags.quirks.bin_ops = false; | ||||||
|         for word in 0..=0xffff { |         for word in 0..=0xffff { | ||||||
|             let (a, b) = (word as u8, (word >> 4) as u8); |             let (a, b) = (word as u8, (word >> 4) as u8); | ||||||
|             let expected_result = a | b; |             let expected_result = a | b; | ||||||
| @@ -297,10 +302,11 @@ mod math { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX |     /// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX | ||||||
|     // TODO: Test with authentic flag set |     // TODO: Test with bin_ops quirk flag set | ||||||
|     #[test] |     #[test] | ||||||
|     fn and() { |     fn and_inaccurate() { | ||||||
|         let (mut cpu, _) = setup_environment(); |         let (mut cpu, _) = setup_environment(); | ||||||
|  |         cpu.flags.quirks.bin_ops = false; | ||||||
|         for word in 0..=0xffff { |         for word in 0..=0xffff { | ||||||
|             let (a, b) = (word as u8, (word >> 4) as u8); |             let (a, b) = (word as u8, (word >> 4) as u8); | ||||||
|             let expected_result = a & b; |             let expected_result = a & b; | ||||||
| @@ -319,10 +325,11 @@ mod math { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX |     /// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX | ||||||
|     // TODO: Test with authentic flag set |     // TODO: Test with bin_ops quirk flag set | ||||||
|     #[test] |     #[test] | ||||||
|     fn xor() { |     fn xor_inaccurate() { | ||||||
|         let (mut cpu, _) = setup_environment(); |         let (mut cpu, _) = setup_environment(); | ||||||
|  |         cpu.flags.quirks.bin_ops = false; | ||||||
|         for word in 0..=0xffff { |         for word in 0..=0xffff { | ||||||
|             let (a, b) = (word as u8, (word >> 4) as u8); |             let (a, b) = (word as u8, (word >> 4) as u8); | ||||||
|             let expected_result = a ^ b; |             let expected_result = a ^ b; | ||||||
| @@ -521,41 +528,74 @@ mod io { | |||||||
|  |  | ||||||
|     mod display { |     mod display { | ||||||
|         use super::*; |         use super::*; | ||||||
|  |         #[derive(Debug)] | ||||||
|         struct ScreenTest { |         struct ScreenTest { | ||||||
|             program: &'static [u8], |             program: &'static [u8], | ||||||
|             screen: &'static [u8], |             screen: &'static [u8], | ||||||
|             steps: usize, |             steps: usize, | ||||||
|             rate: usize, |             quirks: Quirks, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const SCREEN_TESTS: [ScreenTest; 4] = [ |         const SCREEN_TESTS: [ScreenTest; 4] = [ | ||||||
|             // Passing BC_test |             // Passing BC_test | ||||||
|  |             // # Quirks: | ||||||
|  |             // - Requires | ||||||
|             ScreenTest { |             ScreenTest { | ||||||
|                 program: include_bytes!("../../chip-8/BC_test.ch8"), |                 program: include_bytes!("../../chip-8/BC_test.ch8"), | ||||||
|                 screen: include_bytes!("tests/BC_test.ch8_197.bin"), |                 screen: include_bytes!("tests/screens/BC_test.ch8/197.bin"), | ||||||
|                 steps: 197, |                 steps: 250, | ||||||
|                 rate: 8, |                 quirks: Quirks { | ||||||
|  |                     bin_ops: true, | ||||||
|  |                     shift: false, | ||||||
|  |                     draw_wait: true, | ||||||
|  |  | ||||||
|  |                     dma_inc: false, | ||||||
|  |                     stupid_jumps: false, | ||||||
|  |                 }, | ||||||
|             }, |             }, | ||||||
|             // The IBM Logo |             // The IBM Logo | ||||||
|             ScreenTest { |             ScreenTest { | ||||||
|                 program: include_bytes!("../../chip-8/IBM Logo.ch8"), |                 program: include_bytes!("../../chip-8/IBM Logo.ch8"), | ||||||
|                 screen: include_bytes!("tests/IBM Logo.ch8_20.bin"), |                 screen: include_bytes!("tests/screens/IBM Logo.ch8/20.bin"), | ||||||
|                 steps: 20, |                 steps: 20, | ||||||
|                 rate: 8, |                 quirks: Quirks { | ||||||
|  |                     bin_ops: true, | ||||||
|  |                     shift: true, | ||||||
|  |                     draw_wait: false, | ||||||
|  |  | ||||||
|  |                     dma_inc: true, | ||||||
|  |                     stupid_jumps: false, | ||||||
|  |                 }, | ||||||
|             }, |             }, | ||||||
|             // Rule 22 cellular automata |             // Rule 22 cellular automata | ||||||
|  |             // # Quirks | ||||||
|  |             // - Requires draw_wait false, or it just takes AGES. | ||||||
|             ScreenTest { |             ScreenTest { | ||||||
|                 program: include_bytes!("../../chip-8/1dcell.ch8"), |                 program: include_bytes!("../../chip-8/1dcell.ch8"), | ||||||
|                 screen: include_bytes!("tests/1dcell.ch8_123342.bin"), |                 screen: include_bytes!("tests/screens/1dcell.ch8/123342.bin"), | ||||||
|                 steps: 123342, |                 steps: 123342, | ||||||
|                 rate: 8, |                 quirks: Quirks { | ||||||
|  |                     bin_ops: true, | ||||||
|  |                     shift: true, | ||||||
|  |                     draw_wait: false, | ||||||
|  |  | ||||||
|  |                     dma_inc: true, | ||||||
|  |                     stupid_jumps: false, | ||||||
|  |                 }, | ||||||
|             }, |             }, | ||||||
|             // Rule 60 cellular automata |             // Rule 60 cellular automata | ||||||
|             ScreenTest { |             ScreenTest { | ||||||
|                 program: include_bytes!("../../chip-8/1dcell.ch8"), |                 program: include_bytes!("../../chip-8/1dcell.ch8"), | ||||||
|                 screen: include_bytes!("tests/1dcell.ch8_2391162.bin"), |                 screen: include_bytes!("tests/screens/1dcell.ch8/2391162.bin"), | ||||||
|                 steps: 2391162, |                 steps: 2391162, | ||||||
|                 rate: 8, |                 quirks: Quirks { | ||||||
|  |                     bin_ops: true, | ||||||
|  |                     shift: true, | ||||||
|  |                     draw_wait: false, | ||||||
|  |  | ||||||
|  |                     dma_inc: true, | ||||||
|  |                     stupid_jumps: false, | ||||||
|  |                 }, | ||||||
|             }, |             }, | ||||||
|         ]; |         ]; | ||||||
|  |  | ||||||
| @@ -564,11 +604,16 @@ mod io { | |||||||
|         fn draw() { |         fn draw() { | ||||||
|             for test in SCREEN_TESTS { |             for test in SCREEN_TESTS { | ||||||
|                 let (mut cpu, mut bus) = setup_environment(); |                 let (mut cpu, mut bus) = setup_environment(); | ||||||
|  |                 cpu.flags.quirks = test.quirks; | ||||||
|                 // Load the test program |                 // Load the test program | ||||||
|                 bus = bus.load_region(Program, test.program); |                 bus = bus.load_region(Program, test.program); | ||||||
|                 // Run the test program for the specified number of steps |                 // Run the test program for the specified number of steps | ||||||
|                 cpu.multistep(&mut bus, test.steps, test.rate); |                 while cpu.cycle() < test.steps { | ||||||
|  |                     cpu.multistep(&mut bus, test.steps - cpu.cycle()); | ||||||
|  |                 } | ||||||
|                 // Compare the screen to the reference screen buffer |                 // Compare the screen to the reference screen buffer | ||||||
|  |                 bus.print_screen().unwrap(); | ||||||
|  |                 print_screen(test.screen); | ||||||
|                 assert_eq!(bus.get_region(Screen).unwrap(), test.screen); |                 assert_eq!(bus.get_region(Screen).unwrap(), test.screen); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -605,7 +650,7 @@ mod io { | |||||||
|         for word in 0..=0xff { |         for word in 0..=0xff { | ||||||
|             for x in 0..=0xf { |             for x in 0..=0xf { | ||||||
|                 // set the register under test to `word` |                 // set the register under test to `word` | ||||||
|                 cpu.delay = word; |                 cpu.delay = word as f64; | ||||||
|                 // do the thing |                 // do the thing | ||||||
|                 cpu.load_delay_timer(x); |                 cpu.load_delay_timer(x); | ||||||
|                 // validate the result |                 // validate the result | ||||||
| @@ -625,7 +670,7 @@ mod io { | |||||||
|                 // do the thing |                 // do the thing | ||||||
|                 cpu.store_delay_timer(x); |                 cpu.store_delay_timer(x); | ||||||
|                 // validate the result |                 // validate the result | ||||||
|                 assert_eq!(cpu.delay, word); |                 assert_eq!(cpu.delay, word as f64); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -641,7 +686,7 @@ mod io { | |||||||
|                 // do the thing |                 // do the thing | ||||||
|                 cpu.store_sound_timer(x); |                 cpu.store_sound_timer(x); | ||||||
|                 // validate the result |                 // validate the result | ||||||
|                 assert_eq!(cpu.sound, word); |                 assert_eq!(cpu.sound, word as f64); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -744,20 +789,30 @@ mod io { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Fx55: DMA Stor from I to registers 0..X |     /// Fx55: DMA Stor from I to registers 0..X | ||||||
|     // TODO: Test with authentic flag unset |     // TODO: Test with dma_inc quirk set | ||||||
|     // TODO: Test with authentic flag set |     #[test] | ||||||
|     //#[test] |  | ||||||
|     #[allow(dead_code)] |     #[allow(dead_code)] | ||||||
|     fn dma_store() { |     fn dma_store() { | ||||||
|         todo!() |         let (mut cpu, mut bus) = setup_environment(); | ||||||
|         // Load values into registers |         const DATA: &[u8] = b"ABCDEFGHIJKLMNOP"; | ||||||
|         // Perform DMA store |         // Load some test data into memory | ||||||
|         // Check that |         let addr = 0x456; | ||||||
|  |         cpu.v.as_mut_slice().write(DATA).unwrap(); | ||||||
|  |         for len in 0..16 { | ||||||
|  |             // Perform DMA store | ||||||
|  |             cpu.i = addr as u16; | ||||||
|  |             cpu.store_dma(len, &mut bus); | ||||||
|  |             // Check that bus grabbed the correct data | ||||||
|  |             let mut bus = bus.get_mut(addr..addr + DATA.len()).unwrap(); | ||||||
|  |             assert_eq!(bus[0..=len], DATA[0..=len]); | ||||||
|  |             assert_eq!(bus[len + 1..], [0; 16][len + 1..]); | ||||||
|  |             // clear | ||||||
|  |             bus.write(&[0; 16]).unwrap(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Fx65: DMA Load from I to registers 0..X |     /// Fx65: DMA Load from I to registers 0..X | ||||||
|     // TODO: Test with authentic flag unset |     // TODO: Test with dma_inc quirk set | ||||||
|     // TODO: Test with authentic flag set |  | ||||||
|     #[test] |     #[test] | ||||||
|     #[allow(dead_code)] |     #[allow(dead_code)] | ||||||
|     fn dma_load() { |     fn dma_load() { | ||||||
| @@ -781,3 +836,72 @@ mod io { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// These are a series of interpreter tests using Timendus's incredible test suite | ||||||
|  | mod chip8_test_suite { | ||||||
|  |     use super::*; | ||||||
|  |  | ||||||
|  |     struct SuiteTest { | ||||||
|  |         program: &'static [u8], | ||||||
|  |         screen: &'static [u8], | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn run_screentest(test: SuiteTest, mut cpu: CPU, mut bus: Bus) { | ||||||
|  |         // Load the test program | ||||||
|  |         bus = bus.load_region(Program, test.program); | ||||||
|  |         // The test suite always initiates a keypause on test completion | ||||||
|  |         while !cpu.flags.keypause { | ||||||
|  |             cpu.multistep(&mut bus, 8); | ||||||
|  |         } | ||||||
|  |         // Compare the screen to the reference screen buffer | ||||||
|  |         bus.print_screen().unwrap(); | ||||||
|  |         bus! {crate::bus::Region::Screen [0..256] = test.screen} | ||||||
|  |             .print_screen() | ||||||
|  |             .unwrap(); | ||||||
|  |         assert_eq!(bus.get_region(Screen).unwrap(), test.screen); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn splash_screen() { | ||||||
|  |         let (mut c, b) = setup_environment(); | ||||||
|  |         c.flags.quirks = true.into(); | ||||||
|  |         run_screentest( | ||||||
|  |             SuiteTest { | ||||||
|  |                 program: include_bytes!("tests/chip8-test-suite/bin/chip8-test-suite.ch8"), | ||||||
|  |                 screen: include_bytes!("tests/screens/chip8-test-suite.ch8/splash.bin"), | ||||||
|  |             }, | ||||||
|  |             c, | ||||||
|  |             b, | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn flags_test() { | ||||||
|  |         let (mut c, mut b) = setup_environment(); | ||||||
|  |         c.flags.quirks = true.into(); | ||||||
|  |         b.write(0x1ffu16, 3u8); | ||||||
|  |         run_screentest( | ||||||
|  |             SuiteTest { | ||||||
|  |                 program: include_bytes!("tests/chip8-test-suite/bin/chip8-test-suite.ch8"), | ||||||
|  |                 screen: include_bytes!("tests/screens/chip8-test-suite.ch8/flags.bin"), | ||||||
|  |             }, | ||||||
|  |             c, | ||||||
|  |             b, | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn quirks_test() { | ||||||
|  |         let (mut c, mut b) = setup_environment(); | ||||||
|  |         c.flags.quirks = true.into(); | ||||||
|  |         b.write(0x1feu16, 0x0104u16); | ||||||
|  |         run_screentest( | ||||||
|  |             SuiteTest { | ||||||
|  |                 program: include_bytes!("tests/chip8-test-suite/bin/chip8-test-suite.ch8"), | ||||||
|  |                 screen: include_bytes!("tests/screens/chip8-test-suite.ch8/quirks.bin"), | ||||||
|  |             }, | ||||||
|  |             c, | ||||||
|  |             b, | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user