cpu.rs: Refactor instruction implementations

- Use rust standard implementations of overflowing arithmetic
- Make draw more concise and readable
This commit is contained in:
John 2023-03-25 18:14:36 -05:00
parent 5159afa3fd
commit 27ac674616

View File

@ -304,7 +304,6 @@ impl CPU {
_ => self.unimplemented(opcode), _ => self.unimplemented(opcode),
}, },
_ => unimplemented!("Extracted nibble from byte, got >nibble?"), _ => unimplemented!("Extracted nibble from byte, got >nibble?"),
} }
let elapsed = time.elapsed(); let elapsed = time.elapsed();
// Print opcode disassembly: // Print opcode disassembly:
@ -370,7 +369,12 @@ impl Default for CPU {
} }
} }
// private implementation // | 0aaa | Issues a "System call" (ML routine)
//
// |opcode| effect |
// |------|------------------------------------|
// | 00e0 | Clear screen memory to all 0 |
// | 00ee | Return from subroutine |
impl CPU { impl CPU {
/// Unused instructions /// Unused instructions
#[inline] #[inline]
@ -397,11 +401,23 @@ impl CPU {
self.sp = self.sp.wrapping_add(2); self.sp = self.sp.wrapping_add(2);
self.pc = bus.read(self.sp); self.pc = bus.read(self.sp);
} }
}
// | 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] #[inline]
fn jump(&mut self, a: Adr) { fn jump(&mut self, a: Adr) {
// jump to self == halt
if a.wrapping_add(2) == self.pc {
self.flags.pause = true;
}
self.pc = a; self.pc = 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] #[inline]
fn call(&mut self, a: Adr, bus: &mut impl Write<u16>) { fn call(&mut self, a: Adr, bus: &mut impl Write<u16>) {
@ -409,6 +425,10 @@ impl CPU {
self.sp = self.sp.wrapping_sub(2); self.sp = self.sp.wrapping_sub(2);
self.pc = a; self.pc = a;
} }
}
// | 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] #[inline]
fn skip_if_x_equal_byte(&mut self, x: Reg, b: u8) { fn skip_if_x_equal_byte(&mut self, x: Reg, b: u8) {
@ -416,6 +436,10 @@ impl CPU {
self.pc = self.pc.wrapping_add(2); self.pc = self.pc.wrapping_add(2);
} }
} }
}
// | 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] #[inline]
fn skip_if_x_not_equal_byte(&mut self, x: Reg, b: u8) { fn skip_if_x_not_equal_byte(&mut self, x: Reg, b: u8) {
@ -423,6 +447,14 @@ impl CPU {
self.pc = self.pc.wrapping_add(2); self.pc = self.pc.wrapping_add(2);
} }
} }
}
// | 5xyn | Performs a register-register comparison
//
// |opcode| effect |
// |------|------------------------------------|
// | 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] #[inline]
fn skip_if_x_equal_y(&mut self, x: Reg, y: Reg) { fn skip_if_x_equal_y(&mut self, x: Reg, y: Reg) {
@ -430,52 +462,85 @@ impl CPU {
self.pc = self.pc.wrapping_add(2); self.pc = self.pc.wrapping_add(2);
} }
} }
}
// | 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] #[inline]
fn load_immediate(&mut self, x: Reg, b: u8) { fn load_immediate(&mut self, x: Reg, b: u8) {
self.v[x] = b; self.v[x] = b;
} }
}
// | 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] #[inline]
fn add_immediate(&mut self, x: Reg, b: u8) { fn add_immediate(&mut self, x: Reg, b: u8) {
self.v[x] = self.v[x].wrapping_add(b); self.v[x] = self.v[x].wrapping_add(b);
} }
/// Set the carry register (vF) after math
#[inline]
fn set_carry(&mut self, x: Reg, y: Reg, f: fn(u16, u16) -> u16, inv: bool) -> u8 {
let sum = f(self.v[x] as u16, self.v[y] as u16);
self.v[0xf] = if (sum & 0xff00 != 0) ^ inv { 1 } else { 0 };
(sum & 0xff) as u8
} }
// | 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 |
impl CPU {
/// 8xy0: Loads the value of y into x /// 8xy0: Loads the value of y into x
#[inline] #[inline]
fn load_y_into_x(&mut self, x: Reg, y: Reg) { fn load_y_into_x(&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
#[inline] #[inline]
fn x_orequals_y(&mut self, x: Reg, y: Reg) { fn x_orequals_y(&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;
}
} }
/// 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
#[inline] #[inline]
fn x_andequals_y(&mut self, x: Reg, y: Reg) { fn x_andequals_y(&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;
}
} }
/// 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
#[inline] #[inline]
fn x_xorequals_y(&mut self, x: Reg, y: Reg) { fn x_xorequals_y(&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;
}
} }
/// 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] #[inline]
fn x_addequals_y(&mut self, x: Reg, y: Reg) { fn x_addequals_y(&mut self, x: Reg, y: Reg) {
self.v[x] = self.set_carry(x, y, u16::wrapping_add, false); 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] #[inline]
fn x_subequals_y(&mut self, x: Reg, y: Reg) { fn x_subequals_y(&mut self, x: Reg, y: Reg) {
self.v[x] = self.set_carry(x, y, u16::wrapping_sub, true); 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
#[inline] #[inline]
@ -487,7 +552,9 @@ impl CPU {
/// 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] #[inline]
fn backwards_subtract(&mut self, x: Reg, y: Reg) { fn backwards_subtract(&mut self, x: Reg, y: Reg) {
self.v[x] = self.set_carry(y, x, u16::wrapping_sub, true); let carry;
(self.v[x], carry) = self.v[y].overflowing_sub(self.v[x]);
self.v[0xf] = (!carry).into();
} }
/// 8X_E: Performs bitwise left shift of vX /// 8X_E: Performs bitwise left shift of vX
#[inline] #[inline]
@ -496,7 +563,14 @@ impl CPU {
self.v[x] <<= 1; self.v[x] <<= 1;
self.v[0xf] = shift_out; self.v[0xf] = shift_out;
} }
}
// | 9xyn | Performs a register-register comparison
//
// |opcode| effect |
// |------|------------------------------------|
// | 9XY0 | Skip next instruction if vX != vY |
impl CPU {
/// 9xy0: Skip next instruction if X != y /// 9xy0: Skip next instruction if X != y
#[inline] #[inline]
fn skip_if_x_not_equal_y(&mut self, x: Reg, y: Reg) { fn skip_if_x_not_equal_y(&mut self, x: Reg, y: Reg) {
@ -504,33 +578,51 @@ impl CPU {
self.pc = self.pc.wrapping_add(2); self.pc = self.pc.wrapping_add(2);
} }
} }
}
// | Aaaa | Load address #a into register I
impl CPU {
/// Aadr: Load address #adr into register I /// Aadr: Load address #adr into register I
#[inline] #[inline]
fn load_indirect_register(&mut self, a: Adr) { fn load_indirect_register(&mut self, a: Adr) {
self.i = a; self.i = a;
} }
}
// | Baaa | Jump to &adr + v0
impl CPU {
/// Badr: Jump to &adr + v0 /// Badr: Jump to &adr + v0
#[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); self.pc = a.wrapping_add(self.v[0] as Adr);
} }
}
// | 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] #[inline]
fn rand(&mut self, x: Reg, b: u8) { fn rand(&mut self, x: Reg, b: u8) {
self.v[x] = random::<u8>() & b; self.v[x] = random::<u8>() & b;
} }
}
// | 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)
#[inline] #[inline]
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) {
// println!("{}", format_args!("draw\t#{n:x}, (x: {:x}, y: {:x})", self.v[x], self.v[y]).green()); let (x, y) = (self.v[x] as u16, self.v[y] as u16);
let (x, y) = (self.v[x], self.v[y]);
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 {
return;
}
// Calculate the lower bound address based on the X,Y position on the screen // Calculate the lower bound address based on the X,Y position on the screen
let addr = ((y as u16 + byte) * 8) + (x / 8) as u16 + self.screen; let addr = (y + byte) * 8 + (x & 0x3f) / 8 + self.screen;
// Read a byte of sprite data into a u16, and shift it x % 8 bits // Read a byte of sprite data into a u16, and shift it x % 8 bits
let sprite: u8 = bus.read(self.i + byte); let sprite: u8 = bus.read(self.i + byte);
let sprite = (sprite as u16) << 1 + (7 - x % 8); let sprite = (sprite as u16) << 8 - (x & 7) & if x % 64 > 56 { 0xff00 } else { 0xffff };
// Read a u16 from the bus containing the two bytes which might need to be updated // Read a u16 from the bus containing the two bytes which might need to be updated
let mut screen: u16 = bus.read(addr); let mut screen: u16 = bus.read(addr);
// Save the bits-toggled-off flag if necessary // Save the bits-toggled-off flag if necessary
@ -539,10 +631,19 @@ impl CPU {
} }
// Update the screen word by XORing the sprite byte // Update the screen word by XORing the sprite byte
screen ^= sprite; screen ^= sprite;
// Save the result back to the screen // Save the result to the screen
bus.write(addr, screen); bus.write(addr, screen);
} }
} }
}
// | Exbb | Skips instruction on value of keypress
//
// |opcode| effect |
// |------|------------------------------------|
// | eX9e | Skip next instruction if key == #X |
// | eXa1 | Skip next instruction if key != #X |
impl CPU {
/// Ex9E: Skip next instruction if key == #X /// Ex9E: Skip next instruction if key == #X
#[inline] #[inline]
fn skip_if_key_equals_x(&mut self, x: Reg) { fn skip_if_key_equals_x(&mut self, x: Reg) {
@ -559,6 +660,22 @@ impl CPU {
self.pc += 2; self.pc += 2;
} }
} }
}
// | Fxbb | Performs IO
//
// |opcode| effect |
// |------|------------------------------------|
// | fX07 | Set vX to value in delay timer |
// | fX0a | Wait for input, store in vX m |
// | fX15 | Set sound timer to the value in vX |
// | fX18 | set delay timer to the value in vX |
// | fX1e | Add x 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 /// ```py
/// vX = DT /// vX = DT