feat: add inc/dec u8 opcodes and ldh opcodes
This commit is contained in:
parent
c0733e8fec
commit
06c7197468
3 changed files with 274 additions and 0 deletions
|
@ -120,28 +120,44 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
||||||
|
|
||||||
let instruction_result: CycleResult = match opcode {
|
let instruction_result: CycleResult = match opcode {
|
||||||
0x01 => load_store_move::ld_bc_imm_u16,
|
0x01 => load_store_move::ld_bc_imm_u16,
|
||||||
|
0x04 => alu::inc_b,
|
||||||
|
0x05 => alu::dec_b,
|
||||||
0x06 => load_store_move::ld_b_imm_u8,
|
0x06 => load_store_move::ld_b_imm_u8,
|
||||||
0x08 => load_store_move::ld_deref_imm_u16_sp,
|
0x08 => load_store_move::ld_deref_imm_u16_sp,
|
||||||
0x0a => load_store_move::ld_a_deref_bc,
|
0x0a => load_store_move::ld_a_deref_bc,
|
||||||
|
0x0c => alu::inc_c,
|
||||||
|
0x0d => alu::dec_c,
|
||||||
0x0e => load_store_move::ld_c_imm_u8,
|
0x0e => load_store_move::ld_c_imm_u8,
|
||||||
0x11 => load_store_move::ld_de_imm_u16,
|
0x11 => load_store_move::ld_de_imm_u16,
|
||||||
|
0x14 => alu::inc_d,
|
||||||
|
0x15 => alu::dec_d,
|
||||||
0x16 => load_store_move::ld_d_imm_u8,
|
0x16 => load_store_move::ld_d_imm_u8,
|
||||||
0x18 => flow::jr_i8,
|
0x18 => flow::jr_i8,
|
||||||
0x1a => load_store_move::ld_a_deref_de,
|
0x1a => load_store_move::ld_a_deref_de,
|
||||||
|
0x1c => alu::inc_e,
|
||||||
|
0x1d => alu::dec_e,
|
||||||
0x1e => load_store_move::ld_e_imm_u8,
|
0x1e => load_store_move::ld_e_imm_u8,
|
||||||
0x20 => flow::jr_nz_i8,
|
0x20 => flow::jr_nz_i8,
|
||||||
0x21 => load_store_move::ld_hl_imm_u16,
|
0x21 => load_store_move::ld_hl_imm_u16,
|
||||||
0x22 => load_store_move::ld_hl_plus_a,
|
0x22 => load_store_move::ld_hl_plus_a,
|
||||||
|
0x24 => alu::inc_h,
|
||||||
|
0x25 => alu::dec_h,
|
||||||
0x26 => load_store_move::ld_h_imm_u8,
|
0x26 => load_store_move::ld_h_imm_u8,
|
||||||
0x28 => flow::jr_z_i8,
|
0x28 => flow::jr_z_i8,
|
||||||
0x2a => load_store_move::ld_a_hl_plus,
|
0x2a => load_store_move::ld_a_hl_plus,
|
||||||
|
0x2c => alu::inc_l,
|
||||||
|
0x2d => alu::dec_l,
|
||||||
0x2e => load_store_move::ld_l_imm_u8,
|
0x2e => load_store_move::ld_l_imm_u8,
|
||||||
0x30 => flow::jr_nc_i8,
|
0x30 => flow::jr_nc_i8,
|
||||||
0x31 => load_store_move::ld_sp_imm_u16,
|
0x31 => load_store_move::ld_sp_imm_u16,
|
||||||
0x32 => load_store_move::ld_hl_minus_a,
|
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,
|
0x36 => load_store_move::ld_deref_hl_imm_u8,
|
||||||
0x38 => flow::jr_c_i8,
|
0x38 => flow::jr_c_i8,
|
||||||
0x3a => load_store_move::ld_a_hl_minus,
|
0x3a => load_store_move::ld_a_hl_minus,
|
||||||
|
0x3c => alu::inc_a,
|
||||||
|
0x3d => alu::dec_a,
|
||||||
0x3e => load_store_move::ld_a_imm_u8,
|
0x3e => load_store_move::ld_a_imm_u8,
|
||||||
0x40 => load_store_move::ld_b_b,
|
0x40 => load_store_move::ld_b_b,
|
||||||
0x41 => load_store_move::ld_b_c,
|
0x41 => load_store_move::ld_b_c,
|
||||||
|
@ -233,9 +249,15 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
||||||
0xDA => flow::jp_c_u16,
|
0xDA => flow::jp_c_u16,
|
||||||
0xDC => flow::call_c_u16,
|
0xDC => flow::call_c_u16,
|
||||||
0xDE => alu::sbc_a_imm_u8,
|
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,
|
0xE9 => flow::jp_hl,
|
||||||
|
0xEA => load_store_move::ld_deref_imm_u16_a,
|
||||||
0xEE => alu::xor_a_imm_u8,
|
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,
|
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),
|
unknown => panic!("Unrecognized opcode: {:#X}\nRegisters: {:#?}", unknown, state.registers),
|
||||||
}(state);
|
}(state);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,36 @@ pub fn sub_with_carry(lhs: u8, rhs: u8, carry: bool) -> CarryResult {
|
||||||
CarryResult { result, carry, half_carry }
|
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 {
|
macro_rules! define_xor_reg {
|
||||||
($reg:ident) => {
|
($reg:ident) => {
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
|
@ -171,3 +201,107 @@ pub fn sbc_a_imm_u8(state: &mut Gameboy) -> CycleResult {
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! define_inc_reg {
|
||||||
|
($reg:ident) => {
|
||||||
|
paste::paste! {
|
||||||
|
pub fn [<inc_ $reg>](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 [<dec_ $reg>](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!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -262,3 +262,121 @@ pub fn ld_deref_hl_imm_u8(state: &mut Gameboy) -> CycleResult {
|
||||||
_ => unreachable!(),
|
_ => 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!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue