Compare commits

..

No commits in common. "77ef72d2ee7bbe14b0485f9a7cf2e1c7b0502884" and "e12bbe91a0d6dbe4d23b09265acff29ed9ce0c45" have entirely different histories.

4 changed files with 52 additions and 256 deletions

Binary file not shown.

View File

@ -1,18 +1,12 @@
// 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 std::process::exit;
use std::io::{self, Read};
use crate::{util, EmulationOpts};
// Memory map constants // Memory map constants
pub const PRGRM_START: usize = 512; pub const PRGRM_START: usize = 512;
pub const MAX_MEM_SIZE: usize = 4096; pub const MAX_MEM_SIZE: usize = 4096;
pub const MAX_PRG_SIZE: usize = 3584; pub const MAX_PRG_SIZE: usize = 3584;
const MAX_NUM_REGS : usize = 16;
const MAX_STACK_SIZE : usize = 16;
// Instruction constants // Instruction constants
const INSTR_NOOP: u16 = 0x0000; const INSTR_NOOP: u16 = 0x0000;
@ -21,35 +15,21 @@ const INSTR_CLS : u16 = 0x00E0;
const INSTR_RET: u16 = 0x00EE; const INSTR_RET: u16 = 0x00EE;
// Commonn Instruction Masks // Commonn Instruction Masks
const INSTR_MASK_SYS: u16 = 0x0000;
const INSTR_MASK_JP: u16 = 0x1000; const INSTR_MASK_JP: u16 = 0x1000;
const INSTR_MASK_CALL: u16 = 0x2000; 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; const ADDR_MASK: u16 = 0x0FFF;
#[derive(Debug)]
struct EmulationState { struct EmulationState {
// Miscellanious registers // Miscellanious registers
program_counter: u16, program_counter: u16,
stack_ptr: usize, stack_ptr: u8,
// General purpose registers // General purpose registers
registers: [u8; MAX_NUM_REGS], registers: [u8; 16],
// Memory Map: // Memory Map:
// +---------------+= 0xFFF (4095) End of Chip-8 RAM // +---------------+= 0xFFF (4095) End of Chip-8 RAM
@ -74,67 +54,38 @@ struct EmulationState {
// | Reserved for | // | Reserved for |
// | interpreter | // | interpreter |
// +---------------+= 0x000 (0) Start of Chip-8 RAM // +---------------+= 0x000 (0) Start of Chip-8 RAM
memory: [u8; MAX_MEM_SIZE], memory: [u8; 4096]
options: EmulationOpts,
// Program stack
stack: [u16; MAX_STACK_SIZE]
} }
/// # start_emulation pub fn start_emulation(data: [u8; MAX_PRG_SIZE]) {
///
/// Begin emulating the loaded CHIP8 program.
///
/// * `data` - u8 array of program data.
///
pub fn start_emulation(data: [u8; MAX_PRG_SIZE], options_in: EmulationOpts)
{
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]
stack: [0; 16],
options: options_in
}; };
load_program(data, &mut state); load_program(data, &mut state);
exec_program(&mut state); exec_program(&mut state);
} }
/// # load_program // Load the program into virtual memory
/// 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];
} }
} }
/// # exec_program // Execute the program starting at the userspace address
/// 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;
@ -145,151 +96,45 @@ 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;
} }
parse_instruction(state, instruction); parse_instruction(state, instruction);
if state.options.step_mode
{
pause();
}
} }
} }
/// # 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) {
// CLS; Clear the display
if instr == INSTR_CLS if (instr == INSTR_CLS) {
{ println!("Clear screen");
// Clear the display
debug_log_instr(state, instr, "CLS".to_string());
// TODO: Implement this
return;
} }
// RET; Return from subroutine if (instr == INSTR_RET) {
// Sets the program counter to the address at the top of the stack, then pops the value from println!("Return");
// 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; if ((instr & INSTR_MASK_SYS) > 0) {
state.program_counter = state.stack[state.stack_ptr]; let addr: u16 = (instr & ADDR_MASK);
state.stack[state.stack_ptr] = 0x0000; println!("Got SYS instruction {:04X?} with address {:04X?}", instr, addr);
return;
} }
// JP nnn; Jump to location nnn. if ((instr & INSTR_MASK_JP) > 0) {
// Sets the program counter to nnn. let addr: u16 = (instr & ADDR_MASK);
if (instr & INSTR_MASK_JP) > 0 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));
state.program_counter = addr;
return;
} }
// CALL nnn; Call subroutine at nnn if ((instr & INSTR_MASK_CALL) > 0) {
// Puts the current PC in the stack, then increments the stack pointer. The program counter let addr: u16 = (instr & ADDR_MASK);
// is then set to nnn. println!("Got CALL instruction {:04X?} with address {:04X?}", instr, addr);
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;
} }
} }
/// # 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.options.debug_mode
{
println!("PC: {:04?}; INSTR {:04X?}; {}", state.program_counter, instr, msg);
}
}
/// # 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();
}

View File

@ -1,4 +1,3 @@
use std::option;
use std::path; use std::path;
use std::env; use std::env;
use std::fs; use std::fs;
@ -8,27 +7,15 @@ use std::process::exit;
mod util; mod util;
mod chip8; mod chip8;
#[derive(Default)]
struct EmulationOpts {
debug_mode: bool,
step_mode: bool
}
/// # 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().skip(1).collect(); let args: Vec<String> = env::args().collect();
// Check if we received enough arguments // Grab our filepath from our options
if args.len() == 0 if &args.len() < &2
{ {
// No arguments given, terminate // No file given, terminate
util::print_help(); util::print_help();
exit(0); exit(0);
} }
@ -36,39 +23,6 @@ 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 emu_options: EmulationOpts = EmulationOpts{..Default::default()};
// 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' {
println!("Enabling debug mode...");
emu_options.debug_mode = true;
}
if char == 's' {
println!("Enabling step-through mode...");
emu_options.step_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);
@ -93,7 +47,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, emu_options); chip8::start_emulation(program);
} }
} }
else else

View File

@ -1,8 +1,5 @@
pub fn print_help() { pub fn print_help() {
println!("Usage: saltnvinegar [-OPTIONS] [CHIP8_FILE]"); print!("Usage: saltnvinegar [CHIP8_FILE]\n");
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 { pub fn u8_tuple_to_u16(u8_in: (u8, u8)) -> u16 {