From 7b360612685fcd28bd7e4365cc8f720cb9639f4f Mon Sep 17 00:00:00 2001 From: Trevor Merritt Date: Wed, 25 Sep 2024 11:26:09 -0400 Subject: [PATCH] registers better integrated more tests and docs to the executors --- emma/src/bin/emmagui.rs | 2 +- emma/src/bin/support/emmagui_support.rs | 6 +- emma/src/chip8/computer.rs | 3 + emma/src/chip8/instructions.rs | 323 ++++++++++++++++++++++-- emma/src/chip8/keypad.rs | 9 + emma/src/chip8/registers.rs | 2 +- 6 files changed, 313 insertions(+), 32 deletions(-) diff --git a/emma/src/bin/emmagui.rs b/emma/src/bin/emmagui.rs index a7b15ab..5476f89 100644 --- a/emma/src/bin/emmagui.rs +++ b/emma/src/bin/emmagui.rs @@ -63,7 +63,7 @@ fn main() { EmmaGui::system_controls(&mut system, ui); EmmaGui::registers_view(&system, ui); - let active_instruction = system.pc; + let active_instruction = system.registers.peek_pc(); EmmaGui::hex_memory_display(system.memory.clone(), (0x100, 0x10), active_instruction as i16, ui); EmmaGui::video_display(&system, ui); diff --git a/emma/src/bin/support/emmagui_support.rs b/emma/src/bin/support/emmagui_support.rs index 6781c5b..fd2f90d 100644 --- a/emma/src/bin/support/emmagui_support.rs +++ b/emma/src/bin/support/emmagui_support.rs @@ -69,18 +69,18 @@ impl EmmaGui { .build(|| { ui.text("Registers"); for i in 0..0x10 { - ui.text(format!("V{:X}: {}", i, system.registers[i as usize])); + ui.text(format!("V{:X}: {}", i, system.registers.peek(i))); if i != 7 { ui.same_line(); } } ui.text(""); - ui.text(format!("I: {:03X}", system.i_register)); + ui.text(format!("I: {:03X}", system.registers.peek_i())); ui.same_line(); ui.text(format!("ST: {:02X}", system.sound_timer.current())); ui.same_line(); ui.text(format!("DT: {:02X}", system.delay_timer.current())); - ui.text(format!("PC: {:02X}", system.pc)); + ui.text(format!("PC: {:02X}", system.registers.peek_pc())); ui.text(format!("SP: {:02X}", system.sp)); }); } diff --git a/emma/src/chip8/computer.rs b/emma/src/chip8/computer.rs index e436056..f471933 100644 --- a/emma/src/chip8/computer.rs +++ b/emma/src/chip8/computer.rs @@ -1,6 +1,7 @@ use log::{debug, error}; use crate::chip8::delay_timer::DelayTimer; use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION; +use crate::chip8::keypad::Keypad; use crate::chip8::registers::Chip8Registers; use crate::chip8::sound_timer::SoundTimer; use crate::chip8::util::InstructionUtil; @@ -19,6 +20,7 @@ pub struct Chip8Computer { pub delay_timer: DelayTimer, pub video_memory: Chip8Video, pub state: Chip8CpuStates, + pub keypad: Keypad } impl Default for Chip8Computer { @@ -31,6 +33,7 @@ impl Default for Chip8Computer { sound_timer: SoundTimer::new(), delay_timer: DelayTimer::new(), state: Chip8CpuStates::WaitingForInstruction, + keypad: Keypad::default() } } } diff --git a/emma/src/chip8/instructions.rs b/emma/src/chip8/instructions.rs index 70feaad..41eb1cc 100644 --- a/emma/src/chip8/instructions.rs +++ b/emma/src/chip8/instructions.rs @@ -436,21 +436,62 @@ impl Chip8CpuInstructions { let lhs = input.registers.peek(*x as u8); let rhs = input.registers.peek(*y as u8); - let working =(lhs + rhs) as i16; + let working = (lhs as i16 + rhs as i16) as i16; if working > 255 { input.registers.poke(0xf, 0x01); } input.registers.poke(*x as u8, working as u8); } Chip8CpuInstructions::SubVxVy(x,y) => { + // 8xy5 - SUB Vx, Vy + // 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 lhs = input.registers.peek(*x as u8); let rhs = input.registers.peek(*y as u8); input.registers.poke(*x as u8, lhs - rhs); } - Chip8CpuInstructions::ShrVxVy(_, _) => {} - Chip8CpuInstructions::SubnVxVy(_, _) => {} - Chip8CpuInstructions::ShlVxVy(_, _) => {} + Chip8CpuInstructions::ShrVxVy(x, y) => { + // 8xy6 - SHR Vx {, Vy} + // Set Vx = Vx SHR 1. + // + // If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2. + let initial_value = input.registers.peek(*x as u8); + if 0xb1 & initial_value == 1 { + input.registers.poke(0xf, 1); + } + input.registers(x, initial_value.rotate_left(1)); + } + Chip8CpuInstructions::SubnVxVy(x,y) => { + // 8xy7 - SUBN Vx, Vy + // Set Vx = Vy - Vx, set VF = NOT borrow. + // + // If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is subtracted from Vy, and the results stored in Vx. + let y_register = input.registers.peek(y); + let x_register = input.registers.peek(x); + let new_value = if y_register > x_register { 1 } else { 0 }; + input.registers.poke(0xf, new_value); + input.registers.poke(x, x_register - y_register); + } + + Chip8CpuInstructions::ShlVxVy(x, y) => { + // 8xyE - SHL Vx {, Vy} + // Set Vx = Vx SHL 1. + // + // If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2. + + let initial_value = input.registers.peek(*x as u8); + if 0x80 & initial_value == 0x80 { + input.registers.poke(0xf, 1); + } + input.registers(x, initial_value.rotate_left(1)); + } Chip8CpuInstructions::SneVxVy(vx_register, vy_register) => { + // 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 x_reg_value = input.registers.peek(*vx_register as u8); let y_reg_value = input.registers.peek(*vy_register as u8); if x_reg_value != y_reg_value { @@ -458,15 +499,40 @@ impl Chip8CpuInstructions { } } Chip8CpuInstructions::LdIAddr(new_index) => { + // Annn - LD I, addr + // Set I = nnn. + // + // The value of register I is set to nnn. input.registers.poke_i(input.registers.peek(*new_index as u8) as u16); } // 0xBnnn Jump to nnn+V0 - Chip8CpuInstructions::JpV0Addr(_) => {} + Chip8CpuInstructions::JpV0Addr(addr) => { + // Bnnn - JP V0, addr + // Jump to location nnn + V0. + // + // The program counter is set to nnn plus the value of V0. + let x_reg = input.registers.peek(0); + input.registers.poke_pc(x_reg + addr); + } Chip8CpuInstructions::RndVxByte(x, byte) => { + // Cxkk - RND Vx, byte + // Set Vx = random byte AND kk. + // + // The interpreter generates a random number from 0 to 255, which is then ANDed with the value kk. The results are stored in Vx. See instruction 8xy2 for more information on AND. + let new_value: u8 = random() ; input.registers.poke(*x as u8, (new_value & *byte as u8)) } Chip8CpuInstructions::DrawVxVyNibble(x, y, n) => { + // Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. + // The interpreter reads n bytes from memory, starting at the address stored in I. + // These bytes are then displayed as sprites on screen at coordinates (Vx, Vy). + // Sprites are XORed onto the existing screen. + // If this causes any pixels to be erased, VF is set to 1, + // otherwise it is set to 0. + // If the sprite is positioned so part of it is outside the coordinates of the display, + // it wraps around to the opposite side of the screen. + // // read nibble bytes from memory starting at I let mut did_change: bool = false; @@ -483,19 +549,89 @@ impl Chip8CpuInstructions { input.registers.poke(0x10, 0u8); } } - Chip8CpuInstructions::SkpVx(_) => {} - Chip8CpuInstructions::SnkpVx(_) => {} - Chip8CpuInstructions::LdVxDt(_) => {} - Chip8CpuInstructions::LdVxK(_) => {} - Chip8CpuInstructions::LdDtVx(_) => {} - Chip8CpuInstructions::LdStVx(_) => {} - Chip8CpuInstructions::AddIVx(_) => {} - Chip8CpuInstructions::LdFVx(_) => {} - Chip8CpuInstructions::LdBVx(_) => {} - Chip8CpuInstructions::LdIVx(x) => { - input.registers.poke(*x as u8, input.registers.peek_i() as u8); + Chip8CpuInstructions::SkpVx(x) => { + // Ex9E - SKP Vx + // Skip next instruction if key with the value of Vx is pressed. + // + // Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2. + let key_to_check = input.registers.peek(x); + + + } + Chip8CpuInstructions::SnkpVx(x) => { + + // 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. + + + } + Chip8CpuInstructions::LdVxDt(x) => { + // Fx07 - LD Vx, DT + // Set Vx = delay timer value. + // + // The value of DT is placed into Vx. + input.registers.poke(x, input.delay_timer.current()); + } + Chip8CpuInstructions::LdVxK(x) => { + // Fx0A - LD Vx, K + // 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. + } + Chip8CpuInstructions::LdDtVx(new_time) => { + // Fx15 - LD DT, Vx + // Set delay timer = Vx. + // + // DT is set equal to the value of Vx. + input.delay_timer.set_timer(new_time); + } + Chip8CpuInstructions::LdStVx(new_time) => { + input.sound_timer.set_timer(new_time); + } + Chip8CpuInstructions::AddIVx(x) => { + // Fx1E - ADD I, Vx + // Set I = I + Vx. + // + // The values of I and Vx are added, and the results are stored in I. + let base = input.registers.peek_i(); + let x_value = input.registers.peek(x); + input.registers.poke_i( base + x_value); + } + Chip8CpuInstructions::LdFVx(x) => { + // Fx29 - LD F, Vx + // Set I = location of sprite for digit Vx. + // + // The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font. + + } + Chip8CpuInstructions::LdBVx(x) => { + // Fx33 - LD B, Vx + // Store BCD representation of Vx in memory locations I, I+1, and I+2. + // + // The interpreter takes the decimal value of Vx, and places the hundreds digit in memory at location in I, the tens digit at location I+1, and the ones digit at location I+2. + + } + Chip8CpuInstructions::LdIVx(x) => { + // 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. + let offset = input.registers.peek_i(); + for i in 0..x { + input.memory.poke(offset + i, input.registers.peek(i)); + } + } + Chip8CpuInstructions::LdVxI(x) => { + // 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. + let offset = input.registers.peek_i(); + let num_loops = input.registers.peek(x); + for index in 0..num_loops { + input.registers.poke(index, input.memory.peek(index + offset)); + } } - Chip8CpuInstructions::LdVxI(_) => {} Chip8CpuInstructions::XXXXERRORINSTRUCTION => {} }; *input @@ -513,7 +649,7 @@ mod test { } #[test] - fn encoder_test() { + fn encode_decode_test() { assert_eq!(Chip8CpuInstructions::CLS.encode(), 0x00E0); assert_eq!(Chip8CpuInstructions::RET.encode(), 0x00EE); assert_eq!(Chip8CpuInstructions::SysAddr(0x123).encode(), 0x0123); @@ -549,10 +685,6 @@ mod test { assert_eq!(Chip8CpuInstructions::LdBVx(0xd).encode(), 0xfd33); assert_eq!(Chip8CpuInstructions::LdIVx(0xe).encode(), 0xfe55); assert_eq!(Chip8CpuInstructions::LdVxI(0x3).encode(), 0xf365); - } - - #[test] - fn decoder_test() { assert!(matches!( Chip8CpuInstructions::decode(0x00E0u16), Chip8CpuInstructions::CLS)); assert!(matches!( Chip8CpuInstructions::decode(0x00EEu16), Chip8CpuInstructions::RET)); assert!(matches!(Chip8CpuInstructions::decode(0x0123), Chip8CpuInstructions::SysAddr(0x123))); @@ -703,20 +835,157 @@ mod test { #[test] fn AddVxByte_test() { // 0x7xkk Set Vx = Vx + kk - let x = Chip8Computer::new(); + 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 + 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); + assert_eq!(x.registers.peek(1), 0x01); + Chip8CpuInstructions::LdVxVy(0x01, 0x02).execute(&mut x); + assert_eq!(x.registers.peek(1), 0x02); + assert_eq!(x.registers.peek_pc(), 0x206); } #[test] - fn LdVxVy_test() {} + fn OrVxVy_test() { + // 0x8xy1 Set Vx = Vx OR Vy + // 0b0101 0000 (0x50) + // | 0b0000 1010 (0x0A) + // 0b0101 1010 (0x5A) + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(1, 0x50).execute(&mut x); + Chip8CpuInstructions::LdVxByte(2, 0x0A).execute(&mut x); + Chip8CpuInstructions::OrVxVy(1,2).execute(&mut x); + assert_eq!(x.registers.peek(1), 0x5A); + assert_eq!(x.registers.peek_pc(), 0x206); + } #[test] - fn OrVxVy_test() {} + fn AndVxVy_test() { + // 0x8xy2 Set Vx = Vx AND Vy + // 0b1111 1100 (0xFC) + // & 0b1100 1010 (0xCA) + // 0b1100 1000 (0xC8) + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(1, 0xFC).execute(&mut x); + Chip8CpuInstructions::LdVxByte(2, 0xCA).execute(&mut x); + Chip8CpuInstructions::AndVxVy(1,2).execute(&mut x); + assert_eq!(x.registers.peek(1), 0xC8); + assert_eq!(x.registers.peek_pc(), 0x206); + } #[test] - fn AndVxVy_test() {} + fn XorVxVy_test() { + // 0x8xy3 Set Vx = Vx XOR Vy + // 0b1111 1100 (0xFC) + // ^ 0b1100 1010 (0xCA) + // 0b0011 0110 (0x36) + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(1, 0xFC).execute(&mut x); + Chip8CpuInstructions::LdVxByte(2, 0xCA).execute(&mut x); + Chip8CpuInstructions::XorVxVy(1,2).execute(&mut x); + assert_eq!(x.registers.peek(1), 0x36); + assert_eq!(x.registers.peek_pc(), 0x206); + } #[test] - fn XorVxVy_test() {} + fn AddVxVy_test() { + // 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry) + // T1 T2: Judgement Test + // 0x01 0xFF + // + 0x01 0x01 + // 0x02 F0 0x00 F1 + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(0xf, 0x00).execute(&mut x); + Chip8CpuInstructions::LdVxByte(1, 0x01).execute(&mut x); + Chip8CpuInstructions::LdVxByte(2, 0x01).execute(&mut x); + Chip8CpuInstructions::AddVxVy(1, 2).execute(&mut x); + // T1 + assert_eq!(x.registers.peek(0xf), 0); + assert_eq!(x.registers.peek(1), 2); + assert_eq!(x.registers.peek_pc(), 0x208); + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(0xf, 0x00).execute(&mut x); + Chip8CpuInstructions::LdVxByte(0x1, 0xff).execute(&mut x); + Chip8CpuInstructions::LdVxByte(0x2, 0x01).execute(&mut x); + Chip8CpuInstructions::AddVxVy(1,2).execute(&mut x); + // T2 + assert_eq!(x.registers.peek(0xf), 1); + 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); + + } + fn ShrVxVy_test() { + /* + Set Vx = Vx SHR 1. + + If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2. + */ + let x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(0xf, 0x00); + Chip8CpuInstructions::LdVxByte(0x1, 0x08); // 0b0000 1000 (0x08) + Chip8CpuInstructions::LdVxByte(0x2, 0x2); + Chip8CpuInstructions::ShrVxVy(0x1, 0x2); // 0b0000 0010 (0x02) (Not Set) + assert_eq!(x.registers.peek(1), 0x02); + assert_eq!(x.registers.peek(0xf), 0); + assert_eq!(x.registers.peek_pc(), 0x206); + + let x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(0xf, 0x00); + Chip8CpuInstructions::LdVxByte(0x1, 0x09); // 0b0000 1001 (0x09) + Chip8CpuInstructions::LdVxByte(0x2, 0x2); + Chip8CpuInstructions::ShrVxVy(0x1, 0x2); // 0b0000 0010 (0x02) (Set) + assert_eq!(x.registers.peek(1), 0x02); + assert_eq!(x.registers.peek(0xf), 1); + assert_eq!(x.registers.peek_pc(), 0x206); + } + fn SneVxVy_test() {} + fn LdiAddr_test() {} + fn JpV0Addr_test() {} + fn RndVxByte_test() {} + fn DrawVxVyNibble_test() {} + fn SkpVx_test() { + + } + fn SnKpVx_test() { + + } + fn LdVxDt() { + + } + + fn LdVxK_test() {} + fn LdStVx_test() {} + fn LdIVx_test() {} + fn LdVxI_test() {} } \ No newline at end of file diff --git a/emma/src/chip8/keypad.rs b/emma/src/chip8/keypad.rs index 3d36b24..90d3ce4 100644 --- a/emma/src/chip8/keypad.rs +++ b/emma/src/chip8/keypad.rs @@ -3,10 +3,19 @@ use ratatui::{ widgets::Widget, }; +#[derive(Clone, Copy)] pub struct Keypad { keys: [bool; 0x10], } +impl Default for Keypad { + fn default() -> Self { + Keypad { + keys: [ false; 16] + } + } +} + impl Keypad { pub fn push_key(&mut self, key_index: u8) { self.keys[key_index as usize] = true; diff --git a/emma/src/chip8/registers.rs b/emma/src/chip8/registers.rs index 054a29f..2add866 100644 --- a/emma/src/chip8/registers.rs +++ b/emma/src/chip8/registers.rs @@ -43,7 +43,7 @@ impl Chip8Registers { self.registers[(register_number - 1) as usize] = value; } - pub fn peek_pc(&mut self) -> u16 { + pub fn peek_pc(&self) -> u16 { self.pc }