feat: fix a few instructions, add halt
This commit is contained in:
parent
96bc5e117d
commit
00850645cd
16 changed files with 766 additions and 60 deletions
|
@ -87,13 +87,15 @@ pub fn opcode(item: TokenStream) -> TokenStream {
|
|||
let log = if extended.value {
|
||||
quote::quote! {
|
||||
if state.registers.cycle == 1 && state.log_instructions {
|
||||
log::debug!("Prefixed OP {} ({:#02X})", #readable, #opcode);
|
||||
log::debug!("(PC: {:#02X}) Prefixed OP {} ({:#02X})", state.registers.pc, #readable, #opcode);
|
||||
#regs
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote::quote! {
|
||||
if state.registers.cycle == 0 && state.log_instructions {
|
||||
log::debug!("OP {} ({:#02X})", #readable, #opcode);
|
||||
log::debug!("(PC: {:#02X}) OP {} ({:#02X})", state.registers.pc, #readable, #opcode);
|
||||
#regs
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -101,7 +103,6 @@ pub fn opcode(item: TokenStream) -> TokenStream {
|
|||
let out = quote::quote! {
|
||||
#fn_sig {
|
||||
#log
|
||||
#regs
|
||||
|
||||
#match_statement
|
||||
}
|
||||
|
|
|
@ -15,7 +15,12 @@ use memory::Memory;
|
|||
use ppu::Ppu;
|
||||
use timer::Timer;
|
||||
|
||||
use self::{cpu::Registers, mapper::NoMBC, serial::Serial, sound::Sound};
|
||||
use self::{
|
||||
cpu::Registers,
|
||||
mapper::{mbc1::MBC1, NoMBC},
|
||||
serial::Serial,
|
||||
sound::Sound,
|
||||
};
|
||||
|
||||
pub struct DmaState {
|
||||
pub base: u8,
|
||||
|
@ -53,6 +58,9 @@ pub struct Gameboy {
|
|||
pub mem_write_breakpoints: [bool; u16::MAX as usize + 1],
|
||||
trigger_bp: bool,
|
||||
pub log_instructions: bool,
|
||||
pub halt: bool,
|
||||
pub halt_bug: bool,
|
||||
pub used_halt_bug: bool,
|
||||
}
|
||||
|
||||
impl Gameboy {
|
||||
|
@ -74,6 +82,9 @@ impl Gameboy {
|
|||
mem_write_breakpoints: [false; u16::MAX as usize + 1],
|
||||
trigger_bp: false,
|
||||
log_instructions: false,
|
||||
halt: false,
|
||||
halt_bug: false,
|
||||
used_halt_bug: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,6 +111,7 @@ impl Gameboy {
|
|||
pub fn load_cartridge(&mut self, bytes: Vec<u8>) {
|
||||
match bytes[0x147] {
|
||||
0 => self.cartridge = Some(Box::new(NoMBC::new(bytes))),
|
||||
1 => self.cartridge = Some(Box::new(MBC1::new(bytes))),
|
||||
other => unimplemented!("Cartidge type: {:#X}", other),
|
||||
}
|
||||
}
|
||||
|
@ -235,6 +247,9 @@ impl Gameboy {
|
|||
cpu::tick_cpu(self);
|
||||
let redraw_requested = self.ppu.tick(&mut self.interrupts);
|
||||
self.tick_dma();
|
||||
if self.serial.tick() {
|
||||
self.interrupts.write_if_serial(true);
|
||||
}
|
||||
redraw_requested
|
||||
}
|
||||
|
||||
|
|
|
@ -104,6 +104,24 @@ impl Registers {
|
|||
pub fn tick_cpu(state: &mut Gameboy) {
|
||||
state.registers.mem_op_happened = false;
|
||||
|
||||
if state.joypad.interrupt_triggered {
|
||||
state.joypad.interrupt_triggered = false;
|
||||
state.interrupts.write_if_joypad(true);
|
||||
}
|
||||
|
||||
if state.registers.cycle == 0 && state.halt {
|
||||
if (state.interrupts.read_ie_vblank() && state.interrupts.read_if_vblank())
|
||||
|| (state.interrupts.read_ie_lcd_stat() && state.interrupts.read_if_lcd_stat())
|
||||
|| (state.interrupts.read_ie_timer() && state.interrupts.read_if_timer())
|
||||
|| (state.interrupts.read_ie_serial() && state.interrupts.read_if_serial())
|
||||
|| (state.interrupts.read_ie_joypad() && state.interrupts.read_if_joypad())
|
||||
{
|
||||
state.halt = false;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Interrupts
|
||||
if state.registers.cycle == 0 && state.interrupts.ime {
|
||||
if state.interrupts.read_ie_vblank() && state.interrupts.read_if_vblank() {
|
||||
|
@ -129,6 +147,17 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
|||
}
|
||||
}
|
||||
|
||||
if state.registers.cycle == 0 && state.interrupts.ei_queued {
|
||||
state.interrupts.ime = state.interrupts.ei_queued;
|
||||
state.interrupts.ei_queued = false;
|
||||
}
|
||||
|
||||
if state.registers.cycle == 0 && state.halt_bug {
|
||||
state.used_halt_bug = true;
|
||||
state.halt_bug = false;
|
||||
state.registers.pc = state.registers.pc.overflowing_sub(1).0;
|
||||
}
|
||||
|
||||
let result = if let Some(idx) = state.registers.in_interrupt_vector {
|
||||
match state.registers.cycle {
|
||||
0 => {
|
||||
|
@ -138,11 +167,11 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
|||
}
|
||||
1 => CycleResult::NeedsMore,
|
||||
2 => {
|
||||
state.cpu_push_stack((state.registers.pc.overflowing_add(3).0 >> 8) as u8);
|
||||
state.cpu_push_stack((state.registers.pc >> 8) as u8);
|
||||
CycleResult::NeedsMore
|
||||
}
|
||||
3 => {
|
||||
state.cpu_push_stack(state.registers.pc.overflowing_add(3).0 as u8);
|
||||
state.cpu_push_stack(state.registers.pc as u8);
|
||||
CycleResult::NeedsMore
|
||||
}
|
||||
4 => {
|
||||
|
@ -179,6 +208,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
|||
let result: CycleResult = match opcode {
|
||||
0x00 => misc::nop,
|
||||
0x01 => load_store_move::ld_bc_imm_u16,
|
||||
0x02 => load_store_move::ld_deref_bc_a,
|
||||
0x03 => alu::inc_bc,
|
||||
0x04 => alu::inc_b,
|
||||
0x05 => alu::dec_b,
|
||||
|
@ -191,6 +221,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
|||
0x0d => alu::dec_c,
|
||||
0x0e => load_store_move::ld_c_imm_u8,
|
||||
0x11 => load_store_move::ld_de_imm_u16,
|
||||
0x12 => load_store_move::ld_deref_de_a,
|
||||
0x13 => alu::inc_de,
|
||||
0x14 => alu::inc_d,
|
||||
0x15 => alu::dec_d,
|
||||
|
@ -203,6 +234,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
|||
0x1c => alu::inc_e,
|
||||
0x1d => alu::dec_e,
|
||||
0x1e => load_store_move::ld_e_imm_u8,
|
||||
0x1f => alu::rra,
|
||||
0x20 => flow::jr_nz_i8,
|
||||
0x21 => load_store_move::ld_hl_imm_u16,
|
||||
0x22 => load_store_move::ld_hl_plus_a,
|
||||
|
@ -288,6 +320,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
|||
0x73 => load_store_move::ld_deref_hl_e,
|
||||
0x74 => load_store_move::ld_deref_hl_h,
|
||||
0x75 => load_store_move::ld_deref_hl_l,
|
||||
0x76 => misc::halt,
|
||||
0x77 => load_store_move::ld_deref_hl_a,
|
||||
0x78 => load_store_move::ld_a_b,
|
||||
0x79 => load_store_move::ld_a_c,
|
||||
|
@ -380,6 +413,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
|||
0xD0 => flow::ret_nc,
|
||||
0xD1 => load_store_move::pop_de,
|
||||
0xD2 => flow::jp_nc_u16,
|
||||
0xD3 => panic!("Executing bad opcode {:#02X}", opcode),
|
||||
0xD4 => flow::call_nc_u16,
|
||||
0xD5 => load_store_move::push_de,
|
||||
0xD6 => alu::sub_a_imm_u8,
|
||||
|
@ -387,17 +421,21 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
|||
0xD8 => flow::ret_c,
|
||||
0xD9 => flow::reti,
|
||||
0xDA => flow::jp_c_u16,
|
||||
0xDB => panic!("Executing bad opcode {:#02X}", opcode),
|
||||
0xDC => flow::call_c_u16,
|
||||
0xDD => panic!("Executing bad opcode {:#02X}", opcode),
|
||||
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,
|
||||
0xE3 | 0xE4 => panic!("Executing bad opcode {:#02X}", opcode),
|
||||
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,
|
||||
0xEB | 0xEC | 0xED => panic!("Executing bad opcode {:#02X}", opcode),
|
||||
0xEE => alu::xor_a_imm_u8,
|
||||
0xEF => flow::rst_0x28,
|
||||
0xF0 => load_store_move::ldh_a_imm_u8,
|
||||
|
@ -407,9 +445,11 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
|||
0xF5 => load_store_move::push_af,
|
||||
0xF6 => alu::or_a_imm_u8,
|
||||
0xF7 => flow::rst_0x30,
|
||||
0xF8 => load_store_move::ld_hl_sp_i8,
|
||||
0xF9 => load_store_move::ld_sp_hl,
|
||||
0xFA => load_store_move::ld_a_deref_imm_u16,
|
||||
0xFB => misc::ei,
|
||||
0xFC | 0xFD => panic!("Executing bad opcode {:#02X}", opcode),
|
||||
0xFE => alu::cp_a_imm_u8,
|
||||
0xFF => flow::rst_0x38,
|
||||
unknown => {
|
||||
|
@ -425,13 +465,16 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
|||
};
|
||||
|
||||
if result == CycleResult::Finished {
|
||||
if state.used_halt_bug {
|
||||
state.registers.pc = state.registers.pc.overflowing_add(1).0;
|
||||
}
|
||||
|
||||
match state.registers.opcode_bytecount {
|
||||
Some(len) => state.registers.pc += len as u16,
|
||||
Some(len) => state.registers.pc = state.registers.pc.overflowing_add(len as u16).0,
|
||||
None => panic!("Forgot to set opcode len"),
|
||||
}
|
||||
|
||||
if !state.registers.mem_op_happened {
|
||||
log::trace!("Memory bus clear, precaching next opcode");
|
||||
state.cpu_read_u8(state.registers.pc);
|
||||
}
|
||||
|
||||
|
@ -439,7 +482,6 @@ pub fn tick_cpu(state: &mut Gameboy) {
|
|||
state.registers.current_prefixed_opcode = None;
|
||||
state.registers.current_opcode = None;
|
||||
state.registers.opcode_bytecount = None;
|
||||
log::trace!("Cycle finished");
|
||||
} else {
|
||||
state.registers.cycle += 1;
|
||||
}
|
||||
|
|
|
@ -17,12 +17,7 @@ pub fn sub_with_carry(lhs: u8, rhs: u8, carry: bool) -> CarryResult {
|
|||
let (result, second_carry) = first_res.overflowing_sub(carry_u8);
|
||||
|
||||
let carry = first_carry || second_carry;
|
||||
|
||||
let first_hc_res = (lhs & 0xF).overflowing_sub(rhs & 0xF).0;
|
||||
let first_half_carry = (first_hc_res >> 4) & 0b1 == 1;
|
||||
let second_half_carry = ((first_hc_res.overflowing_sub(carry_u8).0) >> 4) & 0b1 == 1;
|
||||
|
||||
let half_carry = first_half_carry || second_half_carry;
|
||||
let half_carry = (lhs & 0xF) < (rhs & 0xF) + carry_u8;
|
||||
|
||||
CarryResult { result, carry, half_carry }
|
||||
}
|
||||
|
@ -34,26 +29,21 @@ pub fn add_with_carry(lhs: u8, rhs: u8, carry: bool) -> CarryResult {
|
|||
let (result, second_carry) = first_res.overflowing_add(carry_u8);
|
||||
|
||||
let carry = first_carry || second_carry;
|
||||
|
||||
let first_hc_res = (lhs & 0xF) + (rhs & 0xF);
|
||||
let first_half_carry = (first_hc_res >> 4) & 0b1 == 1;
|
||||
let second_half_carry = ((first_hc_res + carry_u8) >> 4) & 0b1 == 1;
|
||||
|
||||
let half_carry = first_half_carry || second_half_carry;
|
||||
let half_carry = (lhs & 0xF) + (rhs & 0xF) > 0xF;
|
||||
|
||||
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) + (rhs & 0xF)) >> 4) & 0b1 == 1;
|
||||
let half_carry = (lhs & 0xF) + (rhs & 0xF) > 0xF;
|
||||
|
||||
CarryResult { result, carry, half_carry }
|
||||
}
|
||||
|
||||
pub fn sub(lhs: u8, rhs: u8) -> CarryResult {
|
||||
let (result, carry) = lhs.overflowing_sub(rhs);
|
||||
let half_carry = (((lhs & 0xF).overflowing_sub(rhs & 0xF).0) >> 4) & 0b1 == 1;
|
||||
let half_carry = (lhs & 0xF) < (rhs & 0xF);
|
||||
|
||||
CarryResult { result, carry, half_carry }
|
||||
}
|
||||
|
@ -511,6 +501,25 @@ opcode!(rla, 0x17, "RLA", false, {
|
|||
}
|
||||
});
|
||||
|
||||
opcode!(rra, 0x1f, "RRA", false, {
|
||||
0 => {
|
||||
let carry = state.registers.a & 0b1 == 1;
|
||||
state.registers.a >>= 1;
|
||||
|
||||
if state.registers.get_carry() {
|
||||
state.registers.a = state.registers.a.wrapping_add(1 << 7);
|
||||
}
|
||||
|
||||
state.registers.set_zero(false);
|
||||
state.registers.set_subtract(false);
|
||||
state.registers.set_half_carry(false);
|
||||
state.registers.set_carry(carry);
|
||||
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
|
||||
macro_rules! define_inc_u16_reg {
|
||||
($op:literal, $lreg:ident, $rreg:ident) => {
|
||||
paste::paste! {
|
||||
|
@ -818,15 +827,18 @@ macro_rules! define_add_hl_u16_reg {
|
|||
CycleResult::NeedsMore
|
||||
},
|
||||
1 => {
|
||||
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);
|
||||
|
||||
if state.registers.take_hold() != 0 {
|
||||
let CarryResult { result, carry, half_carry } = add(state.registers.h, state.registers.$lreg);
|
||||
let CarryResult { result, carry: s_carry, half_carry: s_half_carry } = add(state.registers.h, 1);
|
||||
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_half_carry(half_carry || s_half_carry);
|
||||
state.registers.set_carry(carry || s_carry);
|
||||
}
|
||||
|
||||
state.registers.set_subtract(false);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
|
@ -848,16 +860,18 @@ opcode!(add_hl_sp, 0x39, "ADD HL, SP", false, {
|
|||
CycleResult::NeedsMore
|
||||
},
|
||||
1 => {
|
||||
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);
|
||||
|
||||
if state.registers.take_hold() != 0 {
|
||||
let CarryResult { result, carry, half_carry } =
|
||||
add(state.registers.h, (state.registers.sp >> 8) as u8);
|
||||
let CarryResult { result, carry: s_carry, half_carry: s_half_carry } = add(state.registers.h, 1);
|
||||
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_half_carry(half_carry || s_half_carry);
|
||||
state.registers.set_carry(carry || s_carry);
|
||||
}
|
||||
|
||||
state.registers.set_subtract(false);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
|
|
|
@ -477,6 +477,7 @@ opcode!(ret, 0xC9, "RET", false, {
|
|||
},
|
||||
3 => {
|
||||
let address = (state.registers.take_mem() as u16) << 8 | state.registers.take_hold();
|
||||
println!("RET to {:#04X}", address);
|
||||
state.registers.pc = address;
|
||||
state.registers.opcode_bytecount = Some(0);
|
||||
CycleResult::Finished
|
||||
|
@ -634,11 +635,11 @@ macro_rules! define_rst {
|
|||
CycleResult::NeedsMore
|
||||
},
|
||||
1 => {
|
||||
state.cpu_push_stack((state.registers.pc >> 8) as u8);
|
||||
state.cpu_push_stack((state.registers.pc.overflowing_add(1).0 >> 8) as u8);
|
||||
CycleResult::NeedsMore
|
||||
},
|
||||
2 => {
|
||||
state.cpu_push_stack((state.registers.pc & 0xFF) as u8);
|
||||
state.cpu_push_stack((state.registers.pc.overflowing_add(1).0 & 0xFF) as u8);
|
||||
CycleResult::NeedsMore
|
||||
},
|
||||
3 => {
|
||||
|
|
|
@ -168,6 +168,28 @@ define_ld_reg_deref!(0x7E, a, hl);
|
|||
define_ld_reg_deref!(0x0A, a, bc);
|
||||
define_ld_reg_deref!(0x1A, a, de);
|
||||
|
||||
opcode!(ld_deref_bc_a, 0x02, "LD (BC),A", false, {
|
||||
0 => {
|
||||
state.cpu_write_u8(state.registers.get_bc(), state.registers.a);
|
||||
CycleResult::NeedsMore
|
||||
},
|
||||
1 => {
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
|
||||
opcode!(ld_deref_de_a, 0x12, "LD (DE),A", false, {
|
||||
0 => {
|
||||
state.cpu_write_u8(state.registers.get_de(), state.registers.a);
|
||||
CycleResult::NeedsMore
|
||||
},
|
||||
1 => {
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
|
||||
opcode!(ld_hl_plus_a, 0x22, "LD (HL+),A", false, {
|
||||
0 => {
|
||||
state.cpu_write_u8(state.registers.get_hl(), state.registers.a);
|
||||
|
@ -222,6 +244,33 @@ opcode!(ld_a_hl_minus, 0x3A, "LD A,(HL-)", false, {
|
|||
}
|
||||
});
|
||||
|
||||
opcode!(ld_hl_sp_i8, 0xF8, "LD HL,SP+i8", false, {
|
||||
0 => {
|
||||
state.cpu_read_u8(state.registers.pc.overflowing_add(1).0);
|
||||
CycleResult::NeedsMore
|
||||
},
|
||||
1 => {
|
||||
let val = state.registers.take_mem() as i8;
|
||||
|
||||
let rhs = if val < 0 {
|
||||
state.registers.sp.overflowing_sub((val as u8 & !(1u8 << 7)) as u16).0
|
||||
} else {
|
||||
state.registers.sp.overflowing_add(val as u16).0
|
||||
};
|
||||
|
||||
state.registers.set_hl(rhs);
|
||||
state.registers.set_zero(false);
|
||||
state.registers.set_subtract(false);
|
||||
state.registers.set_half_carry((state.registers.sp & 0xF) + (val as u16 & 0xF) > 0xF);
|
||||
state.registers.set_carry((state.registers.sp & 0xFF) + (val as u16 & 0xFF) > 0xFF);
|
||||
CycleResult::NeedsMore
|
||||
},
|
||||
2 => {
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
|
||||
macro_rules! define_ld_reg_imm_u8 {
|
||||
($op:literal, $lreg:ident) => {
|
||||
paste::paste! {
|
||||
|
|
|
@ -12,7 +12,7 @@ opcode!(nop, 0x00, "NOP", false, {
|
|||
|
||||
opcode!(di, 0xF3, "DI", false, {
|
||||
0 => {
|
||||
state.interrupts.ime = false;
|
||||
state.interrupts.cpu_set_ime(false);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
|
@ -20,8 +20,20 @@ opcode!(di, 0xF3, "DI", false, {
|
|||
|
||||
opcode!(ei, 0xFB, "EI", false, {
|
||||
0 => {
|
||||
state.interrupts.ime = true;
|
||||
state.interrupts.cpu_set_ime(true);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
|
||||
opcode!(halt, 0x76, "HALT", false, {
|
||||
0 => {
|
||||
if !state.interrupts.ime && (state.interrupts.interrupt_enable & state.interrupts.interrupt_flag & 0x1F != 0) {
|
||||
state.halt_bug = true;
|
||||
} else {
|
||||
state.halt = true;
|
||||
}
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
|
|
|
@ -26,6 +26,13 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
|
|||
0x14 => rl_h,
|
||||
0x15 => rl_l,
|
||||
0x17 => rl_a,
|
||||
0x18 => rr_b,
|
||||
0x19 => rr_c,
|
||||
0x1a => rr_d,
|
||||
0x1b => rr_e,
|
||||
0x1c => rr_h,
|
||||
0x1d => rr_l,
|
||||
0x1f => rr_a,
|
||||
0x30 => swap_b,
|
||||
0x31 => swap_c,
|
||||
0x32 => swap_d,
|
||||
|
@ -33,6 +40,13 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
|
|||
0x34 => swap_h,
|
||||
0x35 => swap_l,
|
||||
0x37 => swap_a,
|
||||
0x38 => srl_b,
|
||||
0x39 => srl_c,
|
||||
0x3a => srl_d,
|
||||
0x3b => srl_e,
|
||||
0x3c => srl_h,
|
||||
0x3d => srl_l,
|
||||
0x3f => srl_a,
|
||||
0x40 => bit_0_b,
|
||||
0x41 => bit_0_c,
|
||||
0x42 => bit_0_d,
|
||||
|
@ -97,6 +111,134 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
|
|||
0x7d => bit_7_l,
|
||||
0x7e => bit_7_deref_hl,
|
||||
0x7f => bit_7_a,
|
||||
0x80 => res_0_b,
|
||||
0x81 => res_0_c,
|
||||
0x82 => res_0_d,
|
||||
0x83 => res_0_e,
|
||||
0x84 => res_0_h,
|
||||
0x85 => res_0_l,
|
||||
0x86 => res_0_deref_hl,
|
||||
0x87 => res_0_a,
|
||||
0x88 => res_1_b,
|
||||
0x89 => res_1_c,
|
||||
0x8A => res_1_d,
|
||||
0x8B => res_1_e,
|
||||
0x8C => res_1_h,
|
||||
0x8D => res_1_l,
|
||||
0x8E => res_1_deref_hl,
|
||||
0x8F => res_1_a,
|
||||
0x90 => res_2_b,
|
||||
0x91 => res_2_c,
|
||||
0x92 => res_2_d,
|
||||
0x93 => res_2_e,
|
||||
0x94 => res_2_h,
|
||||
0x95 => res_2_l,
|
||||
0x96 => res_2_deref_hl,
|
||||
0x97 => res_2_a,
|
||||
0x98 => res_3_b,
|
||||
0x99 => res_3_c,
|
||||
0x9A => res_3_d,
|
||||
0x9B => res_3_e,
|
||||
0x9C => res_3_h,
|
||||
0x9D => res_3_l,
|
||||
0x9E => res_3_deref_hl,
|
||||
0x9F => res_3_a,
|
||||
0xA0 => res_4_b,
|
||||
0xA1 => res_4_c,
|
||||
0xA2 => res_4_d,
|
||||
0xA3 => res_4_e,
|
||||
0xA4 => res_4_h,
|
||||
0xA5 => res_4_l,
|
||||
0xA6 => res_4_deref_hl,
|
||||
0xA7 => res_4_a,
|
||||
0xA8 => res_5_b,
|
||||
0xA9 => res_5_c,
|
||||
0xAA => res_5_d,
|
||||
0xAB => res_5_e,
|
||||
0xAC => res_5_h,
|
||||
0xAD => res_5_l,
|
||||
0xAE => res_5_deref_hl,
|
||||
0xAF => res_5_a,
|
||||
0xB0 => res_6_b,
|
||||
0xB1 => res_6_c,
|
||||
0xB2 => res_6_d,
|
||||
0xB3 => res_6_e,
|
||||
0xB4 => res_6_h,
|
||||
0xB5 => res_6_l,
|
||||
0xB6 => res_6_deref_hl,
|
||||
0xB7 => res_6_a,
|
||||
0xB8 => res_7_b,
|
||||
0xB9 => res_7_c,
|
||||
0xBA => res_7_d,
|
||||
0xBB => res_7_e,
|
||||
0xBC => res_7_h,
|
||||
0xBD => res_7_l,
|
||||
0xBE => res_7_deref_hl,
|
||||
0xBF => res_7_a,
|
||||
0xC0 => set_0_b,
|
||||
0xC1 => set_0_c,
|
||||
0xC2 => set_0_d,
|
||||
0xC3 => set_0_e,
|
||||
0xC4 => set_0_h,
|
||||
0xC5 => set_0_l,
|
||||
0xC6 => set_0_deref_hl,
|
||||
0xC7 => set_0_a,
|
||||
0xC8 => set_1_b,
|
||||
0xC9 => set_1_c,
|
||||
0xCA => set_1_d,
|
||||
0xCB => set_1_e,
|
||||
0xCC => set_1_h,
|
||||
0xCD => set_1_l,
|
||||
0xCE => set_1_deref_hl,
|
||||
0xCF => set_1_a,
|
||||
0xD0 => set_2_b,
|
||||
0xD1 => set_2_c,
|
||||
0xD2 => set_2_d,
|
||||
0xD3 => set_2_e,
|
||||
0xD4 => set_2_h,
|
||||
0xD5 => set_2_l,
|
||||
0xD6 => set_2_deref_hl,
|
||||
0xD7 => set_2_a,
|
||||
0xD8 => set_3_b,
|
||||
0xD9 => set_3_c,
|
||||
0xDA => set_3_d,
|
||||
0xDB => set_3_e,
|
||||
0xDC => set_3_h,
|
||||
0xDD => set_3_l,
|
||||
0xDE => set_3_deref_hl,
|
||||
0xDF => set_3_a,
|
||||
0xE0 => set_4_b,
|
||||
0xE1 => set_4_c,
|
||||
0xE2 => set_4_d,
|
||||
0xE3 => set_4_e,
|
||||
0xE4 => set_4_h,
|
||||
0xE5 => set_4_l,
|
||||
0xE6 => set_4_deref_hl,
|
||||
0xE7 => set_4_a,
|
||||
0xE8 => set_5_b,
|
||||
0xE9 => set_5_c,
|
||||
0xEA => set_5_d,
|
||||
0xEB => set_5_e,
|
||||
0xEC => set_5_h,
|
||||
0xED => set_5_l,
|
||||
0xEE => set_5_deref_hl,
|
||||
0xEF => set_5_a,
|
||||
0xF0 => set_6_b,
|
||||
0xF1 => set_6_c,
|
||||
0xF2 => set_6_d,
|
||||
0xF3 => set_6_e,
|
||||
0xF4 => set_6_h,
|
||||
0xF5 => set_6_l,
|
||||
0xF6 => set_6_deref_hl,
|
||||
0xF7 => set_6_a,
|
||||
0xF8 => set_7_b,
|
||||
0xF9 => set_7_c,
|
||||
0xFA => set_7_d,
|
||||
0xFB => set_7_e,
|
||||
0xFC => set_7_h,
|
||||
0xFD => set_7_l,
|
||||
0xFE => set_7_deref_hl,
|
||||
0xFF => set_7_a,
|
||||
unknown => panic!(
|
||||
"Unrecognized prefixed opcode: {:#X}\nRegisters: {:#?}",
|
||||
unknown, state.registers
|
||||
|
@ -109,7 +251,7 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
|
|||
macro_rules! define_bit_reg {
|
||||
($op:literal, $bit:literal, $reg:ident) => {
|
||||
paste::paste! {
|
||||
opcode!([<bit_ $bit _ $reg>], $op, std::concat!("BIT ", std::stringify!($bit), ",", std::stringify!($reg)), false, {
|
||||
opcode!([<bit_ $bit _ $reg>], $op, std::concat!("BIT ", std::stringify!($bit), ",", std::stringify!($reg)), true, {
|
||||
1 => {
|
||||
state.registers.set_zero(state.registers.$reg & (1 << $bit) == 0);
|
||||
state.registers.set_subtract(false);
|
||||
|
@ -182,7 +324,7 @@ define_bit_reg!(0x7f, 7, a);
|
|||
macro_rules! define_bit_deref_hl {
|
||||
($op:literal, $bit:literal) => {
|
||||
paste::paste! {
|
||||
opcode!([<bit_ $bit _deref_hl>], $op, std::concat!("BIT ", std::stringify!($bit), ",(HL)"), false, {
|
||||
opcode!([<bit_ $bit _deref_hl>], $op, std::concat!("BIT ", std::stringify!($bit), ",(HL)"), true, {
|
||||
1 => {
|
||||
state.cpu_read_u8(state.registers.get_hl());
|
||||
CycleResult::NeedsMore
|
||||
|
@ -212,7 +354,7 @@ define_bit_deref_hl!(0x7e, 7);
|
|||
macro_rules! define_rl_reg {
|
||||
($op:literal, $reg:ident) => {
|
||||
paste::paste! {
|
||||
opcode!([<rl_ $reg>], $op, std::concat!("RL ", std::stringify!($reg)), false, {
|
||||
opcode!([<rl_ $reg>], $op, std::concat!("RL ", std::stringify!($reg)), true, {
|
||||
1 => {
|
||||
let carry = state.registers.$reg >> 7 == 1;
|
||||
state.registers.$reg <<= 1;
|
||||
|
@ -244,7 +386,7 @@ define_rl_reg!(0x17, a);
|
|||
macro_rules! define_swap_reg {
|
||||
($op:literal, $reg:ident) => {
|
||||
paste::paste! {
|
||||
opcode!([<swap_ $reg>], $op, std::concat!("SWAP ", std::stringify!($reg)), false, {
|
||||
opcode!([<swap_ $reg>], $op, std::concat!("SWAP ", std::stringify!($reg)), true, {
|
||||
1 => {
|
||||
state.registers.$reg = (state.registers.$reg >> 4) | (state.registers.$reg << 4);
|
||||
|
||||
|
@ -267,3 +409,263 @@ define_swap_reg!(0x33, e);
|
|||
define_swap_reg!(0x34, h);
|
||||
define_swap_reg!(0x35, l);
|
||||
define_swap_reg!(0x37, a);
|
||||
|
||||
macro_rules! define_srl_reg {
|
||||
($op:literal, $reg:ident) => {
|
||||
paste::paste! {
|
||||
opcode!([<srl_ $reg>], $op, std::concat!("SRL ", std::stringify!($reg)), true, {
|
||||
1 => {
|
||||
let carry = state.registers.$reg & 0b1 == 1;
|
||||
state.registers.$reg >>= 1;
|
||||
|
||||
state.registers.set_zero(state.registers.$reg == 0);
|
||||
state.registers.set_subtract(false);
|
||||
state.registers.set_half_carry(false);
|
||||
state.registers.set_carry(carry);
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_srl_reg!(0x38, b);
|
||||
define_srl_reg!(0x39, c);
|
||||
define_srl_reg!(0x3a, d);
|
||||
define_srl_reg!(0x3b, e);
|
||||
define_srl_reg!(0x3c, h);
|
||||
define_srl_reg!(0x3d, l);
|
||||
define_srl_reg!(0x3f, a);
|
||||
|
||||
macro_rules! define_rr_reg {
|
||||
($op:literal, $reg:ident) => {
|
||||
paste::paste! {
|
||||
opcode!([<rr_ $reg>], $op, std::concat!("RR ", std::stringify!($reg)), true, {
|
||||
1 => {
|
||||
let carry = state.registers.$reg & 0b1 == 1;
|
||||
state.registers.$reg >>= 1;
|
||||
|
||||
state.registers.set_zero(state.registers.$reg == 0);
|
||||
state.registers.set_subtract(false);
|
||||
state.registers.set_half_carry(false);
|
||||
state.registers.set_carry(carry);
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_rr_reg!(0x18, b);
|
||||
define_rr_reg!(0x19, c);
|
||||
define_rr_reg!(0x1a, d);
|
||||
define_rr_reg!(0x1b, e);
|
||||
define_rr_reg!(0x1c, h);
|
||||
define_rr_reg!(0x1d, l);
|
||||
define_rr_reg!(0x1f, a);
|
||||
|
||||
macro_rules! define_res_idx_reg {
|
||||
($op:literal, $idx:literal, $reg:ident) => {
|
||||
paste::paste! {
|
||||
opcode!([<res_ $idx _ $reg>], $op, std::concat!("RES ", std::stringify!($idx), ", ", std::stringify!($reg)), true, {
|
||||
1 => {
|
||||
state.registers.$reg &= !(1u8 << $idx);
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_res_idx_reg!(0x80, 0, b);
|
||||
define_res_idx_reg!(0x81, 0, c);
|
||||
define_res_idx_reg!(0x82, 0, d);
|
||||
define_res_idx_reg!(0x83, 0, e);
|
||||
define_res_idx_reg!(0x84, 0, h);
|
||||
define_res_idx_reg!(0x85, 0, l);
|
||||
define_res_idx_reg!(0x87, 0, a);
|
||||
define_res_idx_reg!(0x88, 1, b);
|
||||
define_res_idx_reg!(0x89, 1, c);
|
||||
define_res_idx_reg!(0x8a, 1, d);
|
||||
define_res_idx_reg!(0x8b, 1, e);
|
||||
define_res_idx_reg!(0x8c, 1, h);
|
||||
define_res_idx_reg!(0x8d, 1, l);
|
||||
define_res_idx_reg!(0x8f, 1, a);
|
||||
define_res_idx_reg!(0x90, 2, b);
|
||||
define_res_idx_reg!(0x91, 2, c);
|
||||
define_res_idx_reg!(0x92, 2, d);
|
||||
define_res_idx_reg!(0x93, 2, e);
|
||||
define_res_idx_reg!(0x94, 2, h);
|
||||
define_res_idx_reg!(0x95, 2, l);
|
||||
define_res_idx_reg!(0x97, 2, a);
|
||||
define_res_idx_reg!(0x98, 3, b);
|
||||
define_res_idx_reg!(0x99, 3, c);
|
||||
define_res_idx_reg!(0x9a, 3, d);
|
||||
define_res_idx_reg!(0x9b, 3, e);
|
||||
define_res_idx_reg!(0x9c, 3, h);
|
||||
define_res_idx_reg!(0x9d, 3, l);
|
||||
define_res_idx_reg!(0x9f, 3, a);
|
||||
define_res_idx_reg!(0xa0, 4, b);
|
||||
define_res_idx_reg!(0xa1, 4, c);
|
||||
define_res_idx_reg!(0xa2, 4, d);
|
||||
define_res_idx_reg!(0xa3, 4, e);
|
||||
define_res_idx_reg!(0xa4, 4, h);
|
||||
define_res_idx_reg!(0xa5, 4, l);
|
||||
define_res_idx_reg!(0xa7, 4, a);
|
||||
define_res_idx_reg!(0xa8, 5, b);
|
||||
define_res_idx_reg!(0xa9, 5, c);
|
||||
define_res_idx_reg!(0xaa, 5, d);
|
||||
define_res_idx_reg!(0xab, 5, e);
|
||||
define_res_idx_reg!(0xac, 5, h);
|
||||
define_res_idx_reg!(0xad, 5, l);
|
||||
define_res_idx_reg!(0xaf, 5, a);
|
||||
define_res_idx_reg!(0xb0, 6, b);
|
||||
define_res_idx_reg!(0xb1, 6, c);
|
||||
define_res_idx_reg!(0xb2, 6, d);
|
||||
define_res_idx_reg!(0xb3, 6, e);
|
||||
define_res_idx_reg!(0xb4, 6, h);
|
||||
define_res_idx_reg!(0xb5, 6, l);
|
||||
define_res_idx_reg!(0xb7, 6, a);
|
||||
define_res_idx_reg!(0xb8, 7, b);
|
||||
define_res_idx_reg!(0xb9, 7, c);
|
||||
define_res_idx_reg!(0xba, 7, d);
|
||||
define_res_idx_reg!(0xbb, 7, e);
|
||||
define_res_idx_reg!(0xbc, 7, h);
|
||||
define_res_idx_reg!(0xbd, 7, l);
|
||||
define_res_idx_reg!(0xbf, 7, a);
|
||||
|
||||
macro_rules! define_res_idx_deref_hl {
|
||||
($op:literal, $idx:literal) => {
|
||||
paste::paste! {
|
||||
opcode!([<res_ $idx _deref_hl>], $op, std::concat!("RES ", std::stringify!($idx), ", (HL)"), true, {
|
||||
1 => {
|
||||
state.cpu_read_u8(state.registers.get_hl());
|
||||
CycleResult::NeedsMore
|
||||
},
|
||||
2 => {
|
||||
let res = state.registers.take_mem() & !(1u8 << $idx);
|
||||
state.cpu_write_u8(state.registers.get_hl(), res);
|
||||
CycleResult::NeedsMore
|
||||
},
|
||||
3 => {
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_res_idx_deref_hl!(0x86, 0);
|
||||
define_res_idx_deref_hl!(0x8E, 1);
|
||||
define_res_idx_deref_hl!(0x96, 2);
|
||||
define_res_idx_deref_hl!(0x9E, 3);
|
||||
define_res_idx_deref_hl!(0xA6, 4);
|
||||
define_res_idx_deref_hl!(0xAE, 5);
|
||||
define_res_idx_deref_hl!(0xB6, 6);
|
||||
define_res_idx_deref_hl!(0xBE, 7);
|
||||
|
||||
macro_rules! define_set_idx_reg {
|
||||
($op:literal, $idx:literal, $reg:ident) => {
|
||||
paste::paste! {
|
||||
opcode!([<set_ $idx _ $reg>], $op, std::concat!("SET ", std::stringify!($idx), ", ", std::stringify!($reg)), true, {
|
||||
1 => {
|
||||
state.registers.$reg |= 1u8 << $idx;
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_set_idx_reg!(0xc0, 0, b);
|
||||
define_set_idx_reg!(0xc1, 0, c);
|
||||
define_set_idx_reg!(0xc2, 0, d);
|
||||
define_set_idx_reg!(0xc3, 0, e);
|
||||
define_set_idx_reg!(0xc4, 0, h);
|
||||
define_set_idx_reg!(0xc5, 0, l);
|
||||
define_set_idx_reg!(0xc7, 0, a);
|
||||
define_set_idx_reg!(0xc8, 1, b);
|
||||
define_set_idx_reg!(0xc9, 1, c);
|
||||
define_set_idx_reg!(0xca, 1, d);
|
||||
define_set_idx_reg!(0xcb, 1, e);
|
||||
define_set_idx_reg!(0xcc, 1, h);
|
||||
define_set_idx_reg!(0xcd, 1, l);
|
||||
define_set_idx_reg!(0xcf, 1, a);
|
||||
define_set_idx_reg!(0xd0, 2, b);
|
||||
define_set_idx_reg!(0xd1, 2, c);
|
||||
define_set_idx_reg!(0xd2, 2, d);
|
||||
define_set_idx_reg!(0xd3, 2, e);
|
||||
define_set_idx_reg!(0xd4, 2, h);
|
||||
define_set_idx_reg!(0xd5, 2, l);
|
||||
define_set_idx_reg!(0xd7, 2, a);
|
||||
define_set_idx_reg!(0xd8, 3, b);
|
||||
define_set_idx_reg!(0xd9, 3, c);
|
||||
define_set_idx_reg!(0xda, 3, d);
|
||||
define_set_idx_reg!(0xdb, 3, e);
|
||||
define_set_idx_reg!(0xdc, 3, h);
|
||||
define_set_idx_reg!(0xdd, 3, l);
|
||||
define_set_idx_reg!(0xdf, 3, a);
|
||||
define_set_idx_reg!(0xe0, 4, b);
|
||||
define_set_idx_reg!(0xe1, 4, c);
|
||||
define_set_idx_reg!(0xe2, 4, d);
|
||||
define_set_idx_reg!(0xe3, 4, e);
|
||||
define_set_idx_reg!(0xe4, 4, h);
|
||||
define_set_idx_reg!(0xe5, 4, l);
|
||||
define_set_idx_reg!(0xe7, 4, a);
|
||||
define_set_idx_reg!(0xe8, 5, b);
|
||||
define_set_idx_reg!(0xe9, 5, c);
|
||||
define_set_idx_reg!(0xea, 5, d);
|
||||
define_set_idx_reg!(0xeb, 5, e);
|
||||
define_set_idx_reg!(0xec, 5, h);
|
||||
define_set_idx_reg!(0xed, 5, l);
|
||||
define_set_idx_reg!(0xef, 5, a);
|
||||
define_set_idx_reg!(0xf0, 6, b);
|
||||
define_set_idx_reg!(0xf1, 6, c);
|
||||
define_set_idx_reg!(0xf2, 6, d);
|
||||
define_set_idx_reg!(0xf3, 6, e);
|
||||
define_set_idx_reg!(0xf4, 6, h);
|
||||
define_set_idx_reg!(0xf5, 6, l);
|
||||
define_set_idx_reg!(0xf7, 6, a);
|
||||
define_set_idx_reg!(0xf8, 7, b);
|
||||
define_set_idx_reg!(0xf9, 7, c);
|
||||
define_set_idx_reg!(0xfa, 7, d);
|
||||
define_set_idx_reg!(0xfb, 7, e);
|
||||
define_set_idx_reg!(0xfc, 7, h);
|
||||
define_set_idx_reg!(0xfd, 7, l);
|
||||
define_set_idx_reg!(0xff, 7, a);
|
||||
|
||||
macro_rules! define_set_idx_deref_hl {
|
||||
($op:literal, $idx:literal) => {
|
||||
paste::paste! {
|
||||
opcode!([<set_ $idx _deref_hl>], $op, std::concat!("SET ", std::stringify!($idx), ", (HL)"), true, {
|
||||
1 => {
|
||||
state.cpu_read_u8(state.registers.get_hl());
|
||||
CycleResult::NeedsMore
|
||||
},
|
||||
2 => {
|
||||
let res = state.registers.take_mem() | (1u8 << $idx);
|
||||
state.cpu_write_u8(state.registers.get_hl(), res);
|
||||
CycleResult::NeedsMore
|
||||
},
|
||||
3 => {
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_set_idx_deref_hl!(0xC6, 0);
|
||||
define_set_idx_deref_hl!(0xCE, 1);
|
||||
define_set_idx_deref_hl!(0xD6, 2);
|
||||
define_set_idx_deref_hl!(0xDE, 3);
|
||||
define_set_idx_deref_hl!(0xE6, 4);
|
||||
define_set_idx_deref_hl!(0xEE, 5);
|
||||
define_set_idx_deref_hl!(0xF6, 6);
|
||||
define_set_idx_deref_hl!(0xFE, 7);
|
||||
|
|
|
@ -16,13 +16,14 @@ macro_rules! define_bitfield_u8_gs {
|
|||
|
||||
pub struct Interrupts {
|
||||
pub ime: bool,
|
||||
pub ei_queued: bool,
|
||||
pub interrupt_enable: u8,
|
||||
pub interrupt_flag: u8,
|
||||
}
|
||||
|
||||
impl Interrupts {
|
||||
pub fn new() -> Self {
|
||||
Self { ime: false, interrupt_enable: 0, interrupt_flag: 0 }
|
||||
Self { ime: false, interrupt_enable: 0, interrupt_flag: 0, ei_queued: false }
|
||||
}
|
||||
|
||||
define_bitfield_u8_gs!(ie_vblank, 0, interrupt_enable);
|
||||
|
@ -35,4 +36,11 @@ impl Interrupts {
|
|||
define_bitfield_u8_gs!(if_timer, 2, interrupt_flag);
|
||||
define_bitfield_u8_gs!(if_serial, 3, interrupt_flag);
|
||||
define_bitfield_u8_gs!(if_joypad, 4, interrupt_flag);
|
||||
|
||||
pub fn cpu_set_ime(&mut self, val: bool) {
|
||||
self.ei_queued = val;
|
||||
if !val {
|
||||
self.ime = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,19 @@ pub enum JoypadMode {
|
|||
Direction,
|
||||
}
|
||||
|
||||
macro_rules! joypad_input {
|
||||
($input:ident, $mode:ident) => {
|
||||
paste::paste! {
|
||||
pub fn [<set_ $input>](&mut self, val: bool) {
|
||||
if val && self.mode == JoypadMode::$mode {
|
||||
self.interrupt_triggered = true;
|
||||
}
|
||||
self.$input = val
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Joypad {
|
||||
mode: JoypadMode,
|
||||
pub down: bool,
|
||||
|
@ -14,6 +27,7 @@ pub struct Joypad {
|
|||
pub select: bool,
|
||||
pub b: bool,
|
||||
pub a: bool,
|
||||
pub interrupt_triggered: bool,
|
||||
}
|
||||
|
||||
impl Joypad {
|
||||
|
@ -28,6 +42,7 @@ impl Joypad {
|
|||
select: false,
|
||||
b: false,
|
||||
a: false,
|
||||
interrupt_triggered: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,6 +65,15 @@ impl Joypad {
|
|||
}
|
||||
}
|
||||
|
||||
joypad_input!(a, Action);
|
||||
joypad_input!(b, Action);
|
||||
joypad_input!(start, Action);
|
||||
joypad_input!(select, Action);
|
||||
joypad_input!(up, Direction);
|
||||
joypad_input!(down, Direction);
|
||||
joypad_input!(left, Direction);
|
||||
joypad_input!(right, Direction);
|
||||
|
||||
pub fn cpu_write(&mut self, content: u8) {
|
||||
if (content >> 5) & 0b1 == 0 {
|
||||
self.mode = JoypadMode::Action;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
pub mod mbc1;
|
||||
|
||||
pub trait Mapper {
|
||||
fn read_rom_u8(&self, address: u16) -> u8;
|
||||
fn write_rom_u8(&mut self, address: u16, value: u8);
|
||||
|
|
114
deemgee/src/gameboy/mapper/mbc1.rs
Normal file
114
deemgee/src/gameboy/mapper/mbc1.rs
Normal file
|
@ -0,0 +1,114 @@
|
|||
use super::Mapper;
|
||||
|
||||
pub struct MBC1 {
|
||||
rom: Vec<u8>,
|
||||
ram: Option<Vec<u8>>,
|
||||
rom_bank_count: u8,
|
||||
ram_bank_count: u8,
|
||||
ram_enabled: bool,
|
||||
rom_bank_number: u8,
|
||||
extra_2_bit_reg: u8,
|
||||
banking_mode_select: bool,
|
||||
}
|
||||
|
||||
impl MBC1 {
|
||||
pub fn new(data: Vec<u8>) -> Self {
|
||||
let rom_bank_count = 0b10 << data[0x148];
|
||||
let ram_bank_count = match data[0x149] {
|
||||
0 | 1 => 0,
|
||||
2 => 1,
|
||||
3 => 4,
|
||||
4 => 16,
|
||||
5 => 8,
|
||||
_ => panic!("Bad RAM bank count for MBC1"),
|
||||
};
|
||||
|
||||
let ram = match ram_bank_count {
|
||||
0 | 1 => None,
|
||||
2 | 3 | 4 | 5 => Some(vec![0u8; ram_bank_count as usize * (8 * 1024)]),
|
||||
_ => panic!("Bad RAM bank count for MBC1"),
|
||||
};
|
||||
|
||||
assert_eq!(data.len(), rom_bank_count as usize * (16 * 1024));
|
||||
|
||||
Self {
|
||||
rom: data,
|
||||
rom_bank_count,
|
||||
ram_enabled: false,
|
||||
rom_bank_number: 1,
|
||||
extra_2_bit_reg: 0,
|
||||
ram,
|
||||
ram_bank_count,
|
||||
banking_mode_select: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_ram_enabled(&mut self, val: u8) {
|
||||
self.ram_enabled = val & 0xA != 0;
|
||||
}
|
||||
|
||||
fn set_rom_bank_number(&mut self, mut val: u8) {
|
||||
if val == 0 {
|
||||
val = 1;
|
||||
}
|
||||
self.rom_bank_number = val & 0b1_1111;
|
||||
}
|
||||
|
||||
fn set_extra_2_bit_reg(&mut self, val: u8) {
|
||||
self.extra_2_bit_reg = val & 0b11;
|
||||
}
|
||||
|
||||
fn set_banking_mode_select(&mut self, val: u8) {
|
||||
self.banking_mode_select = val & 0b1 == 1;
|
||||
}
|
||||
|
||||
fn is_large_rom(&self) -> bool {
|
||||
self.rom_bank_count > 4
|
||||
}
|
||||
|
||||
fn is_large_ram(&self) -> bool {
|
||||
self.ram_bank_count > 2
|
||||
}
|
||||
}
|
||||
|
||||
impl Mapper for MBC1 {
|
||||
fn read_rom_u8(&self, address: u16) -> u8 {
|
||||
if address <= 0x3FFF {
|
||||
self.rom[if self.is_large_rom() && self.banking_mode_select {
|
||||
((self.extra_2_bit_reg << 5) as usize * 0x4000) + address as usize
|
||||
} else {
|
||||
address as usize
|
||||
}]
|
||||
} else {
|
||||
self.rom[if self.is_large_rom() {
|
||||
(self.rom_bank_number | (self.extra_2_bit_reg << 5)) as usize * 0x4000
|
||||
} else {
|
||||
self.rom_bank_number as usize * 0x4000
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn write_rom_u8(&mut self, address: u16, value: u8) {
|
||||
match address {
|
||||
0..=0x1FFF => self.set_ram_enabled(value),
|
||||
0x2000..=0x3FFF => self.set_rom_bank_number(value),
|
||||
0x4000..=0x5FFF => self.set_extra_2_bit_reg(value),
|
||||
0x6000..=0x7FFF => self.set_banking_mode_select(value),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_eram_u8(&self, address: u16) -> u8 {
|
||||
match self.ram.as_ref() {
|
||||
Some(ram) => unimplemented!(),
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_eram_u8(&mut self, address: u16, value: u8) {
|
||||
match self.ram.as_ref() {
|
||||
Some(ram) => unimplemented!(),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -317,18 +317,18 @@ impl Ppu {
|
|||
}
|
||||
|
||||
if entry.y_flip() {
|
||||
tile_y_idx = 8 - tile_y_idx;
|
||||
tile_y_idx = 7 - tile_y_idx;
|
||||
}
|
||||
|
||||
let tile_idx =
|
||||
if is_second_tile { entry.tile_idx | 1 } else { entry.tile_idx & 0xFE };
|
||||
|
||||
for x in 0..8 {
|
||||
let fb_x = entry.x.overflowing_sub(8 - x).0;
|
||||
let fb_x = entry.x.overflowing_sub(7 - x).0;
|
||||
|
||||
let sprite_line_base = fb_x as usize * 4;
|
||||
|
||||
let tile_x_idx = if entry.x_flip() { 8 - x } else { x };
|
||||
let tile_x_idx = if entry.x_flip() { 7 - x } else { x };
|
||||
|
||||
let color = Self::parse_sprite_tile_color(
|
||||
self.read_obj_tile(tile_idx),
|
||||
|
@ -386,7 +386,7 @@ impl Ppu {
|
|||
}
|
||||
|
||||
fn set_mode(&mut self, interrupts: &mut Interrupts, mode: PPUMode) {
|
||||
log::debug!("PPU switching mode to {:?} @ {}", mode, self.cycle_counter);
|
||||
log::trace!("PPU switching mode to {:?} @ {}", mode, self.cycle_counter);
|
||||
self.stat &= !0b11;
|
||||
self.stat |= mode.mode_flag();
|
||||
self.cycle_counter = 0;
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
use std::io::Write;
|
||||
|
||||
pub struct Serial {
|
||||
pub sb: u8,
|
||||
pub sc: u8,
|
||||
|
||||
internal_tick: u16,
|
||||
}
|
||||
|
||||
impl Serial {
|
||||
pub fn new() -> Serial {
|
||||
Self { sb: 0, sc: 0 }
|
||||
Self { sb: 0, sc: 0, internal_tick: 0 }
|
||||
}
|
||||
|
||||
pub fn set_transfer_in_process(&mut self, value: bool) {
|
||||
|
@ -25,4 +29,20 @@ impl Serial {
|
|||
self.sc &= !0b1;
|
||||
self.sc |= conductor as u8;
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) -> bool {
|
||||
if self.get_transfer_in_process() && self.is_conductor() {
|
||||
if self.internal_tick < 128 {
|
||||
self.internal_tick += 1;
|
||||
} else {
|
||||
print!("{}", self.sb as char);
|
||||
std::io::stdout().flush().expect("flushing stdout failed");
|
||||
self.sb = 0;
|
||||
self.set_transfer_in_process(false);
|
||||
self.internal_tick = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ impl Timer {
|
|||
if self.enable {
|
||||
self.tima_counter = self.tima_counter.overflowing_add(1).0;
|
||||
if self.tima_counter >= self.clock.cycles() {
|
||||
self.tima += self.tima.overflowing_add(1).0;
|
||||
self.tima = self.tima.overflowing_add(1).0;
|
||||
|
||||
return self.tima == 0;
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ impl TimerClock {
|
|||
1 => Self::C16,
|
||||
2 => Self::C64,
|
||||
3 => Self::C256,
|
||||
4 => Self::C1024,
|
||||
0 => Self::C1024,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,14 +103,16 @@ pub fn run_gameboy(
|
|||
'outer: loop {
|
||||
while let Ok(event) = rx.try_recv() {
|
||||
match event {
|
||||
window::WindowEvent::AToggle => gameboy.joypad.a = !gameboy.joypad.a,
|
||||
window::WindowEvent::BToggle => gameboy.joypad.b = !gameboy.joypad.b,
|
||||
window::WindowEvent::SelectToggle => gameboy.joypad.select = !gameboy.joypad.select,
|
||||
window::WindowEvent::StartToggle => gameboy.joypad.start = !gameboy.joypad.start,
|
||||
window::WindowEvent::UpToggle => gameboy.joypad.up = !gameboy.joypad.up,
|
||||
window::WindowEvent::DownToggle => gameboy.joypad.down = !gameboy.joypad.down,
|
||||
window::WindowEvent::LeftToggle => gameboy.joypad.left = !gameboy.joypad.left,
|
||||
window::WindowEvent::RightToggle => gameboy.joypad.right = !gameboy.joypad.right,
|
||||
window::WindowEvent::AToggle => gameboy.joypad.set_a(!gameboy.joypad.a),
|
||||
window::WindowEvent::BToggle => gameboy.joypad.set_b(!gameboy.joypad.b),
|
||||
window::WindowEvent::SelectToggle => {
|
||||
gameboy.joypad.set_select(!gameboy.joypad.select)
|
||||
}
|
||||
window::WindowEvent::StartToggle => gameboy.joypad.set_start(!gameboy.joypad.start),
|
||||
window::WindowEvent::UpToggle => gameboy.joypad.set_up(!gameboy.joypad.up),
|
||||
window::WindowEvent::DownToggle => gameboy.joypad.set_down(!gameboy.joypad.down),
|
||||
window::WindowEvent::LeftToggle => gameboy.joypad.set_left(!gameboy.joypad.left),
|
||||
window::WindowEvent::RightToggle => gameboy.joypad.set_right(!gameboy.joypad.right),
|
||||
window::WindowEvent::PauseToggle => paused = !paused,
|
||||
window::WindowEvent::LogToggle => {
|
||||
gameboy.log_instructions = !gameboy.log_instructions
|
||||
|
|
Loading…
Reference in a new issue