buttery8/src/cpu.rs
2026-04-07 10:48:11 -04:00

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;
});
}
}