Implement CHIP-8 Opcodes

Warning: There are currently no tests and I'm pretty sure none of them
work well /shrug
This commit is contained in:
Rekai Musuka 2020-06-25 01:40:09 -05:00
parent 632eab5e34
commit cb030b992d
3 changed files with 572 additions and 23 deletions

79
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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<u8>,
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<P: AsRef<Path>>(&mut self, path: P) -> Result<(), io::Error> {
let mut file = File::open(path.as_ref())?;
let mut rom_buf: Vec<u8> = 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::<u8>() & 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<Item = u8> {
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<u8> for Digits {
fn from(num: u8) -> Self {
Digits { num, flag: true }
}
}
impl Iterator for Digits {
type Item = u8;
fn next(&mut self) -> Option<u8> {
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");
}
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));
}
}