Implement opcodes, restructure

This commit is contained in:
Michael Kohl 2021-04-10 22:57:14 +07:00
parent 3b23c8c5b1
commit e374d5f4ec
9 changed files with 482 additions and 124 deletions

64
Cargo.lock generated
View File

@ -51,6 +51,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
name = "chip-8"
version = "0.1.0"
dependencies = [
"rand",
"sdl2",
]
@ -105,6 +106,17 @@ dependencies = [
"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]]
name = "lazy_static"
version = "1.4.0"
@ -133,6 +145,52 @@ dependencies = [
"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]]
name = "redox_syscall"
version = "0.2.5"
@ -214,6 +272,12 @@ version = "0.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "winapi"
version = "0.3.9"

View File

@ -7,4 +7,5 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8.3"
sdl2 = { version = "0.34.2", features = ["bundled"] }

View File

@ -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,
}
}
}

View File

@ -32,9 +32,7 @@ impl Screen {
if b & 0b10000000 >> lx > 0 {
let dx = (x + lx) % WIDTH;
let dy = (y + ly) % HEIGHT;
if !pixel_collission && self.is_pixel_set(dx, dy) {
pixel_collission = true;
}
pixel_collission = pixel_collission || self.is_pixel_set(dx, dy);
self.pixel_set(dx, dy);
}
@ -43,6 +41,10 @@ impl Screen {
pixel_collission
}
pub fn clear(&mut self) {
self.pixels = [[false; WIDTH]; HEIGHT];
}
}
#[cfg(test)]

View File

@ -1,10 +1,12 @@
use crate::chip8::audio::Speaker;
use crate::chip8::cpu::Instruction;
use crate::chip8::display::Screen;
use crate::chip8::keyboard::Keyboard;
use crate::chip8::memory::{self, Memory};
use crate::chip8::registers::{Register::*, Registers};
use crate::chip8::registers::Registers;
use crate::chip8::stack::Stack;
use rand;
use sdl2::AudioSubsystem;
use std::{fs, thread, time::Duration};
@ -31,20 +33,18 @@ impl Chip8 {
}
pub fn handle_delay_timer(&mut self) -> () {
let delay_timer = self.registers.get(DT);
if delay_timer > 0 {
if self.registers.get_dt() > 0 {
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) {
let sound_timer = self.registers.get(ST);
let status = sound_timer > 0;
let status = self.registers.get_st() > 0;
self.speaker.beep(status);
if status {
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) {
let pc = self.registers.get(PC);
let pc = self.registers.get_pc();
let opcode = self.memory.read_opcode(pc as usize);
println!("{}", opcode);
self.registers.set(PC, pc + 2);
let instruction = Instruction::parse(opcode);
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"),
}
}
}

View File

@ -54,7 +54,7 @@ mod tests {
#[test]
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::X), Some(0));
assert_eq!(keyboard.map(Keycode::M), None);

View File

@ -1,68 +1,21 @@
use super::cpu;
use super::memory;
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 {
data: [u8; DATA_REGISTERS],
v: [u8; DATA_REGISTERS],
i: u16,
delay_timer: u8,
sound_timer: u8,
pc: u16,
pub sp: u8,
sp: u8,
}
impl Registers {
pub fn new() -> Self {
Registers {
data: [0; DATA_REGISTERS],
v: [0; DATA_REGISTERS],
i: 0,
delay_timer: 0,
sound_timer: 0,
@ -71,26 +24,72 @@ impl Registers {
}
}
pub fn set(&mut self, register: Register, value: u16) -> () {
match register {
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_v(&self, n: usize) -> u8 {
self.v[n]
}
pub fn get(&self, register: Register) -> u16 {
match register {
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 set_v(&mut self, n: usize, value: u8) {
self.v[n] = value;
}
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]
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]
fn it_can_write_data_registers() {
let mut registers = Registers::new();
registers.set(VA, 42);
assert_eq!(registers.data[10], 42);
registers.set_v(0xA, 42);
assert_eq!(registers.v[0xA], 42);
}
#[test]
fn it_can_read_data_registers() {
let mut registers = Registers::new();
registers.set(VA, 42);
assert_eq!(registers.get(VA), 42);
registers.set_v(0xA, 42);
assert_eq!(registers.get_v(0xA), 42);
}
#[test]
fn it_can_write_special_registers() {
let mut registers = Registers::new();
registers.set(PC, 42);
registers.set_pc(42);
assert_eq!(registers.pc, 42);
}
#[test]
fn it_can_read_special_registers() {
let mut registers = Registers::new();
registers.set(PC, 42);
assert_eq!(registers.get(PC), 42);
registers.set_pc(42);
assert_eq!(registers.get_pc(), 42);
}
}

View File

@ -1,3 +1,5 @@
use crate::chip8::registers::Registers;
pub const STACK_DEPTH: usize = 16;
pub struct Stack {
@ -11,39 +13,42 @@ impl Stack {
}
}
pub fn push(&mut self, stack_pointer: &mut u8, value: u16) -> () {
assert!((*stack_pointer as usize) < STACK_DEPTH, "stack overflow");
self.stack[*stack_pointer as usize] = value;
*stack_pointer += 1;
pub fn push(&mut self, registers: &mut Registers, value: u16) {
let stack_pointer = registers.get_sp() as usize;
assert!(stack_pointer < STACK_DEPTH, "stack overflow");
self.stack[stack_pointer] = value;
registers.inc_sp();
}
pub fn pop(&mut self, stack_pointer: &mut u8) -> u16 {
assert!((*stack_pointer as usize) > 0, "stack underflow");
*stack_pointer -= 1;
assert!((*stack_pointer as usize) < STACK_DEPTH, "stack overflow");
self.stack[*stack_pointer as usize]
pub fn pop(&mut self, registers: &mut Registers) -> u16 {
let stack_pointer = registers.get_sp() as usize;
assert!(stack_pointer > 0, "stack underflow");
registers.dec_sp();
assert!(stack_pointer < STACK_DEPTH, "stack overflow");
self.stack[registers.get_sp() as usize]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chip8::registers::{Register::*, Registers};
#[test]
fn it_can_push_to_and_pop_from_the_stack() {
let mut registers = Registers::new();
assert_eq!(registers.get(SP), 0);
assert_eq!(registers.get_sp(), 0);
let mut stack = Stack::new();
stack.push(&mut registers.sp, 0xff);
assert_eq!(registers.get(SP), 1);
stack.push(&mut registers, 0xff);
assert_eq!(registers.get_sp(), 1);
assert_eq!(stack.stack[0], 0xff);
stack.push(&mut registers.sp, 0xaa);
assert_eq!(registers.get(SP), 2);
assert_eq!(stack.pop(&mut registers.sp), 170);
assert_eq!(registers.get(SP), 1);
assert_eq!(stack.pop(&mut registers.sp), 255);
assert_eq!(registers.get(SP), 0);
stack.push(&mut registers, 0xaa);
assert_eq!(registers.get_sp(), 2);
assert_eq!(stack.stack[1], 0xaa);
assert_eq!(stack.pop(&mut registers), 170);
assert_eq!(registers.get_sp(), 1);
assert_eq!(stack.pop(&mut registers), 255);
assert_eq!(registers.get_sp(), 0);
}
}

View File

@ -63,19 +63,6 @@ fn run(rom: &str) -> Result<(), String> {
'mainloop: loop {
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() {
match event {
Event::Quit { .. }
@ -108,8 +95,20 @@ fn run(rom: &str) -> Result<(), String> {
canvas.present();
chip8.handle_delay_timer();
chip8.handle_sound_timer();
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(())