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 { ADDI(_) => INST_ADDI, ADD(_, _) => INST_ADD, ADDR(_, _) => INST_ADDR, AND(_, _) => INST_AND, CLS => INST_CLS, CALL(_) => INST_CALL, DRW(_, _, _) => INST_DRW, EXIT => INST_EXIT, JPA(_) => INST_JPA, JPI(_) => INST_JPI, BCD(_) => INST_BCD, LDD(_) => INST_LDD, LDFX(_) => INST_LDF, LDF2(_) => INST_LDF2, LDIA(_) => INST_LDIA, LDIX(_) => INST_LDIX, LIDR(_) => INST_LIDR, LDIS(_) => INST_LDIS, LDR(_, _) => INST_LDR, LDRD(_) => INST_LDRD, LDRI(_) => INST_LDRI, LDRK(_) => INST_LDRK, LDRY(_, _) => INST_LDRY, OR(_, _) => INST_OR, RET => INST_RET, RND(_, _) => INST_RND, SCD(_) => INST_SCD, SCL => INST_SCL, SCR => INST_SCR, SEX(_, _) => INST_SEX, SEY(_, _) => INST_SEY, SHL(_, _) => INST_SHL, SHR(_, _) => INST_SHR, SKP(_) => INST_SKP, SNEB(_, _) => INST_SNEB, SNEY(_, _) => INST_SNEY, SKNP(_) => INST_SKNP, STR(_) => INST_STR, SUB(_, _) => INST_SUB, SUBC(_, _) => INST_SUBC, SYS(_) => INST_SYS, LOW => INST_LOW, HIGH => INST_HIGH, 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 { SYS(target) => target & 0x0FFF, CLS => 0x00E0, RET => 0x00EE, JPA(new_addr) => 0x1000 | (new_addr & 0x0FFF), CALL(address) => 0x2000 | (address & 0x0FFF), SEX(vx_register, byte) => { 0x3000 | ((*vx_register as u16) << 8) | (*byte as u16) } 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 => XXXXERRORINSTRUCTION_ENCODED, } } 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::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); } 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 } }