From 8c5c1c4b23c42c59a760ea21a451ffcbe12e6044 Mon Sep 17 00:00:00 2001 From: EliseZeroTwo Date: Tue, 7 Dec 2021 18:57:34 +0100 Subject: [PATCH] feat: add more opcodes and stub serial output --- src/gameboy.rs | 23 +-- src/gameboy/cpu.rs | 41 +++++ src/gameboy/cpu/alu.rs | 300 +++++++++++++++++++++++++++++++++++- src/gameboy/cpu/flow.rs | 43 ++++++ src/gameboy/cpu/misc.rs | 34 ++++ src/gameboy/cpu/prefixed.rs | 37 +++++ src/gameboy/serial.rs | 28 ++++ 7 files changed, 493 insertions(+), 13 deletions(-) create mode 100644 src/gameboy/cpu/misc.rs create mode 100644 src/gameboy/serial.rs diff --git a/src/gameboy.rs b/src/gameboy.rs index fb72b95..853ad96 100644 --- a/src/gameboy.rs +++ b/src/gameboy.rs @@ -4,6 +4,7 @@ mod joypad; mod mapper; mod memory; mod ppu; +mod serial; mod sound; mod timer; @@ -14,7 +15,7 @@ use memory::Memory; use ppu::Ppu; use timer::Timer; -use self::{cpu::Registers, mapper::NoMBC, sound::Sound}; +use self::{cpu::Registers, mapper::NoMBC, serial::Serial, sound::Sound}; pub struct DmaState { pub base: u8, @@ -42,13 +43,14 @@ pub struct Gameboy { timer: Timer, pub registers: Registers, pub joypad: Joypad, + pub serial: Serial, pub dma: DmaState, pub sound: Sound, pub single_step: bool, - pub breakpoints: [bool; u16::MAX as usize], - pub mem_read_breakpoints: [bool; u16::MAX as usize], - pub mem_write_breakpoints: [bool; u16::MAX as usize], + pub breakpoints: [bool; u16::MAX as usize + 1], + pub mem_read_breakpoints: [bool; u16::MAX as usize + 1], + pub mem_write_breakpoints: [bool; u16::MAX as usize + 1], trigger_bp: bool, } @@ -60,14 +62,15 @@ impl Gameboy { interrupts: Interrupts::new(), timer: Timer::new(), joypad: Joypad::new(), + serial: Serial::new(), dma: DmaState::new(), ppu: Ppu::new(), registers: Registers::default(), sound: Sound::default(), single_step: false, - breakpoints: [false; u16::MAX as usize], - mem_read_breakpoints: [false; u16::MAX as usize], - mem_write_breakpoints: [false; u16::MAX as usize], + breakpoints: [false; u16::MAX as usize + 1], + mem_read_breakpoints: [false; u16::MAX as usize + 1], + mem_write_breakpoints: [false; u16::MAX as usize + 1], trigger_bp: false, } } @@ -258,7 +261,8 @@ impl Gameboy { fn cpu_read_io(&self, address: u16) -> u8 { match address { 0xFF00 => self.joypad.cpu_read(), - 0xFF01..=0xFF02 => unimplemented!("Serial"), + 0xFF01 => self.serial.sb, + 0xFF02 => self.serial.sc, 0xFF03 => 0, // Unused 0xFF04 => self.timer.div, 0xFF05 => self.timer.tima, @@ -317,7 +321,8 @@ impl Gameboy { fn cpu_write_io(&mut self, address: u16, value: u8) { match address { 0xFF00 => self.joypad.cpu_write(value), - 0xFF01..=0xFF02 => unimplemented!("Serial"), + 0xFF01 => self.serial.sb = value, + 0xFF02 => self.serial.sc = value, 0xFF03 => {} // Unused 0xFF04 => self.timer.div = value, 0xFF05 => self.timer.tima = value, diff --git a/src/gameboy/cpu.rs b/src/gameboy/cpu.rs index 6458f36..04b0531 100644 --- a/src/gameboy/cpu.rs +++ b/src/gameboy/cpu.rs @@ -1,6 +1,7 @@ mod alu; mod flow; mod load_store_move; +mod misc; mod prefixed; use super::Gameboy; @@ -177,13 +178,16 @@ pub fn tick_cpu(state: &mut Gameboy) { }; let result: CycleResult = match opcode { + 0x00 => misc::nop, 0x01 => load_store_move::ld_bc_imm_u16, 0x03 => alu::inc_bc, 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, + 0x09 => alu::add_hl_bc, 0x0a => load_store_move::ld_a_deref_bc, + 0x0b => alu::dec_bc, 0x0c => alu::inc_c, 0x0d => alu::dec_c, 0x0e => load_store_move::ld_c_imm_u8, @@ -194,7 +198,9 @@ pub fn tick_cpu(state: &mut Gameboy) { 0x16 => load_store_move::ld_d_imm_u8, 0x17 => alu::rla, 0x18 => flow::jr_i8, + 0x19 => alu::add_hl_de, 0x1a => load_store_move::ld_a_deref_de, + 0x1b => alu::dec_de, 0x1c => alu::inc_e, 0x1d => alu::dec_e, 0x1e => load_store_move::ld_e_imm_u8, @@ -206,10 +212,13 @@ pub fn tick_cpu(state: &mut Gameboy) { 0x25 => alu::dec_h, 0x26 => load_store_move::ld_h_imm_u8, 0x28 => flow::jr_z_i8, + 0x29 => alu::add_hl_hl, 0x2a => load_store_move::ld_a_hl_plus, + 0x2b => alu::dec_hl, 0x2c => alu::inc_l, 0x2d => alu::dec_l, 0x2e => load_store_move::ld_l_imm_u8, + 0x2f => alu::cpl, 0x30 => flow::jr_nc_i8, 0x31 => load_store_move::ld_sp_imm_u16, 0x32 => load_store_move::ld_hl_minus_a, @@ -217,11 +226,15 @@ pub fn tick_cpu(state: &mut Gameboy) { 0x34 => alu::inc_deref_hl, 0x35 => alu::dec_deref_hl, 0x36 => load_store_move::ld_deref_hl_imm_u8, + 0x37 => alu::scf, 0x38 => flow::jr_c_i8, + 0x39 => alu::add_hl_sp, 0x3a => load_store_move::ld_a_hl_minus, + 0x3b => alu::dec_sp, 0x3c => alu::inc_a, 0x3d => alu::dec_a, 0x3e => load_store_move::ld_a_imm_u8, + 0x3f => alu::ccf, 0x40 => load_store_move::ld_b_b, 0x41 => load_store_move::ld_b_c, 0x42 => load_store_move::ld_b_d, @@ -317,6 +330,14 @@ pub fn tick_cpu(state: &mut Gameboy) { 0x9D => alu::sbc_a_l, 0x9E => alu::sbc_a_deref_hl, 0x9F => alu::sbc_a_a, + 0xA0 => alu::and_a_b, + 0xA1 => alu::and_a_c, + 0xA2 => alu::and_a_d, + 0xA3 => alu::and_a_e, + 0xA4 => alu::and_a_h, + 0xA5 => alu::and_a_l, + 0xA6 => alu::and_a_deref_hl, + 0xA7 => alu::and_a_a, 0xA8 => alu::xor_a_b, 0xA9 => alu::xor_a_c, 0xAA => alu::xor_a_d, @@ -325,6 +346,14 @@ pub fn tick_cpu(state: &mut Gameboy) { 0xAD => alu::xor_a_l, 0xAE => alu::xor_a_deref_hl, 0xAF => alu::xor_a_a, + 0xB0 => alu::or_a_b, + 0xB1 => alu::or_a_c, + 0xB2 => alu::or_a_d, + 0xB3 => alu::or_a_e, + 0xB4 => alu::or_a_h, + 0xB5 => alu::or_a_l, + 0xB6 => alu::or_a_deref_hl, + 0xB7 => alu::or_a_a, 0xB8 => alu::cp_a_b, 0xB9 => alu::cp_a_c, 0xBA => alu::cp_a_d, @@ -340,6 +369,7 @@ pub fn tick_cpu(state: &mut Gameboy) { 0xC4 => flow::call_nz_u16, 0xC5 => load_store_move::push_bc, 0xC6 => alu::add_a_imm_u8, + 0xC7 => flow::rst_0x0, 0xC8 => flow::ret_z, 0xC9 => flow::ret, 0xCA => flow::jp_z_u16, @@ -347,31 +377,42 @@ pub fn tick_cpu(state: &mut Gameboy) { 0xCC => flow::call_z_u16, 0xCD => flow::call_u16, 0xCE => alu::adc_a_imm_u8, + 0xCF => flow::rst_0x08, 0xD0 => flow::ret_nc, 0xD1 => load_store_move::pop_de, 0xD2 => flow::jp_nc_u16, 0xD4 => flow::call_nc_u16, 0xD5 => load_store_move::push_de, 0xD6 => alu::sub_a_imm_u8, + 0xD7 => flow::rst_0x10, 0xD8 => flow::ret_c, 0xD9 => flow::reti, 0xDA => flow::jp_c_u16, 0xDC => flow::call_c_u16, 0xDE => alu::sbc_a_imm_u8, + 0xDF => flow::rst_0x18, 0xE0 => load_store_move::ldh_imm_u8_a, 0xE1 => load_store_move::pop_hl, 0xE2 => load_store_move::ldh_deref_c_a, 0xE5 => load_store_move::push_hl, + 0xE6 => alu::and_a_imm_u8, + 0xE7 => flow::rst_0x20, 0xE9 => flow::jp_hl, 0xEA => load_store_move::ld_deref_imm_u16_a, 0xEE => alu::xor_a_imm_u8, + 0xEF => flow::rst_0x28, 0xF0 => load_store_move::ldh_a_imm_u8, 0xF1 => load_store_move::pop_af, 0xF2 => load_store_move::ldh_a_deref_c, + 0xF3 => misc::di, 0xF5 => load_store_move::push_af, + 0xF6 => alu::or_a_imm_u8, + 0xF7 => flow::rst_0x30, 0xF9 => load_store_move::ld_sp_hl, 0xFA => load_store_move::ld_a_deref_imm_u16, + 0xFB => misc::ei, 0xFE => alu::cp_a_imm_u8, + 0xFF => flow::rst_0x38, unknown => { panic!("Unrecognized opcode: {:#X}\nRegisters: {:#x?}", unknown, state.registers) } diff --git a/src/gameboy/cpu/alu.rs b/src/gameboy/cpu/alu.rs index 7d2bbdc..3da7d63 100644 --- a/src/gameboy/cpu/alu.rs +++ b/src/gameboy/cpu/alu.rs @@ -197,7 +197,7 @@ pub fn sbc_a_imm_u8(state: &mut Gameboy) -> CycleResult { state.registers.set_subtract(true); state.registers.set_half_carry(half_carry); state.registers.set_carry(carry); - state.registers.opcode_bytecount = Some(1); + state.registers.opcode_bytecount = Some(2); CycleResult::Finished } _ => unreachable!(), @@ -272,7 +272,7 @@ pub fn add_a_imm_u8(state: &mut Gameboy) -> CycleResult { state.registers.set_subtract(false); state.registers.set_half_carry(half_carry); state.registers.set_carry(carry); - state.registers.opcode_bytecount = Some(1); + state.registers.opcode_bytecount = Some(2); CycleResult::Finished } _ => unreachable!(), @@ -353,7 +353,7 @@ pub fn adc_a_imm_u8(state: &mut Gameboy) -> CycleResult { state.registers.set_subtract(false); state.registers.set_half_carry(half_carry); state.registers.set_carry(carry); - state.registers.opcode_bytecount = Some(1); + state.registers.opcode_bytecount = Some(2); CycleResult::Finished } _ => unreachable!(), @@ -428,7 +428,7 @@ pub fn sub_a_imm_u8(state: &mut Gameboy) -> CycleResult { state.registers.set_subtract(true); state.registers.set_half_carry(half_carry); state.registers.set_carry(carry); - state.registers.opcode_bytecount = Some(1); + state.registers.opcode_bytecount = Some(2); CycleResult::Finished } _ => unreachable!(), @@ -608,6 +608,49 @@ pub fn inc_sp(state: &mut Gameboy) -> CycleResult { } } +macro_rules! define_dec_u16_reg { + ($lreg:ident, $rreg:ident) => { + paste::paste! { + pub fn [](state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + let (res, carry) = state.registers.$rreg.overflowing_sub(1); + state.registers.$rreg = res; + state.registers.set_hold(carry as u16); + CycleResult::NeedsMore + }, + 1 => { + if state.registers.take_hold() != 0 { + let (res, _) = state.registers.$lreg.overflowing_sub(1); + state.registers.$lreg = res; + } + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } + } + } + }; +} + +define_dec_u16_reg!(b, c); +define_dec_u16_reg!(d, e); +define_dec_u16_reg!(h, l); + +pub fn dec_sp(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => CycleResult::NeedsMore, + 1 => { + let (res, _) = state.registers.sp.overflowing_sub(1); + state.registers.sp = res; + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } +} + macro_rules! define_cp_reg_reg { ($lreg:ident, $rreg:ident) => { paste::paste! { @@ -676,3 +719,252 @@ pub fn cp_a_deref_hl(state: &mut Gameboy) -> CycleResult { _ => unreachable!(), } } + +macro_rules! define_or_reg { + ($reg:ident) => { + paste::paste! { + pub fn [](state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + let result = state.registers.a | state.registers.$reg; + + state.registers.a = result; + state.registers.set_zero(result == 0); + state.registers.set_subtract(false); + state.registers.set_half_carry(false); + state.registers.set_carry(false); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + }, + _ => unreachable!(), + } + } + } + }; +} + +define_or_reg!(a); +define_or_reg!(b); +define_or_reg!(c); +define_or_reg!(d); +define_or_reg!(e); +define_or_reg!(h); +define_or_reg!(l); + +pub fn or_a_deref_hl(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.cpu_read_u8(state.registers.get_hl()); + CycleResult::NeedsMore + } + 1 => { + let result = state.registers.a | state.registers.take_mem(); + + state.registers.a = result; + state.registers.set_zero(result == 0); + state.registers.set_subtract(false); + state.registers.set_half_carry(false); + state.registers.set_carry(false); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +pub fn or_a_imm_u8(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.cpu_read_u8(state.registers.pc + 1); + CycleResult::NeedsMore + } + 1 => { + let result = state.registers.a | state.registers.take_mem(); + + state.registers.a = result; + state.registers.set_zero(result == 0); + state.registers.set_subtract(false); + state.registers.set_half_carry(false); + state.registers.set_carry(false); + state.registers.opcode_bytecount = Some(2); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +macro_rules! define_and_reg { + ($reg:ident) => { + paste::paste! { + pub fn [](state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + let result = state.registers.a & state.registers.$reg; + + state.registers.a = result; + state.registers.set_zero(result == 0); + state.registers.set_subtract(false); + state.registers.set_half_carry(true); + state.registers.set_carry(false); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + }, + _ => unreachable!(), + } + } + } + }; +} + +define_and_reg!(a); +define_and_reg!(b); +define_and_reg!(c); +define_and_reg!(d); +define_and_reg!(e); +define_and_reg!(h); +define_and_reg!(l); + +pub fn and_a_deref_hl(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.cpu_read_u8(state.registers.get_hl()); + CycleResult::NeedsMore + } + 1 => { + let result = state.registers.a & state.registers.take_mem(); + + state.registers.a = result; + state.registers.set_zero(result == 0); + state.registers.set_subtract(false); + state.registers.set_half_carry(true); + state.registers.set_carry(false); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +pub fn and_a_imm_u8(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.cpu_read_u8(state.registers.pc + 1); + CycleResult::NeedsMore + } + 1 => { + let result = state.registers.a & state.registers.take_mem(); + + state.registers.a = result; + state.registers.set_zero(result == 0); + state.registers.set_subtract(false); + state.registers.set_half_carry(true); + state.registers.set_carry(false); + state.registers.opcode_bytecount = Some(2); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +pub fn cpl(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.registers.a = !state.registers.a; + state.registers.set_subtract(true); + state.registers.set_half_carry(true); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +pub fn ccf(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.registers.set_subtract(false); + state.registers.set_half_carry(false); + state.registers.set_carry(!state.registers.get_carry()); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +pub fn scf(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.registers.set_subtract(false); + state.registers.set_half_carry(false); + state.registers.set_carry(true); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +macro_rules! define_add_hl_u16_reg { + ($lreg:ident, $rreg:ident) => { + paste::paste! { + pub fn [](state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + let CarryResult { result, carry, .. } = add(state.registers.l, state.registers.$rreg); + state.registers.l = result; + state.registers.set_hold(carry as u16); + CycleResult::NeedsMore + }, + 1 => { + if state.registers.take_hold() != 0 { + let CarryResult { result, carry, half_carry } = add(state.registers.h, state.registers.$lreg); + state.registers.h = result; + state.registers.set_half_carry(half_carry); + state.registers.set_carry(carry); + } else { + state.registers.set_half_carry(false); + state.registers.set_carry(false); + } + state.registers.set_subtract(false); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } + } + } + }; +} + +define_add_hl_u16_reg!(b, c); +define_add_hl_u16_reg!(d, e); +define_add_hl_u16_reg!(h, l); + +pub fn add_hl_sp(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + let CarryResult { result, carry, .. } = + add(state.registers.l, state.registers.sp as u8); + state.registers.l = result; + state.registers.set_hold(carry as u16); + CycleResult::NeedsMore + } + 1 => { + if state.registers.take_hold() != 0 { + let CarryResult { result, carry, half_carry } = + add(state.registers.h, (state.registers.sp >> 8) as u8); + state.registers.h = result; + state.registers.set_half_carry(half_carry); + state.registers.set_carry(carry); + } else { + state.registers.set_half_carry(false); + state.registers.set_carry(false); + } + state.registers.set_subtract(false); + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } +} diff --git a/src/gameboy/cpu/flow.rs b/src/gameboy/cpu/flow.rs index 214c465..8962335 100644 --- a/src/gameboy/cpu/flow.rs +++ b/src/gameboy/cpu/flow.rs @@ -197,6 +197,7 @@ pub fn jp_nz_u16(state: &mut Gameboy) -> CycleResult { 2 => { if state.registers.get_zero() { state.registers.take_mem(); + state.registers.take_hold(); state.registers.opcode_bytecount = Some(3); CycleResult::Finished } else { @@ -228,6 +229,7 @@ pub fn jp_nc_u16(state: &mut Gameboy) -> CycleResult { 2 => { if state.registers.get_carry() { state.registers.take_mem(); + state.registers.take_hold(); state.registers.opcode_bytecount = Some(3); CycleResult::Finished } else { @@ -259,6 +261,7 @@ pub fn jp_z_u16(state: &mut Gameboy) -> CycleResult { 2 => { if !state.registers.get_zero() { state.registers.take_mem(); + state.registers.take_hold(); state.registers.opcode_bytecount = Some(3); CycleResult::Finished } else { @@ -290,6 +293,7 @@ pub fn jp_c_u16(state: &mut Gameboy) -> CycleResult { 2 => { if !state.registers.get_carry() { state.registers.take_mem(); + state.registers.take_hold(); state.registers.opcode_bytecount = Some(3); CycleResult::Finished } else { @@ -352,6 +356,7 @@ pub fn call_nz_u16(state: &mut Gameboy) -> CycleResult { 2 => { if state.registers.get_zero() { state.registers.take_mem(); + state.registers.take_hold(); state.registers.opcode_bytecount = Some(3); CycleResult::Finished } else { @@ -391,6 +396,7 @@ pub fn call_nc_u16(state: &mut Gameboy) -> CycleResult { 2 => { if state.registers.get_carry() { state.registers.take_mem(); + state.registers.take_hold(); state.registers.opcode_bytecount = Some(3); CycleResult::Finished } else { @@ -430,6 +436,7 @@ pub fn call_z_u16(state: &mut Gameboy) -> CycleResult { 2 => { if !state.registers.get_zero() { state.registers.take_mem(); + state.registers.take_hold(); state.registers.opcode_bytecount = Some(3); CycleResult::Finished } else { @@ -469,6 +476,7 @@ pub fn call_c_u16(state: &mut Gameboy) -> CycleResult { 2 => { if !state.registers.get_carry() { state.registers.take_mem(); + state.registers.take_hold(); state.registers.opcode_bytecount = Some(3); CycleResult::Finished } else { @@ -655,3 +663,38 @@ pub fn ret_c(state: &mut Gameboy) -> CycleResult { _ => unreachable!(), } } + +macro_rules! define_rst { + ($addr:literal) => { + paste::paste! { + pub fn [](state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => CycleResult::NeedsMore, + 1 => { + state.cpu_push_stack((state.registers.pc >> 8) as u8); + CycleResult::NeedsMore + }, + 2 => { + state.cpu_push_stack((state.registers.pc & 0xFF) as u8); + CycleResult::NeedsMore + }, + 3 => { + state.registers.pc = $addr; + state.registers.opcode_bytecount = Some(0); + CycleResult::Finished + }, + _ => unreachable!(), + } + } + } + }; +} + +define_rst!(0x0); +define_rst!(0x08); +define_rst!(0x10); +define_rst!(0x18); +define_rst!(0x20); +define_rst!(0x28); +define_rst!(0x30); +define_rst!(0x38); diff --git a/src/gameboy/cpu/misc.rs b/src/gameboy/cpu/misc.rs new file mode 100644 index 0000000..5ddce13 --- /dev/null +++ b/src/gameboy/cpu/misc.rs @@ -0,0 +1,34 @@ +use super::CycleResult; +use crate::gameboy::Gameboy; + +pub fn nop(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +pub fn di(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.interrupts.ime = false; + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } +} + +pub fn ei(state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.interrupts.ime = true; + state.registers.opcode_bytecount = Some(1); + CycleResult::Finished + } + _ => unreachable!(), + } +} diff --git a/src/gameboy/cpu/prefixed.rs b/src/gameboy/cpu/prefixed.rs index 230fed4..b6df388 100644 --- a/src/gameboy/cpu/prefixed.rs +++ b/src/gameboy/cpu/prefixed.rs @@ -24,6 +24,13 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult { 0x14 => rl_h, 0x15 => rl_l, 0x17 => rl_a, + 0x30 => swap_b, + 0x31 => swap_c, + 0x32 => swap_d, + 0x33 => swap_e, + 0x34 => swap_h, + 0x35 => swap_l, + 0x37 => swap_a, 0x40 => bit_0_b, 0x41 => bit_0_c, 0x42 => bit_0_d, @@ -204,3 +211,33 @@ define_rl_reg!(e); define_rl_reg!(h); define_rl_reg!(l); define_rl_reg!(a); + +macro_rules! define_swap_reg { + ($reg:ident) => { + paste::paste! { + pub fn [](state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 1 => { + state.registers.$reg = (state.registers.$reg >> 4) | (state.registers.$reg << 4); + + state.registers.set_zero(state.registers.$reg == 0); + state.registers.set_subtract(false); + state.registers.set_half_carry(false); + state.registers.set_carry(false); + state.registers.opcode_bytecount = Some(2); + CycleResult::Finished + }, + _ => unreachable!(), + } + } + } + }; +} + +define_swap_reg!(b); +define_swap_reg!(c); +define_swap_reg!(d); +define_swap_reg!(e); +define_swap_reg!(h); +define_swap_reg!(l); +define_swap_reg!(a); diff --git a/src/gameboy/serial.rs b/src/gameboy/serial.rs new file mode 100644 index 0000000..7c9dfe5 --- /dev/null +++ b/src/gameboy/serial.rs @@ -0,0 +1,28 @@ +pub struct Serial { + pub sb: u8, + pub sc: u8, +} + +impl Serial { + pub fn new() -> Serial { + Self { sb: 0, sc: 0 } + } + + pub fn set_transfer_in_process(&mut self, value: bool) { + self.sc &= !(1 << 7); + self.sc |= (value as u8) << 7; + } + + pub fn get_transfer_in_process(&mut self) -> bool { + (self.sc >> 7) & 0b1 == 1 + } + + pub fn is_conductor(&self) -> bool { + self.sc & 0b1 == 1 + } + + pub fn set_side(&mut self, conductor: bool) { + self.sc &= !0b1; + self.sc |= conductor as u8; + } +}