From 06c7197468ec4a14f794d13d02c2f55574798fb9 Mon Sep 17 00:00:00 2001 From: EliseZeroTwo Date: Sun, 28 Nov 2021 17:37:20 +0100 Subject: [PATCH] feat: add inc/dec u8 opcodes and ldh opcodes --- src/gameboy/cpu.rs | 22 +++++ src/gameboy/cpu/alu.rs | 134 +++++++++++++++++++++++++++++ src/gameboy/cpu/load_store_move.rs | 118 +++++++++++++++++++++++++ 3 files changed, 274 insertions(+) diff --git a/src/gameboy/cpu.rs b/src/gameboy/cpu.rs index 4f7a4ca..212cf82 100644 --- a/src/gameboy/cpu.rs +++ b/src/gameboy/cpu.rs @@ -120,28 +120,44 @@ pub fn tick_cpu(state: &mut Gameboy) { let instruction_result: CycleResult = match opcode { 0x01 => load_store_move::ld_bc_imm_u16, + 0x04 => alu::inc_b, + 0x05 => alu::dec_b, 0x06 => load_store_move::ld_b_imm_u8, 0x08 => load_store_move::ld_deref_imm_u16_sp, 0x0a => load_store_move::ld_a_deref_bc, + 0x0c => alu::inc_c, + 0x0d => alu::dec_c, 0x0e => load_store_move::ld_c_imm_u8, 0x11 => load_store_move::ld_de_imm_u16, + 0x14 => alu::inc_d, + 0x15 => alu::dec_d, 0x16 => load_store_move::ld_d_imm_u8, 0x18 => flow::jr_i8, 0x1a => load_store_move::ld_a_deref_de, + 0x1c => alu::inc_e, + 0x1d => alu::dec_e, 0x1e => load_store_move::ld_e_imm_u8, 0x20 => flow::jr_nz_i8, 0x21 => load_store_move::ld_hl_imm_u16, 0x22 => load_store_move::ld_hl_plus_a, + 0x24 => alu::inc_h, + 0x25 => alu::dec_h, 0x26 => load_store_move::ld_h_imm_u8, 0x28 => flow::jr_z_i8, 0x2a => load_store_move::ld_a_hl_plus, + 0x2c => alu::inc_l, + 0x2d => alu::dec_l, 0x2e => load_store_move::ld_l_imm_u8, 0x30 => flow::jr_nc_i8, 0x31 => load_store_move::ld_sp_imm_u16, 0x32 => load_store_move::ld_hl_minus_a, + 0x34 => alu::inc_deref_hl, + 0x35 => alu::dec_deref_hl, 0x36 => load_store_move::ld_deref_hl_imm_u8, 0x38 => flow::jr_c_i8, 0x3a => load_store_move::ld_a_hl_minus, + 0x3c => alu::inc_a, + 0x3d => alu::dec_a, 0x3e => load_store_move::ld_a_imm_u8, 0x40 => load_store_move::ld_b_b, 0x41 => load_store_move::ld_b_c, @@ -233,9 +249,15 @@ pub fn tick_cpu(state: &mut Gameboy) { 0xDA => flow::jp_c_u16, 0xDC => flow::call_c_u16, 0xDE => alu::sbc_a_imm_u8, + 0xE0 => load_store_move::ldh_imm_u8_a, + 0xE2 => load_store_move::ldh_deref_c_a, 0xE9 => flow::jp_hl, + 0xEA => load_store_move::ld_deref_imm_u16_a, 0xEE => alu::xor_a_imm_u8, + 0xF0 => load_store_move::ldh_a_imm_u8, + 0xF2 => load_store_move::ldh_a_deref_c, 0xF9 => load_store_move::ld_sp_hl, + 0xFA => load_store_move::ld_a_deref_imm_u16, unknown => panic!("Unrecognized opcode: {:#X}\nRegisters: {:#?}", unknown, state.registers), }(state); diff --git a/src/gameboy/cpu/alu.rs b/src/gameboy/cpu/alu.rs index b736401..51fcfec 100644 --- a/src/gameboy/cpu/alu.rs +++ b/src/gameboy/cpu/alu.rs @@ -24,6 +24,36 @@ pub fn sub_with_carry(lhs: u8, rhs: u8, carry: bool) -> CarryResult { CarryResult { result, carry, half_carry } } +pub fn add_with_carry(lhs: u8, rhs: u8, carry: bool) -> CarryResult { + let carry_u8 = carry as u8; + + let (first_res, first_carry) = lhs.overflowing_add(rhs); + let (result, second_carry) = first_res.overflowing_add(carry_u8); + + let carry = first_carry || second_carry; + + let (first_hc_res, first_half_carry) = (lhs & 0xF).overflowing_add(rhs & 0xF); + let (_, second_half_carry) = first_hc_res.overflowing_add(carry_u8); + + let half_carry = first_half_carry || second_half_carry; + + CarryResult { result, carry, half_carry } +} + +pub fn add(lhs: u8, rhs: u8) -> CarryResult { + let (result, carry) = lhs.overflowing_add(rhs); + let (_, half_carry) = (lhs & 0xF).overflowing_add(rhs & 0xF); + + CarryResult { result, carry, half_carry } +} + +pub fn sub(lhs: u8, rhs: u8) -> CarryResult { + let (result, carry) = lhs.overflowing_add(rhs); + let (_, half_carry) = (lhs & 0xF).overflowing_add(rhs & 0xF); + + CarryResult { result, carry, half_carry } +} + macro_rules! define_xor_reg { ($reg:ident) => { paste::paste! { @@ -171,3 +201,107 @@ pub fn sbc_a_imm_u8(state: &mut Gameboy) -> CycleResult { _ => unreachable!(), } } + +macro_rules! define_inc_reg { + ($reg:ident) => { + paste::paste! { + pub fn [](state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + let CarryResult { result, half_carry, .. } = add( + state.registers.$reg, + 1, + ); + + state.registers.set_zero(result == 0); + state.registers.set_subtract(false); + state.registers.set_half_carry(half_carry); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + }, + _ => unimplemented!(), + } + } + } + }; +} + +define_inc_reg!(b); +define_inc_reg!(c); +define_inc_reg!(d); +define_inc_reg!(e); +define_inc_reg!(h); +define_inc_reg!(l); +define_inc_reg!(a); + +pub fn inc_deref_hl(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.cpu_read_u8(state.registers.get_hl()); + CycleResult::NeedsMore + } + 1 => { + let CarryResult { result, half_carry, .. } = add(state.registers.take_mem(), 1); + + state.registers.set_zero(result == 0); + state.registers.set_subtract(false); + state.registers.set_half_carry(half_carry); + state.registers.opcode_bytecount = Some(1); + CycleResult::NeedsMore + } + 2 => CycleResult::Finished, + _ => unimplemented!(), + } +} + +macro_rules! define_dec_reg { + ($reg:ident) => { + paste::paste! { + pub fn [](state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + let CarryResult { result, half_carry, .. } = sub( + state.registers.$reg, + 1, + ); + + state.registers.set_zero(result == 0); + state.registers.set_subtract(false); + state.registers.set_half_carry(half_carry); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + }, + _ => unimplemented!(), + } + } + } + }; +} + +define_dec_reg!(b); +define_dec_reg!(c); +define_dec_reg!(d); +define_dec_reg!(e); +define_dec_reg!(h); +define_dec_reg!(l); +define_dec_reg!(a); + +pub fn dec_deref_hl(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.cpu_read_u8(state.registers.get_hl()); + CycleResult::NeedsMore + } + 1 => { + let CarryResult { result, half_carry, .. } = sub(state.registers.take_mem(), 1); + + state.registers.set_zero(result == 0); + state.registers.set_subtract(false); + state.registers.set_half_carry(half_carry); + state.registers.opcode_bytecount = Some(1); + CycleResult::NeedsMore + } + 2 => CycleResult::Finished, + _ => unimplemented!(), + } +} diff --git a/src/gameboy/cpu/load_store_move.rs b/src/gameboy/cpu/load_store_move.rs index c1e4d17..e2d3120 100644 --- a/src/gameboy/cpu/load_store_move.rs +++ b/src/gameboy/cpu/load_store_move.rs @@ -262,3 +262,121 @@ pub fn ld_deref_hl_imm_u8(state: &mut Gameboy) -> CycleResult { _ => unreachable!(), } } + +pub fn ldh_a_imm_u8(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.cpu_read_u8(state.registers.pc.overflowing_add(1).0); + CycleResult::NeedsMore + } + 1 => { + let imm = state.registers.take_mem(); + let addr = 0xFF00u16 | imm as u16; + state.cpu_read_u8(addr); + CycleResult::NeedsMore + } + 2 => { + state.registers.a = state.registers.take_mem(); + state.registers.opcode_bytecount = Some(2); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +pub fn ldh_imm_u8_a(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.cpu_read_u8(state.registers.pc.overflowing_add(1).0); + CycleResult::NeedsMore + } + 1 => { + let imm = state.registers.take_mem(); + let addr = 0xFF00u16 | imm as u16; + state.cpu_write_u8(addr, state.registers.a); + state.registers.opcode_bytecount = Some(2); + CycleResult::NeedsMore + } + 2 => CycleResult::Finished, + _ => unreachable!(), + } +} + +pub fn ldh_a_deref_c(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + let imm = state.registers.c; + let addr = 0xFF00u16 | imm as u16; + state.cpu_read_u8(addr); + CycleResult::NeedsMore + } + 1 => { + state.registers.a = state.registers.take_mem(); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +pub fn ldh_deref_c_a(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + let addr = 0xFF00u16 | state.registers.c as u16; + state.cpu_write_u8(addr, state.registers.a); + state.registers.opcode_bytecount = Some(1); + CycleResult::NeedsMore + } + 1 => CycleResult::Finished, + _ => unreachable!(), + } +} + +pub fn ld_a_deref_imm_u16(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.cpu_read_u8(state.registers.pc.overflowing_add(1).0); + CycleResult::NeedsMore + } + 1 => { + let lsb = state.registers.take_mem() as u16; + state.cpu_read_u8(state.registers.pc.overflowing_add(2).0); + state.registers.set_hold(lsb); + CycleResult::NeedsMore + } + 2 => { + let addr = (state.registers.take_mem() as u16) << 8 | state.registers.take_hold(); + state.cpu_read_u8(addr); + CycleResult::NeedsMore + } + 3 => { + state.registers.a = state.registers.take_mem(); + state.registers.opcode_bytecount = Some(3); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +pub fn ld_deref_imm_u16_a(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.cpu_read_u8(state.registers.pc.overflowing_add(1).0); + CycleResult::NeedsMore + } + 1 => { + let lsb = state.registers.take_mem() as u16; + state.cpu_read_u8(state.registers.pc.overflowing_add(2).0); + state.registers.set_hold(lsb); + CycleResult::NeedsMore + } + 2 => { + let addr = (state.registers.take_mem() as u16) << 8 | state.registers.take_hold(); + state.cpu_write_u8(addr, state.registers.a); + state.registers.opcode_bytecount = Some(3); + CycleResult::NeedsMore + } + 3 => CycleResult::Finished, + _ => unreachable!(), + } +}