242 lines
9.0 KiB
Rust
242 lines
9.0 KiB
Rust
use crate::cpu;
|
|
use rand::{self, RngExt};
|
|
|
|
const FONT: [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
|
|
];
|
|
pub struct Cpu {
|
|
reg: [u8; 16],
|
|
pub ram: [u8; 4096],
|
|
program_counter: u16,
|
|
stack: [u16; 16],
|
|
stack_pointer: u8,
|
|
index: u16,
|
|
pub display: [bool; 64 * 32],
|
|
delay_timer: u8,
|
|
sound_timer: u8,
|
|
pub keys: [bool; 16],
|
|
}
|
|
|
|
impl Cpu {
|
|
pub fn new(rom: &[u8]) -> Cpu {
|
|
let mut cpu = Cpu {
|
|
reg: [0; 16],
|
|
ram: [0; 4096],
|
|
program_counter: 0x200,
|
|
stack: [0; 16],
|
|
stack_pointer: 0,
|
|
index: 0,
|
|
display: [false; 64 * 32],
|
|
delay_timer: 0,
|
|
sound_timer: 0,
|
|
keys: [false; 16],
|
|
};
|
|
cpu.load_font();
|
|
cpu.load_rom(rom);
|
|
cpu
|
|
}
|
|
pub fn load_font(&mut self) {
|
|
self.load_into_ram(&FONT, 0x050);
|
|
}
|
|
pub fn load_rom(&mut self, cart: &[u8]) {
|
|
self.load_into_ram(cart, 0x200);
|
|
}
|
|
fn stack_push(&mut self, data: u16) {
|
|
self.stack[self.stack_pointer as usize] = data;
|
|
self.stack_pointer += 1;
|
|
}
|
|
fn stack_pop(&mut self) -> u16 {
|
|
self.stack_pointer -= 1;
|
|
self.stack[self.stack_pointer as usize]
|
|
}
|
|
pub fn tick(&mut self) {
|
|
// Fetch - read two bytes from RAM at program_counter
|
|
let hi = self.ram[self.program_counter as usize] as u16;
|
|
let lo = self.ram[(self.program_counter + 1) as usize] as u16;
|
|
let mut rng = rand::rng();
|
|
// Increment - increment the program counter by 2
|
|
self.program_counter += 2;
|
|
// Decode - figure out what those bytes mean
|
|
let opcode = (hi << 8) | lo;
|
|
let nibble = (opcode >> 12) & 0xF;
|
|
let x = ((opcode >> 8) & 0xF) as usize; // as usize for indexing into registers
|
|
let y = ((opcode >> 4) & 0xF) as usize;
|
|
let n = (opcode & 0xF) as usize;
|
|
let nn = (opcode & 0xFF) as u8;
|
|
let nnn = opcode & 0xFFF;
|
|
// Execute - do the thing
|
|
match nibble {
|
|
0x0 => {
|
|
match opcode {
|
|
0x00E0 => self.display = [false; 64 * 32], //clear the screen
|
|
0x00EE => self.program_counter = self.stack_pop(), // return from subroutine
|
|
_ => println!("opcode {:#06X} has not been implemented", opcode),
|
|
}
|
|
}
|
|
0x1 => self.program_counter = nnn, //0x1NNN is jump to NNN
|
|
0x2 => {
|
|
// call subroutine at nnn
|
|
self.stack_push(self.program_counter);
|
|
self.program_counter = nnn
|
|
}
|
|
0x3 => {
|
|
if self.reg[x] == nn {
|
|
self.program_counter += 2
|
|
}
|
|
}
|
|
0x4 => {
|
|
if self.reg[x] != nn {
|
|
self.program_counter += 2
|
|
}
|
|
}
|
|
0x5 => {
|
|
if self.reg[x] == self.reg[y] {
|
|
self.program_counter += 2
|
|
}
|
|
}
|
|
0x6 => self.reg[x] = nn,
|
|
0x7 => self.reg[x] = self.reg[x].wrapping_add(nn),
|
|
0x8 => match n {
|
|
0x0 => self.reg[x] = self.reg[y],
|
|
0x1 => self.reg[x] |= self.reg[y],
|
|
0x2 => self.reg[x] &= self.reg[y],
|
|
0x3 => self.reg[x] ^= self.reg[y],
|
|
0x4 => {
|
|
let (result, carry) = self.reg[x].overflowing_add(self.reg[y]);
|
|
self.reg[x] = result;
|
|
self.reg[0xF] = if carry { 1 } else { 0 };
|
|
}
|
|
0x5 => {
|
|
let result = self.reg[x].wrapping_sub(self.reg[y]);
|
|
let not_borrow = (self.reg[x] >= self.reg[y]) as u8;
|
|
self.reg[x] = result;
|
|
self.reg[0xF] = not_borrow;
|
|
}
|
|
0x6 => {
|
|
self.reg[x] = self.reg[y];
|
|
let lsb = self.reg[x] & 1;
|
|
self.reg[x] >>= 1;
|
|
self.reg[0xF] = lsb;
|
|
}
|
|
0x7 => {
|
|
let result = self.reg[y].wrapping_sub(self.reg[x]);
|
|
let not_borrow = (self.reg[y] >= self.reg[x]) as u8;
|
|
self.reg[x] = result;
|
|
self.reg[0xF] = not_borrow;
|
|
}
|
|
0xE => { //8xyE - SHL Vx {, Vy}
|
|
//Set Vx = Vx SHL 1.
|
|
//If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.
|
|
self.reg[x] = self.reg[y];
|
|
let msb = (self.reg[x] >> 7) & 1;
|
|
self.reg[x] <<= 1;
|
|
self.reg[0xF] = msb;
|
|
}
|
|
_ => println!("opcode {:#06X} has not been implemented", opcode),
|
|
},
|
|
0x9 => {
|
|
if self.reg[x] != self.reg[y] {
|
|
self.program_counter += 2
|
|
}
|
|
}
|
|
0xA => self.index = nnn, //0xANNN set index to NNN
|
|
0xB => self.program_counter = self.reg[0] as u16 + nnn,
|
|
0xC => {
|
|
let random_byte: u8 = rng.random();
|
|
self.reg[x] = nn & random_byte;
|
|
}
|
|
0xD => {
|
|
//0xDXYN is display, and is a doozy.
|
|
// get X and Y coords from VX and VY
|
|
let x_coord: usize = self.reg[x].into();
|
|
let y_coord: usize = self.reg[y].into();
|
|
let mut flipped = false;
|
|
for row in 0..n {
|
|
let sprite_byte = self.ram[self.index as usize + row];
|
|
for col in 0..8 {
|
|
let bit = (sprite_byte >> (7 - col)) & 1;
|
|
let y = y_coord + row;
|
|
let x = x_coord + col;
|
|
let idx = y * 64 + x;
|
|
if bit == 1 {
|
|
self.display[idx] ^= true;
|
|
flipped |= !self.display[idx];
|
|
}
|
|
}
|
|
}
|
|
if flipped {
|
|
self.reg[0xF] = 1;
|
|
} else {
|
|
self.reg[0xF] = 0;
|
|
}
|
|
}
|
|
0xE => match nn {
|
|
0x9E => {
|
|
if self.keys[self.reg[x] as usize] {
|
|
self.program_counter += 2
|
|
}
|
|
}
|
|
0xA1 => {
|
|
if !self.keys[self.reg[x] as usize] {
|
|
self.program_counter += 2
|
|
}
|
|
}
|
|
_ => println!("opcode {:#06X} has not been implemented", opcode), // unknown opcode
|
|
},
|
|
0xF => match nn {
|
|
0x07 => self.reg[x] = self.delay_timer,
|
|
0x0A => {}
|
|
0x15 => self.delay_timer = self.reg[x],
|
|
0x18 => self.sound_timer = self.reg[x],
|
|
0x1E => self.index = self.index.wrapping_add(self.reg[x] as u16),
|
|
0x29 => {
|
|
self.index = ((self.reg[x] * 5) + 0x050) as u16;
|
|
}
|
|
0x33 => { // BCD conversion isn't something I've encountered before
|
|
// Very interesting op
|
|
let vx = self.reg[x];
|
|
let hundreds = vx / 100;
|
|
let tens = (vx / 10) % 10;
|
|
let ones = vx % 10;
|
|
self.ram[self.index as usize] = hundreds;
|
|
self.ram[(self.index + 1) as usize] = tens;
|
|
self.ram[(self.index + 2) as usize] = ones;
|
|
}
|
|
0x55 => {
|
|
for reg in 0..=x {
|
|
self.ram[self.index as usize + reg] = self.reg[reg];
|
|
}
|
|
}
|
|
0x65 => {
|
|
for reg in 0..=x {
|
|
self.reg[reg] = self.ram[self.index as usize + reg];
|
|
}
|
|
}
|
|
_ => println!("opcode {:#06X} has not been implemented", opcode),
|
|
},
|
|
_ => println!("opcode {:#06X} has not been implemented", opcode), // unknown opcode
|
|
}
|
|
}
|
|
fn load_into_ram(&mut self, data: &[u8], offset: usize) {
|
|
data.iter().enumerate().for_each(|(i, byte)| {
|
|
//write byte to ram starting at 0x200
|
|
self.ram[offset + i] = *byte;
|
|
});
|
|
}
|
|
}
|