Implement opcodes, restructure
This commit is contained in:
parent
3b23c8c5b1
commit
e374d5f4ec
|
@ -51,6 +51,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
name = "chip-8"
|
name = "chip-8"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"rand",
|
||||||
"sdl2",
|
"sdl2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -105,6 +106,17 @@ dependencies = [
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -133,6 +145,52 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
@ -214,6 +272,12 @@ version = "0.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1"
|
checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
|
@ -7,4 +7,5 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rand = "0.8.3"
|
||||||
sdl2 = { version = "0.34.2", features = ["bundled"] }
|
sdl2 = { version = "0.34.2", features = ["bundled"] }
|
||||||
|
|
|
@ -1 +1,30 @@
|
||||||
|
pub const INSTRUCTION_LENGTH: u16 = 2;
|
||||||
|
|
||||||
|
pub struct Instruction {
|
||||||
|
pub nibbles: (u8, u8, u8, u8),
|
||||||
|
pub addr: u16,
|
||||||
|
pub byte: u8,
|
||||||
|
pub x: usize,
|
||||||
|
pub y: usize,
|
||||||
|
pub nibble: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instruction {
|
||||||
|
pub fn parse(instruction: u16) -> Self {
|
||||||
|
let nibbles = (
|
||||||
|
((instruction & 0xF000) >> 12) as u8,
|
||||||
|
((instruction & 0x0F00) >> 8) as u8,
|
||||||
|
((instruction & 0x00F0) >> 4) as u8,
|
||||||
|
(instruction & 0x000F) as u8,
|
||||||
|
);
|
||||||
|
|
||||||
|
Instruction {
|
||||||
|
nibbles,
|
||||||
|
addr: (instruction & 0x0FFF),
|
||||||
|
nibble: nibbles.3,
|
||||||
|
byte: (instruction & 0x00FF) as u8,
|
||||||
|
x: nibbles.1 as usize,
|
||||||
|
y: nibbles.2 as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,9 +32,7 @@ impl Screen {
|
||||||
if b & 0b10000000 >> lx > 0 {
|
if b & 0b10000000 >> lx > 0 {
|
||||||
let dx = (x + lx) % WIDTH;
|
let dx = (x + lx) % WIDTH;
|
||||||
let dy = (y + ly) % HEIGHT;
|
let dy = (y + ly) % HEIGHT;
|
||||||
if !pixel_collission && self.is_pixel_set(dx, dy) {
|
pixel_collission = pixel_collission || self.is_pixel_set(dx, dy);
|
||||||
pixel_collission = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pixel_set(dx, dy);
|
self.pixel_set(dx, dy);
|
||||||
}
|
}
|
||||||
|
@ -43,6 +41,10 @@ impl Screen {
|
||||||
|
|
||||||
pixel_collission
|
pixel_collission
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.pixels = [[false; WIDTH]; HEIGHT];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use crate::chip8::audio::Speaker;
|
use crate::chip8::audio::Speaker;
|
||||||
|
use crate::chip8::cpu::Instruction;
|
||||||
use crate::chip8::display::Screen;
|
use crate::chip8::display::Screen;
|
||||||
use crate::chip8::keyboard::Keyboard;
|
use crate::chip8::keyboard::Keyboard;
|
||||||
use crate::chip8::memory::{self, Memory};
|
use crate::chip8::memory::{self, Memory};
|
||||||
use crate::chip8::registers::{Register::*, Registers};
|
use crate::chip8::registers::Registers;
|
||||||
use crate::chip8::stack::Stack;
|
use crate::chip8::stack::Stack;
|
||||||
|
|
||||||
|
use rand;
|
||||||
use sdl2::AudioSubsystem;
|
use sdl2::AudioSubsystem;
|
||||||
|
|
||||||
use std::{fs, thread, time::Duration};
|
use std::{fs, thread, time::Duration};
|
||||||
|
@ -31,20 +33,18 @@ impl Chip8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_delay_timer(&mut self) -> () {
|
pub fn handle_delay_timer(&mut self) -> () {
|
||||||
let delay_timer = self.registers.get(DT);
|
if self.registers.get_dt() > 0 {
|
||||||
if delay_timer > 0 {
|
|
||||||
thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
||||||
self.registers.set(DT, delay_timer - 1);
|
self.registers.dec_dt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_sound_timer(&mut self) {
|
pub fn handle_sound_timer(&mut self) {
|
||||||
let sound_timer = self.registers.get(ST);
|
let status = self.registers.get_st() > 0;
|
||||||
let status = sound_timer > 0;
|
|
||||||
self.speaker.beep(status);
|
self.speaker.beep(status);
|
||||||
if status {
|
if status {
|
||||||
thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
||||||
self.registers.set(ST, sound_timer - 1);
|
self.registers.dec_st();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,9 +64,268 @@ impl Chip8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec(&mut self) {
|
pub fn exec(&mut self) {
|
||||||
let pc = self.registers.get(PC);
|
let pc = self.registers.get_pc();
|
||||||
let opcode = self.memory.read_opcode(pc as usize);
|
let opcode = self.memory.read_opcode(pc as usize);
|
||||||
println!("{}", opcode);
|
let instruction = Instruction::parse(opcode);
|
||||||
self.registers.set(PC, pc + 2);
|
self.registers.advance_pc();
|
||||||
|
|
||||||
|
match instruction.nibbles {
|
||||||
|
// CLS: clear the display
|
||||||
|
(0x00, 0x00, 0x0E, 0x00) => self.screen.clear(),
|
||||||
|
|
||||||
|
// RET: return from subroutine
|
||||||
|
(0x00, 0x00, 0x0E, 0x0E) => {
|
||||||
|
let new_pc = self.stack.pop(&mut self.registers);
|
||||||
|
self.registers.set_pc(new_pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// JP addr: jump to location addr
|
||||||
|
(0x01, _, _, _) => self.registers.set_pc(instruction.addr),
|
||||||
|
|
||||||
|
// CALL addr: call subroutine at addr
|
||||||
|
(0x02, _, _, _) => {
|
||||||
|
let pc = self.registers.get_pc();
|
||||||
|
self.stack.push(&mut self.registers, pc);
|
||||||
|
self.registers.set_pc(instruction.addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SE Vx, byte: skip next instruction if Vx = byte
|
||||||
|
(0x03, _, _, _) => {
|
||||||
|
let x = self.registers.get_v(instruction.x);
|
||||||
|
if x == instruction.byte {
|
||||||
|
self.registers.advance_pc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SNE Vx, byte: skip next instruction if Vx != byte
|
||||||
|
(0x04, _, _, _) => {
|
||||||
|
let x = self.registers.get_v(instruction.x);
|
||||||
|
if x != instruction.byte {
|
||||||
|
self.registers.advance_pc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SE Vx, Vy: skip next instruction if Vx = Vy
|
||||||
|
(0x05, _, _, 0x00) => {
|
||||||
|
let x = self.registers.get_v(instruction.x);
|
||||||
|
let y = self.registers.get_v(instruction.y);
|
||||||
|
|
||||||
|
if x == y {
|
||||||
|
self.registers.advance_pc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD Vx, byte: set Vx = byte
|
||||||
|
(0x06, _, _, _) => {
|
||||||
|
let register = instruction.x;
|
||||||
|
let value = instruction.byte;
|
||||||
|
self.registers.set_v(register, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD Vx, byte: set Vx = Vx + byte
|
||||||
|
(0x07, _, _, _) => {
|
||||||
|
let register = instruction.x;
|
||||||
|
let value = self.registers.get_v(register) as u16;
|
||||||
|
let new_value = value + instruction.byte as u16;
|
||||||
|
self.registers.set_v(register, new_value as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD Vx, Vy: set Vx = Vy
|
||||||
|
(0x08, _, _, 0x00) => {
|
||||||
|
let y = self.registers.get_v(instruction.y);
|
||||||
|
self.registers.set_v(instruction.x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OR Vx, Vy: set Vx = Vx OR Vy
|
||||||
|
(0x08, _, _, 0x01) => {
|
||||||
|
let x = self.registers.get_v(instruction.x);
|
||||||
|
let y = self.registers.get_v(instruction.y);
|
||||||
|
self.registers.set_v(instruction.x, x | y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AND Vx, Vy: set Vx = V AND Vy
|
||||||
|
(0x08, _, _, 0x02) => {
|
||||||
|
let x = self.registers.get_v(instruction.x);
|
||||||
|
let y = self.registers.get_v(instruction.y);
|
||||||
|
self.registers.set_v(instruction.x, x & y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XOR Vx, Vy: set Vx = Vx XOR Vy
|
||||||
|
(0x08, _, _, 0x03) => {
|
||||||
|
let x = self.registers.get_v(instruction.x);
|
||||||
|
let y = self.registers.get_v(instruction.y);
|
||||||
|
self.registers.set_v(instruction.x, x ^ y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD Vx, Vy: set Vx = Vx + Vy, set VF = carry
|
||||||
|
(0x08, _, _, 0x04) => {
|
||||||
|
let x = self.registers.get_v(instruction.x) as u16;
|
||||||
|
let y = self.registers.get_v(instruction.y) as u16;
|
||||||
|
let result = x + y;
|
||||||
|
|
||||||
|
self.registers.set_carry_if(result > 255);
|
||||||
|
self.registers.set_v(instruction.x, result as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SUB Vx, Vy: set Vx = Vx - Vy, set VF = NOT borrow
|
||||||
|
(0x08, _, _, 0x05) => {
|
||||||
|
let x = self.registers.get_v(instruction.x);
|
||||||
|
let y = self.registers.get_v(instruction.y);
|
||||||
|
|
||||||
|
self.registers.set_carry_if(x > y);
|
||||||
|
self.registers.set_v(instruction.x, x.wrapping_sub(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHR Vx {, Vy}: set Vx = Vx SHR 1o
|
||||||
|
(0x08, _, _, 0x06) => {
|
||||||
|
let x = self.registers.get_v(instruction.x);
|
||||||
|
self.registers.set_carry_if(x & 1 == 1);
|
||||||
|
self.registers.set_v(instruction.x, x >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SUBN Vx, Vy: set Vx = Vy - Vx, set VF = NOT borrow
|
||||||
|
(0x08, _, _, 0x07) => {
|
||||||
|
let x = self.registers.get_v(instruction.x);
|
||||||
|
let y = self.registers.get_v(instruction.y);
|
||||||
|
|
||||||
|
self.registers.set_carry_if(y > x);
|
||||||
|
self.registers.set_v(instruction.x, y.wrapping_sub(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHL Vx {, Vy}: set Vx = Vx SHL 1
|
||||||
|
(0x08, _, _, 0x0E) => {
|
||||||
|
let x = self.registers.get_v(instruction.x);
|
||||||
|
let msb = 1 << 7;
|
||||||
|
|
||||||
|
self.registers.set_carry_if(x & msb == 1);
|
||||||
|
self.registers.set_v(instruction.x, x << 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SNE Vx, Vy: skip next instruction if Vx != Vy
|
||||||
|
(0x09, _, _, 0x00) => {
|
||||||
|
let x = self.registers.get_v(instruction.x);
|
||||||
|
let y = self.registers.get_v(instruction.y);
|
||||||
|
|
||||||
|
if x != y {
|
||||||
|
self.registers.advance_pc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD I, addr: set I = addr
|
||||||
|
(0x0A, _, _, _) => self.registers.set_i(instruction.addr),
|
||||||
|
|
||||||
|
// JP V0, addr: jump to location nnn + V0
|
||||||
|
(0x0B, _, _, _) => {
|
||||||
|
self.registers
|
||||||
|
.set_pc(self.registers.get_v(0) as u16 + instruction.addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RND Vx, byte: et Vx = random byte AND kk.
|
||||||
|
(0x0C, _, _, _) => {
|
||||||
|
let n: u8 = rand::random();
|
||||||
|
self.registers.set_v(instruction.x, n & instruction.byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
//DRW Vx, Vy, nibble: display n-byte sprite starting at memory
|
||||||
|
// location I at (Vx, Vy), set VF = collision.
|
||||||
|
(0x0D, _, _, _) => {
|
||||||
|
let x = self.registers.get_v(instruction.x) as usize;
|
||||||
|
let y = self.registers.get_v(instruction.y) as usize;
|
||||||
|
let start = self.registers.get_i() as usize;
|
||||||
|
let sprite = &self.memory.read(start, instruction.nibble);
|
||||||
|
|
||||||
|
let collission = self.screen.draw_sprite(x, y, sprite);
|
||||||
|
self.registers.set_carry_if(collission);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SKP Vx: skip next instruction if key with the value of Vx is
|
||||||
|
// pressed
|
||||||
|
(0x0E, _, 0x09, 0x0E) => {
|
||||||
|
let x = self.registers.get_v(instruction.x) as usize;
|
||||||
|
if self.keyboard.is_key_down(x) {
|
||||||
|
self.registers.advance_pc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SKNP Vx: skip next instruction if key with the value of Vx is
|
||||||
|
// not pressed
|
||||||
|
(0x0E, _, 0x0A, 0x01) => {
|
||||||
|
let x = self.registers.get_v(instruction.x) as usize;
|
||||||
|
if !self.keyboard.is_key_down(x) {
|
||||||
|
self.registers.advance_pc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD Vx, DT: set Vx = delay timer value
|
||||||
|
(0x0F, _, 0x00, 0x07) => {
|
||||||
|
self.registers.set_v(instruction.x, self.registers.get_dt());
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD Vx, K: wait for a key press, store the value of the key in V
|
||||||
|
(0x0F, _, 0x00, 0x0A) => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD DT, Vx
|
||||||
|
(0x0F, _, 0x01, 0x05) => {
|
||||||
|
self.registers.set_dt(self.registers.get_v(instruction.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD ST, Vx: set sound timer = Vx
|
||||||
|
(0x0F, _, 0x01, 0x08) => {
|
||||||
|
self.registers.set_st(self.registers.get_v(instruction.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD I, Vx: set I = I + Vx
|
||||||
|
(0x0F, _, 0x01, 0x0E) => {
|
||||||
|
let i = self.registers.get_i();
|
||||||
|
let x = self.registers.get_v(instruction.x) as u16;
|
||||||
|
let result = (i + x) as u16;
|
||||||
|
self.registers.set_i(result);
|
||||||
|
self.registers.set_carry_if(result > (1 << 15));
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD F, Vx: set I = location of sprite for digit Vx
|
||||||
|
(0x0F, _, 0x02, 0x09) => {
|
||||||
|
let x = self.registers.get_v(instruction.x) as u16;
|
||||||
|
self.registers.set_i(x * 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD B, Vx: store BCD representation of Vx in memory locations I,
|
||||||
|
// I+1, and I+2.
|
||||||
|
(0x0F, _, 0x03, 0x03) => {
|
||||||
|
let x = self.registers.get_v(instruction.x) as u16;
|
||||||
|
let i = self.registers.get_i() as usize;
|
||||||
|
|
||||||
|
self.memory.set(i, (x / 100) as u8);
|
||||||
|
self.memory.set(i + 1, ((x % 100) / 10) as u8);
|
||||||
|
self.memory.set(i + 2, (x % 10) as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD [I], Vx: store registers V0 through Vx in memory starting at
|
||||||
|
// location I.
|
||||||
|
(0x0F, _, 0x05, 0x05) => {
|
||||||
|
let x = self.registers.get_v(instruction.x) as usize;
|
||||||
|
let i = self.registers.get_i() as usize;
|
||||||
|
|
||||||
|
for n in 0..=x {
|
||||||
|
self.memory.set(i + n, self.registers.get_v(n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LD Vx, [I]: read registers V0 through Vx from memory starting at
|
||||||
|
// location I
|
||||||
|
(0x0F, _, 0x06, 0x05) => {
|
||||||
|
let x = self.registers.get_v(instruction.x) as usize;
|
||||||
|
let i = self.registers.get_i() as usize;
|
||||||
|
|
||||||
|
for n in 0..x {
|
||||||
|
let value = self.memory.get(i + n);
|
||||||
|
self.registers.set_v(n, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => println!("Invalid instruction"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_maps_physical_keys_to_virtual_ones() {
|
fn it_maps_physical_keys_to_virtual_ones() {
|
||||||
let mut keyboard = Keyboard::new();
|
let keyboard = Keyboard::new();
|
||||||
assert_eq!(keyboard.map(Keycode::A), Some(7));
|
assert_eq!(keyboard.map(Keycode::A), Some(7));
|
||||||
assert_eq!(keyboard.map(Keycode::X), Some(0));
|
assert_eq!(keyboard.map(Keycode::X), Some(0));
|
||||||
assert_eq!(keyboard.map(Keycode::M), None);
|
assert_eq!(keyboard.map(Keycode::M), None);
|
||||||
|
|
|
@ -1,68 +1,21 @@
|
||||||
|
use super::cpu;
|
||||||
use super::memory;
|
use super::memory;
|
||||||
|
|
||||||
const DATA_REGISTERS: usize = 16;
|
const DATA_REGISTERS: usize = 16;
|
||||||
|
|
||||||
pub enum Register {
|
|
||||||
V0,
|
|
||||||
V1,
|
|
||||||
V2,
|
|
||||||
V3,
|
|
||||||
V4,
|
|
||||||
V5,
|
|
||||||
V6,
|
|
||||||
V7,
|
|
||||||
V8,
|
|
||||||
V9,
|
|
||||||
VA,
|
|
||||||
VB,
|
|
||||||
VC,
|
|
||||||
VD,
|
|
||||||
VE,
|
|
||||||
VF,
|
|
||||||
I,
|
|
||||||
DT,
|
|
||||||
ST,
|
|
||||||
PC,
|
|
||||||
SP,
|
|
||||||
}
|
|
||||||
|
|
||||||
use Register::*;
|
|
||||||
|
|
||||||
fn data_register_to_index(register: Register) -> usize {
|
|
||||||
match register {
|
|
||||||
V0 => 0,
|
|
||||||
V1 => 1,
|
|
||||||
V2 => 2,
|
|
||||||
V3 => 3,
|
|
||||||
V4 => 4,
|
|
||||||
V5 => 5,
|
|
||||||
V6 => 6,
|
|
||||||
V7 => 7,
|
|
||||||
V8 => 8,
|
|
||||||
V9 => 9,
|
|
||||||
VA => 10,
|
|
||||||
VB => 11,
|
|
||||||
VC => 12,
|
|
||||||
VD => 13,
|
|
||||||
VE => 14,
|
|
||||||
VF => 15,
|
|
||||||
_ => panic!("invalid data register"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Registers {
|
pub struct Registers {
|
||||||
data: [u8; DATA_REGISTERS],
|
v: [u8; DATA_REGISTERS],
|
||||||
i: u16,
|
i: u16,
|
||||||
delay_timer: u8,
|
delay_timer: u8,
|
||||||
sound_timer: u8,
|
sound_timer: u8,
|
||||||
pc: u16,
|
pc: u16,
|
||||||
pub sp: u8,
|
sp: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Registers {
|
impl Registers {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Registers {
|
Registers {
|
||||||
data: [0; DATA_REGISTERS],
|
v: [0; DATA_REGISTERS],
|
||||||
i: 0,
|
i: 0,
|
||||||
delay_timer: 0,
|
delay_timer: 0,
|
||||||
sound_timer: 0,
|
sound_timer: 0,
|
||||||
|
@ -71,26 +24,72 @@ impl Registers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&mut self, register: Register, value: u16) -> () {
|
pub fn get_v(&self, n: usize) -> u8 {
|
||||||
match register {
|
self.v[n]
|
||||||
I => self.i = value,
|
|
||||||
DT => self.delay_timer = value as u8,
|
|
||||||
ST => self.sound_timer = value as u8,
|
|
||||||
PC => self.pc = value,
|
|
||||||
SP => self.sp = value as u8,
|
|
||||||
_ => self.data[data_register_to_index(register)] = value as u8,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, register: Register) -> u16 {
|
pub fn set_v(&mut self, n: usize, value: u8) {
|
||||||
match register {
|
self.v[n] = value;
|
||||||
I => self.i,
|
|
||||||
DT => self.delay_timer as u16,
|
|
||||||
ST => self.sound_timer as u16,
|
|
||||||
PC => self.pc,
|
|
||||||
SP => self.sp as u16,
|
|
||||||
_ => self.data[data_register_to_index(register)] as u16,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_i(&self) -> u16 {
|
||||||
|
self.i
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_i(&mut self, addr: u16) {
|
||||||
|
self.i = addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dt(&self) -> u8 {
|
||||||
|
self.delay_timer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_dt(&mut self, value: u8) {
|
||||||
|
self.delay_timer = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dec_dt(&mut self) {
|
||||||
|
self.delay_timer -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_st(&self) -> u8 {
|
||||||
|
self.sound_timer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_st(&mut self, value: u8) {
|
||||||
|
self.sound_timer = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dec_st(&mut self) {
|
||||||
|
self.sound_timer -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pc(&self) -> u16 {
|
||||||
|
self.pc
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pc(&mut self, value: u16) {
|
||||||
|
self.pc = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance_pc(&mut self) {
|
||||||
|
self.pc += cpu::INSTRUCTION_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sp(&self) -> u8 {
|
||||||
|
self.sp
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inc_sp(&mut self) {
|
||||||
|
self.sp += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dec_sp(&mut self) {
|
||||||
|
self.sp -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_carry_if(&mut self, condition: bool) {
|
||||||
|
self.v[0xf] = if condition { 1 } else { 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,34 +99,34 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_has_the_correct_number_of_data_registers() {
|
fn it_has_the_correct_number_of_data_registers() {
|
||||||
assert_eq!(Registers::new().data.len(), DATA_REGISTERS);
|
assert_eq!(Registers::new().v.len(), DATA_REGISTERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_can_write_data_registers() {
|
fn it_can_write_data_registers() {
|
||||||
let mut registers = Registers::new();
|
let mut registers = Registers::new();
|
||||||
registers.set(VA, 42);
|
registers.set_v(0xA, 42);
|
||||||
assert_eq!(registers.data[10], 42);
|
assert_eq!(registers.v[0xA], 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_can_read_data_registers() {
|
fn it_can_read_data_registers() {
|
||||||
let mut registers = Registers::new();
|
let mut registers = Registers::new();
|
||||||
registers.set(VA, 42);
|
registers.set_v(0xA, 42);
|
||||||
assert_eq!(registers.get(VA), 42);
|
assert_eq!(registers.get_v(0xA), 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_can_write_special_registers() {
|
fn it_can_write_special_registers() {
|
||||||
let mut registers = Registers::new();
|
let mut registers = Registers::new();
|
||||||
registers.set(PC, 42);
|
registers.set_pc(42);
|
||||||
assert_eq!(registers.pc, 42);
|
assert_eq!(registers.pc, 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_can_read_special_registers() {
|
fn it_can_read_special_registers() {
|
||||||
let mut registers = Registers::new();
|
let mut registers = Registers::new();
|
||||||
registers.set(PC, 42);
|
registers.set_pc(42);
|
||||||
assert_eq!(registers.get(PC), 42);
|
assert_eq!(registers.get_pc(), 42);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::chip8::registers::Registers;
|
||||||
|
|
||||||
pub const STACK_DEPTH: usize = 16;
|
pub const STACK_DEPTH: usize = 16;
|
||||||
|
|
||||||
pub struct Stack {
|
pub struct Stack {
|
||||||
|
@ -11,39 +13,42 @@ impl Stack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, stack_pointer: &mut u8, value: u16) -> () {
|
pub fn push(&mut self, registers: &mut Registers, value: u16) {
|
||||||
assert!((*stack_pointer as usize) < STACK_DEPTH, "stack overflow");
|
let stack_pointer = registers.get_sp() as usize;
|
||||||
self.stack[*stack_pointer as usize] = value;
|
assert!(stack_pointer < STACK_DEPTH, "stack overflow");
|
||||||
*stack_pointer += 1;
|
self.stack[stack_pointer] = value;
|
||||||
|
registers.inc_sp();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop(&mut self, stack_pointer: &mut u8) -> u16 {
|
pub fn pop(&mut self, registers: &mut Registers) -> u16 {
|
||||||
assert!((*stack_pointer as usize) > 0, "stack underflow");
|
let stack_pointer = registers.get_sp() as usize;
|
||||||
*stack_pointer -= 1;
|
assert!(stack_pointer > 0, "stack underflow");
|
||||||
assert!((*stack_pointer as usize) < STACK_DEPTH, "stack overflow");
|
registers.dec_sp();
|
||||||
self.stack[*stack_pointer as usize]
|
assert!(stack_pointer < STACK_DEPTH, "stack overflow");
|
||||||
|
self.stack[registers.get_sp() as usize]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::chip8::registers::{Register::*, Registers};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_can_push_to_and_pop_from_the_stack() {
|
fn it_can_push_to_and_pop_from_the_stack() {
|
||||||
let mut registers = Registers::new();
|
let mut registers = Registers::new();
|
||||||
assert_eq!(registers.get(SP), 0);
|
assert_eq!(registers.get_sp(), 0);
|
||||||
|
|
||||||
let mut stack = Stack::new();
|
let mut stack = Stack::new();
|
||||||
stack.push(&mut registers.sp, 0xff);
|
stack.push(&mut registers, 0xff);
|
||||||
assert_eq!(registers.get(SP), 1);
|
assert_eq!(registers.get_sp(), 1);
|
||||||
|
assert_eq!(stack.stack[0], 0xff);
|
||||||
|
|
||||||
stack.push(&mut registers.sp, 0xaa);
|
stack.push(&mut registers, 0xaa);
|
||||||
assert_eq!(registers.get(SP), 2);
|
assert_eq!(registers.get_sp(), 2);
|
||||||
assert_eq!(stack.pop(&mut registers.sp), 170);
|
assert_eq!(stack.stack[1], 0xaa);
|
||||||
assert_eq!(registers.get(SP), 1);
|
assert_eq!(stack.pop(&mut registers), 170);
|
||||||
assert_eq!(stack.pop(&mut registers.sp), 255);
|
assert_eq!(registers.get_sp(), 1);
|
||||||
assert_eq!(registers.get(SP), 0);
|
assert_eq!(stack.pop(&mut registers), 255);
|
||||||
|
assert_eq!(registers.get_sp(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
27
src/main.rs
27
src/main.rs
|
@ -63,19 +63,6 @@ fn run(rom: &str) -> Result<(), String> {
|
||||||
'mainloop: loop {
|
'mainloop: loop {
|
||||||
canvas.set_draw_color(Color::RGB(255, 255, 255));
|
canvas.set_draw_color(Color::RGB(255, 255, 255));
|
||||||
|
|
||||||
for y in 0..display::HEIGHT {
|
|
||||||
for x in 0..display::WIDTH {
|
|
||||||
if chip8.screen.is_pixel_set(x, y) {
|
|
||||||
canvas.fill_rect(Rect::new(
|
|
||||||
(x as u32 * display::SCALE_FACTOR) as i32,
|
|
||||||
(y as u32 * display::SCALE_FACTOR) as i32,
|
|
||||||
display::SCALE_FACTOR,
|
|
||||||
display::SCALE_FACTOR,
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for event in event_pump.poll_iter() {
|
for event in event_pump.poll_iter() {
|
||||||
match event {
|
match event {
|
||||||
Event::Quit { .. }
|
Event::Quit { .. }
|
||||||
|
@ -108,8 +95,20 @@ fn run(rom: &str) -> Result<(), String> {
|
||||||
canvas.present();
|
canvas.present();
|
||||||
chip8.handle_delay_timer();
|
chip8.handle_delay_timer();
|
||||||
chip8.handle_sound_timer();
|
chip8.handle_sound_timer();
|
||||||
|
|
||||||
chip8.exec();
|
chip8.exec();
|
||||||
|
|
||||||
|
for y in 0..display::HEIGHT {
|
||||||
|
for x in 0..display::WIDTH {
|
||||||
|
if chip8.screen.is_pixel_set(x, y) {
|
||||||
|
canvas.fill_rect(Rect::new(
|
||||||
|
(x as u32 * display::SCALE_FACTOR) as i32,
|
||||||
|
(y as u32 * display::SCALE_FACTOR) as i32,
|
||||||
|
display::SCALE_FACTOR,
|
||||||
|
display::SCALE_FACTOR,
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in New Issue