diff --git a/chip8_core/src/font.rs b/chip8_core/src/font.rs new file mode 100644 index 0000000..ce336be --- /dev/null +++ b/chip8_core/src/font.rs @@ -0,0 +1,20 @@ +pub const FONTSET_SIZE: usize = 80; + +pub const FONTSET: [u8; FONTSET_SIZE] = [ + 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 +]; diff --git a/chip8_core/src/lib.rs b/chip8_core/src/lib.rs index 6d5507c..6f70e3a 100644 --- a/chip8_core/src/lib.rs +++ b/chip8_core/src/lib.rs @@ -1,27 +1,7 @@ use rand::Rng; -const FONTSET_SIZE: usize = 80; - -const FONTSET: [u8; FONTSET_SIZE] = [ - 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 -]; - - +mod font; +use font::*; pub const RAM_SIZE: usize = 4096; pub const REGISTER_COUNT: usize = 16; @@ -120,12 +100,12 @@ impl Emu { // STACK - fn push(&mut self, val:u16) { + fn stack_push(&mut self, val:u16) { self.stack[self.stack_pointer as usize] = val; self.stack_pointer += 1; } - fn pop(&mut self) -> u16 { + fn stack_pop(&mut self) -> u16 { self.stack_pointer -= 1; self.stack[self.stack_pointer as usize] } @@ -158,303 +138,363 @@ impl Emu { let digit2 = (op & 0x0F00) >> 8; let digit3 = (op & 0x00F0) >> 4; let digit4 = op & 0x000F; + + let x = digit2 as usize; + let y = digit3 as usize; - let mut framebuffer_modified = false; + let nnn = op & 0xFFF; + let nn = op & 0xFF; + let n = op & 0xF; match (digit1, digit2, digit3, digit4) { - // NOP - (0, 0, 0, 0) => return false, - // CLS - (0, 0, 0xE, 0) => { - self.screen = [false; SCREEN_WIDTH * SCREEN_HEIGHT]; - }, - // RET - (0, 0, 0xE, 0xE) => { - let ret_addr = self.pop(); - self.programm_counter = ret_addr; - }, - // JMP NNN - (1, _, _, _) => { - let nnn = op & 0xFFF; - self.programm_counter = nnn; - }, - // CALL NNN - (2, _, _, _) => { - let nnn = op & 0xFFF; - self.push(self.programm_counter); - self.programm_counter = nnn; - }, - // SKIP VX == NN - (3, _, _, _) => { - let x = digit2 as usize; - let nn = (op & 0xFF) as u8; - if self.registers[x] == nn { - self.programm_counter += 2; - } - }, - // SKIP VX != NN - (4, _, _, _) => { - let x = digit2 as usize; - let nn = (op & 0xFF) as u8; - if self.registers[x] != nn { - self.programm_counter += 2; - } - }, - // SKIP VX == VY - (5, _, _, _) => { - let x = digit2 as usize; - let y = digit3 as usize; - if self.registers[x] == self.registers[y] { - self.programm_counter += 2; - } - }, - // VX = NN - (6, _, _, _) => { - let x = digit2 as usize; - let nn = (op & 0xFF) as u8; - self.registers[x] = nn; - }, - // VX += NN - (7, _, _, _) => { - let x = digit2 as usize; - let nn = (op & 0xFF) as u8; - self.registers[x] = self.registers[x].wrapping_add(nn); - }, - // VX = VY - (8, _, _, 0) => { - let x = digit2 as usize; - let y = digit3 as usize; - self.registers[x] = self.registers[y]; - }, - // VX |= VY - (8, _, _, 1) => { - let x = digit2 as usize; - let y = digit3 as usize; - self.registers[x] |= self.registers[y]; - }, - // VX &= VY - (8, _, _, 2) => { - let x = digit2 as usize; - let y = digit3 as usize; - self.registers[x] &= self.registers[y]; - }, - // VX ^= VY - (8, _, _, 3) => { - let x = digit2 as usize; - let y = digit3 as usize; - self.registers[x] ^= self.registers[y]; - }, - // VX += VY - (8, _, _, 4) => { - let x = digit2 as usize; - let y = digit3 as usize; - - let (new_vx, carry) = self.registers[x].overflowing_add(self.registers[y]); - let new_vf = if carry { 1 } else { 0 }; - - self.registers[x] = new_vx; - self.registers[0xF] = new_vf; - }, - // VX -= VY - (8, _, _, 5) => { - let x = digit2 as usize; - let y = digit3 as usize; - - let (new_vx, borrow) = self.registers[x].overflowing_sub(self.registers[y]); - let new_vf = if borrow { 0 } else { 1 }; - - self.registers[x] = new_vx; - self.registers[0xF] = new_vf; - }, - // VX >>= 1 - (8, _, _, 6) => { - let x = digit2 as usize; - let lsb = self.registers[x] & 1; - self.registers[x] >>= 1; - self.registers[0xF] = lsb; - }, - // VX = VY - VX - (8, _, _, 7) => { - let x = digit2 as usize; - let y = digit3 as usize; - - let (new_vx, borrow) = self.registers[y].overflowing_sub(self.registers[x]); - let new_vf = if borrow { 0 } else { 1 }; - - self.registers[x] = new_vx; - self.registers[0xF] = new_vf; - }, - // VX <<= 1 - (8, _, _, 0xE) => { - let x = digit2 as usize; - let msb = (self.registers[x] >> 7) & 1; - self.registers[x] <<= 1; - self.registers[0xF] = msb; - }, - // SKIP VX != VY - (9, _, _, 0) => { - let x = digit2 as usize; - let y = digit3 as usize; - if self.registers[x] != self.registers[y] { - self.programm_counter += 2; - } - }, - // I = NNN - (0xA, _, _, _) => { - let nnn = op & 0xFFF; - self.i_register = nnn; - }, - // JMP V0 + NNN - (0xB, _, _, _) => { - let nnn = op & 0xFFF; - self.programm_counter = (self.registers[0] as u16) + nnn; - }, - // VX = rand() & NN - (0xC, _, _, _) => { - let x = digit2 as usize; - let nn = (op & 0xFF) as u8; - let rng: u8 = rand::thread_rng().gen(); - self.registers[x] = rng & nn; - }, - // DRAW - (0xD, _, _, _) => { - // Get the (x, y) coords for our sprite - let x_coord = self.registers[digit2 as usize] as u16; - let y_coord = self.registers[digit3 as usize] as u16; - // The last digit determines how many rows high our sprite is - let num_rows = digit4; - - // Keep track if any pixels were flipped - let mut flipped = false; - // Iterate over each row of our sprite - for y_line in 0..num_rows { - // Determine which memory address our row's data is stored - let addr = self.i_register + y_line as u16; - let pixels = self.ram[addr as usize]; - // Iterate over each column in our row - for x_line in 0..8 { - // Use a mask to fetch current pixel's bit. Only flip if a 1 - if (pixels & (0b1000_0000 >> x_line)) != 0 { - // Sprites should wrap around screen, so apply modulo - let x = (x_coord + x_line) as usize % SCREEN_WIDTH; - let y = (y_coord + y_line) as usize % SCREEN_HEIGHT; - - // Get our pixel's index in the 1D screen array - let idx = x + SCREEN_WIDTH * y; - // Check if we're about to flip the pixel and set - flipped |= self.screen[idx]; - self.screen[idx] ^= true; - } - } - } - // Populate VF register - if flipped { - self.registers[0xF] = 1; - } else { - self.registers[0xF] = 0; - } - - framebuffer_modified = true; - }, - // SKIP KEY PRESS - (0xE, _, 9, 0xE) => { - let x = digit2 as usize; - let vx = self.registers[x]; - let key = self.pressed_keys[vx as usize]; - if key { - self.programm_counter += 2; - } - }, - // SKIP KEY RELEASE - (0xE, _, 0xA, 1) => { - let x = digit2 as usize; - let vx = self.registers[x]; - let key = self.pressed_keys[vx as usize]; - if !key { - self.programm_counter += 2; - } - }, - // VX = DT - (0xF, _, 0, 7) => { - let x = digit2 as usize; - self.registers[x] = self.delay_timer; - }, - // WAIT KEY - (0xF, _, 0, 0xA) => { - let x = digit2 as usize; - let mut pressed = false; - for i in 0..self.pressed_keys.len() { - if self.pressed_keys[i] { - self.registers[x] = i as u8; - pressed = true; - break; - } - } - - if !pressed { - // Redo opcode - self.programm_counter -= 2; - } - }, - // DT = VX - (0xF, _, 1, 5) => { - let x = digit2 as usize; - self.delay_timer = self.registers[x]; - }, - // ST = VX - (0xF, _, 1, 8) => { - let x = digit2 as usize; - self.sound_timer = self.registers[x]; - }, - // I += VX - (0xF, _, 1, 0xE) => { - let x = digit2 as usize; - let vx = self.registers[x] as u16; - self.i_register = self.i_register.wrapping_add(vx); - }, - // I = FONT - (0xF, _, 2, 9) => { - let x = digit2 as usize; - let c = self.registers[x] as u16; - self.i_register = c * 5; - }, - // BCD - (0xF, _, 3, 3) => { - let x = digit2 as usize; - let vx = self.registers[x] as f32; - - // Fetch the hundreds digit by dividing by 100 and tossing the decimal - let hundreds = (vx / 100.0).floor() as u8; - // Fetch the tens digit by dividing by 10, tossing the ones digit and the decimal - let tens = ((vx / 10.0) % 10.0).floor() as u8; - // Fetch the ones digit by tossing the hundreds and the tens - let ones = (vx % 10.0) as u8; - - self.ram[self.i_register as usize] = hundreds; - self.ram[(self.i_register + 1) as usize] = tens; - self.ram[(self.i_register + 2) as usize] = ones; - }, - // STORE V0 - VX - (0xF, _, 5, 5) => { - let x = digit2 as usize; - let i = self.i_register as usize; - for idx in 0..=x { - self.ram[i + idx] = self.registers[idx]; - } - }, - // LOAD V0 - VX - (0xF, _, 6, 5) => { - let x = digit2 as usize; - let i = self.i_register as usize; - for idx in 0..=x { - self.registers[idx] = self.ram[i + idx]; - } - }, + (0, 0, 0, 0) => self.op_0000(), + (0, 0, 0xE, 0) => self.op_00e0(), + (0, 0, 0xE, 0xE) => self.op_00ee(), + (1, _, _, _) => self.op_1nnn(nnn), + (2, _, _, _) => self.op_2nnn(nnn), + (3, _, _, _) => self.op_3xnn(x, nn), + (4, _, _, _) => self.op_4xnn(x, nn), + (5, _, _, _) => self.op_5xy0(x, y), + (6, _, _, _) => self.op_6xnn(x, nn), + (7, _, _, _) => self.op_7xnn(x, nn), + (8, _, _, 0) => self.op_8xy0(x, y), + (8, _, _, 1) => self.op_8xy1(x, y), + (8, _, _, 2) => self.op_8xy2(x, y), + (8, _, _, 3) => self.op_8xy3(x, y), + (8, _, _, 4) => self.op_8xy4(x, y), + (8, _, _, 5) => self.op_8xy5(x, y), + (8, _, _, 6) => self.op_8xy6(x, y), + (8, _, _, 7) => self.op_8xy7(x, y), + (8, _, _, 0xE) => self.op_8xye(x, y), + (9, _, _, 0) => self.op_9xy0(x, y), + (0xA, _, _, _) => self.op_annn(nnn), + (0xB, _, _, _) => self.op_bnnn(nnn), + (0xC, _, _, _) => self.op_cxnn(x, nn), + (0xD, _, _, _) => self.op_dxyn(x, y, n), + (0xE, _, 9, 0xE) => self.op_ex9e(x), + (0xE, _, 0xA, 1) => self.op_exa1(x), + (0xF, _, 0, 7) => self.op_fx07(x), + (0xF, _, 0, 0xA) => self.op_fx0a(x), + (0xF, _, 1, 5) => self.op_fx15(x), + (0xF, _, 1, 8) => self.op_fx18(x), + (0xF, _, 1, 0xE) => self.op_fx1e(x), + (0xF, _, 2, 9) => self.op_fx29(x), + (0xF, _, 3, 3) => self.op_fx33(x), + (0xF, _, 5, 5) => self.op_fx55(x), + (0xF, _, 6, 5) => self.op_fx65(x), (_, _, _, _) => unimplemented!("Unimplemented opcode: {:#04x}", op), } - - framebuffer_modified } + + // NOP + fn op_0000(&self) -> bool { false } + + // CLS + fn op_00e0(&mut self) -> bool { + self.screen = [false; SCREEN_WIDTH * SCREEN_HEIGHT]; + false + } + + // RETURN FROM SUBROUTINE + fn op_00ee(&mut self) -> bool { + let ret_addr = self.stack_pop(); + self.programm_counter = ret_addr; + false + } + + // JUMP TO NNN + fn op_1nnn(&mut self, nnn: u16) -> bool { + self.programm_counter = nnn; + false + } + + // CALL nnn + fn op_2nnn(&mut self, nnn: u16) -> bool { + self.stack_push(self.programm_counter); + self.programm_counter = nnn; + false + } + // SKIP VX == NN + fn op_3xnn(&mut self, x: usize, nn: u16) -> bool { + if self.registers[x] == nn as u8 { + self.programm_counter += 2; + }; + false + } + // SKIP VX != NN + fn op_4xnn(&mut self, x: usize, nn: u16) -> bool { + if self.registers[x] != nn as u8 { + self.programm_counter += 2; + }; + false + } + + // SKIP VX == VY + fn op_5xy0(&mut self, x: usize, y: usize) -> bool { + if self.registers[x] == self.registers[y] { + self.programm_counter += 2; + }; + false + } + + // VX = NN + fn op_6xnn(&mut self, x: usize, nn: u16) -> bool { + self.registers[x] = nn as u8; + false + } + + // VX += NN + fn op_7xnn(&mut self, x: usize, nn: u16) -> bool { + self.registers[x] = self.registers[x].wrapping_add(nn as u8); + false + } + + // VX = VY + fn op_8xy0(&mut self, x: usize, y:usize) -> bool { + self.registers[x] = self.registers[y]; + false + } + + // VX |= VY + fn op_8xy1(&mut self, x: usize, y:usize) -> bool { + self.registers[x] |= self.registers[y]; + false + } + // VX &= VY + fn op_8xy2(&mut self, x: usize, y:usize) -> bool { + self.registers[x] &= self.registers[y]; + false + } + + // VX ^= VY + fn op_8xy3(&mut self, x: usize, y:usize) -> bool { + self.registers[x] ^= self.registers[y]; + false + } + + // VX += VY + fn op_8xy4(&mut self, x: usize, y: usize) -> bool { + let (new_vx, carry) = self.registers[x].overflowing_add(self.registers[y]); + let new_vf = if carry { 1 } else { 0 }; + + self.registers[x] = new_vx; + self.registers[0xF] = new_vf; + false + } + + // VX -= VY + fn op_8xy5(&mut self, x: usize, y: usize) -> bool { + let (new_vx, borrow) = self.registers[x].overflowing_sub(self.registers[y]); + let new_vf = if borrow { 0 } else { 1 }; + + self.registers[x] = new_vx; + self.registers[0xF] = new_vf; + false + } + + // VX >>= 1 + fn op_8xy6(&mut self, x: usize, _y: usize) -> bool { + let lsb = self.registers[x] & 1; + self.registers[x] >>= 1; + self.registers[0xF] = lsb; + false + } + + // VX = VY - VX + fn op_8xy7(&mut self, x: usize, y: usize) -> bool { + let (new_vx, borrow) = self.registers[y].overflowing_sub(self.registers[x]); + let new_vf = if borrow { 0 } else { 1 }; + + self.registers[x] = new_vx; + self.registers[0xF] = new_vf; + false + } + + // VX <<= 1 + fn op_8xye(&mut self, x: usize, _y: usize) -> bool { + let msb = (self.registers[x] >> 7) & 1; + self.registers[x] <<= 1; + self.registers[0xF] = msb; + false + } + + // SKIP VX != VY + fn op_9xy0(&mut self, x: usize, y: usize) -> bool { + if self.registers[x] != self.registers[y] { + self.programm_counter += 2; + }; + false + } + + // I = NNN + fn op_annn(&mut self, nnn: u16) -> bool { + self.i_register = nnn; + false + } + + // JMP V0 + NNN + fn op_bnnn(&mut self, nnn: u16) -> bool { + self.programm_counter = (self.registers[0] as u16) + nnn; + false + } + + // VX = rand() & NN + fn op_cxnn(&mut self, x: usize, nn: u16) -> bool { + let rng: u8 = rand::thread_rng().gen(); + self.registers[x] = rng & (nn as u8); + false + } + + // DRAW + fn op_dxyn(&mut self, x: usize, y: usize, n: u16) -> bool { + // Get the (x, y) coords for our sprite + let x_coord = self.registers[x] as u16; + let y_coord = self.registers[y] as u16; + // The last digit determines how many rows high our sprite is + let num_rows = n; + + // Keep track if any pixels were flipped + let mut flipped = false; + // Iterate over each row of our sprite + for y_line in 0..num_rows { + // Determine which memory address our row's data is stored + let addr = self.i_register + y_line; + let pixels = self.ram[addr as usize]; + // Iterate over each column in our row + for x_line in 0..8 { + // Use a mask to fetch current pixel's bit. Only flip if a 1 + if (pixels & (0b1000_0000 >> x_line)) != 0 { + // Sprites should wrap around screen, so apply modulo + let x = (x_coord + x_line) as usize % SCREEN_WIDTH; + let y = (y_coord + y_line) as usize % SCREEN_HEIGHT; + + // Get our pixel's index in the 1D screen array + let idx = x + SCREEN_WIDTH * y; + // Check if we're about to flip the pixel and set + flipped |= self.screen[idx]; + self.screen[idx] ^= true; + } + } + } + // Populate VF register + if flipped { + self.registers[0xF] = 1; + } else { + self.registers[0xF] = 0; + } + + true + } + + // SKIP KEY PRESS + fn op_ex9e(&mut self, x: usize) -> bool { + let vx = self.registers[x]; + let key = self.pressed_keys[vx as usize]; + if key { + self.programm_counter += 2; + }; + false + } + + // SKIP KEY RELEASE + fn op_exa1(&mut self, x: usize) -> bool { + let vx = self.registers[x]; + let key = self.pressed_keys[vx as usize]; + if !key { + self.programm_counter += 2; + }; + false + } + + // VX = DT + fn op_fx07(&mut self, x: usize) -> bool { + self.registers[x] = self.delay_timer; + false + } + + // WAIT KEY + fn op_fx0a(&mut self, x: usize) -> bool { + let mut pressed = false; + for i in 0..self.pressed_keys.len() { + if self.pressed_keys[i] { + self.registers[x] = i as u8; + pressed = true; + break; + } + } + + if !pressed { + // Redo opcode + self.programm_counter -= 2; + }; + + false + } + + // DT = VX + fn op_fx15(&mut self, x: usize) -> bool { + self.delay_timer = self.registers[x]; + false + } + + // ST = VX + fn op_fx18(&mut self, x: usize) -> bool { + self.sound_timer = self.registers[x]; + false + } + + // I += VX + fn op_fx1e(&mut self, x: usize) -> bool { + let vx = self.registers[x] as u16; + self.i_register = self.i_register.wrapping_add(vx); + false + } + + // I = FONT + fn op_fx29(&mut self, x: usize) -> bool { + let c = self.registers[x] as u16; + self.i_register = c * 5; + false + } + + // BCD + fn op_fx33(&mut self, x: usize) -> bool { + let vx = self.registers[x] as f32; + + // Fetch the hundreds digit by dividing by 100 and tossing the decimal + let hundreds = (vx / 100.0).floor() as u8; + // Fetch the tens digit by dividing by 10, tossing the ones digit and the decimal + let tens = ((vx / 10.0) % 10.0).floor() as u8; + // Fetch the ones digit by tossing the hundreds and the tens + let ones = (vx % 10.0) as u8; + + self.ram[self.i_register as usize] = hundreds; + self.ram[(self.i_register + 1) as usize] = tens; + self.ram[(self.i_register + 2) as usize] = ones; + false + } + + // STORE V0 - VX + fn op_fx55(&mut self, x: usize) -> bool { + let i = self.i_register as usize; + for idx in 0..=x { + self.ram[i + idx] = self.registers[idx]; + }; + false + } + + // LOAD V0 - VX + fn op_fx65(&mut self, x: usize) -> bool { + let i = self.i_register as usize; + for idx in 0..=x { + self.registers[idx] = self.ram[i + idx]; + }; + false + } + + + + // TIMERS pub fn tick_timers(&mut self) -> bool {