Quirks: Make the Cosmac VIP behavior default.
This commit is contained in:
		| @@ -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, | ||||
|   | ||||
							
								
								
									
										259
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										259
									
								
								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<u16>) { | ||||
|         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<u16>) { | ||||
|         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::<u8>() & 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; | ||||
|         } | ||||
|     } | ||||
|   | ||||
							
								
								
									
										299
									
								
								src/cpu/tests.rs
									
									
									
									
									
								
							
							
						
						
									
										299
									
								
								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, | ||||
|                 }, | ||||
|             }, | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user