Function docs, better debug logging, basic option flag parsing

This commit is contained in:
Garrett Dickinson 2024-08-10 03:46:08 -05:00
parent e12bbe91a0
commit fec824610c
2 changed files with 121 additions and 34 deletions

View File

@ -1,6 +1,8 @@
// chip8.rs // chip8.rs
// Implementation of chip8 machine, registers, and memory makeup // Implementation of chip8 machine, registers, and memory makeup
use std::fmt::Debug;
use crate::util; use crate::util;
// Memory map constants // Memory map constants
@ -54,38 +56,64 @@ struct EmulationState {
// | Reserved for | // | Reserved for |
// | interpreter | // | interpreter |
// +---------------+= 0x000 (0) Start of Chip-8 RAM // +---------------+= 0x000 (0) Start of Chip-8 RAM
memory: [u8; 4096] memory: [u8; 4096],
// Misc members
debug_enabled: bool
} }
pub fn start_emulation(data: [u8; MAX_PRG_SIZE]) { /// # start_emulation
///
/// Begin emulating the loaded CHIP8 program.
///
/// * `data` - u8 array of program data.
///
pub fn start_emulation(data: [u8; MAX_PRG_SIZE], dbg_mode: bool)
{
let mut state: EmulationState = EmulationState { let mut state: EmulationState = EmulationState {
program_counter: 0, program_counter: 0,
stack_ptr: 0, stack_ptr: 0,
registers: [0; 16], registers: [0; 16],
memory: [0; MAX_MEM_SIZE] memory: [0; MAX_MEM_SIZE],
debug_enabled: dbg_mode
}; };
load_program(data, &mut state); load_program(data, &mut state);
exec_program(&mut state); exec_program(&mut state);
} }
// Load the program into virtual memory /// # load_program
fn load_program(data: [u8; MAX_PRG_SIZE], state: &mut EmulationState) { ///
/// Load the program into virtual memory.
///
/// * `data` - u8 array of program data.
/// * `state` - Global emulation state.
///
fn load_program(data: [u8; MAX_PRG_SIZE], state: &mut EmulationState)
{
println!("Copying program data to memory"); println!("Copying program data to memory");
for i in 0..MAX_PRG_SIZE { for i in 0..MAX_PRG_SIZE
{
state.memory[i+PRGRM_START] = data[i]; state.memory[i+PRGRM_START] = data[i];
} }
} }
// Execute the program starting at the userspace address /// # exec_program
fn exec_program(state: &mut EmulationState) { ///
/// Execute the program starting at the userspace address.
///
/// * `state` - Global emulation state.
///
fn exec_program(state: &mut EmulationState)
{
println!("Starting emulation"); println!("Starting emulation");
let mut head_byte: u8; let mut head_byte: u8;
let mut tail_byte: u8; let mut tail_byte: u8;
let mut instruction: u16; let mut instruction: u16;
for _i in PRGRM_START+usize::from(state.program_counter)..MAX_MEM_SIZE { for _i in PRGRM_START+usize::from(state.program_counter)..MAX_MEM_SIZE
{
// Grab the first byte of instruction // Grab the first byte of instruction
head_byte = state.memory[PRGRM_START + usize::from(state.program_counter)]; head_byte = state.memory[PRGRM_START + usize::from(state.program_counter)];
state.program_counter += 1; state.program_counter += 1;
@ -96,15 +124,14 @@ fn exec_program(state: &mut EmulationState) {
instruction = util::u8_tuple_to_u16((head_byte, tail_byte)); instruction = util::u8_tuple_to_u16((head_byte, tail_byte));
if instruction == INSTR_EXIT
println!("0x{:02X?}{:02X?}", head_byte, tail_byte); {
if instruction == INSTR_EXIT {
println!("Read exit instruction, stopping emulation"); println!("Read exit instruction, stopping emulation");
return; return;
} }
if instruction == INSTR_NOOP { if instruction == INSTR_NOOP
{
println!("Read no-op instruction, stopping emulation"); println!("Read no-op instruction, stopping emulation");
return; return;
} }
@ -113,28 +140,55 @@ fn exec_program(state: &mut EmulationState) {
} }
} }
/// # parse_instruction
///
/// Parse binary instructions passed from program execution.
///
/// * `state` - Global emulation state.
/// * `instr` - Incoming instruction.
///
fn parse_instruction(state: &mut EmulationState, instr: u16) { fn parse_instruction(state: &mut EmulationState, instr: u16) {
if instr == INSTR_CLS
if (instr == INSTR_CLS) { {
println!("Clear screen"); debug_log_instr(state, instr, "CLS".to_string());
} }
if (instr == INSTR_RET) { if instr == INSTR_RET
println!("Return"); {
debug_log_instr(state, instr, "RET".to_string());
} }
if ((instr & INSTR_MASK_SYS) > 0) { if (instr & INSTR_MASK_SYS) > 0
let addr: u16 = (instr & ADDR_MASK); {
println!("Got SYS instruction {:04X?} with address {:04X?}", instr, addr); let addr: u16 = instr & ADDR_MASK;
debug_log_instr(state, instr, format!("SYS {:04X?}", addr));
} }
if ((instr & INSTR_MASK_JP) > 0) { if (instr & INSTR_MASK_JP) > 0
let addr: u16 = (instr & ADDR_MASK); {
println!("Got JP instruction {:04X?} with address {:04X?}", instr, addr); let addr: u16 = instr & ADDR_MASK;
debug_log_instr(state, instr, format!("JP {:04X?}", addr));
} }
if ((instr & INSTR_MASK_CALL) > 0) { if (instr & INSTR_MASK_CALL) > 0
let addr: u16 = (instr & ADDR_MASK); {
println!("Got CALL instruction {:04X?} with address {:04X?}", instr, addr); let addr: u16 = instr & ADDR_MASK;
debug_log_instr(state, instr, format!("CALL {:04X?}", addr));
}
}
/// # debug_log_instr
///
/// Debug log the program counter, instruction, and a brief string message in one line.
///
/// * `state` - Global emulation state.
/// * `instr` - Incoming instruction.
/// * `msg` - Message string to append
///
fn debug_log_instr(state: &mut EmulationState, instr: u16, msg: String)
{
if state.debug_enabled
{
println!("PC: {:04?}; INSTR {:04X?}; {}", state.program_counter, instr, msg);
} }
} }

View File

@ -1,3 +1,4 @@
use core::num;
use std::path; use std::path;
use std::env; use std::env;
use std::fs; use std::fs;
@ -7,15 +8,19 @@ use std::process::exit;
mod util; mod util;
mod chip8; mod chip8;
/// # main
///
/// Main entry point of the emulator
///
fn main() fn main()
{ {
// Collect our execution args // Collect our execution args
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().skip(1).collect();
// Grab our filepath from our options // Check if we received enough arguments
if &args.len() < &2 if args.len() == 0
{ {
// No file given, terminate // No arguments given, terminate
util::print_help(); util::print_help();
exit(0); exit(0);
} }
@ -23,6 +28,34 @@ fn main()
// Read last argument as path of chip8 executable // Read last argument as path of chip8 executable
let file_path: &String = &args[args.len()-1]; let file_path: &String = &args[args.len()-1];
// Check if an actual file path was provided and not just options
if file_path.starts_with('-')
{
// Only options were given, terminate
println!("[Error] A filename must be provided when passing arguments");
exit(0);
}
// Runtime options
let mut debug_mode: bool = false;
// Option parsing
let num_options: usize = args.len() - 1;
for i in 0..num_options
{
if args[i].starts_with('-')
{
let flags: &str = &args[i][1..args[i].len()];
for char in flags.chars() {
if char == 'd' {
debug_mode = true;
}
}
}
}
// Check if file_path exists in filesystem
if path::Path::new(file_path).exists() if path::Path::new(file_path).exists()
{ {
println!("'{}' exists, reading...", file_path); println!("'{}' exists, reading...", file_path);
@ -47,7 +80,7 @@ fn main()
} }
println!("Read {} bytes of program data", file_data.len()); println!("Read {} bytes of program data", file_data.len());
chip8::start_emulation(program); chip8::start_emulation(program, debug_mode);
} }
} }
else else