From 9d3fabe0c3c3c9d9af7edb97163e6b184b832ce7 Mon Sep 17 00:00:00 2001 From: Trevor Merritt Date: Tue, 1 Oct 2024 15:20:53 -0400 Subject: [PATCH] more test suite evolution --- emma/src/chip8/instructions.rs | 399 ++++++++++++++++----------------- emma/src/chip8/sound_timer.rs | 1 + emma/src/chip8/video.rs | 52 ++--- 3 files changed, 215 insertions(+), 237 deletions(-) diff --git a/emma/src/chip8/instructions.rs b/emma/src/chip8/instructions.rs index 5432558..7e56b23 100644 --- a/emma/src/chip8/instructions.rs +++ b/emma/src/chip8/instructions.rs @@ -17,36 +17,98 @@ kk or byte - An 8-bit value, the lowest 8 bits of the instruction #[derive(Debug)] pub enum Chip8CpuInstructions { - SysAddr(i16), // 0x0nnn Exit to System Call - CLS, // * 0x00E0 Clear Screen - RET, // 0x00EE Return from Subroutine - JpAddr(i16), // 0x1nnn Jump to Address - CallAddr(i16), // 0x2nnn Call Subroutine - SeVxByte(i16, i16), // 0x3xkk Skip next instruction if Vx = kk. - SneVxByte(i16, i16), // 0x4xkk Skip next instruction if Vx != kk - SeVxVy(u16, u16), // 0x5xy0 Skip next instruction if Vx == Vy - LdVxByte(u16, u16), // * 0x6xkk Set Vx = kk - AddVxByte(u16, u16), // 0x7xkk Set Vx = Vx + kk - LdVxVy(u16, u16), // 0x8xy0 Set value of Vy in Vx - OrVxVy(u16, u16), // 0x8xy1 Set Vx = Vx OR Vy - AndVxVy(u16, u16), // 0x8xy2 Set Vx = Vx AND Vy - XorVxVy(u16, u16), // 0x8xy3 Set Vx = Vx XOR Vy - AddVxVy(u16, u16), // 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry) - SubVxVy(u16, u16), // 0x8xy5 Set Vx = Vx - Vy (Set VF NOT Borrow) - ShrVxVy(u16, u16), // 0x8xy6 Set Vx = Vx SHR 1 (Shift Rotated Right 1) - SubnVxVy(u16, u16), // 0x8xy7 Set Vx = Vy - Vx (Set VF NOT Borrow) - ShlVxVy(u16, u16), // 0x8xyE Shift Left - SneVxVy(u16, u16), // 0x9xy0 Skip next instruction if Vx != Vy - LdIAddr(u16), // * 0xAnnn VI = nnn - JpV0Addr(u16), // 0xBnnn Jump to nnn+V0 - RndVxByte(u16, u16), // 0xCxkk Vx = random byte AND kk - DrawVxVyNibble(u16, u16, u16), // * 0xDxyn Display N byte sprite starting at Vx to Vy - SkpVx(u16), // 0xE09E Skip next instruction if key in Vx pressed - SnkpVx(u16), // 0xE0A1 Skip next instruction if key in Vx NOT pressed - LdVxDt(u16), // 0xFx07 Set Vx = Delay timer - LdVxK(u16), // 0xFx0A Wait for key, put in Vx + /// 0nnn + /// Exit to System Call at nnn + SysAddr(i16), + /// Clear Screen + CLS, + /// Return from Subroutine + RET, + /// 1nnn + /// Jump to Address nnn + JpAddr(i16), + /// 2nnn + /// Call Subroutine at nnn + CallAddr(i16), + /// 0x3xkk + /// Skip next instruction if Vx == kk + SeVxByte(i16, i16), + /// 4xkk + /// Skip next instruction if Vx != kk + SneVxByte(i16, i16), + /// 5xy0 + /// Skip next instruction if Vx == Vy + SeVxVy(u16, u16), + /// 6xkk + /// Set Vx = kk + LdVxByte(u16, u16), + /// 7xkk + /// Set Vx = Vx + kk + AddVxByte(u16, u16), + /// 8xy0 + /// Set Vx = Vy + LdVxVy(u16, u16), + /// 8xy1 + /// Set Vx = Vx OR Vy + OrVxVy(u16, u16), + /// 8xy2 + /// Set Vx = Vx AND Vy + AndVxVy(u16, u16), + /// 8xy3 + /// Set Vx = Vx XOR Vy + XorVxVy(u16, u16), + /// 8xy4 + /// Set Vx = Vx + Vy + /// Set VF=1 if Carry + AddVxVy(u16, u16), + /// 8xy5 + /// Set Vx = Vx - Vy + /// Set VF=1 if No Borrow + SubVxVy(u16, u16), + /// 8xy6 + /// Set Vx = Vx SHR 1 + ShrVxVy(u16, u16), + /// 8xy7 + /// Set Vx = Vy - Vx + /// Set VF=1 if No Borrow + SubnVxVy(u16, u16), + /// 8xye + /// Set Vx = Vx SHL 1 + ShlVxVy(u16, u16), + /// 9xy0 + /// Skip next instruction if Vx != Vy + SneVxVy(u16, u16), + /// Annn + /// Load I register with NNN + LdIAddr(u16), + /// Bnnn + /// Jump to nnn+V0 + JpV0Addr(u16), + /// Cxkk + /// Set Vx = Random u8 AND kk + RndVxByte(u16, u16), + /// Dxyn + /// Display N byte tall sprite starting at Vx, Vy + DrawVxVyNibble(u16, u16, u16), + /// Ex9E + /// Skip next instruction of key in Vx pressed + SkpVx(u16), + /// ExA1 + /// Skip Next If Key Not Pressed + SnkpVx(u16), + /// Fx07 + /// Set Vx = Dt + LdVxDt(u16), + /// Fx0A + /// Wait for Key to be pressed and store + /// in Vx + LdVxK(u16), + /// Fx15 + /// Load Value in Delay Timer to Vx LdDtVx(u16), // 0xFx15 Set Delay Timer - LdStVx(u16), // 0xFx18 Set Sount Timer + /// Fx18 + /// Set Dt = Vx + LdStVx(u16), AddIVx(u16), // 0xFx1E I = I + Vx LdFVx(u16), // 0xFx29 Set I = Location of sprite for Digit Vx LdBVx(u16), // 0xFx33 Store BCD of Vx in I, I+1, I+2 @@ -163,7 +225,7 @@ impl Chip8CpuInstructions { Chip8CpuInstructions::LdVxI(x_register) => { 0xf065u16 | x_register << 8 } - _ => { + XXXXERRORINSTRUCTION => { 0xffff } } @@ -393,9 +455,7 @@ impl Chip8CpuInstructions { } // 0x4xkk Skip next instruction if Vx != kk Chip8CpuInstructions::SneVxByte(x, byte) => { - let lhs = input.registers.peek(*x as u8); - let rhs = byte.to_be_bytes()[0]; - if lhs == rhs { + if input.registers.peek(*x as u8) != *byte as u8 { input.registers.advance_pc(); } } @@ -410,7 +470,8 @@ impl Chip8CpuInstructions { } // 0x6xkk Set Vx = kk Chip8CpuInstructions::LdVxByte(register, byte) => { - input.registers.poke(*register as u8, *byte as u8); + let byte_value = *byte as u8; + input.registers.poke(*register as u8, byte_value); } // 0x7xkk Set Vx = Vx + kk Chip8CpuInstructions::AddVxByte(vx_register, byte) => { @@ -565,9 +626,14 @@ impl Chip8CpuInstructions { // ExA1 - SKNP Vx // Skip next instruction if key with the value of Vx is not pressed. // - // Checks the keyboard, and if the key corresponding to the value of Vx is currently in the up position, PC is increased by 2. - let key_to_check = input.registers.peek(*x as u8); - let is_pressed = input.keypad.pressed(*x as u8); + + // Checks the keyboard, + // and if the key corresponding to the value of Vx is currently in the up position, + // PC is increased by 2. + let target_key = input.registers.peek(*x as u8); + println!("TESTING REGISTER {x} -> READ {target_key}"); + let is_pressed = input.keypad.pressed(target_key); + println!("KEY STATE = {is_pressed}"); if is_pressed { input.registers.advance_pc(); } @@ -577,8 +643,8 @@ impl Chip8CpuInstructions { // Set Vx = delay timer value. // // The value of DT is placed into Vx. - let value_to_set = input.registers.peek(*x as u8); - input.delay_timer.set_timer(value_to_set as i32); + let value_to_set = input.delay_timer.current(); + input.registers.poke(*x as u8, value_to_set as u8); } Chip8CpuInstructions::LdVxK(x) => { // Fx0A - LD Vx, K @@ -595,7 +661,8 @@ impl Chip8CpuInstructions { input.delay_timer.set_timer(new_time as i32); } Chip8CpuInstructions::LdStVx(new_time) => { - input.sound_timer.set_timer(*new_time as i32); + let new_value = input.registers.peek(*new_time as u8); + input.sound_timer.set_timer(new_value as i32); } Chip8CpuInstructions::AddIVx(x) => { // Fx1E - ADD I, Vx @@ -646,6 +713,7 @@ impl Chip8CpuInstructions { #[cfg(test)] mod test { + use ratatui::crossterm::execute; use super::*; #[test] @@ -756,19 +824,6 @@ mod test { assert_eq!(x.registers.peek_pc(), 0x0AF); } - fn cls_test() { - // * 0x00E0 Clear Screen - // todo: Need to write this - let mut x = Chip8Computer::new(); - - - } - - fn ret_test() { - // 0x00EE Return from Subroutine - // todo: no stack yet. - } - #[test] fn jpaddr_test() { // 0x1nnn Jump to Address @@ -779,11 +834,6 @@ mod test { assert_eq!(x.registers.peek_pc(), 0xABC); } - fn calladdr_test() { - // 0x2nnn Call Subroutine - // todo: no stack - } - // ** test moved up so it can be used later #[test] fn LdVxByte_test() { @@ -857,17 +907,6 @@ mod test { assert_eq!(x.registers.peek_pc(), 0x20C); } - #[test] - fn AddVxByte_test() { - // 0x7xkk Set Vx = Vx + kk - let mut x = Chip8Computer::new(); - Chip8CpuInstructions::LdVxByte(0x01, 0x01).execute(&mut x); - Chip8CpuInstructions::LdVxByte(0x02, 0x02).execute(&mut x); - assert_eq!(x.registers.peek_pc(), 0x204); - Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x); - assert_eq!(x.registers.peek(1), 0x03); - } - #[test] fn LdVxVy_test() { // 0x8xy0 Set value of Vy in Vx @@ -950,24 +989,6 @@ mod test { assert_eq!(x.registers.peek(1), 0); assert_eq!(x.registers.peek_pc(), 0x208) } - /* #[test] - fn SubVxVy_test() { - todo: this test sucks. dont have the borrow concept in here. - Set Vx = Vx - Vy, set VF = NOT borrow. - If Vx > Vy, then VF is set to 1, otherwise 0. - Then Vy is subtracted from Vx, and the results stored in Vx. - let mut x = Chip8Computer::new(); - Chip8CpuInstructions::LdVxByte(1, 0x10).execute(&mut x); - Chip8CpuInstructions::LdVxByte(2, 0x01).execute(&mut x); - Chip8CpuInstructions::LdVxByte(0xf, 0x00).execute(&mut x); - Chip8CpuInstructions::SubVxVy(0x1, 0x2).execute(&mut x); - - assert_eq!(x.registers.peek_pc(), 0x208); - assert_eq!(x.registers.peek(1), 0xF); - assert_eq!(x.registers.peek(0x10), 0); - - } - */ #[test] fn ShrVxVy_test() { @@ -985,82 +1006,38 @@ mod test { assert_eq!(x.registers.peek(0xf), 0); assert_eq!(x.registers.peek_pc(), 0x208); - let mut x = Chip8Computer::new(); + x = Chip8Computer::new(); Chip8CpuInstructions::LdVxByte(0xf, 0x00).execute(&mut x); Chip8CpuInstructions::LdVxByte(0x1, 0b00001001).execute(&mut x); // 0b0000 1001 (0x09) - Chip8CpuInstructions::ShrVxVy(0x1, 0x2).execute(&mut x); // 0b0000 0100 (0x02) (Set) - Chip8CpuInstructions::ShrVxVy(0x1, 0x1).execute(&mut x); - assert_eq!(x.registers.peek(0x1), 0b00000010); - assert_eq!(x.registers.peek(0xf), 0x1); - assert_eq!(x.registers.peek_pc(), 0x208); + Chip8CpuInstructions::ShrVxVy(0x1, 0x2).execute(&mut x); // 0b0000 0010 (0x02) (Set) + assert_eq!(x.registers.peek(1), 0x04); + assert_eq!(x.registers.peek(0xf), 1); + assert_eq!(x.registers.peek_pc(), 0x206); } - - #[test] - fn SneVxVy_test() { - // 9xy0 - SNE Vx, Vy - // Skip next instruction if Vx != Vy. - // - // The values of Vx and Vy are compared, and if they are not equal, the program counter is increased by 2. - let mut x = Chip8Computer::new(); - Chip8CpuInstructions::LdVxByte(0x01, 0xab).execute(&mut x); - Chip8CpuInstructions::LdVxByte(0x02, 0xba).execute(&mut x); - // they are not the same. we should skip. - assert_eq!(x.registers.peek_pc(), 0x204); - Chip8CpuInstructions::SneVxVy(0x01, 0x02).execute(&mut x); - assert_eq!(x.registers.peek_pc(), 0x208); - - Chip8CpuInstructions::LdVxByte(0x02, 0xab).execute(&mut x); - Chip8CpuInstructions::SneVxVy(0x01, 0x02).execute(&mut x); - assert_eq!(x.registers.peek_pc(), 0x20C); - } - #[test] fn LdiAddr_test() { - // Annn - LD I, addr - // Set I = nnn. - // - // The value of register I is set to nnn. let mut x = Chip8Computer::new(); - - let value_for_memory = 0xbe; - // load the value into V0 - Chip8CpuInstructions::LdIAddr(0xfab).execute(&mut x); - assert_eq!(x.registers.peek_i(), 0xfab); - } - - fn JpV0Addr_test() { - // Bnnn - JP V0, addr - // Jump to location nnn + V0. - // - // The program counter is set to nnn plus the value of V0. - + Chip8CpuInstructions::LdIAddr(0x123).execute(&mut x); + assert_eq!(x.registers.peek_i(), 0x123); + assert_eq!(x.registers.peek_pc(), 0x202); } #[test] - fn RndVxByte_test() { + fn JpV0Addr_test() { let mut x = Chip8Computer::new(); - - // generate random number masked by 0xF0; - let mask = 0xF0u8; - Chip8CpuInstructions::RndVxByte(0x0, mask as u16).execute(&mut x); - let register_value = x.registers.peek(0x0); - assert!(register_value < mask); - - // generate random number masked by 0x0F; - let mask2 = 0x0Fu8; - Chip8CpuInstructions::RndVxByte(0x1, mask2 as u16).execute(&mut x); - let register_value = x.registers.peek(0x1); - assert!(register_value < mask); + /// jump to I + nnn + Chip8CpuInstructions::LdVxByte(0x0, 0xFF).execute(&mut x); + Chip8CpuInstructions::JpV0Addr(0x100).execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0x1FF); } - - fn DrawVxVyNibble_test() {} - - fn SkpVx_test() { - // skip if key pressed - } - +// #[test] fn SnKpVx_test() { // skip key not pressed + let mut x = Chip8Computer::new(); + x.keypad.push_key(2); + Chip8CpuInstructions::LdVxByte(0x1, 0x02); + Chip8CpuInstructions::SnkpVx(2).execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0x204); } #[test] @@ -1069,72 +1046,84 @@ mod test { let mut x = Chip8Computer::new(); // set the value we want in the timer to V0... - Chip8CpuInstructions::LdVxByte(0x0, 0x10).execute(&mut x); + Chip8CpuInstructions::LdVxByte(0x1, 0x10).execute(&mut x); + Chip8CpuInstructions::LdDtVx(0x1).execute(&mut x); // ...then tell the CPU to use that value for the timer. - Chip8CpuInstructions::LdVxDt(0x0).execute(&mut x); - x.delay_timer.tick(); - x.delay_timer.tick(); - x.delay_timer.tick(); - assert_eq!(x.delay_timer.current(), 0xd); - for i in 0..0x10 { - x.delay_timer.tick(); - } - assert_eq!(x.delay_timer.current(), 0x00); - x.delay_timer.tick(); - assert_eq!(x.delay_timer.current(), 0x00); - } + Chip8CpuInstructions::LdVxDt(0x1).execute(&mut x); - fn LdVxK_test() { - // Wait for a key press, store the value of the key in Vx. - // All execution stops until a key is pressed, then the value of that key is stored in Vx. + let new_reg_value = x.registers.peek(0x1); + assert_eq!(new_reg_value, 0x1); + } + #[test] + fn cls_test() { + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::CLS.execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0x202); } #[test] - fn LdStVx_test() { - // sound timer setting + fn skip_next_instruction_ne_text() { let mut x = Chip8Computer::new(); - Chip8CpuInstructions::LdVxByte(0x1, 0x10).execute(&mut x); - Chip8CpuInstructions::LdStVx(0x10).execute(&mut x); - - // tick from 0x8 to 0x1 - for i in 0..6 { x.sound_timer.tick(); } - - assert_eq!(x.sound_timer.current(), 0xA); + Chip8CpuInstructions::LdVxByte(0x1, 0xf0).execute(&mut x); + // 202 + Chip8CpuInstructions::SneVxByte(0x1, 0x0f).execute(&mut x); + // 204+2 + assert_eq!(x.registers.peek_pc(), 0x206); + Chip8CpuInstructions::SneVxByte(0x1, 0xf0).execute(&mut x); + // 208 + assert_eq!(x.registers.peek_pc(), 0x208); } - fn LdIVx_test() { - // Store registers V0 through Vx in memory starting at location I. - // - // The interpreter copies the values of registers V0 through Vx - // into memory, starting at the address in I. - + #[test] + fn lddtvx_test() { + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdDtVx(0x10).execute(&mut x); + assert_eq!(x.delay_timer.current(), 0x10); + x.delay_timer.tick(); + x.delay_timer.tick(); + assert_eq!(x.delay_timer.current(), 0x0E); } - fn LdVxI_test() {} - // Read registers V0 through Vx from memory starting at location I. - // - // The interpreter reads values from memory starting at location I into registers V0 through Vx. + #[test] + fn addivx_test() { + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdIAddr(0xabc).execute(&mut x); + Chip8CpuInstructions::LdVxByte(0x0, 0x10).execute(&mut x); + Chip8CpuInstructions::AddIVx(0x0).execute(&mut x); + assert_eq!(x.registers.peek_i(), 0xacc); + } - /* - #[test] - fn LdDtVx_test() { + #[test] + fn ldstvt_test() { + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(0x01, 0xf0).execute(&mut x); + Chip8CpuInstructions::LdStVx(0x01).execute(&mut x); + assert_eq!(x.sound_timer.current(), 0xf0); + x.sound_timer.tick(); + x.sound_timer.tick(); + x.sound_timer.tick(); + assert_eq!(x.sound_timer.current(), 0xed); + } - // delay timer setting - let mut x = Chip8Computer::new(); + #[test] + fn rnd_vx_byte_text() { + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::RndVxByte(0x1, 0x0f).execute(&mut x); + let new_value = x.registers.peek(0x1); + assert!(new_value < 0x10); + } +/* + #[test] + fn skp_vx_test() { + let mut x = Chip8Computer::new(); + x.keypad.push_key(0x1); + Chip8CpuInstructions::LdVxByte(0x1, 0x1).execute(&mut x); + Chip8CpuInstructions::SkpVx(0x1).execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0x208); + x.keypad.release_key(0x1); + Chip8CpuInstructions::SkpVx(0x1).execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0x20A); + } - // lets set our delay timer... - Chip8CpuInstructions::LdVxByte(0x0, 0x80).execute(&mut x); - Chip8CpuInstructions::LdDtVx(0x0).execute(&mut x); - - // now that we have our timer set to 0x80 we should tick it 0x10 times - // so we are then down to 0x70 - for i in 0..0x10 { - x.delay_timer.tick(); - } - // Then tell the CPU to copy that timer over into our V0 - Chip8CpuInstructions::LdVxK(0x0).execute(&mut x); - let register_value = x.registers.peek(0); - // assert_eq!(register_value, 0x70); - } - */ + */ } diff --git a/emma/src/chip8/sound_timer.rs b/emma/src/chip8/sound_timer.rs index a2bb6bd..c8c62c8 100644 --- a/emma/src/chip8/sound_timer.rs +++ b/emma/src/chip8/sound_timer.rs @@ -16,6 +16,7 @@ impl SoundTimer { } } pub fn set_timer(&mut self, new_value: i32) { + println!("SETTING SOUND TIMER TO {new_value}"); self.counter = new_value } diff --git a/emma/src/chip8/video.rs b/emma/src/chip8/video.rs index 87c63df..6cd50dc 100644 --- a/emma/src/chip8/video.rs +++ b/emma/src/chip8/video.rs @@ -10,25 +10,13 @@ pub struct Chip8Video { } impl Chip8Video { - pub fn as_64bit(&self) -> Vec { - let mut to_return = vec![]; - for row_in_video in 0..32 { - let mut working_row = 0u64; - for bit_in_video in 0..64 { - let data_offset = row_in_video * 64 + bit_in_video; - let to_convert = self.memory[data_offset]; - let shifted_bit = if to_convert { - 1 << bit_in_video - } else { 0 }; - working_row = working_row | shifted_bit; - } - to_return.push(working_row); + pub fn cls(&mut self) { + for i in 0..CHIP8_VIDEO_MEMORY { + self.memory[i] = false; } - to_return } - pub fn new(initial_configuration: [bool; CHIP8_VIDEO_MEMORY]) -> Self { Self { memory: initial_configuration @@ -172,22 +160,22 @@ mod test { } assert_eq!(x.format_as_string(), expected); } - - #[test] - fn poke_sprite() { - let mut expected = String::new(); - let to_poke = [ - 0b11001100, - 0b00110011, - 0b11001100, - 0b00110011 - ]; - - // Position at 4,10 - // 5,10 - // 6,10 - // 7,10 - let start_address = (4 * 64) + 10; + fn cls() { + let mut initial_memory = [false; CHIP8_VIDEO_MEMORY]; + let mut ws = String::new(); + // set our checkerboard + for cbr in 0..32 { + for cbc in 0..64 { + let dof = cbr * 64 + cbc; + if (dof as i32 % 2) == 0 { + initial_memory[dof] = true; + } + ws += " "; + } + ws += "\n"; + } + let mut set_x = Chip8Video::new(initial_memory); + set_x.cls(); + assert_eq!(set_x.format_as_string(), ws); } - } \ No newline at end of file