diff --git a/emma/src/chip8/computer.rs b/emma/src/chip8/computer.rs index 08c9a56..8cf3d68 100644 --- a/emma/src/chip8/computer.rs +++ b/emma/src/chip8/computer.rs @@ -2,6 +2,7 @@ use log::{debug, error}; use crate::chip8::delay_timer::DelayTimer; use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION; use crate::chip8::sound_timer::SoundTimer; +use crate::chip8::util::InstructionUtil; use crate::constants::{CHIP8_MEMORY_SIZE, CHIP8_REGISTER_COUNT}; use super::{ @@ -39,6 +40,17 @@ impl Default for Chip8Computer { } impl Chip8Computer { + pub fn new_with_program(new_program: Box>) -> Self { + let mut working = Chip8Computer::new(); + for i in 0..new_program.len() { + let high_byte = (new_program[i as usize] >> 8) as u8; + let low_byte = (new_program[i] & 0xff) as u8; + let base_offset = i * 2; + working.memory.poke(base_offset as u16, high_byte); + working.memory.poke((base_offset + 1) as u16, low_byte); + } + working + } pub fn new() -> Self { Chip8Computer::default() } @@ -58,47 +70,19 @@ impl Chip8Computer { 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; - working_instruction = high_byte | low_byte; + + working_instruction = InstructionUtil::join_bytes(high_byte as u8, low_byte as u8); let decoded_instruction = - Chip8Computer::decode_instruction(working_instruction); + Chip8CpuInstructions::decode(working_instruction); - // println!("DECODED INSTRUCTION = {:?}", decoded_instruction); + // println!("DECODED INSTRUCTION = {:?}", decoded_instruction); // start by moving to the next instruction - self.pc += 2; + // todo: THIS IS BAD AND IS A SIDE EFFECT + decoded_instruction.execute(self); + /* match (self.state, decoded_instruction) { - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SysAddr(target_address)) => { - debug!("INST: SYS: {target_address}"); - self.pc = target_address as u16; - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::CLS) => { - debug!("INST: CLS"); - for i in 0..(64 * 32) { - self.video_memory.poke(i, false); - } - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::RET) => { - debug!("INST* RET"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::JpAddr(new_address)) => { - debug!("INST: JP_ADDR: {new_address:4x}"); - self.pc = new_address as u16; - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::CallAddr(sub_address)) => { - debug!("INST* CALL_ADDR: {sub_address:4x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SeVxByte(vx_register, byte)) => { - debug!("INST: SeVxByte: 0x{vx_register:1x}/0x{byte:2x}"); - self.registers[vx_register as usize] = byte as u8; - - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SneVxByte(vx_register, byte)) => { - debug!("INST: SneVxByte: 0x{vx_register:1x}/0x{byte:2x}"); - if self.registers[vx_register as usize] == byte as u8 { - self.pc += 2; - } - } (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] { @@ -110,7 +94,7 @@ impl Chip8Computer { } (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; + 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}"); @@ -128,296 +112,20 @@ impl Chip8Computer { 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(vx_register, vy_register)) => { + (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AddVxVy) => { debug!("INST: AddVxVy: {vx_register:1x}/{vy_register:1x}"); - self.registers[vx_register as usize] += self.registers[vy_register as usize]; } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SubVxVy(vx_register, vy_register)) => { - debug!("INST: SubVxVy: {vx_register:1x}/{vy_register:1x}"); - self.registers[vx_register as usize] -= self.registers[vy_register as usize]; - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::ShrVxVy(vx_register, vy_register)) => { - debug!("INST* ShrVxVy: {vx_register:1x}/{vy_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SubnVxVy(vx_register, vy_register)) => { - debug!("INST* SubnVxVy: {vx_register:1x}/{vy_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::ShlVxVy(vx_register, vy_register)) => { - debug!("INST* ShlVxVy: {vx_register:1x}/{vy_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SneVxVy(vx_register, vy_register)) => { - debug!("INST: SneVxVy: {vx_register:1x}/{vy_register:1x}"); - if self.registers[vx_register as usize] != self.registers[vy_register as usize] { - self.pc += 02; - } - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdIAddr(addr)) => { - debug!("INST* LdIAddr: [0x{addr:4x}]"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::JpV0Addr(addr)) => { - debug!("INST* JpV0Addr: 0x{addr:4x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::RndVxByte(vx_register, byte)) => { - debug!("INST* RndVxByte: 0x{vx_register:1x}/0x{byte:2x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::DrawVxVyNibble(vx_register, vy_register, nibble)) => { - debug!("INST* DrawVxVyNibble: 0x{vx_register:1x}/0x{vy_register:1x}/0x{nibble:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SkpVx(vx_register)) => { - debug!("INST* SkpVx: 0x{vx_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SnkpVx(vx_register)) => { - debug!("INST* SnkpVx: 0x{vx_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxDt(vx_register)) => { - debug!("INST* LdVxDt: 0x{vx_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxK(vx_register)) => { - debug!("INST* LdVxK: 0x{vx_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdDtVx(vx_register)) => { - debug!("INST* LdDtVx: 0x{vx_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdStVx(vx_register)) => { - debug!("INST* SkpVx: 0x{vx_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AddIVx(vx_register)) => { - debug!("INST* AddIVx: 0x{vx_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdFVx(_)) => { - debug!("INST* LdFVu:"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdBVx(vx_register)) => { - debug!("INST* LdBVx: 0x{vx_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdIVx(vx_register)) => { - debug!("INST* LdIVx: 0x{vx_register:1x}"); - } - (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxI(vx_register)) => { - debug!("INST* LdVxI: 0x{vx_register:1x}"); - } - _ => { - error!("UNABLE TO PROCEED. CPU IN UNKNOWN STATE"); - } + } + */ self.sound_timer.tick(); self.delay_timer.tick(); self } - // nnn or addr - A 12-bit value, the lowest 12 bits of the instruction - pub fn read_addr_from_instruction(instruction_to_read_from: u16) -> u16 { - instruction_to_read_from & 0b0000111111111111 - } - - // n or nibble - A 4-bit value, the lowest 4 bits of the instruction - pub fn read_nibble_from_instruction(instruction_to_read_from: u16) -> u16 { - instruction_to_read_from & 0b0000000000001111 - } - - // x - A 4-bit value, the lower 4 bits of the high byte of the instruction - pub fn read_x_from_instruction(instruction_to_read_from: u16) -> u16 { - (instruction_to_read_from & 0b0000111100000000).rotate_right(8) - } - - // y - A 4-bit value, the upper 4 bits of the low byte of the instruction - pub fn read_y_from_instruction(instruction_to_read_from: u16) -> u16 { - (instruction_to_read_from & 0b0000000011110000).rotate_right(4) - } - - // kk or byte - An 8-bit value, the lowest 8 bits of the instruction - pub fn read_byte_from_instruction(instruction_to_read_from: u16) -> u16 { - (instruction_to_read_from & 0b0000000011111111) - } - - pub fn read_upper_byte_lower_nibble(to_read_from: u16) -> u16 { - to_read_from & 0x0f00 - } - - fn decode_instruction(to_decode: u16) -> Chip8CpuInstructions { - let mut decoded_instruction = Chip8CpuInstructions::XXXXERRORINSTRUCTION; - - // pull out the various possible parameters for use further along when we - // acutally sort out what kind of instruction we have. - let x_param = Chip8Computer::read_x_from_instruction(to_decode); - let y_param = Chip8Computer::read_y_from_instruction(to_decode); - let addr_param = Chip8Computer::read_addr_from_instruction(to_decode); - let byte_param = Chip8Computer::read_byte_from_instruction(to_decode); - let nibble_param = Chip8Computer::read_nibble_from_instruction(to_decode); - let ubln = u16::rotate_right(Chip8Computer::read_upper_byte_lower_nibble(to_decode), 8); - let last_byte = to_decode & 0xFF; - - match to_decode { - 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 = to_decode & 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 => { - Chip8CpuInstructions::JpV0Addr(addr_param) - // JP V0, Addr - } - 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, to_decode, 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 - } - } - } } - #[cfg(test)] mod test { use rand::random; @@ -429,72 +137,27 @@ mod test { assert!(true) } - #[test] - fn decoder_test_valid_instructions() { - // valid instructions - assert!(matches!(Chip8Computer::decode_instruction(0x00E0u16), Chip8CpuInstructions::CLS )); - assert!(matches!(Chip8Computer::decode_instruction(0x00EE), Chip8CpuInstructions::RET)); - assert!(matches!(Chip8Computer::decode_instruction(0x0123), Chip8CpuInstructions::SysAddr(0x123))); - assert!(matches!(Chip8Computer::decode_instruction(0x0FFF), Chip8CpuInstructions::SysAddr(0xfff))); - assert!(matches!(Chip8Computer::decode_instruction(0x1002), Chip8CpuInstructions::JpAddr(0x2))); - assert!(matches!(Chip8Computer::decode_instruction(0x1FF0), Chip8CpuInstructions::JpAddr(0xFF0))); - assert!(matches!(Chip8Computer::decode_instruction(0x2002), Chip8CpuInstructions::CallAddr(0x2))); - assert!(matches!(Chip8Computer::decode_instruction(0x3123), Chip8CpuInstructions::SeVxByte(0x1, 0x23))); - assert!(matches!(Chip8Computer::decode_instruction(0x4abc), Chip8CpuInstructions::SneVxByte(0xa, 0xbc))); - assert!(matches!(Chip8Computer::decode_instruction(0x5ab0), Chip8CpuInstructions::SeVxVy(0xa, 0xb))); - assert!(matches!(Chip8Computer::decode_instruction(0x6aff), Chip8CpuInstructions::LdVxByte(0xa, 0xff))); - assert!(matches!(Chip8Computer::decode_instruction(0x7abc), Chip8CpuInstructions::AddVxByte(0xa, 0xbc))); - assert!(matches!(Chip8Computer::decode_instruction(0x8ab0), Chip8CpuInstructions::LdVxVy(0xa, 0xb))); - assert!(matches!(Chip8Computer::decode_instruction(0x8ba1), Chip8CpuInstructions::OrVxVy(0xb, 0xa))); - assert!(matches!(Chip8Computer::decode_instruction(0x8cd2), Chip8CpuInstructions::AndVxVy(0xc, 0xd))); - assert!(matches!(Chip8Computer::decode_instruction(0x8de3), Chip8CpuInstructions::XorVxVy(0xd, 0xe))); - assert!(matches!(Chip8Computer::decode_instruction(0x8ef4), Chip8CpuInstructions::AddVxVy(0xe, 0xf))); - assert!(matches!(Chip8Computer::decode_instruction(0x8f05), Chip8CpuInstructions::SubVxVy(0xf, 0x0))); - assert!(matches!(Chip8Computer::decode_instruction(0x8016), Chip8CpuInstructions::ShrVxVy(0x0, 0x1))); - assert!(matches!(Chip8Computer::decode_instruction(0x8127), Chip8CpuInstructions::SubnVxVy(0x1, 0x2))); - assert!(matches!(Chip8Computer::decode_instruction(0x834e), Chip8CpuInstructions::ShlVxVy(0x3, 0x4))); - assert!(matches!(Chip8Computer::decode_instruction(0x9ab0), Chip8CpuInstructions::SneVxVy(0xa, 0xb))); - assert!(matches!(Chip8Computer::decode_instruction(0xa123), Chip8CpuInstructions::LdIAddr(0x123))); - assert!(matches!(Chip8Computer::decode_instruction(0xb234), Chip8CpuInstructions::JpV0Addr(0x234))); - assert!(matches!(Chip8Computer::decode_instruction(0xcaca), Chip8CpuInstructions::RndVxByte(0xa, 0xca))); - assert!(matches!(Chip8Computer::decode_instruction(0xdab4), Chip8CpuInstructions::DrawVxVyNibble(0xa, 0xb, 0x4))); - assert!(matches!(Chip8Computer::decode_instruction(0xe19e), Chip8CpuInstructions::SkpVx(0x1))); - assert!(matches!(Chip8Computer::decode_instruction(0xe2a1), Chip8CpuInstructions::SnkpVx(0x2))); - assert!(matches!(Chip8Computer::decode_instruction(0xf107), Chip8CpuInstructions::LdVxDt(0x1))); - assert!(matches!(Chip8Computer::decode_instruction(0xf40a), Chip8CpuInstructions::LdVxK(0x4))); - assert!(matches!(Chip8Computer::decode_instruction(0xf615), Chip8CpuInstructions::LdDtVx(0x6))); - assert!(matches!(Chip8Computer::decode_instruction(0xfb18), Chip8CpuInstructions::LdStVx(0xb))); - assert!(matches!(Chip8Computer::decode_instruction(0xfd1e), Chip8CpuInstructions::AddIVx(0xd))); - assert!(matches!(Chip8Computer::decode_instruction(0xfc29), Chip8CpuInstructions::LdFVx(0xc))); - assert!(matches!(Chip8Computer::decode_instruction(0xfd33), Chip8CpuInstructions::LdBVx(0xd))); - assert!(matches!(Chip8Computer::decode_instruction(0xfe55), Chip8CpuInstructions::LdIVx(0xe))); - assert!(matches!(Chip8Computer::decode_instruction(0xf365), Chip8CpuInstructions::LdVxI(0x3))); - } - #[test] fn decoder_test_invalid_instructions() { // 'bad' instructions that should be dropped... - // assert!(matches!(Chip8Computer::decode_instruction(0x5ab1), Chip8CpuInstructions::XXXXERRORINSTRUCTION)); - // assert!(matches!(Chip8Computer::decode_instruction(0x8ab8), Chip8CpuInstructions::XXXXERRORINSTRUCTION)); - // assert!(matches!(Chip8Computer::decode_instruction(0xeaba), Chip8CpuInstructions::XXXXERRORINSTRUCTION)); + // assert!(matches!(Chip8Computer::decode_instruction(0x5ab1), Chip8CpuInstructions::XXXXERRORINSTRUCTION)); + // assert!(matches!(Chip8Computer::decode_instruction(0x8ab8), Chip8CpuInstructions::XXXXERRORINSTRUCTION)); + // assert!(matches!(Chip8Computer::decode_instruction(0xeaba), Chip8CpuInstructions::XXXXERRORINSTRUCTION)); } #[test] - fn cls_test() { - let mut start_system = Chip8Computer::new(); - let mut targets = Vec::new(); - for i in 0..10 { - let new_address: i16 = random(); - let new_address = new_address % CHIP8_MEMORY_SIZE as i16; - start_system.video_memory.poke(new_address as u16, true); - targets.push(new_address); - } - start_system.memory.poke(0x200, 0x00); - start_system.memory.poke(0x201, 0xe0); - start_system.step_system(); - for i in 0..CHIP8_MEMORY_SIZE { - assert!(!start_system.video_memory.peek(i as u16)); - } + fn new_with_program() { + // set a known program that sets some registers... + let new_program = Box::new(vec![ + Chip8CpuInstructions::LdVxI(0x10).encode(), + Chip8CpuInstructions::LdVxVy(0x1, 0x1).encode(), + Chip8CpuInstructions::LdStVx(0xff).encode() + ]); + + // ...then load it... + let test_computer = Chip8Computer::new_with_program(new_program); + + // ...and check the registers } } diff --git a/emma/src/chip8/delay_timer.rs b/emma/src/chip8/delay_timer.rs index 3f81824..3f030aa 100644 --- a/emma/src/chip8/delay_timer.rs +++ b/emma/src/chip8/delay_timer.rs @@ -22,4 +22,35 @@ impl DelayTimer { self.counter -= 1; } } +} + +#[cfg(test)] +mod test { + use crate::chip8::sound_timer::SoundTimer; + use super::*; + + #[test] + fn smoke() { + assert!(true) + } + + #[test] + fn ticks_reduce_time() { + let mut st = DelayTimer::new(); + st.set_timer(100); + st.tick(); + st.tick(); + st.tick(); + assert_eq!(st.counter, 97); + } + + #[test] + fn out_of_ticks_works() { + let mut st = DelayTimer::new(); + st.set_timer(0); + st.tick(); + st.tick(); + st.tick(); + assert_eq!(st.counter, 0); + } } \ No newline at end of file diff --git a/emma/src/chip8/instructions.rs b/emma/src/chip8/instructions.rs index d166c9d..73c9dc5 100644 --- a/emma/src/chip8/instructions.rs +++ b/emma/src/chip8/instructions.rs @@ -1,3 +1,8 @@ +use crate::chip8::computer::{Chip8Computer}; +use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION; +use crate::chip8::util::InstructionUtil; +use crate::chip8::video::Chip8Video; + #[derive(Debug)] pub enum Chip8CpuInstructions { SysAddr(i16), // 0x0nnn Exit to System Call @@ -38,154 +43,483 @@ pub enum Chip8CpuInstructions { XXXXERRORINSTRUCTION, } -#[derive(Clone)] -struct Chip8SystemState { - pub video_memory: [bool; 512], - pub memory: [u8; 4096], - pub registers: [u8; 16], - pub i: i16, - pub pc: i16, - pub sound_timer: i8, - pub delay_timer: i8 -} -trait Chip8Instruction { - fn execute(&self, input: Chip8SystemState) -> Chip8SystemState; -} -impl Chip8Instruction for Chip8CpuInstructions { - fn execute(&self, mut input: Chip8SystemState) -> Chip8SystemState { +impl Chip8CpuInstructions { + pub fn encode(&self) -> u16 { match self { - Chip8CpuInstructions::SysAddr(new_address) => { - let mut new_state = input.clone(); - new_state.pc = *new_address; - new_state + Chip8CpuInstructions::SysAddr(target) => { + (0x0000 | (target & 0x0FFF)) as u16 } Chip8CpuInstructions::CLS => { - input.video_memory = [false; 512]; - input + 0x00E0 } Chip8CpuInstructions::RET => { - input + 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 + } + } + } +} + +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); + 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 => { + Chip8CpuInstructions::JpV0Addr(addr_param) + // JP V0, Addr + } + 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 { + input.pc += 2; + let _ = match self { + Chip8CpuInstructions::SysAddr(new_address) => { + let mut new_state = input.clone(); + new_state.pc = *new_address as u16; + } + Chip8CpuInstructions::CLS => { + for i in 0..(64 * 32) { + input.video_memory.poke(i, false); + } + } + Chip8CpuInstructions::RET => {} Chip8CpuInstructions::JpAddr(new_address) => { - input + input.pc = *new_address as u16 } - Chip8CpuInstructions::CallAddr(_) => { - input + Chip8CpuInstructions::CallAddr(_) => {} + Chip8CpuInstructions::SeVxByte(vx_register, byte) => { + input.registers[*vx_register as usize] = *byte as u8; } - Chip8CpuInstructions::SeVxByte(_, _) => { - input - } - Chip8CpuInstructions::SneVxByte(_, _) => { - input - } - Chip8CpuInstructions::SeVxVy(_, _) => { - input + Chip8CpuInstructions::SneVxByte(vx_register, byte) => { + if input.registers[*vx_register as usize] == *byte as u8 { + input.pc += 2; + } } + Chip8CpuInstructions::SeVxVy(_, _) => {} Chip8CpuInstructions::LdVxByte(register, byte) => { input.registers[*register as usize] = byte.to_be_bytes()[0]; - input } - Chip8CpuInstructions::AddVxByte(_, _) => { - input + 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]; } - Chip8CpuInstructions::LdVxVy(_, _) => { - input + Chip8CpuInstructions::SubVxVy(vx_register, vy_register) => { + input.registers[*vx_register as usize] -= input.registers[*vy_register as usize]; } - Chip8CpuInstructions::OrVxVy(_, _) => { - input - } - Chip8CpuInstructions::AndVxVy(_, _) => { - input - } - Chip8CpuInstructions::XorVxVy(_, _) => { - input - } - Chip8CpuInstructions::AddVxVy(_, _) => { - input - } - Chip8CpuInstructions::SubVxVy(_, _) => { - input - } - Chip8CpuInstructions::ShrVxVy(_, _) => { - input - } - Chip8CpuInstructions::SubnVxVy(_, _) => { - input - } - Chip8CpuInstructions::ShlVxVy(_, _) => { - input - } - Chip8CpuInstructions::SneVxVy(_, _) => { - input + 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; + } } Chip8CpuInstructions::LdIAddr(new_index) => { - input.i = *new_index as i16; - input - } - Chip8CpuInstructions::JpV0Addr(_) => { - input - } - Chip8CpuInstructions::RndVxByte(_, _) => { - input + input.i_register = *new_index; } + Chip8CpuInstructions::JpV0Addr(_) => {} + Chip8CpuInstructions::RndVxByte(_, _) => {} Chip8CpuInstructions::DrawVxVyNibble(x, y, n) => { // 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 + draw_x as i16) as usize]; - for draw_y in 0..8 { - } + // let mut new_value = input.memory[(input.i_register + draw_x) as usize]; + //for draw_y in 0..8 { + // } } - if did_change { input.registers[0xF] = 1u8; } else { input.registers[0xF] = 0u8; } - input } - Chip8CpuInstructions::SkpVx(_) => { - input - } - Chip8CpuInstructions::SnkpVx(_) => { - input - } - Chip8CpuInstructions::LdVxDt(_) => { - input - } - Chip8CpuInstructions::LdVxK(_) => { - input - } - Chip8CpuInstructions::LdDtVx(_) => { - input - } - Chip8CpuInstructions::LdStVx(_) => { - input - } - Chip8CpuInstructions::AddIVx(_) => { - input - } - Chip8CpuInstructions::LdFVx(_) => { - input - } - Chip8CpuInstructions::LdBVx(_) => { - input + Chip8CpuInstructions::SkpVx(_) => {} + Chip8CpuInstructions::SnkpVx(_) => {} + Chip8CpuInstructions::LdVxDt(_) => {} + Chip8CpuInstructions::LdVxK(_) => {} + Chip8CpuInstructions::LdDtVx(_) => {} + Chip8CpuInstructions::LdStVx(_) => {} + Chip8CpuInstructions::AddIVx(_) => {} + Chip8CpuInstructions::LdFVx(_) => {} + Chip8CpuInstructions::LdBVx(_) => {} + Chip8CpuInstructions::LdIVx(_) => {} + Chip8CpuInstructions::LdVxI(_) => {} + Chip8CpuInstructions::XXXXERRORINSTRUCTION => {} + }; + *input + } +} - } - Chip8CpuInstructions::LdIVx(_) => { - input - } - Chip8CpuInstructions::LdVxI(_) => { - input - } - Chip8CpuInstructions::XXXXERRORINSTRUCTION => { - input - } - } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn smoke() { + assert!(true) + } + + #[test] + fn encoder_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); + } + + #[test] + fn decoder_test() { + assert!(matches!( Chip8CpuInstructions::decode(0x00E0u16), Chip8CpuInstructions::CLS)); + assert!(matches!( Chip8CpuInstructions::decode(0x00EEu16), Chip8CpuInstructions::RET)); + assert!(matches!(Chip8CpuInstructions::decode(0x0123), Chip8CpuInstructions::SysAddr(0x123))); + 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))); } } \ No newline at end of file diff --git a/emma/src/chip8/sound_timer.rs b/emma/src/chip8/sound_timer.rs index 376f8d6..9c35020 100644 --- a/emma/src/chip8/sound_timer.rs +++ b/emma/src/chip8/sound_timer.rs @@ -6,12 +6,10 @@ use dimensioned::si; pub struct SoundTimer { counter: i32 } - impl SoundTimer { pub fn current(&self) -> i32 { self.counter } - pub fn new() -> Self { SoundTimer { counter: 0 @@ -24,10 +22,44 @@ impl SoundTimer { pub fn tick(&mut self) { if self.counter > 0 { self.counter -= 1; - thread::spawn(|| { + /* + todo: this breaks tests. maybe its wrong? + if let good_thread = thread::spawn(|| { beep(440).expect("Unable to beep"); thread::sleep(time::Duration::from_millis(500)); - }).join().expect("Unable to spawn beep thread"); + }).join().unwrap(). + */ } } } + + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn smoke() { + assert!(true) + } + + #[test] + fn ticks_reduce_time() { + let mut st = SoundTimer::new(); + st.set_timer(100); + st.tick(); + st.tick(); + st.tick(); + assert_eq!(st.counter, 97); + } + + #[test] + fn out_of_ticks_works() { + let mut st = SoundTimer::new(); + st.set_timer(0); + st.tick(); + st.tick(); + st.tick(); + assert_eq!(st.counter, 0); + } +} \ No newline at end of file diff --git a/emma/src/chip8/system_memory.rs b/emma/src/chip8/system_memory.rs index 06901f0..9db61a7 100644 --- a/emma/src/chip8/system_memory.rs +++ b/emma/src/chip8/system_memory.rs @@ -61,14 +61,6 @@ const cell_height: i32 = 5i32; impl Chip8SystemMemory { - pub fn poke_multi(&mut self, start_offset: u16, data: Vec) { - // loop through the data... - // poke all the data into memory offset by the start - let data_len = data.len(); - for i in 0..data_len { - self.poke(start_offset + i as u16, data[i]); - } - } pub fn peek(self, address: u16) -> u8 { trace!("PEEK: {} / {}", address, self.memory[address as usize].clone()); self.memory[address as usize] @@ -79,6 +71,8 @@ impl Chip8SystemMemory { self.memory[address as usize] = value; } + + pub fn load_program(&mut self, program_to_load: Box>) { for load_index in 0..program_to_load.len() { self.poke((load_index + 0x200) as u16, program_to_load[load_index]); diff --git a/emma/src/chip8/util.rs b/emma/src/chip8/util.rs new file mode 100644 index 0000000..e16d70c --- /dev/null +++ b/emma/src/chip8/util.rs @@ -0,0 +1,87 @@ + +pub struct InstructionUtil {} +impl InstructionUtil { + + pub fn split_bytes(to_split: u16) -> (u8, u8) { + let high = to_split.rotate_left(8) as u8; + let low = (to_split & 0xff) as u8; + + (high, low) + } + + pub fn join_bytes(high: u8, low: u8) -> u16 { + (high as u16 )<< 8 & low as u16 + } + + // nnn or addr - A 12-bit value, the lowest 12 bits of the instruction + pub fn read_addr_from_instruction(instruction_to_read_from: u16) -> u16 { + instruction_to_read_from & 0x0FFF + } + + // n or nibble - A 4-bit value, the lowest 4 bits of the instruction + pub fn read_nibble_from_instruction(instruction_to_read_from: u16) -> u16 { + instruction_to_read_from & 0x000F + } + + // x - A 4-bit value, the lower 4 bits of the high byte of the instruction + pub fn read_x_from_instruction(instruction_to_read_from: u16) -> u16 { + (instruction_to_read_from & 0x0F00).rotate_right(8) + } + + // y - A 4-bit value, the upper 4 bits of the low byte of the instruction + pub fn read_y_from_instruction(instruction_to_read_from: u16) -> u16 { + (instruction_to_read_from & 0x00F0).rotate_right(4) + } + + // kk or byte - An 8-bit value, the lowest 8 bits of the instruction + pub fn read_byte_from_instruction(instruction_to_read_from: u16) -> u16 { + (instruction_to_read_from & 0x00FF) + } + + pub fn read_upper_byte_lower_nibble(to_read_from: u16) -> u16 { + to_read_from & 0x0f00 + } +} + + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn smoke() { + assert!(true) + } + + #[test] + fn split_bytes() { + // from 0xABCD we should have AB high, CD low + let (low, high) = InstructionUtil::split_bytes(0xabcd); + assert_eq!(low, 0xAB); + assert_eq!(high, 0xCD); + } + + #[test] + fn join_bytes() { + // from 0xAB low and 0xCD high we get 0xABCD + let merged = InstructionUtil::join_bytes( 0xcd, 0xab); + assert_eq!(merged, 0xcdab); + } + + #[test] + fn read_from_instruction() { + // from 0xABCD + let source = 0xABCD; + assert_eq!(InstructionUtil::read_addr_from_instruction(source), 0xBCD); + assert_eq!(InstructionUtil::read_nibble_from_instruction(source), 0xD); + assert_eq!(InstructionUtil::read_x_from_instruction(source), 0xB); + assert_eq!(InstructionUtil::read_y_from_instruction(source), 0xC); + assert_eq!(InstructionUtil::read_byte_from_instruction(source), 0xCD); + } + + #[test] + fn ubln() { + // from 0xABCD we should see B + assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0xABCD), 0xB << 8); + } +} \ No newline at end of file diff --git a/emma/src/lib.rs b/emma/src/lib.rs index e2943f8..e4ff44d 100644 --- a/emma/src/lib.rs +++ b/emma/src/lib.rs @@ -7,6 +7,7 @@ pub mod chip8 { pub mod system_memory; pub mod instructions; pub mod cpu_states; + pub mod util; } pub mod constants; \ No newline at end of file