From bb8015f33c94400b9b130fa591892ab5bee9e815 Mon Sep 17 00:00:00 2001 From: John Breaux Date: Sat, 1 Apr 2023 00:15:40 -0500 Subject: [PATCH] Quirks: Make the Cosmac VIP behavior default. --- src/bin/chirp-minifb/main.rs | 8 +- src/cpu.rs | 259 +++++++++++++++--------------- src/cpu/tests.rs | 299 ++++++++++++++++++++++++----------- src/cpu/tests/decode.rs | 2 +- tests/chip8_test_suite.rs | 30 ++-- 5 files changed, 359 insertions(+), 239 deletions(-) diff --git a/src/bin/chirp-minifb/main.rs b/src/bin/chirp-minifb/main.rs index ae2ac12..fbc6850 100644 --- a/src/bin/chirp-minifb/main.rs +++ b/src/bin/chirp-minifb/main.rs @@ -117,10 +117,10 @@ impl State { options.breakpoints, ControlFlags { quirks: chirp::cpu::Quirks { - bin_ops: !options.vfreset, - shift: !options.shift, - draw_wait: !options.drawsync, - dma_inc: !options.memory, + bin_ops: options.vfreset, + shift: options.shift, + draw_wait: options.drawsync, + dma_inc: options.memory, stupid_jumps: options.jumping, }, debug: options.debug, diff --git a/src/cpu.rs b/src/cpu.rs index 9538d2f..1c4f680 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -29,17 +29,17 @@ type Adr = u16; type Nib = u8; /// Controls the authenticity behavior of the CPU on a granular level. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Quirks { - /// Binary ops in `8xy`(`1`, `2`, `3`) should set vF to 0 + /// Binary ops in `8xy`(`1`, `2`, `3`) shouldn't set vF to 0 pub bin_ops: bool, - /// Shift ops in `8xy`(`6`, `E`) should source from vY instead of vX + /// Shift ops in `8xy`(`6`, `E`) shouldn't source from vY instead of vX pub shift: bool, - /// Draw operations should pause execution until the next timer tick + /// Draw operations shouldn't pause execution until the next timer tick pub draw_wait: bool, - /// DMA instructions `Fx55`/`Fx65` should change I to I + x + 1 + /// DMA instructions `Fx55`/`Fx65` shouldn't 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`a` where `a` is high nibble of `adr`. pub stupid_jumps: bool, } @@ -593,42 +593,7 @@ impl CPU { // decode opcode if let Ok((inc, insn)) = Insn::decode(opcode) { self.pc = self.pc.wrapping_add(inc as u16); - match insn { - Insn::cls => self.clear_screen(bus), - Insn::ret => self.ret(bus), - Insn::jmp { A } => self.jump(A), - Insn::call { A } => self.call(A, bus), - Insn::seb { B, x } => self.skip_equals_immediate(x, B), - Insn::sneb { B, x } => self.skip_not_equals_immediate(x, B), - Insn::se { y, x } => self.skip_equals(x, y), - Insn::movb { B, x } => self.load_immediate(x, B), - Insn::addb { B, x } => self.add_immediate(x, B), - Insn::mov { x, y } => self.load(x, y), - Insn::or { y, x } => self.or(x, y), - Insn::and { y, x } => self.and(x, y), - Insn::xor { y, x } => self.xor(x, y), - Insn::add { y, x } => self.add(x, y), - Insn::sub { y, x } => self.sub(x, y), - Insn::shr { y, x } => self.shift_right(x, y), - Insn::bsub { y, x } => self.backwards_sub(x, y), - Insn::shl { y, x } => self.shift_left(x, y), - Insn::sne { y, x } => self.skip_not_equals(x, y), - Insn::movI { A } => self.load_i_immediate(A), - Insn::jmpr { A } => self.jump_indexed(A), - Insn::rand { B, x } => self.rand(x, B), - Insn::draw { x, y, n } => self.draw(x, y, n, bus), - Insn::sek { x } => self.skip_key_equals(x), - Insn::snek { x } => self.skip_key_not_equals(x), - Insn::getdt { x } => self.load_delay_timer(x), - Insn::waitk { x } => self.wait_for_key(x), - Insn::setdt { x } => self.store_delay_timer(x), - Insn::movst { x } => self.store_sound_timer(x), - Insn::addI { x } => self.add_i(x), - Insn::font { x } => self.load_sprite(x), - Insn::bcd { x } => self.bcd_convert(x, bus), - Insn::dmao { x } => self.store_dma(x, bus), - Insn::dmai { x } => self.load_dma(x, bus), - } + self.execute(bus, insn); } else { return Err(Error::UnimplementedInstruction { word: u16::from_be_bytes(*opcode), @@ -727,23 +692,65 @@ impl Default for CPU { // Below this point, comments may be duplicated per impl' block, // since some opcodes handle multiple instructions. -// | 0aaa | Issues a "System call" (ML routine) +impl CPU { + /// Executes a single [Insn] + #[inline(always)] + #[rustfmt::skip] + fn execute(&mut self, bus: &mut Bus, instruction: Insn) { + match instruction { + Insn::cls => self.clear_screen(bus), + Insn::ret => self.ret(bus), + Insn::jmp { A } => self.jump(A), + Insn::call { A } => self.call(A, bus), + Insn::seb { B, x } => self.skip_equals_immediate(x, B), + Insn::sneb { B, x } => self.skip_not_equals_immediate(x, B), + Insn::se { y, x } => self.skip_equals(x, y), + Insn::movb { B, x } => self.load_immediate(x, B), + Insn::addb { B, x } => self.add_immediate(x, B), + Insn::mov { x, y } => self.load(x, y), + Insn::or { y, x } => self.or(x, y), + Insn::and { y, x } => self.and(x, y), + Insn::xor { y, x } => self.xor(x, y), + Insn::add { y, x } => self.add(x, y), + Insn::sub { y, x } => self.sub(x, y), + Insn::shr { y, x } => self.shift_right(x, y), + Insn::bsub { y, x } => self.backwards_sub(x, y), + Insn::shl { y, x } => self.shift_left(x, y), + Insn::sne { y, x } => self.skip_not_equals(x, y), + Insn::movI { A } => self.load_i_immediate(A), + Insn::jmpr { A } => self.jump_indexed(A), + Insn::rand { B, x } => self.rand(x, B), + Insn::draw { x, y, n } => self.draw(x, y, n, bus), + Insn::sek { x } => self.skip_key_equals(x), + Insn::snek { x } => self.skip_key_not_equals(x), + Insn::getdt { x } => self.load_delay_timer(x), + Insn::waitk { x } => self.wait_for_key(x), + Insn::setdt { x } => self.store_delay_timer(x), + Insn::movst { x } => self.store_sound_timer(x), + Insn::addI { x } => self.add_i(x), + Insn::font { x } => self.load_sprite(x), + Insn::bcd { x } => self.bcd_convert(x, bus), + Insn::dmao { x } => self.store_dma(x, bus), + Insn::dmai { x } => self.load_dma(x, bus), + } + } +} + +// |`0aaa`| Issues a "System call" (ML routine) // // |opcode| effect | // |------|------------------------------------| -// | 00e0 | Clear screen memory to all 0 | -// | 00ee | Return from subroutine | +// |`00e0`| Clear screen memory to all 0 | +// |`00ee`| Return from subroutine | impl CPU { - /// 00e0: Clears the screen memory to 0 + /// |`00e0`| Clears the screen memory to 0 #[inline(always)] fn clear_screen(&mut self, bus: &mut Bus) { if let Some(screen) = bus.get_region_mut(Region::Screen) { - for byte in screen { - *byte = 0; - } + screen.fill(0); } } - /// 00ee: Returns from subroutine + /// |`00ee`| Returns from subroutine #[inline(always)] fn ret(&mut self, bus: &impl Read) { self.sp = self.sp.wrapping_add(2); @@ -751,9 +758,9 @@ impl CPU { } } -// | 1aaa | Sets pc to an absolute address +// |`1aaa`| Sets pc to an absolute address impl CPU { - /// 1aaa: Sets the program counter to an absolute address + /// |`1aaa`| Sets the program counter to an absolute address #[inline(always)] fn jump(&mut self, a: Adr) { // jump to self == halt @@ -764,9 +771,9 @@ impl CPU { } } -// | 2aaa | Pushes pc onto the stack, then jumps to a +// |`2aaa`| Pushes pc onto the stack, then jumps to a impl CPU { - /// 2aaa: Pushes pc onto the stack, then jumps to a + /// |`2aaa`| Pushes pc onto the stack, then jumps to a #[inline(always)] fn call(&mut self, a: Adr, bus: &mut impl Write) { bus.write(self.sp, self.pc); @@ -775,9 +782,9 @@ impl CPU { } } -// | 3xbb | Skips next instruction if register X == b +// |`3xbb`| Skips next instruction if register X == b impl CPU { - /// 3xbb: Skips the next instruction if register X == b + /// |`3xbb`| Skips the next instruction if register X == b #[inline(always)] fn skip_equals_immediate(&mut self, x: Reg, b: u8) { if self.v[x] == b { @@ -786,9 +793,9 @@ impl CPU { } } -// | 4xbb | Skips next instruction if register X != b +// |`4xbb`| Skips next instruction if register X != b impl CPU { - /// 4xbb: Skips the next instruction if register X != b + /// |`4xbb`| Skips the next instruction if register X != b #[inline(always)] fn skip_not_equals_immediate(&mut self, x: Reg, b: u8) { if self.v[x] != b { @@ -797,13 +804,13 @@ impl CPU { } } -// | 5xyn | Performs a register-register comparison +// |`5xyn`| Performs a register-register comparison // // |opcode| effect | // |------|------------------------------------| -// | 5XY0 | Skip next instruction if vX == vY | +// |`5XY0`| Skip next instruction if vX == vY | impl CPU { - /// 5xy0: Skips the next instruction if register X != register Y + /// |`5xy0`| Skips the next instruction if register X != register Y #[inline(always)] fn skip_equals(&mut self, x: Reg, y: Reg) { if self.v[x] == self.v[y] { @@ -812,102 +819,102 @@ impl CPU { } } -// | 6xbb | Loads immediate byte b into register vX +// |`6xbb`| Loads immediate byte b into register vX impl CPU { - /// 6xbb: Loads immediate byte b into register vX + /// |`6xbb`| Loads immediate byte b into register vX #[inline(always)] fn load_immediate(&mut self, x: Reg, b: u8) { self.v[x] = b; } } -// | 7xbb | Adds immediate byte b to register vX +// |`7xbb`| Adds immediate byte b to register vX impl CPU { - /// 7xbb: Adds immediate byte b to register vX + /// |`7xbb`| Adds immediate byte b to register vX #[inline(always)] fn add_immediate(&mut self, x: Reg, b: u8) { self.v[x] = self.v[x].wrapping_add(b); } } -// | 8xyn | Performs ALU operation +// |`8xyn`| Performs ALU operation // // |opcode| effect | // |------|------------------------------------| -// | 8xy0 | Y = X | -// | 8xy1 | X = X | Y | -// | 8xy2 | X = X & Y | -// | 8xy3 | X = X ^ Y | -// | 8xy4 | X = X + Y; Set vF=carry | -// | 8xy5 | X = X - Y; Set vF=carry | -// | 8xy6 | X = X >> 1 | -// | 8xy7 | X = Y - X; Set vF=carry | -// | 8xyE | X = X << 1 | +// |`8xy0`| Y = X | +// |`8xy1`| X = X | Y | +// |`8xy2`| X = X & Y | +// |`8xy3`| X = X ^ Y | +// |`8xy4`| X = X + Y; Set vF=carry | +// |`8xy5`| X = X - Y; Set vF=carry | +// |`8xy6`| X = X >> 1 | +// |`8xy7`| X = Y - X; Set vF=carry | +// |`8xyE`| X = X << 1 | impl CPU { - /// 8xy0: Loads the value of y into x + /// |`8xy0`| Loads the value of y into x #[inline(always)] fn load(&mut self, x: Reg, y: Reg) { self.v[x] = self.v[y]; } - /// 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 /// /// # Quirk /// The original chip-8 interpreter will clobber vF for any 8-series instruction #[inline(always)] fn or(&mut self, x: Reg, y: Reg) { self.v[x] |= self.v[y]; - if self.flags.quirks.bin_ops { + if !self.flags.quirks.bin_ops { 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 /// /// # Quirk /// The original chip-8 interpreter will clobber vF for any 8-series instruction #[inline(always)] fn and(&mut self, x: Reg, y: Reg) { self.v[x] &= self.v[y]; - if self.flags.quirks.bin_ops { + if !self.flags.quirks.bin_ops { 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 /// /// # Quirk /// The original chip-8 interpreter will clobber vF for any 8-series instruction #[inline(always)] fn xor(&mut self, x: Reg, y: Reg) { self.v[x] ^= self.v[y]; - if self.flags.quirks.bin_ops { + if !self.flags.quirks.bin_ops { self.v[0xf] = 0; } } - /// 8xy4: Performs addition of vX and vY, and stores the result in vX + /// |`8xy4`| Performs addition of vX and vY, and stores the result in vX #[inline(always)] fn add(&mut self, x: Reg, y: Reg) { let carry; (self.v[x], carry) = self.v[x].overflowing_add(self.v[y]); self.v[0xf] = carry.into(); } - /// 8xy5: Performs subtraction of vX and vY, and stores the result in vX + /// |`8xy5`| Performs subtraction of vX and vY, and stores the result in vX #[inline(always)] fn sub(&mut self, x: Reg, y: Reg) { let carry; (self.v[x], carry) = self.v[x].overflowing_sub(self.v[y]); self.v[0xf] = (!carry).into(); } - /// 8xy6: Performs bitwise right shift of vX + /// |`8xy6`| Performs bitwise right shift of vX /// /// # Quirk /// On the original chip-8 interpreter, this shifts vY and stores the result in vX #[inline(always)] fn shift_right(&mut self, x: Reg, y: Reg) { - let src: Reg = if self.flags.quirks.shift { y } else { x }; + let src: Reg = if self.flags.quirks.shift { x } else { y }; let shift_out = self.v[src] & 1; self.v[x] = self.v[src] >> 1; self.v[0xf] = shift_out; } - /// 8xy7: Performs subtraction of vY and vX, and stores the result in vX + /// |`8xy7`| Performs subtraction of vY and vX, and stores the result in vX #[inline(always)] fn backwards_sub(&mut self, x: Reg, y: Reg) { let carry; @@ -921,20 +928,20 @@ impl CPU { /// and store the result in vX. This behavior was left out, for now. #[inline(always)] fn shift_left(&mut self, x: Reg, y: Reg) { - let src: Reg = if self.flags.quirks.shift { y } else { x }; + let src: Reg = if self.flags.quirks.shift { x } else { y }; let shift_out: u8 = self.v[src] >> 7; self.v[x] = self.v[src] << 1; self.v[0xf] = shift_out; } } -// | 9xyn | Performs a register-register comparison +// |`9xyn`| Performs a register-register comparison // // |opcode| effect | // |------|------------------------------------| -// | 9XY0 | Skip next instruction if vX != vY | +// |`9XY0`| Skip next instruction if vX != vY | impl CPU { - /// 9xy0: Skip next instruction if X != y + /// |`9xy0`| Skip next instruction if X != y #[inline(always)] fn skip_not_equals(&mut self, x: Reg, y: Reg) { if self.v[x] != self.v[y] { @@ -943,18 +950,18 @@ impl CPU { } } -// | Aaaa | Load address #a into register I +// |`Aaaa`| Load address #a into register I impl CPU { - /// Aadr: Load address #adr into register I + /// |`Aadr`| Load address #adr into register I #[inline(always)] fn load_i_immediate(&mut self, a: Adr) { self.i = a; } } -// | Baaa | Jump to &adr + v0 +// |`Baaa`| Jump to &adr + v0 impl CPU { - /// Badr: Jump to &adr + v0 + /// |`Badr`| Jump to &adr + v0 /// /// Quirk: /// On the Super-Chip, this does stupid shit @@ -969,25 +976,25 @@ impl CPU { } } -// | Cxbb | Stores a random number & the provided byte into vX +// |`Cxbb`| Stores a random number & the provided byte into vX impl CPU { - /// Cxbb: Stores a random number & the provided byte into vX + /// |`Cxbb`| Stores a random number & the provided byte into vX #[inline(always)] fn rand(&mut self, x: Reg, b: u8) { self.v[x] = random::() & b; } } -// | 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 { - /// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY) + /// |`Dxyn`| Draws n-byte sprite to the screen at coordinates (vX, vY) /// /// # Quirk /// On the original chip-8 interpreter, this will wait for a VBI #[inline(always)] fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { let (x, y) = (self.v[x] as u16 % 64, self.v[y] as u16 % 32); - if self.flags.quirks.draw_wait { + if !self.flags.quirks.draw_wait { self.flags.draw_wait = true; } self.v[0xf] = 0; @@ -1015,14 +1022,14 @@ impl CPU { } } -// | Exbb | Skips instruction on value of keypress +// |`Exbb`| Skips instruction on value of keypress // // |opcode| effect | // |------|------------------------------------| -// | eX9e | Skip next instruction if key == vX | -// | eXa1 | Skip next instruction if key != vX | +// |`eX9e`| Skip next instruction if key == vX | +// |`eXa1`| Skip next instruction if key != vX | impl CPU { - /// Ex9E: Skip next instruction if key == vX + /// |`Ex9E`| Skip next instruction if key == vX #[inline(always)] fn skip_key_equals(&mut self, x: Reg) { let x = self.v[x] as usize; @@ -1030,7 +1037,7 @@ impl CPU { self.pc += 2; } } - /// ExaE: Skip next instruction if key != vX + /// |`ExaE`| Skip next instruction if key != vX #[inline(always)] fn skip_key_not_equals(&mut self, x: Reg) { let x = self.v[x] as usize; @@ -1040,21 +1047,21 @@ impl CPU { } } -// | Fxbb | Performs IO +// |`Fxbb`| Performs IO // // |opcode| effect | // |------|------------------------------------| -// | fX07 | Set vX to value in delay timer | -// | fX0a | Wait for input, store key in vX | -// | fX15 | Set sound timer to the value in vX | -// | fX18 | set delay timer to the value in vX | -// | fX1e | Add vX to I | -// | fX29 | Load sprite for character x into I | -// | fX33 | BCD convert X into I[0..3] | -// | fX55 | DMA Stor from I to registers 0..=X | -// | fX65 | DMA Load from I to registers 0..=X | +// |`fX07`| Set vX to value in delay timer | +// |`fX0a`| Wait for input, store key in vX | +// |`fX15`| Set sound timer to the value in vX | +// |`fX18`| set delay timer to the value in vX | +// |`fX1e`| Add vX to I | +// |`fX29`| Load sprite for character x into I | +// |`fX33`| BCD convert X into I[0..3] | +// |`fX55`| DMA Stor from I to registers 0..=X | +// |`fX65`| DMA Load from I to registers 0..=X | impl CPU { - /// Fx07: Get the current DT, and put it in vX + /// |`Fx07`| Get the current DT, and put it in vX /// ```py /// vX = DT /// ``` @@ -1062,7 +1069,7 @@ impl CPU { fn load_delay_timer(&mut self, x: Reg) { self.v[x] = self.delay as u8; } - /// Fx0A: Wait for key, then vX = K + /// |`Fx0A`| Wait for key, then vX = K #[inline(always)] fn wait_for_key(&mut self, x: Reg) { if let Some(key) = self.flags.lastkey { @@ -1073,7 +1080,7 @@ impl CPU { self.flags.keypause = true; } } - /// Fx15: Load vX into DT + /// |`Fx15`| Load vX into DT /// ```py /// DT = vX /// ``` @@ -1081,7 +1088,7 @@ impl CPU { fn store_delay_timer(&mut self, x: Reg) { self.delay = self.v[x] as f64; } - /// Fx18: Load vX into ST + /// |`Fx18`| Load vX into ST /// ```py /// ST = vX; /// ``` @@ -1089,7 +1096,7 @@ impl CPU { fn store_sound_timer(&mut self, x: Reg) { self.sound = self.v[x] as f64; } - /// Fx1e: Add vX to I, + /// |`Fx1e`| Add vX to I, /// ```py /// I += vX; /// ``` @@ -1097,7 +1104,7 @@ impl CPU { fn add_i(&mut self, x: Reg) { self.i += self.v[x] as u16; } - /// Fx29: Load sprite for character x into I + /// |`Fx29`| Load sprite for character x into I /// ```py /// I = sprite(X); /// ``` @@ -1105,7 +1112,7 @@ impl CPU { fn load_sprite(&mut self, x: Reg) { self.i = self.font + (5 * (self.v[x] as Adr % 0x10)); } - /// Fx33: BCD convert X into I`[0..3]` + /// |`Fx33`| BCD convert X into I`[0..3]` #[inline(always)] fn bcd_convert(&mut self, x: Reg, bus: &mut Bus) { let x = self.v[x]; @@ -1113,7 +1120,7 @@ impl CPU { bus.write(self.i.wrapping_add(1), x / 10 % 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 /// /// # Quirk /// The original chip-8 interpreter uses I to directly index memory, @@ -1129,11 +1136,11 @@ impl CPU { { *value = self.v[reg] } - if self.flags.quirks.dma_inc { + if !self.flags.quirks.dma_inc { self.i += x as Adr + 1; } } - /// Fx65: DMA Load from I to registers 0..=X + /// |`Fx65`| DMA Load from I to registers 0..=X /// /// # Quirk /// The original chip-8 interpreter uses I to directly index memory, @@ -1144,7 +1151,7 @@ impl CPU { for (reg, value) in bus.get(i..=i + x).unwrap_or_default().iter().enumerate() { self.v[reg] = *value; } - if self.flags.quirks.dma_inc { + if !self.flags.quirks.dma_inc { self.i += x as Adr + 1; } } diff --git a/src/cpu/tests.rs b/src/cpu/tests.rs index 2290c41..9b82462 100644 --- a/src/cpu/tests.rs +++ b/src/cpu/tests.rs @@ -331,66 +331,136 @@ mod math { } } } + mod or { + use super::*; - /// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX - // TODO: Test with bin_ops quirk flag set - #[test] - fn or_inaccurate() { - let (mut cpu, _) = setup_environment(); - cpu.flags.quirks.bin_ops = false; - for word in 0..=0xffff { - let (a, b) = (word as u8, (word >> 4) as u8); - let expected_result = a | b; - for reg in 0..=0xff { - let (x, y) = (reg & 0xf, reg >> 4); + /// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX + #[test] + fn or() { + let (mut cpu, _) = setup_environment(); + for word in 0..=0xffff { + let (a, b) = (word as u8, (word >> 4) as u8); + let expected_result = a | b; + for reg in 0..=0xff { + let (x, y) = (reg & 0xf, reg >> 4); - (cpu.v[x], cpu.v[y]) = (a, b); + cpu.v[0xf] = 0xc5; // sentinel + (cpu.v[x], cpu.v[y]) = (a, b); - cpu.or(x, y); + cpu.or(x, y); - assert_eq!(cpu.v[x], if x == y { b } else { expected_result }); + if x != 0xf { + assert_eq!(cpu.v[x], if x == y { b } else { expected_result }); + } + assert_eq!(cpu.v[0xf], 0); + } + } + } + /// Same test, with [Quirks::bin_ops] flag set + #[test] + fn or_quirk() { + let (mut cpu, _) = setup_environment(); + cpu.flags.quirks.bin_ops = true; + for word in 0..=0xffff { + let (a, b) = (word as u8, (word >> 4) as u8); + let expected_result = a | b; + for reg in 0..=0xff { + let (x, y) = (reg & 0xf, reg >> 4); + + (cpu.v[x], cpu.v[y]) = (a, b); + + cpu.or(x, y); + + assert_eq!(cpu.v[x], if x == y { b } else { expected_result }); + } } } } - /// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX - // TODO: Test with bin_ops quirk flag set - #[test] - fn and_inaccurate() { - let (mut cpu, _) = setup_environment(); - cpu.flags.quirks.bin_ops = false; - for word in 0..=0xffff { - let (a, b) = (word as u8, (word >> 4) as u8); - let expected_result = a & b; - for reg in 0..=0xff { - let (x, y) = (reg & 0xf, reg >> 4); + mod and { + use super::*; + /// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX + #[test] + fn and() { + let (mut cpu, _) = setup_environment(); + for word in 0..=0xffff { + let (a, b) = (word as u8, (word >> 4) as u8); + let expected_result = a & b; + for reg in 0..=0xff { + let (x, y) = (reg & 0xf, reg >> 4); - (cpu.v[x], cpu.v[y]) = (a, b); + cpu.v[0xf] = 0xc5; // Sentinel + (cpu.v[x], cpu.v[y]) = (a, b); - cpu.and(x, y); + cpu.and(x, y); + if x != 0xf { + assert_eq!(cpu.v[x], if x == y { b } else { expected_result }); + } + assert_eq!(cpu.v[0xf], 0) + } + } + } + // The same test with [Quirks::bin_ops] flag set + #[test] + fn and_quirk() { + let (mut cpu, _) = setup_environment(); + cpu.flags.quirks.bin_ops = true; + for word in 0..=0xffff { + let (a, b) = (word as u8, (word >> 4) as u8); + let expected_result = a & b; + for reg in 0..=0xff { + let (x, y) = (reg & 0xf, reg >> 4); - assert_eq!(cpu.v[x], if x == y { b } else { expected_result }); + (cpu.v[x], cpu.v[y]) = (a, b); + + cpu.and(x, y); + + assert_eq!(cpu.v[x], if x == y { b } else { expected_result }); + } } } } + mod xor { + use super::*; - /// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX - // TODO: Test with bin_ops quirk flag set - #[test] - fn xor_inaccurate() { - let (mut cpu, _) = setup_environment(); - cpu.flags.quirks.bin_ops = false; - for word in 0..=0xffff { - let (a, b) = (word as u8, (word >> 4) as u8); - let expected_result = a ^ b; - for reg in 0..=0xff { - let (x, y) = (reg & 0xf, reg >> 4); + /// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX + #[test] + fn xor() { + let (mut cpu, _) = setup_environment(); + for word in 0..=0xffff { + let (a, b) = (word as u8, (word >> 4) as u8); + let expected_result = a ^ b; + for reg in 0..=0xff { + let (x, y) = (reg & 0xf, reg >> 4); - (cpu.v[x], cpu.v[y]) = (a, b); + cpu.v[0xf] = 0xc5; // Sentinel + (cpu.v[x], cpu.v[y]) = (a, b); - cpu.xor(x, y); + cpu.xor(x, y); + if x != 0xf { + assert_eq!(cpu.v[x], if x == y { 0 } else { expected_result }); + } + assert_eq!(cpu.v[0xf], 0); + } + } + } + // The same test with [Quirks::bin_ops] flag set + #[test] + fn xor_quirk() { + let (mut cpu, _) = setup_environment(); + cpu.flags.quirks.bin_ops = true; + for word in 0..=0xffff { + let (a, b) = (word as u8, (word >> 4) as u8); + let expected_result = a ^ b; + for reg in 0..=0xff { + let (x, y) = (reg & 0xf, reg >> 4); - assert_eq!(cpu.v[x], if x == y { 0 } else { expected_result }); + (cpu.v[x], cpu.v[y]) = (a, b); + + cpu.xor(x, y); + + assert_eq!(cpu.v[x], if x == y { 0 } else { expected_result }); + } } } } @@ -445,25 +515,49 @@ mod math { } } } + mod shift_right { + use super::*; + /// 8xy6: Performs bitwise right shift of vX, stores carry-out in vF + #[test] + fn shift_right() { + let (mut cpu, _) = setup_environment(); + for word in 0..=0xff { + for reg in 0..=0xff { + let (x, y) = (reg & 0xf, reg >> 4); + // set the register under test to `word` + (cpu.v[x], cpu.v[y]) = (0, word); - /// 8xy6: Performs bitwise right shift of vX, stores carry-out in vF - // TODO: Test with authentic flag set - #[test] - fn shift_right() { - let (mut cpu, _) = setup_environment(); - for word in 0..=0xff { - for x in 0..=0xf { - // set the register under test to `word` - cpu.v[x] = word; + cpu.shift_right(x, y); - cpu.shift_right(x, x); - - // if the destination is vF, the result was discarded, and only the carry was kept - if x != 0xf { - assert_eq!(cpu.v[x], word >> 1); + // if the destination is vF, the result was discarded, and only the carry was kept + if x != 0xf { + assert_eq!(cpu.v[x], word >> 1); + } + // The borrow flag for subtraction is inverted + assert_eq!(cpu.v[0xf], word & 1); + } + } + } + /// The same test, with [Quirks::shift] quirk flag set + #[test] + fn shift_right_quirk() { + let (mut cpu, _) = setup_environment(); + cpu.flags.quirks.shift = true; + for word in 0..=0xff { + for reg in 0..=0xff { + let (x, y) = (reg & 0xf, reg >> 4); + // set the register under test to `word` + cpu.v[x] = word; + + cpu.shift_right(x, y); + + // if the destination is vF, the result was discarded, and only the carry was kept + if x != 0xf { + assert_eq!(cpu.v[x], word >> 1); + } + // The borrow flag for subtraction is inverted + assert_eq!(cpu.v[0xf], word & 1); } - // The borrow flag for subtraction is inverted - assert_eq!(cpu.v[0xf], word & 1); } } } @@ -492,24 +586,49 @@ mod math { } } - /// 8X_E: Performs bitwise left shift of vX - // TODO: Test with authentic flag set - #[test] - fn shift_left() { - let (mut cpu, _) = setup_environment(); - for word in 0..=0xff { - for x in 0..=0xf { - // set the register under test to `word` - cpu.v[x] = word; + mod shift_left { + use super::*; + #[test] + fn shift_left() { + let (mut cpu, _) = setup_environment(); + for word in 1..=0xff { + for reg in 0..=0xff { + let (x, y) = (reg & 0xf, reg >> 4); + // set the register under test to `word` + (cpu.v[x], cpu.v[y]) = dbg!(0, word); - cpu.shift_left(x, x); + cpu.shift_left(x, y); - // if the destination is vF, the result was discarded, and only the carry was kept - if x != 0xf { - assert_eq!(cpu.v[x], word << 1); + // if the destination is vF, the result was discarded, and only the carry was kept + if x != 0xf { + assert_eq!(cpu.v[x], word << 1); + } + // The borrow flag for subtraction is inverted + assert_eq!(cpu.v[0xf], word >> 7); + } + } + } + + /// 8X_E: Performs bitwise left shift of vX + // TODO: Test with authentic flag set + #[test] + fn shift_left_quirk() { + let (mut cpu, _) = setup_environment(); + cpu.flags.quirks.shift = true; + for word in 0..=0xff { + for x in 0..=0xf { + // set the register under test to `word` + cpu.v[x] = word; + + cpu.shift_left(x, x); + + // if the destination is vF, the result was discarded, and only the carry was kept + if x != 0xf { + assert_eq!(cpu.v[x], word << 1); + } + // The borrow flag for subtraction is inverted + assert_eq!(cpu.v[0xf], word >> 7); } - // The borrow flag for subtraction is inverted - assert_eq!(cpu.v[0xf], word >> 7); } } } @@ -590,10 +709,10 @@ mod io { screen: include_bytes!("tests/screens/BC_test.ch8/197.bin"), steps: 250, quirks: Quirks { - bin_ops: true, - shift: false, - draw_wait: true, - dma_inc: false, + bin_ops: false, + shift: true, + draw_wait: false, + dma_inc: true, stupid_jumps: false, }, }, @@ -605,10 +724,10 @@ mod io { screen: include_bytes!("tests/screens/IBM Logo.ch8/20.bin"), steps: 56, quirks: Quirks { - bin_ops: true, - shift: true, - draw_wait: true, - dma_inc: true, + bin_ops: false, + shift: false, + draw_wait: false, + dma_inc: false, stupid_jumps: false, }, }, @@ -620,11 +739,10 @@ mod io { screen: include_bytes!("tests/screens/1dcell.ch8/123342.bin"), steps: 123342, quirks: Quirks { - bin_ops: true, - shift: true, - draw_wait: false, - - dma_inc: true, + bin_ops: false, + shift: false, + draw_wait: true, + dma_inc: false, stupid_jumps: false, }, }, @@ -634,11 +752,10 @@ mod io { screen: include_bytes!("tests/screens/1dcell.ch8/2391162.bin"), steps: 2391162, quirks: Quirks { - bin_ops: true, - shift: true, - draw_wait: false, - - dma_inc: true, + bin_ops: false, + shift: false, + draw_wait: true, + dma_inc: false, stupid_jumps: false, }, }, diff --git a/src/cpu/tests/decode.rs b/src/cpu/tests/decode.rs index 980d4f4..9d247d7 100644 --- a/src/cpu/tests/decode.rs +++ b/src/cpu/tests/decode.rs @@ -13,7 +13,7 @@ fn run_single_op(op: &[u8]) -> CPU { }, ); cpu.v = *INDX; - cpu.flags.quirks = Quirks::from(true); + cpu.flags.quirks = Quirks::from(false); cpu.tick(&mut bus).unwrap(); // will panic if unimplemented cpu } diff --git a/tests/chip8_test_suite.rs b/tests/chip8_test_suite.rs index 908d642..ae3b2e0 100644 --- a/tests/chip8_test_suite.rs +++ b/tests/chip8_test_suite.rs @@ -45,8 +45,7 @@ fn run_screentest(test: SuiteTest, mut cpu: CPU, mut bus: Bus) { #[test] fn splash_screen() { - let (mut c, b) = setup_environment(); - c.flags.quirks = true.into(); + let (c, b) = setup_environment(); run_screentest( SuiteTest { program: include_bytes!("chip8-test-suite/bin/chip8-test-suite.ch8"), @@ -59,45 +58,42 @@ fn splash_screen() { #[test] fn ibm_logo() { - let (mut c, mut b) = setup_environment(); - c.flags.quirks = true.into(); - b.write(0x1ffu16, 1u8); + let (cpu, mut bus) = setup_environment(); + bus.write(0x1ffu16, 1u8); run_screentest( SuiteTest { program: include_bytes!("chip8-test-suite/bin/chip8-test-suite.ch8"), screen: include_bytes!("screens/chip8-test-suite/IBM.bin"), }, - c, - b, + cpu, + bus, ) } #[test] fn flags_test() { - let (mut c, mut b) = setup_environment(); - c.flags.quirks = true.into(); - b.write(0x1ffu16, 3u8); + let (cpu, mut bus) = setup_environment(); + bus.write(0x1ffu16, 3u8); run_screentest( SuiteTest { program: include_bytes!("chip8-test-suite/bin/chip8-test-suite.ch8"), screen: include_bytes!("screens/chip8-test-suite/flags.bin"), }, - c, - b, + cpu, + bus, ) } #[test] fn quirks_test() { - let (mut c, mut b) = setup_environment(); - c.flags.quirks = true.into(); - b.write(0x1feu16, 0x0104u16); + let (cpu, mut bus) = setup_environment(); + bus.write(0x1feu16, 0x0104u16); run_screentest( SuiteTest { program: include_bytes!("chip8-test-suite/bin/chip8-test-suite.ch8"), screen: include_bytes!("screens/chip8-test-suite/quirks.bin"), }, - c, - b, + cpu, + bus, ) }