diff --git a/examples/benchmark/src/main.rs b/examples/benchmark/src/main.rs index b5ca3e94ee584c0f32e89b344fbb24f63e75889b..f590970b62b85a3511e8458d61e96f79c93b1883 100644 --- a/examples/benchmark/src/main.rs +++ b/examples/benchmark/src/main.rs @@ -1,65 +1,44 @@ -use chip_ahoyto::{chip8::Chip8, chip8_neo::Chip8Neo, util::read_file}; +use chip_ahoyto::{ + chip8::Chip8, chip8_classic::Chip8Classic, chip8_neo::Chip8Neo, util::read_file, +}; use time::Instant; const CYCLE_COUNT: u64 = 5_000_000_000; -fn benchmark_chip8() { - let rom_path = "./resources/pong.ch8"; - let rom = read_file(rom_path); - - let mut chip8 = Chip8::new(); - chip8.reset_hard(); - chip8.load_rom(&rom); - - let instant = Instant::now(); - - let cycles = CYCLE_COUNT; - - println!("[Chip8] Running {} cycles for {}", cycles, rom_path); - - for _ in 0..CYCLE_COUNT { - chip8.clock(); - } - - let duration_s = instant.elapsed().as_seconds_f32(); - let cycles_second = cycles as f32 / duration_s; - let mega_second = cycles_second / 1000.0 / 1000.0; - - println!( - "[Chip8] Took {} seconds or {:.2} MHz CPU", - duration_s, mega_second - ); -} +fn main() { + let chips: Vec<Box<dyn Chip8>> = vec![Box::new(Chip8Classic::new()), Box::new(Chip8Neo::new())]; -fn benchmark_chip8_neo() { let rom_path = "./resources/pong.ch8"; let rom = read_file(rom_path); - let mut chip8 = Chip8Neo::new(); - chip8.reset_hard(); - chip8.load_rom(&rom); - - let instant = Instant::now(); + for mut chip8 in chips { + chip8.reset_hard(); + chip8.load_rom(&rom); - let cycles = CYCLE_COUNT; + let instant = Instant::now(); - println!("[Chip8Neo] Running {} cycles for {}", cycles, rom_path); + let cycles = CYCLE_COUNT; - for _ in 0..CYCLE_COUNT { - chip8.clock(); - } + println!( + "[{}] Running {} cycles for {}", + chip8.name(), + cycles, + rom_path + ); - let duration_s = instant.elapsed().as_seconds_f32(); - let cycles_second = cycles as f32 / duration_s; - let mega_second = cycles_second / 1000.0 / 1000.0; + for _ in 0..CYCLE_COUNT { + chip8.clock(); + } - println!( - "[Chip8Neo] Took {} seconds or {:.2} MHz CPU", - duration_s, mega_second - ); -} + let duration_s = instant.elapsed().as_seconds_f32(); + let cycles_second = cycles as f32 / duration_s; + let mega_second = cycles_second / 1000.0 / 1000.0; -fn main() { - benchmark_chip8(); - benchmark_chip8_neo(); + println!( + "[{}] Took {} seconds or {:.2} MHz CPU", + chip8.name(), + duration_s, + mega_second + ); + } } diff --git a/examples/sdl/src/main.rs b/examples/sdl/src/main.rs index 27899a6bdfc066bf7c67a01e4a13415939e9e178..996de6d3a3ea8aecae2d9069a876673dd86858fa 100644 --- a/examples/sdl/src/main.rs +++ b/examples/sdl/src/main.rs @@ -1,6 +1,6 @@ use chip_ahoyto::{ - chip8::Chip8, chip8::SCREEN_PIXEL_HEIGHT, chip8::SCREEN_PIXEL_WIDTH, chip8_neo::Chip8Neo, - util::read_file, + chip8::Chip8, chip8_classic::Chip8Classic, chip8_classic::SCREEN_PIXEL_HEIGHT, + chip8_classic::SCREEN_PIXEL_WIDTH, chip8_neo::Chip8Neo, util::read_file, }; use sdl2::{ audio::AudioCallback, audio::AudioSpecDesired, event::Event, image::LoadSurface, diff --git a/src/chip8.rs b/src/chip8.rs index ce6b37e0f77a8e2a87e06d6c937380c706c1da54..fccf0c688555b1fd2e6822294a7acdce314a6901 100644 --- a/src/chip8.rs +++ b/src/chip8.rs @@ -1,332 +1,15 @@ -use std::fmt::Display; - -use crate::util::random; - -#[cfg(feature = "web")] -use wasm_bindgen::prelude::*; - -/// The width of the screen in pixels. -pub const SCREEN_PIXEL_WIDTH: usize = 64; - -/// The height of the screen in pixels. -pub const SCREEN_PIXEL_HEIGHT: usize = 32; - -/// The number of keys to be allocated to the machine. -const NUM_KEYS: usize = 16; - -/// The total number of CPU registers that are going to be used -/// this value should follow the typical convention. -const NUM_REGISTERS: usize = 16; - -/// The size of the stack, this value is actually a convention -/// as no specific definition on its size was ever created. -const STACK_SIZE: usize = 16; - -/// The size of the RAM memory in bytes. -const RAM_SIZE: usize = 4096; - -/// The starting address for the ROM loading, should be -/// the initial PC position. -const ROM_START: usize = 0x200; - -/// Buffer that contains the base CHIP-8 font set that -/// is going to be used to draw the font in the screen -static 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 -]; - -#[cfg_attr(feature = "web", wasm_bindgen)] -pub struct Chip8 { - vram: [u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT], - ram: [u8; RAM_SIZE], - registers: [u8; NUM_REGISTERS], - stack: [u16; STACK_SIZE], - i: u16, - dt: u8, - st: u8, - pc: u16, - sp: u8, - beep: bool, - last_key: u8, - keys: [bool; NUM_KEYS], -} - -#[cfg_attr(feature = "web", wasm_bindgen)] -impl Chip8 { - #[cfg_attr(feature = "web", wasm_bindgen(constructor))] - pub fn new() -> Chip8 { - let mut chip8 = Chip8 { - vram: [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT], - ram: [0u8; RAM_SIZE], - registers: [0u8; NUM_REGISTERS], - stack: [0u16; STACK_SIZE], - i: 0, - dt: 0, - st: 0, - pc: ROM_START as u16, - sp: 0, - beep: false, - last_key: 0x00, - keys: [false; NUM_KEYS], - }; - chip8.load_font(&FONT_SET); - chip8 - } - - pub fn reset(&mut self) { - self.vram = [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT]; - self.registers = [0u8; NUM_REGISTERS]; - self.stack = [0u16; STACK_SIZE]; - self.i = 0; - self.dt = 0; - self.st = 0; - self.pc = ROM_START as u16; - self.sp = 0; - self.beep = false; - self.last_key = 0x00; - self.keys = [false; NUM_KEYS]; - self.load_font(&FONT_SET); - } - - pub fn reset_hard(&mut self) { - self.ram = [0u8; RAM_SIZE]; - self.reset(); - } - - pub fn name(&self) -> &str { - "classic" - } - - pub fn pixels(&self) -> Vec<u8> { - self.vram.to_vec() - } - - pub fn beep(&self) -> bool { - self.beep - } - - pub fn pc(&self) -> u16 { - self.pc - } - - pub fn sp(&self) -> u8 { - self.sp - } - - pub fn load_rom(&mut self, rom: &[u8]) { - self.ram[ROM_START..ROM_START + rom.len()].clone_from_slice(rom); - } - - pub fn clock(&mut self) { - let opcode = self.fetch_opcode(); - self.process_opcode(opcode); - } - - pub fn clock_dt(&mut self) { - self.dt = self.dt.saturating_sub(1); - } - - pub fn clock_st(&mut self) { - self.st = self.st.saturating_sub(1); - self.beep = self.st > 0; - } - - pub fn key_press(&mut self, key: u8) { - if key < NUM_KEYS as u8 { - self.last_key = key; - self.keys[key as usize] = true; - } - } - - pub fn key_lift(&mut self, key: u8) { - if key < NUM_KEYS as u8 { - self.keys[key as usize] = false; - } - } -} - -impl Chip8 { - fn process_opcode(&mut self, opcode: u16) { - let id = opcode & 0xf000; - let addr = opcode & 0x0fff; - let nibble = (opcode & 0x000f) as u8; - let x = (opcode >> 8 & 0xf) as usize; - let y = (opcode >> 4 & 0xf) as usize; - let byte = (opcode & 0x00ff) as u8; - - match id { - 0x0000 => match byte { - 0xe0 => self.vram = [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT], - 0xee => self.return_subroutine(), - _ => panic!("unknown opcode 0x{:04x}", opcode), - }, - 0x1000 => self.pc = addr, - 0x2000 => self.call_subroutine(addr), - 0x3000 => self.skip_if(self.registers[x] == byte), - 0x4000 => self.skip_if(self.registers[x] != byte), - 0x5000 => self.skip_if(self.registers[x] == self.registers[y]), - 0x6000 => self.registers[x] = byte, - 0x7000 => self.registers[x] = self.registers[x].wrapping_add(byte), - 0x8000 => match nibble { - 0x0 => self.registers[x] = self.registers[y], - 0x1 => self.registers[x] |= self.registers[y], - 0x2 => self.registers[x] &= self.registers[y], - 0x3 => self.registers[x] ^= self.registers[y], - 0x4 => self.add(x, y), - 0x5 => self.registers[x] = self.sub(x, y), - 0x6 => self.shift_right(x), - 0x7 => self.registers[x] = self.sub(y, x), - 0xe => self.shift_left(x), - _ => panic!("unknown opcode 0x{:04x}", opcode), - }, - 0x9000 => self.skip_if(self.registers[x] != self.registers[y]), - 0xa000 => self.i = addr, - 0xb000 => self.pc = addr + self.registers[0] as u16, - 0xc000 => self.registers[x] = byte & random(), - 0xd000 => self.draw_sprite( - self.registers[x] as usize, - self.registers[y] as usize, - nibble as usize, - ), - 0xe000 => match byte { - 0x9e => self.skip_if(self.keys[self.registers[x] as usize]), - 0xa1 => self.skip_if(!self.keys[self.registers[x] as usize]), - _ => panic!("unknown opcode 0x{:04x}", opcode), - }, - 0xf000 => match byte { - 0x07 => self.registers[x] = self.dt, - 0x0a => self.wait_for_key(x), - 0x15 => self.dt = self.registers[x], - 0x18 => self.st = self.registers[x], - 0x1e => self.i += self.registers[x] as u16, - 0x29 => self.i = self.registers[x] as u16 * 5, - 0x33 => self.store_bcd(x), - 0x55 => self.ram[self.i as usize..=self.i as usize + x] - .clone_from_slice(&self.registers[0..=x]), - 0x65 => { - self.registers[0..=x] - .clone_from_slice(&self.ram[self.i as usize..=self.i as usize + x]); - } - _ => panic!("unknown opcode 0x{:04x}", opcode), - }, - _ => panic!("unknown opcode 0x{:04x}", opcode), - } - } - - #[inline(always)] - fn fetch_opcode(&mut self) -> u16 { - let opcode = - (self.ram[self.pc as usize] as u16) << 8 | self.ram[self.pc as usize + 1] as u16; - self.pc += 2; - opcode - } - - #[inline(always)] - fn add(&mut self, x: usize, y: usize) { - let (sum, overflow) = self.registers[x].overflowing_add(self.registers[y]); - self.registers[0xf] = overflow as u8; - self.registers[x] = sum; - } - - #[inline(always)] - fn sub(&mut self, x: usize, y: usize) -> u8 { - self.registers[0xf] = (self.registers[x] > self.registers[y]) as u8; - self.registers[x].saturating_sub(self.registers[y]) - } - - #[inline(always)] - fn call_subroutine(&mut self, addr: u16) { - self.stack[self.sp as usize] = self.pc; - self.sp += 1; - self.pc = addr; - } - - #[inline(always)] - fn return_subroutine(&mut self) { - self.sp -= 1; - self.pc = self.stack[self.sp as usize]; - } - - #[inline(always)] - fn shift_right(&mut self, x: usize) { - self.registers[0xf] = self.registers[x] & 0x01; - self.registers[x] >>= 1; - } - - #[inline(always)] - fn shift_left(&mut self, x: usize) { - self.registers[0xf] = (self.registers[x] & 0x80) >> 7; - self.registers[x] <<= 1; - } - - #[inline(always)] - fn store_bcd(&mut self, x: usize) { - self.ram[self.i as usize] = self.registers[x] / 100; - self.ram[self.i as usize + 1] = (self.registers[x] / 10) % 10; - self.ram[self.i as usize + 2] = self.registers[x] % 10; - } - - #[inline(always)] - fn skip_if(&mut self, skip: bool) { - self.pc += if skip { 2 } else { 0 }; - } - - #[inline(always)] - fn wait_for_key(&mut self, x: usize) { - if self.keys[self.last_key as usize] { - self.registers[x] = self.last_key; - } else { - self.pc -= 2; - } - } - - #[inline(always)] - fn draw_sprite(&mut self, x0: usize, y0: usize, height: usize) { - self.registers[0xf] = 0; - for y in 0..height { - let sprite_line = self.ram[self.i as usize + y]; - for x in 0..8 { - let xf = (x + x0) % SCREEN_PIXEL_WIDTH; - let yf = (y + y0) % SCREEN_PIXEL_HEIGHT; - let addr = yf * SCREEN_PIXEL_WIDTH + xf; - if (sprite_line & (0x80 >> x)) != 0 { - if self.vram[addr] == 1 { - self.registers[0xf] = 1; - } - self.vram[addr] ^= 1 - } - } - } - } - - fn load_font(&mut self, font_set: &[u8]) { - self.ram[..font_set.len()].clone_from_slice(&font_set); - } -} - -impl Default for Chip8 { - fn default() -> Chip8 { - Chip8::new() - } -} - -impl Display for Chip8 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "PC: 0x{:04x}\nSP: 0x{:04x}", self.pc, self.sp) - } +pub trait Chip8 { + fn name(&self) -> &str; + fn reset(&mut self); + fn reset_hard(&mut self); + fn pixels(&self) -> Vec<u8>; + fn beep(&self) -> bool; + fn pc(&self) -> u16; + fn sp(&self) -> u8; + fn load_rom(&mut self, rom: &[u8]); + fn clock(&mut self); + fn clock_dt(&mut self); + fn clock_st(&mut self); + fn key_press(&mut self, key: u8); + fn key_lift(&mut self, key: u8); } diff --git a/src/chip8_classic.rs b/src/chip8_classic.rs new file mode 100644 index 0000000000000000000000000000000000000000..3cc0f69102206d3193872532aa1b90fabbfe04a0 --- /dev/null +++ b/src/chip8_classic.rs @@ -0,0 +1,332 @@ +use std::fmt::Display; + +use crate::{chip8::Chip8, util::random}; + +#[cfg(feature = "web")] +use wasm_bindgen::prelude::*; + +/// The width of the screen in pixels. +pub const SCREEN_PIXEL_WIDTH: usize = 64; + +/// The height of the screen in pixels. +pub const SCREEN_PIXEL_HEIGHT: usize = 32; + +/// The number of keys to be allocated to the machine. +const NUM_KEYS: usize = 16; + +/// The total number of CPU registers that are going to be used +/// this value should follow the typical convention. +const NUM_REGISTERS: usize = 16; + +/// The size of the stack, this value is actually a convention +/// as no specific definition on its size was ever created. +const STACK_SIZE: usize = 16; + +/// The size of the RAM memory in bytes. +const RAM_SIZE: usize = 4096; + +/// The starting address for the ROM loading, should be +/// the initial PC position. +const ROM_START: usize = 0x200; + +/// Buffer that contains the base CHIP-8 font set that +/// is going to be used to draw the font in the screen +static 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 +]; + +#[cfg_attr(feature = "web", wasm_bindgen)] +pub struct Chip8Classic { + vram: [u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT], + ram: [u8; RAM_SIZE], + registers: [u8; NUM_REGISTERS], + stack: [u16; STACK_SIZE], + i: u16, + dt: u8, + st: u8, + pc: u16, + sp: u8, + beep: bool, + last_key: u8, + keys: [bool; NUM_KEYS], +} + +#[cfg_attr(feature = "web", wasm_bindgen)] +impl Chip8 for Chip8Classic { + fn name(&self) -> &str { + "classic" + } + + fn reset(&mut self) { + self.vram = [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT]; + self.registers = [0u8; NUM_REGISTERS]; + self.stack = [0u16; STACK_SIZE]; + self.i = 0; + self.dt = 0; + self.st = 0; + self.pc = ROM_START as u16; + self.sp = 0; + self.beep = false; + self.last_key = 0x00; + self.keys = [false; NUM_KEYS]; + self.load_font(&FONT_SET); + } + + fn reset_hard(&mut self) { + self.ram = [0u8; RAM_SIZE]; + self.reset(); + } + + fn pixels(&self) -> Vec<u8> { + self.vram.to_vec() + } + + fn beep(&self) -> bool { + self.beep + } + + fn pc(&self) -> u16 { + self.pc + } + + fn sp(&self) -> u8 { + self.sp + } + + fn load_rom(&mut self, rom: &[u8]) { + self.ram[ROM_START..ROM_START + rom.len()].clone_from_slice(rom); + } + + fn clock(&mut self) { + let opcode = self.fetch_opcode(); + self.process_opcode(opcode); + } + + fn clock_dt(&mut self) { + self.dt = self.dt.saturating_sub(1); + } + + fn clock_st(&mut self) { + self.st = self.st.saturating_sub(1); + self.beep = self.st > 0; + } + + fn key_press(&mut self, key: u8) { + if key < NUM_KEYS as u8 { + self.last_key = key; + self.keys[key as usize] = true; + } + } + + fn key_lift(&mut self, key: u8) { + if key < NUM_KEYS as u8 { + self.keys[key as usize] = false; + } + } +} + +impl Chip8Classic { + #[cfg_attr(feature = "web", wasm_bindgen(constructor))] + pub fn new() -> Chip8Classic { + let mut chip8 = Chip8Classic { + vram: [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT], + ram: [0u8; RAM_SIZE], + registers: [0u8; NUM_REGISTERS], + stack: [0u16; STACK_SIZE], + i: 0, + dt: 0, + st: 0, + pc: ROM_START as u16, + sp: 0, + beep: false, + last_key: 0x00, + keys: [false; NUM_KEYS], + }; + chip8.load_font(&FONT_SET); + chip8 + } + + fn process_opcode(&mut self, opcode: u16) { + let id = opcode & 0xf000; + let addr = opcode & 0x0fff; + let nibble = (opcode & 0x000f) as u8; + let x = (opcode >> 8 & 0xf) as usize; + let y = (opcode >> 4 & 0xf) as usize; + let byte = (opcode & 0x00ff) as u8; + + match id { + 0x0000 => match byte { + 0xe0 => self.vram = [0u8; SCREEN_PIXEL_WIDTH * SCREEN_PIXEL_HEIGHT], + 0xee => self.return_subroutine(), + _ => panic!("unknown opcode 0x{:04x}", opcode), + }, + 0x1000 => self.pc = addr, + 0x2000 => self.call_subroutine(addr), + 0x3000 => self.skip_if(self.registers[x] == byte), + 0x4000 => self.skip_if(self.registers[x] != byte), + 0x5000 => self.skip_if(self.registers[x] == self.registers[y]), + 0x6000 => self.registers[x] = byte, + 0x7000 => self.registers[x] = self.registers[x].wrapping_add(byte), + 0x8000 => match nibble { + 0x0 => self.registers[x] = self.registers[y], + 0x1 => self.registers[x] |= self.registers[y], + 0x2 => self.registers[x] &= self.registers[y], + 0x3 => self.registers[x] ^= self.registers[y], + 0x4 => self.add(x, y), + 0x5 => self.registers[x] = self.sub(x, y), + 0x6 => self.shift_right(x), + 0x7 => self.registers[x] = self.sub(y, x), + 0xe => self.shift_left(x), + _ => panic!("unknown opcode 0x{:04x}", opcode), + }, + 0x9000 => self.skip_if(self.registers[x] != self.registers[y]), + 0xa000 => self.i = addr, + 0xb000 => self.pc = addr + self.registers[0] as u16, + 0xc000 => self.registers[x] = byte & random(), + 0xd000 => self.draw_sprite( + self.registers[x] as usize, + self.registers[y] as usize, + nibble as usize, + ), + 0xe000 => match byte { + 0x9e => self.skip_if(self.keys[self.registers[x] as usize]), + 0xa1 => self.skip_if(!self.keys[self.registers[x] as usize]), + _ => panic!("unknown opcode 0x{:04x}", opcode), + }, + 0xf000 => match byte { + 0x07 => self.registers[x] = self.dt, + 0x0a => self.wait_for_key(x), + 0x15 => self.dt = self.registers[x], + 0x18 => self.st = self.registers[x], + 0x1e => self.i += self.registers[x] as u16, + 0x29 => self.i = self.registers[x] as u16 * 5, + 0x33 => self.store_bcd(x), + 0x55 => self.ram[self.i as usize..=self.i as usize + x] + .clone_from_slice(&self.registers[0..=x]), + 0x65 => { + self.registers[0..=x] + .clone_from_slice(&self.ram[self.i as usize..=self.i as usize + x]); + } + _ => panic!("unknown opcode 0x{:04x}", opcode), + }, + _ => panic!("unknown opcode 0x{:04x}", opcode), + } + } + + #[inline(always)] + fn fetch_opcode(&mut self) -> u16 { + let opcode = + (self.ram[self.pc as usize] as u16) << 8 | self.ram[self.pc as usize + 1] as u16; + self.pc += 2; + opcode + } + + #[inline(always)] + fn add(&mut self, x: usize, y: usize) { + let (sum, overflow) = self.registers[x].overflowing_add(self.registers[y]); + self.registers[0xf] = overflow as u8; + self.registers[x] = sum; + } + + #[inline(always)] + fn sub(&mut self, x: usize, y: usize) -> u8 { + self.registers[0xf] = (self.registers[x] > self.registers[y]) as u8; + self.registers[x].saturating_sub(self.registers[y]) + } + + #[inline(always)] + fn call_subroutine(&mut self, addr: u16) { + self.stack[self.sp as usize] = self.pc; + self.sp += 1; + self.pc = addr; + } + + #[inline(always)] + fn return_subroutine(&mut self) { + self.sp -= 1; + self.pc = self.stack[self.sp as usize]; + } + + #[inline(always)] + fn shift_right(&mut self, x: usize) { + self.registers[0xf] = self.registers[x] & 0x01; + self.registers[x] >>= 1; + } + + #[inline(always)] + fn shift_left(&mut self, x: usize) { + self.registers[0xf] = (self.registers[x] & 0x80) >> 7; + self.registers[x] <<= 1; + } + + #[inline(always)] + fn store_bcd(&mut self, x: usize) { + self.ram[self.i as usize] = self.registers[x] / 100; + self.ram[self.i as usize + 1] = (self.registers[x] / 10) % 10; + self.ram[self.i as usize + 2] = self.registers[x] % 10; + } + + #[inline(always)] + fn skip_if(&mut self, skip: bool) { + self.pc += if skip { 2 } else { 0 }; + } + + #[inline(always)] + fn wait_for_key(&mut self, x: usize) { + if self.keys[self.last_key as usize] { + self.registers[x] = self.last_key; + } else { + self.pc -= 2; + } + } + + #[inline(always)] + fn draw_sprite(&mut self, x0: usize, y0: usize, height: usize) { + self.registers[0xf] = 0; + for y in 0..height { + let sprite_line = self.ram[self.i as usize + y]; + for x in 0..8 { + let xf = (x + x0) % SCREEN_PIXEL_WIDTH; + let yf = (y + y0) % SCREEN_PIXEL_HEIGHT; + let addr = yf * SCREEN_PIXEL_WIDTH + xf; + if (sprite_line & (0x80 >> x)) != 0 { + if self.vram[addr] == 1 { + self.registers[0xf] = 1; + } + self.vram[addr] ^= 1 + } + } + } + } + + fn load_font(&mut self, font_set: &[u8]) { + self.ram[..font_set.len()].clone_from_slice(&font_set); + } +} + +impl Default for Chip8Classic { + fn default() -> Chip8Classic { + Chip8Classic::new() + } +} + +impl Display for Chip8Classic { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "PC: 0x{:04x}\nSP: 0x{:04x}", self.pc, self.sp) + } +} diff --git a/src/chip8_neo.rs b/src/chip8_neo.rs index fbcbdc039c123f0707a13d042304ea58186306a0..630d0e5fdf205b514ff03d58ff9522549ac0c41f 100644 --- a/src/chip8_neo.rs +++ b/src/chip8_neo.rs @@ -1,4 +1,4 @@ -use crate::util::random; +use crate::{chip8::Chip8, util::random}; pub const DISPLAY_WIDTH: usize = 64; pub const DISPLAY_HEIGHT: usize = 32; @@ -46,27 +46,12 @@ pub struct Chip8Neo { } #[cfg_attr(feature = "web", wasm_bindgen)] -impl Chip8Neo { - #[cfg_attr(feature = "web", wasm_bindgen(constructor))] - pub fn new() -> Chip8Neo { - let mut chip8 = Chip8Neo { - ram: [0u8; RAM_SIZE], - vram: [0u8; DISPLAY_WIDTH * DISPLAY_HEIGHT], - stack: [0u16; STACK_SIZE], - regs: [0u8; REGISTERS_SIZE], - pc: ROM_START as u16, - i: 0x0, - sp: 0x0, - dt: 0x0, - st: 0x0, - keys: [false; KEYS_SIZE], - last_key: 0x0, - }; - chip8.load_default_font(); - chip8 +impl Chip8 for Chip8Neo { + fn name(&self) -> &str { + "neo" } - pub fn reset(&mut self) { + fn reset(&mut self) { self.vram = [0u8; DISPLAY_WIDTH * DISPLAY_HEIGHT]; self.stack = [0u16; STACK_SIZE]; self.regs = [0u8; REGISTERS_SIZE]; @@ -80,20 +65,16 @@ impl Chip8Neo { self.load_default_font(); } - pub fn reset_hard(&mut self) { + fn reset_hard(&mut self) { self.ram = [0u8; RAM_SIZE]; self.reset(); } - pub fn name(&self) -> &str { - "neo" - } - - pub fn pixels(&self) -> Vec<u8> { + fn pixels(&self) -> Vec<u8> { self.vram.to_vec() } - pub fn clock(&mut self) { + fn clock(&mut self) { // fetches the current instruction and increments // the PC (program counter) accordingly let instruction = @@ -220,15 +201,15 @@ impl Chip8Neo { } } - pub fn clock_dt(&mut self) { + fn clock_dt(&mut self) { self.dt = self.dt.saturating_sub(1) } - pub fn clock_st(&mut self) { + fn clock_st(&mut self) { self.st = self.st.saturating_sub(1) } - pub fn key_press(&mut self, key: u8) { + fn key_press(&mut self, key: u8) { if key >= KEYS_SIZE as u8 { return; } @@ -236,28 +217,49 @@ impl Chip8Neo { self.last_key = key; } - pub fn key_lift(&mut self, key: u8) { + fn key_lift(&mut self, key: u8) { if key >= KEYS_SIZE as u8 { return; } self.keys[key as usize] = false; } - pub fn load_rom(&mut self, rom: &[u8]) { + fn load_rom(&mut self, rom: &[u8]) { self.ram[ROM_START..ROM_START + rom.len()].clone_from_slice(&rom); } - pub fn beep(&self) -> bool { + fn beep(&self) -> bool { self.st > 0 } - pub fn pc(&self) -> u16 { + fn pc(&self) -> u16 { self.pc } - pub fn sp(&self) -> u8 { + fn sp(&self) -> u8 { self.sp } +} + +impl Chip8Neo { + #[cfg_attr(feature = "web", wasm_bindgen(constructor))] + pub fn new() -> Chip8Neo { + let mut chip8 = Chip8Neo { + ram: [0u8; RAM_SIZE], + vram: [0u8; DISPLAY_WIDTH * DISPLAY_HEIGHT], + stack: [0u16; STACK_SIZE], + regs: [0u8; REGISTERS_SIZE], + pc: ROM_START as u16, + i: 0x0, + sp: 0x0, + dt: 0x0, + st: 0x0, + keys: [false; KEYS_SIZE], + last_key: 0x0, + }; + chip8.load_default_font(); + chip8 + } fn load_font(&mut self, position: usize, font_set: &[u8]) { self.ram[position..position + font_set.len()].clone_from_slice(&font_set); diff --git a/src/lib.rs b/src/lib.rs index 906a030a2ea6efafe48d498e6e7fd56ed8e45ebf..8dbb219100e47a047cf519d605fbbd2f2e9b462a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ pub mod chip8; +pub mod chip8_classic; pub mod chip8_neo; pub mod util;