From 31cb1bed9e21ac0de33c17a0a1b116686fe5fbbd Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sat, 15 May 2021 21:19:59 +0200 Subject: [PATCH] basic instructions --- Cargo.lock | 78 ++++++++++++++ Cargo.toml | 1 + src/interpreter.rs | 261 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 7 +- 4 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 src/interpreter.rs diff --git a/Cargo.lock b/Cargo.lock index f2f4819..da99ec8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,83 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "chip-8" version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" diff --git a/Cargo.toml b/Cargo.toml index 4289ed7..42cf0d8 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.8.3" \ No newline at end of file diff --git a/src/interpreter.rs b/src/interpreter.rs new file mode 100644 index 0000000..ae28ee2 --- /dev/null +++ b/src/interpreter.rs @@ -0,0 +1,261 @@ +// this should be read before reading the code +// http://devernay.free.fr/hacks/chip8/C8TECH10.HTM + +use rand::Rng; + +#[allow(dead_code)] +struct Emulator { + memory: [u8; 4096], + reg: [u8; 16], + stack: [u16; 16], + pc: u16, + sp: u8, + i: u16, + delay_t: u8, + sound_t: u8, +} + +impl Emulator { + fn push(&mut self, n: u16) { + self.sp += 1; + self.stack[self.sp as usize] = n; + } + fn pop(&mut self) -> u16 { + let n = self.stack[self.sp as usize]; + self.sp -= 1; + n + } + fn jmp(&mut self, location: u16) { + self.pc = location; + } + + fn next(&mut self) { + self.pc += 1; + } + + fn set_flag(&mut self, cond: bool) { + self.reg[0xF] = if cond { + 1 + } else { + 0 + }; + } +} + + +pub fn run(program: &[u16]) { + let mut em = Emulator { + memory: [0; 4096], + reg: [0; 16], + stack: [0; 16], + pc: 0, + sp: 0, + i: 0, + delay_t: 0, + sound_t: 0, + }; + + while em.pc < program.len() as u16 { + let instruction = program[em.pc as usize]; + execute(instruction, &mut em); + em.next(); + } +} + +fn execute(instruction: u16, em: &mut Emulator) { + match instruction { + 0x00E0 => unimplemented!(), // clear display + 0x00EE => { // return subroutine + let location = em.pop(); + em.jmp(location); + } + 0x1000..=0x1FFF => { // jmp + let location = instruction & 0x0FFF; + em.jmp(location - 1); + } + 0x2000..=0x2FFF => { // 0nnn, call nnn + let location = instruction & 0x0FFF; + em.push(em.pc); + em.jmp(location - 1); + } + 0x3000..=0x3FFF => { // SE - Skip equal | 0xkk, skip instruction if Vx == kk + let (x, kk) = extract_0xkk(instruction); + if em.reg[x] == kk { + em.next(); + } + } + 0x4000..=0x4FFF => { // SNE - Skip not equal | 0xkk, skip instruction if Vx != kk + let (x, kk) = extract_0xkk(instruction); + if em.reg[x] != kk { + em.next(); + } + } + 0x5000..=0x5FF0 => { // SE - Skip equal | 0xy0, skip if Vx == Vy + let (x, y) = extract_0xy0(instruction); + if em.reg[x] == em.reg[y] { + em.next(); + } + } + 0x6000..=0x6FFF => { // LD - Load | 0xkk, load kk into Vx + let (x, kk) = extract_0xkk(instruction); + em.reg[x] = kk; + } + 0x7000..=0x7FFF => { // ADD - Add | 0xkk, add kk to Vx + let (x, kk) = extract_0xkk(instruction); + em.reg[x] = em.reg[x].wrapping_add(kk); + } + 0x8000..=0x8FF0 => { // 0xyz, do z on Vy, Vx + let (x, y, z) = extract_0xyz(instruction); + match z { + 0 => { // LD - Load | Stores Vy in Vx + em.reg[x] = em.reg[y]; + } + 1 => { // OR | Stores a bitwise OR of Vx and Vy in Vx + em.reg[x] |= em.reg[y]; + } + 2 => { // AND | Stores a bitwise AND of Vx and Vy in Vx + em.reg[x] &= em.reg[y]; + } + 3 => { // XOR | Stores a bitwise XOR of Vx and Vy in Vx + em.reg[x] ^= em.reg[y]; + } + 4 => { // ADD | Adds Vx to Vy. If it overflows, Vf = 1 else Vf = 0 + let (val, f) = em.reg[x].overflowing_add(em.reg[y]); + em.reg[x] = val; + em.set_flag(f); + } + 5 => { // SUB | If Vx > Vy, Vf = 1, else Vf = 0, then subtract Vy from Vx and store it into Vx + em.set_flag(em.reg[x] > em.reg[y]); + em.reg[x] = em.reg[x].wrapping_sub(em.reg[y]) + } + 6 => { // SHR - shift right | shift Vx right by one bit, if the rightmost bit is 1 set flag + em.set_flag((em.reg[x] & 0x0001) > 0); + em.reg[x] >>= 1; + } + 7 => { // SUBN - Sub not borrow | subtract Vx from Vy and store it into Vx + em.set_flag(em.reg[y] > em.reg[x]); + em.reg[x] = em.reg[y].wrapping_sub(em.reg[x]); + } + 0xE => { // SHL - Shift left | shift Vx left, if the leftmost bit is 1 set flag + em.set_flag((em.reg[x] & 0x80) > 0); + em.reg[x] <<= 1; + } + _ => unreachable!("invalid instruction") + } + } + 0x9000..=0x9FFF => { // SNE - skip not equal | skip next if Vx != Vy + let (x, y) = extract_0xy0(instruction); + if em.reg[x] != em.reg[y] { + em.next(); + } + } + 0xA000..=0xAFFF => { // LDI - load i | load nnn into i + let nnn = extract_0nnn(instruction); + em.i = nnn; + } + 0xB000..=0xBFFF => { // JMP | jump to nnn + V0 + let nnn = extract_0nnn(instruction); + em.jmp(em.reg[0] as u16 + nnn - 1); + } + 0xC000..=0xCFFF => { // RAN | set Vx to a random byte AND kk + let (x, kk) = extract_0xii(instruction); + let ran = rand::thread_rng().gen_range(0..=255); + em.reg[x] = ran & kk; + } + 0xD000..=0xDFFF => { // 0xyn display n-byte sprite starting at location I at (Vx, Vy), set Vf if there's a collision + // read n bytes from memory at loc I and display it at coords (Vx, Vy) + // XOR them onto the screen, Vf = 1 if a pixel was erased + // wrap sprites around + unimplemented!() + } + 0xE000..=0xEFFF => { // skip/not skip instruction if key Vx is pressed + let (x, sub_inst) = extract_0xii(instruction); + fn key_pressed(key: u8) -> bool { unimplemented!("{}", key) } + + match sub_inst { + 0x9E => { // skip + if key_pressed(em.reg[x]) { + em.next(); + } + } + 0xA1 => { // not skip + if !key_pressed(em.reg[x]) { + em.next(); + } + } + _ => unreachable!("invalid instruction") + } + } + 0xF000..=0xFFFF => { // misc + let (x, sub_inst) = extract_0xii(instruction); + match sub_inst { + 0x0F => { // set Vx = delay timer value + em.reg[x] = em.delay_t; + } + 0x0A => { // wait for key and store it in Vx + let _key = unimplemented!(); + em.reg[x] = _key; + } + 0x15 => { // set delay timer = Vx + em.delay_t = em.reg[x]; + } + 0x18 => { // set sound timer = Vx + em.sound_t = em.reg[x]; + } + 0x1E => { // Add Vx to I and store it in I + em.i += em.reg[x] as u16; + } + 0x29 => { // set I to location for the sprite with value Vx + unimplemented!() + } + 0x33 => { // store a decimal representation of Vx at I, I+1, I+2 + // convert Vx to decimal and store the digits, 100 -> I + unimplemented!() + } + 0x55 => { // store registers V0 to Vx in memory I + for i in 0..x as u16 { + em.memory[(em.i + i) as usize] = em.reg[i as usize]; + } + } + 0x65 => { // load V0 to Vx from memory at I + for i in 0..x as u16 { + em.reg[i as usize] = em.memory[(em.i + i) as usize] + } + } + _ => unreachable!("invalid instruction") + } + } + + _ => unimplemented!() + } +} + + +fn extract_0xkk(value: u16) -> (usize, u8) { + let x = value & 0x0F00; + let kk = (value & 0x00FF) as u8; + (x as usize, kk) +} + +fn extract_0xy0(value: u16) -> (usize, usize) { + let x = value & 0x0F00; + let y = value & 0x00F0; + (x as usize, y as usize) +} + +fn extract_0xyz(value: u16) -> (usize, usize, u16) { + let x = value & 0x0F00; + let y = value & 0x00F0; + let z = value & 0x000F; + (x as usize, y as usize, z) +} + +fn extract_0xii(value: u16) -> (usize, u8) { + let x = value & 0x0F00; + let ii = (value & 0x00FF) as u8; + (x as usize, ii) +} + +fn extract_0nnn(value: u16) -> u16 { + value & 0xFFF +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e7a11a9..8ee9923 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,8 @@ +use crate::interpreter::{run}; + +mod interpreter; + fn main() { println!("Hello, world!"); -} + run(&[]) +} \ No newline at end of file