From 953cc4aac6104957a8e8bb7688ec9f6c9f948d8e Mon Sep 17 00:00:00 2001 From: Daniel Bulant Date: Wed, 18 Jan 2023 22:00:00 +0100 Subject: [PATCH] initial version --- .gitignore | 1 + .idea/.gitignore | 8 ++ .idea/misc.xml | 6 + .idea/modules.xml | 8 ++ .idea/untitled.iml | 11 ++ .idea/vcs.xml | 7 + Cargo.lock | 75 +++++++++++ Cargo.toml | 9 ++ src/main.rs | 329 +++++++++++++++++++++++++++++++++++++++++++++ src/opcodes.rs | 5 + 10 files changed, 459 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/untitled.iml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/opcodes.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..3ce3588 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..aeb7613 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/untitled.iml b/.idea/untitled.iml new file mode 100644 index 0000000..c254557 --- /dev/null +++ b/.idea/untitled.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..ca9a474 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2f711e9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,75 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chip8" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e955530 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "chip8" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.5" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c19c3a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,329 @@ +use std::env::args; +use std::fs::File; +use std::io::Read; +use std::time::{Duration, Instant}; +use rand::prelude::*; + +mod opcodes; + + +fn main() { + let file_path = args().nth(1); + match file_path { + None => panic!("No file path"), + Some(file_path) => { + let mut file = File::open(file_path).unwrap(); + let mut data = Vec::new(); + let len = file.read_to_end(&mut data).unwrap(); + + let mut chip8 = Chip8::new(); + chip8.load_rom(data); + loop { + chip8.run_next(); + } + } + } +} + +/// 12 bit address pointer +type Address = u16; + +#[derive(Debug)] +struct Chip8 { + /// 4K memory + memory: [u8; 4096], + /// registers + v: [u8; 16], + /// current address + i: Address, + /// program counter + pc: Address, + /// stack (return addresses) + stack: Vec
, + /// delay timer (60Hz, when non-zero, decremented at 60Hz) + delay_timer: u8, + /// sound timer (when non-zero, beeps, and decrements at 60Hz) + sound_timer: u8, + /// graphics (64x32 black and white) + display: [bool; 64 * 32], + start_time: Instant, + last_processed_timers: Instant +} + +const WIDTH: usize = 32; +const HEIGHT: usize = 64; + +impl Chip8 { + fn new() -> Chip8 { + let mut c = Chip8 { + memory: [0; 4096], + v: [0; 16], + i: 0, + pc: 0x100, + stack: Vec::new(), + delay_timer: 0, + sound_timer: 0, + display: [false; 64 * 32], + start_time: Instant::now(), + last_processed_timers: Instant::now() + }; + + // initialize font + c.memory[0..80].clone_from_slice(&[ + 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 + 0x20, 0x60, 0x20, 0x20, 0x70, // 1 + 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 + 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 + 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 + 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 + 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 + 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 + 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 + 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 + 0xF0, 0x90, 0xF0, 0x90, 0x90, // A + 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B + 0xF0, 0x80, 0x80, 0x80, 0xF0, // C + 0xE0, 0x90, 0x90, 0x90, 0xE0, // D + 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E + 0xF0, 0x80, 0xF0, 0x80, 0x80 // F + ]); + + c + } + + fn load_rom(&mut self, rom: Vec) { + self.memory[0x200..0x200+rom.len()].clone_from_slice(&*rom); + } + + fn run_next(&mut self) { + let current = (self.memory[self.pc as usize * 2] as u16) << 8 | (self.memory[self.pc as usize * 2 + 1] as u16); + let first = ((current & 0xF000) as u16 >> 12) as u8; + let first_raw = (current & 0xF000) as u16; + let second = ((current & 0x0F00 as u16) >> 8 as u16) as u8; + let second_raw = (current & 0x0F00) as u16; + let third = ((current & 0x00F0) as u16 >> 4) as u8; + let third_raw = (current & 0x00F0) as u16; + let fourth = (current & 0x000F) as u8; + let fourth_raw = (current & 0x000F) as u16; + let last_two = (third_raw|fourth_raw) as u8; + let after_first = current ^ (first_raw as Address); + + print!("{:#x}", self.pc); + + if current == 0 { + dbg!(&self); + panic!("Null call {}", self.pc); + } + + // println!("{current:#04x} -> F{first_raw:#x} S{second_raw:#x} T{third_raw:#x} F{fourth_raw:#x} - !F={after_first:#x} {last_two:#x}"); + + if first == 0 { + // syscalls + if current == 0x00e0 { + println!("CLS"); + self.display = [false; 64 * 32]; + } else if current == 0x00ee { + if self.stack.is_empty() { + panic!("Stack underflow"); + } + self.pc = self.stack.pop().unwrap(); + return; + } else { + println!("SYS {}", after_first); + } + } else if first == 1 { + // jump + println!("JP {after_first:#x} ({after_first})"); + if after_first / 2 == self.pc { + panic!("HALT! Jump to self (direct infinite loop)"); + } + self.pc = after_first / 2; + return; + } else if first == 2 { + // call + println!("CALL {}", after_first); + self.stack.push(self.pc); + self.pc = after_first; + } else if first == 3 { + // if eq + println!("SE IF V{second} == {}", last_two); + if self.v[second as usize] != last_two { + self.pc += 1; + } + } else if first == 4 { + // if not eq + println!("SNE IF V{second} != {}", last_two); + if self.v[second as usize] == last_two { + self.pc += 1; + } + } else if first == 5 { + // if Vx == Vy + if self.v[second as usize] != self.v[third as usize] { + self.pc += 1; + } + } else if first == 6 { + // LOAD; Vx = NN + println!("LD V{second} = {last_two:#x} ({last_two})"); + self.v[second as usize] = last_two; + } else if first == 7 { + // ADD; Vx += NN + println!("ADD V{second} += {last_two:#x} ({last_two})"); + self.v[second as usize] += last_two; + } else if first == 8 { + println!("eights"); + if fourth == 0 { + self.v[second as usize] = self.v[third as usize]; + } else if fourth == 1 { + self.v[second as usize] |= self.v[third as usize]; + } else if fourth == 2 { + self.v[second as usize] &= self.v[third as usize]; + } else if fourth == 3 { + self.v[second as usize] ^= self.v[third as usize]; + } else if fourth == 4 { + self.v[second as usize] += self.v[third as usize]; + // TODO! Carry implementation + } else if fourth == 5 { + self.v[second as usize] -= self.v[third as usize]; + // TODO! Borrow implementation + } else if fourth == 6 { + self.v[0xF] = self.v[second as usize] & 1; + self.v[second as usize] >>= 1; + } else if fourth == 7 { + self.v[second as usize] = self.v[third as usize] - self.v[second as usize]; + } else if fourth == 0xE { + self.v[0xF] = if (self.v[second as usize] & 0b10000000) != 0 { 1 } else { 0}; + self.v[second as usize] <<= 1; + } + } else if first == 9 { + // if Vx != Vy + println!("IF V{second} != V{third}"); + if self.v[second as usize] == self.v[third as usize] { + self.pc += 1; + } + } else if first == 0xA { + // I=NNN + println!("I={after_first:#x} ({after_first})"); + self.i = after_first; + } else if first == 0xB { + println!("JP+ {} {after_first:#x} ({after_first})", self.v[0]); + self.pc = (self.v[0] as Address) + after_first; + return; + } else if first == 0xC { + // I=rand() & NN + println!("RND V{second} = random & {last_two:#x} ({last_two} {last_two:#b})"); + let random: u8 = random(); + self.v[second as usize] = random & last_two; + } else if first == 0xD { + // draw(Vx, Vy, N) + + // draws at Vx, Vy, width 8, height N + // sprite (bit coded XOR (set bits flip the bit value)) from I + // VF set to 1 if any bit is set to 0 + + println!("DRW X=V{second} Y=V{third} W=16 H={fourth}"); + + let x = self.v[second as usize] % (WIDTH as u8); + let y = self.v[third as usize] % (HEIGHT as u8); + let sprite_height = fourth as usize; + + self.v[0xF] = 0; + + for sprite_y in 1..sprite_height+1 { + let sprite_y = (sprite_height - sprite_y + y as usize) % (HEIGHT); + let sprite_row = self.memory[self.i as usize + sprite_y]; + println!("\n{:#b} ({:#x})", sprite_row, sprite_row); + for b in 0..8 { + let mut pos: usize = (sprite_y as usize * WIDTH) + ((b as usize) + (x as usize) % WIDTH as usize); + if (x as i16) + (b as i16) < 0 { pos += WIDTH - 1 } + // let orig = self.display[pos]; + self.display[pos] ^= (sprite_row & (1 << (7-b))) > 0; + // println!("pos{} x{} y{}", pos, pos % WIDTH, pos / WIDTH); + // println!("x{x} + y{y}*16 + {b} = {pos} .. {:#010b} = {:#010b}", 1 << (7-b), (memory & (1 << (7-b)))); + // print!("{}", if (sprite_row & (1 << (7-b))) > 0 { "█" } else { " " }); + // println!(""); + if self.v[0xF] == 0 { + self.v[0xF] = if self.display[pos] && (sprite_row & (1 << (7-b))) > 0 { 1 } else { 0 }; + } + } + } + + for x in 0..WIDTH { + for y in 0..HEIGHT { + print!("{}", if self.display[y*WIDTH + x] { "█" } else { " " }); + } + println!(); + } + } else if first == 0xE { + // Keyboard operations + + // TODO! implement keyboard + + if last_two == 0x9E { + // if key() == Vx + println!("key() == V{last_two}"); + } else if last_two == 0xA1 { + // if key() != Vx + println!("key() != V{last_two}"); + } + } else if first == 0xF { + if last_two == 0x07 { + // Vx = get_delay() + println!("V{second} = delay()"); + self.v[second as usize] = self.delay_timer; + } else if last_two == 0x0A { + // Vx = get_key() + println!("V{second} = key()"); + self.v[second as usize] = 0; // TODO! implement keyboard + } else if last_two == 0x15 { + println!("delay(V{second})"); + self.delay_timer = self.v[second as usize]; + } else if last_two == 0x18 { + println!("sound({second})"); + self.sound_timer = self.v[second as usize]; + } else if last_two == 0x1E { + println!("I += V{second}"); + self.i += self.v[second as usize] as u16; + } else if last_two == 0x29 { + // I = sprite_addr[Vx] + // Characters 0x0-0xF are represented by a 4x5 font + println!("I=sprite_addr[V{second}]"); + self.i = (4 * 5 * second) as Address; + } else if last_two == 0x33 { + // Stores binary-coded decimal representation of Vx, hundreds digit at I, tens digit at I+1 and ones digit at I+2 + let num = self.v[second as usize]; + self.memory[self.i as usize] = num / 100; + self.memory[self.i as usize + 1] = num / 10 % 100; + self.memory[self.i as usize + 2] = num % 10; + println!("encoded I = V{second}"); + } else if last_two == 0x55 { + // reg_dump(Vx, &I) + println!("reg_dump(V{second}, I)"); + if (self.i+second as u16) as usize >= self.memory.len() { + panic!("overflow with reg_dump"); + } + self.memory[self.i as usize..second as usize].clone_from_slice(&self.v[0..second as usize]) + } else if last_two == 0x65 { + // reg_load(Vx, &I) + println!("reg_load(V{second}, I)"); + if (self.i+second as u16) as usize >= self.memory.len() { + panic!("overflow with reg_load"); + } + self.v[0..second as usize].clone_from_slice(&self.memory[self.i as usize..second as usize]); + } + } + self.pc += 1; + if self.pc as usize > self.memory.len() { + panic!("Halted (Program counter over ROM length)"); + } + let now = Instant::now(); + if now - self.last_processed_timers > Duration::from_millis(16) { + if self.sound_timer > 0 { + print!("\x07"); + self.sound_timer -= 1; + } + if self.delay_timer > 0 { + self.delay_timer -= 1; + } + } + } +} \ No newline at end of file diff --git a/src/opcodes.rs b/src/opcodes.rs new file mode 100644 index 0000000..1b2e142 --- /dev/null +++ b/src/opcodes.rs @@ -0,0 +1,5 @@ + +pub enum Opcode { + DisplayClear +} +