diff --git a/src/drivers/audio_driver.rs b/src/drivers/audio_driver.rs index 225b9bc..9057c23 100644 --- a/src/drivers/audio_driver.rs +++ b/src/drivers/audio_driver.rs @@ -1,62 +1,59 @@ -use sdl2; -use sdl2::audio::{AudioDevice, AudioCallback, AudioSpecDesired}; - -pub struct AudioDriver { - device: AudioDevice, -} - -impl AudioDriver { - pub fn new(sdl_context: &sdl2::Sdl) -> Self { - let audio_subsystem = sdl_context.audio().unwrap(); - - let desired_spec = AudioSpecDesired { - freq: Some(44100), - channels: Some(1), // mono - samples: None, // default sample size - }; - - let device = audio_subsystem - .open_playback(None, &desired_spec, |spec| { - // Show obtained AudioSpec - println!("{:?}", spec); - - // initialize the audio callback - SquareWave { - phase_inc: 240.0 / spec.freq as f32, - phase: 0.0, - volume: 0.25, - } - }) - .unwrap(); - - AudioDriver { device: device } - } - - pub fn start_beep(&self) { - self.device.resume(); - } - pub fn stop_beep(&self) { - self.device.pause(); - } -} - - - - -struct SquareWave { - phase_inc: f32, - phase: f32, - volume: f32, -} - -impl AudioCallback for SquareWave { - type Channel = f32; - - fn callback(&mut self, out: &mut [f32]) { - // Generate a square wave - for x in out.iter_mut() { - *x = self.volume * if self.phase < 0.5 { 1.0 } else { -1.0 }; - self.phase = (self.phase + self.phase_inc) % 1.0; - } - } -} +use sdl2; +use sdl2::audio::{AudioCallback, AudioDevice, AudioSpecDesired}; + +pub struct AudioDriver { + device: AudioDevice, +} + +impl AudioDriver { + pub fn new(sdl_context: &sdl2::Sdl) -> Self { + let audio_subsystem = sdl_context.audio().unwrap(); + + let desired_spec = AudioSpecDesired { + freq: Some(44100), + channels: Some(1), // mono + samples: None, // default sample size + }; + + let device = audio_subsystem + .open_playback(None, &desired_spec, |spec| { + // Show obtained AudioSpec + println!("{:?}", spec); + + // initialize the audio callback + SquareWave { + phase_inc: 240.0 / spec.freq as f32, + phase: 0.0, + volume: 0.25, + } + }) + .unwrap(); + + AudioDriver { device: device } + } + + pub fn start_beep(&self) { + self.device.resume(); + } + pub fn stop_beep(&self) { + self.device.pause(); + } +} + +struct SquareWave { + phase_inc: f32, + phase: f32, + volume: f32, +} + +impl AudioCallback for SquareWave { + type Channel = f32; + + fn callback(&mut self, out: &mut [f32]) { + // Generate a square wave + for x in out.iter_mut() { + *x = self.volume * if self.phase < 0.5 { 1.0 } else { -1.0 }; + self.phase = (self.phase + self.phase_inc) % 1.0; + } + } +} diff --git a/src/drivers/cartridge_driver.rs b/src/drivers/cartridge_driver.rs index 6975ec4..d6db901 100644 --- a/src/drivers/cartridge_driver.rs +++ b/src/drivers/cartridge_driver.rs @@ -1,26 +1,17 @@ -use std::fs::File; -use std::io::prelude::*; - -pub struct CartridgeDriver { - pub rom: [u8; 3584], - pub size: usize, -} - -impl CartridgeDriver { - pub fn new(filename: &str) -> Self { - - let mut f = File::open(filename).expect("file not found"); - let mut buffer = [0u8; 3584]; - - let bytes_read = if let Ok(bytes_read) = f.read(&mut buffer) { - bytes_read - } else { - 0 - }; - - CartridgeDriver { - rom: buffer, - size: bytes_read, - } - } -} +use std::fs::File; +use std::io::prelude::*; + +pub struct CartridgeDriver { + pub rom: [u8; 3584], +} + +impl CartridgeDriver { + pub fn new(filename: &str) -> Self { + let mut f = File::open(filename).expect("file not found"); + let mut buffer = [0u8; 3584]; + + f.read(&mut buffer).expect("file couldn't be read"); + + CartridgeDriver { rom: buffer } + } +} diff --git a/src/drivers/display_driver.rs b/src/drivers/display_driver.rs index cb69fc6..8ba79db 100644 --- a/src/drivers/display_driver.rs +++ b/src/drivers/display_driver.rs @@ -1,110 +1,69 @@ -use sdl2; -use sdl2::pixels; -use sdl2::rect::Rect; -use sdl2::render::Canvas; -use sdl2::video::Window; - -use CHIP8_WIDTH; -use CHIP8_HEIGHT; - -const SCALE_FACTOR: u32 = 20; -const SCREEN_WIDTH: u32 = (CHIP8_WIDTH as u32) * SCALE_FACTOR; -const SCREEN_HEIGHT: u32 = (CHIP8_HEIGHT as u32) * SCALE_FACTOR; - -pub struct DisplayDriver { - canvas: Canvas, -} - -impl DisplayDriver { - pub fn new(sdl_context: &sdl2::Sdl) -> Self { - let video_subsys = sdl_context.video().unwrap(); - let window = video_subsys - .window( - "rust-sdl2_gfx: draw line & FPSManager", - SCREEN_WIDTH, - SCREEN_HEIGHT, - ) - .position_centered() - .opengl() - .build() - .unwrap(); - - let mut canvas = window.into_canvas().build().unwrap(); - - canvas.set_draw_color(pixels::Color::RGB(0, 0, 0)); - canvas.clear(); - canvas.present(); - - DisplayDriver { canvas: canvas } - } - - pub fn draw(&mut self, pixels: &[u64; CHIP8_HEIGHT]) { - - for y in 0usize..CHIP8_HEIGHT { - for x in 0usize..CHIP8_WIDTH { - let _x = (x as u32) * SCALE_FACTOR; - let _y = (y as u32) * SCALE_FACTOR; - - self.canvas.set_draw_color( color( (pixels[y] >> (63 - x) ) & 1 )); - - let _ = self.canvas - .fill_rect( Rect::new( _x as i32, _y as i32, SCALE_FACTOR, SCALE_FACTOR ) ); - } - } - self.canvas.present(); - } -} - -fn color(value: u64) -> pixels::Color { - if value == 0 { - pixels::Color::RGB(0, 0,100) - } else { - pixels::Color::RGB(0, 150, 0) - } -} - -// pub fn run() { - - - -// let mut lastx = 0; -// let mut lasty = 0; - -// let mut events = sdl_context.event_pump().unwrap(); - -// 'main: loop { -// for event in events.poll_iter() { - -// match event { - -// Event::Quit {..} => break 'main, - -// Event::KeyDown {keycode: Some(keycode), ..} => { -// if keycode == Keycode::Escape { -// break 'main -// } else if keycode == Keycode::Space { -// println!("space down"); -// for i in 0..400 { -// driver.canvas.pixel(i as i16, i as i16, 0xFF000FFu32).unwrap(); -// } -// driver.canvas.present(); - -// } -// } - -// Event::MouseButtonDown {x, y, ..} => { -// let color = pixels::Color::RGB(x as u8, y as u8, 255); -// let _ = driver.canvas.line(lastx, lasty, x as i16, y as i16, color); -// let _ = driver.canvas.fill_rect(Rect::new(0,0,100,100)); - -// lastx = x as i16; -// lasty = y as i16; -// println!("mouse btn down at ({},{})", x, y); -// driver.canvas.present(); -// } - -// _ => {} -// } -// } -// } -// } +use sdl2; +use sdl2::pixels; +use sdl2::rect::Rect; +use sdl2::render::Canvas; +use sdl2::video::Window; + +use CHIP8_HEIGHT; +use CHIP8_WIDTH; + +const SCALE_FACTOR: u32 = 20; +const SCREEN_WIDTH: u32 = (CHIP8_WIDTH as u32) * SCALE_FACTOR; +const SCREEN_HEIGHT: u32 = (CHIP8_HEIGHT as u32) * SCALE_FACTOR; + +pub struct DisplayDriver { + canvas: Canvas, +} + +impl DisplayDriver { + pub fn new(sdl_context: &sdl2::Sdl) -> Self { + let video_subsys = sdl_context.video().unwrap(); + let window = video_subsys + .window( + "rust-sdl2_gfx: draw line & FPSManager", + SCREEN_WIDTH, + SCREEN_HEIGHT, + ) + .position_centered() + .opengl() + .build() + .unwrap(); + + let mut canvas = window.into_canvas().build().unwrap(); + + canvas.set_draw_color(pixels::Color::RGB(0, 0, 0)); + canvas.clear(); + canvas.present(); + + DisplayDriver { canvas: canvas } + } + + pub fn draw(&mut self, pixels: &[u64; CHIP8_HEIGHT]) { + pixels.iter().enumerate().for_each(|(y_index, y)| { + (0usize..CHIP8_WIDTH).for_each(|x_index| { + let _x_start = (x_index as u32) * SCALE_FACTOR; + let _y_start = (y_index as u32) * SCALE_FACTOR; + + self.canvas + .set_draw_color(color((*y >> (63 - x_index)) & 1)); + + let _ = self.canvas.fill_rect(Rect::new( + _x_start as i32, + _y_start as i32, + SCALE_FACTOR, + SCALE_FACTOR, + )); + }); + }); + + self.canvas.present(); + } +} + +fn color(value: u64) -> pixels::Color { + if value == 0 { + pixels::Color::RGB(0, 0, 100) + } else { + pixels::Color::RGB(0, 150, 0) + } +} diff --git a/src/drivers/input_driver.rs b/src/drivers/input_driver.rs index 9d220e0..11e24e3 100644 --- a/src/drivers/input_driver.rs +++ b/src/drivers/input_driver.rs @@ -1,64 +1,65 @@ -use sdl2; -use sdl2::event::Event; -use sdl2::keyboard::Keycode; - -pub struct InputDriver { - events: sdl2::EventPump, -} - -impl InputDriver { - pub fn new(sdl_context: &sdl2::Sdl) -> Self { - InputDriver { events: sdl_context.event_pump().unwrap() } - } - - - pub fn poll(&mut self) -> Result { - - for event in self.events.poll_iter() { - if let Event::Quit { .. } = event { - return Err(()); - }; - } - - let keys: Vec = self.events - .keyboard_state() - .pressed_scancodes() - .filter_map(Keycode::from_scancode) - .collect(); - - let mut chip8_keys: u16 = 0; - /* - fedc ba98 7654 3210 - 8421 8421 8421 8421 - 0b0000 0000 0000 0000 - - */ - for key in keys { - let index = match key { - Keycode::Num1 => Some(0x0002), - Keycode::Num2 => Some(0x0004), - Keycode::Num3 => Some(0x0008), - Keycode::Num4 => Some(0x1000), - Keycode::Q => Some(0x0010), - Keycode::W => Some(0x0020), - Keycode::E => Some(0x0040), - Keycode::R => Some(0x2000), - Keycode::A => Some(0x0080), - Keycode::S => Some(0x0100), - Keycode::D => Some(0x0200), - Keycode::F => Some(0x4000), - Keycode::Z => Some(0x0400), - Keycode::X => Some(0x0001), - Keycode::C => Some(0x0800), - Keycode::V => Some(0x8000), - _ => None, - }; - - if let Some(i) = index { - chip8_keys |= i; - } - } - - Ok(chip8_keys) - } -} +use sdl2; +use sdl2::event::Event; +use sdl2::keyboard::Keycode; + +pub struct InputDriver { + events: sdl2::EventPump, +} + +impl InputDriver { + pub fn new(sdl_context: &sdl2::Sdl) -> Self { + InputDriver { + events: sdl_context.event_pump().unwrap(), + } + } + + pub fn poll(&mut self) -> Result { + for event in self.events.poll_iter() { + if let Event::Quit { .. } = event { + return Err(()); + }; + } + + let keys: Vec = self + .events + .keyboard_state() + .pressed_scancodes() + .filter_map(Keycode::from_scancode) + .collect(); + + let mut chip8_keys: u16 = 0; + /* + fedc ba98 7654 3210 + 8421 8421 8421 8421 + 0b0000 0000 0000 0000 + + */ + for key in keys { + let index = match key { + Keycode::Num1 => Some(0x0002), + Keycode::Num2 => Some(0x0004), + Keycode::Num3 => Some(0x0008), + Keycode::Num4 => Some(0x1000), + Keycode::Q => Some(0x0010), + Keycode::W => Some(0x0020), + Keycode::E => Some(0x0040), + Keycode::R => Some(0x2000), + Keycode::A => Some(0x0080), + Keycode::S => Some(0x0100), + Keycode::D => Some(0x0200), + Keycode::F => Some(0x4000), + Keycode::Z => Some(0x0400), + Keycode::X => Some(0x0001), + Keycode::C => Some(0x0800), + Keycode::V => Some(0x8000), + _ => None, + }; + + if let Some(i) = index { + chip8_keys |= i; + } + } + + Ok(chip8_keys) + } +} diff --git a/src/drivers/mod.rs b/src/drivers/mod.rs index 40a7822..d26dc03 100644 --- a/src/drivers/mod.rs +++ b/src/drivers/mod.rs @@ -1,9 +1,9 @@ -mod display_driver; -mod audio_driver; -mod input_driver; -mod cartridge_driver; - -pub use self::display_driver::DisplayDriver; -pub use self::audio_driver::AudioDriver; -pub use self::input_driver::InputDriver; -pub use self::cartridge_driver::CartridgeDriver; +mod audio_driver; +mod cartridge_driver; +mod display_driver; +mod input_driver; + +pub use self::audio_driver::AudioDriver; +pub use self::cartridge_driver::CartridgeDriver; +pub use self::display_driver::DisplayDriver; +pub use self::input_driver::InputDriver; diff --git a/src/font.rs b/src/font.rs index b0b9ec2..b2e708a 100644 --- a/src/font.rs +++ b/src/font.rs @@ -1,82 +1,7 @@ -pub const FONT_SET: [u8; 80] = [ - 0xF0, - 0x90, - 0x90, - 0x90, - 0xF0, - 0x20, - 0x60, - 0x20, - 0x20, - 0x70, - 0xF0, - 0x10, - 0xF0, - 0x80, - 0xF0, - 0xF0, - 0x10, - 0xF0, - 0x10, - 0xF0, - 0x90, - 0x90, - 0xF0, - 0x10, - 0x10, - 0xF0, - 0x80, - 0xF0, - 0x10, - 0xF0, - 0xF0, - 0x80, - 0xF0, - 0x90, - 0xF0, - 0xF0, - 0x10, - 0x20, - 0x40, - 0x40, - 0xF0, - 0x90, - 0xF0, - 0x90, - 0xF0, - 0xF0, - 0x90, - 0xF0, - 0x10, - 0xF0, - 0xF0, - 0x90, - 0xF0, - 0x90, - 0x90, - 0xE0, - 0x90, - 0xE0, - 0x90, - 0xE0, - 0xF0, - 0x80, - 0x80, - 0x80, - 0xF0, - 0xE0, - 0x90, - 0x90, - 0x90, - 0xE0, - 0xF0, - 0x80, - 0xF0, - 0x80, - 0xF0, - 0xF0, - 0x80, - 0xF0, - 0x80, - 0x80, -]; +pub const FONT_SET: [u8; 80] = [ + 0xF0, 0x90, 0x90, 0x90, 0xF0, 0x20, 0x60, 0x20, 0x20, 0x70, 0xF0, 0x10, 0xF0, 0x80, 0xF0, 0xF0, + 0x10, 0xF0, 0x10, 0xF0, 0x90, 0x90, 0xF0, 0x10, 0x10, 0xF0, 0x80, 0xF0, 0x10, 0xF0, 0xF0, 0x80, + 0xF0, 0x90, 0xF0, 0xF0, 0x10, 0x20, 0x40, 0x40, 0xF0, 0x90, 0xF0, 0x90, 0xF0, 0xF0, 0x90, 0xF0, + 0x10, 0xF0, 0xF0, 0x90, 0xF0, 0x90, 0x90, 0xE0, 0x90, 0xE0, 0x90, 0xE0, 0xF0, 0x80, 0x80, 0x80, + 0xF0, 0xE0, 0x90, 0x90, 0x90, 0xE0, 0xF0, 0x80, 0xF0, 0x80, 0xF0, 0xF0, 0x80, 0xF0, 0x80, 0x80, +]; diff --git a/src/main.rs b/src/main.rs index 708d308..c77d1d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,66 +1,63 @@ -extern crate rand; -extern crate sdl2; -mod drivers; -mod processor; -mod font; - -use std::thread; -use std::time::{Duration}; -use std::env; - -use drivers::{DisplayDriver, AudioDriver, InputDriver, CartridgeDriver}; -use processor::Processor; - -const CHIP8_WIDTH: usize = 64; -const CHIP8_HEIGHT: usize = 32; -const CHIP8_RAM: usize = 4096; - - - -fn main() { - let sleep_duration = Duration::from_millis(16); - //let sleep_duration = Duration::from_micros(16670); //60hz - let sdl_context = sdl2::init().unwrap(); - - let args: Vec = env::args().collect(); - let cartridge_filename = &args[1]; - - let cartridge_driver = CartridgeDriver::new(cartridge_filename); - let audio_driver = AudioDriver::new(&sdl_context); - let mut display_driver = DisplayDriver::new(&sdl_context); - let mut input_driver = InputDriver::new(&sdl_context); - let mut processor = Processor::new(); - - processor.load(&cartridge_driver.rom); - let mut opcode_count = 0; - - - - while let Ok(keypad) = input_driver.poll() { - //duct tape of the century - let output = processor.tick(keypad); - opcode_count+=1; - - if output.beep { - audio_driver.start_beep(); - } - else { - audio_driver.stop_beep(); - } - - if output.vram_changed { - display_driver.draw(&output.vram); - processor.vram_changed = false; - } - - - //buffer of opcodes per 60hz, set it to where it feels right, around 10-15 - if opcode_count >=15 { - opcode_count =0; - if processor.sound_timer > 0 {processor.sound_timer -=1} - if processor.delay_timer > 0 {processor.delay_timer -=1} - thread::sleep(sleep_duration); - } - } - -} +extern crate rand; +extern crate sdl2; +mod drivers; +mod font; +mod processor; + +use std::env; +use std::thread; +use std::time::Duration; + +use drivers::{AudioDriver, CartridgeDriver, DisplayDriver, InputDriver}; +use processor::Processor; + +const CHIP8_WIDTH: usize = 64; +const CHIP8_HEIGHT: usize = 32; +const CHIP8_RAM: usize = 4096; + +fn main() { + let sleep_duration = Duration::from_millis(16); + //let sleep_duration = Duration::from_micros(16670); //60hz + let sdl_context = sdl2::init().unwrap(); + + let args: Vec = env::args().collect(); + let cartridge_filename = &args[1]; + + let cartridge_driver = CartridgeDriver::new(cartridge_filename); + let audio_driver = AudioDriver::new(&sdl_context); + let mut display_driver = DisplayDriver::new(&sdl_context); + let mut input_driver = InputDriver::new(&sdl_context); + let mut processor = Processor::new(); + + processor.load(&cartridge_driver.rom); + let mut opcode_count = 0; + + while let Ok(keypad) = input_driver.poll() { + //duct tape of the century + let output = processor.tick(keypad); + opcode_count += 1; + + if output.beep { + audio_driver.start_beep(); + } else { + audio_driver.stop_beep(); + } + + if output.vram_changed { + display_driver.draw(&output.vram); + processor.vram_changed = false; + } + + //buffer of opcodes per 60hz, set it to where it feels right, around 10-15 + if opcode_count >= 15 { + opcode_count = 0; + if processor.sound_timer > 0 { + processor.sound_timer -= 1 + } + if processor.delay_timer > 0 { + processor.delay_timer -= 1 + } + thread::sleep(sleep_duration); + } + } +} diff --git a/src/processor.rs b/src/processor.rs index 2c6239f..30d43a9 100644 --- a/src/processor.rs +++ b/src/processor.rs @@ -1,477 +1,470 @@ -use rand; -use rand::Rng; -use font::FONT_SET; -/* - the simplest, most straightforward way to implement display wait is, when a DXYN occurs, check if the instruction is the first one that occurred on a frame. If it is, allow DXYN to run normally. If not, break the loop so that it will restart at the next frame instead (doesn't increment PC) -currently there isnt any -*/ -use CHIP8_HEIGHT; -use CHIP8_WIDTH; -use CHIP8_RAM; - -const OPCODE_SIZE: usize = 2; - -pub struct OutputState<'a> { - pub vram: &'a [u64; CHIP8_HEIGHT], - pub vram_changed: bool, - pub beep: bool, -} - -enum ProgramCounter { - Unknown(u16), - //Stay, - Next, - Skip, - Jump(usize), -} - -impl ProgramCounter { - fn skip_if(condition: bool) -> ProgramCounter { - if condition { - ProgramCounter::Skip - } else { - ProgramCounter::Next - } - } -} - -pub struct Processor { - vram: [u64; CHIP8_HEIGHT], - pub vram_changed: bool, - ram: [u8; CHIP8_RAM], - stack: [usize; 16], - v: [u8; 16], - i: usize, - pc: usize, - sp: usize, - pub delay_timer: u8, - pub sound_timer: u8, - keypad: u16, - keypad_wait: bool, - keypad_wait_register: usize, -} - -impl Processor { - pub fn new() -> Self { - - let mut ram = [0u8; CHIP8_RAM]; - for i in 0..FONT_SET.len() { - ram[i] = FONT_SET[i]; - } - - Processor { - vram: [0; CHIP8_HEIGHT], - vram_changed: false, - ram: ram, - stack: [0; 16], - v: [0; 16], - i: 0, - pc: 0x200, - sp: 0, - delay_timer: 0, - sound_timer: 0, - keypad: 0, - keypad_wait: false, - keypad_wait_register: 0, - } - } - - pub fn load(&mut self, data: &[u8]) { - for (i, &byte) in data.iter().enumerate() { - let addr = 0x200 + i; - if addr < 4096 { - self.ram[0x200 + i] = byte; - } else { - break; - } - } - } - - pub fn tick(&mut self, keypad: u16) -> OutputState { - //self.vram_changed = false; - self.keypad = keypad; - - if self.keypad_wait { - if self.keypad > 0{ - /* - fedc ba98 7654 3210 - 0b0000 0000 0000 0000 - */ - self.keypad_wait = false; - self.v[self.keypad_wait_register] = self.keypad.trailing_zeros() as u8; - } - } - else { - self.run_opcode(self.get_opcode()); - } - - OutputState { - vram: &self.vram, - vram_changed: self.vram_changed, - beep: self.sound_timer > 0, - } - } - - fn get_opcode(&self) -> u16 { - (self.ram[self.pc] as u16) << 8 | (self.ram[self.pc + 1] as u16) - } - - fn run_opcode(&mut self, opcode: u16) { - let nibbles = ( - (opcode & 0xF000) >> 12 as u8, - (opcode & 0x0F00) >> 8 as u8, - (opcode & 0x00F0) >> 4 as u8, - (opcode & 0x000F) as u8, - ); - let nnn = (opcode & 0x0FFF) as usize; - let kk = (opcode & 0x00FF) as u8; - let x = nibbles.1 as usize; - let y = nibbles.2 as usize; - let n = nibbles.3 as usize; - - let pc_change = match nibbles { - (0x00, 0x00, 0x0e, 0x00) => self.op_00e0(), - (0x00, 0x00, 0x0e, 0x0e) => self.op_00ee(), - (0x01, _, _, _) => self.op_1nnn(nnn), - (0x02, _, _, _) => self.op_2nnn(nnn), - (0x03, _, _, _) => self.op_3xkk(x, kk), - (0x04, _, _, _) => self.op_4xkk(x, kk), - (0x05, _, _, 0x00) => self.op_5xy0(x, y), - (0x06, _, _, _) => self.op_6xkk(x, kk), - (0x07, _, _, _) => self.op_7xkk(x, kk), - (0x08, _, _, 0x00) => self.op_8xy0(x, y), - (0x08, _, _, 0x01) => self.op_8xy1(x, y), - (0x08, _, _, 0x02) => self.op_8xy2(x, y), - (0x08, _, _, 0x03) => self.op_8xy3(x, y), - (0x08, _, _, 0x04) => self.op_8xy4(x, y), - (0x08, _, _, 0x05) => self.op_8xy5(x, y), - (0x08, _, _, 0x06) => self.op_8x06(x), - (0x08, _, _, 0x07) => self.op_8xy7(x, y), - (0x08, _, _, 0x0e) => self.op_8x0e(x), - (0x09, _, _, 0x00) => self.op_9xy0(x, y), - (0x0a, _, _, _) => self.op_annn(nnn), - (0x0b, _, _, _) => self.op_bnnn(nnn), - (0x0c, _, _, _) => self.op_cxkk(x, kk), - (0x0d, _, _, _) => self.op_dxyn(x, y, n), - (0x0e, _, 0x09, 0x0e) => self.op_ex9e(x), - (0x0e, _, 0x0a, 0x01) => self.op_exa1(x), - (0x0f, _, 0x00, 0x07) => self.op_fx07(x), - (0x0f, _, 0x00, 0x0a) => self.op_fx0a(x), - (0x0f, _, 0x01, 0x05) => self.op_fx15(x), - (0x0f, _, 0x01, 0x08) => self.op_fx18(x), - (0x0f, _, 0x01, 0x0e) => self.op_fx1e(x), - (0x0f, _, 0x02, 0x09) => self.op_fx29(x), - (0x0f, _, 0x03, 0x03) => self.op_fx33(x), - (0x0f, _, 0x05, 0x05) => self.op_fx55(x), - (0x0f, _, 0x06, 0x05) => self.op_fx65(x), - _ => ProgramCounter::Unknown(opcode), - }; - - match pc_change { - ProgramCounter::Unknown(opcode) => { - println!("ERROR: OPCODE {:#06x} UNKNOWN",opcode); - self.pc += OPCODE_SIZE; - }, - //ProgramCounter::Stay => (), - ProgramCounter::Next => self.pc += OPCODE_SIZE, - ProgramCounter::Skip => self.pc += 2 * OPCODE_SIZE, - ProgramCounter::Jump(addr) => self.pc = addr, - } - - - } - - - // CLS: Clear the display. - fn op_00e0(&mut self) -> ProgramCounter { - self.vram = [0; CHIP8_HEIGHT]; - self.vram_changed = true; - ProgramCounter::Next - - } - // RET: Return from a subroutine. - // The interpreter sets the program counter to the address at the - // top of the stack, then subtracts 1 from the stack pointer. - fn op_00ee(&mut self) -> ProgramCounter { - self.sp -= 1; - ProgramCounter::Jump(self.stack[self.sp]) - } - // JP addr - // The interpreter sets the program counter to nnn. - fn op_1nnn(&mut self, nnn: usize) -> ProgramCounter { - ProgramCounter::Jump(nnn) - } - // CALL addr - // The interpreter increments the stack pointer, then puts the - // current PC on the top of the stack. The PC is then set to nnn. - fn op_2nnn(&mut self, nnn: usize) -> ProgramCounter { - self.stack[self.sp] = self.pc + OPCODE_SIZE; - self.sp += 1; - ProgramCounter::Jump(nnn) - } - // SE Vx, byte: - // Skip next instruction if Vx = kk. - fn op_3xkk(&mut self, x: usize, kk: u8) -> ProgramCounter { - ProgramCounter::skip_if(self.v[x] == kk) - } - // SNE Vx, byte. - // Skip next instruction if Vx != kk. - fn op_4xkk(&mut self, x: usize, kk: u8) -> ProgramCounter { - ProgramCounter::skip_if(self.v[x] != kk) - } - // SE Vx, Vy - // Skip next instruction if Vx = Vy. - fn op_5xy0(&mut self, x: usize, y: usize) -> ProgramCounter { - ProgramCounter::skip_if(self.v[x] == self.v[y]) - } - // LD Vx, byte - // Set Vx = kk. - fn op_6xkk(&mut self, x: usize, kk: u8) -> ProgramCounter { - self.v[x] = kk; - ProgramCounter::Next - } - // ADD Vx, byte - // Set Vx = Vx + kk. - fn op_7xkk(&mut self, x: usize, kk: u8) -> ProgramCounter { - let vx = self.v[x] as u16; - let val = kk as u16; - let result = vx + val; - self.v[x] = result as u8; - ProgramCounter::Next - } - // LD Vx, Vy - // Set Vx = Vy. - fn op_8xy0(&mut self, x: usize, y: usize) -> ProgramCounter { - self.v[x] = self.v[y]; - ProgramCounter::Next - } - // OR Vx, Vy - // Set Vx = Vx OR Vy. - fn op_8xy1(&mut self, x: usize, y: usize) -> ProgramCounter { - self.v[x] |= self.v[y]; - ProgramCounter::Next - } - // AND Vx, Vy - // Set Vx = Vx AND Vy. - fn op_8xy2(&mut self, x: usize, y: usize) -> ProgramCounter { - self.v[x] &= self.v[y]; - ProgramCounter::Next - } - // XOR Vx, Vy - // Set Vx = Vx XOR Vy. - fn op_8xy3(&mut self, x: usize, y: usize) -> ProgramCounter { - self.v[x] ^= self.v[y]; - ProgramCounter::Next - } - // ADD Vx, Vy - // The values of Vx and Vy are added together. If the result is - // greater than 8 bits (i.e., > 255,) VF is set to 1, otherwise 0. - // Only the lowest 8 bits of the result are kept, and stored in Vx. - fn op_8xy4(&mut self, x: usize, y: usize) -> ProgramCounter { - let vx = self.v[x] as u16; - let vy = self.v[y] as u16; - let result = vx + vy; - self.v[x] = result as u8; - self.v[0x0f] = if result > 0xFF { 1 } else { 0 }; - ProgramCounter::Next - } - // SUB Vx, Vy - // If Vx > Vy, then VF is set to 1, otherwise 0. Then Vy is subtracted from Vx, and the results stored in Vx. - fn op_8xy5(&mut self, x: usize, y: usize) -> ProgramCounter { - let temp = if self.v[x] >= self.v[y] { 1 } else { 0 }; - self.v[x] = self.v[x].wrapping_sub(self.v[y]); - self.v[0xf] = temp; - ProgramCounter::Next - } - // SHR Vx {, Vy} - // If the least-significant bit of Vx is 1, then VF is set to 1, - // otherwise 0. Then Vx is divided by 2. - fn op_8x06(&mut self, x: usize) -> ProgramCounter { - let temp = self.v[x] & 1; - self.v[x] >>= 1; - self.v[0xf] = temp; - ProgramCounter::Next - } - // SUBN Vx, Vy - // If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is subtracted - // from Vy, and the results stored in Vx. - fn op_8xy7(&mut self, x: usize, y: usize) -> ProgramCounter { - let temp = if self.v[y] >= self.v[x] { 1 } else { 0 }; - self.v[x] = self.v[y].wrapping_sub(self.v[x]); - self.v[0xf] = temp; - ProgramCounter::Next - } - // SHL Vx {, Vy} - // If the most-significant bit of Vx is 1, then VF is set to 1, - // otherwise to 0. Then Vx is multiplied by 2. - fn op_8x0e(&mut self, x: usize) -> ProgramCounter { - let temp = self.v[x] >> 7; - self.v[x] <<= 1; - self.v[0xf] = temp; - ProgramCounter::Next - } - // SNE Vx, Vy - // Skip next instruction if Vx != Vy. - fn op_9xy0(&mut self, x: usize, y: usize) -> ProgramCounter { - ProgramCounter::skip_if(self.v[x] != self.v[y]) - } - // LD I, addr - // Set I = nnn. - fn op_annn(&mut self, nnn: usize) -> ProgramCounter { - self.i = nnn; - ProgramCounter::Next - } - // JP V0, addr - // The program counter is set to nnn plus the value of V0. - fn op_bnnn(&mut self, nnn: usize) -> ProgramCounter { - ProgramCounter::Jump((self.v[0] as usize) + nnn) - } - // RND Vx, byte - // The interpreter generates a random number from 0 to 255, - // which is then ANDed with the value kk. The results are stored in Vx. - fn op_cxkk(&mut self, x: usize, kk: u8) -> ProgramCounter { - let mut rng = rand::thread_rng(); - self.v[x] = rng.gen::() & kk; - ProgramCounter::Next - } - // DRW Vx, Vy, n - // The interpreter reads n bytes from memory, starting at the address - // stored in I. These bytes are then displayed as sprites on screen at - // coordinates (Vx, Vy). Sprites are XORed onto the existing screen. - // If this causes any pixels to be erased, VF is set to 1, otherwise - // it is set to 0. If the sprite is positioned so part of it is outside - // the coordinates of the display, it wraps around to the opposite side - // of the screen. - - /* - - 0b1_0110_010 - 0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000 - - */ - /* - 0x1110_1010_0000 - 0x0011_0101_0010 AND - 0x0010_0000_0000 - */ - - /* - 0x0000_1100_0101 - 0x0000_ - */ - - fn op_dxyn(&mut self, x: usize, y: usize, n: usize) -> ProgramCounter { - self.v[0x0f] = 0; - for byte in 0..n { - let y = (self.v[y] as usize + byte) % CHIP8_HEIGHT; - let x = self.v[x] as usize % CHIP8_WIDTH; - - let mut mask = self.ram[self.i + byte] as u64; - - if x + 8 > CHIP8_WIDTH { - let tmp = mask >> (x - 56); - mask <<= 56 + ((x + 8) % CHIP8_WIDTH as usize); - mask |= tmp; - } - else { - mask = mask << (56 - x); - } - self.v[0xf] |= if self.vram[y] & mask > 0 { 1 } else { 0 }; - self.vram[y] = self.vram[y] ^ mask; - } - self.vram_changed = true; - ProgramCounter::Next - } - // SKP Vx - // SKP Vx - // Skip next instruction if key with the value of Vx is pressed. - fn op_ex9e(&mut self, x: usize) -> ProgramCounter { - ProgramCounter::skip_if((self.keypad >> self.v[x]) & 0x1 == 1) - } - // SKNP Vx - // Skip next instruction if key with the value of Vx is NOT pressed. - fn op_exa1(&mut self, x: usize) -> ProgramCounter { - ProgramCounter::skip_if((self.keypad >> self.v[x]) & 0x1 == 0) - } - // LD Vx, sDT - // Set Vx = delay timer value. - fn op_fx07(&mut self, x: usize) -> ProgramCounter { - self.v[x] = self.delay_timer; - ProgramCounter::Next - } - // LD Vx, K - // Wait for a key press, store the value of the key in Vx. - - /* - 8421 8421 - 0b0011_0101 - 0b1100_1010 - 0b0110_0101 - */ - - fn op_fx0a(&mut self, x: usize) -> ProgramCounter { - self.keypad_wait = true; - self.keypad_wait_register = x; - ProgramCounter::Next - } - // LD DT, Vx - // Set delay timer = Vx. - fn op_fx15(&mut self, x: usize) -> ProgramCounter { - self.delay_timer = self.v[x]; - ProgramCounter::Next - } - // LD ST, Vx - // Set sound timer = Vx. - fn op_fx18(&mut self, x: usize) -> ProgramCounter { - self.sound_timer = self.v[x]; - ProgramCounter::Next - } - // ADD I, Vx - // Set I = I + Vx - fn op_fx1e(&mut self, x: usize) -> ProgramCounter { - self.i += self.v[x] as usize; - self.v[0x0f] = if self.i > 0x0F00 { 1 } else { 0 }; - ProgramCounter::Next - } - // LD F, Vx - // Set I = location of sprite for digit Vx. - fn op_fx29(&mut self, x: usize) -> ProgramCounter { - self.i = (self.v[x] as usize) * 5; - ProgramCounter::Next - } - - // LD B, Vx - // The interpreter takes the decimal value of Vx, and places - // the hundreds digit in memory at location in I, the tens digit - // at location I+1, and the ones digit at location I+2. - fn op_fx33(&mut self, x: usize) -> ProgramCounter { - self.ram[self.i] = self.v[x] / 100; - self.ram[self.i + 1] = (self.v[x] % 100) / 10; - self.ram[self.i + 2] = self.v[x] % 10; - ProgramCounter::Next - } - - // LD [I], Vx - // The interpreter copies the values of registers V0 through Vx - // into memory, starting at the address in I. - fn op_fx55(&mut self, x: usize) -> ProgramCounter { - for i in 0..x + 1 { - self.ram[self.i + i] = self.v[i]; - } - ProgramCounter::Next - } - - // LD Vx, [I] - // The interpreter reads values from memory starting at location - // I into registers V0 through Vx. - fn op_fx65(&mut self, x: usize) -> ProgramCounter { - for i in 0..x + 1 { - self.v[i] = self.ram[self.i + i]; - } - ProgramCounter::Next - } -} - -#[cfg(test)] -#[path = "./processor_test.rs"] -mod processor_test; +use font::FONT_SET; +use rand; +use rand::Rng; +/* + the simplest, most straightforward way to implement display wait is, when a DXYN occurs, check if the instruction is the first one that occurred on a frame. If it is, allow DXYN to run normally. If not, break the loop so that it will restart at the next frame instead (doesn't increment PC) +currently there isnt any +*/ +use CHIP8_HEIGHT; +use CHIP8_RAM; +use CHIP8_WIDTH; + +const OPCODE_SIZE: usize = 2; + +pub struct OutputState<'a> { + pub vram: &'a [u64; CHIP8_HEIGHT], + pub vram_changed: bool, + pub beep: bool, +} + +enum ProgramCounter { + Unknown(u16), + //Stay, + Next, + Skip, + Jump(usize), +} + +impl ProgramCounter { + fn skip_if(condition: bool) -> ProgramCounter { + if condition { + ProgramCounter::Skip + } else { + ProgramCounter::Next + } + } +} + +pub struct Processor { + vram: [u64; CHIP8_HEIGHT], + pub vram_changed: bool, + ram: [u8; CHIP8_RAM], + stack: [usize; 16], + v: [u8; 16], + i: usize, + pc: usize, + sp: usize, + pub delay_timer: u8, + pub sound_timer: u8, + keypad: u16, + keypad_wait: bool, + keypad_wait_register: usize, +} + +impl Processor { + pub fn new() -> Self { + let mut ram = [0u8; CHIP8_RAM]; + for i in 0..FONT_SET.len() { + ram[i] = FONT_SET[i]; + } + + Processor { + vram: [0; CHIP8_HEIGHT], + vram_changed: false, + ram: ram, + stack: [0; 16], + v: [0; 16], + i: 0, + pc: 0x200, + sp: 0, + delay_timer: 0, + sound_timer: 0, + keypad: 0, + keypad_wait: false, + keypad_wait_register: 0, + } + } + + pub fn load(&mut self, data: &[u8]) { + for (i, &byte) in data.iter().enumerate() { + let addr = 0x200 + i; + if addr < 4096 { + self.ram[0x200 + i] = byte; + } else { + break; + } + } + } + + pub fn tick(&mut self, keypad: u16) -> OutputState { + //self.vram_changed = false; + self.keypad = keypad; + + if self.keypad_wait { + if self.keypad > 0 { + /* + fedc ba98 7654 3210 + 0b0000 0000 0000 0000 + */ + self.keypad_wait = false; + self.v[self.keypad_wait_register] = self.keypad.trailing_zeros() as u8; + } + } else { + self.run_opcode(self.get_opcode()); + } + + OutputState { + vram: &self.vram, + vram_changed: self.vram_changed, + beep: self.sound_timer > 0, + } + } + + fn get_opcode(&self) -> u16 { + (self.ram[self.pc] as u16) << 8 | (self.ram[self.pc + 1] as u16) + } + + fn run_opcode(&mut self, opcode: u16) { + let nibbles = ( + (opcode & 0xF000) >> 12 as u8, + (opcode & 0x0F00) >> 8 as u8, + (opcode & 0x00F0) >> 4 as u8, + (opcode & 0x000F) as u8, + ); + let nnn = (opcode & 0x0FFF) as usize; + let kk = (opcode & 0x00FF) as u8; + let x = nibbles.1 as usize; + let y = nibbles.2 as usize; + let n = nibbles.3 as usize; + + let pc_change = match nibbles { + (0x00, 0x00, 0x0e, 0x00) => self.op_00e0(), + (0x00, 0x00, 0x0e, 0x0e) => self.op_00ee(), + (0x01, _, _, _) => self.op_1nnn(nnn), + (0x02, _, _, _) => self.op_2nnn(nnn), + (0x03, _, _, _) => self.op_3xkk(x, kk), + (0x04, _, _, _) => self.op_4xkk(x, kk), + (0x05, _, _, 0x00) => self.op_5xy0(x, y), + (0x06, _, _, _) => self.op_6xkk(x, kk), + (0x07, _, _, _) => self.op_7xkk(x, kk), + (0x08, _, _, 0x00) => self.op_8xy0(x, y), + (0x08, _, _, 0x01) => self.op_8xy1(x, y), + (0x08, _, _, 0x02) => self.op_8xy2(x, y), + (0x08, _, _, 0x03) => self.op_8xy3(x, y), + (0x08, _, _, 0x04) => self.op_8xy4(x, y), + (0x08, _, _, 0x05) => self.op_8xy5(x, y), + (0x08, _, _, 0x06) => self.op_8x06(x), + (0x08, _, _, 0x07) => self.op_8xy7(x, y), + (0x08, _, _, 0x0e) => self.op_8x0e(x), + (0x09, _, _, 0x00) => self.op_9xy0(x, y), + (0x0a, _, _, _) => self.op_annn(nnn), + (0x0b, _, _, _) => self.op_bnnn(nnn), + (0x0c, _, _, _) => self.op_cxkk(x, kk), + (0x0d, _, _, _) => self.op_dxyn(x, y, n), + (0x0e, _, 0x09, 0x0e) => self.op_ex9e(x), + (0x0e, _, 0x0a, 0x01) => self.op_exa1(x), + (0x0f, _, 0x00, 0x07) => self.op_fx07(x), + (0x0f, _, 0x00, 0x0a) => self.op_fx0a(x), + (0x0f, _, 0x01, 0x05) => self.op_fx15(x), + (0x0f, _, 0x01, 0x08) => self.op_fx18(x), + (0x0f, _, 0x01, 0x0e) => self.op_fx1e(x), + (0x0f, _, 0x02, 0x09) => self.op_fx29(x), + (0x0f, _, 0x03, 0x03) => self.op_fx33(x), + (0x0f, _, 0x05, 0x05) => self.op_fx55(x), + (0x0f, _, 0x06, 0x05) => self.op_fx65(x), + _ => ProgramCounter::Unknown(opcode), + }; + + match pc_change { + ProgramCounter::Unknown(opcode) => { + println!("ERROR: OPCODE {:#06x} UNKNOWN", opcode); + self.pc += OPCODE_SIZE; + } + //ProgramCounter::Stay => (), + ProgramCounter::Next => self.pc += OPCODE_SIZE, + ProgramCounter::Skip => self.pc += 2 * OPCODE_SIZE, + ProgramCounter::Jump(addr) => self.pc = addr, + } + } + + // CLS: Clear the display. + fn op_00e0(&mut self) -> ProgramCounter { + self.vram = [0; CHIP8_HEIGHT]; + self.vram_changed = true; + ProgramCounter::Next + } + // RET: Return from a subroutine. + // The interpreter sets the program counter to the address at the + // top of the stack, then subtracts 1 from the stack pointer. + fn op_00ee(&mut self) -> ProgramCounter { + self.sp -= 1; + ProgramCounter::Jump(self.stack[self.sp]) + } + // JP addr + // The interpreter sets the program counter to nnn. + fn op_1nnn(&mut self, nnn: usize) -> ProgramCounter { + ProgramCounter::Jump(nnn) + } + // CALL addr + // The interpreter increments the stack pointer, then puts the + // current PC on the top of the stack. The PC is then set to nnn. + fn op_2nnn(&mut self, nnn: usize) -> ProgramCounter { + self.stack[self.sp] = self.pc + OPCODE_SIZE; + self.sp += 1; + ProgramCounter::Jump(nnn) + } + // SE Vx, byte: + // Skip next instruction if Vx = kk. + fn op_3xkk(&mut self, x: usize, kk: u8) -> ProgramCounter { + ProgramCounter::skip_if(self.v[x] == kk) + } + // SNE Vx, byte. + // Skip next instruction if Vx != kk. + fn op_4xkk(&mut self, x: usize, kk: u8) -> ProgramCounter { + ProgramCounter::skip_if(self.v[x] != kk) + } + // SE Vx, Vy + // Skip next instruction if Vx = Vy. + fn op_5xy0(&mut self, x: usize, y: usize) -> ProgramCounter { + ProgramCounter::skip_if(self.v[x] == self.v[y]) + } + // LD Vx, byte + // Set Vx = kk. + fn op_6xkk(&mut self, x: usize, kk: u8) -> ProgramCounter { + self.v[x] = kk; + ProgramCounter::Next + } + // ADD Vx, byte + // Set Vx = Vx + kk. + fn op_7xkk(&mut self, x: usize, kk: u8) -> ProgramCounter { + let vx = self.v[x] as u16; + let val = kk as u16; + let result = vx + val; + self.v[x] = result as u8; + ProgramCounter::Next + } + // LD Vx, Vy + // Set Vx = Vy. + fn op_8xy0(&mut self, x: usize, y: usize) -> ProgramCounter { + self.v[x] = self.v[y]; + ProgramCounter::Next + } + // OR Vx, Vy + // Set Vx = Vx OR Vy. + fn op_8xy1(&mut self, x: usize, y: usize) -> ProgramCounter { + self.v[x] |= self.v[y]; + ProgramCounter::Next + } + // AND Vx, Vy + // Set Vx = Vx AND Vy. + fn op_8xy2(&mut self, x: usize, y: usize) -> ProgramCounter { + self.v[x] &= self.v[y]; + ProgramCounter::Next + } + // XOR Vx, Vy + // Set Vx = Vx XOR Vy. + fn op_8xy3(&mut self, x: usize, y: usize) -> ProgramCounter { + self.v[x] ^= self.v[y]; + ProgramCounter::Next + } + // ADD Vx, Vy + // The values of Vx and Vy are added together. If the result is + // greater than 8 bits (i.e., > 255,) VF is set to 1, otherwise 0. + // Only the lowest 8 bits of the result are kept, and stored in Vx. + fn op_8xy4(&mut self, x: usize, y: usize) -> ProgramCounter { + let vx = self.v[x] as u16; + let vy = self.v[y] as u16; + let result = vx + vy; + self.v[x] = result as u8; + self.v[0x0f] = if result > 0xFF { 1 } else { 0 }; + ProgramCounter::Next + } + // SUB Vx, Vy + // If Vx > Vy, then VF is set to 1, otherwise 0. Then Vy is subtracted from Vx, and the results stored in Vx. + fn op_8xy5(&mut self, x: usize, y: usize) -> ProgramCounter { + let temp = if self.v[x] >= self.v[y] { 1 } else { 0 }; + self.v[x] = self.v[x].wrapping_sub(self.v[y]); + self.v[0xf] = temp; + ProgramCounter::Next + } + // SHR Vx {, Vy} + // If the least-significant bit of Vx is 1, then VF is set to 1, + // otherwise 0. Then Vx is divided by 2. + fn op_8x06(&mut self, x: usize) -> ProgramCounter { + let temp = self.v[x] & 1; + self.v[x] >>= 1; + self.v[0xf] = temp; + ProgramCounter::Next + } + // SUBN Vx, Vy + // If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is subtracted + // from Vy, and the results stored in Vx. + fn op_8xy7(&mut self, x: usize, y: usize) -> ProgramCounter { + let temp = if self.v[y] >= self.v[x] { 1 } else { 0 }; + self.v[x] = self.v[y].wrapping_sub(self.v[x]); + self.v[0xf] = temp; + ProgramCounter::Next + } + // SHL Vx {, Vy} + // If the most-significant bit of Vx is 1, then VF is set to 1, + // otherwise to 0. Then Vx is multiplied by 2. + fn op_8x0e(&mut self, x: usize) -> ProgramCounter { + let temp = self.v[x] >> 7; + self.v[x] <<= 1; + self.v[0xf] = temp; + ProgramCounter::Next + } + // SNE Vx, Vy + // Skip next instruction if Vx != Vy. + fn op_9xy0(&mut self, x: usize, y: usize) -> ProgramCounter { + ProgramCounter::skip_if(self.v[x] != self.v[y]) + } + // LD I, addr + // Set I = nnn. + fn op_annn(&mut self, nnn: usize) -> ProgramCounter { + self.i = nnn; + ProgramCounter::Next + } + // JP V0, addr + // The program counter is set to nnn plus the value of V0. + fn op_bnnn(&mut self, nnn: usize) -> ProgramCounter { + ProgramCounter::Jump((self.v[0] as usize) + nnn) + } + // RND Vx, byte + // The interpreter generates a random number from 0 to 255, + // which is then ANDed with the value kk. The results are stored in Vx. + fn op_cxkk(&mut self, x: usize, kk: u8) -> ProgramCounter { + let mut rng = rand::thread_rng(); + self.v[x] = rng.gen::() & kk; + ProgramCounter::Next + } + // DRW Vx, Vy, n + // The interpreter reads n bytes from memory, starting at the address + // stored in I. These bytes are then displayed as sprites on screen at + // coordinates (Vx, Vy). Sprites are XORed onto the existing screen. + // If this causes any pixels to be erased, VF is set to 1, otherwise + // it is set to 0. If the sprite is positioned so part of it is outside + // the coordinates of the display, it wraps around to the opposite side + // of the screen. + + /* + + 0b1_0110_010 + 0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000 + + */ + /* + 0x1110_1010_0000 + 0x0011_0101_0010 AND + 0x0010_0000_0000 + */ + + /* + 0x0000_1100_0101 + 0x0000_ + */ + + fn op_dxyn(&mut self, x: usize, y: usize, n: usize) -> ProgramCounter { + self.v[0x0f] = 0; + for byte in 0..n { + let y = (self.v[y] as usize + byte) % CHIP8_HEIGHT; + let x = self.v[x] as usize % CHIP8_WIDTH; + + let mut mask = self.ram[self.i + byte] as u64; + + if x + 8 > CHIP8_WIDTH { + let tmp = mask >> (x - 56); + mask <<= 56 + ((x + 8) % CHIP8_WIDTH as usize); + mask |= tmp; + } else { + mask = mask << (56 - x); + } + self.v[0xf] |= if self.vram[y] & mask > 0 { 1 } else { 0 }; + self.vram[y] = self.vram[y] ^ mask; + } + self.vram_changed = true; + ProgramCounter::Next + } + // SKP Vx + // SKP Vx + // Skip next instruction if key with the value of Vx is pressed. + fn op_ex9e(&mut self, x: usize) -> ProgramCounter { + ProgramCounter::skip_if((self.keypad >> self.v[x]) & 0x1 == 1) + } + // SKNP Vx + // Skip next instruction if key with the value of Vx is NOT pressed. + fn op_exa1(&mut self, x: usize) -> ProgramCounter { + ProgramCounter::skip_if((self.keypad >> self.v[x]) & 0x1 == 0) + } + // LD Vx, sDT + // Set Vx = delay timer value. + fn op_fx07(&mut self, x: usize) -> ProgramCounter { + self.v[x] = self.delay_timer; + ProgramCounter::Next + } + // LD Vx, K + // Wait for a key press, store the value of the key in Vx. + + /* + 8421 8421 + 0b0011_0101 + 0b1100_1010 + 0b0110_0101 + */ + + fn op_fx0a(&mut self, x: usize) -> ProgramCounter { + self.keypad_wait = true; + self.keypad_wait_register = x; + ProgramCounter::Next + } + // LD DT, Vx + // Set delay timer = Vx. + fn op_fx15(&mut self, x: usize) -> ProgramCounter { + self.delay_timer = self.v[x]; + ProgramCounter::Next + } + // LD ST, Vx + // Set sound timer = Vx. + fn op_fx18(&mut self, x: usize) -> ProgramCounter { + self.sound_timer = self.v[x]; + ProgramCounter::Next + } + // ADD I, Vx + // Set I = I + Vx + fn op_fx1e(&mut self, x: usize) -> ProgramCounter { + self.i += self.v[x] as usize; + self.v[0x0f] = if self.i > 0x0F00 { 1 } else { 0 }; + ProgramCounter::Next + } + // LD F, Vx + // Set I = location of sprite for digit Vx. + fn op_fx29(&mut self, x: usize) -> ProgramCounter { + self.i = (self.v[x] as usize) * 5; + ProgramCounter::Next + } + + // LD B, Vx + // The interpreter takes the decimal value of Vx, and places + // the hundreds digit in memory at location in I, the tens digit + // at location I+1, and the ones digit at location I+2. + fn op_fx33(&mut self, x: usize) -> ProgramCounter { + self.ram[self.i] = self.v[x] / 100; + self.ram[self.i + 1] = (self.v[x] % 100) / 10; + self.ram[self.i + 2] = self.v[x] % 10; + ProgramCounter::Next + } + + // LD [I], Vx + // The interpreter copies the values of registers V0 through Vx + // into memory, starting at the address in I. + fn op_fx55(&mut self, x: usize) -> ProgramCounter { + for i in 0..x + 1 { + self.ram[self.i + i] = self.v[i]; + } + ProgramCounter::Next + } + + // LD Vx, [I] + // The interpreter reads values from memory starting at location + // I into registers V0 through Vx. + fn op_fx65(&mut self, x: usize) -> ProgramCounter { + for i in 0..x + 1 { + self.v[i] = self.ram[self.i + i]; + } + ProgramCounter::Next + } +} + +#[cfg(test)] +#[path = "./processor_test.rs"] +mod processor_test; diff --git a/src/processor_test.rs b/src/processor_test.rs index f683df7..bf322b7 100644 --- a/src/processor_test.rs +++ b/src/processor_test.rs @@ -1,489 +1,479 @@ -use super::*; -const START_PC: usize = 0xF00; -const NEXT_PC: usize = START_PC + OPCODE_SIZE; -const SKIPPED_PC: usize = START_PC + (2 * OPCODE_SIZE); -fn build_processor() -> Processor { - let mut processor = Processor::new(); - processor.pc = START_PC; - processor.v = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7]; - processor -} -#[test] -fn test_initial_state() { - let processor = Processor::new(); - assert_eq!(processor.pc, 0x200); - assert_eq!(processor.sp, 0); - assert_eq!(processor.stack, [0; 16]); - // First char in font: 0 - assert_eq!(processor.ram[0..5], [0xF0, 0x90, 0x90, 0x90, 0xF0]); - // Last char in font: F - assert_eq!( - processor.ram[FONT_SET.len() - 5..FONT_SET.len()], - [0xF0, 0x80, 0xF0, 0x80, 0x80] - ); - - -} -#[test] -fn test_load_data() { - let mut processor = Processor::new(); - processor.load(&[1, 2, 3]); - assert_eq!(processor.ram[0x200], 1); - assert_eq!(processor.ram[0x201], 2); - assert_eq!(processor.ram[0x202], 3); -} - -// CLS -#[test] -fn test_op_00e0() { - let mut processor = build_processor(); - processor.vram = [1; CHIP8_HEIGHT]; - processor.run_opcode(0x00e0); - - for y in 0..CHIP8_HEIGHT { - assert_eq!(processor.vram[y], 0); - } - assert_eq!(processor.pc, NEXT_PC); -} -// RET -#[test] -fn test_op_00ee() { - let mut processor = Processor::new(); - processor.sp = 5; - processor.stack[4] = 0x6666; - processor.run_opcode(0x00ee); - assert_eq!(processor.sp, 4); - assert_eq!(processor.pc, 0x6666); -} -// JP -#[test] -fn test_op_1nnn() { - let mut processor = Processor::new(); - processor.run_opcode(0x1666); - assert_eq!(processor.pc, 0x0666); -} -// CALL -#[test] -fn test_op_2nnn() { - let mut processor = build_processor(); - processor.run_opcode(0x2666); - assert_eq!(processor.pc, 0x0666); - assert_eq!(processor.sp, 1); - assert_eq!(processor.stack[0], NEXT_PC); -} -// SE VX, byte -#[test] -fn test_op_3xkk() { - let mut processor = build_processor(); - processor.run_opcode(0x3201); - assert_eq!(processor.pc, SKIPPED_PC); - let mut processor = build_processor(); - processor.run_opcode(0x3200); - assert_eq!(processor.pc, NEXT_PC); -} -// SNE VX, byte -#[test] -fn test_op_4xkk() { - let mut processor = build_processor(); - processor.run_opcode(0x4200); - assert_eq!(processor.pc, SKIPPED_PC); - let mut processor = build_processor(); - processor.run_opcode(0x4201); - assert_eq!(processor.pc, NEXT_PC); -} -// SE VX, VY -#[test] -fn test_op_5xy0() { - let mut processor = build_processor(); - processor.run_opcode(0x5540); - assert_eq!(processor.pc, SKIPPED_PC); - let mut processor = build_processor(); - processor.run_opcode(0x5500); - assert_eq!(processor.pc, NEXT_PC); -} -// LD Vx, byte -#[test] -fn test_op_6xkk() { - let mut processor = build_processor(); - processor.run_opcode(0x65ff); - assert_eq!(processor.v[5], 0xff); - assert_eq!(processor.pc, NEXT_PC); -} -// ADD Vx, byte -#[test] -fn test_op_7xkk() { - let mut processor = build_processor(); - processor.run_opcode(0x75f0); - assert_eq!(processor.v[5], 0xf2); - assert_eq!(processor.pc, NEXT_PC); -} -// LD Vx, Vy -#[test] -fn test_op_8xy0() { - let mut processor = build_processor(); - processor.run_opcode(0x8050); - assert_eq!(processor.v[0], 0x02); - assert_eq!(processor.pc, NEXT_PC); -} -fn check_math(v1: u8, v2: u8, op: u16, result: u8, vf: u8) { - let mut processor = build_processor(); - processor.v[0] = v1; - processor.v[1] = v2; - processor.v[0x0f] = 0; - processor.run_opcode(0x8010 + op); - assert_eq!(processor.v[0], result); - assert_eq!(processor.v[0x0f], vf); - assert_eq!(processor.pc, NEXT_PC); -} -// OR Vx, Vy -#[test] -fn test_op_8xy1() { - // 0x0F or 0xF0 == 0xFF - check_math(0x0F, 0xF0, 1, 0xFF, 0); -} -// AND Vx, Vy -#[test] -fn test_op_8xy2() { - // 0x0F and 0xFF == 0x0F - check_math(0x0F, 0xFF, 2, 0x0F, 0); -} -// XOR Vx, Vy -#[test] -fn test_op_8xy3() { - // 0x0F xor 0xFF == 0xF0 - check_math(0x0F, 0xFF, 3, 0xF0, 0); -} -// ADD Vx, Vy -#[test] -fn test_op_8xy4() { - check_math(0x0F, 0x0F, 4, 0x1E, 0); - check_math(0xFF, 0xFF, 4, 0xFE, 1); -} -// SUB Vx, Vy -#[test] -fn test_op_8xy5() { - check_math(0x0F, 0x01, 5, 0x0E, 1); - check_math(0x0F, 0xFF, 5, 0x10, 0); -} -// SHR Vx -#[test] -fn test_op_8x06() { - // 4 >> 1 == 2 - check_math(0x04, 0, 6, 0x02, 0); - // 5 >> 1 == 2 with carry - check_math(0x05, 0, 6, 0x02, 1); -} -// SUBN Vx, Vy -#[test] -fn test_op_8xy7() { - check_math(0x01, 0x0F, 7, 0x0E, 1); - check_math(0xFF, 0x0F, 7, 0x10, 0); -} - -// SHL Vx -#[test] -fn test_op_8x0e() { - check_math(0b11000000, 0, 0x0e, 0b10000000, 1); - check_math(0b00000111, 0, 0x0e, 0b00001110, 0); -} - -// SNE VX, VY -#[test] -fn test_op_9xy0() { - let mut processor = build_processor(); - processor.run_opcode(0x90e0); - assert_eq!(processor.pc, SKIPPED_PC); - let mut processor = build_processor(); - processor.run_opcode(0x9010); - assert_eq!(processor.pc, NEXT_PC); -} - -// LD I, byte -#[test] -fn test_op_annn() { - let mut processor = build_processor(); - processor.run_opcode(0xa123); - assert_eq!(processor.i, 0x123); -} - -// JP V0, addr -#[test] -fn test_op_bnnn() { - let mut processor = build_processor(); - processor.v[0] = 3; - processor.run_opcode(0xb123); - assert_eq!(processor.pc, 0x126); -} - -// RND Vx, byte -// Generates random u8, then ANDs it with kk. -// We can't test randomness, but we can test the AND. -#[test] -fn test_op_cxkk() { - let mut processor = build_processor(); - processor.run_opcode(0xc000); - assert_eq!(processor.v[0], 0); - processor.run_opcode(0xc00f); - assert_eq!(processor.v[0] & 0xf0, 0); -} - -fn print_vram(vram: &[u64; 32]){ - for x in 0..32 { - println!("{:#066b}", vram[x]); - } -} - -// DRW Vx, Vy, nibble -#[test] -fn test_op_dxyn() { - let mut processor = build_processor(); - processor.i = 0; - processor.ram[0] = 0b11111111; - processor.ram[1] = 0b00000000; - //processor.vram[0][0] = 1; - //processor.vram[0][1] = 0; - processor.vram[0] = 0b01 << 63; - //processor.vram[1][0] = 1; - //processor.vram[1][1] = 0; - processor.vram[1] = 0b01 << 63; - processor.v[0] = 0; - processor.run_opcode(0xd002); - print_vram(&processor.vram); - //assert_eq!(processor.vram[0][0], 0); - assert_eq!(processor.vram[0] >> 63 & 1, 0); - //assert_eq!(processor.vram[0][1], 1); - assert_eq!((processor.vram[0] >> 62) & 1, 1); - //assert_eq!(processor.vram[1][0], 1); - assert_eq!(processor.vram[1] >> 63 & 1, 1); - //assert_eq!(processor.vram[1][1], 0); - assert_eq!((processor.vram[1] >> 62) & 1, 0); - assert_eq!(processor.v[0x0f], 1); - assert!(processor.vram_changed); - assert_eq!(processor.pc, NEXT_PC); -} - - -#[test] -fn test_op_dxyn_wrap_horizontal() { - let mut processor = build_processor(); - - let x = CHIP8_WIDTH - 4; - - processor.i = 0; - processor.ram[0] = 0b11111111; - processor.v[0] = x as u8; - processor.v[1] = 0; - processor.run_opcode(0xd011); - - print_vram(&processor.vram); - - //assert_eq!(processor.vram[0][x - 1], 0); //64 - 4 - 1 = 59 - assert_eq!((processor.vram[0] >> (x - 1)) & 1, 0); - //assert_eq!(processor.vram[0][x], 1); - assert_eq!((processor.vram[0] >> x) & 1 , 1); - //assert_eq!(processor.vram[0][x + 1], 1); - assert_eq!((processor.vram[0] >> (x + 1)) & 1, 1); - //assert_eq!(processor.vram[0][x + 2], 1); - assert_eq!((processor.vram[0] >> (x + 2)) & 1, 1); - //assert_eq!(processor.vram[0][x + 3], 1); - assert_eq!((processor.vram[0] >> (x + 3)) & 1, 1); - //assert_eq!(processor.vram[0][0], 1); - assert_eq!(processor.vram[0] & 1, 1); - //assert_eq!(processor.vram[0][1], 1); - assert_eq!((processor.vram[0] >> 1) & 1, 1); - //assert_eq!(processor.vram[0][2], 1); - assert_eq!(processor.vram[0] >> 2 & 1, 1); - //assert_eq!(processor.vram[0][3], 1); - assert_eq!(processor.vram[0] >> 3 & 1, 1); - //assert_eq!(processor.vram[0][4], 0); - assert_eq!(processor.vram[0] >> 4 & 1, 0); - - assert_eq!(processor.v[0x0f], 0); -} - -// DRW Vx, Vy, nibble -#[test] -fn test_op_dxyn_wrap_vertical() { - let mut processor = build_processor(); - let y = CHIP8_HEIGHT - 1; - - processor.i = 0; - processor.ram[0] = 0b11111111; - processor.ram[1] = 0b11111111; - processor.v[0] = 0; - processor.v[1] = y as u8; - processor.run_opcode(0xd012); - - print_vram(&processor.vram); - - //assert_eq!(processor.vram[y][0], 1); - assert_eq!(processor.vram[y] >> 63 & 1, 1); - //assert_eq!(processor.vram[0][0], 1); - assert_eq!(processor.vram[0] >> 63 & 1, 1); - assert_eq!(processor.v[0x0f], 0); -} - - -// SKP Vx -#[test] -fn test_op_ex9e() { - let mut processor = build_processor(); - // fedcba9876543210 - processor.keypad = 0b0000001000000000; - processor.v[5] = 9; - processor.run_opcode(0xe59e); - assert_eq!(processor.pc, SKIPPED_PC); - - - let mut processor = build_processor(); - processor.v[5] = 9; - processor.run_opcode(0xe59e); - assert_eq!(processor.pc, NEXT_PC); -} - -// SKNP Vx -#[test] -fn test_op_exa1() { - let mut processor = build_processor(); - processor.keypad = 0b0000001000000000; - processor.v[5] = 9; - processor.run_opcode(0xe5a1); - assert_eq!(processor.pc, NEXT_PC); - - - let mut processor = build_processor(); - processor.v[5] = 9; - processor.run_opcode(0xe5a1); - assert_eq!(processor.pc, SKIPPED_PC); -} - -// LD Vx, DT -#[test] -fn test_op_fx07() { - let mut processor = build_processor(); - processor.delay_timer = 20; - processor.run_opcode(0xf507); - assert_eq!(processor.v[5], 20); - assert_eq!(processor.pc, NEXT_PC); -} - -// LD Vx, K -#[test] -fn test_op_fx0a() { - let mut processor = build_processor(); - processor.run_opcode(0xf50a); - assert_eq!(processor.keypad_wait, true); - assert_eq!(processor.keypad_wait_register, 5); - assert_eq!(processor.pc, NEXT_PC); - - // Tick with no keypresses doesn't do anything - processor.tick(0x0); - assert_eq!(processor.keypad_wait, true); - assert_eq!(processor.keypad_wait_register, 5); - assert_eq!(processor.pc, NEXT_PC); - - // Tick with a keypress finishes wait and loads - // first pressed key into vx - processor.tick(0xffff); - assert_eq!(processor.keypad_wait, false); - assert_eq!(processor.v[5], 0); - assert_eq!(processor.pc, NEXT_PC); - -} - -// LD DT, vX -#[test] -fn test_op_fx15() { - let mut processor = build_processor(); - processor.v[5] = 9; - processor.run_opcode(0xf515); - assert_eq!(processor.delay_timer, 9); - assert_eq!(processor.pc, NEXT_PC); -} - -// LD ST, vX -#[test] -fn test_op_fx18() { - let mut processor = build_processor(); - processor.v[5] = 9; - processor.run_opcode(0xf518); - assert_eq!(processor.sound_timer, 9); - assert_eq!(processor.pc, NEXT_PC); -} - -// ADD I, Vx -#[test] -fn test_op_fx1e() { - let mut processor = build_processor(); - processor.v[5] = 9; - processor.i = 9; - processor.run_opcode(0xf51e); - assert_eq!(processor.i, 18); - assert_eq!(processor.pc, NEXT_PC); -} - -// LD F, Vx -#[test] -fn test_op_fx29() { - let mut processor = build_processor(); - processor.v[5] = 9; - processor.run_opcode(0xf529); - assert_eq!(processor.i, 5 * 9); - assert_eq!(processor.pc, NEXT_PC); - -} - -// LD B, Vx -#[test] -fn test_op_fx33() { - let mut processor = build_processor(); - processor.v[5] = 123; - processor.i = 1000; - processor.run_opcode(0xf533); - assert_eq!(processor.ram[1000], 1); - assert_eq!(processor.ram[1001], 2); - assert_eq!(processor.ram[1002], 3); - assert_eq!(processor.pc, NEXT_PC); - -} - -// LD [I], Vx -#[test] -fn test_op_fx55() { - let mut processor = build_processor(); - processor.i = 1000; - processor.run_opcode(0xff55); - for i in 0..16 { - assert_eq!(processor.ram[1000 + i as usize], processor.v[i]); - } - assert_eq!(processor.pc, NEXT_PC); -} - -// LD Vx, [I] -#[test] -fn test_op_fx65() { - let mut processor = build_processor(); - for i in 0..16 as usize { - processor.ram[1000 + i] = i as u8; - } - processor.i = 1000; - processor.run_opcode(0xff65); - - for i in 0..16 as usize { - assert_eq!(processor.v[i], processor.ram[1000 + i]); - } - assert_eq!(processor.pc, NEXT_PC); - -} - -#[test] -fn test_timers() { - let mut processor = build_processor(); - processor.delay_timer = 200; - processor.sound_timer = 100; - processor.tick(0x0000); - processor.delay_timer -=1; - processor.sound_timer -=1; - assert_eq!(processor.delay_timer, 199); - assert_eq!(processor.sound_timer, 99); -} +use super::*; +const START_PC: usize = 0xF00; +const NEXT_PC: usize = START_PC + OPCODE_SIZE; +const SKIPPED_PC: usize = START_PC + (2 * OPCODE_SIZE); +fn build_processor() -> Processor { + let mut processor = Processor::new(); + processor.pc = START_PC; + processor.v = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7]; + processor +} +#[test] +fn test_initial_state() { + let processor = Processor::new(); + assert_eq!(processor.pc, 0x200); + assert_eq!(processor.sp, 0); + assert_eq!(processor.stack, [0; 16]); + // First char in font: 0 + assert_eq!(processor.ram[0..5], [0xF0, 0x90, 0x90, 0x90, 0xF0]); + // Last char in font: F + assert_eq!( + processor.ram[FONT_SET.len() - 5..FONT_SET.len()], + [0xF0, 0x80, 0xF0, 0x80, 0x80] + ); +} +#[test] +fn test_load_data() { + let mut processor = Processor::new(); + processor.load(&[1, 2, 3]); + assert_eq!(processor.ram[0x200], 1); + assert_eq!(processor.ram[0x201], 2); + assert_eq!(processor.ram[0x202], 3); +} + +// CLS +#[test] +fn test_op_00e0() { + let mut processor = build_processor(); + processor.vram = [1; CHIP8_HEIGHT]; + processor.run_opcode(0x00e0); + + for y in 0..CHIP8_HEIGHT { + assert_eq!(processor.vram[y], 0); + } + assert_eq!(processor.pc, NEXT_PC); +} +// RET +#[test] +fn test_op_00ee() { + let mut processor = Processor::new(); + processor.sp = 5; + processor.stack[4] = 0x6666; + processor.run_opcode(0x00ee); + assert_eq!(processor.sp, 4); + assert_eq!(processor.pc, 0x6666); +} +// JP +#[test] +fn test_op_1nnn() { + let mut processor = Processor::new(); + processor.run_opcode(0x1666); + assert_eq!(processor.pc, 0x0666); +} +// CALL +#[test] +fn test_op_2nnn() { + let mut processor = build_processor(); + processor.run_opcode(0x2666); + assert_eq!(processor.pc, 0x0666); + assert_eq!(processor.sp, 1); + assert_eq!(processor.stack[0], NEXT_PC); +} +// SE VX, byte +#[test] +fn test_op_3xkk() { + let mut processor = build_processor(); + processor.run_opcode(0x3201); + assert_eq!(processor.pc, SKIPPED_PC); + let mut processor = build_processor(); + processor.run_opcode(0x3200); + assert_eq!(processor.pc, NEXT_PC); +} +// SNE VX, byte +#[test] +fn test_op_4xkk() { + let mut processor = build_processor(); + processor.run_opcode(0x4200); + assert_eq!(processor.pc, SKIPPED_PC); + let mut processor = build_processor(); + processor.run_opcode(0x4201); + assert_eq!(processor.pc, NEXT_PC); +} +// SE VX, VY +#[test] +fn test_op_5xy0() { + let mut processor = build_processor(); + processor.run_opcode(0x5540); + assert_eq!(processor.pc, SKIPPED_PC); + let mut processor = build_processor(); + processor.run_opcode(0x5500); + assert_eq!(processor.pc, NEXT_PC); +} +// LD Vx, byte +#[test] +fn test_op_6xkk() { + let mut processor = build_processor(); + processor.run_opcode(0x65ff); + assert_eq!(processor.v[5], 0xff); + assert_eq!(processor.pc, NEXT_PC); +} +// ADD Vx, byte +#[test] +fn test_op_7xkk() { + let mut processor = build_processor(); + processor.run_opcode(0x75f0); + assert_eq!(processor.v[5], 0xf2); + assert_eq!(processor.pc, NEXT_PC); +} +// LD Vx, Vy +#[test] +fn test_op_8xy0() { + let mut processor = build_processor(); + processor.run_opcode(0x8050); + assert_eq!(processor.v[0], 0x02); + assert_eq!(processor.pc, NEXT_PC); +} +fn check_math(v1: u8, v2: u8, op: u16, result: u8, vf: u8) { + let mut processor = build_processor(); + processor.v[0] = v1; + processor.v[1] = v2; + processor.v[0x0f] = 0; + processor.run_opcode(0x8010 + op); + assert_eq!(processor.v[0], result); + assert_eq!(processor.v[0x0f], vf); + assert_eq!(processor.pc, NEXT_PC); +} +// OR Vx, Vy +#[test] +fn test_op_8xy1() { + // 0x0F or 0xF0 == 0xFF + check_math(0x0F, 0xF0, 1, 0xFF, 0); +} +// AND Vx, Vy +#[test] +fn test_op_8xy2() { + // 0x0F and 0xFF == 0x0F + check_math(0x0F, 0xFF, 2, 0x0F, 0); +} +// XOR Vx, Vy +#[test] +fn test_op_8xy3() { + // 0x0F xor 0xFF == 0xF0 + check_math(0x0F, 0xFF, 3, 0xF0, 0); +} +// ADD Vx, Vy +#[test] +fn test_op_8xy4() { + check_math(0x0F, 0x0F, 4, 0x1E, 0); + check_math(0xFF, 0xFF, 4, 0xFE, 1); +} +// SUB Vx, Vy +#[test] +fn test_op_8xy5() { + check_math(0x0F, 0x01, 5, 0x0E, 1); + check_math(0x0F, 0xFF, 5, 0x10, 0); +} +// SHR Vx +#[test] +fn test_op_8x06() { + // 4 >> 1 == 2 + check_math(0x04, 0, 6, 0x02, 0); + // 5 >> 1 == 2 with carry + check_math(0x05, 0, 6, 0x02, 1); +} +// SUBN Vx, Vy +#[test] +fn test_op_8xy7() { + check_math(0x01, 0x0F, 7, 0x0E, 1); + check_math(0xFF, 0x0F, 7, 0x10, 0); +} + +// SHL Vx +#[test] +fn test_op_8x0e() { + check_math(0b11000000, 0, 0x0e, 0b10000000, 1); + check_math(0b00000111, 0, 0x0e, 0b00001110, 0); +} + +// SNE VX, VY +#[test] +fn test_op_9xy0() { + let mut processor = build_processor(); + processor.run_opcode(0x90e0); + assert_eq!(processor.pc, SKIPPED_PC); + let mut processor = build_processor(); + processor.run_opcode(0x9010); + assert_eq!(processor.pc, NEXT_PC); +} + +// LD I, byte +#[test] +fn test_op_annn() { + let mut processor = build_processor(); + processor.run_opcode(0xa123); + assert_eq!(processor.i, 0x123); +} + +// JP V0, addr +#[test] +fn test_op_bnnn() { + let mut processor = build_processor(); + processor.v[0] = 3; + processor.run_opcode(0xb123); + assert_eq!(processor.pc, 0x126); +} + +// RND Vx, byte +// Generates random u8, then ANDs it with kk. +// We can't test randomness, but we can test the AND. +#[test] +fn test_op_cxkk() { + let mut processor = build_processor(); + processor.run_opcode(0xc000); + assert_eq!(processor.v[0], 0); + processor.run_opcode(0xc00f); + assert_eq!(processor.v[0] & 0xf0, 0); +} + +fn print_vram(vram: &[u64; 32]) { + for x in 0..32 { + println!("{:#066b}", vram[x]); + } +} + +// DRW Vx, Vy, nibble +#[test] +fn test_op_dxyn() { + let mut processor = build_processor(); + processor.i = 0; + processor.ram[0] = 0b11111111; + processor.ram[1] = 0b00000000; + //processor.vram[0][0] = 1; + //processor.vram[0][1] = 0; + processor.vram[0] = 0b01 << 63; + //processor.vram[1][0] = 1; + //processor.vram[1][1] = 0; + processor.vram[1] = 0b01 << 63; + processor.v[0] = 0; + processor.run_opcode(0xd002); + print_vram(&processor.vram); + //assert_eq!(processor.vram[0][0], 0); + assert_eq!(processor.vram[0] >> 63 & 1, 0); + //assert_eq!(processor.vram[0][1], 1); + assert_eq!((processor.vram[0] >> 62) & 1, 1); + //assert_eq!(processor.vram[1][0], 1); + assert_eq!(processor.vram[1] >> 63 & 1, 1); + //assert_eq!(processor.vram[1][1], 0); + assert_eq!((processor.vram[1] >> 62) & 1, 0); + assert_eq!(processor.v[0x0f], 1); + assert!(processor.vram_changed); + assert_eq!(processor.pc, NEXT_PC); +} + +#[test] +fn test_op_dxyn_wrap_horizontal() { + let mut processor = build_processor(); + + let x = CHIP8_WIDTH - 4; + + processor.i = 0; + processor.ram[0] = 0b11111111; + processor.v[0] = x as u8; + processor.v[1] = 0; + processor.run_opcode(0xd011); + + print_vram(&processor.vram); + + //assert_eq!(processor.vram[0][x - 1], 0); //64 - 4 - 1 = 59 + assert_eq!((processor.vram[0] >> (x - 1)) & 1, 0); + //assert_eq!(processor.vram[0][x], 1); + assert_eq!((processor.vram[0] >> x) & 1, 1); + //assert_eq!(processor.vram[0][x + 1], 1); + assert_eq!((processor.vram[0] >> (x + 1)) & 1, 1); + //assert_eq!(processor.vram[0][x + 2], 1); + assert_eq!((processor.vram[0] >> (x + 2)) & 1, 1); + //assert_eq!(processor.vram[0][x + 3], 1); + assert_eq!((processor.vram[0] >> (x + 3)) & 1, 1); + //assert_eq!(processor.vram[0][0], 1); + assert_eq!(processor.vram[0] & 1, 1); + //assert_eq!(processor.vram[0][1], 1); + assert_eq!((processor.vram[0] >> 1) & 1, 1); + //assert_eq!(processor.vram[0][2], 1); + assert_eq!(processor.vram[0] >> 2 & 1, 1); + //assert_eq!(processor.vram[0][3], 1); + assert_eq!(processor.vram[0] >> 3 & 1, 1); + //assert_eq!(processor.vram[0][4], 0); + assert_eq!(processor.vram[0] >> 4 & 1, 0); + + assert_eq!(processor.v[0x0f], 0); +} + +// DRW Vx, Vy, nibble +#[test] +fn test_op_dxyn_wrap_vertical() { + let mut processor = build_processor(); + let y = CHIP8_HEIGHT - 1; + + processor.i = 0; + processor.ram[0] = 0b11111111; + processor.ram[1] = 0b11111111; + processor.v[0] = 0; + processor.v[1] = y as u8; + processor.run_opcode(0xd012); + + print_vram(&processor.vram); + + //assert_eq!(processor.vram[y][0], 1); + assert_eq!(processor.vram[y] >> 63 & 1, 1); + //assert_eq!(processor.vram[0][0], 1); + assert_eq!(processor.vram[0] >> 63 & 1, 1); + assert_eq!(processor.v[0x0f], 0); +} + +// SKP Vx +#[test] +fn test_op_ex9e() { + let mut processor = build_processor(); + // fedcba9876543210 + processor.keypad = 0b0000001000000000; + processor.v[5] = 9; + processor.run_opcode(0xe59e); + assert_eq!(processor.pc, SKIPPED_PC); + + let mut processor = build_processor(); + processor.v[5] = 9; + processor.run_opcode(0xe59e); + assert_eq!(processor.pc, NEXT_PC); +} + +// SKNP Vx +#[test] +fn test_op_exa1() { + let mut processor = build_processor(); + processor.keypad = 0b0000001000000000; + processor.v[5] = 9; + processor.run_opcode(0xe5a1); + assert_eq!(processor.pc, NEXT_PC); + + let mut processor = build_processor(); + processor.v[5] = 9; + processor.run_opcode(0xe5a1); + assert_eq!(processor.pc, SKIPPED_PC); +} + +// LD Vx, DT +#[test] +fn test_op_fx07() { + let mut processor = build_processor(); + processor.delay_timer = 20; + processor.run_opcode(0xf507); + assert_eq!(processor.v[5], 20); + assert_eq!(processor.pc, NEXT_PC); +} + +// LD Vx, K +#[test] +fn test_op_fx0a() { + let mut processor = build_processor(); + processor.run_opcode(0xf50a); + assert_eq!(processor.keypad_wait, true); + assert_eq!(processor.keypad_wait_register, 5); + assert_eq!(processor.pc, NEXT_PC); + + // Tick with no keypresses doesn't do anything + processor.tick(0x0); + assert_eq!(processor.keypad_wait, true); + assert_eq!(processor.keypad_wait_register, 5); + assert_eq!(processor.pc, NEXT_PC); + + // Tick with a keypress finishes wait and loads + // first pressed key into vx + processor.tick(0xffff); + assert_eq!(processor.keypad_wait, false); + assert_eq!(processor.v[5], 0); + assert_eq!(processor.pc, NEXT_PC); +} + +// LD DT, vX +#[test] +fn test_op_fx15() { + let mut processor = build_processor(); + processor.v[5] = 9; + processor.run_opcode(0xf515); + assert_eq!(processor.delay_timer, 9); + assert_eq!(processor.pc, NEXT_PC); +} + +// LD ST, vX +#[test] +fn test_op_fx18() { + let mut processor = build_processor(); + processor.v[5] = 9; + processor.run_opcode(0xf518); + assert_eq!(processor.sound_timer, 9); + assert_eq!(processor.pc, NEXT_PC); +} + +// ADD I, Vx +#[test] +fn test_op_fx1e() { + let mut processor = build_processor(); + processor.v[5] = 9; + processor.i = 9; + processor.run_opcode(0xf51e); + assert_eq!(processor.i, 18); + assert_eq!(processor.pc, NEXT_PC); +} + +// LD F, Vx +#[test] +fn test_op_fx29() { + let mut processor = build_processor(); + processor.v[5] = 9; + processor.run_opcode(0xf529); + assert_eq!(processor.i, 5 * 9); + assert_eq!(processor.pc, NEXT_PC); +} + +// LD B, Vx +#[test] +fn test_op_fx33() { + let mut processor = build_processor(); + processor.v[5] = 123; + processor.i = 1000; + processor.run_opcode(0xf533); + assert_eq!(processor.ram[1000], 1); + assert_eq!(processor.ram[1001], 2); + assert_eq!(processor.ram[1002], 3); + assert_eq!(processor.pc, NEXT_PC); +} + +// LD [I], Vx +#[test] +fn test_op_fx55() { + let mut processor = build_processor(); + processor.i = 1000; + processor.run_opcode(0xff55); + for i in 0..16 { + assert_eq!(processor.ram[1000 + i as usize], processor.v[i]); + } + assert_eq!(processor.pc, NEXT_PC); +} + +// LD Vx, [I] +#[test] +fn test_op_fx65() { + let mut processor = build_processor(); + for i in 0..16 as usize { + processor.ram[1000 + i] = i as u8; + } + processor.i = 1000; + processor.run_opcode(0xff65); + + for i in 0..16 as usize { + assert_eq!(processor.v[i], processor.ram[1000 + i]); + } + assert_eq!(processor.pc, NEXT_PC); +} + +#[test] +fn test_timers() { + let mut processor = build_processor(); + processor.delay_timer = 200; + processor.sound_timer = 100; + processor.tick(0x0000); + processor.delay_timer -= 1; + processor.sound_timer -= 1; + assert_eq!(processor.delay_timer, 199); + assert_eq!(processor.sound_timer, 99); +}