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 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 LdDtVx(u16), // 0xFx15 Set Delay Timer LdStVx(u16), // 0xFx18 Set Sount Timer 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 LdIVx(u16), // 0xFx55 Store V0 to Vx in memory starting at I LdVxI(u16), // 0xFx65 Load V0 to Vx in memory starting at I XXXXERRORINSTRUCTION, } impl Chip8CpuInstructions { pub fn encode(&self) -> u16 { match self { Chip8CpuInstructions::SysAddr(target) => { (0x0000 | (target & 0x0FFF)) as u16 } Chip8CpuInstructions::CLS => { 0x00E0 } Chip8CpuInstructions::RET => { 0x00EE } Chip8CpuInstructions::JpAddr(new_addr) => { 0x1000 | (new_addr & 0x0FFF) as u16 } Chip8CpuInstructions::CallAddr(address) => { (0x2000 | (address & 0x0FFF)) as u16 } Chip8CpuInstructions::SeVxByte(vx_register, byte) => { (0x3000 | (vx_register << 8 | byte) as u16) } Chip8CpuInstructions::SneVxByte(vx_register, byte) => { (0x4000i16 | vx_register << 8 | byte) as u16 } Chip8CpuInstructions::SeVxVy(x_register, y_register) => { 0x5000u16 | (x_register << 8 | y_register << 4) } Chip8CpuInstructions::LdVxByte(x_register, byte) => { 0x6000u16 | x_register << 8 | byte } Chip8CpuInstructions::AddVxByte(x_register, byte) => { 0x7000u16 | x_register << 8 | byte } Chip8CpuInstructions::LdVxVy(x_register, y_register) => { 0x8000u16 | x_register << 8 | y_register << 4 } 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 } Chip8CpuInstructions::XorVxVy(x_register, y_register) => { 0x8003u16 | x_register << 8 | y_register << 4 } 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 } Chip8CpuInstructions::ShlVxVy(x_register, y_register) => { 0x800Eu16 | x_register << 8 | y_register << 4 } Chip8CpuInstructions::SneVxVy(x_register, y_register) => { 0x9000u16 | x_register << 8 | y_register << 4 } Chip8CpuInstructions::LdIAddr(addr) => { 0xA000u16 | addr } Chip8CpuInstructions::JpV0Addr(addr) => { 0xB000u16 | addr } Chip8CpuInstructions::RndVxByte(x_register, byte) => { 0xC000u16 | x_register << 8 | byte } Chip8CpuInstructions::DrawVxVyNibble(x_register, y_register, height) => { 0xD000u16 | x_register << 8 | y_register << 4 | height } Chip8CpuInstructions::SkpVx(x_register) => { 0xE09Eu16 | x_register << 8 } Chip8CpuInstructions::SnkpVx(x_register) => { 0xE0A1u16 | x_register << 8 } Chip8CpuInstructions::LdVxDt(x_register) => { 0xF007u16 | x_register << 8 } Chip8CpuInstructions::LdVxK(x_register) => { 0xF00Au16 | x_register << 8 } Chip8CpuInstructions::LdDtVx(x_register) => { 0xF015u16 | x_register << 8 } Chip8CpuInstructions::LdStVx(x_register) => { 0xF018u16 | x_register << 8 } Chip8CpuInstructions::AddIVx(x_register) => { 0xF01Eu16 | x_register << 8 } Chip8CpuInstructions::LdFVx(x_register) => { 0xF029u16 | x_register << 8 } Chip8CpuInstructions::LdBVx(x_register) => { 0xf033u16 | x_register << 8 } Chip8CpuInstructions::LdIVx(x_register) => { 0xf055u16 | x_register << 8 } Chip8CpuInstructions::LdVxI(x_register) => { 0xf065u16 | x_register << 8 } _ => { 0xffff } } } pub fn decode(input: u16) -> Chip8CpuInstructions { let x_param = InstructionUtil::read_x_from_instruction(input); let y_param = InstructionUtil::read_y_from_instruction(input); let addr_param = InstructionUtil::read_addr_from_instruction(input); let byte_param = InstructionUtil::read_byte_from_instruction(input); let nibble_param = InstructionUtil::read_nibble_from_instruction(input); let ubln = u16::rotate_right(InstructionUtil::read_upper_byte_lower_nibble(input), 8); let last_byte = input & 0xFF; match input { 0x00E0 => { // 00E0 - CLS // Clear the display. Chip8CpuInstructions::CLS } 0x00EE => { // 00EE - RET // Return from a subroutine. Chip8CpuInstructions::RET } 0x0000..=0x0FFF => { // 0nnn - SYS addr // Jump to a machine code routine at nnn. Chip8CpuInstructions::SysAddr(addr_param as i16) } 0x1000..=0x1FFF => { // 1nnn - JP addr // Jump to location nnn. Chip8CpuInstructions::JpAddr(addr_param as i16) } 0x2000..=0x2FFF => { // 2nnn - CALL addr // Call subroutine at nnn. Chip8CpuInstructions::CallAddr(addr_param as i16) } 0x3000..=0x3FFF => { // 3xkk - SE Vx, byte // Skip next instruction if Vx = kk. Chip8CpuInstructions::SeVxByte(x_param as i16, byte_param as i16) } 0x4000..=0x4FFF => { // 4xkk - SNE Vx, byte // Skip next instruction if Vx != kk. Chip8CpuInstructions::SneVxByte(x_param as i16, byte_param as i16) } 0x5000..=0x5FF0 => { // 5xy0 - SE Vx, Vy // Skip next instruction if Vx = Vy. Chip8CpuInstructions::SeVxVy(x_param, y_param) } 0x6000..=0x6FFF => { // 6xkk - LD Vx, byte // Set Vx = kk. Chip8CpuInstructions::LdVxByte(x_param, byte_param) } 0x7000..=0x7FFF => { // ADD Vx, Byte Chip8CpuInstructions::AddVxByte(x_param, byte_param) } 0x8000..=0x8FFE => { // 0x8000 Series let last_nibble = input & 0xF; match last_nibble { 0x0 => { // LD Vx, Vy Chip8CpuInstructions::LdVxVy(x_param, y_param) } 0x1 => { // OR Vx, Vy Chip8CpuInstructions::OrVxVy(x_param, y_param) } 0x2 => { // AND Vx, Vy Chip8CpuInstructions::AndVxVy(x_param, y_param) } 0x3 => { // XOR Vx, Vy Chip8CpuInstructions::XorVxVy(x_param, y_param) } 0x4 => { // ADD Vx, Vy Chip8CpuInstructions::AddVxVy(x_param, y_param) } 0x5 => { // SUB Vx, Vy Chip8CpuInstructions::SubVxVy(x_param, y_param) } 0x6 => { // SHR Vx, {, Vy } Chip8CpuInstructions::ShrVxVy(x_param, y_param) } 0x7 => { // SUBN Vx, Vy Chip8CpuInstructions::SubnVxVy(x_param, y_param) } 0xE => { // SHL Vx, {, Vy} Chip8CpuInstructions::ShlVxVy(x_param, y_param) } _ => { panic!("UNABLE TO DECODE 0x8000 SERIES INSTRUCTION"); } } } 0x9000..=0x9FF0 => { // SNE Vx, Vy Chip8CpuInstructions::SneVxVy(x_param, y_param) } 0xA000..=0xAFFF => { // LD I, Addr Chip8CpuInstructions::LdIAddr(addr_param) } 0xB000..=0xBFFF => { // JP V0, Addr Chip8CpuInstructions::JpV0Addr(addr_param) } 0xC000..=0xCFFF => { // RND Vx, byte Chip8CpuInstructions::RndVxByte(x_param, byte_param) } 0xD000..=0xDFFF => { // DRAW Vx, Vy, nibble Chip8CpuInstructions::DrawVxVyNibble(x_param, y_param, nibble_param) } 0xE09E..=0xEFA1 => { match last_byte { 0x9E => { Chip8CpuInstructions::SkpVx(ubln) } 0xA1 => { Chip8CpuInstructions::SnkpVx(ubln) } _ => { XXXXERRORINSTRUCTION } } } 0xF007..=0xFF65 => { // println!("COMPARING LAST BYTE FROM TODECODE: {:2x} to {:4x} with {:2x}", last_byte, input, ubln); match last_byte { 0x07 => { Chip8CpuInstructions::LdVxDt(ubln) } 0x0A => { Chip8CpuInstructions::LdVxK(ubln) } 0x15 => { Chip8CpuInstructions::LdDtVx(ubln) } 0x18 => { Chip8CpuInstructions::LdStVx(ubln) } 0x1E => { Chip8CpuInstructions::AddIVx(ubln) } 0x29 => { Chip8CpuInstructions::LdFVx(ubln) } 0x33 => { Chip8CpuInstructions::LdBVx(ubln) } 0x55 => { Chip8CpuInstructions::LdIVx(ubln) } 0x65 => { Chip8CpuInstructions::LdVxI(ubln) } _ => { XXXXERRORINSTRUCTION } } } _ => { XXXXERRORINSTRUCTION } } } pub fn execute(&self, mut input: &mut Chip8Computer) -> Chip8Computer { 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) => { // 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); } } // 0x00EE Return from Subroutine Chip8CpuInstructions::RET => { debug!("RET"); } // 0x1nnn Jump to Address Chip8CpuInstructions::JpAddr(new_address) => { input.registers.poke_pc(*new_address as u16); } // 0x2nnn Call Subroutine Chip8CpuInstructions::CallAddr(new_address) => { debug!("CALL ADDR {new_address}"); } // 0x3xkk Skip next instruction if Vx = kk. Chip8CpuInstructions::SeVxByte(vx_register, byte) => { if input.registers.peek(*vx_register as u8) == *byte as u8 { input.registers.advance_pc(); } } // 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) => { 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); } // 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)); } // 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 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(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.poke(*x as u8, 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 as u8); let x_register = input.registers.peek(*x as u8); let new_value = if y_register > x_register { 1 } else { 0 }; input.registers.poke(0xf, new_value); input.registers.poke(*x as u8, 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.poke(*x as u8, 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 { input.registers.advance_pc(); } } Chip8CpuInstructions::LdIAddr(new_index) => { // Annn - LD I, addr // Set I = nnn. // // The value of register I is set to nnn. input.registers.poke_i(*new_index); } // 0xBnnn Jump to nnn+V0 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 as u16 + 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; for draw_x in 0..*n { // let mut new_value = input.memory[(input.i_register + draw_x) as usize]; //for draw_y in 0..8 { // } } if did_change { input.registers.poke(0xf, 1u8); } else { input.registers.poke(0xf, 0u8); } } 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 as u8); } 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 as u8, input.delay_timer.current() as u8); } 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 as i32); } Chip8CpuInstructions::LdStVx(new_time) => { input.sound_timer.set_timer(*new_time as i32); } 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 as u8); input.registers.poke_i(base + x_value as u16); } 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 as u8)); } } 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 as u8); for index in 0..num_loops { input.registers.poke(index, input.memory.peek(index as u16 + offset)); } } Chip8CpuInstructions::XXXXERRORINSTRUCTION => {} }; *input } } #[cfg(test)] mod test { use super::*; #[test] fn smoke() { assert!(true) } #[test] fn encode_decode_test() { assert_eq!(Chip8CpuInstructions::CLS.encode(), 0x00E0); assert_eq!(Chip8CpuInstructions::RET.encode(), 0x00EE); assert_eq!(Chip8CpuInstructions::SysAddr(0x123).encode(), 0x0123); assert_eq!(Chip8CpuInstructions::JpAddr(0x234).encode(), 0x1234); assert_eq!(Chip8CpuInstructions::CallAddr(0x345).encode(), 0x2345); assert_eq!(Chip8CpuInstructions::SeVxByte(0x4, 0x56).encode(), 0x3456); assert_eq!(Chip8CpuInstructions::SneVxByte(0xa, 0xbc).encode(), 0x4abc); assert_eq!(Chip8CpuInstructions::SeVxVy(0xa, 0xb).encode(), 0x5ab0); assert_eq!(Chip8CpuInstructions::LdVxByte(0xa, 0xff).encode(), 0x6aff); assert_eq!(Chip8CpuInstructions::AddVxByte(0xa, 0xbc).encode(), 0x7abc); assert_eq!(Chip8CpuInstructions::LdVxVy(0xa, 0xb).encode(), 0x8ab0); assert_eq!(Chip8CpuInstructions::OrVxVy(0xb, 0xa).encode(), 0x8ba1); assert_eq!(Chip8CpuInstructions::AndVxVy(0xc, 0xd).encode(), 0x8cd2); assert_eq!(Chip8CpuInstructions::XorVxVy(0xd, 0xe).encode(), 0x8de3); assert_eq!(Chip8CpuInstructions::AddVxVy(0xe, 0xf).encode(), 0x8ef4); assert_eq!(Chip8CpuInstructions::SubVxVy(0xf, 0x0).encode(), 0x8f05); assert_eq!(Chip8CpuInstructions::ShrVxVy(0x0, 0x1).encode(), 0x8016); assert_eq!(Chip8CpuInstructions::SubnVxVy(0x1, 0x2).encode(), 0x8127); assert_eq!(Chip8CpuInstructions::ShlVxVy(0x3, 0x4).encode(), 0x834e); assert_eq!(Chip8CpuInstructions::SneVxVy(0xa, 0xb).encode(), 0x9ab0); assert_eq!(Chip8CpuInstructions::LdIAddr(0x123).encode(), 0xa123); assert_eq!(Chip8CpuInstructions::JpV0Addr(0x234).encode(), 0xb234); assert_eq!(Chip8CpuInstructions::RndVxByte(0xa, 0xca).encode(), 0xcaca); assert_eq!(Chip8CpuInstructions::DrawVxVyNibble(0xa, 0xb, 0x4).encode(), 0xdab4); assert_eq!(Chip8CpuInstructions::SkpVx(0x1).encode(), 0xe19e); assert_eq!(Chip8CpuInstructions::SnkpVx(0x2).encode(), 0xe2a1); assert_eq!(Chip8CpuInstructions::LdVxDt(0x1).encode(), 0xf107); assert_eq!(Chip8CpuInstructions::LdVxK(0x4).encode(), 0xf40a); assert_eq!(Chip8CpuInstructions::LdDtVx(0x6).encode(), 0xf615); assert_eq!(Chip8CpuInstructions::LdStVx(0xb).encode(), 0xfb18); assert_eq!(Chip8CpuInstructions::AddIVx(0xd).encode(), 0xfd1e); assert_eq!(Chip8CpuInstructions::LdFVx(0xc).encode(), 0xfc29); assert_eq!(Chip8CpuInstructions::LdBVx(0xd).encode(), 0xfd33); assert_eq!(Chip8CpuInstructions::LdIVx(0xe).encode(), 0xfe55); assert_eq!(Chip8CpuInstructions::LdVxI(0x3).encode(), 0xf365); assert!(matches!( Chip8CpuInstructions::decode(0x00E0u16), Chip8CpuInstructions::CLS)); assert!(matches!( Chip8CpuInstructions::decode(0x00EEu16), Chip8CpuInstructions::RET)); assert!(matches!(Chip8CpuInstructions::decode(0x0123), Chip8CpuInstructions::SysAddr(0x123))); assert!(matches!(Chip8CpuInstructions::decode(0x0FFF), Chip8CpuInstructions::SysAddr(0xfff))); assert!(matches!(Chip8CpuInstructions::decode(0x1002), Chip8CpuInstructions::JpAddr(0x2))); assert!(matches!(Chip8CpuInstructions::decode(0x1FF0), Chip8CpuInstructions::JpAddr(0xFF0))); assert!(matches!(Chip8CpuInstructions::decode(0x2002), Chip8CpuInstructions::CallAddr(0x2))); assert!(matches!(Chip8CpuInstructions::decode(0x3123), Chip8CpuInstructions::SeVxByte(0x1, 0x23))); assert!(matches!(Chip8CpuInstructions::decode(0x4abc), Chip8CpuInstructions::SneVxByte(0xa, 0xbc))); assert!(matches!(Chip8CpuInstructions::decode(0x5ab0), Chip8CpuInstructions::SeVxVy(0xa, 0xb))); assert!(matches!(Chip8CpuInstructions::decode(0x6aff), Chip8CpuInstructions::LdVxByte(0xa, 0xff))); assert!(matches!(Chip8CpuInstructions::decode(0x7abc), Chip8CpuInstructions::AddVxByte(0xa, 0xbc))); assert!(matches!(Chip8CpuInstructions::decode(0x8ab0), Chip8CpuInstructions::LdVxVy(0xa, 0xb))); assert!(matches!(Chip8CpuInstructions::decode(0x8ba1), Chip8CpuInstructions::OrVxVy(0xb, 0xa))); assert!(matches!(Chip8CpuInstructions::decode(0x8cd2), Chip8CpuInstructions::AndVxVy(0xc, 0xd))); assert!(matches!(Chip8CpuInstructions::decode(0x8de3), Chip8CpuInstructions::XorVxVy(0xd, 0xe))); assert!(matches!(Chip8CpuInstructions::decode(0x8ef4), Chip8CpuInstructions::AddVxVy(0xe, 0xf))); assert!(matches!(Chip8CpuInstructions::decode(0x8f05), Chip8CpuInstructions::SubVxVy(0xf, 0x0))); assert!(matches!(Chip8CpuInstructions::decode(0x8016), Chip8CpuInstructions::ShrVxVy(0x0, 0x1))); assert!(matches!(Chip8CpuInstructions::decode(0x8127), Chip8CpuInstructions::SubnVxVy(0x1, 0x2))); assert!(matches!(Chip8CpuInstructions::decode(0x834e), Chip8CpuInstructions::ShlVxVy(0x3, 0x4))); assert!(matches!(Chip8CpuInstructions::decode(0x9ab0), Chip8CpuInstructions::SneVxVy(0xa, 0xb))); assert!(matches!(Chip8CpuInstructions::decode(0xa123), Chip8CpuInstructions::LdIAddr(0x123))); assert!(matches!(Chip8CpuInstructions::decode(0xb234), Chip8CpuInstructions::JpV0Addr(0x234))); assert!(matches!(Chip8CpuInstructions::decode(0xcaca), Chip8CpuInstructions::RndVxByte(0xa, 0xca))); assert!(matches!(Chip8CpuInstructions::decode(0xdab4), Chip8CpuInstructions::DrawVxVyNibble(0xa, 0xb, 0x4))); assert!(matches!(Chip8CpuInstructions::decode(0xe19e), Chip8CpuInstructions::SkpVx(0x1))); assert!(matches!(Chip8CpuInstructions::decode(0xe2a1), Chip8CpuInstructions::SnkpVx(0x2))); assert!(matches!(Chip8CpuInstructions::decode(0xf107), Chip8CpuInstructions::LdVxDt(0x1))); assert!(matches!(Chip8CpuInstructions::decode(0xf40a), Chip8CpuInstructions::LdVxK(0x4))); assert!(matches!(Chip8CpuInstructions::decode(0xf615), Chip8CpuInstructions::LdDtVx(0x6))); assert!(matches!(Chip8CpuInstructions::decode(0xfb18), Chip8CpuInstructions::LdStVx(0xb))); assert!(matches!(Chip8CpuInstructions::decode(0xfd1e), Chip8CpuInstructions::AddIVx(0xd))); assert!(matches!(Chip8CpuInstructions::decode(0xfc29), Chip8CpuInstructions::LdFVx(0xc))); assert!(matches!(Chip8CpuInstructions::decode(0xfd33), Chip8CpuInstructions::LdBVx(0xd))); 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); assert_eq!(x.registers.peek(1), 0x12); assert_eq!(x.registers.peek_pc(), 0x202); Chip8CpuInstructions::LdVxByte(2, 0x21).execute(&mut x); assert_eq!(x.registers.peek(2), 0x21); assert_eq!(x.registers.peek_pc(), 0x204); } #[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 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 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 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 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 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() {} }