diff --git a/src/cpu.rs b/src/cpu.rs index 2137cc8..1076560 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,7 +1,4 @@ -use registers::{Register16Bit, Register8Bit}; - use crate::memory::Memory; - use self::instructions::{InstructionResult, Instructions}; pub mod decode; @@ -14,7 +11,7 @@ mod step; pub mod interrupts; mod joypad; mod timer; - +mod helpers; /// 4.194304 MHz /// This is the frequency of the CPU @@ -60,104 +57,4 @@ impl CPU { instruction: 0, } } - - /// Skip the bootrom - /// Set the registers to the correct values - /// Used for: https://robertheaton.com/gameboy-doctor/ - pub fn skip_boot_rom(&mut self) { - self.set_8bit_register(Register8Bit::A, 0x01); - self.set_zero_flag(); - self.set_half_carry_flag(); - self.set_carry_flag(); - self.set_8bit_register(Register8Bit::B, 0x00); - self.set_8bit_register(Register8Bit::C, 0x13); - self.set_8bit_register(Register8Bit::D, 0x00); - self.set_8bit_register(Register8Bit::E, 0xD8); - self.set_8bit_register(Register8Bit::H, 0x01); - self.set_8bit_register(Register8Bit::L, 0x4D); - self.set_16bit_register(Register16Bit::SP, 0xFFFE); - self.set_16bit_register(Register16Bit::PC, 0x0100); - self.memory.boot_rom_enabled = false; - // Set Joypad register - self.memory.write_byte(0xFF00, 0b1111_1111); - } - - /// Polls the inputs - /// Warning, this will loop till input is received when self.stop_mode is true - pub fn poll_inputs(&mut self) { - loop { - self.update_key_input(); - - if !self.is_in_stop_mode() { - break; - } - } - } - - pub fn is_in_stop_mode(&self) -> bool { - self.stop_mode - } - - // Print blarg serial output - pub fn blarg_print(&mut self) { - let serial_data = self.memory.read_byte(0xFF02); - if serial_data == 0x81 { - let data = self.memory.read_byte(0xFF01); - print!("{}", data as char); - self.memory.write_byte(0xFF02, 0x0); - } - } - - pub fn load_from_file(&mut self, file: &str, offset: usize) { - self.memory.load_from_file(file, offset); - } - - pub fn get_next_opcode(&mut self) -> u8 { - self.memory - .read_byte(self.get_16bit_register(registers::Register16Bit::PC)) - } - - pub fn write_memory(&mut self, address: u16, value: u8) { - self.memory.write_byte(address, value); - } - - /// Set the next instruction to be executed - /// This is used for testing - pub fn set_instruction(&mut self, instruction: Instructions) { - self.next_instruction = instruction; - } - - pub fn dump_memory(&self) { - self.memory.dump_to_file(); - } - - /// Get the last step result - /// This is used for testing purposes - pub fn get_last_step_result(&self) -> InstructionResult { - self.last_step_result.clone() - } - - pub fn get_cycles(&self) -> u64 { - self.cycles - } - - pub fn is_boot_rom_enabled(&self) -> bool { - self.memory.is_boot_rom_enabled().clone() - } - - pub fn get_instruction(&self) -> &Instructions { - &self.next_instruction - } - - #[cfg(test)] - pub fn get_registry_dump(&self) -> [u8; 12] { - self.registers.clone() - } - - /// Gets the full memory of the CPU - /// This is used for testing purposes - /// @warning This is a very expensive operation - pub fn get_memory(&self) -> Memory { - self.memory.clone() - } } diff --git a/src/cpu/decode.rs b/src/cpu/decode.rs index 29ab4c1..1944218 100644 --- a/src/cpu/decode.rs +++ b/src/cpu/decode.rs @@ -1,529 +1,19 @@ -use super::{ - instructions::{InstParam, InstructionCondition, Instructions}, - registers::{Register16Bit, Register8Bit}, - CPU, -}; +use super::{instructions::Instructions, CPU}; -impl CPU { - /// Decode the tail of an opcode to a 8 Bit Register - fn tail_to_inst_param(&self, tail: u8) -> InstParam { - // The tail repeats every 8 values, e.g. 0x0 & 0x8 are the same (B) - let tail = if tail > 0x7 { tail - 0x8 } else { tail }; - - match tail { - 0x0 => InstParam::Register8Bit(Register8Bit::B), - 0x1 => InstParam::Register8Bit(Register8Bit::C), - 0x2 => InstParam::Register8Bit(Register8Bit::D), - 0x3 => InstParam::Register8Bit(Register8Bit::E), - 0x4 => InstParam::Register8Bit(Register8Bit::H), - 0x5 => InstParam::Register8Bit(Register8Bit::L), - 0x6 => InstParam::Register16Bit(Register16Bit::HL), //haben Befehle mit [HL] nicht meistens eine andere Byte oder Cycle Anzahl? (z.B. CP A,[HL] und CPA, n8) - 0x7 => InstParam::Register8Bit(Register8Bit::A), - _ => panic!("Unknown tail: {:X}", tail), - } - } - - /// Calculate the target of a LD instruction based on the opcode - /// Returns None if the opcode is not a LD instruction - fn opcode_to_ld_target(&self, opcode: u8) -> Option { - Some(match opcode { - 0x40..=0x47 => InstParam::Register8Bit(Register8Bit::B), - 0x48..=0x4F => InstParam::Register8Bit(Register8Bit::C), - 0x50..=0x57 => InstParam::Register8Bit(Register8Bit::D), - 0x58..=0x5F => InstParam::Register8Bit(Register8Bit::E), - 0x60..=0x67 => InstParam::Register8Bit(Register8Bit::H), - 0x68..=0x6F => InstParam::Register8Bit(Register8Bit::L), - 0x70..=0x77 => InstParam::Register16Bit(Register16Bit::HL), - 0x78..=0x7F => InstParam::Register8Bit(Register8Bit::A), - _ => return None, - }) - } - - /// Get a 16-bit value from the program counter at the next two positions (PC + 1, PC + 2) - /// @warning: This will *not* increment the program counter - fn get_16bit_from_pc(&self) -> u16 { - self.memory - .read_word(self.get_16bit_register(Register16Bit::PC) + 1) - } - - /// Get a 8-bit value from the program counter at the next position (PC + 1) - /// @warning: This will *not* increment the program counter - fn get_8bit_from_pc(&self) -> u8 { - self.memory - .read_byte(self.get_16bit_register(Register16Bit::PC) + 1) - } - - fn get_8bit_from_hl(&self) -> u8 { - self.memory - .read_byte(self.get_16bit_register(Register16Bit::HL)) - } - - fn decode_0x0_to_0x3_commons(&self, opcode: u8) -> Result { - let head = opcode >> 4; - let tail = opcode & 0xF; - - let register_8bit = match head { - 0x0 => { - if tail < 0xB { - Register8Bit::B - } else { - Register8Bit::C - } - } - 0x1 => { - if tail < 0xB { - Register8Bit::D - } else { - Register8Bit::E - } - } - 0x2 => { - if tail < 0xB { - Register8Bit::H - } else { - Register8Bit::L - } - } - 0x3 => Register8Bit::A, - _ => return Err(format!("{:#02X}", opcode)), - }; - - let register_16bit = match head { - 0x0 => Register16Bit::BC, - 0x1 => Register16Bit::DE, - 0x2 => Register16Bit::HL, - 0x3 => Register16Bit::SP, - _ => return Err(format!("{:#02X}", opcode)), - }; - - Ok(match tail { - 0x1 => Instructions::LD( - InstParam::Register16Bit(register_16bit), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0x3 => Instructions::INC(InstParam::Register16Bit(register_16bit),InstParam::Boolean(false)), - 0x4 => Instructions::INC(if head == 0x3 { - InstParam::Register16Bit(Register16Bit::HL) // Special case for (HL) - } else { - InstParam::Register8Bit(register_8bit) - }, if head == 0x3 {InstParam::Boolean(true)}else{InstParam::Boolean(false)}), - 0x5 => Instructions::DEC(if head == 0x3 { - InstParam::Register16Bit(Register16Bit::HL) // Special case for (HL) - } else { - InstParam::Register8Bit(register_8bit) - }, if head == 0x3 {InstParam::Boolean(true)}else{InstParam::Boolean(false)}), - 0x6 => Instructions::LD( - if head == 0x3 { - InstParam::Register16Bit(Register16Bit::HL) // Special case for (HL) - } else { - InstParam::Register8Bit(register_8bit) - }, - InstParam::Number8Bit(self.get_8bit_from_pc()), - ), - 0x9 => Instructions::ADD_HL(InstParam::Register16Bit(register_16bit)), - 0xB => Instructions::DEC(InstParam::Register16Bit(register_16bit),InstParam::Boolean(false)), - 0xC => Instructions::INC(InstParam::Register8Bit(register_8bit),InstParam::Boolean(false)), - 0xD => Instructions::DEC(InstParam::Register8Bit(register_8bit),InstParam::Boolean(false)), - 0xE => Instructions::LD( - InstParam::Register8Bit(register_8bit), - InstParam::Number8Bit(self.get_8bit_from_pc()), - ), - _ => return Err(format!("Not covered in common {:#02X}", opcode)), - }) - } - - fn not_implemented(&self, opcode: u8) -> Result { - Err(format!("Opcode is not implemented (yet): {:#02X}", opcode)) - } +mod helpers; +mod unprefixed_commons; +mod unprefixed; +mod prefixed; +mod test; +impl CPU { + /// Decode an opcode, returning the instruction pub fn decode(&self, opcode: u8) -> Result { // 0xCB is a prefixed opcode with a completely different table if opcode == 0xCB { - return self.decode_prefixed(); - } - - // Split the opcode into head and tail - // The head is the first 4 bits of the opcode e.g. 0x42 -> 0x4 - // The tail is the last 4 bits of the opcode e.g. 0x42 -> 0x2 - // This makes it a bit easier to decode the opcode - let head = opcode >> 4; - let tail = opcode & 0xF; - Ok(match head { - 0x0 => match tail { - 0x0 => Instructions::NOP, - 0x1 => self.decode_0x0_to_0x3_commons(opcode)?, - 0x2 => Instructions::LD( - InstParam::Register16Bit(Register16Bit::BC), - InstParam::Register8Bit(Register8Bit::A), - ), - 0x3..=0x6 => self.decode_0x0_to_0x3_commons(opcode)?, - 0x7 => Instructions::RLCA(), - 0x8 => Instructions::LD( - InstParam::Number16Bit(self.get_16bit_from_pc()), - InstParam::Register16Bit(Register16Bit::SP), - ), - 0x9 => self.decode_0x0_to_0x3_commons(opcode)?, - 0xA => Instructions::LD( - InstParam::Register8Bit(Register8Bit::A), - InstParam::Register16Bit(Register16Bit::BC), - ), - 0xB..=0xE => self.decode_0x0_to_0x3_commons(opcode)?, - 0xF => Instructions::RRCA(), - _ => self.not_implemented(opcode)?, - }, - 0x1 => match tail { - 0x0 => Instructions::STOP, - 0x1 => self.decode_0x0_to_0x3_commons(opcode)?, - 0x2 => Instructions::LD( - InstParam::Register16Bit(Register16Bit::DE), - InstParam::Register8Bit(Register8Bit::A), - ), - 0x3..=0x6 => self.decode_0x0_to_0x3_commons(opcode)?, - 0x7 => Instructions::RLA(), - 0x8 => Instructions::JR( - InstParam::ConditionCodes(InstructionCondition::SkipConditionCodes), - InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8), - ), - 0x9 => self.decode_0x0_to_0x3_commons(opcode)?, - 0xA => Instructions::LD( - InstParam::Register8Bit(Register8Bit::A), - InstParam::Register16Bit(Register16Bit::DE), - ), - 0xB..=0xE => self.decode_0x0_to_0x3_commons(opcode)?, - 0xF => Instructions::RRA(), - _ => self.not_implemented(opcode)?, - }, - 0x2 => match tail { - 0x0 => Instructions::JR( - InstParam::ConditionCodes(InstructionCondition::NotZero), - InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8), - ), - 0x1 => self.decode_0x0_to_0x3_commons(opcode)?, - 0x2 => Instructions::LDHLIA, - 0x3..=0x6 => self.decode_0x0_to_0x3_commons(opcode)?, - 0x7 => Instructions::DAA, - 0x8 => Instructions::JR( - InstParam::ConditionCodes(InstructionCondition::Zero), - InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8), - ), - 0x9 => self.decode_0x0_to_0x3_commons(opcode)?, - 0xA => Instructions::LDAHLI, - 0xB..=0xE => self.decode_0x0_to_0x3_commons(opcode)?, - 0xF => Instructions::CPL, - _ => self.not_implemented(opcode)?, - }, - 0x3 => match tail { - 0x0 => Instructions::JR( - InstParam::ConditionCodes(InstructionCondition::NotCarry), - InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8), - ), - 0x1 => self.decode_0x0_to_0x3_commons(opcode)?, - 0x2 => Instructions::LDHLDA, - 0x3..=0x6 => self.decode_0x0_to_0x3_commons(opcode)?, - 0x7 => Instructions::SCF, - 0x8 => Instructions::JR( - InstParam::ConditionCodes(InstructionCondition::Carry), - InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8), - ), - 0x9 => self.decode_0x0_to_0x3_commons(opcode)?, - 0xA => Instructions::LDAHLD, - 0xB..=0xE => self.decode_0x0_to_0x3_commons(opcode)?, - 0xF => Instructions::CCF, - _ => self.not_implemented(opcode)?, - }, - // LD instructions (& HALT) - 0x4..=0x7 => { - let value = self.tail_to_inst_param(tail); - let ld_target = match self.opcode_to_ld_target(opcode) { - Some(target) => target, - None => return self.not_implemented(opcode), - }; - - // There is a single opcode within this range that is not a LD instruction - if opcode == 0x76 { - Instructions::HALT - } else { - Instructions::LD(ld_target, value) - } - } - // ADD, ADC, SUB, SBC, AND, XOR, OR, CP - 0x8..=0xB => { - let value = self.tail_to_inst_param(tail); - let is_second_half = tail > 0x7; - - if is_second_half { - match head { - 0x8 => Instructions::ADC(value), - 0x9 => Instructions::SBC(value), - 0xA => Instructions::XOR(value), - 0xB => match tail { - 0xE => Instructions::CP(InstParam::Register16Bit(Register16Bit::HL)), - _ => Instructions::CP(value), - } - _ => self.not_implemented(opcode)?, - } - } else { - match head { - 0x8 => Instructions::ADD(value), - 0x9 => Instructions::SUB(value), - 0xA => Instructions::AND(value), - 0xB => Instructions::OR(value), - _ => self.not_implemented(opcode)?, - } - } - } - 0xC => match tail { - 0x0 => Instructions::RET(InstParam::ConditionCodes(InstructionCondition::NotZero)), - 0x1 => Instructions::POP(InstParam::Register16Bit(Register16Bit::BC)), - 0x2 => Instructions::JP( - InstParam::ConditionCodes(InstructionCondition::NotZero), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0x3 => Instructions::JP( - InstParam::ConditionCodes(InstructionCondition::SkipConditionCodes), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0x4 => Instructions::CALL( - InstParam::ConditionCodes(InstructionCondition::NotZero), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0x5 => Instructions::PUSH(InstParam::Register16Bit(Register16Bit::BC)), - 0x6 => Instructions::ADD(InstParam::Number8Bit(self.get_8bit_from_pc())), - 0x7 => Instructions::RST(InstParam::Number8Bit(0x00)), - 0x8 => Instructions::RET(InstParam::ConditionCodes(InstructionCondition::Zero)), - 0x9 => Instructions::RET(InstParam::ConditionCodes( - InstructionCondition::SkipConditionCodes, - )), - 0xA => Instructions::JP( - InstParam::ConditionCodes(InstructionCondition::Zero), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0xB => { - return Err(format!( - "Prefixed Opcodes should have already been handled 😕 {:#02X}", - opcode - )) - } - 0xC => Instructions::CALL( - InstParam::ConditionCodes(InstructionCondition::Zero), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0xD => Instructions::CALL( - InstParam::ConditionCodes(InstructionCondition::SkipConditionCodes), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0xE => Instructions::ADC(InstParam::Number8Bit(self.get_8bit_from_pc())), - 0xF => Instructions::RST(InstParam::Number8Bit(0x08)), - _ => self.not_implemented(opcode)?, - }, - 0xD => match tail { - 0x0 => Instructions::RET(InstParam::ConditionCodes(InstructionCondition::NotCarry)), - 0x1 => Instructions::POP(InstParam::Register16Bit(Register16Bit::DE)), - 0x2 => Instructions::JP( - InstParam::ConditionCodes(InstructionCondition::NotCarry), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0x3 => Instructions::INVALID(0x3), - 0x4 => Instructions::CALL( - InstParam::ConditionCodes(InstructionCondition::NotCarry), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0x5 => Instructions::PUSH(InstParam::Register16Bit(Register16Bit::DE)), - 0x6 => Instructions::SUB(InstParam::Number8Bit(self.get_8bit_from_pc())), - 0x7 => Instructions::RST(InstParam::Number8Bit(0x10)), - 0x8 => Instructions::RET(InstParam::ConditionCodes(InstructionCondition::Carry)), - 0x9 => Instructions::RETI, - 0xA => Instructions::JP( - InstParam::ConditionCodes(InstructionCondition::Carry), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0xB => Instructions::INVALID(0xB), - 0xC => Instructions::CALL( - InstParam::ConditionCodes(InstructionCondition::Carry), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0xD => Instructions::INVALID(0xD), - 0xE => Instructions::SBC(InstParam::Number8Bit(self.get_8bit_from_pc())), - 0xF => Instructions::RST(InstParam::Number8Bit(0x18)), - _ => self.not_implemented(opcode)?, - }, - 0xE => match tail { - 0x0 => Instructions::LDH( - InstParam::Number8Bit(self.get_8bit_from_pc()), - InstParam::Register8Bit(Register8Bit::A), - ), - 0x1 => Instructions::POP(InstParam::Register16Bit(Register16Bit::HL)), - 0x2 => Instructions::LDH( - InstParam::Register8Bit(Register8Bit::C), - InstParam::Register8Bit(Register8Bit::A), - ), - 0x3 | 0x4 => Instructions::INVALID(0x3), - 0x5 => Instructions::PUSH(InstParam::Register16Bit(Register16Bit::HL)), - 0x6 => Instructions::AND(InstParam::Number8Bit(self.get_8bit_from_pc())), - 0x7 => Instructions::RST(InstParam::Number8Bit(0x20)), - 0x8 => { - Instructions::ADD(InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8)) - } - 0x9 => Instructions::JP( - InstParam::ConditionCodes(InstructionCondition::SkipConditionCodes), - InstParam::Register16Bit(Register16Bit::HL), - ), - 0xA => Instructions::LD( - InstParam::Number16Bit(self.get_16bit_from_pc()), - InstParam::Register8Bit(Register8Bit::A), - ), - 0xB..=0xD => Instructions::INVALID(0xB), - 0xE => Instructions::XOR(InstParam::Number8Bit(self.get_8bit_from_pc())), - 0xF => Instructions::RST(InstParam::Number8Bit(0x28)), - _ => self.not_implemented(opcode)?, - }, - 0xF => match tail { - 0x0 => Instructions::LDH( - InstParam::Register8Bit(Register8Bit::A), - InstParam::Number8Bit(self.get_8bit_from_pc()), - ), - 0x1 => Instructions::POP(InstParam::Register16Bit(Register16Bit::AF)), - 0x2 => Instructions::LDH( - InstParam::Register8Bit(Register8Bit::A), - InstParam::Register8Bit(Register8Bit::C), - ), - 0x3 => Instructions::DI, - 0x4 => Instructions::INVALID(0x4), - 0x5 => Instructions::PUSH(InstParam::Register16Bit(Register16Bit::AF)), - 0x6 => Instructions::OR(InstParam::Number8Bit(self.get_8bit_from_pc())), - 0x7 => Instructions::RST(InstParam::Number8Bit(0x30)), - 0x8 => Instructions::LD(InstParam::Register16Bit(Register16Bit::HL),InstParam::SignedNumber8Bit( - self.get_8bit_from_pc() as i8, - )), - 0x9 => Instructions::LD( - InstParam::Register16Bit(Register16Bit::SP), - InstParam::Register16Bit(Register16Bit::HL), - ), - 0xA => Instructions::LD( - InstParam::Register8Bit(Register8Bit::A), - InstParam::Number16Bit(self.get_16bit_from_pc()), - ), - 0xB => Instructions::EI, - 0xC | 0xD => Instructions::INVALID(0xC), - 0xE => Instructions::CP(InstParam::Number8Bit(self.get_8bit_from_pc())), - 0xF => Instructions::RST(InstParam::Number8Bit(0x38)), - _ => self.not_implemented(opcode)?, - }, - _ => self.not_implemented(opcode)?, - }) - } - - /// Decode a prefixed opcode - fn decode_prefixed(&self) -> Result { - let opcode = self - .memory - .read_byte(self.get_16bit_register(Register16Bit::PC) + 1); - - let head = opcode >> 4; - let tail = opcode & 0xF; - - let register_tail = if tail > 0x7 { tail - 0x8 } else { tail }; - let register = match register_tail { - 0x0 => InstParam::Register8Bit(Register8Bit::B), - 0x1 => InstParam::Register8Bit(Register8Bit::C), - 0x2 => InstParam::Register8Bit(Register8Bit::D), - 0x3 => InstParam::Register8Bit(Register8Bit::E), - 0x4 => InstParam::Register8Bit(Register8Bit::H), - 0x5 => InstParam::Register8Bit(Register8Bit::L), - 0x6 => InstParam::Register16Bit(Register16Bit::HL), - 0x7 => InstParam::Register8Bit(Register8Bit::A), - _ => return Err(format!("Unknown tail: {:#02X}", tail)), - }; - - // The second half of the tail is offset by 1 - let offset: u8 = if tail >= 0x8 { 1 } else { 0 }; - - Ok(match head { - 0x0 => match tail { - 0x0..=0x7 => Instructions::RLC(register), - 0x8..=0xF => Instructions::RRC(register), - _ => return self.not_implemented(opcode), - } - 0x1 => match tail { - 0x0..=0x7 => Instructions::RL(register), - 0x8..=0xF => Instructions::RR(register), - _ => return self.not_implemented(opcode), - } - 0x2 => match tail { - 0x0..=0x7 => Instructions::SLA(register), - 0x8..=0xF => Instructions::SRA(register), - _ => return self.not_implemented(opcode), - } - 0x3 => match tail { - 0x0..=0x7 => Instructions::SWAP(register), - 0x8..=0xF => Instructions::SRL(register), - _ => return self.not_implemented(opcode), - } - 0x4 => Instructions::BIT(InstParam::Unsigned3Bit(0 + offset), register), - 0x5 => Instructions::BIT(InstParam::Unsigned3Bit(2 + offset), register), - 0x6 => Instructions::BIT(InstParam::Unsigned3Bit(4 + offset), register), - 0x7 => Instructions::BIT(InstParam::Unsigned3Bit(6 + offset), register), - 0x8 => Instructions::RES(InstParam::Unsigned3Bit(0 + offset), register), - 0x9 => Instructions::RES(InstParam::Unsigned3Bit(2 + offset), register), - 0xA => Instructions::RES(InstParam::Unsigned3Bit(4 + offset), register), - 0xB => Instructions::RES(InstParam::Unsigned3Bit(6 + offset), register), - 0xC => Instructions::SET(InstParam::Unsigned3Bit(0 + offset), register), - 0xD => Instructions::SET(InstParam::Unsigned3Bit(2 + offset), register), - 0xE => Instructions::SET(InstParam::Unsigned3Bit(4 + offset), register), - 0xF => Instructions::SET(InstParam::Unsigned3Bit(6 + offset), register), - _ => return self.not_implemented(opcode), - }) - } -} - -/// Test the decoding of all opcodes -/// This will print out all decoded values and failed values -/// The decoded values will be written to `decoded_values.txt` and the failed values to `failed_values.txt` -/// This is useful to check if all opcodes are correctly decoded -/// Warning: This doesn't check if the decoded values are correct, only if they are decoded -/// Please cross-reference with a Gameboy opcode table -#[test] -pub fn test_decode() { - let mut cpu = CPU::new(false); - let mut decoded_values = String::new(); - let mut failed_values = String::new(); - - for i in 0..=0xFF { - // Write the opcode for 0xCB to memory - cpu.memory - .write_byte(cpu.get_16bit_register(Register16Bit::SP) + 1, i.clone()); - - for opcode in [i, 0xCB] { - let decoded_value = cpu.decode(opcode); - - let opcode = if opcode == 0xCB { - format!("0xCB | {:#02X}", i) - } else { - format!("{:#02X}", i) - }; - - if let Ok(val) = decoded_value { - decoded_values.push_str(&format!("{} -> {:?}\n", opcode, val)); - } else { - failed_values.push_str(&format!( - "{} -> {:?}\n", - opcode, - decoded_value.unwrap_err() - )); - } + self.decode_prefixed() + } else { + self.decode_unprefixed(opcode) } } - - println!("🟢 Decoded values: {:?}", decoded_values); - println!("🔴 Failed values: {:?}", failed_values); - - // To file - - use std::fs::File; - use std::io::Write; - - let mut file = File::create("decoded_values.txt").unwrap(); - file.write_all(decoded_values.as_bytes()).unwrap(); - let mut file = File::create("failed_values.txt").unwrap(); - file.write_all(failed_values.as_bytes()).unwrap(); -} +} \ No newline at end of file diff --git a/src/cpu/decode/helpers.rs b/src/cpu/decode/helpers.rs new file mode 100644 index 0000000..cb4fdf1 --- /dev/null +++ b/src/cpu/decode/helpers.rs @@ -0,0 +1,60 @@ +use crate::cpu::{instructions::{InstParam, Instructions}, registers::{Register16Bit, Register8Bit}, CPU}; + +impl CPU { + /// Decode the tail of an opcode to a 8 Bit Register + pub fn tail_to_inst_param(&self, tail: u8) -> InstParam { + // The tail repeats every 8 values, e.g. 0x0 & 0x8 are the same (B) + let tail = if tail > 0x7 { tail - 0x8 } else { tail }; + + match tail { + 0x0 => InstParam::Register8Bit(Register8Bit::B), + 0x1 => InstParam::Register8Bit(Register8Bit::C), + 0x2 => InstParam::Register8Bit(Register8Bit::D), + 0x3 => InstParam::Register8Bit(Register8Bit::E), + 0x4 => InstParam::Register8Bit(Register8Bit::H), + 0x5 => InstParam::Register8Bit(Register8Bit::L), + 0x6 => InstParam::Register16Bit(Register16Bit::HL), //haben Befehle mit [HL] nicht meistens eine andere Byte oder Cycle Anzahl? (z.B. CP A,[HL] und CPA, n8) + 0x7 => InstParam::Register8Bit(Register8Bit::A), + _ => panic!("Unknown tail: {:X}", tail), + } + } + + /// Calculate the target of a LD instruction based on the opcode + /// Returns None if the opcode is not a LD instruction + pub fn opcode_to_ld_target(&self, opcode: u8) -> Option { + Some(match opcode { + 0x40..=0x47 => InstParam::Register8Bit(Register8Bit::B), + 0x48..=0x4F => InstParam::Register8Bit(Register8Bit::C), + 0x50..=0x57 => InstParam::Register8Bit(Register8Bit::D), + 0x58..=0x5F => InstParam::Register8Bit(Register8Bit::E), + 0x60..=0x67 => InstParam::Register8Bit(Register8Bit::H), + 0x68..=0x6F => InstParam::Register8Bit(Register8Bit::L), + 0x70..=0x77 => InstParam::Register16Bit(Register16Bit::HL), + 0x78..=0x7F => InstParam::Register8Bit(Register8Bit::A), + _ => return None, + }) + } + + /// Get a 16-bit value from the program counter at the next two positions (PC + 1, PC + 2) + /// @warning: This will *not* increment the program counter + pub fn get_16bit_from_pc(&self) -> u16 { + self.memory + .read_word(self.get_16bit_register(Register16Bit::PC) + 1) + } + + /// Get a 8-bit value from the program counter at the next position (PC + 1) + /// @warning: This will *not* increment the program counter + pub fn get_8bit_from_pc(&self) -> u8 { + self.memory + .read_byte(self.get_16bit_register(Register16Bit::PC) + 1) + } + + pub fn get_8bit_from_hl(&self) -> u8 { + self.memory + .read_byte(self.get_16bit_register(Register16Bit::HL)) + } + + pub fn not_implemented(&self, opcode: u8) -> Result { + Err(format!("Opcode is not implemented (yet): {:#02X}", opcode)) + } +} \ No newline at end of file diff --git a/src/cpu/decode/prefixed.rs b/src/cpu/decode/prefixed.rs new file mode 100644 index 0000000..aadaae7 --- /dev/null +++ b/src/cpu/decode/prefixed.rs @@ -0,0 +1,65 @@ +use crate::cpu::{instructions::{InstParam, Instructions}, registers::{Register16Bit, Register8Bit}, CPU}; + +impl CPU { + /// Decode a prefixed opcode (0xCB) + pub fn decode_prefixed(&self) -> Result { + let opcode = self + .memory + .read_byte(self.get_16bit_register(Register16Bit::PC) + 1); + + let head = opcode >> 4; + let tail = opcode & 0xF; + + let register_tail = if tail > 0x7 { tail - 0x8 } else { tail }; + let register = match register_tail { + 0x0 => InstParam::Register8Bit(Register8Bit::B), + 0x1 => InstParam::Register8Bit(Register8Bit::C), + 0x2 => InstParam::Register8Bit(Register8Bit::D), + 0x3 => InstParam::Register8Bit(Register8Bit::E), + 0x4 => InstParam::Register8Bit(Register8Bit::H), + 0x5 => InstParam::Register8Bit(Register8Bit::L), + 0x6 => InstParam::Register16Bit(Register16Bit::HL), + 0x7 => InstParam::Register8Bit(Register8Bit::A), + _ => return Err(format!("Unknown tail: {:#02X}", tail)), + }; + + // The second half of the tail is offset by 1 + let offset: u8 = if tail >= 0x8 { 1 } else { 0 }; + + Ok(match head { + 0x0 => match tail { + 0x0..=0x7 => Instructions::RLC(register), + 0x8..=0xF => Instructions::RRC(register), + _ => return self.not_implemented(opcode), + } + 0x1 => match tail { + 0x0..=0x7 => Instructions::RL(register), + 0x8..=0xF => Instructions::RR(register), + _ => return self.not_implemented(opcode), + } + 0x2 => match tail { + 0x0..=0x7 => Instructions::SLA(register), + 0x8..=0xF => Instructions::SRA(register), + _ => return self.not_implemented(opcode), + } + 0x3 => match tail { + 0x0..=0x7 => Instructions::SWAP(register), + 0x8..=0xF => Instructions::SRL(register), + _ => return self.not_implemented(opcode), + } + 0x4 => Instructions::BIT(InstParam::Unsigned3Bit(offset), register), + 0x5 => Instructions::BIT(InstParam::Unsigned3Bit(2 + offset), register), + 0x6 => Instructions::BIT(InstParam::Unsigned3Bit(4 + offset), register), + 0x7 => Instructions::BIT(InstParam::Unsigned3Bit(6 + offset), register), + 0x8 => Instructions::RES(InstParam::Unsigned3Bit(offset), register), + 0x9 => Instructions::RES(InstParam::Unsigned3Bit(2 + offset), register), + 0xA => Instructions::RES(InstParam::Unsigned3Bit(4 + offset), register), + 0xB => Instructions::RES(InstParam::Unsigned3Bit(6 + offset), register), + 0xC => Instructions::SET(InstParam::Unsigned3Bit(offset), register), + 0xD => Instructions::SET(InstParam::Unsigned3Bit(2 + offset), register), + 0xE => Instructions::SET(InstParam::Unsigned3Bit(4 + offset), register), + 0xF => Instructions::SET(InstParam::Unsigned3Bit(6 + offset), register), + _ => return self.not_implemented(opcode), + }) + } +} \ No newline at end of file diff --git a/src/cpu/decode/test.rs b/src/cpu/decode/test.rs new file mode 100644 index 0000000..6e221ba --- /dev/null +++ b/src/cpu/decode/test.rs @@ -0,0 +1,54 @@ +#[cfg(test)] +use crate::cpu::{registers::Register16Bit, CPU}; + +/// Test the decoding of all opcodes +/// This will print out all decoded values and failed values +/// The decoded values will be written to `decoded_values.txt` and the failed values to `failed_values.txt` +/// This is useful to check if all opcodes are correctly decoded +/// Warning: This doesn't check if the decoded values are correct, only if they are decoded +/// Please cross-reference with a Gameboy opcode table +#[test] +pub fn test_decode() { + let mut cpu = CPU::new(false); + let mut decoded_values = String::new(); + let mut failed_values = String::new(); + + for i in 0..=0xFF { + // Write the opcode for 0xCB to memory + cpu.memory + .write_byte(cpu.get_16bit_register(Register16Bit::SP) + 1, i); + + for opcode in [i, 0xCB] { + let decoded_value = cpu.decode(opcode); + + let opcode = if opcode == 0xCB { + format!("0xCB | {:#02X}", i) + } else { + format!("{:#02X}", i) + }; + + if let Ok(val) = decoded_value { + decoded_values.push_str(&format!("{} -> {:?}\n", opcode, val)); + } else { + failed_values.push_str(&format!( + "{} -> {:?}\n", + opcode, + decoded_value.unwrap_err() + )); + } + } + } + + println!("🟢 Decoded values: {:?}", decoded_values); + println!("🔴 Failed values: {:?}", failed_values); + + // To file + + use std::fs::File; + use std::io::Write; + + let mut file = File::create("decoded_values.txt").unwrap(); + file.write_all(decoded_values.as_bytes()).unwrap(); + let mut file = File::create("failed_values.txt").unwrap(); + file.write_all(failed_values.as_bytes()).unwrap(); +} \ No newline at end of file diff --git a/src/cpu/decode/unprefixed.rs b/src/cpu/decode/unprefixed.rs new file mode 100644 index 0000000..50bcea7 --- /dev/null +++ b/src/cpu/decode/unprefixed.rs @@ -0,0 +1,276 @@ +use crate::cpu::{instructions::{InstParam, InstructionCondition, Instructions}, registers::{Register16Bit, Register8Bit}, CPU}; + +impl CPU { + /// Decode an unprefixed opcode (Everything that is not 0xCB) + pub fn decode_unprefixed(&self, opcode: u8) -> Result { + // Split the opcode into head and tail + // The head is the first 4 bits of the opcode e.g. 0x42 -> 0x4 + // The tail is the last 4 bits of the opcode e.g. 0x42 -> 0x2 + // This makes it a bit easier to decode the opcode + let head = opcode >> 4; + let tail = opcode & 0xF; + Ok(match head { + 0x0 => match tail { + 0x0 => Instructions::NOP, + 0x1 => self.decode_0x0_to_0x3_commons(opcode)?, + 0x2 => Instructions::LD( + InstParam::Register16Bit(Register16Bit::BC), + InstParam::Register8Bit(Register8Bit::A), + ), + 0x3..=0x6 => self.decode_0x0_to_0x3_commons(opcode)?, + 0x7 => Instructions::RLCA(), + 0x8 => Instructions::LD( + InstParam::Number16Bit(self.get_16bit_from_pc()), + InstParam::Register16Bit(Register16Bit::SP), + ), + 0x9 => self.decode_0x0_to_0x3_commons(opcode)?, + 0xA => Instructions::LD( + InstParam::Register8Bit(Register8Bit::A), + InstParam::Register16Bit(Register16Bit::BC), + ), + 0xB..=0xE => self.decode_0x0_to_0x3_commons(opcode)?, + 0xF => Instructions::RRCA(), + _ => self.not_implemented(opcode)?, + }, + 0x1 => match tail { + 0x0 => Instructions::STOP, + 0x1 => self.decode_0x0_to_0x3_commons(opcode)?, + 0x2 => Instructions::LD( + InstParam::Register16Bit(Register16Bit::DE), + InstParam::Register8Bit(Register8Bit::A), + ), + 0x3..=0x6 => self.decode_0x0_to_0x3_commons(opcode)?, + 0x7 => Instructions::RLA(), + 0x8 => Instructions::JR( + InstParam::ConditionCodes(InstructionCondition::SkipConditionCodes), + InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8), + ), + 0x9 => self.decode_0x0_to_0x3_commons(opcode)?, + 0xA => Instructions::LD( + InstParam::Register8Bit(Register8Bit::A), + InstParam::Register16Bit(Register16Bit::DE), + ), + 0xB..=0xE => self.decode_0x0_to_0x3_commons(opcode)?, + 0xF => Instructions::RRA(), + _ => self.not_implemented(opcode)?, + }, + 0x2 => match tail { + 0x0 => Instructions::JR( + InstParam::ConditionCodes(InstructionCondition::NotZero), + InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8), + ), + 0x1 => self.decode_0x0_to_0x3_commons(opcode)?, + 0x2 => Instructions::LDHLIA, + 0x3..=0x6 => self.decode_0x0_to_0x3_commons(opcode)?, + 0x7 => Instructions::DAA, + 0x8 => Instructions::JR( + InstParam::ConditionCodes(InstructionCondition::Zero), + InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8), + ), + 0x9 => self.decode_0x0_to_0x3_commons(opcode)?, + 0xA => Instructions::LDAHLI, + 0xB..=0xE => self.decode_0x0_to_0x3_commons(opcode)?, + 0xF => Instructions::CPL, + _ => self.not_implemented(opcode)?, + }, + 0x3 => match tail { + 0x0 => Instructions::JR( + InstParam::ConditionCodes(InstructionCondition::NotCarry), + InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8), + ), + 0x1 => self.decode_0x0_to_0x3_commons(opcode)?, + 0x2 => Instructions::LDHLDA, + 0x3..=0x6 => self.decode_0x0_to_0x3_commons(opcode)?, + 0x7 => Instructions::SCF, + 0x8 => Instructions::JR( + InstParam::ConditionCodes(InstructionCondition::Carry), + InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8), + ), + 0x9 => self.decode_0x0_to_0x3_commons(opcode)?, + 0xA => Instructions::LDAHLD, + 0xB..=0xE => self.decode_0x0_to_0x3_commons(opcode)?, + 0xF => Instructions::CCF, + _ => self.not_implemented(opcode)?, + }, + // LD instructions (& HALT) + 0x4..=0x7 => { + let value = self.tail_to_inst_param(tail); + let ld_target = match self.opcode_to_ld_target(opcode) { + Some(target) => target, + None => return self.not_implemented(opcode), + }; + + // There is a single opcode within this range that is not a LD instruction + if opcode == 0x76 { + Instructions::HALT + } else { + Instructions::LD(ld_target, value) + } + } + // ADD, ADC, SUB, SBC, AND, XOR, OR, CP + 0x8..=0xB => { + let value = self.tail_to_inst_param(tail); + let is_second_half = tail > 0x7; + + if is_second_half { + match head { + 0x8 => Instructions::ADC(value), + 0x9 => Instructions::SBC(value), + 0xA => Instructions::XOR(value), + 0xB => match tail { + 0xE => Instructions::CP(InstParam::Register16Bit(Register16Bit::HL)), + _ => Instructions::CP(value), + } + _ => self.not_implemented(opcode)?, + } + } else { + match head { + 0x8 => Instructions::ADD(value), + 0x9 => Instructions::SUB(value), + 0xA => Instructions::AND(value), + 0xB => Instructions::OR(value), + _ => self.not_implemented(opcode)?, + } + } + } + 0xC => match tail { + 0x0 => Instructions::RET(InstParam::ConditionCodes(InstructionCondition::NotZero)), + 0x1 => Instructions::POP(InstParam::Register16Bit(Register16Bit::BC)), + 0x2 => Instructions::JP( + InstParam::ConditionCodes(InstructionCondition::NotZero), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0x3 => Instructions::JP( + InstParam::ConditionCodes(InstructionCondition::SkipConditionCodes), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0x4 => Instructions::CALL( + InstParam::ConditionCodes(InstructionCondition::NotZero), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0x5 => Instructions::PUSH(InstParam::Register16Bit(Register16Bit::BC)), + 0x6 => Instructions::ADD(InstParam::Number8Bit(self.get_8bit_from_pc())), + 0x7 => Instructions::RST(InstParam::Number8Bit(0x00)), + 0x8 => Instructions::RET(InstParam::ConditionCodes(InstructionCondition::Zero)), + 0x9 => Instructions::RET(InstParam::ConditionCodes( + InstructionCondition::SkipConditionCodes, + )), + 0xA => Instructions::JP( + InstParam::ConditionCodes(InstructionCondition::Zero), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0xB => { + return Err(format!( + "Prefixed Opcodes should have already been handled 😕 {:#02X}", + opcode + )) + } + 0xC => Instructions::CALL( + InstParam::ConditionCodes(InstructionCondition::Zero), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0xD => Instructions::CALL( + InstParam::ConditionCodes(InstructionCondition::SkipConditionCodes), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0xE => Instructions::ADC(InstParam::Number8Bit(self.get_8bit_from_pc())), + 0xF => Instructions::RST(InstParam::Number8Bit(0x08)), + _ => self.not_implemented(opcode)?, + }, + 0xD => match tail { + 0x0 => Instructions::RET(InstParam::ConditionCodes(InstructionCondition::NotCarry)), + 0x1 => Instructions::POP(InstParam::Register16Bit(Register16Bit::DE)), + 0x2 => Instructions::JP( + InstParam::ConditionCodes(InstructionCondition::NotCarry), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0x3 => Instructions::INVALID(0x3), + 0x4 => Instructions::CALL( + InstParam::ConditionCodes(InstructionCondition::NotCarry), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0x5 => Instructions::PUSH(InstParam::Register16Bit(Register16Bit::DE)), + 0x6 => Instructions::SUB(InstParam::Number8Bit(self.get_8bit_from_pc())), + 0x7 => Instructions::RST(InstParam::Number8Bit(0x10)), + 0x8 => Instructions::RET(InstParam::ConditionCodes(InstructionCondition::Carry)), + 0x9 => Instructions::RETI, + 0xA => Instructions::JP( + InstParam::ConditionCodes(InstructionCondition::Carry), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0xB => Instructions::INVALID(0xB), + 0xC => Instructions::CALL( + InstParam::ConditionCodes(InstructionCondition::Carry), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0xD => Instructions::INVALID(0xD), + 0xE => Instructions::SBC(InstParam::Number8Bit(self.get_8bit_from_pc())), + 0xF => Instructions::RST(InstParam::Number8Bit(0x18)), + _ => self.not_implemented(opcode)?, + }, + 0xE => match tail { + 0x0 => Instructions::LDH( + InstParam::Number8Bit(self.get_8bit_from_pc()), + InstParam::Register8Bit(Register8Bit::A), + ), + 0x1 => Instructions::POP(InstParam::Register16Bit(Register16Bit::HL)), + 0x2 => Instructions::LDH( + InstParam::Register8Bit(Register8Bit::C), + InstParam::Register8Bit(Register8Bit::A), + ), + 0x3 | 0x4 => Instructions::INVALID(0x3), + 0x5 => Instructions::PUSH(InstParam::Register16Bit(Register16Bit::HL)), + 0x6 => Instructions::AND(InstParam::Number8Bit(self.get_8bit_from_pc())), + 0x7 => Instructions::RST(InstParam::Number8Bit(0x20)), + 0x8 => { + Instructions::ADD(InstParam::SignedNumber8Bit(self.get_8bit_from_pc() as i8)) + } + 0x9 => Instructions::JP( + InstParam::ConditionCodes(InstructionCondition::SkipConditionCodes), + InstParam::Register16Bit(Register16Bit::HL), + ), + 0xA => Instructions::LD( + InstParam::Number16Bit(self.get_16bit_from_pc()), + InstParam::Register8Bit(Register8Bit::A), + ), + 0xB..=0xD => Instructions::INVALID(0xB), + 0xE => Instructions::XOR(InstParam::Number8Bit(self.get_8bit_from_pc())), + 0xF => Instructions::RST(InstParam::Number8Bit(0x28)), + _ => self.not_implemented(opcode)?, + }, + 0xF => match tail { + 0x0 => Instructions::LDH( + InstParam::Register8Bit(Register8Bit::A), + InstParam::Number8Bit(self.get_8bit_from_pc()), + ), + 0x1 => Instructions::POP(InstParam::Register16Bit(Register16Bit::AF)), + 0x2 => Instructions::LDH( + InstParam::Register8Bit(Register8Bit::A), + InstParam::Register8Bit(Register8Bit::C), + ), + 0x3 => Instructions::DI, + 0x4 => Instructions::INVALID(0x4), + 0x5 => Instructions::PUSH(InstParam::Register16Bit(Register16Bit::AF)), + 0x6 => Instructions::OR(InstParam::Number8Bit(self.get_8bit_from_pc())), + 0x7 => Instructions::RST(InstParam::Number8Bit(0x30)), + 0x8 => Instructions::LD(InstParam::Register16Bit(Register16Bit::HL),InstParam::SignedNumber8Bit( + self.get_8bit_from_pc() as i8, + )), + 0x9 => Instructions::LD( + InstParam::Register16Bit(Register16Bit::SP), + InstParam::Register16Bit(Register16Bit::HL), + ), + 0xA => Instructions::LD( + InstParam::Register8Bit(Register8Bit::A), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0xB => Instructions::EI, + 0xC | 0xD => Instructions::INVALID(0xC), + 0xE => Instructions::CP(InstParam::Number8Bit(self.get_8bit_from_pc())), + 0xF => Instructions::RST(InstParam::Number8Bit(0x38)), + _ => self.not_implemented(opcode)?, + }, + _ => self.not_implemented(opcode)?, + }) + } +} \ No newline at end of file diff --git a/src/cpu/decode/unprefixed_commons.rs b/src/cpu/decode/unprefixed_commons.rs new file mode 100644 index 0000000..95271bb --- /dev/null +++ b/src/cpu/decode/unprefixed_commons.rs @@ -0,0 +1,78 @@ +use crate::cpu::{instructions::{InstParam, Instructions}, registers::{Register16Bit, Register8Bit}, CPU}; + +impl CPU { + /// Decode the unprefixed common opcodes (0x0 - 0x3) + pub fn decode_0x0_to_0x3_commons(&self, opcode: u8) -> Result { + let head = opcode >> 4; + let tail = opcode & 0xF; + + let register_8bit = match head { + 0x0 => { + if tail < 0xB { + Register8Bit::B + } else { + Register8Bit::C + } + } + 0x1 => { + if tail < 0xB { + Register8Bit::D + } else { + Register8Bit::E + } + } + 0x2 => { + if tail < 0xB { + Register8Bit::H + } else { + Register8Bit::L + } + } + 0x3 => Register8Bit::A, + _ => return Err(format!("{:#02X}", opcode)), + }; + + let register_16bit = match head { + 0x0 => Register16Bit::BC, + 0x1 => Register16Bit::DE, + 0x2 => Register16Bit::HL, + 0x3 => Register16Bit::SP, + _ => return Err(format!("{:#02X}", opcode)), + }; + + Ok(match tail { + 0x1 => Instructions::LD( + InstParam::Register16Bit(register_16bit), + InstParam::Number16Bit(self.get_16bit_from_pc()), + ), + 0x3 => Instructions::INC(InstParam::Register16Bit(register_16bit),InstParam::Boolean(false)), + 0x4 => Instructions::INC(if head == 0x3 { + InstParam::Register16Bit(Register16Bit::HL) // Special case for (HL) + } else { + InstParam::Register8Bit(register_8bit) + }, if head == 0x3 {InstParam::Boolean(true)}else{InstParam::Boolean(false)}), + 0x5 => Instructions::DEC(if head == 0x3 { + InstParam::Register16Bit(Register16Bit::HL) // Special case for (HL) + } else { + InstParam::Register8Bit(register_8bit) + }, if head == 0x3 {InstParam::Boolean(true)}else{InstParam::Boolean(false)}), + 0x6 => Instructions::LD( + if head == 0x3 { + InstParam::Register16Bit(Register16Bit::HL) // Special case for (HL) + } else { + InstParam::Register8Bit(register_8bit) + }, + InstParam::Number8Bit(self.get_8bit_from_pc()), + ), + 0x9 => Instructions::ADD_HL(InstParam::Register16Bit(register_16bit)), + 0xB => Instructions::DEC(InstParam::Register16Bit(register_16bit),InstParam::Boolean(false)), + 0xC => Instructions::INC(InstParam::Register8Bit(register_8bit),InstParam::Boolean(false)), + 0xD => Instructions::DEC(InstParam::Register8Bit(register_8bit),InstParam::Boolean(false)), + 0xE => Instructions::LD( + InstParam::Register8Bit(register_8bit), + InstParam::Number8Bit(self.get_8bit_from_pc()), + ), + _ => return Err(format!("Not covered in common {:#02X}", opcode)), + }) + } +} \ No newline at end of file diff --git a/src/cpu/helpers.rs b/src/cpu/helpers.rs new file mode 100644 index 0000000..86e407a --- /dev/null +++ b/src/cpu/helpers.rs @@ -0,0 +1,107 @@ +use crate::memory::Memory; + +use super::{instructions::{InstructionResult, Instructions}, registers::{Register16Bit, Register8Bit}, CPU}; + + + +impl CPU { + /// Skip the bootrom + /// Set the registers to the correct values + /// Used for: https://robertheaton.com/gameboy-doctor/ + pub fn skip_boot_rom(&mut self) { + self.set_8bit_register(Register8Bit::A, 0x01); + self.set_zero_flag(); + self.set_half_carry_flag(); + self.set_carry_flag(); + self.set_8bit_register(Register8Bit::B, 0x00); + self.set_8bit_register(Register8Bit::C, 0x13); + self.set_8bit_register(Register8Bit::D, 0x00); + self.set_8bit_register(Register8Bit::E, 0xD8); + self.set_8bit_register(Register8Bit::H, 0x01); + self.set_8bit_register(Register8Bit::L, 0x4D); + self.set_16bit_register(Register16Bit::SP, 0xFFFE); + self.set_16bit_register(Register16Bit::PC, 0x0100); + self.memory.boot_rom_enabled = false; + // Set Joypad register + self.memory.write_byte(0xFF00, 0b1111_1111); + } + + /// Polls the inputs + /// Warning, this will loop till input is received when self.stop_mode is true + pub fn poll_inputs(&mut self) { + loop { + self.update_key_input(); + + if !self.is_in_stop_mode() { + break; + } + } + } + + pub fn is_in_stop_mode(&self) -> bool { + self.stop_mode + } + + // Print blarg serial output + pub fn blarg_print(&mut self) { + let serial_data = self.memory.read_byte(0xFF02); + if serial_data == 0x81 { + let data = self.memory.read_byte(0xFF01); + print!("{}", data as char); + self.memory.write_byte(0xFF02, 0x0); + } + } + + pub fn load_from_file(&mut self, file: &str, offset: usize) { + self.memory.load_from_file(file, offset); + } + + pub fn get_next_opcode(&mut self) -> u8 { + self.memory + .read_byte(self.get_16bit_register(Register16Bit::PC)) + } + + pub fn write_memory(&mut self, address: u16, value: u8) { + self.memory.write_byte(address, value); + } + + /// Set the next instruction to be executed + /// This is used for testing + pub fn set_instruction(&mut self, instruction: Instructions) { + self.next_instruction = instruction; + } + + pub fn dump_memory(&self) { + self.memory.dump_to_file(); + } + + /// Get the last step result + /// This is used for testing purposes + pub fn get_last_step_result(&self) -> InstructionResult { + self.last_step_result.clone() + } + + pub fn get_cycles(&self) -> u64 { + self.cycles + } + + pub fn is_boot_rom_enabled(&self) -> bool { + self.memory.is_boot_rom_enabled() + } + + pub fn get_instruction(&self) -> &Instructions { + &self.next_instruction + } + + #[cfg(test)] + pub fn get_registry_dump(&self) -> [u8; 12] { + self.registers + } + + /// Gets the full memory of the CPU + /// This is used for testing purposes + /// @warning This is a very expensive operation + pub fn get_memory(&self) -> Memory { + self.memory.clone() + } +} \ No newline at end of file diff --git a/src/cpu/instructions.rs b/src/cpu/instructions.rs index 911e7a1..5f56a77 100644 --- a/src/cpu/instructions.rs +++ b/src/cpu/instructions.rs @@ -43,8 +43,8 @@ pub struct InstructionResult { pub condition_codes: ConditionCodes, } -impl InstructionResult { - pub fn default() -> InstructionResult { +impl Default for InstructionResult { + fn default() -> InstructionResult { InstructionResult { cycles: 0, bytes: 0, @@ -88,6 +88,7 @@ pub enum InstParam { #[derive(Debug, PartialEq, Clone)] pub enum Instructions { ADD(InstParam), + #[allow(non_camel_case_types)] ADD_HL(InstParam), //ADD_SP(InstParam), ADC(InstParam), diff --git a/src/cpu/instructions/arithmetic_and_logic/add.rs b/src/cpu/instructions/arithmetic_and_logic/add.rs index 8eee053..5b32837 100644 --- a/src/cpu/instructions/arithmetic_and_logic/add.rs +++ b/src/cpu/instructions/arithmetic_and_logic/add.rs @@ -147,7 +147,7 @@ impl CPU { /// https://rgbds.gbdev.io/docs/v0.6.1/gbz80.7/#ADD_SP,e8 pub fn add_sp_e8(&mut self, value: i8) -> InstructionResult { let sp = self.get_16bit_register(Register16Bit::SP); - let (result, overflow) = sp.overflowing_add(value as u16); + let (result, _overflow) = sp.overflowing_add(value as u16); self.set_16bit_register(Register16Bit::SP, result); diff --git a/src/cpu/instructions/arithmetic_and_logic/cp.rs b/src/cpu/instructions/arithmetic_and_logic/cp.rs index 7b8fc33..af7e954 100644 --- a/src/cpu/instructions/arithmetic_and_logic/cp.rs +++ b/src/cpu/instructions/arithmetic_and_logic/cp.rs @@ -3,7 +3,7 @@ impl CPU { /// subtract r8 from A without storing and set flags accordingly pub fn cp_a_r8(&mut self, register: Register8Bit) -> InstructionResult { let a_value = self.get_8bit_register(Register8Bit::A); - let tail = a_value & 0xF; + let _tail = a_value & 0xF; let r8_value = self.get_8bit_register(register); let (value,overflow) = a_value.overflowing_sub(r8_value); @@ -38,7 +38,7 @@ impl CPU { pub fn cp_a_hl(&mut self) -> InstructionResult { let addr = self.get_16bit_register(Register16Bit::HL); let a_value = self.get_8bit_register(Register8Bit::A); - let tail = a_value & 0xF; + let _tail = a_value & 0xF; let r8_value = self.memory.read_byte(addr); let (value,overflow) = a_value.overflowing_sub(r8_value); diff --git a/src/cpu/instructions/arithmetic_and_logic/dec.rs b/src/cpu/instructions/arithmetic_and_logic/dec.rs index 7c74edf..626805d 100644 --- a/src/cpu/instructions/arithmetic_and_logic/dec.rs +++ b/src/cpu/instructions/arithmetic_and_logic/dec.rs @@ -2,7 +2,7 @@ use crate::cpu::{instructions::{ConditionCodes, FlagState, InstructionResult}, r impl CPU { /// decrements the 16bit_register register, wraps on overflow pub fn dec_r16(&mut self, register: Register16Bit) -> InstructionResult { - let (value,overflow) = self.get_16bit_register(register).overflowing_sub(1); + let (value,_overflow) = self.get_16bit_register(register).overflowing_sub(1); self.set_16bit_register(register, value); InstructionResult { diff --git a/src/cpu/instructions/arithmetic_and_logic/inc.rs b/src/cpu/instructions/arithmetic_and_logic/inc.rs index 43043e3..9d439d9 100644 --- a/src/cpu/instructions/arithmetic_and_logic/inc.rs +++ b/src/cpu/instructions/arithmetic_and_logic/inc.rs @@ -3,7 +3,7 @@ use crate::cpu::{instructions::{ConditionCodes, FlagState, InstructionResult}, r impl CPU { pub fn inc(&mut self, register: Register8Bit) -> InstructionResult { let r8_value = self.get_8bit_register(register); - let (result, overflow) = r8_value.overflowing_add(1); + let (result, _overflow) = r8_value.overflowing_add(1); self.set_8bit_register(register, result); let tail = r8_value & 0xF; @@ -22,7 +22,7 @@ impl CPU { pub fn inc_hl(&mut self) -> InstructionResult { let addr = self.get_16bit_register(Register16Bit::HL); let r8_value = self.memory.read_byte(addr); - let (value,overflow) = r8_value.overflowing_add(1); + let (value,_overflow) = r8_value.overflowing_add(1); self.memory.write_byte(addr, value); let tail = r8_value & 0xF; @@ -39,7 +39,7 @@ impl CPU { } /// increments the 16bit_register register, wraps on overflow pub fn inc_r16(&mut self, register: Register16Bit) -> InstructionResult { - let (value,overflow) = self.get_16bit_register(register).overflowing_add(1); + let (value,_overflow) = self.get_16bit_register(register).overflowing_add(1); self.set_16bit_register(register, value); InstructionResult { diff --git a/src/cpu/instructions/arithmetic_and_logic/sub.rs b/src/cpu/instructions/arithmetic_and_logic/sub.rs index c97e5aa..6c3ea0f 100644 --- a/src/cpu/instructions/arithmetic_and_logic/sub.rs +++ b/src/cpu/instructions/arithmetic_and_logic/sub.rs @@ -24,8 +24,7 @@ impl CPU { half_carry: if ((a ^ value) & 0x10) != (result & 0x10) {FlagState::Set} else {FlagState::Unset}, carry: if add_carry { if is_carry > a || overflow {FlagState::Set} else {FlagState::Unset} - }else { - if value > a {FlagState::Set} else {FlagState::Unset}}, + }else if value > a {FlagState::Set} else {FlagState::Unset}, }, } } diff --git a/src/cpu/instructions/bit_operations.rs b/src/cpu/instructions/bit_operations.rs index 2686a78..01fac34 100644 --- a/src/cpu/instructions/bit_operations.rs +++ b/src/cpu/instructions/bit_operations.rs @@ -1,5 +1,5 @@ use crate::cpu::{ - instructions::{ConditionCodes, FlagState, InstParam, InstructionResult, Instructions}, + instructions::{ConditionCodes, FlagState, InstructionResult}, registers::{Register16Bit, Register8Bit}, CPU, }; diff --git a/src/cpu/instructions/jumps_subroutines.rs b/src/cpu/instructions/jumps_subroutines.rs index 7c92303..f671072 100644 --- a/src/cpu/instructions/jumps_subroutines.rs +++ b/src/cpu/instructions/jumps_subroutines.rs @@ -1,6 +1,6 @@ use crate::cpu::{ - instructions::{ConditionCodes, FlagState, InstParam, InstructionResult, Instructions}, - registers::{Register16Bit, Register8Bit}, + instructions::{ConditionCodes, FlagState, InstructionResult}, + registers::{Register16Bit}, CPU, }; @@ -247,7 +247,7 @@ pub fn jumps_subroutines_test() { registers = cpu.get_registry_dump(); let register_value = Register16Bit::PC as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0x00A0); @@ -259,7 +259,7 @@ pub fn jumps_subroutines_test() { assert_correct_instruction_step(&mut cpu, Instructions::JP(super::InstParam::Register16Bit(Register16Bit::HL), super::InstParam::Number16Bit(0x000A)), expected_result); registers = cpu.get_registry_dump(); let register_value = Register16Bit::PC as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0x0A00); @@ -270,7 +270,7 @@ pub fn jumps_subroutines_test() { assert_correct_instruction_step(&mut cpu, Instructions::RET(super::InstParam::Offset), expected_result); registers = cpu.get_registry_dump(); let register_value = Register16Bit::PC as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0x000D); @@ -290,7 +290,7 @@ pub fn jumps_subroutines_test() { assert_correct_instruction_step(&mut cpu, Instructions::RST(super::InstParam::Number8Bit(0x18)), expected_result); registers = cpu.get_registry_dump(); let register_value = Register16Bit::PC as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0x18); diff --git a/src/cpu/instructions/load.rs b/src/cpu/instructions/load.rs index be865b1..d6dbd16 100644 --- a/src/cpu/instructions/load.rs +++ b/src/cpu/instructions/load.rs @@ -1,5 +1,5 @@ use crate::cpu::{ - instructions::{ConditionCodes, FlagState, InstructionResult,Instructions}, + instructions::{ConditionCodes, FlagState, InstructionResult}, registers::{Register16Bit, Register8Bit}, CPU, }; @@ -426,7 +426,7 @@ pub fn load_test() { registers = cpu.get_registry_dump(); assert_eq!(registers[Register8Bit::A as usize], 42); let register_value = Register16Bit::DE as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0xF000u16); @@ -470,7 +470,7 @@ pub fn load_test() { cpu.ld_hli_a(); registers = cpu.get_registry_dump(); let register_value = Register16Bit::HL as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 256); @@ -479,7 +479,7 @@ pub fn load_test() { cpu.ld_hld_a(); registers = cpu.get_registry_dump(); let register_value = Register16Bit::HL as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0x00FF); @@ -493,7 +493,7 @@ pub fn load_test() { assert_eq!(registers[Register8Bit::A as usize], 131); let register_value = Register16Bit::HL as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 255); diff --git a/src/cpu/instructions/misc.rs b/src/cpu/instructions/misc.rs index ad2a43f..902d01f 100644 --- a/src/cpu/instructions/misc.rs +++ b/src/cpu/instructions/misc.rs @@ -1,13 +1,13 @@ -use std::{f32::consts::E, ops::Add}; -use macroquad::telemetry::capture_frame; + + use crate::cpu::CPU; #[cfg(test)] use crate::test_helpers::{assert_correct_instruction_decode, assert_correct_instruction_step}; -use super::{ConditionCodes, FlagState, InstructionResult, Instructions, Register8Bit}; +use super::{ConditionCodes, FlagState, InstructionResult, Register8Bit}; impl CPU { /// NOP instruction diff --git a/src/cpu/instructions/stack_operations.rs b/src/cpu/instructions/stack_operations.rs index 941fc59..75c767f 100644 --- a/src/cpu/instructions/stack_operations.rs +++ b/src/cpu/instructions/stack_operations.rs @@ -256,7 +256,7 @@ pub fn stack_ops_test() { assert_correct_instruction_step(&mut cpu, Instructions::ADD(super::InstParam::Register16Bit(Register16Bit::SP)), expected_result); registers = cpu.get_registry_dump(); let register_value = Register16Bit::SP as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0xFEF0u16); @@ -268,7 +268,7 @@ pub fn stack_ops_test() { assert_correct_instruction_step(&mut cpu, Instructions::INC(super::InstParam::Register16Bit(Register16Bit::SP),super::InstParam::Boolean(false)), expected_result); registers = cpu.get_registry_dump(); let register_value = Register16Bit::SP as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0xFEF1u16); @@ -280,7 +280,7 @@ pub fn stack_ops_test() { registers = cpu.get_registry_dump(); let register_value = Register16Bit::SP as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0xFEF0u16); @@ -292,7 +292,7 @@ pub fn stack_ops_test() { assert_correct_instruction_step(&mut cpu, Instructions::LD(super::InstParam::Register16Bit(Register16Bit::SP), super::InstParam::Number16Bit(0xFF00)), expected_result); registers = cpu.get_registry_dump(); let register_value = Register16Bit::SP as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0xFF00u16); @@ -309,7 +309,7 @@ pub fn stack_ops_test() { assert_correct_instruction_step(&mut cpu, Instructions::LD(super::InstParam::Register16Bit(Register16Bit::HL), super::InstParam::SignedNumber8Bit(0xF0u8 as i8)), expected_result); registers = cpu.get_registry_dump(); let register_value = Register16Bit::HL as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0xFEF0u16); @@ -320,7 +320,7 @@ pub fn stack_ops_test() { assert_correct_instruction_step(&mut cpu, Instructions::LD(super::InstParam::Register16Bit(Register16Bit::SP), super::InstParam::Register16Bit(Register16Bit::HL)), expected_result); registers = cpu.get_registry_dump(); let register_value = Register16Bit::SP as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0xFEF0u16); @@ -359,13 +359,13 @@ pub fn stack_ops_test() { assert_eq!(mem, 0xA0); registers = cpu.get_registry_dump(); let register_value = Register16Bit::AF as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0xAAA0); registers = cpu.get_registry_dump(); let register_value = Register16Bit::DE as usize; - let high = registers[register_value.clone()] as u16; + let high = registers[register_value] as u16; let low = registers[register_value + 1] as u16; let result = (high << 8) | low; assert_eq!(result, 0xA1A0); diff --git a/src/cpu/interrupts.rs b/src/cpu/interrupts.rs index 5166423..4e1bce4 100644 --- a/src/cpu/interrupts.rs +++ b/src/cpu/interrupts.rs @@ -1,8 +1,8 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; -use crate::{cpu::registers::Register16Bit, rendering::line_rendering::Ppu}; +use crate::{cpu::registers::Register16Bit}; -use super::{instructions::InstructionResult, CPU}; +use super::{CPU}; #[derive(Debug, IntoPrimitive, Clone, Copy)] #[repr(u8)] diff --git a/src/cpu/registers.rs b/src/cpu/registers.rs index cdadd8d..280de23 100644 --- a/src/cpu/registers.rs +++ b/src/cpu/registers.rs @@ -35,7 +35,7 @@ impl CPU { /// Get a 16-bit register (e.g. AF) pub fn get_16bit_register(&self, register: Register16Bit) -> u16 { let register_value = register as usize; - let high = self.registers[register_value.clone()] as u16; + let high = self.registers[register_value] as u16; let low = self.registers[register_value + 1] as u16; (high << 8) | low } diff --git a/src/cpu/render_operations.rs b/src/cpu/render_operations.rs index 04d5707..3033367 100644 --- a/src/cpu/render_operations.rs +++ b/src/cpu/render_operations.rs @@ -60,7 +60,7 @@ impl CPU { ) -> [u8; 8] { let mut line_data: [u8; 8] = [0; 8]; - let mut line_addr: u16 = 0x8000 + (tile_line * 2) as u16 + 16 * tile_index as u16; + let mut line_addr: u16 = 0x8000 + (tile_line * 2) as u16 + 16 * tile_index; if high_addressing && tile_index < 0x80 { line_addr += 0x1000; diff --git a/src/cpu/step.rs b/src/cpu/step.rs index be19af1..3c01d72 100644 --- a/src/cpu/step.rs +++ b/src/cpu/step.rs @@ -1,12 +1,12 @@ -use core::task; -use std::{thread::sleep, time::Duration}; -use crate::cpu::instructions::ConditionCodes; + + + use super::{ instructions::{FlagState, InstParam, InstructionCondition, InstructionResult, Instructions}, - registers::{self, Register16Bit, Register8Bit}, - CPU, CPU_FREQUENCY, + registers::{Register16Bit, Register8Bit}, + CPU, }; impl CPU { @@ -41,7 +41,7 @@ impl CPU { self.last_step_result = match &self.next_instruction { Instructions::ADD(param) => match param { - InstParam::Register8Bit(register) => self.add_a_r8(register.clone()), + InstParam::Register8Bit(register) => self.add_a_r8(*register), InstParam::Register16Bit(register) => { if *register == Register16Bit::HL { self.add_a_hl() @@ -58,44 +58,44 @@ impl CPU { _ => return Err(format!("ADD_HL with {:?} not implemented", param)), } Instructions::ADC(param) => match param { - InstParam::Register8Bit(register) => self.adc_a_r8(register.clone()), - InstParam::Register16Bit(register) => self.adc_a_hl(), + InstParam::Register8Bit(register) => self.adc_a_r8(*register), + InstParam::Register16Bit(_register) => self.adc_a_hl(), InstParam::Number8Bit(value) => self.adc_a_n8(*value), _ => return Err(format!("ADD with {:?} not implemented", param)), }, Instructions::INC(param, hl_memory) => match param { - InstParam::Register8Bit(register) => self.inc(register.clone()), + InstParam::Register8Bit(register) => self.inc(*register), InstParam::Register16Bit(register) => match register { Register16Bit::SP => self.inc_sp(), Register16Bit::HL => match hl_memory { InstParam::Boolean(hl_with_memory) => { - if (*hl_with_memory) { + if *hl_with_memory { self.inc_hl() } else { - self.inc_r16(register.clone()) + self.inc_r16(*register) } } _ => return Err(format!("INC with {:?} not implemented", param)), }, - _ => self.inc_r16(register.clone()), + _ => self.inc_r16(*register), }, _ => return Err(format!("INC with {:?} not implemented", param)), }, Instructions::DEC(param, hl_memory) => match param { - InstParam::Register8Bit(register) => self.dec_r8(register.clone()), + InstParam::Register8Bit(register) => self.dec_r8(*register), InstParam::Register16Bit(register) => match register { Register16Bit::SP => self.dec_sp(), Register16Bit::HL => match hl_memory { InstParam::Boolean(hl_with_memory) => { - if (*hl_with_memory) { + if *hl_with_memory { self.dec_hl() } else { - self.dec_r16(register.clone()) + self.dec_r16(*register) } } _ => return Err(format!("INC with {:?} not implemented", param)), }, - _ => self.dec_r16(register.clone()), + _ => self.dec_r16(*register), }, _ => return Err(format!("INC with {:?} not implemented", param)), }, @@ -319,7 +319,7 @@ impl CPU { InstParam::Number8Bit(source_number) => { self.ld_r8_n8(*target_register, *source_number) } - InstParam::Register16Bit(source_register) => { + InstParam::Register16Bit(_source_register) => { self.ld_r8_hl(*target_register) } _ => return Err(format!("Handling of {:?} not implemented", source)), @@ -329,7 +329,7 @@ impl CPU { InstParam::Register16Bit(target_register) => { if *target_register == Register16Bit::SP { match source { - InstParam::Register16Bit(source_register) => self.ld_sp_hl(), + InstParam::Register16Bit(_source_register) => self.ld_sp_hl(), InstParam::Number16Bit(source_address) => { self.ld_sp_n16(*source_address) } @@ -354,7 +354,7 @@ impl CPU { InstParam::Number16Bit(source_number) => { self.ld_r16_n16(*target_register, *source_number) } - InstParam::Register8Bit(source_register) => { + InstParam::Register8Bit(_source_register) => { self.ld_r16_a(*target_register) } _ => return Err(format!("Handling of {:?} not implemented", source)), @@ -362,8 +362,8 @@ impl CPU { } } InstParam::Number16Bit(number) => match source { - InstParam::Register8Bit(source_register) => self.ld_n16_a(*number), - InstParam::Register16Bit(source_register) => self.ld_n16_sp(*number), + InstParam::Register8Bit(_source_register) => self.ld_n16_a(*number), + InstParam::Register16Bit(_source_register) => self.ld_n16_sp(*number), _ => { return Err(format!( "LD with n16 address of {:?} not implemented", @@ -451,10 +451,16 @@ impl CPU { | Instructions::RST(_) | Instructions::RET(_) | Instructions::RETI => {} - _ => self.set_16bit_register( + _ => { + let (new_val, overflow) = self.get_16bit_register(Register16Bit::PC).overflowing_add(self.last_step_result.bytes as u16); + if overflow { + log::warn!("Overflow when adding {:?} to PC", self.last_step_result.bytes); + } + self.set_16bit_register( Register16Bit::PC, - self.get_16bit_register(Register16Bit::PC) + self.last_step_result.bytes as u16, - ), + new_val + ) + } } self.update_ime(); @@ -508,60 +514,28 @@ impl CPU { fn check_condition(&self, cond: &InstructionCondition) -> bool { match cond { InstructionCondition::Zero => { - if self.is_zero_flag_set() { - true - } else { - false - } + self.is_zero_flag_set() } InstructionCondition::NotZero => { - if self.is_zero_flag_set() { - false - } else { - true - } + !self.is_zero_flag_set() } InstructionCondition::Subtract => { - if self.is_subtraction_flag_set() { - true - } else { - false - } + self.is_subtraction_flag_set() } InstructionCondition::NotSubtract => { - if self.is_subtraction_flag_set() { - false - } else { - true - } + !self.is_subtraction_flag_set() } InstructionCondition::Halfcarry => { - if self.is_half_carry_flag_set() { - true - } else { - false - } + self.is_half_carry_flag_set() } InstructionCondition::NotHalfcarry => { - if self.is_half_carry_flag_set() { - false - } else { - true - } + !self.is_half_carry_flag_set() } InstructionCondition::Carry => { - if self.is_carry_flag_set() { - true - } else { - false - } + self.is_carry_flag_set() } InstructionCondition::NotCarry => { - if self.is_carry_flag_set() { - false - } else { - true - } + !self.is_carry_flag_set() } InstructionCondition::SkipConditionCodes => true, } diff --git a/src/cpu/timer.rs b/src/cpu/timer.rs index 5cf0853..4b1642d 100644 --- a/src/cpu/timer.rs +++ b/src/cpu/timer.rs @@ -16,7 +16,7 @@ impl CPU { let (new_val, overflow) = previous_val.overflowing_add(1); if overflow { - log::info!("Timer overflow at Speed: {:#?} - resetting to modulo & setting interrupt flag", + log::debug!("Timer overflow at Speed: {:#?} - resetting to modulo & setting interrupt flag", self.get_timer_modulo()); self.memory.write_byte( TIMER_COUNTER_ADDRESS, @@ -31,7 +31,7 @@ impl CPU { /// Increment the divider register pub fn increment_div(&mut self) { let previous_val = self.memory.read_byte(0xFF04); - let (new_val, overflow) = previous_val.overflowing_add(1); + let (new_val, _overflow) = previous_val.overflowing_add(1); // Set to 0 if overflow self.memory.write_div_register(new_val); } diff --git a/src/main.rs b/src/main.rs index 5f286ff..b385cee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,6 @@ use rendering::{ use rfd::FileDialog; use simple_log::LogConfigBuilder; -#[macro_use] extern crate simple_log; use crate::cpu::registers::{Register16Bit, Register8Bit}; @@ -34,7 +33,7 @@ const WINDOWS: bool = true; async fn main() { //Set up logging let config = LogConfigBuilder::builder() - .size(1 * 1000) + .size(1000) .roll_count(10) .level("info") .output_console() @@ -203,7 +202,7 @@ fn dump_cpu_info(cpu: &CPU, destination: &mut File) { // Dump registers to file for Gameboy Doctor like this // A:00 F:11 B:22 C:33 D:44 E:55 H:66 L:77 SP:8888 PC:9999 PCMEM:AA,BB,CC,DD let _ = destination.write_all( - info_to_string(&cpu) + info_to_string(cpu) .as_bytes(), ); } diff --git a/src/memory.rs b/src/memory.rs index e3f53f6..bf3494a 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,16 +1,9 @@ -use std::{io::Write, mem}; -use std::sync::{Arc, Mutex}; - - /// Module for memory abstraction - -// These following modules do most of the abstraction work pub mod raw_memory_operations; -pub mod io_abstraction; +mod helpers; const MEMORY_SIZE: usize = 65537; const ROM_SIZE: usize = 256; - /// Abstraction over the raw memory of the Gameboy #[derive(Debug, Clone)] pub struct Memory { @@ -52,40 +45,4 @@ impl Memory { boot_rom, } } - - pub fn is_boot_rom_enabled(&self) -> bool { - self.boot_rom_enabled - } - - /// This is used for testing purposes - /// @warning This is really expensive and should only be used for testing - pub fn return_full_memory(&self) -> [u8; MEMORY_SIZE] { - self.memory.clone() - } - - pub fn load_from_file(&mut self, file_path: &str, offset: usize) { - let rom = std::fs::read(file_path).expect("Unable to read file"); - - for (i, byte) in rom.iter().enumerate() { - if i >= 0xFFFF { - log::error!("ROM is too large for memory: size: {}", rom.len()); - break; - } - - self.memory[i + offset] = *byte; - } - } - - /// Creates a new thread to dump the memory to a file (non-blocking) - pub fn dump_to_file(&self) { - let memory = self.clone(); - - std::thread::spawn(move || { - let mut file = std::fs::File::create("memory_dump.bin").expect("Unable to create file"); - - for byte in 0..MEMORY_SIZE { - file.write_all(memory.read_byte(byte as u16).to_le_bytes().as_ref()).expect("Unable to write to file"); - } - }); - } } \ No newline at end of file diff --git a/src/memory/helpers.rs b/src/memory/helpers.rs new file mode 100644 index 0000000..689f1dc --- /dev/null +++ b/src/memory/helpers.rs @@ -0,0 +1,37 @@ +use std::io::Write; + +use super::{Memory, MEMORY_SIZE}; + +impl Memory { + + pub fn is_boot_rom_enabled(&self) -> bool { + self.boot_rom_enabled + } + + /// This is used for testing purposes + /// @warning This is really expensive and should only be used for testing + pub fn return_full_memory(&self) -> [u8; MEMORY_SIZE] { + self.memory + } + + pub fn load_from_file(&mut self, file_path: &str, offset: usize) { + let rom = std::fs::read(file_path).expect("Unable to read file"); + + for (i, byte) in rom.iter().enumerate() { + self.memory[i + offset] = *byte; + } + } + + /// Creates a new thread to dump the memory to a file (non-blocking) + pub fn dump_to_file(&self) { + let memory = self.clone(); + + std::thread::spawn(move || { + let mut file = std::fs::File::create("memory_dump.bin").expect("Unable to create file"); + + for byte in 0..MEMORY_SIZE { + file.write_all(memory.read_byte(byte as u16).to_le_bytes().as_ref()).expect("Unable to write to file"); + } + }); + } +} \ No newline at end of file diff --git a/src/memory/io_abstraction.rs b/src/memory/io_abstraction.rs deleted file mode 100644 index 329cc22..0000000 --- a/src/memory/io_abstraction.rs +++ /dev/null @@ -1,75 +0,0 @@ -/// Used to abstract I/O register reads and writes for the Frontend - -use num_enum::IntoPrimitive; - -use super::Memory; - -/// Enum for the I/O registers -/// See: https://gbdev.io/pandocs/Hardware_Reg_List.html -#[derive(IntoPrimitive, Debug, Clone, Copy)] -#[repr(u16)] -pub enum HardwareRegisters { - JOYP = 0xFF00, // Joypad - SB = 0xFF01, // Serial Transfer Data - SC = 0xFF02, // Serial Transfer Control - DIV = 0xFF04, // Divider Register - TIMA = 0xFF05, // Timer Counter - TMA = 0xFF06, // Timer Modulo - TAC = 0xFF07, // Timer Control - IF = 0xFF0F, // Interrupt Flag - NR10 = 0xFF10, // Sound Mode 1 Sweep - NR11 = 0xFF11, // Sound Mode 1 Sound Length/Wave Pattern Duty - NR12 = 0xFF12, // Sound Mode 1 Envelope - NR13 = 0xFF13, // Sound Mode 1 Frequency Lo - NR14 = 0xFF14, // Sound Mode 1 Frequency Hi - NR21 = 0xFF16, // Sound Mode 2 Sound Length/Wave Pattern Duty - NR22 = 0xFF17, // Sound Mode 2 Envelope - NR23 = 0xFF18, // Sound Mode 2 Frequency Lo - NR24 = 0xFF19, // Sound Mode 2 Frequency Hi - NR30 = 0xFF1A, // Sound Mode 3 Sound On/Off - NR31 = 0xFF1B, // Sound Mode 3 Sound Length - NR32 = 0xFF1C, // Sound Mode 3 Select Output Level - NR33 = 0xFF1D, // Sound Mode 3 Frequency Lo - NR34 = 0xFF1E, // Sound Mode 3 Frequency Hi - NR41 = 0xFF20, // Sound Mode 4 Sound Length - NR42 = 0xFF21, // Sound Mode 4 Envelope - NR43 = 0xFF22, // Sound Mode 4 Polynomial Counter - NR44 = 0xFF23, // Sound Mode 4 Counter/Consecutive; Initial - NR50 = 0xFF24, // Channel Control / On-Off / Volume - NR51 = 0xFF25, // Selection of Sound Output Terminal - NR52 = 0xFF26, // Sound on/off - LCDC = 0xFF40, // LCD Control - STAT = 0xFF41, // LCDC Status - SCY = 0xFF42, // Viewport Y - SCX = 0xFF43, // Viewport X - LY = 0xFF44, // LCD Y-Coordinate - LYC = 0xFF45, // LY Compare - DMA = 0xFF46, // DMA Transfer and Start Address - BGP = 0xFF47, // BG Palette Data - OBP0 = 0xFF48, // Object Palette 0 Data - OBP1 = 0xFF49, // Object Palette 1 Data - WY = 0xFF4A, // Window Y Position - WX = 0xFF4B, // Window X Position + 7 - // If we get to the Gameboy Color, implement 0xFF4D - 0xFF77 - IE = 0xFFFF, // Interrupt Enable -} - -impl Memory { - /// Abstraction for Frontend to read from I/O registers - /// This is a convenience method to read from the I/O registers - /// but with easy to read enum values :) - /// Usage: memory.read_io_register(HardwareRegisters::JOYP); - /// This will read the value from the I/O register at 0xFF00 (JOYP) - pub fn read_io_register(&self, register: HardwareRegisters) -> u8 { - self.read_byte(register as u16) - } - - /// Abstraction for Frontend to write from I/O registers - /// This is a convenience method to write from the I/O registers - /// but with easy to read enum values :) - /// Usage: memory.write_io_register(HardwareRegisters::JOYP, 0x3F); - /// This will write the value 0x3F to the I/O register at 0xFF00 (JOYP) - pub fn write_io_register(&mut self, register: HardwareRegisters, value: u8) { - self.write_byte(register as u16, value); - } -} \ No newline at end of file diff --git a/src/rendering/line_rendering.rs b/src/rendering/line_rendering.rs index ab493c2..f578b8d 100644 --- a/src/rendering/line_rendering.rs +++ b/src/rendering/line_rendering.rs @@ -7,27 +7,28 @@ const DOTS_PER_LINE: u32 = 456; const SCAN_DOTS: u32 = 80; const MIN_DRAW_DOTS: u32 = 172; +#[allow(dead_code)] const MIN_HBLANK_DOTS: u32 = 87; const SCANLINES_ACTUAL: u8 = 144; const SCANLINES_EXTRA: u8 = 10; // Mode 2 -pub fn oam_scan(cpu: &CPU) {} +pub fn oam_scan(_cpu: &CPU) {} // Mode 3 pub fn draw_pixels(cpu: &mut CPU, game_diplay: &mut Image, palette: &[Color; 4]) { let high_map: bool = false; let high_addressing: bool = !cpu.get_lcdc_bg_window_tile_data(); - let scx = cpu.get_lcd_scx(); - let scy = cpu.get_lcd_scy(); + let _scx = cpu.get_lcd_scx(); + let _scy = cpu.get_lcd_scy(); let line: u8 = cpu.get_lcd_y_coordinate(); for xtile in 0..20 { let tile_index = cpu.get_vram_tile_map(high_map, (line as u16 / 8) * 32 + xtile); let line_data = - cpu.get_vram_tile_line(high_addressing, tile_index as u16, (line % 8) as u8); + cpu.get_vram_tile_line(high_addressing, tile_index as u16, line % 8); for x_pixel in 0..8 { //log::info!("Drawing pixel at x: {}, y: {}, xtile: {}, line: {}, color: {}", xtile as u32 * 8 + x_pixel, line as u32, xtile, line, line_data[x_pixel as usize]); @@ -52,6 +53,12 @@ pub struct Ppu { enabled: bool, } +impl Default for Ppu { + fn default() -> Self { + Self::new() + } +} + impl Ppu { pub fn new() -> Self { Ppu { @@ -75,7 +82,7 @@ impl Ppu { match ppu_mode { PpuMode::OamScan => { if dot % DOTS_PER_LINE == SCAN_DOTS - DOTS_PER_CYCLE { - oam_scan(&cpu); + oam_scan(cpu); cpu.set_ppu_mode(PpuMode::Drawing); } else if dot % DOTS_PER_LINE >= SCAN_DOTS { panic!("dot must be < 80 in OAM Scan Mode"); @@ -84,7 +91,7 @@ impl Ppu { PpuMode::Drawing => { // TODO Implement Variable Drawing Mode duration if dot % DOTS_PER_LINE == SCAN_DOTS + MIN_DRAW_DOTS - DOTS_PER_CYCLE { - draw_pixels(cpu, final_image, &palette); + draw_pixels(cpu, final_image, palette); cpu.set_ppu_mode(PpuMode::HorizontalBlank); } else if dot % DOTS_PER_LINE >= SCAN_DOTS + MIN_DRAW_DOTS { panic!("dot has an invalid value"); diff --git a/src/rendering/utils.rs b/src/rendering/utils.rs index b262d3f..43c273d 100644 --- a/src/rendering/utils.rs +++ b/src/rendering/utils.rs @@ -7,7 +7,7 @@ pub fn draw_scaled_text( scaling: f32, ) { draw_text( - &text, + text, offset_x + 4.0, offset_y + 24.0 * 8.0 * scaling + 16.0, 16.0, diff --git a/src/rendering/views.rs b/src/rendering/views.rs index e584b46..8845470 100644 --- a/src/rendering/views.rs +++ b/src/rendering/views.rs @@ -148,10 +148,10 @@ pub struct EmulationControls { impl EmulationControls { pub fn new(offset_x: f32, offset_y: f32, scaling: f32) -> EmulationControls { - let mut ec = EmulationControls { - offset_x: offset_x, - offset_y: offset_y, - scaling: scaling, + let ec = EmulationControls { + offset_x, + offset_y, + scaling, play_active: Texture2D::from_image( &Image::from_file_with_format( include_bytes!("../../assets/buttons/play-active.png"), diff --git a/src/test_helpers.rs b/src/test_helpers.rs index dfeef0e..6c2ed11 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -7,7 +7,7 @@ use super::cpu::instructions::Instructions; #[cfg(test)] pub fn assert_correct_instruction_step(cpu: &mut CPU, instruction: Instructions, expected_result: InstructionResult) { cpu.set_instruction(instruction); - cpu.step(); + let _ = cpu.step(); assert_eq!(cpu.get_last_step_result(), expected_result); }