1567 lines
63 KiB
Rust
1567 lines
63 KiB
Rust
use std::ops::{BitAnd, Shr};
|
|
use log::debug;
|
|
use rand::random;
|
|
use crate::chip8::computer::{Chip8Computer};
|
|
use crate::chip8::cpu_states::Chip8CpuStates::WaitingForKey;
|
|
use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
|
|
use crate::chip8::util::InstructionUtil;
|
|
|
|
/*
|
|
nnn or addr - A 12-bit value, the lowest 12 bits of the instruction
|
|
n or nibble - A 4-bit value, the lowest 4 bits of the instruction
|
|
x - A 4-bit value, the lower 4 bits of the high byte of the instruction
|
|
y - A 4-bit value, the upper 4 bits of the low byte of the instruction
|
|
kk or byte - An 8-bit value, the lowest 8 bits of the instruction
|
|
*/
|
|
|
|
#[derive(Debug)]
|
|
pub enum Chip8CpuInstructions {
|
|
/// 0nnn
|
|
/// Exit to System Call at nnn
|
|
SysAddr(i16),
|
|
/// Clear Screen
|
|
CLS,
|
|
/// Return from Subroutine
|
|
RET,
|
|
/// 1nnn
|
|
/// Jump to Address nnn
|
|
JpAddr(i16),
|
|
/// 2nnn
|
|
/// Call Subroutine at nnn
|
|
CallAddr(i16),
|
|
/// 0x3xkk
|
|
/// Skip next instruction if Vx == kk
|
|
SeVxByte(u8, u8),
|
|
/// 4xkk
|
|
/// Skip next instruction if Vx != kk
|
|
SneVxByte(u8, u8),
|
|
/// 5xy0
|
|
/// Skip next instruction if Vx == Vy
|
|
SeVxVy(u8, u8),
|
|
/// 6xkk
|
|
/// Set Vx = kk
|
|
LdVxByte(u8, u8),
|
|
/// 7xkk
|
|
/// Set Vx = Vx + kk
|
|
AddVxByte(u8, u8),
|
|
/// 8xy0
|
|
/// Set Vx = Vy
|
|
LdVxVy(u8, u8),
|
|
/// 8xy1
|
|
/// Set Vx = Vx OR Vy
|
|
OrVxVy(u8, u8),
|
|
/// 8xy2
|
|
/// Set Vx = Vx AND Vy
|
|
AndVxVy(u8, u8),
|
|
/// 8xy3
|
|
/// Set Vx = Vx XOR Vy
|
|
XorVxVy(u8, u8),
|
|
/// 8xy4
|
|
/// Set Vx = Vx + Vy
|
|
/// Set VF=1 if Carry
|
|
AddVxVy(u8, u8),
|
|
/// 8xy5
|
|
/// Set Vx = Vx - Vy
|
|
/// Set VF=1 if No Borrow
|
|
SubVxVy(u8, u8),
|
|
/// 8xy6
|
|
/// Set Vx = Vx SHR 1
|
|
ShrVxVy(u8, u8),
|
|
/// 8xy7
|
|
/// Set Vx = Vy - Vx
|
|
/// Set VF=1 if No Borrow
|
|
SubnVxVy(u8, u8),
|
|
/// 8xye
|
|
/// Set Vx = Vx SHL 1
|
|
ShlVxVy(u8, u8),
|
|
/// 9xy0
|
|
/// Skip next instruction if Vx != Vy
|
|
SneVxVy(u8, u8),
|
|
/// Annn
|
|
/// Load I register with NNN
|
|
LdIAddr(u16),
|
|
/// Bnnn
|
|
/// Jump to nnn+V0
|
|
JpV0Addr(u16),
|
|
/// Cxkk
|
|
/// Set Vx = Random u8 AND kk
|
|
RndVxByte(u8, u8),
|
|
/// Dxyn
|
|
/// Display N byte tall sprite starting at Vx, Vy
|
|
DrawVxVyNibble(u8, u8, u8),
|
|
/// Ex9E
|
|
/// Skip next instruction of key in Vx pressed
|
|
SkpVx(u8),
|
|
/// ExA1
|
|
/// Skip Next If Key Not Pressed
|
|
SnkpVx(u8),
|
|
/// Fx07
|
|
/// Set Vx = Dt
|
|
LdVxDt(u8),
|
|
/// Fx0A
|
|
/// Wait for Key to be pressed and store
|
|
/// in Vx
|
|
LdVxK(u8),
|
|
/// Fx15
|
|
/// Load Value in Delay Timer to Vx
|
|
LdDtVx(u8), // 0xFx15 Set Delay Timer
|
|
/// Fx18
|
|
/// Set Dt = Vx
|
|
LdStVx(u8),
|
|
AddIVx(u8), // 0xFx1E I = I + Vx
|
|
LdFVx(u8), // 0xFx29 Set I = Location of sprite for Digit Vx
|
|
LdBVx(u8), // 0xFx33 Store BCD of Vx in I, I+1, I+2
|
|
LdIVx(u8), // 0xFx55 Store V0 to Vx in memory starting at I
|
|
LdVxI(u8), // 0xFx65 Load V0 to Vx in memory starting at I
|
|
XXXXERRORINSTRUCTION,
|
|
}
|
|
|
|
impl Chip8CpuInstructions {
|
|
pub fn encode(&self) -> u16 {
|
|
match self {
|
|
Chip8CpuInstructions::SysAddr(target) => {
|
|
(0x0000 | (target & 0x0FFF)) as u16
|
|
}
|
|
Chip8CpuInstructions::CLS => {
|
|
0x00E0
|
|
}
|
|
Chip8CpuInstructions::RET => {
|
|
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 as u16) << 8 | *byte as u16) as u16
|
|
}
|
|
Chip8CpuInstructions::SneVxByte(vx_register, byte) => {
|
|
0x4000u16 | (*vx_register as u16) << 8 | *byte as u16
|
|
}
|
|
Chip8CpuInstructions::SeVxVy(x_register, y_register) => {
|
|
0x5000u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4
|
|
}
|
|
Chip8CpuInstructions::LdVxByte(x_register, byte) => {
|
|
0x6000u16 | (*x_register as u16) << 8 | (*byte as u16)
|
|
}
|
|
Chip8CpuInstructions::AddVxByte(x_register, byte) => {
|
|
0x7000u16 | (*x_register as u16) << 8 | *byte as u16
|
|
}
|
|
Chip8CpuInstructions::LdVxVy(x_register, y_register) => {
|
|
0x8000u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4
|
|
}
|
|
Chip8CpuInstructions::OrVxVy(x_register, y_register) => {
|
|
0x8001u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4
|
|
}
|
|
Chip8CpuInstructions::AndVxVy(x_register, y_register) => {
|
|
0x8002u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4
|
|
}
|
|
Chip8CpuInstructions::XorVxVy(x_register, y_register) => {
|
|
0x8003u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4
|
|
}
|
|
Chip8CpuInstructions::AddVxVy(x_register, y_register) => {
|
|
0x8004u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4
|
|
}
|
|
Chip8CpuInstructions::SubVxVy(x_register, y_register) => {
|
|
0x8005u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4
|
|
}
|
|
Chip8CpuInstructions::ShrVxVy(x_register, y_register) => {
|
|
0x8006u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4
|
|
}
|
|
Chip8CpuInstructions::SubnVxVy(x_register, y_register) => {
|
|
0x8007u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4
|
|
}
|
|
Chip8CpuInstructions::ShlVxVy(x_register, y_register) => {
|
|
0x800Eu16 | (*x_register as u16) << 8 | (*y_register as u16) << 4
|
|
}
|
|
Chip8CpuInstructions::SneVxVy(x_register, y_register) => {
|
|
0x9000u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4
|
|
}
|
|
Chip8CpuInstructions::LdIAddr(addr) => {
|
|
0xA000u16 | addr
|
|
}
|
|
Chip8CpuInstructions::JpV0Addr(addr) => {
|
|
0xB000u16 | addr
|
|
}
|
|
Chip8CpuInstructions::RndVxByte(x_register, byte) => {
|
|
0xC000u16 | (*x_register as u16) << 8 | (*byte as u16)
|
|
}
|
|
Chip8CpuInstructions::DrawVxVyNibble(x_register, y_register, height) => {
|
|
0xD000u16 | (*x_register as u16) << 8 | (*y_register as u16) << 4 | (*height as u16)
|
|
}
|
|
Chip8CpuInstructions::SkpVx(x_register) => {
|
|
0xE09Eu16 | (*x_register as u16) << 8
|
|
}
|
|
Chip8CpuInstructions::SnkpVx(x_register) => {
|
|
0xE0A1u16 | (*x_register as u16) << 8
|
|
}
|
|
Chip8CpuInstructions::LdVxDt(x_register) => {
|
|
0xF007u16 | (*x_register as u16) << 8
|
|
}
|
|
Chip8CpuInstructions::LdVxK(x_register) => {
|
|
0xF00Au16 | (*x_register as u16) << 8
|
|
}
|
|
Chip8CpuInstructions::LdDtVx(x_register) => {
|
|
0xF015u16 | (*x_register as u16) << 8
|
|
}
|
|
Chip8CpuInstructions::LdStVx(x_register) => {
|
|
0xF018u16 | (*x_register as u16) << 8
|
|
}
|
|
Chip8CpuInstructions::AddIVx(x_register) => {
|
|
0xF01Eu16 | (*x_register as u16) << 8
|
|
}
|
|
Chip8CpuInstructions::LdFVx(x_register) => {
|
|
0xF029u16 | (*x_register as u16) << 8
|
|
}
|
|
Chip8CpuInstructions::LdBVx(x_register) => {
|
|
0xf033u16 | (*x_register as u16) << 8
|
|
}
|
|
Chip8CpuInstructions::LdIVx(x_register) => {
|
|
0xf055u16 | (*x_register as u16) << 8
|
|
}
|
|
Chip8CpuInstructions::LdVxI(x_register) => {
|
|
0xf065u16 | (*x_register as u16) << 8
|
|
}
|
|
XXXXERRORINSTRUCTION => {
|
|
0xffff
|
|
}
|
|
}
|
|
}
|
|
pub fn decode(input: u16) -> Chip8CpuInstructions {
|
|
let x_param = InstructionUtil::read_x_from_instruction(input) as u8;
|
|
let y_param = InstructionUtil::read_y_from_instruction(input) as u8;
|
|
let addr_param = InstructionUtil::read_addr_from_instruction(input) as u16;
|
|
let byte_param = InstructionUtil::read_byte_from_instruction(input) as u8;
|
|
let nibble_param = InstructionUtil::read_nibble_from_instruction(input) as u8;
|
|
let ubln = InstructionUtil::read_upper_byte_lower_nibble(input) as u8;
|
|
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, byte_param)
|
|
}
|
|
0x4000..=0x4FFF => {
|
|
// 4xkk - SNE Vx, byte
|
|
// Skip next instruction if Vx != kk.
|
|
Chip8CpuInstructions::SneVxByte(x_param, byte_param)
|
|
}
|
|
0x5000..=0x5FF0 => {
|
|
// 5xy0 - SE Vx, Vy
|
|
// Skip next instruction if Vx = Vy.
|
|
// if the last nibble isn't 0...
|
|
if 0xf & input > 0 {
|
|
// ... we have a problem.
|
|
Chip8CpuInstructions::XXXXERRORINSTRUCTION
|
|
} else {
|
|
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)
|
|
}
|
|
_ => {
|
|
Chip8CpuInstructions::XXXXERRORINSTRUCTION
|
|
}
|
|
}
|
|
}
|
|
0x9000..=0x9FF0 => {
|
|
// SNE Vx, Vy
|
|
if 0xf & input > 0 {
|
|
XXXXERRORINSTRUCTION
|
|
} else {
|
|
Chip8CpuInstructions::SneVxVy(x_param, y_param)
|
|
}
|
|
}
|
|
0xA000..=0xAFFF => {
|
|
// LD I, Addr
|
|
Chip8CpuInstructions::LdIAddr(addr_param)
|
|
}
|
|
0xB000..=0xBFFF => {
|
|
// JP V0, Addr
|
|
Chip8CpuInstructions::JpV0Addr(addr_param)
|
|
}
|
|
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 => {
|
|
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, input: &mut Chip8Computer) -> Chip8Computer {
|
|
let start_pc = input.registers.peek_pc();
|
|
input.registers.poke_pc(start_pc + 2);
|
|
let _ = match self {
|
|
// 0x0nnn Exit to System Call
|
|
Chip8CpuInstructions::SysAddr(new_address) => {
|
|
debug!("SysAddr [0x{new_address:3x}]");
|
|
|
|
input.registers.poke_pc(*new_address as u16);
|
|
}
|
|
// * 0x00E0 Clear Screen
|
|
Chip8CpuInstructions::CLS => {
|
|
for i in 0..(64 * 32) {
|
|
input.video_memory.poke(i, false);
|
|
}
|
|
}
|
|
// 0x00EE Return from Subroutine
|
|
Chip8CpuInstructions::RET => {
|
|
let return_address = input.stack.pop();
|
|
debug!("Returning from subroutine to {return_address:03x}");
|
|
input.registers.poke_pc(return_address);
|
|
}
|
|
// 0x1nnn Jump to Address
|
|
Chip8CpuInstructions::JpAddr(new_address) => {
|
|
debug!("JpAddr [0x{new_address:3x}]");
|
|
input.registers.poke_pc(*new_address as u16);
|
|
}
|
|
// 0x2nnn Call Subroutine
|
|
Chip8CpuInstructions::CallAddr(new_address) => {
|
|
debug!("CALL ADDR 0x{new_address:3x}");
|
|
let return_address = input.registers.peek_pc();
|
|
input.registers.poke_pc(*new_address as u16);
|
|
input.stack.push(&return_address);
|
|
}
|
|
// 0x3xkk Skip next instruction if Vx = kk.
|
|
Chip8CpuInstructions::SeVxByte(vx_register, byte) => {
|
|
if input.registers.peek(*vx_register as u8) == *byte as u8 {
|
|
input.registers.advance_pc();
|
|
}
|
|
debug!("SeVxByte [0x{vx_register:1x}] [0x{byte:2x}]");
|
|
}
|
|
// 0x4xkk Skip next instruction if Vx != kk
|
|
Chip8CpuInstructions::SneVxByte(x, byte) => {
|
|
// 4xkk - SNE Vx, byte
|
|
// Skip next instruction if Vx != kk.
|
|
//
|
|
// The interpreter compares register Vx to kk, and if they are not equal,
|
|
// increments the program counter by 2.
|
|
let lhs = input.registers.peek(*x);
|
|
let rhs = *byte;
|
|
|
|
if lhs != rhs {
|
|
input.registers.advance_pc();
|
|
}
|
|
debug!("SneVxByte [0x{x:02x}] [0x{byte:02x}");
|
|
}
|
|
// 0x5xy0 Skip next instruction if Vx == Vy
|
|
Chip8CpuInstructions::SeVxVy(x, y) => {
|
|
let lhs = input.registers.peek(*x as u8);
|
|
let rhs = input.registers.peek(*y as u8);
|
|
|
|
if lhs == rhs {
|
|
input.registers.advance_pc();
|
|
}
|
|
debug!("SeVxVy [0x{x:1x}] [0x{y:1x}]");
|
|
}
|
|
// 0x6xkk Set Vx = kk
|
|
Chip8CpuInstructions::LdVxByte(register, byte) => {
|
|
let byte_value = *byte as u8;
|
|
input.registers.poke(*register as u8, byte_value);
|
|
debug!("LdVxByte [0x{register:1x}] [0x{byte:2.0x}]");
|
|
}
|
|
// 0x7xkk Set Vx = Vx + kk
|
|
Chip8CpuInstructions::AddVxByte(vx_register, byte) => {
|
|
let x_value: u16 = input.registers.peek(*vx_register) as u16;
|
|
let value_to_poke = (x_value + *byte as u16) as u8;
|
|
input.registers.poke(*vx_register, value_to_poke);
|
|
debug!("AddVxByte [0x{vx_register:1x}] [0x{byte:02x}] [0x{value_to_poke:02x}");
|
|
}
|
|
// 0x8xy0 Set value of Vy in Vx
|
|
Chip8CpuInstructions::LdVxVy(x, y) => {
|
|
input.registers.poke(*x as u8, input.registers.peek(*y as u8));
|
|
debug!("LdVxVy [0x{x:1x}] [0x{y:1x}]");
|
|
}
|
|
// 0x8xy1 Set Vx = Vx OR Vy
|
|
Chip8CpuInstructions::OrVxVy(x, y) => {
|
|
// shift them to 16 bit
|
|
let working_16_x = input.registers.peek(*x as u8) as u16;
|
|
let working_16_y = input.registers.peek(*y as u8) as u16;
|
|
// OR them
|
|
let working_16_or = working_16_x | working_16_y;
|
|
// shift them back to 8 bit.
|
|
let to_set = working_16_or as u8;
|
|
input.registers.poke(*x as u8, to_set);
|
|
debug!("OrVxVy [0x{x:1x}] [0x{y:1x}]")
|
|
}
|
|
// 0x8xy2 Set Vx = Vx AND Vy
|
|
Chip8CpuInstructions::AndVxVy(x, y) => {
|
|
let lhs_16 = input.registers.peek(*x as u8) as u16;
|
|
let rhs_16 = input.registers.peek(*y as u8) as u16;
|
|
let and_16 = lhs_16 & rhs_16;
|
|
|
|
input.registers.poke(*x as u8, and_16 as u8);
|
|
debug!("AndVxVy [0x{x:02x}] [0x{y:02x}] [0b{and_16:08b}");
|
|
}
|
|
// 0x8xy3 Set Vx = Vx XOR Vy
|
|
Chip8CpuInstructions::XorVxVy(x, y) => {
|
|
let lhs_16 = input.registers.peek(*x as u8) as u16;
|
|
let rhs_16 = input.registers.peek(*y as u8) as u16;
|
|
let xor_16 = lhs_16 ^ rhs_16;
|
|
input.registers.poke(*x as u8, xor_16 as u8);
|
|
}
|
|
// 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
|
|
Chip8CpuInstructions::AddVxVy(x, y) => {
|
|
let lhs = input.registers.peek(*x as u8) as i16;
|
|
let rhs = input.registers.peek(*y as u8) as i16;
|
|
let working = lhs + rhs;
|
|
input.registers.poke(*x as u8, working as u8);
|
|
|
|
if working >= 0x100 {
|
|
input.registers.poke(0xf, 0x01);
|
|
} else {
|
|
input.registers.poke(0x0f, 0x00);
|
|
}
|
|
}
|
|
Chip8CpuInstructions::SubVxVy(x, y) => {
|
|
// 8xy5 - SUB Vx, Vy
|
|
// Set Vx = Vx - Vy, set VF = NOT borrow.
|
|
//
|
|
// If Vx > Vy, then VF is set to 1, otherwise 0. Then Vy is subtracted from Vx, and the results stored in Vx.
|
|
|
|
let lhs = input.registers.peek(*x);
|
|
let rhs = input.registers.peek(*y);
|
|
|
|
let mut result = 0;
|
|
|
|
let borrow_flag: u8 = if rhs > lhs {
|
|
result = (lhs as u16 + 0x100) - rhs as u16;
|
|
0
|
|
} else {
|
|
result = lhs as u16 - rhs as u16;
|
|
1
|
|
};
|
|
|
|
input.registers.poke(*x as u8, result as u8);
|
|
input.registers.poke(0x0f, borrow_flag);
|
|
}
|
|
Chip8CpuInstructions::ShrVxVy(x, _) => {
|
|
// 8xy6 - SHR Vx {, Vy}
|
|
// Set Vx = Vx SHR 1.
|
|
// SHIFT 1 Bit ---->
|
|
// If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
|
|
let initial_value = input.registers.peek(*x);
|
|
|
|
// overflow check
|
|
let rotated = initial_value >> 1;
|
|
input.registers.poke(*x as u8, rotated);
|
|
if initial_value.bitand(0b1) == 1 {
|
|
input.registers.poke(0x0f, 0x01);
|
|
} else {
|
|
input.registers.poke(0x0f, 0x00);
|
|
}
|
|
}
|
|
Chip8CpuInstructions::SubnVxVy(x, y) => {
|
|
// 8xy7 - SUBN Vx, Vy
|
|
// Set Vx = Vy - Vx, set VF = NOT borrow.
|
|
//
|
|
// If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is subtracted from Vy, and the results stored in Vx.
|
|
let y_register = input.registers.peek(*y as u8);
|
|
let x_register = input.registers.peek(*x as u8);
|
|
let mut value_to_poke = 0;
|
|
|
|
let new_value = if y_register < x_register {
|
|
value_to_poke = (y_register as u16 + 256) - x_register as u16;
|
|
0
|
|
} else {
|
|
value_to_poke = (y_register - x_register) as u16;
|
|
1
|
|
};
|
|
|
|
debug!("SUB CARRY -> REGISTER 1 = [0x{x:02x}] / [{x_register}] REGISTER 2 = [0x{y:02x}] / [{y_register}] SUB = {value_to_poke} CARRY = {new_value}");
|
|
|
|
input.registers.poke(*x, value_to_poke as u8);
|
|
input.registers.poke(0xf, new_value);
|
|
}
|
|
|
|
Chip8CpuInstructions::ShlVxVy(x, _) => {
|
|
// 8xyE - SHL Vx {, Vy}
|
|
// Set Vx = Vx SHL 1.
|
|
//
|
|
// If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.
|
|
let initial_value = input.registers.peek(*x);
|
|
|
|
// overflow check
|
|
let rotated = initial_value << 1;
|
|
input.registers.poke(*x as u8, rotated);
|
|
if initial_value.bitand(0b10000000) == 0b10000000 {
|
|
input.registers.poke(0x0f, 0x01);
|
|
} else {
|
|
input.registers.poke(0x0f, 0x00);
|
|
}
|
|
}
|
|
Chip8CpuInstructions::SneVxVy(vx_register, vy_register) => {
|
|
// 9xy0 - SNE Vx, Vy
|
|
// Skip next instruction if Vx != Vy.
|
|
//
|
|
// The values of Vx and Vy are compared, and if they are not equal, the program counter is increased by 2.
|
|
|
|
let x_reg_value = input.registers.peek(*vx_register as u8);
|
|
let y_reg_value = input.registers.peek(*vy_register as u8);
|
|
if x_reg_value != y_reg_value {
|
|
input.registers.advance_pc();
|
|
}
|
|
}
|
|
Chip8CpuInstructions::LdIAddr(new_index) => {
|
|
// Annn - LD I, addr
|
|
// Set I = nnn.
|
|
//
|
|
// The value of register I is set to nnn.
|
|
debug!("LdiAddr [0x{new_index:3x}]");
|
|
input.registers.poke_i(*new_index);
|
|
}
|
|
// 0xBnnn Jump to nnn+V0
|
|
Chip8CpuInstructions::JpV0Addr(addr) => {
|
|
// Bnnn - JP V0, addr
|
|
// Jump to location nnn + V0.
|
|
//
|
|
// The program counter is set to nnn plus the value of V0.
|
|
input.registers.poke_pc(input.registers.peek(0) as u16 + addr);
|
|
}
|
|
Chip8CpuInstructions::RndVxByte(x, byte) => {
|
|
// Cxkk - RND Vx, byte
|
|
// Set Vx = random byte AND kk.
|
|
//
|
|
// The interpreter generates a random number from 0 to 255,
|
|
// which is then ANDed with the value kk.
|
|
// The results are stored in Vx.
|
|
let new_value: u8 = random();
|
|
let and_value: u8 = *byte;
|
|
let result = new_value & and_value;
|
|
debug!("RANDOM: [{new_value:02x}] AND: [{and_value:02x} Result: [{result:02x}]");
|
|
input.registers.poke(*x as u8, new_value & *byte as u8)
|
|
}
|
|
Chip8CpuInstructions::DrawVxVyNibble(y, x, n) => {
|
|
// Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.
|
|
|
|
// The interpreter reads n bytes from memory, starting at the address stored in I.
|
|
|
|
|
|
// These bytes are then displayed as sprites on screen at coordinates (Vx, Vy).
|
|
// Sprites are XORed onto the existing screen.
|
|
// If this causes any pixels to be erased, VF is set to 1,
|
|
// otherwise it is set to 0.
|
|
// If the sprite is positioned so part of it is outside the coordinates of the display,
|
|
// it wraps around to the opposite side of the screen.
|
|
|
|
let source_memory_offset = input.registers.peek_i();
|
|
|
|
let x_offset = input.registers.peek(*x as u8);
|
|
let y_offset = input.registers.peek(*y as u8);
|
|
|
|
debug!("X_OFFSET = {x_offset} / y_offset = {y_offset}");
|
|
let target_memory_offset = x_offset as u16 * 64 + y_offset as u16;
|
|
|
|
debug!("CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> STARTING AT {source_memory_offset} WRITING TO {target_memory_offset}");
|
|
let num_bytes_to_read = *n;
|
|
debug!("CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> PREPARING TO READ {num_bytes_to_read} BYTES FROM MEMORY TO VIDEO");
|
|
for byte_index in 0..num_bytes_to_read {
|
|
let current_byte = input.memory.peek(byte_index as u16 + source_memory_offset);
|
|
debug!("CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> READ BYTE [0x{byte_index:2x}]\t{current_byte:02x}\t{current_byte:08b}");
|
|
for bit_index in 0..8 {
|
|
let data_offset = ((x_offset as u16 + byte_index as u16) * 64) + (y_offset as u16 + bit_index as u16) as u16;
|
|
let current_bit = (current_byte.shr(7 - bit_index) & 0x1u8) == 0x1u8;
|
|
input.video_memory.poke(data_offset, current_bit);
|
|
}
|
|
}
|
|
|
|
debug!("PPOOSSTT -> CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> {}", input.video_memory.format_as_string());
|
|
|
|
let mut did_change: bool = input.video_memory.has_frame_changed;
|
|
|
|
if did_change {
|
|
input.registers.poke(0xf, 1u8);
|
|
} else {
|
|
input.registers.poke(0xf, 0u8);
|
|
}
|
|
}
|
|
Chip8CpuInstructions::SkpVx(x) => {
|
|
// Ex9E - SKP Vx
|
|
// Skip next instruction if key with the value of Vx is pressed.
|
|
//
|
|
// Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2.
|
|
let key_to_check = input.registers.peek(*x as u8);
|
|
let is_pressed = input.keypad.pressed(key_to_check);
|
|
if is_pressed {
|
|
input.registers.advance_pc();
|
|
}
|
|
}
|
|
Chip8CpuInstructions::SnkpVx(x) => {
|
|
// ExA1 - SKNP Vx
|
|
// Skip next instruction if key with the value of Vx is not pressed.
|
|
//
|
|
|
|
// Checks the keyboard,
|
|
// and if the key corresponding to the value of Vx is currently in the up position,
|
|
// PC is increased by 2.
|
|
let target_key = input.registers.peek(*x as u8);
|
|
let is_pressed = input.keypad.pressed(target_key);
|
|
debug!("SnKpVx [{x:1x}]");
|
|
if !is_pressed {
|
|
input.registers.advance_pc();
|
|
}
|
|
}
|
|
Chip8CpuInstructions::LdVxDt(x) => {
|
|
// Fx07 - LD Vx, DT
|
|
// Set Vx = delay timer value.
|
|
//
|
|
// The value of DT is placed into Vx.
|
|
let value_to_set = input.delay_timer.current();
|
|
input.registers.poke(*x as u8, value_to_set as u8);
|
|
}
|
|
Chip8CpuInstructions::LdVxK(x) => {
|
|
// Fx0A - LD Vx, K
|
|
// Wait for a key press, store the value of the key in Vx.
|
|
//
|
|
// All execution stops until a key is pressed, then the value of that key is stored in Vx.
|
|
input.state = WaitingForKey;
|
|
}
|
|
Chip8CpuInstructions::LdDtVx(source_register) => {
|
|
// Fx15 - LD DT, Vx
|
|
// Set delay timer = Vx.
|
|
//
|
|
// DT is set equal to the value of Vx.
|
|
let new_time = input.registers.peek(*source_register as u8);
|
|
input.delay_timer.set_timer(new_time as i32);
|
|
}
|
|
Chip8CpuInstructions::LdStVx(new_time) => {
|
|
let new_value = input.registers.peek(*new_time as u8);
|
|
input.sound_timer.set_timer(new_value as i32);
|
|
}
|
|
Chip8CpuInstructions::AddIVx(x) => {
|
|
// Fx1E - ADD I, Vx
|
|
// Set I = I + Vx.
|
|
//
|
|
// The values of I and Vx are added, and the results are stored in I.
|
|
let base = input.registers.peek_i();
|
|
let x_value = input.registers.peek(*x as u8);
|
|
input.registers.poke_i(base + x_value as u16);
|
|
}
|
|
Chip8CpuInstructions::LdFVx(x) => {
|
|
// Fx29 - LD F, Vx
|
|
// Set I = location of sprite for digit Vx.
|
|
//
|
|
// The value of I is set to the location for the hexadecimal sprite corresponding
|
|
// to the value of Vx. See section 2.4, Display, for more information on
|
|
// the Chip-8 hexadecimal font.
|
|
|
|
let x_value: u8 = input.registers.peek(*x);
|
|
|
|
let real_offset = x_value as u16 * 5;
|
|
input.registers.poke_i(real_offset as u16);
|
|
}
|
|
Chip8CpuInstructions::LdBVx(x) => {
|
|
// Fx33 - LD B, Vx
|
|
// Store BCD representation of Vx in memory locations I, I+1, and I+2.
|
|
//
|
|
// The interpreter takes the decimal value of Vx, and places the hundreds
|
|
// digit in memory at location in I, the tens digit at location I+1,
|
|
// and the ones digit at location I+2.
|
|
|
|
let to_convert = input.registers.peek(*x as u8);
|
|
|
|
// how many hundreds
|
|
let hundreds = to_convert / 100;
|
|
// how many tens, minus the hundreds
|
|
let tens = (to_convert / 10) - (hundreds * 10);
|
|
// whats the leftover when dividing by 10
|
|
let units = to_convert % 10;
|
|
|
|
// Convert to BCD
|
|
let result = ((hundreds as u16) << 8) | units as u16 | ((tens as u16) << 4) | units as u16;
|
|
// write them to the memory pointed to by I, I+1, and I+2
|
|
let target_start_offset = input.registers.peek_i();
|
|
input.memory.poke(target_start_offset, hundreds);
|
|
input.memory.poke(target_start_offset + 1, tens);
|
|
input.memory.poke(target_start_offset + 2, units);
|
|
}
|
|
Chip8CpuInstructions::LdIVx(x) => {
|
|
// Store registers V0 through Vx in memory starting at location I.
|
|
//
|
|
// The interpreter copies the values of registers V0 through Vx into memory,
|
|
// starting at the address in I.
|
|
let offset = input.registers.peek_i();
|
|
for i in 0..=*x {
|
|
input.memory.poke(offset + i as u16, input.registers.peek(i as u8));
|
|
}
|
|
input.registers.poke_i(offset + 1);
|
|
}
|
|
Chip8CpuInstructions::LdVxI(x) => {
|
|
// Read registers V0 through Vx from memory starting at location I.
|
|
//
|
|
// The interpreter reads values from memory starting at location I into registers V0 through Vx.
|
|
let offset = input.registers.peek_i();
|
|
debug!("STARTING TO READ AT {offset:03x}");
|
|
let num_loops = x + 1;
|
|
debug!("WILL READ {num_loops:x} BYTES");
|
|
for index in 0..num_loops {
|
|
let src_value = input.memory.peek(index as u16 + offset);
|
|
input.registers.poke(index as u8, src_value);
|
|
debug!("POKING Register 0x{index:02x} with 0x{src_value:04x} using offset 0x{offset:04x}");
|
|
}
|
|
input.registers.poke_i(offset + 1);
|
|
}
|
|
Chip8CpuInstructions::XXXXERRORINSTRUCTION => {}
|
|
};
|
|
input.clone()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use crate::chip8::system_memory::{CHIP8FONT_0, CHIP8FONT_1, CHIP8FONT_2, CHIP8FONT_9};
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn smoke() {
|
|
assert!(true)
|
|
}
|
|
|
|
#[test]
|
|
fn encode_decode_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);
|
|
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)));
|
|
}
|
|
|
|
#[test]
|
|
fn decoder_test_invalid_instructions() {
|
|
let invalid_to_encode = [
|
|
0x5ab1, 0x5abf, 0x8ab8, 0x8abd, 0x8abf,
|
|
0x9ab1, 0x9abf, 0xea9d, 0xea9f, 0xeaa0,
|
|
0xeaa2, 0xf006, 0xf008
|
|
];
|
|
|
|
for i in invalid_to_encode {
|
|
assert_eq!(Chip8CpuInstructions::decode(i).encode(), 0xffff);
|
|
assert!(matches!(Chip8CpuInstructions::decode(i), Chip8CpuInstructions::XXXXERRORINSTRUCTION));
|
|
}
|
|
}
|
|
|
|
/// START OF THE EXECUTION TESTS
|
|
#[test]
|
|
fn sys_test() {
|
|
// 0x0nnn Exit to System Call
|
|
let mut x = Chip8Computer::new();
|
|
Chip8CpuInstructions::SysAddr(0).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
Chip8CpuInstructions::SysAddr(0xFA0).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0xFA0);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
Chip8CpuInstructions::SysAddr(0x0AF).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x0AF);
|
|
}
|
|
|
|
#[test]
|
|
fn jpaddr_test() {
|
|
// 0x1nnn Jump to Address
|
|
let mut x = Chip8Computer::new();
|
|
Chip8CpuInstructions::JpAddr(0).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
Chip8CpuInstructions::JpAddr(0xABC).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0xABC);
|
|
}
|
|
|
|
// ** test moved up so it can be used later
|
|
#[test]
|
|
fn ld_vx_byte_test() {
|
|
// 0x6xkk Set Vx = kk
|
|
let mut x = Chip8Computer::new();
|
|
Chip8CpuInstructions::LdVxByte(1, 0x12).execute(&mut x);
|
|
assert_eq!(x.registers.peek(1), 0x12);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
Chip8CpuInstructions::LdVxByte(2, 0x21).execute(&mut x);
|
|
assert_eq!(x.registers.peek(2), 0x21);
|
|
}
|
|
|
|
#[test]
|
|
fn sevxbyte_match_test() {
|
|
// 0x3xkk Skip next instruction if Vx = kk.
|
|
// The interpreter compares register Vx to kk,
|
|
// and if they are equal, increments the program counter by 2.
|
|
|
|
// test setup: Load value 0x84 into V1
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0x84);
|
|
Chip8CpuInstructions::SeVxByte(1, 0x48).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x202);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0x84);
|
|
Chip8CpuInstructions::SeVxByte(1, 0x84).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x204);
|
|
}
|
|
|
|
#[test]
|
|
fn se_vx_vy_test() {
|
|
// 0x4xkk Skip next instruction if Vx != kk
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0x84);
|
|
x.registers.poke(0x2, 0x84);
|
|
// skip, compare 0x84 to 0x84
|
|
Chip8CpuInstructions::SeVxVy(0x1, 0x2).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x204);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0x84);
|
|
x.registers.poke(0x2, 0x48);
|
|
Chip8CpuInstructions::SeVxVy(0x01, 0x02).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x202);
|
|
}
|
|
|
|
#[test]
|
|
fn ld_vx_vy_test() {
|
|
// 0x8xy0 Set value of Vy in Vx
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0x01);
|
|
x.registers.poke(0x02, 0x02);
|
|
Chip8CpuInstructions::LdVxVy(0x01, 0x02).execute(&mut x);
|
|
assert_eq!(x.registers.peek(1), 0x02);
|
|
}
|
|
|
|
#[test]
|
|
fn or_vx_vy_test() {
|
|
// 0x8xy1 Set Vx = Vx OR Vy
|
|
// 0b0101 0000 (0x50)
|
|
// | 0b0000 1010 (0x0A)
|
|
// 0b0101 1010 (0x5A)
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0b01010000);
|
|
x.registers.poke(0x02, 0b00001010);
|
|
Chip8CpuInstructions::OrVxVy(1, 2).execute(&mut x);
|
|
assert_eq!(x.registers.peek(1), 0b01011010);
|
|
}
|
|
|
|
#[test]
|
|
fn and_vx_vy_test() {
|
|
// 0x8xy2 Set Vx = Vx AND Vy
|
|
// 0b1111 1100 (0xFC)
|
|
// & 0b1100 1010 (0xCA)
|
|
// 0b1100 1000 (0xC8)
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0xFC);
|
|
x.registers.poke(0x02, 0xCA);
|
|
Chip8CpuInstructions::AndVxVy(1, 2).execute(&mut x);
|
|
assert_eq!(x.registers.peek(1), 0xC8);
|
|
}
|
|
|
|
#[test]
|
|
fn xor_vx_vy_test() {
|
|
// 0x8xy3 Set Vx = Vx XOR Vy
|
|
// 0b1111 1100 (0xFC)
|
|
// ^ 0b1100 1010 (0xCA)
|
|
// 0b0011 0110 (0x36)
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0b11111100);
|
|
x.registers.poke(0x02, 0b11001010);
|
|
Chip8CpuInstructions::XorVxVy(1, 2).execute(&mut x);
|
|
assert_eq!(x.registers.peek(1), 0b00110110);
|
|
}
|
|
|
|
#[test]
|
|
fn add_vx_vy_test() {
|
|
// 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
|
|
// T1 T2: Judgement Test
|
|
// 0x01 0xFF
|
|
// + 0x01 0x01
|
|
// 0x02 F0 0x00 F1
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x0f, 00);
|
|
x.registers.poke(0x01, 0x01);
|
|
x.registers.poke(0x02, 0x01);
|
|
Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0xf), 0x00);
|
|
assert_eq!(x.registers.peek(0x01), 0x02);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x0f, 0x00);
|
|
x.registers.poke(0x01, 0xff);
|
|
x.registers.poke(0x02, 0x01);
|
|
Chip8CpuInstructions::AddVxVy(1, 2).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0xf), 1);
|
|
assert_eq!(x.registers.peek(1), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn shr_vx_vy_test() {
|
|
/*
|
|
Set Vx = Vx SHR 1.
|
|
|
|
If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
|
|
*/
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x0f, 0x00);
|
|
x.registers.poke(0x01, 0b00001000);
|
|
x.registers.poke(0x02, 0b00000000);
|
|
Chip8CpuInstructions::ShrVxVy(0x1, 0x2).execute(&mut x); // 0b0000 0010 (0x02) (Not Set)
|
|
assert_eq!(x.registers.peek(1), 0b00000100);
|
|
assert_eq!(x.registers.peek(0xf), 0);
|
|
|
|
x = Chip8Computer::new();
|
|
x.registers.poke(0x0f, 0x00);
|
|
x.registers.poke(0x01, 0b00001001);
|
|
Chip8CpuInstructions::ShrVxVy(0x1, 0x2).execute(&mut x);
|
|
assert_eq!(x.registers.peek(1), 0b00000100);
|
|
assert_eq!(x.registers.peek(0xf), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn ldi_addr_test() {
|
|
let mut x = Chip8Computer::new();
|
|
Chip8CpuInstructions::LdIAddr(0x123).execute(&mut x);
|
|
assert_eq!(x.registers.peek_i(), 0x123);
|
|
assert_eq!(x.registers.peek_pc(), 0x202);
|
|
}
|
|
|
|
#[test]
|
|
fn jp_v0addr_test() {
|
|
let mut x = Chip8Computer::new();
|
|
/// jump to I + nnn
|
|
x.registers.poke(0x0, 0xff);
|
|
Chip8CpuInstructions::JpV0Addr(0x100).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x1FF);
|
|
}
|
|
|
|
#[test]
|
|
fn cls_test() {
|
|
let mut x = Chip8Computer::new();
|
|
Chip8CpuInstructions::CLS.execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x202);
|
|
}
|
|
|
|
#[test]
|
|
fn skip_next_instruction_ne_text() {
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0xf0);
|
|
Chip8CpuInstructions::SneVxByte(0x1, 0x0f).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x204);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0xf0);
|
|
Chip8CpuInstructions::SneVxByte(0x1, 0xf0).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x202);
|
|
}
|
|
|
|
#[test]
|
|
fn addivx_test() {
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke_i(0xabc);
|
|
x.registers.poke(0x0, 0x10);
|
|
Chip8CpuInstructions::AddIVx(0x0).execute(&mut x);
|
|
assert_eq!(x.registers.peek_i(), 0xacc);
|
|
}
|
|
|
|
#[test]
|
|
fn ldstvt_test() {
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0xf0);
|
|
Chip8CpuInstructions::LdStVx(0x01).execute(&mut x);
|
|
assert_eq!(x.sound_timer.current(), 0xf0);
|
|
x.sound_timer.tick();
|
|
x.sound_timer.tick();
|
|
x.sound_timer.tick();
|
|
assert_eq!(x.sound_timer.current(), 0xed);
|
|
}
|
|
|
|
#[test]
|
|
fn rnd_vx_byte_text() {
|
|
let mut x = Chip8Computer::new();
|
|
Chip8CpuInstructions::RndVxByte(0x1, 0x0f).execute(&mut x);
|
|
let new_value = x.registers.peek(0x1);
|
|
assert!(new_value < 0x10);
|
|
}
|
|
|
|
#[test]
|
|
fn add_vx_byte_test() {
|
|
let mut x = Chip8Computer::new();
|
|
// set a value in the register
|
|
x.registers.poke(0x01, 0xab);
|
|
// add 0x10 to register
|
|
Chip8CpuInstructions::AddVxByte(0x1, 0x10).execute(&mut x);
|
|
assert_eq!(x.registers.peek(1), 0xbb);
|
|
}
|
|
|
|
#[test]
|
|
fn sub_vx_vy_test() {
|
|
let mut x = Chip8Computer::new();
|
|
// load values in 2 registers
|
|
x.registers.poke(0x1, 0x10);
|
|
x.registers.poke(0x2, 0x08);
|
|
Chip8CpuInstructions::SubVxVy(0x1, 0x02).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0xf), 1);
|
|
assert_eq!(x.registers.peek(0x1), 0x8);
|
|
assert_eq!(x.registers.peek_pc(), 0x202);
|
|
}
|
|
|
|
#[test]
|
|
fn sne_vx_vy_test() {
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0x10);
|
|
x.registers.poke(0x2, 0x10);
|
|
Chip8CpuInstructions::SneVxVy(0x1, 0x2).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x202);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0x10);
|
|
x.registers.poke(0x2, 0x00);
|
|
Chip8CpuInstructions::SneVxVy(0x01, 0x02).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x204)
|
|
}
|
|
|
|
#[test]
|
|
fn ld_dt_vx_test() {
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0x10);
|
|
Chip8CpuInstructions::LdDtVx(0x1).execute(&mut x);
|
|
assert_eq!(x.delay_timer.current(), 0x10);
|
|
for i in 0..0x20 {
|
|
x.delay_timer.tick();
|
|
}
|
|
assert_eq!(x.delay_timer.current(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn ld_vx_dt_test() {
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0xf0);
|
|
Chip8CpuInstructions::LdDtVx(0x1).execute(&mut x);
|
|
x.delay_timer.tick();
|
|
x.delay_timer.tick();
|
|
x.delay_timer.tick();
|
|
assert_eq!(x.delay_timer.current(), 0xed);
|
|
}
|
|
|
|
#[test]
|
|
fn subn_vx_vy_test() {
|
|
// This instruction subtracts the value in
|
|
// register Vx from the value in register Vy and stores the result in register Vx.
|
|
// The subtraction is performed as follows: Vx = Vy - Vx. If Vy is less than Vx,
|
|
// the result will wrap around (due to the 8-bit nature of the registers).
|
|
// The carry flag (VF) is set to 1 if there is no borrow (i.e., Vy is greater
|
|
// than or equal to Vx), and it is set to 0 if there is a borrow.
|
|
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0xa0);
|
|
x.registers.poke(0x2, 0xab);
|
|
Chip8CpuInstructions::SubnVxVy(0x1, 0x2).execute(&mut x);
|
|
// expect the result to be 0x0b
|
|
assert_eq!(x.registers.peek(0x1), 0x0b);
|
|
// expect the vf register to be set to 1 as there was overflow
|
|
assert_eq!(x.registers.peek(0xf), 0x1);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0xab);
|
|
x.registers.poke(0x02, 0xa0);
|
|
Chip8CpuInstructions::SubnVxVy(0x1, 0x2).execute(&mut x);
|
|
|
|
// expect the result to be 11110101, -0xB, -11, 245, 0xF5
|
|
assert_eq!(x.registers.peek(0x1), 0xf5);
|
|
assert_eq!(x.registers.peek(0xf), 0x0);
|
|
}
|
|
|
|
#[test]
|
|
fn shl_vx_vy_test_1() {
|
|
// 8xyE - SHL Vx {, Vy}
|
|
// Set Vx = Vx SHL 1.
|
|
//
|
|
// If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.
|
|
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0b00100000);
|
|
Chip8CpuInstructions::ShlVxVy(0x1, 0x1).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0x1), 0b01000000);
|
|
assert_eq!(x.registers.peek(0xf), 0x0);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x1, 0b10101010);
|
|
Chip8CpuInstructions::ShlVxVy(0x1, 0x1).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0x1), 0b01010100);
|
|
assert_eq!(x.registers.peek(0xf), 0x1);
|
|
}
|
|
|
|
#[test]
|
|
fn ld_f_vx_test() {
|
|
// Fx29 - LD F, Vx
|
|
// Set I = location of sprite for digit Vx.
|
|
//
|
|
// The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font.
|
|
let mut x = Chip8Computer::new();
|
|
// target_sprite = 2
|
|
// target_offset = 0x5
|
|
x.registers.poke(0x1, 0x2);
|
|
Chip8CpuInstructions::LdFVx(0x1).execute(&mut x);
|
|
|
|
assert_eq!(x.registers.peek_i(), 10);
|
|
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0x06);
|
|
Chip8CpuInstructions::LdFVx(0x1).execute(&mut x);
|
|
assert_eq!(x.registers.peek_i(), 30);
|
|
}
|
|
|
|
#[test]
|
|
fn ld_b_vx_test() {
|
|
// Fx33 - LD B, Vx
|
|
// Store BCD representation of Vx in memory locations I, I+1, and I+2.
|
|
//
|
|
// The interpreter takes the decimal value of Vx, and places the hundreds digit
|
|
// in memory at location in I, the tens digit at location I+1,
|
|
// and the ones digit at location I+2.
|
|
let mut x = Chip8Computer::new();
|
|
|
|
// load the value 123 (0x7b)
|
|
x.registers.poke(0x1, 0x7b);
|
|
x.registers.poke_i(0x500);
|
|
Chip8CpuInstructions::LdBVx(0x1).execute(&mut x);
|
|
assert_eq!(x.memory.peek(0x500), 0x1);
|
|
assert_eq!(x.memory.peek(0x501), 0x2);
|
|
assert_eq!(x.memory.peek(0x502), 0x3);
|
|
}
|
|
|
|
#[test]
|
|
fn ld_i_vx_test() {
|
|
// Store registers V0 through Vx in memory starting at location I.
|
|
//
|
|
// The interpreter copies the values of registers V0 through Vx into memory,
|
|
// starting at the address in I.
|
|
let mut x = Chip8Computer::new();
|
|
|
|
// Load Registers.
|
|
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
|
|
for (idx, val) in to_load.iter().enumerate() {
|
|
x.registers.poke(idx as u8, *val);
|
|
}
|
|
x.registers.poke_i(0x500);
|
|
|
|
Chip8CpuInstructions::LdIVx(to_load.len() as u8).execute(&mut x);
|
|
|
|
// Verify the values are in memory from 0x500 to 0x507
|
|
for (idx, value) in to_load.iter().enumerate() {
|
|
assert_eq!(x.memory.peek(0x500 + idx as u16), *value);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ld_vx_i_test() {
|
|
// Read registers V0 through Vx from memory starting at location I.
|
|
//
|
|
// The interpreter reads values from memory starting at location I into registers V0 through Vx.
|
|
|
|
let mut x = Chip8Computer::new();
|
|
|
|
let base_offset = 0x500;
|
|
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
|
|
|
|
// start by setting values in memory
|
|
for (idx, memory) in to_load.iter().enumerate() {
|
|
let target_address = base_offset + idx;
|
|
let target_value = *memory;
|
|
x.memory.poke(target_address as u16, target_value);
|
|
}
|
|
// where to load from
|
|
x.registers.poke_i(0x500);
|
|
// how much to load
|
|
x.registers.poke(0x6, to_load.len() as u8);
|
|
|
|
// then copying them values memory to registers
|
|
Chip8CpuInstructions::LdVxI(0x6).execute(&mut x);
|
|
|
|
// now check that we have the right values in our registers
|
|
for (idx, value) in to_load.iter().enumerate() {
|
|
assert_eq!(x.registers.peek(idx as u8), *value);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn Sknkpvx_test() {
|
|
// ExA1 - SKNP Vx
|
|
// Skip next instruction if key with the value of Vx is not pressed.
|
|
//
|
|
// Checks the keyboard,
|
|
// and if the key corresponding to the value of Vx is currently in the up position,
|
|
// PC is increased by 2.
|
|
let mut x = Chip8Computer::new();
|
|
x.keypad.push_key(0x5);
|
|
x.registers.poke(0x1, 0x5);
|
|
Chip8CpuInstructions::SnkpVx(0x1).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x202);
|
|
x.keypad.release_key(0x5);
|
|
Chip8CpuInstructions::SnkpVx(0x1).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x206);
|
|
}
|
|
|
|
#[test]
|
|
fn skpvx_test() {
|
|
// Ex9E - SKP Vx
|
|
// Skip next instruction if key with the value of Vx is pressed.
|
|
//
|
|
// Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2.
|
|
let mut x = Chip8Computer::new();
|
|
x.keypad.push_key(0x5);
|
|
x.registers.poke(0x1, 0x5);
|
|
Chip8CpuInstructions::SkpVx(0x1).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x204);
|
|
|
|
x.keypad.release_key(0x5);
|
|
Chip8CpuInstructions::SkpVx(0x1).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x206);
|
|
}
|
|
|
|
#[test]
|
|
fn draw_nibble_vx_vy_n_test() {
|
|
let mut x = Chip8Computer::new();
|
|
let x_register = 0x1;
|
|
let y_register = 0x2;
|
|
let x_offset = 1;
|
|
let y_offset = 2;
|
|
let char_offset = 0x0A;
|
|
|
|
// now lets set the X and Y to 1,2
|
|
x.registers.poke(x_register, x_offset);
|
|
x.registers.poke(y_register, y_offset);
|
|
x.registers.poke_i(char_offset);
|
|
// we are using 5 rows.
|
|
Chip8CpuInstructions::DrawVxVyNibble(x_register, y_register, 5).execute(&mut x);
|
|
|
|
// now check that video memory has the values at
|
|
// 1,2->1,9
|
|
// 2,2->2,9
|
|
// 3,2->3,9
|
|
// 4,2->4,9
|
|
// 5,2->5,9
|
|
// let byte_to_check = CHIP8FONT_0[0];
|
|
for row_in_sprite in 0..5 {
|
|
let row_data = CHIP8FONT_2[row_in_sprite];
|
|
for bit_in_byte in 0..8 {
|
|
let data_offset = (x_offset
|
|
as u16 + row_in_sprite as u16) * 64 + (bit_in_byte + y_offset) as u16;
|
|
let real_bit_in_byte = 7 - bit_in_byte;
|
|
let shifted_one = 0x01 << real_bit_in_byte;
|
|
let one_shift_set = (shifted_one & row_data) > 0;
|
|
debug!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]");
|
|
debug!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}",
|
|
bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, one_shift_set, row_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn sub_test() {
|
|
// 2nnn
|
|
// Call a subroutine at 2nnn
|
|
let mut x = Chip8Computer::new();
|
|
Chip8CpuInstructions::CallAddr(0x124).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x124);
|
|
assert_eq!(x.stack.depth(), 1);
|
|
Chip8CpuInstructions::CallAddr(0x564).execute(&mut x);
|
|
assert_eq!(x.registers.peek_pc(), 0x564);
|
|
assert_eq!(x.stack.depth(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn ret_test() {
|
|
// SETUP
|
|
// Return from a subroutine.
|
|
let mut x = Chip8Computer::new();
|
|
x.stack.push(&0x132);
|
|
x.stack.push(&0xabc);
|
|
// EXECUTE
|
|
Chip8CpuInstructions::RET.execute(&mut x);
|
|
// VERIFY
|
|
assert_eq!(x.registers.peek_pc(), 0xabc);
|
|
assert_eq!(x.stack.depth(), 1);
|
|
// EXECUTE
|
|
Chip8CpuInstructions::RET.execute(&mut x);
|
|
// VERIFY
|
|
assert_eq!(x.registers.peek_pc(), 0x132);
|
|
assert_eq!(x.stack.depth(), 0);
|
|
}
|
|
|
|
|
|
#[test]
|
|
fn ldvxk_test() {
|
|
// SETUP
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0x01);
|
|
Chip8CpuInstructions::LdVxK(0x1).execute(&mut x);
|
|
assert!(matches!(x.state, WaitingForKey));
|
|
}
|
|
|
|
#[test]
|
|
fn series8xy4_corex_tests() {
|
|
/// 8xy4
|
|
/// Set Vx = Vx + Vy
|
|
/// Set VF=1 if Carry
|
|
///
|
|
|
|
// 1 + 1
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0x01);
|
|
x.registers.poke(0x02, 0x01);
|
|
Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0x01), 0x02);
|
|
assert_eq!(x.registers.peek(0x0f), 0x00);
|
|
|
|
// 255+1
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0xff);
|
|
x.registers.poke(0x02, 0x01);
|
|
Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0x01), 0x00);
|
|
assert_eq!(x.registers.peek(0x0f), 0x01);
|
|
|
|
// 128+192
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 128);
|
|
x.registers.poke(0x02, 192);
|
|
Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0x01), 64);
|
|
assert_eq!(x.registers.peek(0x0f), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn series8xy6_corex_tests() {
|
|
// 8xy6 - SHR Vx {, Vy}
|
|
// Set Vx = Vx SHR 1.
|
|
//
|
|
// If the least-significant bit of Vx is 1, then VF is set to 1,
|
|
// otherwise 0. Then Vx is divided by 2.
|
|
let mut x = Chip8Computer::new();
|
|
// 0b10101010 -> 0b01010101
|
|
x.registers.poke(0x01, 0b10101010);
|
|
x.registers.poke(0x0f, 0x0);
|
|
|
|
Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0x01), 0b01010100);
|
|
assert_eq!(x.registers.peek(0x0f), 1);
|
|
|
|
Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0x01), 0b10101000);
|
|
assert_eq!(x.registers.peek(0x0f), 0x00);
|
|
|
|
Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0x01), 0b01010000);
|
|
assert_eq!(x.registers.peek(0x0f), 0x01);
|
|
|
|
Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0x01), 0b10100000);
|
|
assert_eq!(x.registers.peek(0x0f), 0x00);
|
|
|
|
Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x);
|
|
assert_eq!(x.registers.peek(0x01), 0b01000000);
|
|
assert_eq!(x.registers.peek(0x0f), 0x01);
|
|
}
|
|
|
|
#[test]
|
|
fn random_produces_different_numbers() {
|
|
let mut x = Chip8Computer::new();
|
|
x.registers.poke(0x01, 0x00);
|
|
let first_number = Chip8CpuInstructions::RndVxByte(0x01, 0xff).execute(&mut x).registers.peek(0x01);
|
|
let second_number = Chip8CpuInstructions::RndVxByte(0x01, 0xff).execute(&mut x).registers.peek(0x01);
|
|
assert_ne!(first_number, second_number);
|
|
}
|
|
}
|