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