moves instruction code into instruction from computer
adds test suite to verify encode/decode of instructions. (( next is execution of instructions )) moves utility code for splitting parts of instructions to its own place with tests
This commit is contained in:
parent
9eb735a858
commit
d00d093b11
@ -2,6 +2,7 @@ use log::{debug, error};
|
||||
use crate::chip8::delay_timer::DelayTimer;
|
||||
use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
|
||||
use crate::chip8::sound_timer::SoundTimer;
|
||||
use crate::chip8::util::InstructionUtil;
|
||||
use crate::constants::{CHIP8_MEMORY_SIZE, CHIP8_REGISTER_COUNT};
|
||||
|
||||
use super::{
|
||||
@ -39,6 +40,17 @@ impl Default for Chip8Computer {
|
||||
}
|
||||
|
||||
impl Chip8Computer {
|
||||
pub fn new_with_program(new_program: Box<Vec<u16>>) -> Self {
|
||||
let mut working = Chip8Computer::new();
|
||||
for i in 0..new_program.len() {
|
||||
let high_byte = (new_program[i as usize] >> 8) as u8;
|
||||
let low_byte = (new_program[i] & 0xff) as u8;
|
||||
let base_offset = i * 2;
|
||||
working.memory.poke(base_offset as u16, high_byte);
|
||||
working.memory.poke((base_offset + 1) as u16, low_byte);
|
||||
}
|
||||
working
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Chip8Computer::default()
|
||||
}
|
||||
@ -58,47 +70,19 @@ impl Chip8Computer {
|
||||
let mut working_instruction: u16 = 0b0000000000000000;
|
||||
let high_byte = (self.memory.clone().peek(self.pc) as u16).rotate_left(8);
|
||||
let low_byte = self.memory.clone().peek(self.pc + 1) as u16;
|
||||
working_instruction = high_byte | low_byte;
|
||||
|
||||
working_instruction = InstructionUtil::join_bytes(high_byte as u8, low_byte as u8);
|
||||
|
||||
let decoded_instruction =
|
||||
Chip8Computer::decode_instruction(working_instruction);
|
||||
Chip8CpuInstructions::decode(working_instruction);
|
||||
|
||||
// println!("DECODED INSTRUCTION = {:?}", decoded_instruction);
|
||||
// start by moving to the next instruction
|
||||
self.pc += 2;
|
||||
// todo: THIS IS BAD AND IS A SIDE EFFECT
|
||||
decoded_instruction.execute(self);
|
||||
|
||||
/*
|
||||
match (self.state, decoded_instruction) {
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SysAddr(target_address)) => {
|
||||
debug!("INST: SYS: {target_address}");
|
||||
self.pc = target_address as u16;
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::CLS) => {
|
||||
debug!("INST: CLS");
|
||||
for i in 0..(64 * 32) {
|
||||
self.video_memory.poke(i, false);
|
||||
}
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::RET) => {
|
||||
debug!("INST* RET");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::JpAddr(new_address)) => {
|
||||
debug!("INST: JP_ADDR: {new_address:4x}");
|
||||
self.pc = new_address as u16;
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::CallAddr(sub_address)) => {
|
||||
debug!("INST* CALL_ADDR: {sub_address:4x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SeVxByte(vx_register, byte)) => {
|
||||
debug!("INST: SeVxByte: 0x{vx_register:1x}/0x{byte:2x}");
|
||||
self.registers[vx_register as usize] = byte as u8;
|
||||
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SneVxByte(vx_register, byte)) => {
|
||||
debug!("INST: SneVxByte: 0x{vx_register:1x}/0x{byte:2x}");
|
||||
if self.registers[vx_register as usize] == byte as u8 {
|
||||
self.pc += 2;
|
||||
}
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SeVxVy(vx_register, vy_register)) => {
|
||||
debug!("INST* SeVxVy: 0x{vx_register:1x}/0x{vy_register:1x}");
|
||||
if self.registers[vx_register as usize] == self.registers[vy_register as usize] {
|
||||
@ -128,296 +112,20 @@ impl Chip8Computer {
|
||||
debug!("INST: XorVxVy: {vx_register:1x}/{vy_register:1x}");
|
||||
self.registers[vx_register as usize] = self.registers[vx_register as usize] ^ self.registers[vy_register as usize];
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AddVxVy(vx_register, vy_register)) => {
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AddVxVy) => {
|
||||
debug!("INST: AddVxVy: {vx_register:1x}/{vy_register:1x}");
|
||||
self.registers[vx_register as usize] += self.registers[vy_register as usize];
|
||||
}
|
||||
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SubVxVy(vx_register, vy_register)) => {
|
||||
debug!("INST: SubVxVy: {vx_register:1x}/{vy_register:1x}");
|
||||
self.registers[vx_register as usize] -= self.registers[vy_register as usize];
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::ShrVxVy(vx_register, vy_register)) => {
|
||||
debug!("INST* ShrVxVy: {vx_register:1x}/{vy_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SubnVxVy(vx_register, vy_register)) => {
|
||||
debug!("INST* SubnVxVy: {vx_register:1x}/{vy_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::ShlVxVy(vx_register, vy_register)) => {
|
||||
debug!("INST* ShlVxVy: {vx_register:1x}/{vy_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SneVxVy(vx_register, vy_register)) => {
|
||||
debug!("INST: SneVxVy: {vx_register:1x}/{vy_register:1x}");
|
||||
if self.registers[vx_register as usize] != self.registers[vy_register as usize] {
|
||||
self.pc += 02;
|
||||
}
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdIAddr(addr)) => {
|
||||
debug!("INST* LdIAddr: [0x{addr:4x}]");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::JpV0Addr(addr)) => {
|
||||
debug!("INST* JpV0Addr: 0x{addr:4x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::RndVxByte(vx_register, byte)) => {
|
||||
debug!("INST* RndVxByte: 0x{vx_register:1x}/0x{byte:2x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::DrawVxVyNibble(vx_register, vy_register, nibble)) => {
|
||||
debug!("INST* DrawVxVyNibble: 0x{vx_register:1x}/0x{vy_register:1x}/0x{nibble:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SkpVx(vx_register)) => {
|
||||
debug!("INST* SkpVx: 0x{vx_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SnkpVx(vx_register)) => {
|
||||
debug!("INST* SnkpVx: 0x{vx_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxDt(vx_register)) => {
|
||||
debug!("INST* LdVxDt: 0x{vx_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxK(vx_register)) => {
|
||||
debug!("INST* LdVxK: 0x{vx_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdDtVx(vx_register)) => {
|
||||
debug!("INST* LdDtVx: 0x{vx_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdStVx(vx_register)) => {
|
||||
debug!("INST* SkpVx: 0x{vx_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AddIVx(vx_register)) => {
|
||||
debug!("INST* AddIVx: 0x{vx_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdFVx(_)) => {
|
||||
debug!("INST* LdFVu:");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdBVx(vx_register)) => {
|
||||
debug!("INST* LdBVx: 0x{vx_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdIVx(vx_register)) => {
|
||||
debug!("INST* LdIVx: 0x{vx_register:1x}");
|
||||
}
|
||||
(Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxI(vx_register)) => {
|
||||
debug!("INST* LdVxI: 0x{vx_register:1x}");
|
||||
}
|
||||
_ => {
|
||||
error!("UNABLE TO PROCEED. CPU IN UNKNOWN STATE");
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
self.sound_timer.tick();
|
||||
self.delay_timer.tick();
|
||||
self
|
||||
}
|
||||
|
||||
// nnn or addr - A 12-bit value, the lowest 12 bits of the instruction
|
||||
pub fn read_addr_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
instruction_to_read_from & 0b0000111111111111
|
||||
}
|
||||
|
||||
// n or nibble - A 4-bit value, the lowest 4 bits of the instruction
|
||||
pub fn read_nibble_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
instruction_to_read_from & 0b0000000000001111
|
||||
}
|
||||
|
||||
// x - A 4-bit value, the lower 4 bits of the high byte of the instruction
|
||||
pub fn read_x_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
(instruction_to_read_from & 0b0000111100000000).rotate_right(8)
|
||||
}
|
||||
|
||||
// y - A 4-bit value, the upper 4 bits of the low byte of the instruction
|
||||
pub fn read_y_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
(instruction_to_read_from & 0b0000000011110000).rotate_right(4)
|
||||
}
|
||||
|
||||
// kk or byte - An 8-bit value, the lowest 8 bits of the instruction
|
||||
pub fn read_byte_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
(instruction_to_read_from & 0b0000000011111111)
|
||||
}
|
||||
|
||||
pub fn read_upper_byte_lower_nibble(to_read_from: u16) -> u16 {
|
||||
to_read_from & 0x0f00
|
||||
}
|
||||
|
||||
fn decode_instruction(to_decode: u16) -> Chip8CpuInstructions {
|
||||
let mut decoded_instruction = Chip8CpuInstructions::XXXXERRORINSTRUCTION;
|
||||
|
||||
// pull out the various possible parameters for use further along when we
|
||||
// acutally sort out what kind of instruction we have.
|
||||
let x_param = Chip8Computer::read_x_from_instruction(to_decode);
|
||||
let y_param = Chip8Computer::read_y_from_instruction(to_decode);
|
||||
let addr_param = Chip8Computer::read_addr_from_instruction(to_decode);
|
||||
let byte_param = Chip8Computer::read_byte_from_instruction(to_decode);
|
||||
let nibble_param = Chip8Computer::read_nibble_from_instruction(to_decode);
|
||||
let ubln = u16::rotate_right(Chip8Computer::read_upper_byte_lower_nibble(to_decode), 8);
|
||||
let last_byte = to_decode & 0xFF;
|
||||
|
||||
match to_decode {
|
||||
0x00E0 => {
|
||||
// 00E0 - CLS
|
||||
// Clear the display.
|
||||
Chip8CpuInstructions::CLS
|
||||
}
|
||||
0x00EE => {
|
||||
// 00EE - RET
|
||||
// Return from a subroutine.
|
||||
Chip8CpuInstructions::RET
|
||||
}
|
||||
0x0000..=0x0FFF => {
|
||||
// 0nnn - SYS addr
|
||||
// Jump to a machine code routine at nnn.
|
||||
Chip8CpuInstructions::SysAddr(addr_param as i16)
|
||||
}
|
||||
0x1000..=0x1FFF => {
|
||||
// 1nnn - JP addr
|
||||
// Jump to location nnn.
|
||||
Chip8CpuInstructions::JpAddr(addr_param as i16)
|
||||
}
|
||||
0x2000..=0x2FFF => {
|
||||
// 2nnn - CALL addr
|
||||
// Call subroutine at nnn.
|
||||
Chip8CpuInstructions::CallAddr(addr_param as i16)
|
||||
}
|
||||
0x3000..=0x3FFF => {
|
||||
// 3xkk - SE Vx, byte
|
||||
// Skip next instruction if Vx = kk.
|
||||
Chip8CpuInstructions::SeVxByte(x_param as i16, byte_param as i16)
|
||||
}
|
||||
0x4000..=0x4FFF => {
|
||||
// 4xkk - SNE Vx, byte
|
||||
// Skip next instruction if Vx != kk.
|
||||
Chip8CpuInstructions::SneVxByte(x_param as i16, byte_param as i16)
|
||||
}
|
||||
0x5000..=0x5FF0 => {
|
||||
// 5xy0 - SE Vx, Vy
|
||||
// Skip next instruction if Vx = Vy.
|
||||
Chip8CpuInstructions::SeVxVy(x_param, y_param)
|
||||
}
|
||||
0x6000..=0x6FFF => {
|
||||
// 6xkk - LD Vx, byte
|
||||
// Set Vx = kk.
|
||||
Chip8CpuInstructions::LdVxByte(x_param, byte_param)
|
||||
}
|
||||
0x7000..=0x7FFF => {
|
||||
// ADD Vx, Byte
|
||||
Chip8CpuInstructions::AddVxByte(x_param, byte_param)
|
||||
}
|
||||
0x8000..=0x8FFE => {
|
||||
// 0x8000 Series
|
||||
let last_nibble = to_decode & 0xF;
|
||||
match last_nibble {
|
||||
0x0 => {
|
||||
// LD Vx, Vy
|
||||
Chip8CpuInstructions::LdVxVy(x_param, y_param)
|
||||
}
|
||||
0x1 => {
|
||||
// OR Vx, Vy
|
||||
Chip8CpuInstructions::OrVxVy(x_param, y_param)
|
||||
}
|
||||
0x2 => {
|
||||
// AND Vx, Vy
|
||||
Chip8CpuInstructions::AndVxVy(x_param, y_param)
|
||||
}
|
||||
0x3 => {
|
||||
// XOR Vx, Vy
|
||||
Chip8CpuInstructions::XorVxVy(x_param, y_param)
|
||||
}
|
||||
0x4 => {
|
||||
// ADD Vx, Vy
|
||||
Chip8CpuInstructions::AddVxVy(x_param, y_param)
|
||||
}
|
||||
0x5 => {
|
||||
// SUB Vx, Vy
|
||||
Chip8CpuInstructions::SubVxVy(x_param, y_param)
|
||||
}
|
||||
0x6 => {
|
||||
// SHR Vx, {, Vy }
|
||||
Chip8CpuInstructions::ShrVxVy(x_param, y_param)
|
||||
}
|
||||
0x7 => {
|
||||
// SUBN Vx, Vy
|
||||
Chip8CpuInstructions::SubnVxVy(x_param, y_param)
|
||||
}
|
||||
0xE => {
|
||||
// SHL Vx, {, Vy}
|
||||
Chip8CpuInstructions::ShlVxVy(x_param, y_param)
|
||||
}
|
||||
_ => {
|
||||
panic!("UNABLE TO DECODE 0x8000 SERIES INSTRUCTION");
|
||||
}
|
||||
}
|
||||
}
|
||||
0x9000..=0x9FF0 => {
|
||||
// SNE Vx, Vy
|
||||
Chip8CpuInstructions::SneVxVy(x_param, y_param)
|
||||
}
|
||||
0xA000..=0xAFFF => {
|
||||
// LD I, Addr
|
||||
Chip8CpuInstructions::LdIAddr(addr_param)
|
||||
}
|
||||
0xB000..=0xBFFF => {
|
||||
Chip8CpuInstructions::JpV0Addr(addr_param)
|
||||
// JP V0, Addr
|
||||
}
|
||||
0xC000..=0xCFFF => {
|
||||
// RND Vx, byte
|
||||
Chip8CpuInstructions::RndVxByte(x_param, byte_param)
|
||||
}
|
||||
0xD000..=0xDFFF => {
|
||||
// DRAW Vx, Vy, nibble
|
||||
|
||||
Chip8CpuInstructions::DrawVxVyNibble(x_param, y_param, nibble_param)
|
||||
}
|
||||
0xE09E..=0xEFA1 => {
|
||||
match last_byte {
|
||||
0x9E => {
|
||||
Chip8CpuInstructions::SkpVx(ubln)
|
||||
}
|
||||
0xA1 => {
|
||||
Chip8CpuInstructions::SnkpVx(ubln)
|
||||
}
|
||||
_ => {
|
||||
XXXXERRORINSTRUCTION
|
||||
}
|
||||
}
|
||||
}
|
||||
0xF007..=0xFF65 => {
|
||||
println!("COMPARING LAST BYTE FROM TODECODE: {:2x} to {:4x} with {:2x}", last_byte, to_decode, ubln);
|
||||
match last_byte {
|
||||
0x07 => {
|
||||
Chip8CpuInstructions::LdVxDt(ubln)
|
||||
}
|
||||
0x0A => {
|
||||
Chip8CpuInstructions::LdVxK(ubln)
|
||||
}
|
||||
0x15 => {
|
||||
Chip8CpuInstructions::LdDtVx(ubln)
|
||||
}
|
||||
0x18 => {
|
||||
Chip8CpuInstructions::LdStVx(ubln)
|
||||
}
|
||||
0x1E => {
|
||||
Chip8CpuInstructions::AddIVx(ubln)
|
||||
}
|
||||
0x29 => {
|
||||
Chip8CpuInstructions::LdFVx(ubln)
|
||||
}
|
||||
0x33 => {
|
||||
Chip8CpuInstructions::LdBVx(ubln)
|
||||
}
|
||||
0x55 => {
|
||||
Chip8CpuInstructions::LdIVx(ubln)
|
||||
}
|
||||
0x65 => {
|
||||
Chip8CpuInstructions::LdVxI(ubln)
|
||||
}
|
||||
_ => { XXXXERRORINSTRUCTION }
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
XXXXERRORINSTRUCTION
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::random;
|
||||
@ -429,48 +137,6 @@ mod test {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoder_test_valid_instructions() {
|
||||
// valid instructions
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x00E0u16), Chip8CpuInstructions::CLS ));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x00EE), Chip8CpuInstructions::RET));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x0123), Chip8CpuInstructions::SysAddr(0x123)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x0FFF), Chip8CpuInstructions::SysAddr(0xfff)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x1002), Chip8CpuInstructions::JpAddr(0x2)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x1FF0), Chip8CpuInstructions::JpAddr(0xFF0)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x2002), Chip8CpuInstructions::CallAddr(0x2)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x3123), Chip8CpuInstructions::SeVxByte(0x1, 0x23)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x4abc), Chip8CpuInstructions::SneVxByte(0xa, 0xbc)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x5ab0), Chip8CpuInstructions::SeVxVy(0xa, 0xb)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x6aff), Chip8CpuInstructions::LdVxByte(0xa, 0xff)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x7abc), Chip8CpuInstructions::AddVxByte(0xa, 0xbc)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x8ab0), Chip8CpuInstructions::LdVxVy(0xa, 0xb)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x8ba1), Chip8CpuInstructions::OrVxVy(0xb, 0xa)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x8cd2), Chip8CpuInstructions::AndVxVy(0xc, 0xd)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x8de3), Chip8CpuInstructions::XorVxVy(0xd, 0xe)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x8ef4), Chip8CpuInstructions::AddVxVy(0xe, 0xf)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x8f05), Chip8CpuInstructions::SubVxVy(0xf, 0x0)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x8016), Chip8CpuInstructions::ShrVxVy(0x0, 0x1)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x8127), Chip8CpuInstructions::SubnVxVy(0x1, 0x2)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x834e), Chip8CpuInstructions::ShlVxVy(0x3, 0x4)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0x9ab0), Chip8CpuInstructions::SneVxVy(0xa, 0xb)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xa123), Chip8CpuInstructions::LdIAddr(0x123)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xb234), Chip8CpuInstructions::JpV0Addr(0x234)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xcaca), Chip8CpuInstructions::RndVxByte(0xa, 0xca)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xdab4), Chip8CpuInstructions::DrawVxVyNibble(0xa, 0xb, 0x4)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xe19e), Chip8CpuInstructions::SkpVx(0x1)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xe2a1), Chip8CpuInstructions::SnkpVx(0x2)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xf107), Chip8CpuInstructions::LdVxDt(0x1)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xf40a), Chip8CpuInstructions::LdVxK(0x4)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xf615), Chip8CpuInstructions::LdDtVx(0x6)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xfb18), Chip8CpuInstructions::LdStVx(0xb)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xfd1e), Chip8CpuInstructions::AddIVx(0xd)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xfc29), Chip8CpuInstructions::LdFVx(0xc)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xfd33), Chip8CpuInstructions::LdBVx(0xd)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xfe55), Chip8CpuInstructions::LdIVx(0xe)));
|
||||
assert!(matches!(Chip8Computer::decode_instruction(0xf365), Chip8CpuInstructions::LdVxI(0x3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoder_test_invalid_instructions() {
|
||||
// 'bad' instructions that should be dropped...
|
||||
@ -480,21 +146,18 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cls_test() {
|
||||
let mut start_system = Chip8Computer::new();
|
||||
let mut targets = Vec::new();
|
||||
for i in 0..10 {
|
||||
let new_address: i16 = random();
|
||||
let new_address = new_address % CHIP8_MEMORY_SIZE as i16;
|
||||
start_system.video_memory.poke(new_address as u16, true);
|
||||
targets.push(new_address);
|
||||
}
|
||||
start_system.memory.poke(0x200, 0x00);
|
||||
start_system.memory.poke(0x201, 0xe0);
|
||||
start_system.step_system();
|
||||
for i in 0..CHIP8_MEMORY_SIZE {
|
||||
assert!(!start_system.video_memory.peek(i as u16));
|
||||
}
|
||||
fn new_with_program() {
|
||||
// set a known program that sets some registers...
|
||||
let new_program = Box::new(vec![
|
||||
Chip8CpuInstructions::LdVxI(0x10).encode(),
|
||||
Chip8CpuInstructions::LdVxVy(0x1, 0x1).encode(),
|
||||
Chip8CpuInstructions::LdStVx(0xff).encode()
|
||||
]);
|
||||
|
||||
// ...then load it...
|
||||
let test_computer = Chip8Computer::new_with_program(new_program);
|
||||
|
||||
// ...and check the registers
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,3 +23,34 @@ impl DelayTimer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::chip8::sound_timer::SoundTimer;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ticks_reduce_time() {
|
||||
let mut st = DelayTimer::new();
|
||||
st.set_timer(100);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.counter, 97);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_ticks_works() {
|
||||
let mut st = DelayTimer::new();
|
||||
st.set_timer(0);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.counter, 0);
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,8 @@
|
||||
use crate::chip8::computer::{Chip8Computer};
|
||||
use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
|
||||
use crate::chip8::util::InstructionUtil;
|
||||
use crate::chip8::video::Chip8Video;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Chip8CpuInstructions {
|
||||
SysAddr(i16), // 0x0nnn Exit to System Call
|
||||
@ -38,154 +43,483 @@ pub enum Chip8CpuInstructions {
|
||||
XXXXERRORINSTRUCTION,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Chip8SystemState {
|
||||
pub video_memory: [bool; 512],
|
||||
pub memory: [u8; 4096],
|
||||
pub registers: [u8; 16],
|
||||
pub i: i16,
|
||||
pub pc: i16,
|
||||
pub sound_timer: i8,
|
||||
pub delay_timer: i8
|
||||
}
|
||||
|
||||
trait Chip8Instruction {
|
||||
fn execute(&self, input: Chip8SystemState) -> Chip8SystemState;
|
||||
}
|
||||
|
||||
impl Chip8Instruction for Chip8CpuInstructions {
|
||||
fn execute(&self, mut input: Chip8SystemState) -> Chip8SystemState {
|
||||
impl Chip8CpuInstructions {
|
||||
pub fn encode(&self) -> u16 {
|
||||
match self {
|
||||
Chip8CpuInstructions::SysAddr(new_address) => {
|
||||
let mut new_state = input.clone();
|
||||
new_state.pc = *new_address;
|
||||
new_state
|
||||
Chip8CpuInstructions::SysAddr(target) => {
|
||||
(0x0000 | (target & 0x0FFF)) as u16
|
||||
}
|
||||
Chip8CpuInstructions::CLS => {
|
||||
input.video_memory = [false; 512];
|
||||
input
|
||||
0x00E0
|
||||
}
|
||||
Chip8CpuInstructions::RET => {
|
||||
input
|
||||
0x00EE
|
||||
}
|
||||
Chip8CpuInstructions::JpAddr(new_addr) => {
|
||||
0x1000 | (new_addr & 0x0FFF) as u16
|
||||
}
|
||||
Chip8CpuInstructions::CallAddr(address) => {
|
||||
(0x2000 | (address & 0x0FFF)) as u16
|
||||
}
|
||||
Chip8CpuInstructions::SeVxByte(vx_register, byte) => {
|
||||
(0x3000 | (vx_register << 8 | byte) as u16)
|
||||
}
|
||||
Chip8CpuInstructions::SneVxByte(vx_register, byte) => {
|
||||
(0x4000i16 | vx_register << 8 | byte) as u16
|
||||
}
|
||||
Chip8CpuInstructions::SeVxVy(x_register, y_register) => {
|
||||
0x5000u16 | (x_register << 8 | y_register << 4)
|
||||
}
|
||||
Chip8CpuInstructions::LdVxByte(x_register, byte) => {
|
||||
0x6000u16 | x_register << 8 | byte
|
||||
}
|
||||
Chip8CpuInstructions::AddVxByte(x_register, byte) => {
|
||||
0x7000u16 | x_register << 8 | byte
|
||||
}
|
||||
Chip8CpuInstructions::LdVxVy(x_register, y_register) => {
|
||||
0x8000u16 | x_register << 8 | y_register << 4
|
||||
}
|
||||
Chip8CpuInstructions::OrVxVy(x_register, y_register) => {
|
||||
0x8001u16 | x_register << 8 | y_register << 4
|
||||
}
|
||||
|
||||
Chip8CpuInstructions::AndVxVy(x_register, y_register) => {
|
||||
0x8002u16 | x_register << 8 | y_register << 4
|
||||
}
|
||||
Chip8CpuInstructions::XorVxVy(x_register, y_register) => {
|
||||
0x8003u16 | x_register << 8 | y_register << 4
|
||||
}
|
||||
Chip8CpuInstructions::AddVxVy(x_register, y_register) => {
|
||||
0x8004u16 | x_register << 8 | y_register << 4
|
||||
|
||||
}
|
||||
Chip8CpuInstructions::SubVxVy(x_register, y_register) => {
|
||||
0x8005u16 | x_register << 8 | y_register << 4
|
||||
}
|
||||
|
||||
Chip8CpuInstructions::ShrVxVy(x_register, y_register) => {
|
||||
0x8006u16 | x_register << 8 | y_register << 4
|
||||
|
||||
}
|
||||
Chip8CpuInstructions::SubnVxVy(x_register, y_register) => {
|
||||
0x8007u16 | x_register << 8 | y_register << 4
|
||||
}
|
||||
Chip8CpuInstructions::ShlVxVy(x_register, y_register) => {
|
||||
0x800Eu16 | x_register << 8 | y_register << 4
|
||||
}
|
||||
Chip8CpuInstructions::SneVxVy(x_register, y_register) => {
|
||||
0x9000u16 | x_register << 8 | y_register << 4
|
||||
}
|
||||
Chip8CpuInstructions::LdIAddr(addr) => {
|
||||
0xA000u16 | addr
|
||||
}
|
||||
Chip8CpuInstructions::JpV0Addr(addr) => {
|
||||
0xB000u16 | addr
|
||||
}
|
||||
Chip8CpuInstructions::RndVxByte(x_register, byte) => {
|
||||
0xC000u16 | x_register << 8 | byte
|
||||
}
|
||||
Chip8CpuInstructions::DrawVxVyNibble(x_register, y_register, height) => {
|
||||
0xD000u16 | x_register << 8 | y_register << 4 | height
|
||||
}
|
||||
Chip8CpuInstructions::SkpVx(x_register) => {
|
||||
0xE09Eu16 | x_register << 8
|
||||
}
|
||||
Chip8CpuInstructions::SnkpVx(x_register) => {
|
||||
0xE0A1u16 | x_register << 8
|
||||
|
||||
}
|
||||
Chip8CpuInstructions::LdVxDt(x_register) => {
|
||||
0xF007u16 | x_register << 8
|
||||
}
|
||||
Chip8CpuInstructions::LdVxK(x_register) => {
|
||||
0xF00Au16 | x_register << 8
|
||||
}
|
||||
Chip8CpuInstructions::LdDtVx(x_register) => {
|
||||
0xF015u16 | x_register << 8
|
||||
}
|
||||
Chip8CpuInstructions::LdStVx(x_register) => {
|
||||
0xF018u16 | x_register << 8
|
||||
}
|
||||
Chip8CpuInstructions::AddIVx(x_register) => {
|
||||
0xF01Eu16 | x_register << 8
|
||||
|
||||
}
|
||||
Chip8CpuInstructions::LdFVx(x_register) => {
|
||||
0xF029u16 | x_register << 8
|
||||
}
|
||||
Chip8CpuInstructions::LdBVx(x_register) => {
|
||||
0xf033u16 | x_register << 8
|
||||
}
|
||||
Chip8CpuInstructions::LdIVx(x_register) => {
|
||||
0xf055u16 | x_register << 8
|
||||
}
|
||||
Chip8CpuInstructions::LdVxI(x_register) => {
|
||||
0xf065u16 | x_register << 8
|
||||
}
|
||||
_ => {
|
||||
0xffff
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Chip8CpuInstructions {
|
||||
pub fn decode(input: u16) -> Chip8CpuInstructions {
|
||||
let x_param = InstructionUtil::read_x_from_instruction(input);
|
||||
let y_param = InstructionUtil::read_y_from_instruction(input);
|
||||
let addr_param = InstructionUtil::read_addr_from_instruction(input);
|
||||
let byte_param = InstructionUtil::read_byte_from_instruction(input);
|
||||
let nibble_param = InstructionUtil::read_nibble_from_instruction(input);
|
||||
let ubln = u16::rotate_right(InstructionUtil::read_upper_byte_lower_nibble(input), 8);
|
||||
let last_byte = input & 0xFF;
|
||||
|
||||
match input {
|
||||
0x00E0 => {
|
||||
// 00E0 - CLS
|
||||
// Clear the display.
|
||||
Chip8CpuInstructions::CLS
|
||||
}
|
||||
0x00EE => {
|
||||
// 00EE - RET
|
||||
// Return from a subroutine.
|
||||
Chip8CpuInstructions::RET
|
||||
}
|
||||
0x0000..=0x0FFF => {
|
||||
// 0nnn - SYS addr
|
||||
// Jump to a machine code routine at nnn.
|
||||
Chip8CpuInstructions::SysAddr(addr_param as i16)
|
||||
}
|
||||
0x1000..=0x1FFF => {
|
||||
// 1nnn - JP addr
|
||||
// Jump to location nnn.
|
||||
Chip8CpuInstructions::JpAddr(addr_param as i16)
|
||||
}
|
||||
0x2000..=0x2FFF => {
|
||||
// 2nnn - CALL addr
|
||||
// Call subroutine at nnn.
|
||||
Chip8CpuInstructions::CallAddr(addr_param as i16)
|
||||
}
|
||||
0x3000..=0x3FFF => {
|
||||
// 3xkk - SE Vx, byte
|
||||
// Skip next instruction if Vx = kk.
|
||||
Chip8CpuInstructions::SeVxByte(x_param as i16, byte_param as i16)
|
||||
}
|
||||
0x4000..=0x4FFF => {
|
||||
// 4xkk - SNE Vx, byte
|
||||
// Skip next instruction if Vx != kk.
|
||||
Chip8CpuInstructions::SneVxByte(x_param as i16, byte_param as i16)
|
||||
}
|
||||
0x5000..=0x5FF0 => {
|
||||
// 5xy0 - SE Vx, Vy
|
||||
// Skip next instruction if Vx = Vy.
|
||||
Chip8CpuInstructions::SeVxVy(x_param, y_param)
|
||||
}
|
||||
0x6000..=0x6FFF => {
|
||||
// 6xkk - LD Vx, byte
|
||||
// Set Vx = kk.
|
||||
Chip8CpuInstructions::LdVxByte(x_param, byte_param)
|
||||
}
|
||||
0x7000..=0x7FFF => {
|
||||
// ADD Vx, Byte
|
||||
Chip8CpuInstructions::AddVxByte(x_param, byte_param)
|
||||
}
|
||||
0x8000..=0x8FFE => {
|
||||
// 0x8000 Series
|
||||
let last_nibble = input & 0xF;
|
||||
match last_nibble {
|
||||
0x0 => {
|
||||
// LD Vx, Vy
|
||||
Chip8CpuInstructions::LdVxVy(x_param, y_param)
|
||||
}
|
||||
0x1 => {
|
||||
// OR Vx, Vy
|
||||
Chip8CpuInstructions::OrVxVy(x_param, y_param)
|
||||
}
|
||||
0x2 => {
|
||||
// AND Vx, Vy
|
||||
Chip8CpuInstructions::AndVxVy(x_param, y_param)
|
||||
}
|
||||
0x3 => {
|
||||
// XOR Vx, Vy
|
||||
Chip8CpuInstructions::XorVxVy(x_param, y_param)
|
||||
}
|
||||
0x4 => {
|
||||
// ADD Vx, Vy
|
||||
Chip8CpuInstructions::AddVxVy(x_param, y_param)
|
||||
}
|
||||
0x5 => {
|
||||
// SUB Vx, Vy
|
||||
Chip8CpuInstructions::SubVxVy(x_param, y_param)
|
||||
}
|
||||
0x6 => {
|
||||
// SHR Vx, {, Vy }
|
||||
Chip8CpuInstructions::ShrVxVy(x_param, y_param)
|
||||
}
|
||||
0x7 => {
|
||||
// SUBN Vx, Vy
|
||||
Chip8CpuInstructions::SubnVxVy(x_param, y_param)
|
||||
}
|
||||
0xE => {
|
||||
// SHL Vx, {, Vy}
|
||||
Chip8CpuInstructions::ShlVxVy(x_param, y_param)
|
||||
}
|
||||
_ => {
|
||||
panic!("UNABLE TO DECODE 0x8000 SERIES INSTRUCTION");
|
||||
}
|
||||
}
|
||||
}
|
||||
0x9000..=0x9FF0 => {
|
||||
// SNE Vx, Vy
|
||||
Chip8CpuInstructions::SneVxVy(x_param, y_param)
|
||||
}
|
||||
0xA000..=0xAFFF => {
|
||||
// LD I, Addr
|
||||
Chip8CpuInstructions::LdIAddr(addr_param)
|
||||
}
|
||||
0xB000..=0xBFFF => {
|
||||
Chip8CpuInstructions::JpV0Addr(addr_param)
|
||||
// JP V0, Addr
|
||||
}
|
||||
0xC000..=0xCFFF => {
|
||||
// RND Vx, byte
|
||||
Chip8CpuInstructions::RndVxByte(x_param, byte_param)
|
||||
}
|
||||
0xD000..=0xDFFF => {
|
||||
// DRAW Vx, Vy, nibble
|
||||
|
||||
Chip8CpuInstructions::DrawVxVyNibble(x_param, y_param, nibble_param)
|
||||
}
|
||||
0xE09E..=0xEFA1 => {
|
||||
match last_byte {
|
||||
0x9E => {
|
||||
Chip8CpuInstructions::SkpVx(ubln)
|
||||
}
|
||||
0xA1 => {
|
||||
Chip8CpuInstructions::SnkpVx(ubln)
|
||||
}
|
||||
_ => {
|
||||
XXXXERRORINSTRUCTION
|
||||
}
|
||||
}
|
||||
}
|
||||
0xF007..=0xFF65 => {
|
||||
println!("COMPARING LAST BYTE FROM TODECODE: {:2x} to {:4x} with {:2x}", last_byte, input, ubln);
|
||||
match last_byte {
|
||||
0x07 => {
|
||||
Chip8CpuInstructions::LdVxDt(ubln)
|
||||
}
|
||||
0x0A => {
|
||||
Chip8CpuInstructions::LdVxK(ubln)
|
||||
}
|
||||
0x15 => {
|
||||
Chip8CpuInstructions::LdDtVx(ubln)
|
||||
}
|
||||
0x18 => {
|
||||
Chip8CpuInstructions::LdStVx(ubln)
|
||||
}
|
||||
0x1E => {
|
||||
Chip8CpuInstructions::AddIVx(ubln)
|
||||
}
|
||||
0x29 => {
|
||||
Chip8CpuInstructions::LdFVx(ubln)
|
||||
}
|
||||
0x33 => {
|
||||
Chip8CpuInstructions::LdBVx(ubln)
|
||||
}
|
||||
0x55 => {
|
||||
Chip8CpuInstructions::LdIVx(ubln)
|
||||
}
|
||||
0x65 => {
|
||||
Chip8CpuInstructions::LdVxI(ubln)
|
||||
}
|
||||
_ => {
|
||||
XXXXERRORINSTRUCTION
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
XXXXERRORINSTRUCTION
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(&self, mut input: &mut Chip8Computer) -> Chip8Computer {
|
||||
input.pc += 2;
|
||||
let _ = match self {
|
||||
Chip8CpuInstructions::SysAddr(new_address) => {
|
||||
let mut new_state = input.clone();
|
||||
new_state.pc = *new_address as u16;
|
||||
}
|
||||
Chip8CpuInstructions::CLS => {
|
||||
for i in 0..(64 * 32) {
|
||||
input.video_memory.poke(i, false);
|
||||
}
|
||||
}
|
||||
Chip8CpuInstructions::RET => {}
|
||||
Chip8CpuInstructions::JpAddr(new_address) => {
|
||||
input
|
||||
input.pc = *new_address as u16
|
||||
}
|
||||
Chip8CpuInstructions::CallAddr(_) => {
|
||||
input
|
||||
Chip8CpuInstructions::CallAddr(_) => {}
|
||||
Chip8CpuInstructions::SeVxByte(vx_register, byte) => {
|
||||
input.registers[*vx_register as usize] = *byte as u8;
|
||||
}
|
||||
Chip8CpuInstructions::SeVxByte(_, _) => {
|
||||
input
|
||||
Chip8CpuInstructions::SneVxByte(vx_register, byte) => {
|
||||
if input.registers[*vx_register as usize] == *byte as u8 {
|
||||
input.pc += 2;
|
||||
}
|
||||
Chip8CpuInstructions::SneVxByte(_, _) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::SeVxVy(_, _) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::SeVxVy(_, _) => {}
|
||||
Chip8CpuInstructions::LdVxByte(register, byte) => {
|
||||
input.registers[*register as usize] = byte.to_be_bytes()[0];
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::AddVxByte(_, _) => {
|
||||
input
|
||||
Chip8CpuInstructions::AddVxByte(_, _) => {}
|
||||
Chip8CpuInstructions::LdVxVy(_, _) => {}
|
||||
Chip8CpuInstructions::OrVxVy(_, _) => {}
|
||||
Chip8CpuInstructions::AndVxVy(_, _) => {}
|
||||
Chip8CpuInstructions::XorVxVy(_, _) => {}
|
||||
Chip8CpuInstructions::AddVxVy(vx_register, vy_register) => {
|
||||
input.registers[*vx_register as usize] += input.registers[*vy_register as usize];
|
||||
}
|
||||
Chip8CpuInstructions::LdVxVy(_, _) => {
|
||||
input
|
||||
Chip8CpuInstructions::SubVxVy(vx_register, vy_register) => {
|
||||
input.registers[*vx_register as usize] -= input.registers[*vy_register as usize];
|
||||
}
|
||||
Chip8CpuInstructions::OrVxVy(_, _) => {
|
||||
input
|
||||
Chip8CpuInstructions::ShrVxVy(_, _) => {}
|
||||
Chip8CpuInstructions::SubnVxVy(_, _) => {}
|
||||
Chip8CpuInstructions::ShlVxVy(_, _) => {}
|
||||
Chip8CpuInstructions::SneVxVy(vx_register, vy_register) => {
|
||||
if input.registers[*vx_register as usize] != input.registers[*vy_register as usize] {
|
||||
input.pc += 02;
|
||||
}
|
||||
Chip8CpuInstructions::AndVxVy(_, _) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::XorVxVy(_, _) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::AddVxVy(_, _) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::SubVxVy(_, _) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::ShrVxVy(_, _) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::SubnVxVy(_, _) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::ShlVxVy(_, _) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::SneVxVy(_, _) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::LdIAddr(new_index) => {
|
||||
input.i = *new_index as i16;
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::JpV0Addr(_) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::RndVxByte(_, _) => {
|
||||
input
|
||||
input.i_register = *new_index;
|
||||
}
|
||||
Chip8CpuInstructions::JpV0Addr(_) => {}
|
||||
Chip8CpuInstructions::RndVxByte(_, _) => {}
|
||||
Chip8CpuInstructions::DrawVxVyNibble(x, y, n) => {
|
||||
// read nibble bytes from memory starting at I
|
||||
|
||||
let mut did_change: bool = false;
|
||||
|
||||
for draw_x in 0..*n {
|
||||
let mut new_value = input.memory[(input.i + draw_x as i16) as usize];
|
||||
for draw_y in 0..8 {
|
||||
// let mut new_value = input.memory[(input.i_register + draw_x) as usize];
|
||||
//for draw_y in 0..8 {
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if did_change {
|
||||
input.registers[0xF] = 1u8;
|
||||
} else {
|
||||
input.registers[0xF] = 0u8;
|
||||
}
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::SkpVx(_) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::SnkpVx(_) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::LdVxDt(_) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::LdVxK(_) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::LdDtVx(_) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::LdStVx(_) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::AddIVx(_) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::LdFVx(_) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::LdBVx(_) => {
|
||||
input
|
||||
|
||||
}
|
||||
Chip8CpuInstructions::LdIVx(_) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::LdVxI(_) => {
|
||||
input
|
||||
}
|
||||
Chip8CpuInstructions::XXXXERRORINSTRUCTION => {
|
||||
input
|
||||
}
|
||||
}
|
||||
Chip8CpuInstructions::SkpVx(_) => {}
|
||||
Chip8CpuInstructions::SnkpVx(_) => {}
|
||||
Chip8CpuInstructions::LdVxDt(_) => {}
|
||||
Chip8CpuInstructions::LdVxK(_) => {}
|
||||
Chip8CpuInstructions::LdDtVx(_) => {}
|
||||
Chip8CpuInstructions::LdStVx(_) => {}
|
||||
Chip8CpuInstructions::AddIVx(_) => {}
|
||||
Chip8CpuInstructions::LdFVx(_) => {}
|
||||
Chip8CpuInstructions::LdBVx(_) => {}
|
||||
Chip8CpuInstructions::LdIVx(_) => {}
|
||||
Chip8CpuInstructions::LdVxI(_) => {}
|
||||
Chip8CpuInstructions::XXXXERRORINSTRUCTION => {}
|
||||
};
|
||||
*input
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoder_test() {
|
||||
assert_eq!(Chip8CpuInstructions::CLS.encode(), 0x00E0);
|
||||
assert_eq!(Chip8CpuInstructions::RET.encode(), 0x00EE);
|
||||
assert_eq!(Chip8CpuInstructions::SysAddr(0x123).encode(), 0x0123);
|
||||
assert_eq!(Chip8CpuInstructions::JpAddr(0x234).encode(), 0x1234);
|
||||
assert_eq!(Chip8CpuInstructions::CallAddr(0x345).encode(), 0x2345);
|
||||
assert_eq!(Chip8CpuInstructions::SeVxByte(0x4, 0x56).encode(), 0x3456);
|
||||
assert_eq!(Chip8CpuInstructions::SneVxByte(0xa, 0xbc).encode(), 0x4abc);
|
||||
assert_eq!(Chip8CpuInstructions::SeVxVy(0xa, 0xb).encode(), 0x5ab0);
|
||||
assert_eq!(Chip8CpuInstructions::LdVxByte(0xa, 0xff).encode(), 0x6aff);
|
||||
assert_eq!(Chip8CpuInstructions::AddVxByte(0xa, 0xbc).encode(), 0x7abc);
|
||||
assert_eq!(Chip8CpuInstructions::LdVxVy(0xa, 0xb).encode(), 0x8ab0);
|
||||
assert_eq!(Chip8CpuInstructions::OrVxVy(0xb, 0xa).encode(), 0x8ba1);
|
||||
assert_eq!(Chip8CpuInstructions::AndVxVy(0xc, 0xd).encode(), 0x8cd2);
|
||||
assert_eq!(Chip8CpuInstructions::XorVxVy(0xd, 0xe).encode(), 0x8de3);
|
||||
assert_eq!(Chip8CpuInstructions::AddVxVy(0xe, 0xf).encode(), 0x8ef4);
|
||||
assert_eq!(Chip8CpuInstructions::SubVxVy(0xf, 0x0).encode(), 0x8f05);
|
||||
assert_eq!(Chip8CpuInstructions::ShrVxVy(0x0, 0x1).encode(), 0x8016);
|
||||
assert_eq!(Chip8CpuInstructions::SubnVxVy(0x1, 0x2).encode(), 0x8127);
|
||||
assert_eq!(Chip8CpuInstructions::ShlVxVy(0x3, 0x4).encode(), 0x834e);
|
||||
assert_eq!(Chip8CpuInstructions::SneVxVy(0xa, 0xb).encode(), 0x9ab0);
|
||||
assert_eq!(Chip8CpuInstructions::LdIAddr(0x123).encode(), 0xa123);
|
||||
assert_eq!(Chip8CpuInstructions::JpV0Addr(0x234).encode(), 0xb234);
|
||||
assert_eq!(Chip8CpuInstructions::RndVxByte(0xa, 0xca).encode(), 0xcaca);
|
||||
assert_eq!(Chip8CpuInstructions::DrawVxVyNibble(0xa, 0xb, 0x4).encode(), 0xdab4);
|
||||
assert_eq!(Chip8CpuInstructions::SkpVx(0x1).encode(), 0xe19e);
|
||||
assert_eq!(Chip8CpuInstructions::SnkpVx(0x2).encode(), 0xe2a1);
|
||||
assert_eq!(Chip8CpuInstructions::LdVxDt(0x1).encode(), 0xf107);
|
||||
assert_eq!(Chip8CpuInstructions::LdVxK(0x4).encode(), 0xf40a);
|
||||
assert_eq!(Chip8CpuInstructions::LdDtVx(0x6).encode(), 0xf615);
|
||||
assert_eq!(Chip8CpuInstructions::LdStVx(0xb).encode(), 0xfb18);
|
||||
assert_eq!(Chip8CpuInstructions::AddIVx(0xd).encode(), 0xfd1e);
|
||||
assert_eq!(Chip8CpuInstructions::LdFVx(0xc).encode(), 0xfc29);
|
||||
assert_eq!(Chip8CpuInstructions::LdBVx(0xd).encode(), 0xfd33);
|
||||
assert_eq!(Chip8CpuInstructions::LdIVx(0xe).encode(), 0xfe55);
|
||||
assert_eq!(Chip8CpuInstructions::LdVxI(0x3).encode(), 0xf365);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoder_test() {
|
||||
assert!(matches!( Chip8CpuInstructions::decode(0x00E0u16), Chip8CpuInstructions::CLS));
|
||||
assert!(matches!( Chip8CpuInstructions::decode(0x00EEu16), Chip8CpuInstructions::RET));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x0123), Chip8CpuInstructions::SysAddr(0x123)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x0FFF), Chip8CpuInstructions::SysAddr(0xfff)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x1002), Chip8CpuInstructions::JpAddr(0x2)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x1FF0), Chip8CpuInstructions::JpAddr(0xFF0)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x2002), Chip8CpuInstructions::CallAddr(0x2)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x3123), Chip8CpuInstructions::SeVxByte(0x1, 0x23)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x4abc), Chip8CpuInstructions::SneVxByte(0xa, 0xbc)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x5ab0), Chip8CpuInstructions::SeVxVy(0xa, 0xb)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x6aff), Chip8CpuInstructions::LdVxByte(0xa, 0xff)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x7abc), Chip8CpuInstructions::AddVxByte(0xa, 0xbc)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x8ab0), Chip8CpuInstructions::LdVxVy(0xa, 0xb)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x8ba1), Chip8CpuInstructions::OrVxVy(0xb, 0xa)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x8cd2), Chip8CpuInstructions::AndVxVy(0xc, 0xd)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x8de3), Chip8CpuInstructions::XorVxVy(0xd, 0xe)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x8ef4), Chip8CpuInstructions::AddVxVy(0xe, 0xf)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x8f05), Chip8CpuInstructions::SubVxVy(0xf, 0x0)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x8016), Chip8CpuInstructions::ShrVxVy(0x0, 0x1)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x8127), Chip8CpuInstructions::SubnVxVy(0x1, 0x2)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x834e), Chip8CpuInstructions::ShlVxVy(0x3, 0x4)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0x9ab0), Chip8CpuInstructions::SneVxVy(0xa, 0xb)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xa123), Chip8CpuInstructions::LdIAddr(0x123)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xb234), Chip8CpuInstructions::JpV0Addr(0x234)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xcaca), Chip8CpuInstructions::RndVxByte(0xa, 0xca)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xdab4), Chip8CpuInstructions::DrawVxVyNibble(0xa, 0xb, 0x4)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xe19e), Chip8CpuInstructions::SkpVx(0x1)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xe2a1), Chip8CpuInstructions::SnkpVx(0x2)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xf107), Chip8CpuInstructions::LdVxDt(0x1)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xf40a), Chip8CpuInstructions::LdVxK(0x4)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xf615), Chip8CpuInstructions::LdDtVx(0x6)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xfb18), Chip8CpuInstructions::LdStVx(0xb)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xfd1e), Chip8CpuInstructions::AddIVx(0xd)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xfc29), Chip8CpuInstructions::LdFVx(0xc)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xfd33), Chip8CpuInstructions::LdBVx(0xd)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xfe55), Chip8CpuInstructions::LdIVx(0xe)));
|
||||
assert!(matches!(Chip8CpuInstructions::decode(0xf365), Chip8CpuInstructions::LdVxI(0x3)));
|
||||
}
|
||||
}
|
||||
@ -6,12 +6,10 @@ use dimensioned::si;
|
||||
pub struct SoundTimer {
|
||||
counter: i32
|
||||
}
|
||||
|
||||
impl SoundTimer {
|
||||
pub fn current(&self) -> i32 {
|
||||
self.counter
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
SoundTimer {
|
||||
counter: 0
|
||||
@ -24,10 +22,44 @@ impl SoundTimer {
|
||||
pub fn tick(&mut self) {
|
||||
if self.counter > 0 {
|
||||
self.counter -= 1;
|
||||
thread::spawn(|| {
|
||||
/*
|
||||
todo: this breaks tests. maybe its wrong?
|
||||
if let good_thread = thread::spawn(|| {
|
||||
beep(440).expect("Unable to beep");
|
||||
thread::sleep(time::Duration::from_millis(500));
|
||||
}).join().expect("Unable to spawn beep thread");
|
||||
}).join().unwrap().
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ticks_reduce_time() {
|
||||
let mut st = SoundTimer::new();
|
||||
st.set_timer(100);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.counter, 97);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_ticks_works() {
|
||||
let mut st = SoundTimer::new();
|
||||
st.set_timer(0);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.counter, 0);
|
||||
}
|
||||
}
|
||||
@ -61,14 +61,6 @@ const cell_height: i32 = 5i32;
|
||||
|
||||
impl Chip8SystemMemory {
|
||||
|
||||
pub fn poke_multi(&mut self, start_offset: u16, data: Vec<u8>) {
|
||||
// loop through the data...
|
||||
// poke all the data into memory offset by the start
|
||||
let data_len = data.len();
|
||||
for i in 0..data_len {
|
||||
self.poke(start_offset + i as u16, data[i]);
|
||||
}
|
||||
}
|
||||
pub fn peek(self, address: u16) -> u8 {
|
||||
trace!("PEEK: {} / {}", address, self.memory[address as usize].clone());
|
||||
self.memory[address as usize]
|
||||
@ -79,6 +71,8 @@ impl Chip8SystemMemory {
|
||||
self.memory[address as usize] = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn load_program(&mut self, program_to_load: Box<Vec<u8>>) {
|
||||
for load_index in 0..program_to_load.len() {
|
||||
self.poke((load_index + 0x200) as u16, program_to_load[load_index]);
|
||||
|
||||
87
emma/src/chip8/util.rs
Normal file
87
emma/src/chip8/util.rs
Normal file
@ -0,0 +1,87 @@
|
||||
|
||||
pub struct InstructionUtil {}
|
||||
impl InstructionUtil {
|
||||
|
||||
pub fn split_bytes(to_split: u16) -> (u8, u8) {
|
||||
let high = to_split.rotate_left(8) as u8;
|
||||
let low = (to_split & 0xff) as u8;
|
||||
|
||||
(high, low)
|
||||
}
|
||||
|
||||
pub fn join_bytes(high: u8, low: u8) -> u16 {
|
||||
(high as u16 )<< 8 & low as u16
|
||||
}
|
||||
|
||||
// nnn or addr - A 12-bit value, the lowest 12 bits of the instruction
|
||||
pub fn read_addr_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
instruction_to_read_from & 0x0FFF
|
||||
}
|
||||
|
||||
// n or nibble - A 4-bit value, the lowest 4 bits of the instruction
|
||||
pub fn read_nibble_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
instruction_to_read_from & 0x000F
|
||||
}
|
||||
|
||||
// x - A 4-bit value, the lower 4 bits of the high byte of the instruction
|
||||
pub fn read_x_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
(instruction_to_read_from & 0x0F00).rotate_right(8)
|
||||
}
|
||||
|
||||
// y - A 4-bit value, the upper 4 bits of the low byte of the instruction
|
||||
pub fn read_y_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
(instruction_to_read_from & 0x00F0).rotate_right(4)
|
||||
}
|
||||
|
||||
// kk or byte - An 8-bit value, the lowest 8 bits of the instruction
|
||||
pub fn read_byte_from_instruction(instruction_to_read_from: u16) -> u16 {
|
||||
(instruction_to_read_from & 0x00FF)
|
||||
}
|
||||
|
||||
pub fn read_upper_byte_lower_nibble(to_read_from: u16) -> u16 {
|
||||
to_read_from & 0x0f00
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_bytes() {
|
||||
// from 0xABCD we should have AB high, CD low
|
||||
let (low, high) = InstructionUtil::split_bytes(0xabcd);
|
||||
assert_eq!(low, 0xAB);
|
||||
assert_eq!(high, 0xCD);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_bytes() {
|
||||
// from 0xAB low and 0xCD high we get 0xABCD
|
||||
let merged = InstructionUtil::join_bytes( 0xcd, 0xab);
|
||||
assert_eq!(merged, 0xcdab);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_from_instruction() {
|
||||
// from 0xABCD
|
||||
let source = 0xABCD;
|
||||
assert_eq!(InstructionUtil::read_addr_from_instruction(source), 0xBCD);
|
||||
assert_eq!(InstructionUtil::read_nibble_from_instruction(source), 0xD);
|
||||
assert_eq!(InstructionUtil::read_x_from_instruction(source), 0xB);
|
||||
assert_eq!(InstructionUtil::read_y_from_instruction(source), 0xC);
|
||||
assert_eq!(InstructionUtil::read_byte_from_instruction(source), 0xCD);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ubln() {
|
||||
// from 0xABCD we should see B
|
||||
assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0xABCD), 0xB << 8);
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ pub mod chip8 {
|
||||
pub mod system_memory;
|
||||
pub mod instructions;
|
||||
pub mod cpu_states;
|
||||
pub mod util;
|
||||
}
|
||||
|
||||
pub mod constants;
|
||||
Loading…
x
Reference in New Issue
Block a user