1206 lines
51 KiB
Rust
1206 lines
51 KiB
Rust
use crate::chip8::computer::Chip8Computer;
|
|
use crate::chip8::cpu_states::Chip8CpuStates::WaitingForKey;
|
|
use crate::chip8::instructions::Chip8CpuInstructions::*;
|
|
use crate::chip8::quirk_modes::QuirkMode;
|
|
use crate::chip8::util::InstructionUtil;
|
|
use crate::constants::*;
|
|
use log::debug;
|
|
use rand::Rng;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::fmt::{Debug, Display, Formatter};
|
|
use std::ops::BitAnd;
|
|
|
|
/*
|
|
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, Clone, Serialize, Deserialize)]
|
|
pub enum Chip8CpuInstructions {
|
|
/// 0nnn
|
|
/// Exit to System Call at nnn
|
|
SYS(u16),
|
|
/// 00E0
|
|
/// Clear the display.
|
|
CLS,
|
|
/// 00EE
|
|
/// Return from a subroutine.
|
|
///
|
|
/// The interpreter sets the program counter to the address at the top of the stack,
|
|
/// then subtracts 1 from the stack pointer.
|
|
RET,
|
|
/// 1nnn
|
|
/// Jump to location nnn.
|
|
///
|
|
/// The interpreter sets the program counter to nnn.
|
|
JPA(u16),
|
|
/// 2nnn
|
|
/// Call subroutine at nnn.
|
|
///
|
|
/// The interpreter increments the stack pointer, then puts the current PC on the top
|
|
/// of the stack. The PC is then set to nnn.
|
|
CALL(u16),
|
|
/// 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.
|
|
SEX(u8, u8),
|
|
/// 4xkk
|
|
/// 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.
|
|
SNEB(u8, u8),
|
|
/// 5xy0
|
|
/// Skip next instruction if Vx = Vy.
|
|
///
|
|
/// The interpreter compares register Vx to register Vy, and if they are equal, increments
|
|
/// the program counter by 2.
|
|
SEY(u8, u8),
|
|
/// 6xkk
|
|
/// Set Vx = kk
|
|
LDR(u8, u8),
|
|
/// 7xkk
|
|
/// The interpreter puts the value kk into register Vx.
|
|
ADD(u8, u8),
|
|
/// 8xy0
|
|
/// Adds the value kk to the value of register Vx, then stores the result in Vx.
|
|
LDRY(u8, u8),
|
|
/// 8xy1
|
|
/// Stores the value of register Vy in register Vx.
|
|
OR(u8, u8),
|
|
/// 8xy2
|
|
/// Set Vx = Vx OR Vy.
|
|
///
|
|
/// Performs a bitwise OR on the values of Vx and Vy, then stores the result in Vx.
|
|
/// A bitwise OR compares the corrseponding bits from two values, and if either
|
|
/// bit is 1, then the same bit in the result is also 1. Otherwise, it is 0.
|
|
AND(u8, u8),
|
|
/// 8xy3
|
|
/// Set Vx = Vx AND Vy.
|
|
///
|
|
/// Performs a bitwise AND on the values of Vx and Vy, then stores the result in Vx.
|
|
/// A bitwise AND compares the corrseponding bits from two values, and if both bits
|
|
/// are 1, then the same bit in the result is also 1. Otherwise, it is 0.
|
|
ORY(u8, u8),
|
|
/// 8xy4
|
|
/// Set Vx = Vx XOR Vy.
|
|
///
|
|
/// Performs a bitwise exclusive OR on the values of Vx and Vy, then stores the
|
|
/// result in Vx. An exclusive OR compares the corrseponding bits from two values,
|
|
/// and if the bits are not both the same, then the corresponding bit in the result
|
|
/// is set to 1. Otherwise, it is 0.
|
|
ADDR(u8, u8),
|
|
/// 8xy5
|
|
/// Set Vx = Vx + Vy, set VF = carry.
|
|
///
|
|
/// The values of Vx and Vy are added together. If the result is greater than 8 bits
|
|
/// (i.e., > 255,) VF is set to 1, otherwise 0. Only the lowest 8 bits of the result
|
|
/// are kept, and stored in Vx.
|
|
SUB(u8, u8),
|
|
/// 8xy6
|
|
/// 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.
|
|
SHR(u8, u8),
|
|
/// 8xy7
|
|
/// If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx
|
|
/// is divided by 2.
|
|
SUBC(u8, u8),
|
|
/// 8xye
|
|
/// 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.
|
|
SHL(u8, u8),
|
|
/// 9xy0
|
|
/// 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.
|
|
SNEY(u8, u8),
|
|
/// Annn
|
|
/// Set I = nnn.
|
|
///
|
|
/// The value of register I is set to nnn
|
|
LDIA(u16),
|
|
/// Bnnn
|
|
/// Jump to location nnn + V0.
|
|
///
|
|
/// The program counter is set to nnn plus the value of V0.
|
|
JPI(u16),
|
|
/// Cxkk
|
|
/// 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. See instruction
|
|
/// 8xy2 for more information on AND.
|
|
RND(u8, u8),
|
|
/// Dxyn
|
|
/// Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.
|
|
///
|
|
/// In Chip-8,
|
|
///
|
|
/// 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. See instruction 8xy3 for more information on XOR, and section 2.4,
|
|
/// Display, for more information on the Chip-8 screen and sprites.
|
|
///
|
|
/// In SCHIP
|
|
///
|
|
/// Show N-byte sprite from M(I) at Coords (VX,VY) VF = Collision.
|
|
/// If N=0 AND ExtendedMode, 16x16 sprite
|
|
DRW(u8, u8, u8),
|
|
/// Ex9E
|
|
/// 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.
|
|
SKP(u8),
|
|
/// ExA1
|
|
/// 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.
|
|
/// On XO Chip, Skips 2 more bytes when the instruction is 4 bytes long
|
|
SKNP(u8),
|
|
/// Fx07
|
|
/// The value of DT is placed into Vx.
|
|
LDRD(u8),
|
|
/// Fx0A
|
|
/// 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.
|
|
LDRK(u8),
|
|
/// Fx15
|
|
/// Set delay timer = Vx.
|
|
///
|
|
/// DT is set equal to the value of Vx.
|
|
LDD(u8), // 0xFx15 Set Delay Timer
|
|
/// Fx18
|
|
/// Set sound timer = Vx.
|
|
///
|
|
/// ST is set equal to the value of Vx.
|
|
LDIS(u8),
|
|
/// Fx1E - ADD I, Vx
|
|
/// Set I = I + Vx.
|
|
///
|
|
/// The values of I and Vx are added, and the results are stored in I.
|
|
ADDI(u8),
|
|
/// 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.
|
|
LDFX(u8),
|
|
/// 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.
|
|
BCD(u8),
|
|
/// Fx55 - LD [I], Vx
|
|
/// 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.
|
|
LDIX(u8),
|
|
/// Fx65 - LD Vx, [I]
|
|
/// 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.
|
|
LDRI(u8),
|
|
XXXXERRORINSTRUCTION,
|
|
// 00Dn - CHIP8 - SCHIP * XOCHIP
|
|
/* START OF SCHIP-8 */
|
|
/// 00CN - CHIP8 * SCHIP * XOCHIP
|
|
///
|
|
/// Scrolll Display N Lines Down
|
|
SCD(u8),
|
|
/// 00FB
|
|
///
|
|
/// Scroll 4 lines Right
|
|
SCR,
|
|
/// 00FC
|
|
///
|
|
/// Scroll 4 lines Left
|
|
SCL,
|
|
/// 00FE
|
|
///
|
|
/// Disable Extended Mode
|
|
LOW,
|
|
/// 00FF
|
|
///
|
|
/// Enable Extended Mode
|
|
HIGH,
|
|
/// 00FD
|
|
///
|
|
/// Exit App
|
|
EXIT,
|
|
/// FX30
|
|
///
|
|
/// Point I to 10 yte font sprite for digit VX (0..9)
|
|
LDF2(u8),
|
|
/// FX75
|
|
///
|
|
/// Store V0..VX in RPL user flags (X <= 7
|
|
STR(u8),
|
|
/// FX85
|
|
///
|
|
/// Load V0..VX from RPL user flags (X <= 7)
|
|
LIDR(u8),
|
|
/// 0xBxNN
|
|
///
|
|
/// Jump to Address XNN+Vx
|
|
JPX(u8, u16),
|
|
/// 0x00Cn
|
|
///
|
|
/// scroll screen content down N pixel, in XO-CHIP only selected bit
|
|
/// planes are scrolled (Quirks are HP48 specific)
|
|
SCU(u8),
|
|
/// 0xNNNN
|
|
///
|
|
/// data word
|
|
/// used for specifying data to be used in system memory
|
|
DW(u16),
|
|
}
|
|
|
|
impl Chip8CpuInstructions {
|
|
pub fn name(&self) -> &str {
|
|
match self {
|
|
Chip8CpuInstructions::ADDI(_) => INST_ADDI,
|
|
Chip8CpuInstructions::ADD(_, _) => INST_ADD,
|
|
Chip8CpuInstructions::ADDR(_, _) => INST_ADDR,
|
|
Chip8CpuInstructions::AND(_, _) => INST_AND,
|
|
Chip8CpuInstructions::CLS => INST_CLS,
|
|
Chip8CpuInstructions::CALL(_) => INST_CALL,
|
|
Chip8CpuInstructions::DRW(_, _, _) => INST_DRW,
|
|
Chip8CpuInstructions::EXIT => INST_EXIT,
|
|
Chip8CpuInstructions::JPA(_) => INST_JPA,
|
|
Chip8CpuInstructions::JPI(_) => INST_JPI,
|
|
Chip8CpuInstructions::BCD(_) => INST_BCD,
|
|
Chip8CpuInstructions::LDD(_) => INST_LDD,
|
|
Chip8CpuInstructions::LDFX(_) => INST_LDF,
|
|
Chip8CpuInstructions::LDF2(_) => INST_LDF2,
|
|
Chip8CpuInstructions::LDIA(_) => INST_LDIA,
|
|
Chip8CpuInstructions::LDIX(_) => INST_LDIX,
|
|
Chip8CpuInstructions::LIDR(_) => INST_LIDR,
|
|
Chip8CpuInstructions::LDIS(_) => INST_LDIS,
|
|
Chip8CpuInstructions::LDR(_, _) => INST_LDR,
|
|
Chip8CpuInstructions::LDRD(_) => INST_LDRD,
|
|
Chip8CpuInstructions::LDRI(_) => INST_LDRI,
|
|
Chip8CpuInstructions::LDRK(_) => INST_LDRK,
|
|
Chip8CpuInstructions::LDRY(_, _) => INST_LDRY,
|
|
Chip8CpuInstructions::OR(_, _) => INST_OR,
|
|
Chip8CpuInstructions::RET => INST_RET,
|
|
Chip8CpuInstructions::RND(_, _) => INST_RND,
|
|
Chip8CpuInstructions::SCD(_) => INST_SCD,
|
|
Chip8CpuInstructions::SCL => INST_SCL,
|
|
Chip8CpuInstructions::SCR => INST_SCR,
|
|
Chip8CpuInstructions::SEX(_, _) => INST_SEX,
|
|
Chip8CpuInstructions::SEY(_, _) => INST_SEY,
|
|
Chip8CpuInstructions::SHL(_, _) => INST_SHL,
|
|
Chip8CpuInstructions::SHR(_, _) => INST_SHR,
|
|
Chip8CpuInstructions::SKP(_) => INST_SKP,
|
|
Chip8CpuInstructions::SNEB(_, _) => INST_SNEB,
|
|
Chip8CpuInstructions::SNEY(_, _) => INST_SNEY,
|
|
Chip8CpuInstructions::SKNP(_) => INST_SKNP,
|
|
Chip8CpuInstructions::STR(_) => INST_STR,
|
|
Chip8CpuInstructions::SUB(_, _) => INST_SUB,
|
|
Chip8CpuInstructions::SUBC(_, _) => INST_SUBC,
|
|
Chip8CpuInstructions::SYS(_) => INST_SYS,
|
|
Chip8CpuInstructions::LOW => INST_LOW,
|
|
Chip8CpuInstructions::HIGH => INST_HIGH,
|
|
Chip8CpuInstructions::ORY(_, _) => INST_ORY,
|
|
JPX(_, _) => INST_JPX,
|
|
XXXXERRORINSTRUCTION => "XX ERROR XX",
|
|
SCU(_) => INST_SCU,
|
|
DW(_) => INST_DW,
|
|
}
|
|
}
|
|
|
|
pub fn operands(&self) -> String {
|
|
match self {
|
|
JPX(x, addr) => {
|
|
let addr_for_display = (*x as u16) << 8 | *addr;
|
|
format!("0x{x:02x}, 0x{addr_for_display:04x}")
|
|
}
|
|
Chip8CpuInstructions::DW(addr)
|
|
| Chip8CpuInstructions::SYS(addr)
|
|
| Chip8CpuInstructions::JPI(addr)
|
|
| Chip8CpuInstructions::JPA(addr)
|
|
| Chip8CpuInstructions::LDIA(addr)
|
|
| Chip8CpuInstructions::CALL(addr) => {
|
|
format!("0x{addr:04x}")
|
|
}
|
|
Chip8CpuInstructions::SEX(x, byte)
|
|
| Chip8CpuInstructions::SNEB(x, byte)
|
|
| Chip8CpuInstructions::LDR(x, byte)
|
|
| Chip8CpuInstructions::RND(x, byte)
|
|
| Chip8CpuInstructions::ADD(x, byte) => {
|
|
format!("0x{x:02x}, 0x{byte:02x}")
|
|
}
|
|
// Reg, Reg
|
|
SEY(x, y)
|
|
| Chip8CpuInstructions::LDRY(x, y)
|
|
| Chip8CpuInstructions::OR(x, y)
|
|
| Chip8CpuInstructions::AND(x, y)
|
|
| Chip8CpuInstructions::ORY(x, y)
|
|
| Chip8CpuInstructions::ADDR(x, y)
|
|
| Chip8CpuInstructions::SUB(x, y)
|
|
| Chip8CpuInstructions::SHR(x, y)
|
|
| Chip8CpuInstructions::SUBC(x, y)
|
|
| Chip8CpuInstructions::SHL(x, y)
|
|
| Chip8CpuInstructions::SNEY(x, y) => {
|
|
format!("0x{x:01x}, 0x{y:01x}")
|
|
}
|
|
// Reg, Reg, Nibble
|
|
Chip8CpuInstructions::DRW(x, y, nibble) => {
|
|
format!("0x{x:02x}, 0x{y:02x}, 0x{nibble:02x}")
|
|
}
|
|
// Registers. 0-F
|
|
Chip8CpuInstructions::SCU(x)
|
|
| Chip8CpuInstructions::LDD(x)
|
|
| Chip8CpuInstructions::LDIS(x)
|
|
| Chip8CpuInstructions::ADDI(x)
|
|
| Chip8CpuInstructions::LDFX(x)
|
|
| Chip8CpuInstructions::BCD(x)
|
|
| Chip8CpuInstructions::LDIX(x)
|
|
| Chip8CpuInstructions::LDRD(x)
|
|
| Chip8CpuInstructions::LDRK(x)
|
|
| Chip8CpuInstructions::LDRI(x)
|
|
| Chip8CpuInstructions::LDF2(x)
|
|
| Chip8CpuInstructions::STR(x)
|
|
| Chip8CpuInstructions::LIDR(x)
|
|
| Chip8CpuInstructions::SCD(x)
|
|
| Chip8CpuInstructions::SKNP(x)
|
|
| Chip8CpuInstructions::SKP(x) => {
|
|
format!("0x{x:02x}")
|
|
}
|
|
Chip8CpuInstructions::EXIT
|
|
| Chip8CpuInstructions::HIGH
|
|
| Chip8CpuInstructions::LOW
|
|
| Chip8CpuInstructions::SCL
|
|
| Chip8CpuInstructions::XXXXERRORINSTRUCTION
|
|
| Chip8CpuInstructions::SCR
|
|
| Chip8CpuInstructions::CLS
|
|
| Chip8CpuInstructions::RET => String::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for Chip8CpuInstructions {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
let ops = self.operands();
|
|
let space = if ops.is_empty() { "" } else { " " };
|
|
write!(f, "{}{}{}", self.name(), space, ops)
|
|
}
|
|
}
|
|
|
|
impl Chip8CpuInstructions {
|
|
pub fn from_str(input: &str) -> Chip8CpuInstructions {
|
|
let mut parts = input.split(" ");
|
|
// print!("THERE ARE {} PARTS", parts.clone().count());
|
|
let first_part = parts.next().unwrap_or("").to_ascii_uppercase();
|
|
// take the next value...
|
|
// ...strip off the extra...
|
|
// ...convert it to an integer from base 16
|
|
let param1 = u16::from_str_radix(
|
|
parts
|
|
.next()
|
|
.unwrap_or("0")
|
|
.to_ascii_lowercase()
|
|
.trim_start_matches("0x")
|
|
.trim_end_matches(","),
|
|
16,
|
|
)
|
|
.unwrap_or(0);
|
|
let param2 = u16::from_str_radix(
|
|
parts
|
|
.next()
|
|
.unwrap_or("0")
|
|
.to_ascii_lowercase()
|
|
.trim_start_matches("0x")
|
|
.trim_end_matches(","),
|
|
16,
|
|
)
|
|
.unwrap_or(0);
|
|
let param3 = u16::from_str_radix(
|
|
parts
|
|
.next()
|
|
.unwrap_or("0")
|
|
.to_ascii_lowercase()
|
|
.trim_start_matches("0x")
|
|
.trim_end_matches(","),
|
|
16,
|
|
)
|
|
.unwrap_or(0);
|
|
println!(
|
|
"\tFirst part is {:?} / {:?} / {:?} / {:?}",
|
|
first_part, param1, param2, param3
|
|
);
|
|
match first_part.as_str() {
|
|
INST_ADDI => ADDI(param1 as u8),
|
|
INST_ADD => ADD(param1 as u8, param2 as u8),
|
|
INST_CLS => CLS,
|
|
INST_DRW => DRW(param1 as u8, param2 as u8, param3 as u8),
|
|
INST_CALL => CALL(param1),
|
|
INST_SYS => SYS(param1),
|
|
INST_RET => RET,
|
|
INST_JPA => JPA(param1),
|
|
INST_JPI => JPI(param1),
|
|
INST_SEX => SEX(param1 as u8, param2 as u8),
|
|
INST_SNEB => SNEB(param1 as u8, param2 as u8),
|
|
INST_SCD => SCD(param1 as u8),
|
|
INST_STR => STR(param1 as u8),
|
|
INST_SCL => SCL,
|
|
INST_EXIT => EXIT,
|
|
INST_LOW => LOW,
|
|
INST_HIGH => HIGH,
|
|
INST_SEY => SEY(param1 as u8, param2 as u8),
|
|
INST_LDRY => LDRY(param1 as u8, param2 as u8),
|
|
INST_LDR => LDR(param1 as u8, param2 as u8),
|
|
INST_OR => OR(param1 as u8, param2 as u8),
|
|
INST_AND => AND(param1 as u8, param2 as u8),
|
|
INST_ORY => ORY(param1 as u8, param2 as u8),
|
|
INST_ADDR => ADDR(param1 as u8, param2 as u8),
|
|
INST_SUB => SUB(param1 as u8, param2 as u8),
|
|
INST_SHR => SHR(param1 as u8, param2 as u8),
|
|
INST_SHL => SHL(param1 as u8, param2 as u8),
|
|
INST_SUBC => SUBC(param1 as u8, param2 as u8),
|
|
INST_SNEY => SNEY(param1 as u8, param2 as u8),
|
|
INST_LDIA => LDIA(param1),
|
|
INST_RND => RND(param1 as u8, param2 as u8),
|
|
INST_SKP => SKP(param1 as u8),
|
|
INST_SKNP => SKNP(param1 as u8),
|
|
INST_LDRD => LDRD(param1 as u8),
|
|
INST_LDRK => LDRK(param1 as u8),
|
|
INST_LDRI => LDRI(param1 as u8),
|
|
INST_BCD => BCD(param1 as u8),
|
|
INST_LDF => LDFX(param1 as u8),
|
|
INST_LDF2 => LDF2(param1 as u8),
|
|
INST_LDIX => LDIX(param1 as u8),
|
|
INST_LIDR => LIDR(param1 as u8),
|
|
INST_LDIS => LDIS(param1 as u8),
|
|
INST_LDD => LDD(param1 as u8),
|
|
INST_JPX => JPX(param1 as u8, param2),
|
|
INST_DW => DW(param1 as u16),
|
|
_ => XXXXERRORINSTRUCTION,
|
|
}
|
|
}
|
|
|
|
pub fn encode(&self) -> u16 {
|
|
match self {
|
|
Chip8CpuInstructions::SYS(target) => target & 0x0FFF,
|
|
Chip8CpuInstructions::CLS => 0x00E0,
|
|
Chip8CpuInstructions::RET => 0x00EE,
|
|
Chip8CpuInstructions::JPA(new_addr) => 0x1000 | (new_addr & 0x0FFF),
|
|
Chip8CpuInstructions::CALL(address) => 0x2000 | (address & 0x0FFF),
|
|
Chip8CpuInstructions::SEX(vx_register, byte) => {
|
|
0x3000 | ((*vx_register as u16) << 8) | (*byte as u16)
|
|
}
|
|
Chip8CpuInstructions::SNEB(vx_register, byte) => {
|
|
0x4000 | ((*vx_register as u16) << 8) | (*byte as u16)
|
|
}
|
|
Chip8CpuInstructions::SEY(x_register, y_register) => {
|
|
0x5000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
|
}
|
|
Chip8CpuInstructions::LDR(x_register, byte) => {
|
|
0x6000 | ((*x_register as u16) << 8) | (*byte as u16)
|
|
}
|
|
Chip8CpuInstructions::ADD(x_register, byte) => {
|
|
0x7000 | ((*x_register as u16) << 8) | (*byte as u16)
|
|
}
|
|
Chip8CpuInstructions::LDRY(x_register, y_register) => {
|
|
0x8000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
|
}
|
|
Chip8CpuInstructions::OR(x_register, y_register) => {
|
|
0x8001 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
|
}
|
|
Chip8CpuInstructions::AND(x_register, y_register) => {
|
|
0x8002 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
|
}
|
|
Chip8CpuInstructions::ORY(x_register, y_register) => {
|
|
0x8003 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
|
}
|
|
Chip8CpuInstructions::ADDR(x_register, y_register) => {
|
|
0x8004 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
|
}
|
|
Chip8CpuInstructions::SUB(x_register, y_register) => {
|
|
0x8005 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
|
}
|
|
Chip8CpuInstructions::SHR(x_register, y_register) => {
|
|
0x8006 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
|
}
|
|
Chip8CpuInstructions::SUBC(x_register, y_register) => {
|
|
0x8007 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
|
}
|
|
Chip8CpuInstructions::SHL(x_register, y_register) => {
|
|
0x800E | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
|
}
|
|
Chip8CpuInstructions::SNEY(x_register, y_register) => {
|
|
0x9000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
|
}
|
|
Chip8CpuInstructions::LDIA(addr) => 0xA000 | addr,
|
|
Chip8CpuInstructions::JPI(addr) => 0xB000 | addr,
|
|
JPX(x_register, addr) => (0xb000 | (*x_register as u16) << 8) | *addr,
|
|
Chip8CpuInstructions::RND(x_register, byte) => {
|
|
0xC000 | ((*x_register as u16) << 8) | (*byte as u16)
|
|
}
|
|
Chip8CpuInstructions::DRW(x_register, y_register, height) => {
|
|
0xD000
|
|
| ((*x_register as u16) << 8)
|
|
| ((*y_register as u16) << 4)
|
|
| (*height as u16)
|
|
}
|
|
Chip8CpuInstructions::SKP(x_register) => 0xE09E | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::SKNP(x_register) => 0xE0A1 | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::LDRD(x_register) => 0xF007 | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::LDRK(x_register) => 0xF00A | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::LDD(x_register) => 0xF015 | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::LDIS(x_register) => 0xF018 | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::ADDI(x_register) => 0xF01E | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::LDFX(x_register) => 0xF029 | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::BCD(x_register) => 0xF033 | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::LDIX(x_register) => 0xF055 | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::LDRI(x_register) => 0xF065 | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::SCD(x_register) => 0x00C0 | (*x_register as u16),
|
|
Chip8CpuInstructions::SCR => 0x00FB,
|
|
Chip8CpuInstructions::SCL => 0x00FC,
|
|
Chip8CpuInstructions::LOW => 0x00FE,
|
|
Chip8CpuInstructions::HIGH => 0x00FF,
|
|
Chip8CpuInstructions::EXIT => 0x00FD,
|
|
Chip8CpuInstructions::LDF2(x_register) => 0xF030 | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::STR(x_register) => 0xF075 | ((*x_register as u16) << 8),
|
|
Chip8CpuInstructions::LIDR(x_register) => 0xF085 | ((*x_register as u16) << 8),
|
|
SCU(x_register) => 0x00D0 | (*x_register as u16),
|
|
DW(address) => *address as u16,
|
|
XXXXERRORINSTRUCTION => 0x0000,
|
|
}
|
|
}
|
|
pub fn decode(input: u16, quirk_mode: &QuirkMode) -> 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 = InstructionUtil::read_upper_byte_lower_nibble(input);
|
|
let last_byte = (input & 0xFF) as u8;
|
|
let last_nibble = (input & 0xF) as u8;
|
|
|
|
match input {
|
|
0x00C0..=0x00CF => match quirk_mode {
|
|
QuirkMode::Chip8 => XXXXERRORINSTRUCTION,
|
|
QuirkMode::XOChip => SCD(last_nibble),
|
|
QuirkMode::SChipModern => SCD(last_nibble),
|
|
},
|
|
0x00E0 => Chip8CpuInstructions::CLS,
|
|
0x00EE => Chip8CpuInstructions::RET,
|
|
0x00FB => {
|
|
match quirk_mode {
|
|
QuirkMode::Chip8 => {
|
|
// does not exist on Chip8
|
|
XXXXERRORINSTRUCTION
|
|
}
|
|
QuirkMode::XOChip => Chip8CpuInstructions::SCR,
|
|
QuirkMode::SChipModern => Chip8CpuInstructions::SCR,
|
|
}
|
|
}
|
|
0x00FC => Chip8CpuInstructions::SCL,
|
|
0x00FD => Chip8CpuInstructions::EXIT,
|
|
0x00FE => match quirk_mode {
|
|
QuirkMode::Chip8 => XXXXERRORINSTRUCTION,
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => LOW,
|
|
},
|
|
0x00FF => match quirk_mode {
|
|
QuirkMode::Chip8 => XXXXERRORINSTRUCTION,
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => HIGH,
|
|
},
|
|
0x0000..=0x0FFF => match quirk_mode {
|
|
QuirkMode::Chip8 => Chip8CpuInstructions::SYS(addr_param),
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => XXXXERRORINSTRUCTION,
|
|
},
|
|
0x1000..=0x1FFF => JPA(addr_param),
|
|
0x2000..=0x2FFF => CALL(addr_param),
|
|
0x3000..=0x3FFF => SEX(x_param, byte_param),
|
|
0x4000..=0x4FFF => SNEB(x_param, byte_param),
|
|
0x5000..=0x5FF0 if input & 0x01 == 0 => SEY(x_param, y_param),
|
|
0x6000..=0x6FFF => LDR(x_param, byte_param),
|
|
0x7000..=0x7FFF => ADD(x_param, byte_param),
|
|
0x8000..=0x8FFE => match last_nibble {
|
|
0x0 => Chip8CpuInstructions::LDRY(x_param, y_param),
|
|
0x1 => Chip8CpuInstructions::OR(x_param, y_param),
|
|
0x2 => Chip8CpuInstructions::AND(x_param, y_param),
|
|
0x3 => Chip8CpuInstructions::ORY(x_param, y_param),
|
|
0x4 => Chip8CpuInstructions::ADDR(x_param, y_param),
|
|
0x5 => Chip8CpuInstructions::SUB(x_param, y_param),
|
|
0x6 => Chip8CpuInstructions::SHR(x_param, y_param),
|
|
0x7 => Chip8CpuInstructions::SUBC(x_param, y_param),
|
|
0xE => Chip8CpuInstructions::SHL(x_param, y_param),
|
|
_ => XXXXERRORINSTRUCTION,
|
|
},
|
|
0x9000..=0x9FF0 if input & 0x01 == 0 => Chip8CpuInstructions::SNEY(x_param, y_param),
|
|
0xA000..=0xAFFF => Chip8CpuInstructions::LDIA(addr_param),
|
|
0xB000..=0xBFFF => Chip8CpuInstructions::JPI(addr_param),
|
|
0xC000..=0xCFFF => Chip8CpuInstructions::RND(x_param, byte_param),
|
|
0xD000..=0xDFFF => Chip8CpuInstructions::DRW(x_param, y_param, nibble_param),
|
|
0xE09E..=0xEFA1 => match last_byte {
|
|
0x9E => Chip8CpuInstructions::SKP(ubln),
|
|
0xA1 => Chip8CpuInstructions::SKNP(ubln),
|
|
_ => XXXXERRORINSTRUCTION,
|
|
},
|
|
0xF007..=0xFF65 => match last_byte {
|
|
0x07 => Chip8CpuInstructions::LDRD(ubln),
|
|
0x0A => Chip8CpuInstructions::LDRK(ubln),
|
|
0x15 => Chip8CpuInstructions::LDD(ubln),
|
|
0x18 => Chip8CpuInstructions::LDIS(ubln),
|
|
0x1E => Chip8CpuInstructions::ADDI(ubln),
|
|
0x29 => Chip8CpuInstructions::LDFX(ubln),
|
|
0x30 => Chip8CpuInstructions::LDF2(ubln),
|
|
0x33 => Chip8CpuInstructions::BCD(ubln),
|
|
0x55 => Chip8CpuInstructions::LDIX(ubln),
|
|
0x65 => Chip8CpuInstructions::LDRI(ubln),
|
|
0x75 => Chip8CpuInstructions::STR(ubln),
|
|
0x85 => Chip8CpuInstructions::LIDR(ubln),
|
|
_ => XXXXERRORINSTRUCTION,
|
|
},
|
|
_ => DW(addr_param),
|
|
}
|
|
}
|
|
pub fn execute(&self, input: &mut Chip8Computer) -> Chip8Computer {
|
|
// print!("INSTRUCTION {}", self);
|
|
// let start_time = Instant::now();
|
|
let start_pc = input.registers.peek_pc();
|
|
input.registers.poke_pc(start_pc + 2);
|
|
match self {
|
|
// 0x0nnn Exit to System Call
|
|
Chip8CpuInstructions::SYS(new_address) => {
|
|
input.registers.poke_pc(*new_address);
|
|
}
|
|
// * 0x00E0 Clear Screen
|
|
Chip8CpuInstructions::CLS => {
|
|
input.video_memory.cls();
|
|
}
|
|
// 0x00EE Return from Subroutine
|
|
Chip8CpuInstructions::RET => {
|
|
input.registers.poke_pc(input.stack.pop());
|
|
}
|
|
// 0x1nnn Jump to Address
|
|
Chip8CpuInstructions::JPA(new_address) => {
|
|
input.registers.poke_pc(*new_address);
|
|
}
|
|
// 0x2nnn Call Subroutine
|
|
Chip8CpuInstructions::CALL(new_address) => {
|
|
let return_address = input.registers.peek_pc();
|
|
input.registers.poke_pc(*new_address);
|
|
input.stack.push(&return_address);
|
|
}
|
|
// 0x3xkk Skip next instruction if Vx = kk.
|
|
Chip8CpuInstructions::SEX(vx_register, byte) => {
|
|
if input.registers.peek(*vx_register) == { *byte } {
|
|
input.registers.advance_pc();
|
|
}
|
|
}
|
|
// 0x4xkk Skip next instruction if Vx != kk
|
|
Chip8CpuInstructions::SNEB(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.
|
|
if input.registers.peek(*x) != *byte {
|
|
input.registers.advance_pc();
|
|
}
|
|
}
|
|
// 0x5xy0 Skip next instruction if Vx == Vy
|
|
Chip8CpuInstructions::SEY(x, y) => {
|
|
if input.registers.peek(*x) == input.registers.peek(*y) {
|
|
input.registers.advance_pc();
|
|
}
|
|
}
|
|
// 0x6xkk Set Vx = kk
|
|
Chip8CpuInstructions::LDR(register, byte) => {
|
|
input.registers.poke(*register, *byte);
|
|
}
|
|
// 0x7xkk Set Vx = Vx + kk
|
|
Chip8CpuInstructions::ADD(vx_register, byte) => {
|
|
input.registers.poke(
|
|
*vx_register,
|
|
(input.registers.peek(*vx_register) as u16 + *byte as u16) as u8,
|
|
);
|
|
}
|
|
// 0x8xy0 Set value of Vy in Vx
|
|
Chip8CpuInstructions::LDRY(x, y) => {
|
|
input.registers.poke(*x, input.registers.peek(*y));
|
|
}
|
|
// 0x8xy1 Set Vx = Vx OR Vy
|
|
Chip8CpuInstructions::OR(x, y) => {
|
|
// shift them to 16 bit
|
|
let working_16_x = input.registers.peek(*x) as u16;
|
|
let working_16_y = input.registers.peek(*y) as u16;
|
|
// OR them
|
|
let working_16_or = working_16_x | working_16_y;
|
|
// shift them back to 8 bit.
|
|
input.registers.poke(*x, working_16_or as u8);
|
|
// reset of VF quirk
|
|
input.registers.poke(0x0f, 0x00);
|
|
debug!("OrVxVy [0x{x:1x}] [0x{y:1x}]")
|
|
}
|
|
// 0x8xy2 Set Vx = Vx AND Vy
|
|
AND(x, y) => {
|
|
let lhs_16 = input.registers.peek(*x) as u16;
|
|
let rhs_16 = input.registers.peek(*y) as u16;
|
|
// reset of VF quirk
|
|
input.registers.poke(0x0f, 0x00);
|
|
input.registers.poke(*x, (lhs_16 & rhs_16) as u8);
|
|
}
|
|
// 0x8xy3 Set Vx = Vx XOR Vy
|
|
ORY(x, y) => {
|
|
let lhs_16 = input.registers.peek(*x) as u16;
|
|
let rhs_16 = input.registers.peek(*y) as u16;
|
|
// reset of VF quirk
|
|
input.registers.poke(0x0f, 0x00);
|
|
input.registers.poke(*x, (lhs_16 ^ rhs_16) as u8);
|
|
}
|
|
// 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
|
|
ADDR(x, y) => {
|
|
let lhs = input.registers.peek(*x) as i16;
|
|
let rhs = input.registers.peek(*y) as i16;
|
|
let working = lhs + rhs;
|
|
input.registers.poke(*x, working as u8);
|
|
|
|
if working >= 0x100 {
|
|
input.registers.poke(0xf, 0x01);
|
|
} else {
|
|
input.registers.poke(0x0f, 0x00);
|
|
}
|
|
}
|
|
SUB(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 result;
|
|
|
|
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, result as u8);
|
|
input.registers.poke(0x0f, borrow_flag);
|
|
}
|
|
Chip8CpuInstructions::SHR(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, rotated);
|
|
if initial_value.bitand(0b1) == 1 {
|
|
input.registers.poke(0x0f, 0x01);
|
|
} else {
|
|
input.registers.poke(0x0f, 0x00);
|
|
}
|
|
}
|
|
Chip8CpuInstructions::SUBC(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);
|
|
let x_register = input.registers.peek(*x);
|
|
|
|
let (new_value, value_to_poke) = if y_register < x_register {
|
|
(0, (y_register as u16 + 256) - x_register as u16)
|
|
} else {
|
|
(1, (y_register - x_register) as u16)
|
|
};
|
|
input.registers.poke(*x, value_to_poke as u8);
|
|
input.registers.poke(0xf, new_value);
|
|
}
|
|
|
|
Chip8CpuInstructions::SHL(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, rotated);
|
|
if initial_value.bitand(0b10000000) == 0b10000000 {
|
|
input.registers.poke(0x0f, 0x01);
|
|
} else {
|
|
input.registers.poke(0x0f, 0x00);
|
|
}
|
|
}
|
|
Chip8CpuInstructions::SNEY(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);
|
|
let y_reg_value = input.registers.peek(*vy_register);
|
|
if x_reg_value != y_reg_value {
|
|
input.registers.advance_pc();
|
|
}
|
|
}
|
|
Chip8CpuInstructions::LDIA(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::JPI(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);
|
|
}
|
|
// 0xBxnn Jump to Xnn+Vx
|
|
JPX(vx_register, addr) => {
|
|
let x_reg_value: u16 = input.registers.peek(*vx_register) as u16;
|
|
let shifted_x_reg = x_reg_value << 8;
|
|
let added_addr = shifted_x_reg | addr;
|
|
let final_addr = added_addr + shifted_x_reg;
|
|
println!("JPX -> {x_reg_value:02x} {shifted_x_reg:04x} {added_addr:04x} {final_addr:04x}");
|
|
input.registers.poke_i(final_addr);
|
|
}
|
|
Chip8CpuInstructions::RND(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 mut rng = rand::thread_rng();
|
|
let new_value: u8 = rng.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, new_value & { *byte })
|
|
}
|
|
Chip8CpuInstructions::DRW(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 u16;
|
|
let y_offset = input.registers.peek(*y) as u16;
|
|
if input.video_memory.is_highres() {
|
|
// if n == 0 we have a 16 row sprite (font maybe)
|
|
let actual_num_loops = if *n == 0u8 { 16 } else { *n };
|
|
for byte_index in 0..actual_num_loops {
|
|
let current_byte =
|
|
input.memory.peek(byte_index as u16 + source_memory_offset);
|
|
let next_byte = input
|
|
.memory
|
|
.peek(byte_index as u16 + 1u16 + source_memory_offset);
|
|
let x_offset = (x_offset + byte_index as u16) * 64;
|
|
for bit_index in 0..8 {
|
|
input.video_memory.poke(
|
|
x_offset + (y_offset + bit_index as u16),
|
|
(current_byte & (0x80 >> bit_index)) != 0,
|
|
);
|
|
input.video_memory.poke(
|
|
x_offset + (y_offset + bit_index as u16) + 8,
|
|
(next_byte & (0x80 >> bit_index)) != 0,
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
for byte_index in 0..*n {
|
|
let current_byte =
|
|
input.memory.peek(byte_index as u16 + source_memory_offset);
|
|
let x_offset: u16 = (x_offset + byte_index as u16) * 64;
|
|
for bit_index in 0..8 {
|
|
input.video_memory.poke(
|
|
x_offset + (y_offset + bit_index as u16),
|
|
(current_byte & (0x80 >> bit_index)) != 0,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
let target = if input.video_memory.has_frame_changed {
|
|
1u8
|
|
} else {
|
|
0u8
|
|
};
|
|
|
|
input.registers.poke(0xf, target);
|
|
}
|
|
Chip8CpuInstructions::SKP(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);
|
|
let is_pressed = input.keypad.pressed(key_to_check);
|
|
if is_pressed {
|
|
input.registers.advance_pc();
|
|
}
|
|
}
|
|
Chip8CpuInstructions::SKNP(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);
|
|
let is_pressed = input.keypad.pressed(target_key);
|
|
debug!("SnKpVx [{x:1x}]");
|
|
if !is_pressed {
|
|
input.registers.advance_pc();
|
|
}
|
|
}
|
|
Chip8CpuInstructions::LDRD(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, value_to_set);
|
|
}
|
|
Chip8CpuInstructions::LDRK(_) => {
|
|
// 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::LDD(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);
|
|
input.delay_timer.set_timer(new_time);
|
|
}
|
|
Chip8CpuInstructions::LDIS(new_time) => {
|
|
let new_value = input.registers.peek(*new_time);
|
|
input.sound_timer.set_timer(new_value as i32);
|
|
}
|
|
Chip8CpuInstructions::ADDI(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);
|
|
input.registers.poke_i(base + x_value as u16);
|
|
}
|
|
Chip8CpuInstructions::LDFX(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);
|
|
}
|
|
Chip8CpuInstructions::BCD(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);
|
|
|
|
// 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;
|
|
|
|
// 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::LDIX(x) => {
|
|
// Store registers V0 through Vx in memory starting at location I.
|
|
//
|
|
// The interpreter copi=es 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));
|
|
}
|
|
input.registers.poke_i(offset + 1);
|
|
}
|
|
Chip8CpuInstructions::LDRI(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, 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 => {}
|
|
// SCHIP1.1
|
|
Chip8CpuInstructions::SCD(x) => match input.quirk_mode {
|
|
QuirkMode::Chip8 => {
|
|
debug!("Attempt to execute SCD in Chip8 Mode");
|
|
}
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
|
input.video_memory.scroll_down(*x as i32);
|
|
}
|
|
},
|
|
// SCHIP 1.1
|
|
Chip8CpuInstructions::SCR => match input.quirk_mode {
|
|
QuirkMode::Chip8 => {
|
|
debug!("Attempt to execute SCR in Chip8 Mode");
|
|
}
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
|
input.video_memory.scroll_right();
|
|
}
|
|
},
|
|
// SCHIP 1.1
|
|
Chip8CpuInstructions::SCL => match input.quirk_mode {
|
|
QuirkMode::Chip8 => {
|
|
debug!("Attempt to execute SCL in Chip8 Mode");
|
|
}
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
|
input.video_memory.scroll_left();
|
|
}
|
|
},
|
|
// SCHIP 1.0
|
|
Chip8CpuInstructions::LOW => match input.quirk_mode {
|
|
QuirkMode::Chip8 => {
|
|
debug!("ATTEMPT TO SET LOWRES IN CHIP8MODE");
|
|
}
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
|
input.video_memory.set_lowres();
|
|
}
|
|
},
|
|
// SCHIP 1.0
|
|
Chip8CpuInstructions::HIGH => match input.quirk_mode {
|
|
QuirkMode::Chip8 => {
|
|
debug!("ATTEMPT TO SET HIGHRES IN CHIP8MODE");
|
|
}
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
|
input.video_memory.set_highres();
|
|
}
|
|
},
|
|
Chip8CpuInstructions::EXIT => match input.quirk_mode {
|
|
QuirkMode::Chip8 => {
|
|
debug!("ATTEMPT TO EXIT FROM CHIP8 INTERPRETER");
|
|
}
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
|
println!("EXIT INTERPRETER");
|
|
}
|
|
},
|
|
Chip8CpuInstructions::LDF2(x) => {
|
|
match input.quirk_mode {
|
|
QuirkMode::Chip8 => {
|
|
debug!("ATTEMPT TO LDF2 IN CHIP8MODE");
|
|
}
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
|
println!("POINTING TO FONT AT {x:02x}");
|
|
// base = 0x100 + 0x0A*X
|
|
input.registers.poke_i(0x100 + (0xA * x) as u16);
|
|
}
|
|
}
|
|
}
|
|
Chip8CpuInstructions::STR(x) => match input.quirk_mode {
|
|
QuirkMode::Chip8 => {
|
|
debug!("ATTEMPT TO STORE RPL IN CHIP8MODE");
|
|
}
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
|
println!("STORING FROM RPL FOR {x}");
|
|
}
|
|
},
|
|
Chip8CpuInstructions::LIDR(x) => match input.quirk_mode {
|
|
QuirkMode::Chip8 => {
|
|
debug!("ATTEMPT TO LOAD RPL IN CHIP8MODE");
|
|
}
|
|
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
|
println!("LOADING FROM RPL FOR {x}");
|
|
}
|
|
},
|
|
SCU(x) => {
|
|
println!("SCROLL SCREEN UP {x} ROWS");
|
|
match input.quirk_mode {
|
|
QuirkMode::Chip8 | QuirkMode::SChipModern => {
|
|
debug!("Attempt to run SCU outside XO mode");
|
|
}
|
|
QuirkMode::XOChip => {
|
|
input.video_memory.scroll_up(x);
|
|
}
|
|
}
|
|
}
|
|
DW(_) => {
|
|
println!("DATA WORD FOUND...");
|
|
}
|
|
};
|
|
// let cycle_time = Instant::now().duration_since(start_time).as_nanos();
|
|
// println!("\t\tTook {cycle_time}ms");
|
|
input.to_owned()
|
|
}
|
|
|
|
pub fn from_string(input: &str) -> Chip8CpuInstructions {
|
|
let parts = input.split(" ");
|
|
println!("Parts -> {:?}", parts);
|
|
|
|
Chip8CpuInstructions::XXXXERRORINSTRUCTION
|
|
}
|
|
}
|