From cb030b992d89d2a53625ffd5da31e3200c8b08c3 Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Thu, 25 Jun 2020 01:40:09 -0500 Subject: [PATCH] Implement CHIP-8 Opcodes Warning: There are currently no tests and I'm pretty sure none of them work well /shrug --- Cargo.lock | 79 ++++++++ Cargo.toml | 1 + src/main.rs | 515 +++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 572 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60fb3b2..5425e3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,84 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "chip8" version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "ppv-lite86" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" diff --git a/Cargo.toml b/Cargo.toml index 36dd0f2..e712fb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +rand = "0.7.3" diff --git a/src/main.rs b/src/main.rs index ad3dcd0..044736b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,19 @@ +// I used: http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#00E0 +// and https://en.wikipedia.org/wiki/CHIP-8#Opcode_table +// for information about how to implement a CHIP-8 Emulator +use std::fs::File; +use std::io::{self, Read}; +use std::path::Path; +use std::time::Duration; const SCREEN_SIZE: usize = 64 * 32; +#[derive(Copy, Clone)] struct Chip8 { opcode: u16, i: u16, pc: u16, - sp: u16, + sp: u8, key: Option, v: [u8; 16], stack: [u16; 16], @@ -15,13 +23,12 @@ struct Chip8 { display: Display, } - impl Default for Chip8 { fn default() -> Self { Chip8 { opcode: 0, i: 0, - pc: 0, + pc: 0x200, // Progrm counter starts at 0x200 sp: 0, key: None, v: [0; 16], @@ -34,18 +41,31 @@ impl Default for Chip8 { } } - impl Chip8 { - fn new() -> Self { - Default::default() + pub fn execute_cycle(&mut self) { + self.opcode = self.get_opcode(); + println!("{:#x}", self.opcode); + self.handle_opcode(); + + self.delay.tick(); + self.sound.tick(); } - pub fn execute_cycle(&self) { - + pub fn load_rom>(&mut self, path: P) -> Result<(), io::Error> { + let mut file = File::open(path.as_ref())?; + let mut rom_buf: Vec = vec![]; + file.read_to_end(&mut rom_buf)?; + + for (i, byte) in rom_buf.iter().enumerate() { + self.memory[i + 0x200] = *byte; + } + + Ok(()) } fn get_opcode(&self) -> u16 { - let pc = self.pc as usize; + let pc = self.pc as usize; + ((self.memory[pc] as u16) << 8) | self.memory[pc + 1] as u16 } @@ -54,31 +74,411 @@ impl Chip8 { let nib_1 = (self.opcode & 0xF000) >> 12; //0xA let nib_2 = (self.opcode & 0x0F00) >> 8; // 0x2 let nib_3 = (self.opcode & 0x00F0) >> 4; // 0xF - let nib_4 = self.opcode & 0x000F; // 0x0 + let nib_4 = self.opcode & 0x000F; // 0x0 - // nib_ns are u16s so we waste 4 bytes here. + // nib_ns are u16s so we waste 4 bytes here. match (nib_1, nib_2, nib_3, nib_4) { // CLS - (0x0, 0x0, 0xE, 0x0) => self.clear_display(), + (0x0, 0x0, 0xE, 0x0) => self.cls(), // 00EE - (0x0, 0x0, 0xE, 0xE) => {} + (0x0, 0x0, 0xE, 0xE) => self.ret(), // 1NNN - (0x1, _, _, _) => {} + (0x1, _, _, _) => self.jmp_addr(nib_2, nib_3, nib_4), // 2NNN - (0x2, _, _, _) => {} + (0x2, _, _, _) => self.call_addr(nib_2, nib_3, nib_4), // 3XKK - (0x3, _, _, _) => {} - _ => unimplemented!("UNIMPLEMENTED OPCODE: {:#x}", self.opcode) + (0x3, _, _, _) => self.se_vx_byte(nib_2, nib_3, nib_4), + // 4xkk + (0x4, _, _, _) => self.sne_vx_byte(nib_2, nib_3, nib_4), + // 5xy0 + (0x5, _, _, 0x0) => self.se_vx_vy(nib_2, nib_3), + // 6xkk + (0x6, _, _, _) => self.ld_vx_byte(nib_2, nib_3, nib_4), + // 7xkk + (0x7, _, _, _) => self.add_vx_byte(nib_2, nib_3, nib_4), + // 8xy0 + (0x8, _, _, 0x0) => self.ld_vx_vy(nib_2, nib_3), + // 8xy1 + (0x8, _, _, 0x1) => self.or_vx_vy(nib_2, nib_3), + // 8xy2 + (0x8, _, _, 0x2) => self.and_vx_vy(nib_2, nib_3), + // 8xy3 + (0x8, _, _, 0x3) => self.xor_vx_vy(nib_2, nib_3), + // 8xy4 + (0x8, _, _, 0x4) => self.add_vx_vy(nib_2, nib_3), + // 8xy5 + (0x8, _, _, 0x5) => self.sub_vx_vy(nib_2, nib_3), + // 8xy6 + (0x8, _, _, 0x6) => self.shr_vx(nib_2), + // 8xy7 + (0x8, _, _, 0x7) => self.subn_vx_vy(nib_2, nib_3), + // 8xyE + (0x8, _, _, 0xE) => self.shl_vx(nib_2), + // 9xy0 + (0x9, _, _, 0x0) => self.sne_vx_vy(nib_2, nib_3), + // Annn + (0xA, _, _, _) => self.ld_i_addr(nib_2, nib_3, nib_4), + // Bnnn + (0xB, _, _, _) => self.jmp_v0_addr(nib_2, nib_3, nib_4), + // Cxkk + (0xC, _, _, _) => self.rnd_vx_byte(nib_2, nib_3, nib_4), + //Dxyn + (0xD, _, _, _) => self.drw_vx_vy_nib(nib_2, nib_3, nib_4), + // Ex9E + (0xE, _, 0x9, 0xE) => self.skp_vx(nib_2), + // ExA1 + (0xE, _, 0xA, 0x1) => self.sknp_vx(nib_2), + // Fx07 + (0xF, _, 0x0, 0x7) => self.ld_vx_dt(nib_2), + // Fx0A + (0xF, _, 0x0, 0xA) => self.ld_vx_k(nib_2), + // Fx15 + (0xF, _, 0x1, 0x15) => self.ld_dt_vx(nib_2), + // Fx18 + (0xF, _, 0x1, 0x8) => self.ld_st_vx(nib_2), + // Fx1E + (0xF, _, 0x1, 0xE) => self.add_i_vx(nib_2), + // Fx29 + (0xF, _, 0x2, 0x9) => self.ld_f_vx(nib_2), + // Fx33 + (0xF, _, 0x3, 0x3) => self.ld_b_vx(nib_2), + // Fx55 + (0xF, _, 0x5, 0x5) => self.ld_i_vx(nib_2), + // Fx65 + (0xF, _, 0x6, 0x5) => self.ld_vx_i(nib_2), + // General Case + _ => println!("UNIMPLEMENTED OPCODE: {:#x}", self.opcode), } } - fn clear_display(&mut self) { + fn cls(&mut self) { + // Clear the display self.display.clear(); + self.pc += 1; + } + + fn jmp_addr(&mut self, n_1: u16, n_2: u16, n_3: u16) { + // sets the program counter to addr (nnn) + self.pc = Self::convert_to_addr(n_1, n_2, n_3); + self.pc += 1; + } + + fn ret(&mut self) { + // sets the program counter to the addres at the top of the stack, + // then subtracts one (1) from the stack pointer + self.pc = self.stack[self.sp as usize]; + self.sp -= 1; + self.pc += 1; + } + + fn call_addr(&mut self, n_1: u16, n_2: u16, n_3: u16) { + // increments the stack pointer, then puts current pc on top of stack + // pc is then set to addr + self.sp += 1; + self.stack[self.sp as usize] = self.pc; + self.pc = Self::convert_to_addr(n_1, n_2, n_3); + self.pc += 1; + } + + fn se_vx_byte(&mut self, x: u16, k_1: u16, k_2: u16) { + // compares Vx to kk. If they are equal, pc is incremented by 2 + if self.v[x as usize] == Self::convert_to_byte(k_1, k_2) { + self.pc += 2; + } else { + self.pc += 1; + } + } + + fn sne_vx_byte(&mut self, x: u16, k_1: u16, k_2: u16) { + // compares Vx to kk. If they are **not** equal, pc is incremented by 2 + if self.v[x as usize] != Self::convert_to_byte(k_1, k_2) { + self.pc += 2; + } else { + self.pc += 1; + } + } + + fn se_vx_vy(&mut self, x: u16, y: u16) { + // compares Vx to Vy. If they are equal, pc is incremented by 2 + if self.v[x as usize] == self.v[y as usize] { + self.pc += 2; + } else { + self.pc += 1; + } + } + + fn ld_vx_byte(&mut self, x: u16, k_1: u16, k_2: u16) { + // put value kk into Vx + self.v[x as usize] = Self::convert_to_byte(k_1, k_2); + self.pc += 1; + } + + fn add_vx_byte(&mut self, x: u16, k_1: u16, k_2: u16) { + // calculate Vx + kk, then store it in Vx + let x = x as usize; + self.v[x] = self.v[x] + Self::convert_to_byte(k_1, k_2); + self.pc += 1; + } + + fn ld_vx_vy(&mut self, x: u16, y: u16) { + // store Vy in Vx + self.v[x as usize] = self.v[y as usize]; + self.pc += 1; + } + + fn or_vx_vy(&mut self, x: u16, y: u16) { + // calc bitwise OR on Vx and Vy, then store in Vx + let x = x as usize; + self.v[x] = self.v[x] | self.v[y as usize]; + self.pc += 1; + } + + fn and_vx_vy(&mut self, x: u16, y: u16) { + // calc bitwise AND on Vx and Vy, then store in Vx + let x = x as usize; + self.v[x] = self.v[x] & self.v[y as usize]; + self.pc += 1; + } + + fn xor_vx_vy(&mut self, x: u16, y: u16) { + // calc bitwise XOR on Vx and Vy, then store in Vx + let x = x as usize; + self.v[x] = self.v[x] ^ self.v[y as usize]; + self.pc += 1; + } + + fn add_vx_vy(&mut self, x: u16, y: u16) { + // add Vx and Vy, if result is greater than 8 bits + // set VF to 1, otherwise 0 + // only the lowest 8 bits of result are stored in Vx + let x = x as usize; + + let (res, did_overflow) = self.v[x].overflowing_add(self.v[y as usize]); + self.v[0xF as usize] = if did_overflow { 1 } else { 0 }; + self.v[x] = res; + self.pc += 1; + } + + fn sub_vx_vy(&mut self, x: u16, y: u16) { + // subtract Vx and Vy, if Vx > Vy VF is set to 1, otherwise 0 + // then set Vx to Vx - Vy + let vx = self.v[x as usize]; + let vy = self.v[y as usize]; + + self.v[0xF as usize] = if vx > vy { 1 } else { 0 }; + self.v[x as usize] = vx - vy; + self.pc += 1; + } + + fn shr_vx(&mut self, x: u16) { + // if LSB is set to 1, set VF to 1, otherwise set VF to 0 + // then shift Vx one to the right + let x = x as usize; + + self.v[0xF as usize] = if (self.v[x] & 1) == 1 { 1 } else { 0 }; + self.v[x] = self.v[x] >> 1; + self.pc += 1; + } + + fn subn_vx_vy(&mut self, x: u16, y: u16) { + // subtract Vy and Vx, if Vy > Vx VF is set to 1, otherwise 0 + // then set Vx = Vy - Vx + let vx = self.v[x as usize]; + let vy = self.v[y as usize]; + + self.v[0xF as usize] = if vy > vx { 1 } else { 0 }; + self.v[x as usize] = vy - vx; + self.pc += 1; + } + + fn shl_vx(&mut self, x: u16) { + // if MSB is set to 1, set VF to 1, otherwise set VF to 0 + // then shift Vx one to the left; + let x = x as usize; + + self.v[0xF as usize] = if (self.v[x] & 0x80) == 1 { 1 } else { 0 }; + self.v[x] = self.v[x] << 1; + self.pc += 1; + } + + fn sne_vx_vy(&mut self, x: u16, y: u16) { + // if Vx != vy program counter is increased by 2 + if self.v[x as usize] != self.v[y as usize] { + self.pc += 2; + } else { + self.pc += 1; + } + } + + fn ld_i_addr(&mut self, n_1: u16, n_2: u16, n_3: u16) { + // set i to addr + self.i = Self::convert_to_addr(n_1, n_2, n_3); + self.pc += 1; + } + + fn jmp_v0_addr(&mut self, n_1: u16, n_2: u16, n_3: u16) { + // set program counter to addr + V0 + self.pc = Self::convert_to_addr(n_1, n_2, n_3) + self.v[0 as usize] as u16; + self.pc += 1; + } + + fn rnd_vx_byte(&mut self, x: u16, k_1: u16, k_2: u16) { + // generate a random number from 0 to 255 + // AND with the value of kk, then store in Vx + + self.v[x as usize] = rand::random::() & Self::convert_to_byte(k_1, k_2); + self.pc += 1; + } + + fn drw_vx_vy_nib(&mut self, x: u16, y: u16, nib: u16) { + // read n bytes from memory starting from self.i + // then display them starting at (vx, vy) + let i = self.i as usize; + let _disp_x = self.v[x as usize]; + let _disp_y = self.v[y as usize]; + + // There's Probably an off-by-one error here. + let _sprite_buf = &self.memory[i..=(i + nib as usize)]; + let _display_buf = self.display.buf; + + todo!("Finish implementing DRW"); + } + + fn skp_vx(&mut self, x: u16) { + // if current key is the same as the on in Vx + // program counter is increased by 2 + if let Some(key) = self.key { + if self.v[x as usize] == key { + self.pc += 2; + } else { + self.pc += 1; + } + } else { + self.pc += 1; + } + } + + fn sknp_vx(&mut self, x: u16) { + // if current key is not the sameas the one in Vx + // increment the program counter by 2 + match self.key { + Some(key) => { + if key != self.v[x as usize] { + self.pc += 2; + } else { + self.pc += 1; + } + } + None => self.pc += 2, + } + } + + fn ld_vx_dt(&mut self, x: u16) { + // set Vx to be the value of the delay timer + self.v[x as usize] = self.delay.get(); + self.pc += 1; + } + + fn ld_vx_k(&mut self, x: u16) { + // wait (blocking) until a key is pressed + // once pressed, store in Vx + loop { + if let Some(key) = self.key { + self.v[x as usize] = key; + break; + } + } + self.pc += 1; + } + + fn ld_dt_vx(&mut self, x: u16) { + // set delay timer to be value of Vx + self.delay.set(self.v[x as usize]); + self.pc += 1; + } + + fn ld_st_vx(&mut self, x: u16) { + // set sound timer to be value of Vx + self.delay.set(self.v[x as usize]); + self.pc += 1; + } + + fn add_i_vx(&mut self, x: u16) { + // set I to be I + Vx + self.i = self.i + self.v[x as usize] as u16; + self.pc += 1; + } + + fn ld_f_vx(&mut self, _x: u16) { + todo!("Implement 0xF_29"); + } + + fn ld_b_vx(&mut self, x: u16) { + // take hundreds digit and place it at I + // take tens digit and place it at I + 1 + // take ones digit and place it at I + 2 + let i = self.i as usize; + let mut iter = Self::digits(self.v[x as usize]); + + let ones = iter.next().unwrap(); // Ther has to at least be a ones lol + let tens = iter.next().unwrap_or(0); + let hundreds = iter.next().unwrap_or(0); + + self.memory[i] = hundreds; + self.memory[i + 1] = tens; + self.memory[i + 2] = ones; + self.pc += 1; + } + + fn ld_i_vx(&mut self, x: u16) { + // copy values v0 -> Vx to memory starting at i + + for n in 0..=(x as usize) { + self.memory[self.i as usize + n] = self.v[n]; + } + self.pc += 1; + } + + fn ld_vx_i(&mut self, x: u16) { + // read what will be values of v0 -> vx from memory starting at i + let x = x as usize; + + for n in 0..=x { + self.v[n] = self.memory[self.i as usize + n]; + } + self.pc += 1; + } + + fn convert_to_byte(k_1: u16, k_2: u16) -> u8 { + ((k_1 as u8) << 4) | (k_2 as u8) + } + + fn convert_to_addr(n_1: u16, n_2: u16, n_3: u16) -> u16 { + (n_1 << 8) | (n_2 << 4) | n_3 + } + + // https://stackoverflow.com/questions/41536479/how-do-i-split-an-integer-into-individual-digits + fn digits(mut num: u8) -> impl Iterator { + let mut divisor = 1; + while num >= divisor * 10 { + divisor *= 10; + } + + std::iter::from_fn(move || { + if divisor == 0 { + None + } else { + let v = num / divisor; + num %= divisor; + divisor /= 10; + Some(v) + } + }) } - } +#[derive(Copy, Clone)] struct Display { buf: [u8; SCREEN_SIZE], } @@ -97,18 +497,87 @@ impl Default for Display { } } +#[derive(Debug, Copy, Clone)] struct Timer { - value: u8, + remaining: u8, + enabled: bool, } impl Default for Timer { fn default() -> Self { Timer { - value: 0, + remaining: 0, + enabled: false, + } + } +} + +impl Timer { + pub fn set(&mut self, secs: u8) { + self.remaining = secs; + self.enabled = true; + } + + pub fn get(&self) -> u8 { + self.remaining + } + + pub fn tick(&mut self) { + if self.enabled { + self.remaining -= 1; + + if self.remaining == 0 { + println!("Beep!"); + self.enabled = false; + } + } + } +} + +/// Remember that the digits are yielded in the oposite order +#[derive(Debug, Copy, Clone)] +pub struct Digits { + num: u8, + flag: bool, +} + +impl From for Digits { + fn from(num: u8) -> Self { + Digits { num, flag: true } + } +} + +impl Iterator for Digits { + type Item = u8; + + fn next(&mut self) -> Option { + if self.num >= 10 { + // There is something next + let tmp = self.num; + self.num = self.num / 10; + + Some(tmp % 10) + } else { + if self.flag { + self.flag = false; + Some(self.num % 10) + } else { + None + } } } } fn main() { - println!("Chip8 Emulator"); -} \ No newline at end of file + let mut chip8: Chip8 = Default::default(); + + chip8 + .load_rom(Path::new("C:\\Users\\paoda\\Documents\\chip8_roms\\TICTAC")) + .expect("Unable to load ROM"); + + loop { + chip8.execute_cycle(); + + std::thread::sleep(Duration::from_millis(1000)); + } +}