diff --git a/src/cpu.rs b/src/cpu.rs index e0d07e9..e714dcd 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -34,6 +34,7 @@ impl Processor { parse::next_instruction(&memory_view).expect("invalid instruction"); let interrupt_enable_pending_already = self.interrupt_enable_pending; + let pc_before_instruction_run = self.registers.program_counter; self.run_instruction(instruction); // EI does not work until after the following instruction, so we check after running an instruction if @@ -43,8 +44,12 @@ impl Processor { self.registers.enable_interrupts(); } - let next_pc = pc.wrapping_add(bytes_read); - self.registers.program_counter = next_pc; + if pc_before_instruction_run == self.registers.program_counter { + // The program counter should be advanced *ONLY* if the instruction did not modify the PC register + + let next_pc = pc.wrapping_add(bytes_read); + self.registers.program_counter = next_pc; + } } /// Run a single instruction on the CPU. diff --git a/src/cpu/instructions.rs b/src/cpu/instructions.rs index 8537ed3..09ea854 100644 --- a/src/cpu/instructions.rs +++ b/src/cpu/instructions.rs @@ -6,6 +6,7 @@ use super::parse::{self, Error}; pub mod arith16; pub mod arith8; +pub mod control; pub mod load16; pub mod load8; pub mod misc; @@ -19,6 +20,7 @@ pub enum Instruction { EightBitArithmetic(arith8::EightBitArithmeticInstruction), StackPointerAdjust(arith8::AdjustStackPointerInstruction), SixteenBitArithmetic(arith16::SixteenBitArithmeticInstruction), + ControlFlow(control::ControlFlowInstruction), Misc(misc::MiscInstruction), } diff --git a/src/cpu/instructions/control.rs b/src/cpu/instructions/control.rs new file mode 100644 index 0000000..673fa7a --- /dev/null +++ b/src/cpu/instructions/control.rs @@ -0,0 +1,5 @@ +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Clone, Copy)] +pub enum ControlFlowInstruction { + JumpToImmediate { addr: u16 }, +} diff --git a/src/cpu/parse.rs b/src/cpu/parse.rs index 6b89ad2..ed7b6da 100644 --- a/src/cpu/parse.rs +++ b/src/cpu/parse.rs @@ -7,6 +7,7 @@ use thiserror::Error; mod arith16; mod arith8; +mod control; mod load16; mod load8; mod misc; @@ -45,6 +46,7 @@ pub fn next_instruction(data: &View) -> ParseResult { load16::stack::Parser::parse_opcode, arith8::Parser::parse_opcode, arith16::Parser::parse_opcode, + control::Parser::parse_opcode, misc::Parser::parse_opcode, ]; diff --git a/src/cpu/parse/control.rs b/src/cpu/parse/control.rs new file mode 100644 index 0000000..d92ce3a --- /dev/null +++ b/src/cpu/parse/control.rs @@ -0,0 +1,33 @@ +use crate::{ + cpu::{ + instructions::{control::ControlFlowInstruction, Instruction}, + parse, + }, + memory::{GetViewTuple, View}, +}; + +use super::{OpcodeParser, ParseOutput, ParseResult}; + +pub struct Parser; + +impl OpcodeParser for Parser { + fn parse_opcode(data: &View) -> ParseResult { + let opcode = parse::get_opcode_from_data(data); + match opcode { + 0xC3 => Ok(build_jump_to_immediate_data(data)), + _ => Err(parse::Error::UnknownOpcode(opcode)), + } + } +} + +fn build_jump_to_immediate_data(data: &View) -> ParseOutput { + let (_opcode, lower_bytes, upper_bytes) = data.get_tuple(); + + // manual doesn't state this should be LE, but some inspection of games and googling + // indicates it should be. + let addr = u16::from_le_bytes([lower_bytes, upper_bytes]); + ( + Instruction::ControlFlow(ControlFlowInstruction::JumpToImmediate { addr }), + 3, + ) +} diff --git a/src/cpu/run.rs b/src/cpu/run.rs index 2b40572..0b27321 100644 --- a/src/cpu/run.rs +++ b/src/cpu/run.rs @@ -7,6 +7,7 @@ use thiserror::Error; mod arith16; mod arith8; mod arithutil; +mod control; mod load16; mod load8; mod misc; @@ -48,6 +49,7 @@ pub fn run_instruction( Instruction::EightBitArithmetic(arith_instruction) => arith_instruction.run_on(processor), Instruction::StackPointerAdjust(adjust_instruction) => adjust_instruction.run_on(processor), Instruction::SixteenBitArithmetic(arith_instruction) => arith_instruction.run_on(processor), + Instruction::ControlFlow(control_instruction) => control_instruction.run_on(processor), Instruction::Misc(instruction) => instruction.run_on(processor), } } diff --git a/src/cpu/run/control.rs b/src/cpu/run/control.rs new file mode 100644 index 0000000..d5fd151 --- /dev/null +++ b/src/cpu/run/control.rs @@ -0,0 +1,17 @@ +use crate::cpu::{instructions::control::ControlFlowInstruction, register, Processor}; + +use super::{Cycles, Run}; + +impl Run for ControlFlowInstruction { + fn run_on(&self, processor: &mut Processor) -> Result { + match *self { + Self::JumpToImmediate { addr } => { + processor + .registers + .set_single_16bit_register(register::SingleSixteenBit::ProgramCounter, addr); + + Ok(Cycles(16)) + } + } + } +} diff --git a/tests/cpu/control.rs b/tests/cpu/control.rs new file mode 100644 index 0000000..265800a --- /dev/null +++ b/tests/cpu/control.rs @@ -0,0 +1,15 @@ +use ferris_boi::cpu::{instructions::Instruction, Processor}; + +#[test] +fn test_can_jump_to_immediate() { + let mut processor = Processor::default(); + + let data = [0xC3, 0x37, 0x13, 0x06]; + let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); + + assert_eq!(extra_data, &[0x06]); + + processor.run_instruction(ins); + + assert_eq!(0x1337, processor.registers.program_counter); +} diff --git a/tests/cpu/jsmoo/testdata/disabled/c3.json b/tests/cpu/jsmoo/testdata/c3.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/c3.json rename to tests/cpu/jsmoo/testdata/c3.json diff --git a/tests/cpu/jsmoo/tests.rs b/tests/cpu/jsmoo/tests.rs index cf9712c..f5f8584 100644 --- a/tests/cpu/jsmoo/tests.rs +++ b/tests/cpu/jsmoo/tests.rs @@ -73,6 +73,7 @@ fn test_jsmoo_test(filename: &str) { processor.registers.l, test_case.r#final.l, "register l value was incorrect" ); + assert_eq!( processor.registers.program_counter, test_case.r#final.pc, "program counter was incorrect" diff --git a/tests/cpu/main.rs b/tests/cpu/main.rs index 0e23c35..dced632 100644 --- a/tests/cpu/main.rs +++ b/tests/cpu/main.rs @@ -1,5 +1,6 @@ mod arith16; mod arith8; +mod control; mod jsmoo; mod load16; mod load8;