diff --git a/emma/src/chip8/computer.rs b/emma/src/chip8/computer.rs index 8cf3d68..e436056 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::registers::Chip8Registers; use crate::chip8::sound_timer::SoundTimer; use crate::chip8::util::InstructionUtil; use crate::constants::{CHIP8_MEMORY_SIZE, CHIP8_REGISTER_COUNT}; @@ -9,16 +10,13 @@ use super::{ cpu_states::Chip8CpuStates, instructions::Chip8CpuInstructions, system_memory::Chip8SystemMemory, video::Chip8Video, }; - #[derive(Clone, Copy)] pub struct Chip8Computer { - pub pc: u16, pub sp: u8, pub memory: Chip8SystemMemory, - pub registers: [u8; 16], + pub registers: Chip8Registers, pub sound_timer: SoundTimer, pub delay_timer: DelayTimer, - pub i_register: u16, pub video_memory: Chip8Video, pub state: Chip8CpuStates, } @@ -26,14 +24,12 @@ pub struct Chip8Computer { impl Default for Chip8Computer { fn default() -> Self { Self { - pc: 0x0200, sp: 0x00, memory: Chip8SystemMemory::default(), video_memory: Chip8Video::default(), - registers: [0; CHIP8_REGISTER_COUNT as usize], + registers: Chip8Registers::default(), sound_timer: SoundTimer::new(), delay_timer: DelayTimer::new(), - i_register: 0x0000, state: Chip8CpuStates::WaitingForInstruction, } } @@ -68,8 +64,9 @@ impl Chip8Computer { // read the next instruction let mut working_instruction: u16 = 0b0000000000000000; - let high_byte = (self.memory.clone().peek(self.pc) as u16).rotate_left(8); - let low_byte = self.memory.clone().peek(self.pc + 1) as u16; + let start_pc = self.registers.peek_pc(); + let high_byte = (self.memory.clone().peek(start_pc) as u16).rotate_left(8); + let low_byte = self.memory.clone().peek(start_pc + 1) as u16; working_instruction = InstructionUtil::join_bytes(high_byte as u8, low_byte as u8); @@ -81,44 +78,6 @@ impl Chip8Computer { // todo: THIS IS BAD AND IS A SIDE EFFECT decoded_instruction.execute(self); - /* - match (self.state, decoded_instruction) { - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SeVxVy(vx_register, vy_register)) => { - debug!("INST* SeVxVy: 0x{vx_register:1x}/0x{vy_register:1x}"); - if self.registers[vx_register as usize] == self.registers[vy_register as usize] { - self.pc += 2; - }; - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxByte(vx_register, byte)) => { - debug!("INST* LdVxByte: 0x{vx_register:1x}/0x{byte:2x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AddVxByte(vx_register, byte)) => { - debug!("INST: AddVxByte: 0x{vx_register:1x}/0x{byte:2x}"); - self.registers[vx_register as usize] += byte as u8; - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxVy(vx_register, vy_register)) => { - debug!("INST: LdVxVy: 0x{vx_register:1x}/0x{vy_register:1x}"); - self.registers[vx_register as usize] = self.registers[vy_register as usize]; - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::OrVxVy(vx_register, vy_register)) => { - debug!("INST: OrVxVy: {vx_register}/{vy_register:1x}"); - self.registers[vx_register as usize] = self.registers[vx_register as usize] | self.registers[vy_register as usize]; - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AndVxVy(vx_register, vy_register)) => { - debug!("INST: AndVxVy: {vx_register:1x}/{vy_register:1x}"); - self.registers[vx_register as usize] = self.registers[vx_register as usize] & self.registers[vy_register as usize]; - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::XorVxVy(vx_register, vy_register)) => { - debug!("INST: XorVxVy: {vx_register:1x}/{vy_register:1x}"); - self.registers[vx_register as usize] = self.registers[vx_register as usize] ^ self.registers[vy_register as usize]; - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AddVxVy) => { - debug!("INST: AddVxVy: {vx_register:1x}/{vy_register:1x}"); - } - - - } - */ self.sound_timer.tick(); self.delay_timer.tick(); self diff --git a/emma/src/chip8/instructions.rs b/emma/src/chip8/instructions.rs index 73c9dc5..70feaad 100644 --- a/emma/src/chip8/instructions.rs +++ b/emma/src/chip8/instructions.rs @@ -1,8 +1,18 @@ +use log::debug; +use rand::random; use crate::chip8::computer::{Chip8Computer}; use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION; use crate::chip8::util::InstructionUtil; use crate::chip8::video::Chip8Video; +/* +nnn or addr - A 12-bit value, the lowest 12 bits of the instruction +n or nibble - A 4-bit value, the lowest 4 bits of the instruction +x - A 4-bit value, the lower 4 bits of the high byte of the instruction +y - A 4-bit value, the upper 4 bits of the low byte of the instruction +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 @@ -42,9 +52,6 @@ pub enum Chip8CpuInstructions { LdVxI(u16), // 0xFx65 Load V0 to Vx in memory starting at I XXXXERRORINSTRUCTION, } - - - impl Chip8CpuInstructions { pub fn encode(&self) -> u16 { match self { @@ -84,7 +91,6 @@ impl Chip8CpuInstructions { Chip8CpuInstructions::OrVxVy(x_register, y_register) => { 0x8001u16 | x_register << 8 | y_register << 4 } - Chip8CpuInstructions::AndVxVy(x_register, y_register) => { 0x8002u16 | x_register << 8 | y_register << 4 } @@ -93,15 +99,12 @@ impl Chip8CpuInstructions { } Chip8CpuInstructions::AddVxVy(x_register, y_register) => { 0x8004u16 | x_register << 8 | y_register << 4 - } Chip8CpuInstructions::SubVxVy(x_register, y_register) => { 0x8005u16 | x_register << 8 | y_register << 4 } - Chip8CpuInstructions::ShrVxVy(x_register, y_register) => { 0x8006u16 | x_register << 8 | y_register << 4 - } Chip8CpuInstructions::SubnVxVy(x_register, y_register) => { 0x8007u16 | x_register << 8 | y_register << 4 @@ -129,7 +132,6 @@ impl Chip8CpuInstructions { } Chip8CpuInstructions::SnkpVx(x_register) => { 0xE0A1u16 | x_register << 8 - } Chip8CpuInstructions::LdVxDt(x_register) => { 0xF007u16 | x_register << 8 @@ -145,7 +147,6 @@ impl Chip8CpuInstructions { } Chip8CpuInstructions::AddIVx(x_register) => { 0xF01Eu16 | x_register << 8 - } Chip8CpuInstructions::LdFVx(x_register) => { 0xF029u16 | x_register << 8 @@ -164,9 +165,6 @@ impl Chip8CpuInstructions { } } } -} - -impl Chip8CpuInstructions { pub fn decode(input: u16) -> Chip8CpuInstructions { let x_param = InstructionUtil::read_x_from_instruction(input); let y_param = InstructionUtil::read_y_from_instruction(input); @@ -306,7 +304,7 @@ impl Chip8CpuInstructions { } } 0xF007..=0xFF65 => { - println!("COMPARING LAST BYTE FROM TODECODE: {:2x} to {:4x} with {:2x}", last_byte, input, ubln); + // println!("COMPARING LAST BYTE FROM TODECODE: {:2x} to {:4x} with {:2x}", last_byte, input, ubln); match last_byte { 0x07 => { Chip8CpuInstructions::LdVxDt(ubln) @@ -347,58 +345,127 @@ impl Chip8CpuInstructions { } pub fn execute(&self, mut input: &mut Chip8Computer) -> Chip8Computer { - input.pc += 2; + let start_pc = input.registers.peek_pc(); + input.registers.poke_pc(start_pc + 2); let _ = match self { + // 0x0nnn Exit to System Call Chip8CpuInstructions::SysAddr(new_address) => { - let mut new_state = input.clone(); - new_state.pc = *new_address as u16; + // println!("SYS TO [{new_address}]"); + input.registers.poke_pc(*new_address as u16); } + // * 0x00E0 Clear Screen Chip8CpuInstructions::CLS => { for i in 0..(64 * 32) { input.video_memory.poke(i, false); } } - Chip8CpuInstructions::RET => {} + // 0x00EE Return from Subroutine + Chip8CpuInstructions::RET => { + debug!("RET"); + } + // 0x1nnn Jump to Address Chip8CpuInstructions::JpAddr(new_address) => { - input.pc = *new_address as u16 + input.registers.poke_pc(*new_address as u16); } - Chip8CpuInstructions::CallAddr(_) => {} + // 0x2nnn Call Subroutine + Chip8CpuInstructions::CallAddr(new_address) => { + debug!("CALL ADDR {new_address}"); + } + // 0x3xkk Skip next instruction if Vx = kk. Chip8CpuInstructions::SeVxByte(vx_register, byte) => { - input.registers[*vx_register as usize] = *byte as u8; - } - Chip8CpuInstructions::SneVxByte(vx_register, byte) => { - if input.registers[*vx_register as usize] == *byte as u8 { - input.pc += 2; + if input.registers.peek(*vx_register as u8) == *byte as u8 { + input.registers.advance_pc(); } } - Chip8CpuInstructions::SeVxVy(_, _) => {} + // 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 { + input.registers.advance_pc(); + } + } + // 0x5xy0 Skip next instruction if Vx == Vy + Chip8CpuInstructions::SeVxVy(x,y) => { + let lhs = input.registers.peek(*x as u8); + let rhs = input.registers.peek(*y as u8); + // println!("COMPARING [{lhs}] to [{rhs}]"); + if lhs == rhs { + input.registers.advance_pc(); + } + } + // 0x6xkk Set Vx = kk Chip8CpuInstructions::LdVxByte(register, byte) => { - input.registers[*register as usize] = byte.to_be_bytes()[0]; + let start_value = input.registers.peek(*register as u8); + let byte_value = *byte as u8; + // println!("SETTING REGISTER [{register}] FROM [{start_value}] to [{byte_value}] by loading."); + input.registers.poke(*register as u8, byte_value); } - Chip8CpuInstructions::AddVxByte(_, _) => {} - Chip8CpuInstructions::LdVxVy(_, _) => {} - Chip8CpuInstructions::OrVxVy(_, _) => {} - Chip8CpuInstructions::AndVxVy(_, _) => {} - Chip8CpuInstructions::XorVxVy(_, _) => {} - Chip8CpuInstructions::AddVxVy(vx_register, vy_register) => { - input.registers[*vx_register as usize] += input.registers[*vy_register as usize]; + // 0x7xkk Set Vx = Vx + kk + Chip8CpuInstructions::AddVxByte(vx_register, byte) => { + let to_add = *byte as u8; + let old_value = input.registers.peek(*vx_register as u8); + println!("Adding [{old_value}] from register [{vx_register}] to [{to_add}] "); + input.registers.poke(*vx_register as u8, (old_value + to_add)); } - Chip8CpuInstructions::SubVxVy(vx_register, vy_register) => { - input.registers[*vx_register as usize] -= input.registers[*vy_register as usize]; + // 0x8xy0 Set value of Vy in Vx + Chip8CpuInstructions::LdVxVy(x, y) => { + input.registers.poke(*x as u8, input.registers.peek(*y as u8)); + } + // 0x8xy1 Set Vx = Vx OR Vy + Chip8CpuInstructions::OrVxVy(x, y) => { + let lhs = input.registers.peek(*x as u8); + let rhs = input.registers.peek(*y as u8); + input.registers.poke(*x as u8, lhs | rhs); + } + // 0x8xy2 Set Vx = Vx AND Vy + Chip8CpuInstructions::AndVxVy(x, y) => { + let lhs = input.registers.peek(*x as u8); + let rhs = input.registers.peek(*y as u8); + + input.registers.poke(*x as u8, lhs & rhs); + } + // 0x8xy3 Set Vx = Vx XOR Vy + Chip8CpuInstructions::XorVxVy(x, y) => { + let lhs = input.registers.peek(*x as u8); + let rhs = input.registers.peek(*y as u8); + input.registers.poke(*x as u8, lhs ^ rhs); + } + // 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry) + Chip8CpuInstructions::AddVxVy(x, y) => { + let lhs = input.registers.peek(*x as u8); + let rhs = input.registers.peek(*y as u8); + + let working =(lhs + rhs) as i16; + if working > 255 { + input.registers.poke(0xf, 0x01); + } + input.registers.poke(*x as u8, working as u8); + } + Chip8CpuInstructions::SubVxVy(x,y) => { + 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::SneVxVy(vx_register, vy_register) => { - if input.registers[*vx_register as usize] != input.registers[*vy_register as usize] { - input.pc += 02; + 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 { + input.registers.advance_pc(); } } Chip8CpuInstructions::LdIAddr(new_index) => { - input.i_register = *new_index; + input.registers.poke_i(input.registers.peek(*new_index as u8) as u16); } + // 0xBnnn Jump to nnn+V0 Chip8CpuInstructions::JpV0Addr(_) => {} - Chip8CpuInstructions::RndVxByte(_, _) => {} + Chip8CpuInstructions::RndVxByte(x, byte) => { + let new_value: u8 = random() ; + input.registers.poke(*x as u8, (new_value & *byte as u8)) + } Chip8CpuInstructions::DrawVxVyNibble(x, y, n) => { // read nibble bytes from memory starting at I @@ -411,9 +478,9 @@ impl Chip8CpuInstructions { } if did_change { - input.registers[0xF] = 1u8; + input.registers.poke(0x10, 1u8); } else { - input.registers[0xF] = 0u8; + input.registers.poke(0x10, 0u8); } } Chip8CpuInstructions::SkpVx(_) => {} @@ -425,7 +492,9 @@ impl Chip8CpuInstructions { Chip8CpuInstructions::AddIVx(_) => {} Chip8CpuInstructions::LdFVx(_) => {} Chip8CpuInstructions::LdBVx(_) => {} - Chip8CpuInstructions::LdIVx(_) => {} + Chip8CpuInstructions::LdIVx(x) => { + input.registers.poke(*x as u8, input.registers.peek_i() as u8); + } Chip8CpuInstructions::LdVxI(_) => {} Chip8CpuInstructions::XXXXERRORINSTRUCTION => {} }; @@ -522,4 +591,132 @@ mod test { assert!(matches!(Chip8CpuInstructions::decode(0xfe55), Chip8CpuInstructions::LdIVx(0xe))); assert!(matches!(Chip8CpuInstructions::decode(0xf365), Chip8CpuInstructions::LdVxI(0x3))); } + + /// START OF THE EXECUTION TESTS + #[test] + fn sys_test() { + // 0x0nnn Exit to System Call + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::SysAddr(0).execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0); + Chip8CpuInstructions::SysAddr(0xFA0).execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0xFA0); + Chip8CpuInstructions::SysAddr(0x0AF).execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0x0AF); + } + fn cls_test() { + // * 0x00E0 Clear Screen + // todo: Need to write this + } + + fn ret_test() { + // 0x00EE Return from Subroutine + // todo: no stack yet. + } + + #[test] + fn jpaddr_test() { + // 0x1nnn Jump to Address + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::JpAddr(0).execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0); + Chip8CpuInstructions::JpAddr(0xABC).execute(&mut x); + 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() { + // 0x6xkk Set Vx = kk + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(1, 0x12).execute(&mut x); + Chip8CpuInstructions::LdVxByte(2, 0x21).execute(&mut x); + assert_eq!(x.registers.peek(1), 0x12); + assert_eq!(x.registers.peek(2), 0x21); + } + + #[test] + fn sevxbyte_match_test() { + // 0x3xkk Skip next instruction if Vx = kk. + // The interpreter compares register Vx to kk, + // and if they are equal, increments the program counter by 2. + + // test setup: Load value 0x84 into V1 + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(1, 0x84).execute(&mut x); + Chip8CpuInstructions::SeVxByte(1, 0x84).execute(&mut x); + // we should be 6 instructions past. + // 2 for the LDVXBYTE + // 2 for the SEVXBYTE + // 2 for skipping. + assert_eq!(x.registers.peek_pc(), 0x206); + } + + #[test] + fn sevxbyte_nomatch_test() { + // 0x3xkk Skip next instruction if Vx = kk. + // The interpreter compares register Vx to kk, + // and if they are equal, increments the program counter by 2. + + // test setup: Load value 0x84 into V1 + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(0x01, 0x84).execute(&mut x); + // PC will be 2 bytes past as we executed an instruction... + assert_eq!(x.registers.peek_pc(), 0x202); + + Chip8CpuInstructions::SeVxByte(1, 0x48).execute(&mut x); + // we should be 2 instructions past. + // 2 for what we executed + assert_eq!(x.registers.peek_pc(), 0x204); + + Chip8CpuInstructions::SeVxByte(1, 0x84).execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0x208); + } + + #[test] + fn SeVxVy_test() { + // 0x4xkk Skip next instruction if Vx != kk + let mut x = Chip8Computer::new(); + Chip8CpuInstructions::LdVxByte(0x01, 0x84).execute(&mut x); + Chip8CpuInstructions::LdVxByte(0x02, 0x84).execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0x204); + + // skip, compare 0x84 to 0x84 + Chip8CpuInstructions::SeVxVy(0x1, 0x2).execute(&mut x); + assert_eq!(x.registers.peek_pc(), 0x208); + + // load 0x48 (not matching) into V2 + Chip8CpuInstructions::LdVxByte(0x2, 0x48).execute(&mut x); + // verify its there. + assert_eq!(x.registers.peek(2), 0x48); + // no skip, compare 0x84 and 0x48 + Chip8CpuInstructions::SeVxVy(0x01, 0x02).execute(&mut x); + assert_eq!(x.registers.peek(2), 0x48); + assert_eq!(x.registers.peek_pc(), 0x20C); + } + + #[test] + fn AddVxByte_test() { + // 0x7xkk Set Vx = Vx + kk + let x = Chip8Computer::new(); + + } + + #[test] + fn LdVxVy_test() {} + + #[test] + fn OrVxVy_test() {} + + #[test] + fn AndVxVy_test() {} + + #[test] + fn XorVxVy_test() {} + } \ No newline at end of file diff --git a/emma/src/chip8/registers.rs b/emma/src/chip8/registers.rs new file mode 100644 index 0000000..054a29f --- /dev/null +++ b/emma/src/chip8/registers.rs @@ -0,0 +1,53 @@ + +/// Registers. numbered 1-16 publicly. +/// Privately using zero base array so -1 to shift from pub to priv. +#[derive(Clone, Copy)] +pub struct Chip8Registers { + registers: [u8; 16], + i_register: u16, + pc: u16 +} + +impl Chip8Registers { + pub fn advance_pc(&mut self) { + self.pc += 2; + } +} + +impl Default for Chip8Registers { + fn default() -> Self { + Chip8Registers { + registers: [0x00; 16], + i_register: 0x00, + pc: 0x200 + } + } +} + +impl Chip8Registers { + pub fn poke_pc(&mut self, new_pc: u16) { + self.pc = new_pc + } + pub fn peek_i(&self) -> u16 { + self.i_register + } + + pub fn poke_i(&mut self, new_value: u16) { + self.i_register = new_value; + } + pub fn peek(&self, register_number: u8) -> u8 { + self.registers[(register_number - 1) as usize] + } + + pub fn poke(&mut self, register_number: u8, value: u8) { + self.registers[(register_number - 1) as usize] = value; + } + + pub fn peek_pc(&mut self) -> u16 { + self.pc + } + + pub fn set_pc(&mut self, new_pc: u16) { + self.pc = new_pc + } +} diff --git a/emma/src/chip8/util.rs b/emma/src/chip8/util.rs index e16d70c..cd9a840 100644 --- a/emma/src/chip8/util.rs +++ b/emma/src/chip8/util.rs @@ -10,7 +10,8 @@ impl InstructionUtil { } pub fn join_bytes(high: u8, low: u8) -> u16 { - (high as u16 )<< 8 & low as u16 + let result = (high as u16 )<< 8 | low as u16; + result } // nnn or addr - A 12-bit value, the lowest 12 bits of the instruction diff --git a/emma/src/lib.rs b/emma/src/lib.rs index e4ff44d..d6e4c0e 100644 --- a/emma/src/lib.rs +++ b/emma/src/lib.rs @@ -8,6 +8,7 @@ pub mod chip8 { pub mod instructions; pub mod cpu_states; pub mod util; + pub mod registers; } pub mod constants; \ No newline at end of file