extern crate rand;
use std::io::{Read, Write, BufWriter};
use error::Chip8Error;
use instructions::Register;
use instructions::{RawInstruction, Instruction};
use std::slice::Chunks;
use rand::Rng;
const RAM_SIZE: usize = 4096;
const STACK_SIZE: usize = 256;
const NUM_DATA_REGISTERS: usize = 16;
const PROGRAM_START: usize = 0x200;
const CLOCK_HZ: f32 = 600.0;
const FONT_ADDR: usize = 0;
const FONT_HEIGHT: usize = 5;
const FONT_BYTES: usize = FONT_HEIGHT * 16;
const FONT: [u8; FONT_BYTES] = [
0xF0, 0x90, 0x90, 0x90, 0xF0, 0x20, 0x60, 0x20, 0x20, 0x70, 0xF0, 0x10, 0xF0, 0x80, 0xF0, 0xF0, 0x10, 0xF0, 0x10, 0xF0, 0x90, 0x90, 0xF0, 0x10, 0x10, 0xF0, 0x80, 0xF0, 0x10, 0xF0, 0xF0, 0x80, 0xF0, 0x90, 0xF0, 0xF0, 0x10, 0x20, 0x40, 0x40, 0xF0, 0x90, 0xF0, 0x90, 0xF0, 0xF0, 0x90, 0xF0, 0x10, 0xF0, 0xF0, 0x90, 0xF0, 0x90, 0x90, 0xE0, 0x90, 0xE0, 0x90, 0xE0, 0xF0, 0x80, 0x80, 0x80, 0xF0, 0xE0, 0x90, 0x90, 0x90, 0xE0, 0xF0, 0x80, 0xF0, 0x80, 0xF0, 0xF0, 0x80, 0xF0, 0x80, 0x80, ];
const SCREEN_WIDTH: usize = 64;
const SCREEN_HEIGHT: usize = 32;
const SCREEN_PIXELS: usize = SCREEN_WIDTH * SCREEN_HEIGHT;
const NUM_KEYS: usize = 16;
pub struct Vm {
reg: [u8; NUM_DATA_REGISTERS],
i: usize,
pc: usize,
sp: usize,
stack: [usize; STACK_SIZE],
ram: [u8; RAM_SIZE],
timer: u8,
t_tick: f32,
sound_timer: u8,
st_tick: f32,
screen: [u8; SCREEN_PIXELS],
keys: [u8; NUM_KEYS],
waiting_on_key: Option<Register>,
}
impl Vm {
pub fn new() -> Vm {
let mut vm = Vm {
reg: [0; NUM_DATA_REGISTERS],
i: 0,
pc: PROGRAM_START,
sp: 0,
stack: [0; STACK_SIZE],
ram: [0; RAM_SIZE],
timer: 0,
t_tick: 0.0,
sound_timer: 0,
st_tick: 0.0,
screen: [0; SCREEN_PIXELS],
keys: [0; NUM_KEYS],
waiting_on_key: None,
};
{
let mut ram = BufWriter::new(&mut vm.ram[FONT_ADDR..(FONT_ADDR + FONT_BYTES)]);
ram.write_all(FONT.as_ref()).unwrap();
debug!("Initialized VM with built-in font");
}
vm
}
pub fn load_rom(&mut self, reader: &mut Read) -> Result<usize, Chip8Error> {
let mut rom = Vec::new();
try!(reader.read_to_end(&mut rom));
let rom_len = rom.len();
let available_ram = RAM_SIZE - PROGRAM_START;
if rom_len > available_ram {
error!("ROM size ({}) is larger than available RAM ({})!", rom_len, available_ram);
return Err(Chip8Error::Io("ROM was larger than available RAM", None))
}
let mut ram = BufWriter::new(&mut self.ram[PROGRAM_START..RAM_SIZE]);
try!(ram.write_all(rom.as_ref()));
debug!("Loaded ROM of size {}", rom_len);
return Ok(rom_len);
}
#[allow(dead_code)]
pub fn dump_ram(&self, writer: &mut Write) {
writer.write_all(&self.ram).unwrap();
}
pub fn beeping(&self) -> bool {
self.sound_timer > 0
}
pub fn set_key(&mut self, idx: u8) {
debug!("Set key {}", idx);
self.keys[idx as usize] = 1;
if let Some(vx) = self.waiting_on_key {
debug!("No longer waiting on key");
self.reg[vx as usize] = idx;
self.waiting_on_key = None;
}
}
pub fn unset_key(&mut self, idx: u8) {
debug!("Unset key {}", idx);
self.keys[idx as usize] = 0;
}
fn exec(&mut self, ins: &Instruction) -> bool {
use instructions::Instruction::*;
match *ins {
Clear => {
for b in self.screen.iter_mut() {
*b = 0;
}
},
Return => {
self.pc = self.stack[self.sp];
self.sp-=1;
},
Jump(addr) => {
let idle = self.pc-2 == addr.bits as usize;
self.pc = addr.bits as usize;
if idle { return true; }
}
Call(addr) => {
self.sp+=1;
self.stack[self.sp] = self.pc;
self.pc = addr.bits as usize;
},
SkipEqualK(vx, k) => {
if self.reg[vx as usize] == k {
self.pc += 2;
}
},
SkipNotEqualK(vx, k) => {
if self.reg[vx as usize] != k {
self.pc += 2;
}
},
SkipEqual(vx, vy) => {
let x = self.reg[vx as usize];
let y = self.reg[vy as usize];
if x == y {
self.pc += 2;
}
},
SetK(vx, byte) => {
self.reg[vx as usize] = byte;
},
AddK(vx, byte) => {
self.reg[vx as usize] = self.reg[vx as usize].wrapping_add(byte);
},
Set(vx, vy) => self.reg[vx as usize] = self.reg[vy as usize],
Or(vx, vy) => self.reg[vx as usize] |= self.reg[vy as usize],
And(vx, vy) => self.reg[vx as usize] &= self.reg[vy as usize],
XOr(vx, vy) => self.reg[vx as usize] ^= self.reg[vy as usize],
Add(vx, vy) => {
let x = self.reg[vx as usize] as u16;
let y = self.reg[vy as usize] as u16;
let res = x + y;
self.reg[Register::VF as usize] = (res > 255) as u8;
self.reg[vx as usize] = res as u8;
},
Sub(vx, vy) => {
let x = self.reg[vx as usize];
let y = self.reg[vy as usize];
self.reg[Register::VF as usize] = (x > y) as u8;
self.reg[vx as usize] = x.wrapping_sub(y);
},
ShiftRight(vx, vy) => {
let y = self.reg[vy as usize];
self.reg[Register::VF as usize] = 0x1 & y;
self.reg[vx as usize] = y >> 1;
},
SubInv(vx, vy) => {
let x = self.reg[vx as usize];
let y = self.reg[vy as usize];
self.reg[Register::VF as usize] = (y > x) as u8;
self.reg[vx as usize] = y.wrapping_sub(x);
},
ShiftLeft(vx, vy) => {
let y = self.reg[vy as usize];
self.reg[Register::VF as usize] = y >> 7;
self.reg[vx as usize] = y << 1;
}
SkipNotEqual(vx, vy) => {
let x = self.reg[vx as usize];
let y = self.reg[vy as usize];
if x != y {
self.pc += 2;
}
},
LoadI(addr) => {
self.i = addr.bits as usize;
},
LongJump(addr) => {
self.pc = (self.reg[Register::V0 as usize] as u16 + addr.bits) as usize;
},
Rand(vx, byte) => {
self.reg[vx as usize] = rand::thread_rng().gen::<u8>() & byte;
}
Draw(vx, vy, n) => {
let x = self.reg[vx as usize] as usize;
let y = self.reg[vy as usize] as usize;
let i = self.i;
let n = n.bits as usize;
let sprite = &self.ram[i..i+n];
self.reg[Register::VF as usize] = 0;
for (sy, byte) in sprite.iter().enumerate() {
let dy = (y + sy) % SCREEN_HEIGHT;
for sx in 0usize..8 {
let px = (*byte >> (7 - sx)) & 0b00000001;
let dx = (x + sx) % SCREEN_WIDTH;
let idx = dy * SCREEN_WIDTH + dx;
self.screen[idx] ^= px;
self.reg[Register::VF as usize] |= (self.screen[idx] == 0 && px == 1) as u8;
}
}
},
SkipPressed(vx) => {
let idx = self.reg[vx as usize];
if self.keys[idx as usize] == 1 {
self.pc += 2;
}
}
SkipNotPressed(vx) => {
let idx = self.reg[vx as usize];
if self.keys[idx as usize] != 1 {
self.pc += 2;
}
}
GetTimer(vx) => {
self.reg[vx as usize] = self.timer;
},
WaitKey(vx) => {
self.waiting_on_key = Some(vx);
},
SetTimer(vx) => {
self.timer = self.reg[vx as usize];
self.t_tick = 1.0 / 60.0;
},
SetSoundTimer(vx) => {
self.sound_timer = self.reg[vx as usize];
self.st_tick = 1.0 / 60.0;
},
AddToI(vx) => {
self.i += self.reg[vx as usize] as usize;
},
LoadHexGlyph(vx) => {
let x = self.reg[vx as usize];
self.i = FONT_ADDR + x as usize * FONT_HEIGHT;
}
StoreBCD(vx) => {
let mut x = self.reg[vx as usize];
let mut place = 100;
for i in 0usize..3 {
let bcd = x / place;
self.ram[self.i + i] = bcd;
x -= bcd * place;
place /= 10;
}
}
StoreRegisters(vx) => {
let vx = vx as usize;
let i = self.i;
let mut dst = &mut self.ram[i..i+vx+1];
for (x,b) in dst.iter_mut().enumerate() {
*b = self.reg[x];
}
self.i += vx+1;
},
LoadRegisters(vx) => {
let vx = vx as usize;
let i = self.i;
let src = &self.ram[i..i+vx+1];
for (x,b) in src.iter().enumerate() {
self.reg[x] = *b;
}
self.i += vx+1;
},
ref other => {
debug!("Instruction not implemented {:?} skipping...", other)
}
}
return false;
}
fn time_step(&mut self, dt:f32) {
if self.timer > 0 {
self.t_tick -= dt;
if self.t_tick <= 0.0 {
self.timer -= 1;
self.t_tick = 1.0 / 60.0;
}
}
if self.sound_timer > 0 {
self.st_tick -= dt;
if self.st_tick <= 0.0 {
self.sound_timer -= 1;
self.st_tick = 1.0 / 60.0;
}
}
}
pub fn step(&mut self, dt:f32) {
let sub_steps = (CLOCK_HZ * dt).round() as usize;
let ddt = dt / sub_steps as f32;
for step in 0..sub_steps {
trace!("Executing step {}/{}", step, sub_steps);
self.time_step(ddt);
if self.waiting_on_key.is_some() {
debug!("Cancel remaining execution steps while waiting for key");
return;
}
let raw = {
let codes = &self.ram[self.pc..self.pc+2];
((codes[0] as u16) << 8) | codes[1] as u16
};
let raw_ins = RawInstruction::new(raw);
self.pc += 2;
self.exec(&Instruction::from_raw(&raw_ins));
}
}
pub fn screen_rows<'a>(&'a self) -> Chunks<'a, u8> {
self.screen.chunks(SCREEN_WIDTH)
}
#[allow(dead_code)]
pub fn print_screen(&self) {
for row in self.screen.chunks(SCREEN_WIDTH) {
println!("");
for byte in row.iter() {
match *byte {
0x0 => print!("â–‘"),
_ => print!("â–“")
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use instructions::*;
use instructions::Register::*;
macro_rules! reg_test {
(
$name:ident {
before: {$($reg_before:expr => $reg_before_val:expr),+},
after: {$($reg_after:expr => $reg_after_val:expr),+},
overflow: $over:expr,
ins: $ins:expr
}
) =>
(
#[test]
fn $name() {
let mut vm = Vm::new();
$(
vm.reg[$reg_before as usize] = $reg_before_val;
)+
vm.exec(&$ins);
$(
assert!(vm.reg[$reg_after as usize] == $reg_after_val);
)+
assert!(vm.reg[VF as usize] == $over, "overflow was {}, wanted {}", vm.reg[VF as usize], $over);
}
)
}
reg_test!(
add_vx {
before: { V2 => 0xFE, V3 => 0x01 },
after: { V2 => 0xFF, V3 => 0x01 },
overflow: 0,
ins: Instruction::Add(V2, V3)
});
reg_test!(
add_vx_overflows {
before: { V2 => 0xFF, V3 => 0x01 },
after: { V2 => 0x00, V3 => 0x01 },
overflow: 1,
ins: Instruction::Add(V2, V3)
});
reg_test!(
add_k {
before: { V0 => 0x09 },
after: { V0 => 0x0B },
overflow: 0,
ins: Instruction::AddK(V0, 2)
});
reg_test!(
add_k_overflows {
before: { V0 => 0xFF },
after: { V0 => 0x01 },
overflow: 0, ins: Instruction::AddK(V0, 2)
});
reg_test!(
sub {
before: { V0 => 0x3, V1 => 0x2 },
after: { V0 => 0x1, V1 => 0x2 },
overflow: 1, ins: Instruction::Sub(V0, V1)
});
reg_test!(
sub_borrow {
before: { V0 => 0x3, V1 => 0x5 },
after: { V0 => 0xFE, V1 => 0x5 },
overflow: 0, ins: Instruction::Sub(V0, V1)
});
reg_test!(
sub_inv {
before: { V0 => 0x2, V1 => 0x3 },
after: { V0 => 0x1, V1 => 0x3 },
overflow: 1, ins: Instruction::SubInv(V0, V1)
});
reg_test!(
sub_inv_borrow {
before: { V0 => 0x5, V1 => 0x3 },
after: { V0 => 0xFE, V1 => 0x3 },
overflow: 0, ins: Instruction::SubInv(V0, V1)
});
reg_test!(
shiftl_vx_vy {
before: { V2 => 0xBB, V3 => 0x02 },
after: { V2 => 0x04, V3 => 0x02 },
overflow: 0,
ins: Instruction::ShiftLeft(V2, V3)
});
reg_test!(
shiftl_vx_inplace {
before: { V2 => 0b0111_0111 },
after: { V2 => 0b1110_1110 },
overflow: 0,
ins: Instruction::ShiftLeft(V2, V2)
});
reg_test!(
shiftl_vx_inplace_overflow {
before: { V2 => 0b1111_1111 },
after: { V2 => 0b1111_1110 },
overflow: 1,
ins: Instruction::ShiftLeft(V2, V2)
});
reg_test!(
shiftr_vx_vy {
before: { V2 => 0xBB, V3 => 0x04 },
after: { V2 => 0x02, V3 => 0x04 },
overflow: 0,
ins: Instruction::ShiftRight(V2, V3)
});
reg_test!(
shiftr_vx_inplace {
before: { V2 => 0b1110_1110 },
after: { V2 => 0b0111_0111 },
overflow: 0,
ins: Instruction::ShiftRight(V2, V2)
});
reg_test!(
shiftr_vx_inplace_overflow {
before: { V2 => 0b1111_1111 },
after: { V2 => 0b0111_1111 },
overflow: 1,
ins: Instruction::ShiftRight(V2, V2)
});
#[test]
fn oversized_rom() {
use std::io::Cursor;
let mut vm = Vm::new();
let rom = vec![0; super::RAM_SIZE + 1];
assert!(vm.load_rom(&mut Cursor::new(rom)).is_err());
}
}