diff --git a/programs/test.ch8 b/programs/test.ch8 index 0bdc1c1..99a4756 100644 Binary files a/programs/test.ch8 and b/programs/test.ch8 differ diff --git a/src/chip8.rs b/src/chip8.rs index 192fa91..70c14f0 100644 --- a/src/chip8.rs +++ b/src/chip8.rs @@ -2,36 +2,54 @@ // Implementation of chip8 machine, registers, and memory makeup use std::fmt::Debug; +use std::process::exit; +use std::io::{self, Read}; -use crate::util; +use crate::{util, EmulationOpts}; // Memory map constants -pub const PRGRM_START: usize = 512; -pub const MAX_MEM_SIZE: usize = 4096; -pub const MAX_PRG_SIZE: usize = 3584; +pub const PRGRM_START : usize = 512; +pub const MAX_MEM_SIZE : usize = 4096; +pub const MAX_PRG_SIZE : usize = 3584; +const MAX_NUM_REGS : usize = 16; +const MAX_STACK_SIZE : usize = 16; // Instruction constants -const INSTR_NOOP: u16 = 0x0000; -const INSTR_EXIT: u16 = 0x00FD; -const INSTR_CLS: u16 = 0x00E0; -const INSTR_RET: u16 = 0x00EE; +const INSTR_NOOP : u16 = 0x0000; +const INSTR_EXIT : u16 = 0x00FD; +const INSTR_CLS : u16 = 0x00E0; +const INSTR_RET : u16 = 0x00EE; // Commonn Instruction Masks -const INSTR_MASK_SYS: u16 = 0x0000; -const INSTR_MASK_JP: u16 = 0x1000; -const INSTR_MASK_CALL: u16 = 0x2000; +const INSTR_MASK_JP : u16 = 0x1000; +const INSTR_MASK_CALL : u16 = 0x2000; +const INSTR_MASK_SE_VX : u16 = 0x3000; +const INSTR_MASK_SNE : u16 = 0x4000; +const INSTR_MASK_SE_VX_VY : u16 = 0x5000; +const INSTR_MASK_LD_VX : u16 = 0x6000; +const INSTR_MASK_ADD_VX : u16 = 0x7000; +const INSTR_MASK_LD_VX_VY : u16 = 0x8000; +const INSTR_MASK_OR_VX_VY : u16 = 0x8001; +const INSTR_MASK_AND_VX_VY : u16 = 0x8002; +const INSTR_MASK_XOR_VX_VY : u16 = 0x8003; +const INSTR_MASK_ADD_VX_VY : u16 = 0x8004; +const INSTR_MASK_SUB_VX_VY : u16 = 0x8005; +const INSTR_MASK_SHR_VX_VY : u16 = 0x8006; +const INSTR_MASK_SUBN_VX_VY : u16 = 0x8007; +const INSTR_MASK_SHL_VX_VY : u16 = 0x800E; +const INSTR_MASK_SNE_VX_VY : u16 = 0x9000; +const INSTR_MASK_LD_I : u16 = 0xA000; +const INSTR_MASK_JP_V0 : u16 = 0xB000; const ADDR_MASK: u16 = 0x0FFF; - -#[derive(Debug)] struct EmulationState { // Miscellanious registers program_counter: u16, - stack_ptr: u8, + stack_ptr: usize, // General purpose registers - registers: [u8; 16], + registers: [u8; MAX_NUM_REGS], // Memory Map: // +---------------+= 0xFFF (4095) End of Chip-8 RAM @@ -56,10 +74,12 @@ struct EmulationState { // | Reserved for | // | interpreter | // +---------------+= 0x000 (0) Start of Chip-8 RAM - memory: [u8; 4096], + memory: [u8; MAX_MEM_SIZE], - // Misc members - debug_enabled: bool + options: EmulationOpts, + + // Program stack + stack: [u16; MAX_STACK_SIZE] } /// # start_emulation @@ -68,14 +88,15 @@ struct EmulationState { /// /// * `data` - u8 array of program data. /// -pub fn start_emulation(data: [u8; MAX_PRG_SIZE], dbg_mode: bool) +pub fn start_emulation(data: [u8; MAX_PRG_SIZE], options_in: EmulationOpts) { let mut state: EmulationState = EmulationState { program_counter: 0, stack_ptr: 0, registers: [0; 16], memory: [0; MAX_MEM_SIZE], - debug_enabled: dbg_mode + stack: [0; 16], + options: options_in }; load_program(data, &mut state); @@ -137,6 +158,11 @@ fn exec_program(state: &mut EmulationState) } parse_instruction(state, instruction); + + if state.options.step_mode + { + pause(); + } } } @@ -148,32 +174,82 @@ fn exec_program(state: &mut EmulationState) /// * `instr` - Incoming instruction. /// fn parse_instruction(state: &mut EmulationState, instr: u16) { + // CLS; Clear the display if instr == INSTR_CLS { + // Clear the display debug_log_instr(state, instr, "CLS".to_string()); + // TODO: Implement this + return; } + // RET; Return from subroutine + // Sets the program counter to the address at the top of the stack, then pops the value from + // the stack if instr == INSTR_RET { debug_log_instr(state, instr, "RET".to_string()); + + // Halt emulation if stack_ptr is 0, since decrementing on an empty stack + // is undefined behavior + if state.stack_ptr == 0 + { + halt(state, instr, format!("Cannot decrement stack pointer below stack limit {}", MAX_STACK_SIZE)); + } + + state.stack_ptr -= 1; + state.program_counter = state.stack[state.stack_ptr]; + state.stack[state.stack_ptr] = 0x0000; + return; } - if (instr & INSTR_MASK_SYS) > 0 - { - let addr: u16 = instr & ADDR_MASK; - debug_log_instr(state, instr, format!("SYS {:04X?}", addr)); - } - + // JP nnn; Jump to location nnn. + // Sets the program counter to nnn. if (instr & INSTR_MASK_JP) > 0 { let addr: u16 = instr & ADDR_MASK; debug_log_instr(state, instr, format!("JP {:04X?}", addr)); + + state.program_counter = addr; + return; } + // CALL nnn; Call subroutine at nnn + // Puts the current PC in the stack, then increments the stack pointer. The program counter + // is then set to nnn. if (instr & INSTR_MASK_CALL) > 0 { let addr: u16 = instr & ADDR_MASK; debug_log_instr(state, instr, format!("CALL {:04X?}", addr)); + + // Halt emulation if stack_ptr is increments above stack limit, as this will cause a + // stack overflow + if (state.stack_ptr + 1) > MAX_STACK_SIZE + { + halt(state, instr, format!("Cannot increment stack pointer above stack limit {}", MAX_STACK_SIZE)); + } + + state.stack[state.stack_ptr] = state.program_counter; + state.stack_ptr += 1; + state.program_counter = addr; + return; + } + + // SE Vx, byte; Skip next instruction if Vx = kk. + // Compares register Vx to kk, and if they are equal, increments the + // program counter by 2. + if (instr & INSTR_MASK_SE_VX) > 0 + { + let bytes:[u8; 2] = instr.to_be_bytes(); + let register_num: u8 = bytes[0] & 0x0F; + let value: u8 = bytes[1] & 0xFF; + debug_log_instr(state, instr, format!("SE V{:01X?} {:02X?}", register_num, value)); + + if value == state.registers[register_num as usize] + { + state.program_counter += 2; + } + return; } } @@ -187,8 +263,33 @@ fn parse_instruction(state: &mut EmulationState, instr: u16) { /// fn debug_log_instr(state: &mut EmulationState, instr: u16, msg: String) { - if state.debug_enabled + if state.options.debug_mode { println!("PC: {:04?}; INSTR {:04X?}; {}", state.program_counter, instr, msg); } -} \ No newline at end of file +} + +/// # halt +/// +/// Halt the emulator and throw an error +/// +/// * `msg` - Message string describing the error +/// +fn halt(state: &mut EmulationState, instr: u16, msg: String) +{ + println!("ERROR: EMULATION HALTED\n"); + println!("\t{}\n", msg); + println!("\tPC: {:04?}\t\tINSTR {:04X?}", state.program_counter, instr); + exit(0); +} + +/// # Pause +/// +/// Pause the emulation and poll for keyboard input; +/// Used for stepping over program data 1 instruction at a time +/// +fn pause() { + // Read a single byte and discard + let mut stdin = io::stdin(); + let _ = stdin.read(&mut [0u8]).unwrap(); +} diff --git a/src/main.rs b/src/main.rs index 8ec75b6..fb33cb2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use core::num; +use std::option; use std::path; use std::env; use std::fs; @@ -8,6 +8,14 @@ use std::process::exit; mod util; mod chip8; + +#[derive(Default)] +struct EmulationOpts { + debug_mode: bool, + step_mode: bool +} + + /// # main /// /// Main entry point of the emulator @@ -37,7 +45,7 @@ fn main() } // Runtime options - let mut debug_mode: bool = false; + let mut emu_options: EmulationOpts = EmulationOpts{..Default::default()}; // Option parsing let num_options: usize = args.len() - 1; @@ -49,7 +57,12 @@ fn main() for char in flags.chars() { if char == 'd' { - debug_mode = true; + println!("Enabling debug mode..."); + emu_options.debug_mode = true; + } + if char == 's' { + println!("Enabling step-through mode..."); + emu_options.step_mode = true; } } } @@ -80,7 +93,7 @@ fn main() } println!("Read {} bytes of program data", file_data.len()); - chip8::start_emulation(program, debug_mode); + chip8::start_emulation(program, emu_options); } } else diff --git a/src/util.rs b/src/util.rs index c6c6ac5..7280cd9 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,9 +1,12 @@ pub fn print_help() { - print!("Usage: saltnvinegar [CHIP8_FILE]\n"); + println!("Usage: saltnvinegar [-OPTIONS] [CHIP8_FILE]"); + println!("Options:"); + println!("\t-d\tEnable debug output to terminal"); + println!("\t-s\tEnable step-through-instruction mode"); } pub fn u8_tuple_to_u16(u8_in: (u8, u8)) -> u16 { let head_byte: u16 = u16::from(u8_in.0) << 8; let tail_byte: u16 = u16::from(u8_in.1); return head_byte + tail_byte; -} \ No newline at end of file +}