use std::ops::{BitAnd, Shr}; use log::debug; use rand::random; use crate::chip8::computer::{Chip8Computer}; use crate::chip8::cpu_states::Chip8CpuStates::WaitingForKey; use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION; use crate::chip8::util::InstructionUtil; /* 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 { /// 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(u8, u8), /// 4xkk /// Skip next instruction if Vx != kk SneVxByte(u8, u8), /// 5xy0 /// Skip next instruction if Vx == Vy SeVxVy(u8, u8), /// 6xkk /// Set Vx = kk LdVxByte(u8, u8), /// 7xkk /// Set Vx = Vx + kk AddVxByte(u8, u8), /// 8xy0 /// Set Vx = Vy LdVxVy(u8, u8), /// 8xy1 /// Set Vx = Vx OR Vy OrVxVy(u8, u8), /// 8xy2 /// Set Vx = Vx AND Vy AndVxVy(u8, u8), /// 8xy3 /// Set Vx = Vx XOR Vy XorVxVy(u8, u8), /// 8xy4 /// Set Vx = Vx + Vy /// Set VF=1 if Carry AddVxVy(u8, u8), /// 8xy5 /// Set Vx = Vx - Vy /// Set VF=1 if No Borrow SubVxVy(u8, u8), /// 8xy6 /// Set Vx = Vx SHR 1 ShrVxVy(u8, u8), /// 8xy7 /// Set Vx = Vy - Vx /// Set VF=1 if No Borrow SubnVxVy(u8, u8), /// 8xye /// Set Vx = Vx SHL 1 ShlVxVy(u8, u8), /// 9xy0 /// Skip next instruction if Vx != Vy SneVxVy(u8, u8), /// Annn /// Load I register with NNN LdIAddr(u16), /// Bnnn /// Jump to nnn+V0 JpV0Addr(u16), /// Cxkk /// Set Vx = Random u8 AND kk RndVxByte(u8, u8), /// Dxyn /// Display N byte tall sprite starting at Vx, Vy DrawVxVyNibble(u8, u8, u8), /// Ex9E /// Skip next instruction of key in Vx pressed SkpVx(u8), /// ExA1 /// Skip Next If Key Not Pressed SnkpVx(u8), /// Fx07 /// Set Vx = Dt LdVxDt(u8), /// Fx0A /// Wait for Key to be pressed and store /// in Vx LdVxK(u8), /// Fx15 /// Load Value in Delay Timer to Vx LdDtVx(u8), // 0xFx15 Set Delay Timer /// Fx18 /// Set Dt = Vx LdStVx(u8), AddIVx(u8), // 0xFx1E I = I + Vx LdFVx(u8), // 0xFx29 Set I = Location of sprite for Digit Vx LdBVx(u8), // 0xFx33 Store BCD of Vx in I, I+1, I+2 LdIVx(u8), // 0xFx55 Store V0 to Vx in memory starting at I LdVxI(u8), // 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 as u16) << 8 | *byte as u16) as u16 } Chip8CpuInstructions::SneVxByte(vx_register, byte) => { 0x4000u16 | (*vx_register as u16) << 8 | *byte as u16 } Chip8CpuInstructions::SeVxVy(x_register, y_register) => { 0x5000u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 } Chip8CpuInstructions::LdVxByte(x_register, byte) => { 0x6000u16 | (*x_register as u16) << 8 | (*byte as u16) } Chip8CpuInstructions::AddVxByte(x_register, byte) => { 0x7000u16 | (*x_register as u16) << 8 | *byte as u16 } Chip8CpuInstructions::LdVxVy(x_register, y_register) => { 0x8000u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 } Chip8CpuInstructions::OrVxVy(x_register, y_register) => { 0x8001u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 } Chip8CpuInstructions::AndVxVy(x_register, y_register) => { 0x8002u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 } Chip8CpuInstructions::XorVxVy(x_register, y_register) => { 0x8003u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 } Chip8CpuInstructions::AddVxVy(x_register, y_register) => { 0x8004u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 } Chip8CpuInstructions::SubVxVy(x_register, y_register) => { 0x8005u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 } Chip8CpuInstructions::ShrVxVy(x_register, y_register) => { 0x8006u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 } Chip8CpuInstructions::SubnVxVy(x_register, y_register) => { 0x8007u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 } Chip8CpuInstructions::ShlVxVy(x_register, y_register) => { 0x800Eu16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 } Chip8CpuInstructions::SneVxVy(x_register, y_register) => { 0x9000u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 } Chip8CpuInstructions::LdIAddr(addr) => { 0xA000u16 | addr } Chip8CpuInstructions::JpV0Addr(addr) => { 0xB000u16 | addr } Chip8CpuInstructions::RndVxByte(x_register, byte) => { 0xC000u16 | (*x_register as u16) << 8 | (*byte as u16) } Chip8CpuInstructions::DrawVxVyNibble(x_register, y_register, height) => { 0xD000u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 | (*height as u16) } Chip8CpuInstructions::SkpVx(x_register) => { 0xE09Eu16 | (*x_register as u16) << 8 } Chip8CpuInstructions::SnkpVx(x_register) => { 0xE0A1u16 | (*x_register as u16) << 8 } Chip8CpuInstructions::LdVxDt(x_register) => { 0xF007u16 | (*x_register as u16) << 8 } Chip8CpuInstructions::LdVxK(x_register) => { 0xF00Au16 | (*x_register as u16) << 8 } Chip8CpuInstructions::LdDtVx(x_register) => { 0xF015u16 | (*x_register as u16) << 8 } Chip8CpuInstructions::LdStVx(x_register) => { 0xF018u16 | (*x_register as u16) << 8 } Chip8CpuInstructions::AddIVx(x_register) => { 0xF01Eu16 | (*x_register as u16) << 8 } Chip8CpuInstructions::LdFVx(x_register) => { 0xF029u16 | (*x_register as u16) << 8 } Chip8CpuInstructions::LdBVx(x_register) => { 0xf033u16 | (*x_register as u16) << 8 } Chip8CpuInstructions::LdIVx(x_register) => { 0xf055u16 | (*x_register as u16) << 8 } Chip8CpuInstructions::LdVxI(x_register) => { 0xf065u16 | (*x_register as u16) << 8 } XXXXERRORINSTRUCTION => { 0xffff } } } pub fn decode(input: u16) -> Chip8CpuInstructions { let x_param = InstructionUtil::read_x_from_instruction(input) as u8; let y_param = InstructionUtil::read_y_from_instruction(input) as u8; let addr_param = InstructionUtil::read_addr_from_instruction(input) as u16; let byte_param = InstructionUtil::read_byte_from_instruction(input) as u8; let nibble_param = InstructionUtil::read_nibble_from_instruction(input) as u8; let ubln = InstructionUtil::read_upper_byte_lower_nibble(input) as u8; 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, byte_param) } 0x4000..=0x4FFF => { // 4xkk - SNE Vx, byte // Skip next instruction if Vx != kk. Chip8CpuInstructions::SneVxByte(x_param, byte_param) } 0x5000..=0x5FF0 => { // 5xy0 - SE Vx, Vy // Skip next instruction if Vx = Vy. // if the last nibble isn't 0... if 0xf & input > 0 { // ... we have a problem. Chip8CpuInstructions::XXXXERRORINSTRUCTION } else { 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) } _ => { Chip8CpuInstructions::XXXXERRORINSTRUCTION } } } 0x9000..=0x9FF0 => { // SNE Vx, Vy if 0xf & input > 0 { XXXXERRORINSTRUCTION } else { 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 => { 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, 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) => { debug!("SysAddr [0x{new_address:3x}]"); 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 => { let return_address = input.stack.pop(); debug!("Returning from subroutine to {return_address:03x}"); input.registers.poke_pc(return_address); } // 0x1nnn Jump to Address Chip8CpuInstructions::JpAddr(new_address) => { debug!("JpAddr [0x{new_address:3x}]"); input.registers.poke_pc(*new_address as u16); } // 0x2nnn Call Subroutine Chip8CpuInstructions::CallAddr(new_address) => { debug!("CALL ADDR 0x{new_address:3x}"); let return_address = input.registers.peek_pc(); input.registers.poke_pc(*new_address as u16); input.stack.push(&return_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(); } debug!("SeVxByte [0x{vx_register:1x}] [0x{byte:2x}]"); } // 0x4xkk Skip next instruction if Vx != kk Chip8CpuInstructions::SneVxByte(x, byte) => { // 4xkk - SNE Vx, byte // Skip next instruction if Vx != kk. // // The interpreter compares register Vx to kk, and if they are not equal, // increments the program counter by 2. let lhs = input.registers.peek(*x); let rhs = *byte; if lhs != rhs { input.registers.advance_pc(); } debug!("SneVxByte [0x{x:02x}] [0x{byte:02x}"); } // 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); if lhs == rhs { input.registers.advance_pc(); } debug!("SeVxVy [0x{x:1x}] [0x{y:1x}]"); } // 0x6xkk Set Vx = kk Chip8CpuInstructions::LdVxByte(register, byte) => { let byte_value = *byte as u8; input.registers.poke(*register as u8, byte_value); debug!("LdVxByte [0x{register:1x}] [0x{byte:2.0x}]"); } // 0x7xkk Set Vx = Vx + kk Chip8CpuInstructions::AddVxByte(vx_register, byte) => { let x_value: u16 = input.registers.peek(*vx_register) as u16; let value_to_poke = (x_value + *byte as u16) as u8; input.registers.poke(*vx_register, value_to_poke); debug!("AddVxByte [0x{vx_register:1x}] [0x{byte:02x}] [0x{value_to_poke:02x}"); } // 0x8xy0 Set value of Vy in Vx Chip8CpuInstructions::LdVxVy(x, y) => { input.registers.poke(*x as u8, input.registers.peek(*y as u8)); debug!("LdVxVy [0x{x:1x}] [0x{y:1x}]"); } // 0x8xy1 Set Vx = Vx OR Vy Chip8CpuInstructions::OrVxVy(x, y) => { // shift them to 16 bit let working_16_x = input.registers.peek(*x as u8) as u16; let working_16_y = input.registers.peek(*y as u8) as u16; // OR them let working_16_or = working_16_x | working_16_y; // shift them back to 8 bit. let to_set = working_16_or as u8; input.registers.poke(*x as u8, to_set); debug!("OrVxVy [0x{x:1x}] [0x{y:1x}]") } // 0x8xy2 Set Vx = Vx AND Vy Chip8CpuInstructions::AndVxVy(x, y) => { let lhs_16 = input.registers.peek(*x as u8) as u16; let rhs_16 = input.registers.peek(*y as u8) as u16; let and_16 = lhs_16 & rhs_16; input.registers.poke(*x as u8, and_16 as u8); debug!("AndVxVy [0x{x:02x}] [0x{y:02x}] [0b{and_16:08b}"); } // 0x8xy3 Set Vx = Vx XOR Vy Chip8CpuInstructions::XorVxVy(x, y) => { let lhs_16 = input.registers.peek(*x as u8) as u16; let rhs_16 = input.registers.peek(*y as u8) as u16; let xor_16 = lhs_16 ^ rhs_16; input.registers.poke(*x as u8, xor_16 as u8); } // 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry) Chip8CpuInstructions::AddVxVy(x, y) => { let lhs = input.registers.peek(*x as u8) as i16; let rhs = input.registers.peek(*y as u8) as i16; let working = lhs + rhs; input.registers.poke(*x as u8, working as u8); if working >= 0x100 { input.registers.poke(0xf, 0x01); } else { input.registers.poke(0x0f, 0x00); } } 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); let rhs = input.registers.peek(*y); let mut result = 0; let borrow_flag: u8 = if rhs > lhs { result = (lhs as u16 + 0x100) - rhs as u16; 0 } else { result = lhs as u16 - rhs as u16; 1 }; input.registers.poke(*x as u8, result as u8); input.registers.poke(0x0f, borrow_flag); } Chip8CpuInstructions::ShrVxVy(x, _) => { // 8xy6 - SHR Vx {, Vy} // Set Vx = Vx SHR 1. // SHIFT 1 Bit ----> // 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); // overflow check let rotated = initial_value >> 1; input.registers.poke(*x as u8, rotated); if initial_value.bitand(0b1) == 1 { input.registers.poke(0x0f, 0x01); } else { input.registers.poke(0x0f, 0x00); } } 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 mut value_to_poke = 0; let new_value = if y_register < x_register { value_to_poke = (y_register as u16 + 256) - x_register as u16; 0 } else { value_to_poke = (y_register - x_register) as u16; 1 }; debug!("SUB CARRY -> REGISTER 1 = [0x{x:02x}] / [{x_register}] REGISTER 2 = [0x{y:02x}] / [{y_register}] SUB = {value_to_poke} CARRY = {new_value}"); input.registers.poke(*x, value_to_poke as u8); input.registers.poke(0xf, new_value); } Chip8CpuInstructions::ShlVxVy(x, _) => { // 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); // overflow check let rotated = initial_value << 1; input.registers.poke(*x as u8, rotated); if initial_value.bitand(0b10000000) == 0b10000000 { input.registers.poke(0x0f, 0x01); } else { input.registers.poke(0x0f, 0x00); } } 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. debug!("LdiAddr [0x{new_index:3x}]"); 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. input.registers.poke_pc(input.registers.peek(0) 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. let new_value: u8 = random(); let and_value: u8 = *byte; let result = new_value & and_value; debug!("RANDOM: [{new_value:02x}] AND: [{and_value:02x} Result: [{result:02x}]"); input.registers.poke(*x as u8, new_value & *byte as u8) } Chip8CpuInstructions::DrawVxVyNibble(y, x, 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. let source_memory_offset = input.registers.peek_i(); let x_offset = input.registers.peek(*x as u8); let y_offset = input.registers.peek(*y as u8); debug!("X_OFFSET = {x_offset} / y_offset = {y_offset}"); let target_memory_offset = x_offset as u16 * 64 + y_offset as u16; debug!("CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> STARTING AT {source_memory_offset} WRITING TO {target_memory_offset}"); let num_bytes_to_read = *n; debug!("CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> PREPARING TO READ {num_bytes_to_read} BYTES FROM MEMORY TO VIDEO"); for byte_index in 0..num_bytes_to_read { let current_byte = input.memory.peek(byte_index as u16 + source_memory_offset); debug!("CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> READ BYTE [0x{byte_index:2x}]\t{current_byte:02x}\t{current_byte:08b}"); for bit_index in 0..8 { let data_offset = ((x_offset as u16 + byte_index as u16) * 64) + (y_offset as u16 + bit_index as u16) as u16; let current_bit = (current_byte.shr(7 - bit_index) & 0x1u8) == 0x1u8; input.video_memory.poke(data_offset, current_bit); } } debug!("PPOOSSTT -> CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> {}", input.video_memory.format_as_string()); let mut did_change: bool = input.video_memory.has_frame_changed; 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); let is_pressed = input.keypad.pressed(key_to_check); if is_pressed { input.registers.advance_pc(); } } 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. let target_key = input.registers.peek(*x as u8); let is_pressed = input.keypad.pressed(target_key); debug!("SnKpVx [{x:1x}]"); if !is_pressed { input.registers.advance_pc(); } } Chip8CpuInstructions::LdVxDt(x) => { // Fx07 - LD Vx, DT // Set Vx = delay timer value. // // The value of DT is placed into Vx. 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 // 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. input.state = WaitingForKey; } Chip8CpuInstructions::LdDtVx(source_register) => { // Fx15 - LD DT, Vx // Set delay timer = Vx. // // DT is set equal to the value of Vx. let new_time = input.registers.peek(*source_register as u8); input.delay_timer.set_timer(new_time as i32); } Chip8CpuInstructions::LdStVx(new_time) => { 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 // 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. let x_value: u8 = input.registers.peek(*x); let real_offset = x_value as u16 * 5; input.registers.poke_i(real_offset as u16); } 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. let to_convert = input.registers.peek(*x as u8); // how many hundreds let hundreds = to_convert / 100; // how many tens, minus the hundreds let tens = (to_convert / 10) - (hundreds * 10); // whats the leftover when dividing by 10 let units = to_convert % 10; // Convert to BCD let result = ((hundreds as u16) << 8) | units as u16 | ((tens as u16) << 4) | units as u16; // write them to the memory pointed to by I, I+1, and I+2 let target_start_offset = input.registers.peek_i(); input.memory.poke(target_start_offset, hundreds); input.memory.poke(target_start_offset + 1, tens); input.memory.poke(target_start_offset + 2, units); } 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 as u16, input.registers.peek(i as u8)); } input.registers.poke_i(offset + 1); } 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(); debug!("STARTING TO READ AT {offset:03x}"); let num_loops = x + 1; debug!("WILL READ {num_loops:x} BYTES"); for index in 0..num_loops { let src_value = input.memory.peek(index as u16 + offset); input.registers.poke(index as u8, src_value); debug!("POKING Register 0x{index:02x} with 0x{src_value:04x} using offset 0x{offset:04x}"); } input.registers.poke_i(offset + 1); } Chip8CpuInstructions::XXXXERRORINSTRUCTION => {} }; input.clone() } } #[cfg(test)] mod test { use crate::chip8::system_memory::{CHIP8FONT_0, CHIP8FONT_1, CHIP8FONT_2, CHIP8FONT_9}; 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))); } #[test] fn decoder_test_invalid_instructions() { let invalid_to_encode = [ 0x5ab1, 0x5abf, 0x8ab8, 0x8abd, 0x8abf, 0x9ab1, 0x9abf, 0xea9d, 0xea9f, 0xeaa0, 0xeaa2, 0xf006, 0xf008 ]; for i in invalid_to_encode { assert_eq!(Chip8CpuInstructions::decode(i).encode(), 0xffff); assert!(matches!(Chip8CpuInstructions::decode(i), Chip8CpuInstructions::XXXXERRORINSTRUCTION)); } } /// 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); let mut x = Chip8Computer::new(); Chip8CpuInstructions::SysAddr(0xFA0).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0xFA0); let mut x = Chip8Computer::new(); Chip8CpuInstructions::SysAddr(0x0AF).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x0AF); } #[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); let mut x = Chip8Computer::new(); Chip8CpuInstructions::JpAddr(0xABC).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0xABC); } // ** test moved up so it can be used later #[test] fn ld_vx_byte_test() { // 0x6xkk Set Vx = kk let mut x = Chip8Computer::new(); Chip8CpuInstructions::LdVxByte(1, 0x12).execute(&mut x); assert_eq!(x.registers.peek(1), 0x12); let mut x = Chip8Computer::new(); Chip8CpuInstructions::LdVxByte(2, 0x21).execute(&mut x); 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(); x.registers.poke(0x1, 0x84); Chip8CpuInstructions::SeVxByte(1, 0x48).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x84); Chip8CpuInstructions::SeVxByte(1, 0x84).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); } #[test] fn se_vx_vy_test() { // 0x4xkk Skip next instruction if Vx != kk let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x84); x.registers.poke(0x2, 0x84); // skip, compare 0x84 to 0x84 Chip8CpuInstructions::SeVxVy(0x1, 0x2).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x84); x.registers.poke(0x2, 0x48); Chip8CpuInstructions::SeVxVy(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); } #[test] fn ld_vx_vy_test() { // 0x8xy0 Set value of Vy in Vx let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x01); x.registers.poke(0x02, 0x02); Chip8CpuInstructions::LdVxVy(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(1), 0x02); } #[test] fn or_vx_vy_test() { // 0x8xy1 Set Vx = Vx OR Vy // 0b0101 0000 (0x50) // | 0b0000 1010 (0x0A) // 0b0101 1010 (0x5A) let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0b01010000); x.registers.poke(0x02, 0b00001010); Chip8CpuInstructions::OrVxVy(1, 2).execute(&mut x); assert_eq!(x.registers.peek(1), 0b01011010); } #[test] fn and_vx_vy_test() { // 0x8xy2 Set Vx = Vx AND Vy // 0b1111 1100 (0xFC) // & 0b1100 1010 (0xCA) // 0b1100 1000 (0xC8) let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0xFC); x.registers.poke(0x02, 0xCA); Chip8CpuInstructions::AndVxVy(1, 2).execute(&mut x); assert_eq!(x.registers.peek(1), 0xC8); } #[test] fn xor_vx_vy_test() { // 0x8xy3 Set Vx = Vx XOR Vy // 0b1111 1100 (0xFC) // ^ 0b1100 1010 (0xCA) // 0b0011 0110 (0x36) let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0b11111100); x.registers.poke(0x02, 0b11001010); Chip8CpuInstructions::XorVxVy(1, 2).execute(&mut x); assert_eq!(x.registers.peek(1), 0b00110110); } #[test] fn add_vx_vy_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(); x.registers.poke(0x0f, 00); x.registers.poke(0x01, 0x01); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0xf), 0x00); assert_eq!(x.registers.peek(0x01), 0x02); let mut x = Chip8Computer::new(); x.registers.poke(0x0f, 0x00); x.registers.poke(0x01, 0xff); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::AddVxVy(1, 2).execute(&mut x); assert_eq!(x.registers.peek(0xf), 1); assert_eq!(x.registers.peek(1), 0); } #[test] fn shr_vx_vy_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 mut x = Chip8Computer::new(); x.registers.poke(0x0f, 0x00); x.registers.poke(0x01, 0b00001000); x.registers.poke(0x02, 0b00000000); Chip8CpuInstructions::ShrVxVy(0x1, 0x2).execute(&mut x); // 0b0000 0010 (0x02) (Not Set) assert_eq!(x.registers.peek(1), 0b00000100); assert_eq!(x.registers.peek(0xf), 0); x = Chip8Computer::new(); x.registers.poke(0x0f, 0x00); x.registers.poke(0x01, 0b00001001); Chip8CpuInstructions::ShrVxVy(0x1, 0x2).execute(&mut x); assert_eq!(x.registers.peek(1), 0b00000100); assert_eq!(x.registers.peek(0xf), 1); } #[test] fn ldi_addr_test() { let mut x = Chip8Computer::new(); Chip8CpuInstructions::LdIAddr(0x123).execute(&mut x); assert_eq!(x.registers.peek_i(), 0x123); assert_eq!(x.registers.peek_pc(), 0x202); } #[test] fn jp_v0addr_test() { let mut x = Chip8Computer::new(); /// jump to I + nnn x.registers.poke(0x0, 0xff); Chip8CpuInstructions::JpV0Addr(0x100).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x1FF); } #[test] fn cls_test() { let mut x = Chip8Computer::new(); Chip8CpuInstructions::CLS.execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); } #[test] fn skip_next_instruction_ne_text() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); Chip8CpuInstructions::SneVxByte(0x1, 0x0f).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); Chip8CpuInstructions::SneVxByte(0x1, 0xf0).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); } #[test] fn addivx_test() { let mut x = Chip8Computer::new(); x.registers.poke_i(0xabc); x.registers.poke(0x0, 0x10); Chip8CpuInstructions::AddIVx(0x0).execute(&mut x); assert_eq!(x.registers.peek_i(), 0xacc); } #[test] fn ldstvt_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); 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); } #[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 add_vx_byte_test() { let mut x = Chip8Computer::new(); // set a value in the register x.registers.poke(0x01, 0xab); // add 0x10 to register Chip8CpuInstructions::AddVxByte(0x1, 0x10).execute(&mut x); assert_eq!(x.registers.peek(1), 0xbb); } #[test] fn sub_vx_vy_test() { let mut x = Chip8Computer::new(); // load values in 2 registers x.registers.poke(0x1, 0x10); x.registers.poke(0x2, 0x08); Chip8CpuInstructions::SubVxVy(0x1, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0xf), 1); assert_eq!(x.registers.peek(0x1), 0x8); assert_eq!(x.registers.peek_pc(), 0x202); } #[test] fn sne_vx_vy_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x10); x.registers.poke(0x2, 0x10); Chip8CpuInstructions::SneVxVy(0x1, 0x2).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x10); x.registers.poke(0x2, 0x00); Chip8CpuInstructions::SneVxVy(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204) } #[test] fn ld_dt_vx_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x10); Chip8CpuInstructions::LdDtVx(0x1).execute(&mut x); assert_eq!(x.delay_timer.current(), 0x10); for i in 0..0x20 { x.delay_timer.tick(); } assert_eq!(x.delay_timer.current(), 0); } #[test] fn ld_vx_dt_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); Chip8CpuInstructions::LdDtVx(0x1).execute(&mut x); x.delay_timer.tick(); x.delay_timer.tick(); x.delay_timer.tick(); assert_eq!(x.delay_timer.current(), 0xed); } #[test] fn subn_vx_vy_test() { // This instruction subtracts the value in // register Vx from the value in register Vy and stores the result in register Vx. // The subtraction is performed as follows: Vx = Vy - Vx. If Vy is less than Vx, // the result will wrap around (due to the 8-bit nature of the registers). // The carry flag (VF) is set to 1 if there is no borrow (i.e., Vy is greater // than or equal to Vx), and it is set to 0 if there is a borrow. let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xa0); x.registers.poke(0x2, 0xab); Chip8CpuInstructions::SubnVxVy(0x1, 0x2).execute(&mut x); // expect the result to be 0x0b assert_eq!(x.registers.peek(0x1), 0x0b); // expect the vf register to be set to 1 as there was overflow assert_eq!(x.registers.peek(0xf), 0x1); let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0xab); x.registers.poke(0x02, 0xa0); Chip8CpuInstructions::SubnVxVy(0x1, 0x2).execute(&mut x); // expect the result to be 11110101, -0xB, -11, 245, 0xF5 assert_eq!(x.registers.peek(0x1), 0xf5); assert_eq!(x.registers.peek(0xf), 0x0); } #[test] fn shl_vx_vy_test_1() { // 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 mut x = Chip8Computer::new(); x.registers.poke(0x1, 0b00100000); Chip8CpuInstructions::ShlVxVy(0x1, 0x1).execute(&mut x); assert_eq!(x.registers.peek(0x1), 0b01000000); assert_eq!(x.registers.peek(0xf), 0x0); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0b10101010); Chip8CpuInstructions::ShlVxVy(0x1, 0x1).execute(&mut x); assert_eq!(x.registers.peek(0x1), 0b01010100); assert_eq!(x.registers.peek(0xf), 0x1); } #[test] fn ld_f_vx_test() { // 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. let mut x = Chip8Computer::new(); // target_sprite = 2 // target_offset = 0x5 x.registers.poke(0x1, 0x2); Chip8CpuInstructions::LdFVx(0x1).execute(&mut x); assert_eq!(x.registers.peek_i(), 10); let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x06); Chip8CpuInstructions::LdFVx(0x1).execute(&mut x); assert_eq!(x.registers.peek_i(), 30); } #[test] fn ld_b_vx_test() { // 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. let mut x = Chip8Computer::new(); // load the value 123 (0x7b) x.registers.poke(0x1, 0x7b); x.registers.poke_i(0x500); Chip8CpuInstructions::LdBVx(0x1).execute(&mut x); assert_eq!(x.memory.peek(0x500), 0x1); assert_eq!(x.memory.peek(0x501), 0x2); assert_eq!(x.memory.peek(0x502), 0x3); } #[test] fn ld_i_vx_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. let mut x = Chip8Computer::new(); // Load Registers. let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef]; for (idx, val) in to_load.iter().enumerate() { x.registers.poke(idx as u8, *val); } x.registers.poke_i(0x500); Chip8CpuInstructions::LdIVx(to_load.len() as u8).execute(&mut x); // Verify the values are in memory from 0x500 to 0x507 for (idx, value) in to_load.iter().enumerate() { assert_eq!(x.memory.peek(0x500 + idx as u16), *value); } } #[test] fn ld_vx_i_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. let mut x = Chip8Computer::new(); let base_offset = 0x500; let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef]; // start by setting values in memory for (idx, memory) in to_load.iter().enumerate() { let target_address = base_offset + idx; let target_value = *memory; x.memory.poke(target_address as u16, target_value); } // where to load from x.registers.poke_i(0x500); // how much to load x.registers.poke(0x6, to_load.len() as u8); // then copying them values memory to registers Chip8CpuInstructions::LdVxI(0x6).execute(&mut x); // now check that we have the right values in our registers for (idx, value) in to_load.iter().enumerate() { assert_eq!(x.registers.peek(idx as u8), *value); } } #[test] fn Sknkpvx_test() { // 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 mut x = Chip8Computer::new(); x.keypad.push_key(0x5); x.registers.poke(0x1, 0x5); Chip8CpuInstructions::SnkpVx(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); x.keypad.release_key(0x5); Chip8CpuInstructions::SnkpVx(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x206); } #[test] fn skpvx_test() { // 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 mut x = Chip8Computer::new(); x.keypad.push_key(0x5); x.registers.poke(0x1, 0x5); Chip8CpuInstructions::SkpVx(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); x.keypad.release_key(0x5); Chip8CpuInstructions::SkpVx(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x206); } #[test] fn draw_nibble_vx_vy_n_test() { let mut x = Chip8Computer::new(); let x_register = 0x1; let y_register = 0x2; let x_offset = 1; let y_offset = 2; let char_offset = 0x0A; // now lets set the X and Y to 1,2 x.registers.poke(x_register, x_offset); x.registers.poke(y_register, y_offset); x.registers.poke_i(char_offset); // we are using 5 rows. Chip8CpuInstructions::DrawVxVyNibble(x_register, y_register, 5).execute(&mut x); // now check that video memory has the values at // 1,2->1,9 // 2,2->2,9 // 3,2->3,9 // 4,2->4,9 // 5,2->5,9 // let byte_to_check = CHIP8FONT_0[0]; for row_in_sprite in 0..5 { let row_data = CHIP8FONT_2[row_in_sprite]; for bit_in_byte in 0..8 { let data_offset = (x_offset as u16 + row_in_sprite as u16) * 64 + (bit_in_byte + y_offset) as u16; let real_bit_in_byte = 7 - bit_in_byte; let shifted_one = 0x01 << real_bit_in_byte; let one_shift_set = (shifted_one & row_data) > 0; debug!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]"); debug!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}", bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, one_shift_set, row_data); } } } #[test] fn sub_test() { // 2nnn // Call a subroutine at 2nnn let mut x = Chip8Computer::new(); Chip8CpuInstructions::CallAddr(0x124).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x124); assert_eq!(x.stack.depth(), 1); Chip8CpuInstructions::CallAddr(0x564).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x564); assert_eq!(x.stack.depth(), 2); } #[test] fn ret_test() { // SETUP // Return from a subroutine. let mut x = Chip8Computer::new(); x.stack.push(&0x132); x.stack.push(&0xabc); // EXECUTE Chip8CpuInstructions::RET.execute(&mut x); // VERIFY assert_eq!(x.registers.peek_pc(), 0xabc); assert_eq!(x.stack.depth(), 1); // EXECUTE Chip8CpuInstructions::RET.execute(&mut x); // VERIFY assert_eq!(x.registers.peek_pc(), 0x132); assert_eq!(x.stack.depth(), 0); } #[test] fn ldvxk_test() { // SETUP let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x01); Chip8CpuInstructions::LdVxK(0x1).execute(&mut x); assert!(matches!(x.state, WaitingForKey)); } #[test] fn series8xy4_corex_tests() { /// 8xy4 /// Set Vx = Vx + Vy /// Set VF=1 if Carry /// // 1 + 1 let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x01); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0x02); assert_eq!(x.registers.peek(0x0f), 0x00); // 255+1 let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0xff); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0x00); assert_eq!(x.registers.peek(0x0f), 0x01); // 128+192 let mut x = Chip8Computer::new(); x.registers.poke(0x01, 128); x.registers.poke(0x02, 192); Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0x01), 64); assert_eq!(x.registers.peek(0x0f), 1); } #[test] fn series8xy6_corex_tests() { // 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 mut x = Chip8Computer::new(); // 0b10101010 -> 0b01010101 x.registers.poke(0x01, 0b10101010); x.registers.poke(0x0f, 0x0); Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b01010100); assert_eq!(x.registers.peek(0x0f), 1); Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b10101000); assert_eq!(x.registers.peek(0x0f), 0x00); Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b01010000); assert_eq!(x.registers.peek(0x0f), 0x01); Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b10100000); assert_eq!(x.registers.peek(0x0f), 0x00); Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b01000000); assert_eq!(x.registers.peek(0x0f), 0x01); } #[test] fn random_produces_different_numbers() { let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x00); let first_number = Chip8CpuInstructions::RndVxByte(0x01, 0xff).execute(&mut x).registers.peek(0x01); let second_number = Chip8CpuInstructions::RndVxByte(0x01, 0xff).execute(&mut x).registers.peek(0x01); assert_ne!(first_number, second_number); } }