2020-06-25 18:13:09 +00:00
|
|
|
use super::periph::{Display, Keypad, Timer};
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::{self, Read};
|
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
pub struct Chip8 {
|
|
|
|
opcode: u16,
|
|
|
|
i: u16,
|
|
|
|
pc: u16,
|
|
|
|
sp: u8,
|
|
|
|
key: Keypad,
|
|
|
|
v: [u8; 16],
|
|
|
|
stack: [u16; 16],
|
|
|
|
memory: [u8; 4096],
|
|
|
|
delay: Timer,
|
|
|
|
sound: Timer,
|
2020-06-25 19:22:37 +00:00
|
|
|
pub display: Display,
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Chip8 {
|
|
|
|
fn default() -> Self {
|
2020-06-25 19:22:37 +00:00
|
|
|
let mut chip8 = Chip8 {
|
2020-06-25 18:13:09 +00:00
|
|
|
opcode: 0,
|
|
|
|
i: 0,
|
|
|
|
pc: 0x200, // Progrm counter starts at 0x200
|
|
|
|
sp: 0,
|
|
|
|
key: Default::default(),
|
|
|
|
v: [0; 16],
|
|
|
|
stack: [0; 16],
|
|
|
|
memory: [0; 4096],
|
|
|
|
delay: Default::default(),
|
|
|
|
sound: Default::default(),
|
|
|
|
display: Display::default(),
|
2020-06-25 19:22:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
chip8.load_font_set();
|
|
|
|
chip8
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Chip8 {
|
2020-06-25 19:22:37 +00:00
|
|
|
const FONT_SET: [u8; 80] = [
|
|
|
|
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
|
|
|
|
0x20, 0x60, 0x20, 0x20, 0x70, // 1
|
|
|
|
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
|
|
|
|
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
|
|
|
|
0x90, 0x90, 0xF0, 0x10, 0x10, // 4
|
|
|
|
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
|
|
|
|
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
|
|
|
|
0xF0, 0x10, 0x20, 0x40, 0x40, // 7
|
|
|
|
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
|
|
|
|
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
|
|
|
|
0xF0, 0x90, 0xF0, 0x90, 0x90, // A
|
|
|
|
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
|
|
|
|
0xF0, 0x80, 0x80, 0x80, 0xF0, // C
|
|
|
|
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
|
|
|
|
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
|
|
|
|
0xF0, 0x80, 0xF0, 0x80, 0x80, // F
|
|
|
|
];
|
|
|
|
|
2020-06-25 18:13:09 +00:00
|
|
|
pub fn execute_cycle(&mut self) {
|
|
|
|
self.opcode = self.get_opcode();
|
|
|
|
println!("{:#x}", self.opcode);
|
|
|
|
self.handle_opcode();
|
|
|
|
|
|
|
|
self.delay.tick();
|
|
|
|
self.sound.tick();
|
|
|
|
}
|
|
|
|
|
2020-06-25 19:22:37 +00:00
|
|
|
pub fn load_font_set(&mut self) {
|
2020-06-26 00:06:27 +00:00
|
|
|
self.memory[0..0x50].copy_from_slice(&Self::FONT_SET);
|
2020-06-25 19:22:37 +00:00
|
|
|
}
|
|
|
|
|
2020-06-25 18:13:09 +00:00
|
|
|
pub fn load_rom<P: AsRef<Path>>(&mut self, path: P) -> Result<(), io::Error> {
|
|
|
|
let mut file = File::open(path.as_ref())?;
|
|
|
|
let mut rom_buf: Vec<u8> = vec![];
|
|
|
|
file.read_to_end(&mut rom_buf)?;
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
self.memory[0x200..(0x200 + rom_buf.len())].copy_from_slice(&rom_buf);
|
2020-06-25 18:13:09 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_opcode(&self) -> u16 {
|
|
|
|
let pc = self.pc as usize;
|
|
|
|
|
|
|
|
((self.memory[pc] as u16) << 8) | self.memory[pc + 1] as u16
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn get_nibs(opcode: u16) -> (u8, u8, u8, u8) {
|
|
|
|
// Given 0xA2F0
|
|
|
|
(
|
|
|
|
((opcode & 0xF000) >> 12) as u8, // 0xA
|
|
|
|
((opcode & 0x0F00) >> 8) as u8, // 0x2
|
|
|
|
((opcode & 0x00F0) >> 4) as u8, // 0xF
|
|
|
|
(opcode & 0x000F) as u8, // 0x0
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-06-25 18:13:09 +00:00
|
|
|
fn handle_opcode(&mut self) {
|
2020-06-26 00:06:27 +00:00
|
|
|
let (nib_1, nib_2, nib_3, nib_4) = Self::get_nibs(self.opcode);
|
2020-06-25 18:13:09 +00:00
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
let nnn: u16 = self.opcode & 0x0FFF;
|
|
|
|
let kk: u8 = (self.opcode & 0x00FF) as u8;
|
|
|
|
let x = nib_2;
|
|
|
|
let y = nib_3;
|
|
|
|
let n = nib_4;
|
2020-06-25 18:13:09 +00:00
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
self.pc += 2;
|
2020-06-25 18:13:09 +00:00
|
|
|
|
|
|
|
match (nib_1, nib_2, nib_3, nib_4) {
|
|
|
|
// CLS
|
|
|
|
(0x0, 0x0, 0xE, 0x0) => self.cls(),
|
|
|
|
// 00EE
|
|
|
|
(0x0, 0x0, 0xE, 0xE) => self.ret(),
|
|
|
|
// 1NNN
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x1, _, _, _) => self.jmp_addr(nnn),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 2NNN
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x2, _, _, _) => self.call_addr(nnn),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 3XKK
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x3, _, _, _) => self.se_vx_byte(x, kk),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 4xkk
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x4, _, _, _) => self.sne_vx_byte(x, kk),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 5xy0
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x5, _, _, 0x0) => self.se_vx_vy(x, y),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 6xkk
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x6, _, _, _) => self.ld_vx_byte(x, kk),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 7xkk
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x7, _, _, _) => self.add_vx_byte(x, kk),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 8xy0
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x8, _, _, 0x0) => self.ld_vx_vy(x, y),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 8xy1
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x8, _, _, 0x1) => self.or(x, y),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 8xy2
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x8, _, _, 0x2) => self.and_vx_vy(x, y),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 8xy3
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x8, _, _, 0x3) => self.xor(x, y),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 8xy4
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x8, _, _, 0x4) => self.add_vx_vy(x, y),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 8xy5
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x8, _, _, 0x5) => self.sub_vx_vy(x, y),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 8xy6
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x8, _, _, 0x6) => self.shr(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 8xy7
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x8, _, _, 0x7) => self.subn_vx_vy(x, y),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 8xyE
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x8, _, _, 0xE) => self.shl(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// 9xy0
|
2020-06-26 00:06:27 +00:00
|
|
|
(0x9, _, _, 0x0) => self.sne_vx_vy(x, y),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Annn
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xA, _, _, _) => self.load_addr_to_i(nnn),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Bnnn
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xB, _, _, _) => self.jmp_addr_with_offset(nnn),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Cxkk
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xC, _, _, _) => self.rand(x, kk),
|
2020-06-25 18:13:09 +00:00
|
|
|
//Dxyn
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xD, _, _, _) => self.draw(x, y, n),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Ex9E
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xE, _, 0x9, 0xE) => self.skip_on_press(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// ExA1
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xE, _, 0xA, 0x1) => self.skip_not_pressed(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Fx07
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xF, _, 0x0, 0x7) => self.copy_delay_timer_val(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Fx0A
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xF, _, 0x0, 0xA) => self.loop_until_key_vx(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Fx15
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xF, _, 0x1, 0x15) => self.set_delay_to_vx(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Fx18
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xF, _, 0x1, 0x8) => self.set_sound_to_vx(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Fx1E
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xF, _, 0x1, 0xE) => self.add_vx_to_i(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Fx29
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xF, _, 0x2, 0x9) => self.set_i_to_hex_sprite_loc(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Fx33
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xF, _, 0x3, 0x3) => self.copy_digits_to_i(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Fx55
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xF, _, 0x5, 0x5) => self.copy_from_vx_to_memory(x),
|
2020-06-25 18:13:09 +00:00
|
|
|
// Fx65
|
2020-06-26 00:06:27 +00:00
|
|
|
(0xF, _, 0x6, 0x5) => self.load_into_vx_from_i(x),
|
|
|
|
// Otherwise...
|
|
|
|
_ => panic!("UNIMPLEMENTED OPCODE: {:#x}", self.opcode),
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn cls(&mut self) {
|
|
|
|
// Clear the display
|
|
|
|
self.display.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ret(&mut self) {
|
2020-06-26 00:06:27 +00:00
|
|
|
// sets the program counter to the address at the top of the stack,
|
2020-06-25 18:13:09 +00:00
|
|
|
// then subtracts one (1) from the stack pointer
|
|
|
|
self.pc = self.stack[self.sp as usize];
|
|
|
|
self.sp -= 1;
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn jmp_addr(&mut self, nnn: u16) {
|
|
|
|
// sets the program counter to addr (nnn)
|
|
|
|
self.pc = nnn;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn call_addr(&mut self, nnn: u16) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// increments the stack pointer, then puts current pc on top of stack
|
|
|
|
// pc is then set to addr
|
|
|
|
self.sp += 1;
|
|
|
|
self.stack[self.sp as usize] = self.pc;
|
2020-06-26 00:06:27 +00:00
|
|
|
self.pc = nnn;
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn se_vx_byte(&mut self, x: u8, kk: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// compares Vx to kk. If they are equal, pc is incremented by 2
|
2020-06-26 00:06:27 +00:00
|
|
|
if self.v[x as usize] == kk {
|
2020-06-25 18:13:09 +00:00
|
|
|
self.pc += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn sne_vx_byte(&mut self, x: u8, kk: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// compares Vx to kk. If they are **not** equal, pc is incremented by 2
|
2020-06-26 00:06:27 +00:00
|
|
|
if self.v[x as usize] != kk {
|
2020-06-25 18:13:09 +00:00
|
|
|
self.pc += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn se_vx_vy(&mut self, x: u8, y: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// compares Vx to Vy. If they are equal, pc is incremented by 2
|
|
|
|
if self.v[x as usize] == self.v[y as usize] {
|
|
|
|
self.pc += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn ld_vx_byte(&mut self, x: u8, kk: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// put value kk into Vx
|
2020-06-26 00:06:27 +00:00
|
|
|
self.v[x as usize] = kk;
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn add_vx_byte(&mut self, x: u8, kk: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// calculate Vx + kk, then store it in Vx
|
2020-06-26 04:59:59 +00:00
|
|
|
self.v[x as usize] += kk;
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn ld_vx_vy(&mut self, x: u8, y: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// store Vy in Vx
|
|
|
|
self.v[x as usize] = self.v[y as usize];
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn or(&mut self, x: u8, y: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// calc bitwise OR on Vx and Vy, then store in Vx
|
2020-06-26 04:59:59 +00:00
|
|
|
self.v[x as usize] |= self.v[y as usize];
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn and_vx_vy(&mut self, x: u8, y: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// calc bitwise AND on Vx and Vy, then store in Vx
|
|
|
|
let x = x as usize;
|
|
|
|
self.v[x] = self.v[x] & self.v[y as usize];
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn xor(&mut self, x: u8, y: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// calc bitwise XOR on Vx and Vy, then store in Vx
|
2020-06-26 04:59:59 +00:00
|
|
|
self.v[x as usize] ^= self.v[y as usize];
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn add_vx_vy(&mut self, x: u8, y: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// add Vx and Vy, if result is greater than 8 bits
|
|
|
|
// set VF to 1, otherwise 0
|
|
|
|
// only the lowest 8 bits of result are stored in Vx
|
|
|
|
let x = x as usize;
|
|
|
|
|
|
|
|
let (res, did_overflow) = self.v[x].overflowing_add(self.v[y as usize]);
|
2020-06-26 00:06:27 +00:00
|
|
|
self.v[0xF] = if did_overflow { 1 } else { 0 };
|
2020-06-25 18:13:09 +00:00
|
|
|
self.v[x] = res;
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn sub_vx_vy(&mut self, x: u8, y: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// subtract Vx and Vy, if Vx > Vy VF is set to 1, otherwise 0
|
|
|
|
// then set Vx to Vx - Vy
|
|
|
|
let vx = self.v[x as usize];
|
|
|
|
let vy = self.v[y as usize];
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
self.v[0xF] = if vx > vy { 1 } else { 0 };
|
|
|
|
self.v[x as usize] = vx - vy;
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn shr(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// if LSB is set to 1, set VF to 1, otherwise set VF to 0
|
|
|
|
// then shift Vx one to the right
|
|
|
|
let x = x as usize;
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
self.v[0xF] = if (self.v[x] & 1) == 1 { 1 } else { 0 };
|
|
|
|
self.v[x] >>= 1;
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn subn_vx_vy(&mut self, x: u8, y: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// subtract Vy and Vx, if Vy > Vx VF is set to 1, otherwise 0
|
|
|
|
// then set Vx = Vy - Vx
|
|
|
|
let vx = self.v[x as usize];
|
|
|
|
let vy = self.v[y as usize];
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
self.v[0xF] = if vy > vx { 1 } else { 0 };
|
2020-06-25 18:13:09 +00:00
|
|
|
self.v[x as usize] = vy - vx;
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn shl(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// if MSB is set to 1, set VF to 1, otherwise set VF to 0
|
|
|
|
// then shift Vx one to the left;
|
|
|
|
let x = x as usize;
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
self.v[0xF] = if (self.v[x] & 0x80) == 1 { 1 } else { 0 };
|
2020-06-25 18:13:09 +00:00
|
|
|
self.v[x] = self.v[x] << 1;
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn sne_vx_vy(&mut self, x: u8, y: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// if Vx != vy program counter is increased by 2
|
|
|
|
if self.v[x as usize] != self.v[y as usize] {
|
|
|
|
self.pc += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn load_addr_to_i(&mut self, nnn: u16) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// set i to addr
|
2020-06-26 00:06:27 +00:00
|
|
|
self.i = nnn;
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn jmp_addr_with_offset(&mut self, nnn: u16) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// set program counter to addr + V0
|
2020-06-26 00:06:27 +00:00
|
|
|
self.pc = nnn + self.v[0] as u16;
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn rand(&mut self, x: u8, kk: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// generate a random number from 0 to 255
|
|
|
|
// AND with the value of kk, then store in Vx
|
2020-06-26 00:06:27 +00:00
|
|
|
self.v[x as usize] = rand::random::<u8>() & kk;
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn draw(&mut self, x: u8, y: u8, n: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// read n bytes from memory starting from self.i
|
|
|
|
// then display them starting at (vx, vy)
|
|
|
|
let disp_x = self.v[x as usize];
|
|
|
|
let disp_y = self.v[y as usize];
|
|
|
|
|
|
|
|
let mut display_buf = self.display.buf;
|
|
|
|
|
|
|
|
let mut pixel: u8;
|
2020-06-26 00:06:27 +00:00
|
|
|
self.v[0xF] = 0;
|
2020-06-25 18:13:09 +00:00
|
|
|
|
|
|
|
// TODO: Rewrite Your Solution to better fit
|
|
|
|
// the rest of your code base.
|
|
|
|
// http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/
|
2020-06-26 00:06:27 +00:00
|
|
|
for i in 0..(n as usize) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// height of sprite (y-axis)
|
|
|
|
pixel = self.memory[self.i as usize + i];
|
|
|
|
for j in 0..8 {
|
|
|
|
// Loop over the 8 bits (x-axis)
|
|
|
|
if (pixel & (0x80 >> j)) != 0 {
|
|
|
|
let index = (disp_x as usize) + j + (((disp_y as usize) + i) * 64);
|
|
|
|
|
|
|
|
if display_buf[index] == 1 {
|
2020-06-26 00:06:27 +00:00
|
|
|
self.v[0xF] = 1;
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
display_buf[index] ^= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn skip_on_press(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// if current key is the same as the on in Vx
|
|
|
|
// program counter is increased by 2
|
|
|
|
|
|
|
|
if let Some(key) = self.key.get_pressed() {
|
|
|
|
if self.v[x as usize] == key {
|
|
|
|
self.pc += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn skip_not_pressed(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// if current key is not the sameas the one in Vx
|
|
|
|
// increment the program counter by 2
|
|
|
|
|
|
|
|
match self.key.get_pressed() {
|
|
|
|
Some(key) => {
|
|
|
|
if self.v[x as usize] != key {
|
|
|
|
self.pc += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => self.pc += 2,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn copy_delay_timer_val(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// set Vx to be the value of the delay timer
|
|
|
|
self.v[x as usize] = self.delay.get();
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn loop_until_key_vx(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// wait (blocking) until a key is pressed
|
|
|
|
// once pressed, store in Vx
|
|
|
|
|
|
|
|
match self.key.get_pressed() {
|
|
|
|
Some(key) => self.v[x as usize] = key,
|
|
|
|
None => self.pc -= 2,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn set_delay_to_vx(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// set delay timer to be value of Vx
|
|
|
|
self.delay.set(self.v[x as usize]);
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn set_sound_to_vx(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// set sound timer to be value of Vx
|
|
|
|
self.delay.set(self.v[x as usize]);
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn add_vx_to_i(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// set I to be I + Vx
|
|
|
|
self.i = self.i + self.v[x as usize] as u16;
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn set_i_to_hex_sprite_loc(&mut self, x: u8) {
|
2020-06-25 19:22:37 +00:00
|
|
|
// set I to location of hex sprite related to Vx
|
|
|
|
self.i = self.v[x as usize] as u16 * 5;
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn copy_digits_to_i(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// take hundreds digit and place it at I
|
|
|
|
// take tens digit and place it at I + 1
|
|
|
|
// take ones digit and place it at I + 2
|
|
|
|
let i = self.i as usize;
|
2020-06-25 19:22:37 +00:00
|
|
|
let mut iter = Self::digits(self.v[x as usize] as usize);
|
2020-06-25 18:13:09 +00:00
|
|
|
|
|
|
|
let ones = iter.next().unwrap(); // Ther has to at least be a ones lol
|
|
|
|
let tens = iter.next().unwrap_or(0);
|
|
|
|
let hundreds = iter.next().unwrap_or(0);
|
|
|
|
|
2020-06-25 19:22:37 +00:00
|
|
|
self.memory[i] = hundreds as u8;
|
|
|
|
self.memory[i + 1] = tens as u8;
|
|
|
|
self.memory[i + 2] = ones as u8;
|
2020-06-25 18:13:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn copy_from_vx_to_memory(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// copy values v0 -> Vx to memory starting at i
|
|
|
|
|
|
|
|
for n in 0..=(x as usize) {
|
|
|
|
self.memory[self.i as usize + n] = self.v[n];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 00:06:27 +00:00
|
|
|
fn load_into_vx_from_i(&mut self, x: u8) {
|
2020-06-25 18:13:09 +00:00
|
|
|
// read what will be values of v0 -> vx from memory starting at i
|
|
|
|
let x = x as usize;
|
|
|
|
|
|
|
|
for n in 0..=x {
|
|
|
|
self.v[n] = self.memory[self.i as usize + n];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://stackoverflow.com/questions/41536479/how-do-i-split-an-integer-into-individual-digits
|
2020-06-25 19:22:37 +00:00
|
|
|
fn digits(mut num: usize) -> impl Iterator<Item = usize> {
|
2020-06-25 18:13:09 +00:00
|
|
|
let mut divisor = 1;
|
|
|
|
while num >= divisor * 10 {
|
|
|
|
divisor *= 10;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::iter::from_fn(move || {
|
|
|
|
if divisor == 0 {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
let v = num / divisor;
|
|
|
|
num %= divisor;
|
|
|
|
divisor /= 10;
|
|
|
|
Some(v)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-06-26 00:06:27 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::Chip8;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn chip8_initializes_properly() {
|
|
|
|
let chip8: Chip8 = Default::default();
|
|
|
|
assert_eq!(chip8.pc, 0x200);
|
|
|
|
|
|
|
|
let slice: &[u8] = &chip8.memory[0..0x50];
|
|
|
|
let font_set: &[u8] = &Chip8::FONT_SET;
|
|
|
|
|
|
|
|
for (i, byte) in slice.iter().enumerate() {
|
|
|
|
assert_eq!(*byte, font_set[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn get_opcode_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
let expected = 0x00E0; // Clear Screen
|
|
|
|
|
|
|
|
chip8.memory[0x202] = 0x00;
|
|
|
|
chip8.memory[0x203] = 0xE0;
|
|
|
|
chip8.pc = 0x202;
|
|
|
|
|
|
|
|
assert_eq!(chip8.get_opcode(), expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn get_nibs_works() {
|
|
|
|
let opcode = 0xA2F0;
|
|
|
|
|
|
|
|
let (n1, n2, n3, n4) = Chip8::get_nibs(opcode);
|
|
|
|
|
|
|
|
assert_eq!(n1, 0xA);
|
|
|
|
assert_eq!(n2, 0x2);
|
|
|
|
assert_eq!(n3, 0xF);
|
|
|
|
assert_eq!(n4, 0x0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_cls_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
// Display is now noise
|
|
|
|
for byte in chip8.display.buf.iter_mut() {
|
|
|
|
*byte = rand::random();
|
|
|
|
}
|
|
|
|
chip8.cls();
|
|
|
|
// now check that everything in the display buffer is zeroed out
|
|
|
|
for byte in chip8.display.buf.iter() {
|
|
|
|
assert_eq!(*byte, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_ret_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
chip8.pc = 0x202;
|
|
|
|
chip8.stack[0] = 0xABF0;
|
|
|
|
chip8.stack[1] = 0x50D5; // 80
|
|
|
|
chip8.sp = 1;
|
|
|
|
|
|
|
|
chip8.ret();
|
|
|
|
|
|
|
|
// Ret should set the programcouner to the address at the top of the stack
|
|
|
|
// and then decrement the stack pointer by 1
|
|
|
|
assert_eq!(chip8.sp, 0);
|
|
|
|
assert_eq!(chip8.pc, 0x50D5);
|
|
|
|
}
|
2020-06-26 04:59:59 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_jmp_addr_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
let opcode = 0x1ABC;
|
|
|
|
let nnn = opcode & 0x0FFF;
|
|
|
|
|
|
|
|
chip8.jmp_addr(nnn);
|
|
|
|
|
|
|
|
assert_eq!(chip8.pc, nnn);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_call_addr_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
let nnn = 0x0ABC;
|
|
|
|
chip8.call_addr(nnn);
|
|
|
|
|
|
|
|
assert_eq!(chip8.sp, 1);
|
|
|
|
assert_eq!(chip8.stack[chip8.sp as usize], 0x200);
|
|
|
|
assert_eq!(chip8.pc, nnn);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_se_vx_byte_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
// Opcode: 0x32D5
|
|
|
|
|
|
|
|
// Test Vx == kk
|
|
|
|
chip8.pc = 0x200;
|
|
|
|
chip8.v[2] = 0xD5;
|
|
|
|
chip8.se_vx_byte(0x2, 0xD5);
|
|
|
|
|
|
|
|
assert_eq!(chip8.pc, 0x202);
|
|
|
|
|
|
|
|
// Test Vx != kk
|
|
|
|
chip8.pc = 0x200;
|
|
|
|
chip8.v[2] = 0xD6;
|
|
|
|
chip8.se_vx_byte(0x2, 0xD5);
|
|
|
|
|
|
|
|
assert_ne!(chip8.pc, 0x202);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_sne_vx_byte_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
// Opcode: 0x32D6
|
|
|
|
|
|
|
|
// Test Vx != kk
|
|
|
|
chip8.pc = 0x200;
|
|
|
|
chip8.v[2] = 0xD5;
|
|
|
|
chip8.sne_vx_byte(0x2, 0xD6);
|
|
|
|
|
|
|
|
assert_eq!(chip8.pc, 0x202);
|
|
|
|
|
|
|
|
// Test Vx == kk
|
|
|
|
chip8.pc = 0x200;
|
|
|
|
chip8.v[2] = 0xD6;
|
|
|
|
chip8.sne_vx_byte(0x2, 0xD6);
|
|
|
|
|
|
|
|
assert_ne!(chip8.pc, 0x202);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_se_vx_vy_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
// When Equal
|
|
|
|
chip8.pc = 0x200;
|
|
|
|
chip8.v[4] = 0xAB;
|
|
|
|
chip8.v[7] = 0xAB;
|
|
|
|
chip8.se_vx_vy(0x4, 0x7);
|
|
|
|
|
|
|
|
assert_eq!(chip8.pc, 0x202);
|
|
|
|
|
|
|
|
// When not Equal
|
|
|
|
chip8.pc = 0x200;
|
|
|
|
chip8.v[4] = 0xAC;
|
|
|
|
chip8.v[7] = 0xAB;
|
|
|
|
|
|
|
|
chip8.se_vx_vy(0x4, 0x7);
|
|
|
|
assert_eq!(chip8.pc, 0x200);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_ld_vx_byte_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
assert_ne!(chip8.v[5], 0xAB);
|
|
|
|
chip8.ld_vx_byte(0x5, 0xAB);
|
|
|
|
assert_eq!(chip8.v[5], 0xAB);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_add_vx_byte_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
chip8.v[3] = 0x0A;
|
|
|
|
chip8.add_vx_byte(0x3, 0x36);
|
|
|
|
assert_eq!(chip8.v[3], 0x40);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_ld_vx_vy_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
chip8.v[1] = 0x64;
|
|
|
|
chip8.v[4] = 0x96;
|
|
|
|
|
|
|
|
chip8.ld_vx_vy(0x1, 0x4);
|
|
|
|
assert_eq!(chip8.v[1], 0x96);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_or_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
chip8.v[0xA] = 0xAB;
|
|
|
|
chip8.v[0x9] = 0xC6;
|
|
|
|
|
|
|
|
chip8.or(0xA, 0x9);
|
|
|
|
assert_eq!(chip8.v[0xA], 0xAB | 0xC6);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_and_vx_vy_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
chip8.v[0xC] = 0x58;
|
|
|
|
chip8.v[0x2] = 0x9a;
|
|
|
|
|
|
|
|
chip8.and_vx_vy(0xC, 0x2);
|
|
|
|
assert_eq!(chip8.v[0xC], 0x58 & 0x9a);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_xor_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
chip8.v[0x5] = 0x38;
|
|
|
|
chip8.v[0xB] = 0x4C;
|
|
|
|
|
|
|
|
chip8.xor(0x5, 0xB);
|
|
|
|
assert_eq!(chip8.v[0x5], 0x38 ^ 0x4C);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_add_vx_vy_works() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
chip8.v[0xA] = 0x01;
|
|
|
|
chip8.v[0x8] = 0xC8;
|
|
|
|
|
|
|
|
chip8.add_vx_vy(0xA, 0x8);
|
|
|
|
assert_eq!(chip8.v[0xA], 0x01 + 0xC8);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn opcode_add_vx_vy_overflows_as_expected() {
|
|
|
|
let mut chip8: Chip8 = Default::default();
|
|
|
|
|
|
|
|
chip8.v[0xE] = 0xFF;
|
|
|
|
chip8.v[0x7] = 0x01;
|
|
|
|
|
|
|
|
chip8.add_vx_vy(0xE, 0x7);
|
|
|
|
|
|
|
|
assert_eq!(chip8.v[0xA], (0xFF as u8).wrapping_add(0x01));
|
|
|
|
assert_eq!(chip8.v[0xF], 1);
|
|
|
|
}
|
2020-06-26 00:06:27 +00:00
|
|
|
}
|