Compare commits
No commits in common. "77ef72d2ee7bbe14b0485f9a7cf2e1c7b0502884" and "e12bbe91a0d6dbe4d23b09265acff29ed9ce0c45" have entirely different histories.
77ef72d2ee
...
e12bbe91a0
Binary file not shown.
223
src/chip8.rs
223
src/chip8.rs
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
56
src/main.rs
56
src/main.rs
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user