From 462a4474893ce7e2c8e10499b4288200d06725f2 Mon Sep 17 00:00:00 2001 From: Atreya Bain Date: Sat, 10 Aug 2024 10:26:27 +0530 Subject: [PATCH] [feat] add arg to delete if invalid --- src/args.rs | 28 +++++--- src/device/device.rs | 66 +++++++++++------ src/device/instruction.rs | 146 +++++++++++++++----------------------- src/main.rs | 7 +- 4 files changed, 126 insertions(+), 121 deletions(-) diff --git a/src/args.rs b/src/args.rs index 0439ae0..fe57269 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,12 +1,24 @@ use clap::Parser; -#[derive(Parser,Debug,Clone)] -#[command(version,about,author)] +#[derive(Parser, Debug, Clone)] +#[command(version, about, author)] pub struct Porcel8ProgramArgs { - #[arg(short,long,help = "Filename of ROM to load.")] - pub filename:Option, - #[arg(short,long,help = "Draw scale of window",default_value_t=8f32)] + #[arg(short, long, help = "Filename of ROM to load.")] + pub filename: Option, + #[arg(short, long, help = "Draw scale of window", default_value_t = 8f32)] pub draw_scale: f32, - #[arg(short,long,help = "Emulate new behaviour of instructions (As seen in Chip-48 and SuperChip8)",default_value_t=true)] - pub new_chip8_behaviour: bool -} \ No newline at end of file + #[arg( + short, + long, + help = "Emulate new behaviour of instructions (As seen in Chip-48 and SuperChip8)", + default_value_t = true + )] + pub new_chip8_behaviour: bool, + #[arg( + short='i', + long, + help = "Halt on invalid instruction", + default_value_t = false + )] + pub halt_on_invalid: bool, +} diff --git a/src/device/device.rs b/src/device/device.rs index 5badf4f..7080532 100644 --- a/src/device/device.rs +++ b/src/device/device.rs @@ -1,10 +1,9 @@ -use std::sync::{Arc, Mutex}; -use rand::random; -use crate::device::instruction::Instruction; +use crate::{device::instruction::Instruction, util::EmulatorError}; use crate::device::keyboard::Keyboard; use crate::device::timer::DeviceTimerManager; use crate::util::EmulatorResult; - +use rand::random; +use std::sync::{Arc, Mutex}; pub struct Device { pub registers: RegisterFile, @@ -13,6 +12,7 @@ pub struct Device { pub stack: Vec, pub frame_buffer: Arc>>, pub new_chip8_mode: bool, + pub halt_on_invalid: bool, pub device_keyboard: Keyboard, } @@ -21,14 +21,24 @@ impl Device { pub const FRAME_BUFFER_WIDTH: usize = 64; pub const FRAME_BUFFER_HEIGHT: usize = 32; pub const FRAME_BUFFER_SIZE: usize = Self::FRAME_BUFFER_WIDTH * Self::FRAME_BUFFER_HEIGHT; - pub fn new(timer: DeviceTimerManager, fb: Arc>>, device_keyboard: Keyboard, new_chip8_mode: bool) -> Device { - let memory = vec![0u8; Self::DEVICE_MEMORY_SIZE].into_boxed_slice().try_into().unwrap(); + pub fn new( + timer: DeviceTimerManager, + fb: Arc>>, + device_keyboard: Keyboard, + new_chip8_mode: bool, + halt_on_invalid: bool + ) -> Device { + let memory = vec![0u8; Self::DEVICE_MEMORY_SIZE] + .into_boxed_slice() + .try_into() + .unwrap(); log::trace!("Successfully initiated device memory"); Device { registers: RegisterFile::new(), memory, frame_buffer: fb, stack: Vec::with_capacity(16), + halt_on_invalid, timer, new_chip8_mode, device_keyboard, @@ -41,9 +51,7 @@ impl Device { const FONT_DEFAULT_MEM_LOCATION_START: usize = 0x50; const FONT_DEFAULT_MEM_LOCATION_END: usize = 0x9F; const ROM_START: usize = 0x200; - - - + pub fn cycle(&mut self) -> EmulatorResult<()> { self.device_keyboard.update_keyboard()?; @@ -60,13 +68,19 @@ impl Device { } pub fn execute_instruction(&mut self, instruction: Instruction) -> EmulatorResult<()> { // thread::sleep(Duration::from_millis(250)); - log::trace!("Executing {:?}, {:?}",&instruction,&self.registers); + log::trace!("Executing {:?}, {:?}", &instruction, &self.registers); match instruction { - Instruction::PassThrough => { + Instruction::InvalidInstruction => { log::info!("Executing passthrough"); - } + if self.halt_on_invalid { + return Err(EmulatorError::IOError("Caught Invalid Instruction".to_string())); + } + }, Instruction::ClearScreen => { - let mut frame_buffer = self.frame_buffer.lock().expect("Failed to grab framebuffer for drawing"); + let mut frame_buffer = self + .frame_buffer + .lock() + .expect("Failed to grab framebuffer for drawing"); for pixel in frame_buffer.iter_mut() { *pixel = false; } @@ -154,7 +168,8 @@ impl Device { } Instruction::Add(x, y) => { let left = self.registers.v[x]; - let (wrapped_addition_result, is_overflow) = left.overflowing_add(self.registers.v[y]); + let (wrapped_addition_result, is_overflow) = + left.overflowing_add(self.registers.v[y]); self.registers.v[x] = wrapped_addition_result; self.set_flag_register(is_overflow); } @@ -167,7 +182,8 @@ impl Device { } Instruction::RSub(x, y) => { let left = self.registers.v[y]; - let (wrapped_subtraction_result, is_overflow) = left.overflowing_sub(self.registers.v[x]); + let (wrapped_subtraction_result, is_overflow) = + left.overflowing_sub(self.registers.v[x]); self.registers.v[x] = wrapped_subtraction_result; self.set_flag_register(!is_overflow); } @@ -223,7 +239,8 @@ impl Device { } Instruction::SetIndexToFontCharacter(x) => { let requested_char = self.registers.v[x]; - let font_address = Self::FONT_DEFAULT_MEM_LOCATION_START as u16 + Self::FONT_HEIGHT * requested_char as u16; + let font_address = Self::FONT_DEFAULT_MEM_LOCATION_START as u16 + + Self::FONT_HEIGHT * requested_char as u16; self.registers.i = font_address; } Instruction::DoBCDConversion(x) => { @@ -267,7 +284,10 @@ impl Device { /// Draw a sprite at location at (x,y) for n pixels long and 8 pixels wide. /// Returns whether any pixel was toggled fn draw_sprite_at_location(&mut self, x: usize, y: usize, n: u8) -> bool { - let mut frame_buffer = self.frame_buffer.lock().expect("Failed to grab framebuffer for drawing"); + let mut frame_buffer = self + .frame_buffer + .lock() + .expect("Failed to grab framebuffer for drawing"); let mut is_pixel_toggled_off = false; for i in 0..n as usize { @@ -286,8 +306,11 @@ impl Device { let bit_is_true = (slice_from_memory & (1 << bit_index)) == (1 << bit_index); // if the pixel is going to be toggled false, set this flag bit to true - if frame_buffer[index + (7 - bit_index)] && (bit_is_true) { is_pixel_toggled_off = true; } - frame_buffer[index + (7 - bit_index)] = frame_buffer[index + (7 - bit_index)] ^ (bit_is_true); + if frame_buffer[index + (7 - bit_index)] && (bit_is_true) { + is_pixel_toggled_off = true; + } + frame_buffer[index + (7 - bit_index)] = + frame_buffer[index + (7 - bit_index)] ^ (bit_is_true); } } is_pixel_toggled_off @@ -313,10 +336,11 @@ impl Device { 0xF0, 0x80, 0x80, 0x80, 0xF0, // C 0xE0, 0x90, 0x90, 0x90, 0xE0, // D 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E - 0xF0, 0x80, 0xF0, 0x80, 0x80 // F + 0xF0, 0x80, 0xF0, 0x80, 0x80, // F ]; log::info!("Loaded default font from memory"); - self.memory[Self::FONT_DEFAULT_MEM_LOCATION_START..=Self::FONT_DEFAULT_MEM_LOCATION_END].copy_from_slice(&DEFAULT_FONT); + self.memory[Self::FONT_DEFAULT_MEM_LOCATION_START..=Self::FONT_DEFAULT_MEM_LOCATION_END] + .copy_from_slice(&DEFAULT_FONT); } /// load a rom from bytes pub fn load_rom(&mut self, rom: &[u8]) { diff --git a/src/device/instruction.rs b/src/device/instruction.rs index a08582a..1231714 100644 --- a/src/device/instruction.rs +++ b/src/device/instruction.rs @@ -2,8 +2,8 @@ use byteorder::{BigEndian, ByteOrder}; #[derive(Eq, PartialEq, Debug)] pub enum Instruction { - /// Blanket instruction that does nothing - PassThrough, + /// Invalid instruction that may be skipped or raise error + InvalidInstruction, /// 00E0 - Clear the screen ClearScreen, /// 00EE - Return from procedure @@ -26,14 +26,14 @@ pub enum Instruction { ConditionalInEqRegisterSkipNext(usize, usize), /// ANNN - Set index value SetIndex(u16), - /// B(X+N)NN - Jump to address with offset. Either v0 or specified register + /// B(X+N)NN - Jump to address with offset. Either v0 or specified register JumpWithOffset(usize, u16), /// CXNN - AND a random number with NN and place in register RandomAnd(usize, u8), /// DXYN - Draw pixels at xy pointed by register for n bytes long Draw(usize, usize, u8), - /// EX9E - Check if key is pressed + /// EX9E - Check if key is pressed SkipIfKeyPressed(usize), /// EXA1 - Check if key is not pressed SkipIfKeyNotPressed(usize), @@ -83,22 +83,14 @@ impl Instruction { let instruction = BigEndian::read_u16(location); let outer_instruction_nibble = (instruction & 0xF000) >> 12; match outer_instruction_nibble { - 0x0 if instruction == 0xe0 => { - Instruction::ClearScreen - } - 0x0 if instruction == 0xee => { - Instruction::ReturnFromProcedure - } + 0x0 if instruction == 0xe0 => Instruction::ClearScreen, + 0x0 if instruction == 0xee => Instruction::ReturnFromProcedure, 0x0 => { - log::warn!("Ignoring unsupported instruction {}",instruction); - Instruction::PassThrough - } - 0x1 => { - Instruction::JumpTo(instruction & 0xfff) - } - 0x2 => { - Instruction::JumpAndLink(instruction & 0xfff) + log::warn!("Ignoring unsupported instruction {}", instruction); + Instruction::InvalidInstruction } + 0x1 => Instruction::JumpTo(instruction & 0xfff), + 0x2 => Instruction::JumpAndLink(instruction & 0xfff), 0x3 => { let register = (instruction & 0xf00) >> 8; let val = instruction & 0xff; @@ -115,24 +107,25 @@ impl Instruction { Instruction::ConditionalEqRegisterSkipNext(register_x as usize, register_y as usize) } - 0x6 => { - Instruction::SetRegister(((instruction & 0x0f00) >> 8) as usize, (instruction & 0xff) as u8) - } - 0x7 => { - Instruction::AddValueToRegister(((instruction & 0x0f00) >> 8) as usize, (instruction & 0xff) as u8) - } - 0x8 => { - Self::decode_arithmetic_instruction(instruction) - } + 0x6 => Instruction::SetRegister( + ((instruction & 0x0f00) >> 8) as usize, + (instruction & 0xff) as u8, + ), + 0x7 => Instruction::AddValueToRegister( + ((instruction & 0x0f00) >> 8) as usize, + (instruction & 0xff) as u8, + ), + 0x8 => Self::decode_arithmetic_instruction(instruction), 0x9 => { let register_x = (instruction & 0xf00) >> 8; let register_y = (instruction & 0xf0) >> 4; - Instruction::ConditionalInEqRegisterSkipNext(register_x as usize, register_y as usize) - } - 0xA => { - Instruction::SetIndex(instruction & 0xfff) + Instruction::ConditionalInEqRegisterSkipNext( + register_x as usize, + register_y as usize, + ) } + 0xA => Instruction::SetIndex(instruction & 0xfff), 0xB => { let register_x = (instruction & 0xf00) >> 8; let jump_address_base = instruction & 0xfff; @@ -152,45 +145,45 @@ impl Instruction { 0xE if (instruction & 0xff) == 0x9e => { let x = (instruction & 0xf00) >> 8; Instruction::SkipIfKeyPressed(x as usize) - }, - 0xE if (instruction & 0xff) == 0xa1 => { + } + 0xE if (instruction & 0xff) == 0xa1 => { let x = (instruction & 0xf00) >> 8; Instruction::SkipIfKeyNotPressed(x as usize) } - 0xF if (instruction & 0xff) == 0x07 =>{ + 0xF if (instruction & 0xff) == 0x07 => { let x = (instruction & 0xf00) >> 8; Instruction::FetchDelayTimer(x as usize) } - 0xF if (instruction & 0xff) == 0x15 =>{ + 0xF if (instruction & 0xff) == 0x15 => { let x = (instruction & 0xf00) >> 8; Instruction::SetDelayTimer(x as usize) } - 0xF if (instruction & 0xff) == 0x18 =>{ + 0xF if (instruction & 0xff) == 0x18 => { let x = (instruction & 0xf00) >> 8; Instruction::SetSoundTimer(x as usize) } - 0xF if (instruction & 0xff) == 0x1E =>{ + 0xF if (instruction & 0xff) == 0x1E => { let x = (instruction & 0xf00) >> 8; Instruction::AddToIndex(x as usize) } - 0xF if (instruction & 0xff) == 0x0A =>{ + 0xF if (instruction & 0xff) == 0x0A => { let x = (instruction & 0xf00) >> 8; Instruction::GetKey(x as usize) } //TODO add tests from here - 0xF if (instruction & 0xff) == 0x29 =>{ + 0xF if (instruction & 0xff) == 0x29 => { let x = (instruction & 0xf00) >> 8; Instruction::SetIndexToFontCharacter(x as usize) } - 0xF if (instruction & 0xff) == 0x33 =>{ + 0xF if (instruction & 0xff) == 0x33 => { let x = (instruction & 0xf00) >> 8; Instruction::DoBCDConversion(x as usize) } - 0xF if (instruction & 0xff) == 0x55 =>{ + 0xF if (instruction & 0xff) == 0x55 => { let x = (instruction & 0xf00) >> 8; Instruction::StoreRegistersToMemory(x as usize) } - 0xF if (instruction & 0xff) == 0x65 =>{ + 0xF if (instruction & 0xff) == 0x65 => { let x = (instruction & 0xf00) >> 8; Instruction::LoadRegistersFromMemory(x as usize) } @@ -206,36 +199,18 @@ impl Instruction { let reg_y = ((instruction & 0xf0) >> 4) as usize; let operation = instruction & 0xf; match operation { - 0 => { - Instruction::Set(reg_x, reg_y) - } - 1 => { - Instruction::Or(reg_x, reg_y) - } - 2 => { - Instruction::And(reg_x, reg_y) - } - 3 => { - Instruction::Xor(reg_x, reg_y) - } - 4 => { - Instruction::Add(reg_x, reg_y) - } - 5 => { - Instruction::Sub(reg_x, reg_y) - } - 6 => { - Instruction::RShift(reg_x, reg_y) - } - 7 => { - Instruction::RSub(reg_x, reg_y) - } - 0xe => { - Instruction::LShift(reg_x, reg_y) - } + 0 => Instruction::Set(reg_x, reg_y), + 1 => Instruction::Or(reg_x, reg_y), + 2 => Instruction::And(reg_x, reg_y), + 3 => Instruction::Xor(reg_x, reg_y), + 4 => Instruction::Add(reg_x, reg_y), + 5 => Instruction::Sub(reg_x, reg_y), + 6 => Instruction::RShift(reg_x, reg_y), + 7 => Instruction::RSub(reg_x, reg_y), + 0xe => Instruction::LShift(reg_x, reg_y), _ => { - log::error!("Encountered unexpected alu instruction {}",instruction); - Instruction::PassThrough + log::error!("Encountered unexpected alu instruction {}", instruction); + Instruction::InvalidInstruction } } } @@ -265,11 +240,10 @@ mod tests { for instruction_hex in [0xf0u16, 0x0, 0x1, 0x10] { let instruction_bytes = instruction_hex.to_be_bytes(); let instruction = Instruction::decode_instruction(&instruction_bytes); - assert_eq!(instruction, PassThrough) + assert_eq!(instruction, InvalidInstruction) } } - #[test] fn test_jump_to_instruction_1() { let instruction_bytes = 0x1123_u16.to_be_bytes(); @@ -361,7 +335,6 @@ mod tests { assert_eq!(ins, RandomAnd(0xa, 0xbd)); } - #[test] fn test_draw() { let instruction_bytes = 0xdfab_u16.to_be_bytes(); @@ -432,71 +405,70 @@ mod tests { assert_eq!(ins, LShift(0x1, 0x2)) } #[test] - fn test_skip_if_keypress(){ + fn test_skip_if_keypress() { let instruction_bytes = 0xef9e_u16.to_be_bytes(); let ins = Instruction::decode_instruction(&instruction_bytes); assert_eq!(ins, SkipIfKeyPressed(0xf)) } #[test] - fn test_skip_if_not_keypress(){ + fn test_skip_if_not_keypress() { let instruction_bytes = 0xeba1_u16.to_be_bytes(); let ins = Instruction::decode_instruction(&instruction_bytes); assert_eq!(ins, SkipIfKeyNotPressed(0xb)) } #[test] - fn test_fetch_delay_timer(){ + fn test_fetch_delay_timer() { let instruction_bytes = 0xfa07_u16.to_be_bytes(); let ins = Instruction::decode_instruction(&instruction_bytes); assert_eq!(ins, FetchDelayTimer(0xa)) } #[test] - fn test_set_delay_timer(){ + fn test_set_delay_timer() { let instruction_bytes = 0xfb15_u16.to_be_bytes(); let ins = Instruction::decode_instruction(&instruction_bytes); assert_eq!(ins, SetDelayTimer(0xb)) } #[test] - fn test_set_sound_timer(){ + fn test_set_sound_timer() { let instruction_bytes = 0xfc18_u16.to_be_bytes(); let ins = Instruction::decode_instruction(&instruction_bytes); assert_eq!(ins, SetSoundTimer(0xc)) } #[test] - fn test_add_to_index(){ + fn test_add_to_index() { let instruction_bytes = 0xfb1e_u16.to_be_bytes(); let ins = Instruction::decode_instruction(&instruction_bytes); assert_eq!(ins, AddToIndex(0xb)) } #[test] - fn test_get_key(){ + fn test_get_key() { let instruction_bytes = 0xf50a_u16.to_be_bytes(); let ins = Instruction::decode_instruction(&instruction_bytes); assert_eq!(ins, GetKey(0x5)) } #[test] - fn test_set_index_to_font_char(){ + fn test_set_index_to_font_char() { let instruction_bytes = 0xfb29_u16.to_be_bytes(); let ins = Instruction::decode_instruction(&instruction_bytes); assert_eq!(ins, SetIndexToFontCharacter(0xb)) } #[test] - fn test_do_bcd_conversion(){ + fn test_do_bcd_conversion() { let instruction_bytes = 0xfd33_u16.to_be_bytes(); let ins = Instruction::decode_instruction(&instruction_bytes); assert_eq!(ins, DoBCDConversion(0xd)) } #[test] - fn test_store_regs_to_mem(){ + fn test_store_regs_to_mem() { let instruction_bytes = 0xfb55_u16.to_be_bytes(); let ins = Instruction::decode_instruction(&instruction_bytes); assert_eq!(ins, StoreRegistersToMemory(0xb)) } #[test] - fn test_load_regs_to_mem(){ + fn test_load_regs_to_mem() { let instruction_bytes = 0xf965_u16.to_be_bytes(); let ins = Instruction::decode_instruction(&instruction_bytes); assert_eq!(ins, LoadRegistersFromMemory(0b1001)) } - -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 7593eb0..8f8e60c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,7 @@ mod rom; fn main() -> EmulatorResult<()> { SimpleLogger::new().with_level(LevelFilter::Info).env().init().unwrap(); - let Porcel8ProgramArgs { filename, new_chip8_behaviour, draw_scale } = Porcel8ProgramArgs::parse(); + let Porcel8ProgramArgs { filename, new_chip8_behaviour, draw_scale, halt_on_invalid } = Porcel8ProgramArgs::parse(); log::info!("Started emulator"); @@ -46,7 +46,7 @@ fn main() -> EmulatorResult<()> { timer.start(); - let device = Device::new(timer, frame_buffer_for_device, device_keyboard, new_chip8_behaviour); + let device = Device::new(timer, frame_buffer_for_device, device_keyboard, new_chip8_behaviour, halt_on_invalid); let (device_termination_signal_sender, compute_handle) = start_compute_thread(filename, device)?; @@ -163,6 +163,3 @@ fn try_initiate_sdl(draw_scale: f32) -> EmulatorResult<(WindowCanvas, EventPump, let event_pump = sdl_context.event_pump()?; Ok((canvas, event_pump, audio_queue)) } - - -