feat: interrupts actually trigger & some alu ops
This commit is contained in:
parent
664418ba95
commit
3148226d73
6 changed files with 626 additions and 213 deletions
|
@ -14,7 +14,7 @@ use memory::Memory;
|
|||
use ppu::Ppu;
|
||||
use timer::Timer;
|
||||
|
||||
use self::{cpu::Registers, sound::Sound};
|
||||
use self::{cpu::Registers, mapper::NoMBC, sound::Sound};
|
||||
|
||||
pub struct DmaState {
|
||||
pub base: u8,
|
||||
|
@ -61,7 +61,7 @@ impl Gameboy {
|
|||
ppu: Ppu::new(),
|
||||
registers: Registers::default(),
|
||||
sound: Sound::default(),
|
||||
single_step: true,
|
||||
single_step: false,
|
||||
breakpoints: [false; u16::MAX as usize],
|
||||
}
|
||||
}
|
||||
|
@ -70,9 +70,26 @@ impl Gameboy {
|
|||
let op = self.internal_cpu_read_u8(self.registers.pc);
|
||||
if op == 0xCB {
|
||||
let op = self.internal_cpu_read_u8(self.registers.pc.overflowing_add(1).0);
|
||||
log::info!("Next opcode (prefixed) (cycle {}): {:#X}", self.registers.cycle, op);
|
||||
log::info!(
|
||||
"Next opcode @ {:#X} (prefixed) (cycle {}): {:#X}",
|
||||
self.registers.pc,
|
||||
self.registers.cycle,
|
||||
op
|
||||
);
|
||||
} else {
|
||||
log::info!("Next opcode (cycle {}): {:#X}", self.registers.cycle, op);
|
||||
log::info!(
|
||||
"Next opcode @ {:#X} (cycle {}): {:#X}",
|
||||
self.registers.pc,
|
||||
self.registers.cycle,
|
||||
op
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_cartridge(&mut self, bytes: Vec<u8>) {
|
||||
match bytes[0x147] {
|
||||
0 => self.cartridge = Some(Box::new(NoMBC::new(bytes))),
|
||||
other => unimplemented!("Cartidge type: {:#X}", other),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ pub struct Registers {
|
|||
pub current_prefixed_opcode: Option<u8>,
|
||||
pub mem_read_hold: Option<u8>,
|
||||
pub mem_op_happened: bool,
|
||||
pub in_interrupt_vector: Option<u8>,
|
||||
}
|
||||
|
||||
impl Registers {
|
||||
|
@ -81,9 +82,9 @@ impl Registers {
|
|||
}
|
||||
|
||||
define_flag!(zero, 7);
|
||||
define_flag!(subtract, 7);
|
||||
define_flag!(half_carry, 7);
|
||||
define_flag!(carry, 7);
|
||||
define_flag!(subtract, 6);
|
||||
define_flag!(half_carry, 5);
|
||||
define_flag!(carry, 4);
|
||||
|
||||
pub fn take_mem(&mut self) -> u8 {
|
||||
self.mem_read_hold.take().unwrap()
|
||||
|
@ -101,195 +102,292 @@ impl Registers {
|
|||
|
||||
pub fn tick_cpu(state: &mut Gameboy) {
|
||||
state.registers.mem_op_happened = false;
|
||||
// TODO: Interrupts
|
||||
|
||||
let opcode = match state.registers.current_opcode {
|
||||
Some(opcode) => opcode,
|
||||
None => match state.registers.mem_read_hold.take() {
|
||||
Some(opcode) => {
|
||||
log::debug!("Executing instruction {:#X}", opcode);
|
||||
state.registers.current_opcode = Some(opcode);
|
||||
opcode
|
||||
// TODO: Interrupts
|
||||
if state.registers.cycle == 0 && state.interrupts.ime {
|
||||
if state.interrupts.read_ie_vblank() && state.interrupts.read_if_vblank() {
|
||||
state.registers.in_interrupt_vector = Some(0);
|
||||
state.interrupts.ime = false;
|
||||
state.interrupts.write_if_vblank(false);
|
||||
} else if state.interrupts.read_ie_lcd_stat() && state.interrupts.read_if_lcd_stat() {
|
||||
state.registers.in_interrupt_vector = Some(1);
|
||||
state.interrupts.ime = false;
|
||||
state.interrupts.write_if_lcd_stat(false);
|
||||
} else if state.interrupts.read_ie_timer() && state.interrupts.read_if_timer() {
|
||||
state.registers.in_interrupt_vector = Some(2);
|
||||
state.interrupts.ime = false;
|
||||
state.interrupts.write_if_timer(false);
|
||||
} else if state.interrupts.read_ie_serial() && state.interrupts.read_if_serial() {
|
||||
state.registers.in_interrupt_vector = Some(3);
|
||||
state.interrupts.ime = false;
|
||||
state.interrupts.write_if_serial(false);
|
||||
} else if state.interrupts.read_ie_joypad() && state.interrupts.read_if_joypad() {
|
||||
state.registers.in_interrupt_vector = Some(4);
|
||||
state.interrupts.ime = false;
|
||||
state.interrupts.write_if_joypad(false);
|
||||
}
|
||||
}
|
||||
|
||||
let result = if let Some(idx) = state.registers.in_interrupt_vector {
|
||||
match state.registers.cycle {
|
||||
0 => {
|
||||
// Invalidate prefetch if present
|
||||
state.registers.mem_read_hold = None;
|
||||
CycleResult::NeedsMore
|
||||
}
|
||||
None => {
|
||||
state.cpu_read_u8(state.registers.pc);
|
||||
return;
|
||||
1 => CycleResult::NeedsMore,
|
||||
2 => {
|
||||
state.cpu_push_stack((state.registers.pc.overflowing_add(3).0 >> 8) as u8);
|
||||
CycleResult::NeedsMore
|
||||
}
|
||||
},
|
||||
3 => {
|
||||
state.cpu_push_stack(state.registers.pc.overflowing_add(3).0 as u8);
|
||||
CycleResult::NeedsMore
|
||||
}
|
||||
4 => {
|
||||
state.registers.pc = match idx {
|
||||
0 => 0x40,
|
||||
1 => 0x48,
|
||||
2 => 0x50,
|
||||
3 => 0x58,
|
||||
4 => 0x60,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
state.registers.in_interrupt_vector = None;
|
||||
state.registers.opcode_bytecount = Some(0);
|
||||
log::info!("Triggering interrupt to {:#X}", state.registers.pc);
|
||||
CycleResult::Finished
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
let opcode = match state.registers.current_opcode {
|
||||
Some(opcode) => opcode,
|
||||
None => match state.registers.mem_read_hold.take() {
|
||||
Some(opcode) => {
|
||||
log::debug!("Executing instruction {:#X}", opcode);
|
||||
state.registers.current_opcode = Some(opcode);
|
||||
opcode
|
||||
}
|
||||
None => {
|
||||
state.cpu_read_u8(state.registers.pc);
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let result: CycleResult = match opcode {
|
||||
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,
|
||||
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,
|
||||
0x13 => alu::inc_de,
|
||||
0x14 => alu::inc_d,
|
||||
0x15 => alu::dec_d,
|
||||
0x16 => load_store_move::ld_d_imm_u8,
|
||||
0x17 => alu::rla,
|
||||
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,
|
||||
0x23 => alu::inc_hl,
|
||||
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,
|
||||
0x33 => alu::inc_sp,
|
||||
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,
|
||||
0x42 => load_store_move::ld_b_d,
|
||||
0x43 => load_store_move::ld_b_e,
|
||||
0x44 => load_store_move::ld_b_h,
|
||||
0x45 => load_store_move::ld_b_l,
|
||||
0x46 => load_store_move::ld_b_deref_hl,
|
||||
0x47 => load_store_move::ld_b_a,
|
||||
0x48 => load_store_move::ld_c_b,
|
||||
0x49 => load_store_move::ld_c_c,
|
||||
0x4a => load_store_move::ld_c_d,
|
||||
0x4b => load_store_move::ld_c_e,
|
||||
0x4c => load_store_move::ld_c_h,
|
||||
0x4d => load_store_move::ld_c_l,
|
||||
0x4e => load_store_move::ld_c_deref_hl,
|
||||
0x4f => load_store_move::ld_c_a,
|
||||
0x50 => load_store_move::ld_d_b,
|
||||
0x51 => load_store_move::ld_d_c,
|
||||
0x52 => load_store_move::ld_d_d,
|
||||
0x53 => load_store_move::ld_d_e,
|
||||
0x54 => load_store_move::ld_d_h,
|
||||
0x55 => load_store_move::ld_d_l,
|
||||
0x56 => load_store_move::ld_d_deref_hl,
|
||||
0x57 => load_store_move::ld_d_a,
|
||||
0x58 => load_store_move::ld_e_b,
|
||||
0x59 => load_store_move::ld_e_c,
|
||||
0x5a => load_store_move::ld_e_d,
|
||||
0x5b => load_store_move::ld_e_e,
|
||||
0x5c => load_store_move::ld_e_h,
|
||||
0x5d => load_store_move::ld_e_l,
|
||||
0x5e => load_store_move::ld_e_deref_hl,
|
||||
0x5f => load_store_move::ld_e_a,
|
||||
0x60 => load_store_move::ld_h_b,
|
||||
0x61 => load_store_move::ld_h_c,
|
||||
0x62 => load_store_move::ld_h_d,
|
||||
0x63 => load_store_move::ld_h_e,
|
||||
0x64 => load_store_move::ld_h_h,
|
||||
0x65 => load_store_move::ld_h_l,
|
||||
0x66 => load_store_move::ld_h_deref_hl,
|
||||
0x67 => load_store_move::ld_h_a,
|
||||
0x68 => load_store_move::ld_l_b,
|
||||
0x69 => load_store_move::ld_l_c,
|
||||
0x6a => load_store_move::ld_l_d,
|
||||
0x6b => load_store_move::ld_l_e,
|
||||
0x6c => load_store_move::ld_l_h,
|
||||
0x6d => load_store_move::ld_l_l,
|
||||
0x6e => load_store_move::ld_l_deref_hl,
|
||||
0x6f => load_store_move::ld_l_a,
|
||||
0x70 => load_store_move::ld_deref_hl_b,
|
||||
0x71 => load_store_move::ld_deref_hl_c,
|
||||
0x72 => load_store_move::ld_deref_hl_d,
|
||||
0x73 => load_store_move::ld_deref_hl_e,
|
||||
0x74 => load_store_move::ld_deref_hl_h,
|
||||
0x75 => load_store_move::ld_deref_hl_l,
|
||||
0x77 => load_store_move::ld_deref_hl_a,
|
||||
0x78 => load_store_move::ld_a_b,
|
||||
0x79 => load_store_move::ld_a_c,
|
||||
0x7a => load_store_move::ld_a_d,
|
||||
0x7b => load_store_move::ld_a_e,
|
||||
0x7c => load_store_move::ld_a_h,
|
||||
0x7d => load_store_move::ld_a_l,
|
||||
0x7e => load_store_move::ld_a_deref_hl,
|
||||
0x7f => load_store_move::ld_a_a,
|
||||
0x80 => alu::add_a_b,
|
||||
0x81 => alu::add_a_c,
|
||||
0x82 => alu::add_a_d,
|
||||
0x83 => alu::add_a_e,
|
||||
0x84 => alu::add_a_h,
|
||||
0x85 => alu::add_a_l,
|
||||
0x86 => alu::add_a_deref_hl,
|
||||
0x87 => alu::add_a_a,
|
||||
0x88 => alu::adc_a_b,
|
||||
0x89 => alu::adc_a_c,
|
||||
0x8A => alu::adc_a_d,
|
||||
0x8B => alu::adc_a_e,
|
||||
0x8C => alu::adc_a_h,
|
||||
0x8D => alu::adc_a_l,
|
||||
0x8E => alu::adc_a_deref_hl,
|
||||
0x8F => alu::adc_a_a,
|
||||
0x90 => alu::sub_a_b,
|
||||
0x91 => alu::sub_a_c,
|
||||
0x92 => alu::sub_a_d,
|
||||
0x93 => alu::sub_a_e,
|
||||
0x94 => alu::sub_a_h,
|
||||
0x95 => alu::sub_a_l,
|
||||
0x96 => alu::sub_a_deref_hl,
|
||||
0x97 => alu::sub_a_a,
|
||||
0x98 => alu::sbc_a_b,
|
||||
0x99 => alu::sbc_a_c,
|
||||
0x9A => alu::sbc_a_d,
|
||||
0x9B => alu::sbc_a_e,
|
||||
0x9C => alu::sbc_a_h,
|
||||
0x9D => alu::sbc_a_l,
|
||||
0x9E => alu::sbc_a_deref_hl,
|
||||
0x9F => alu::sbc_a_a,
|
||||
0xA8 => alu::xor_a_b,
|
||||
0xA9 => alu::xor_a_c,
|
||||
0xAA => alu::xor_a_d,
|
||||
0xAB => alu::xor_a_e,
|
||||
0xAC => alu::xor_a_h,
|
||||
0xAD => alu::xor_a_l,
|
||||
0xAE => alu::xor_a_deref_hl,
|
||||
0xAF => alu::xor_a_a,
|
||||
0xB8 => alu::cp_a_b,
|
||||
0xB9 => alu::cp_a_c,
|
||||
0xBA => alu::cp_a_d,
|
||||
0xBB => alu::cp_a_e,
|
||||
0xBC => alu::cp_a_h,
|
||||
0xBD => alu::cp_a_l,
|
||||
0xBE => alu::cp_a_deref_hl,
|
||||
0xBF => alu::cp_a_a,
|
||||
0xC0 => flow::ret_nz,
|
||||
0xC1 => load_store_move::pop_bc,
|
||||
0xC2 => flow::jp_nz_u16,
|
||||
0xC3 => flow::jp_u16,
|
||||
0xC4 => flow::call_nz_u16,
|
||||
0xC5 => load_store_move::push_bc,
|
||||
0xC6 => alu::add_a_imm_u8,
|
||||
0xC8 => flow::ret_z,
|
||||
0xC9 => flow::ret,
|
||||
0xCA => flow::jp_z_u16,
|
||||
0xCB => prefixed::prefixed_handler,
|
||||
0xCC => flow::call_z_u16,
|
||||
0xCD => flow::call_u16,
|
||||
0xCE => alu::adc_a_imm_u8,
|
||||
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,
|
||||
0xD8 => flow::ret_c,
|
||||
0xD9 => flow::reti,
|
||||
0xDA => flow::jp_c_u16,
|
||||
0xDC => flow::call_c_u16,
|
||||
0xDE => alu::sbc_a_imm_u8,
|
||||
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,
|
||||
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,
|
||||
0xF1 => load_store_move::pop_af,
|
||||
0xF2 => load_store_move::ldh_a_deref_c,
|
||||
0xF5 => load_store_move::push_af,
|
||||
0xF9 => load_store_move::ld_sp_hl,
|
||||
0xFA => load_store_move::ld_a_deref_imm_u16,
|
||||
0xFE => alu::cp_a_imm_u8,
|
||||
unknown => {
|
||||
panic!("Unrecognized opcode: {:#X}\nRegisters: {:#x?}", unknown, state.registers)
|
||||
}
|
||||
}(state);
|
||||
|
||||
if result == CycleResult::Finished && state.registers.opcode_bytecount.is_none() {
|
||||
panic!("Forgot to set opcode len for {:#X}", opcode)
|
||||
}
|
||||
|
||||
result
|
||||
};
|
||||
|
||||
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,
|
||||
0x17 => alu::rla,
|
||||
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,
|
||||
0x42 => load_store_move::ld_b_d,
|
||||
0x43 => load_store_move::ld_b_e,
|
||||
0x44 => load_store_move::ld_b_h,
|
||||
0x45 => load_store_move::ld_b_l,
|
||||
0x46 => load_store_move::ld_b_deref_hl,
|
||||
0x47 => load_store_move::ld_b_a,
|
||||
0x48 => load_store_move::ld_c_b,
|
||||
0x49 => load_store_move::ld_c_c,
|
||||
0x4a => load_store_move::ld_c_d,
|
||||
0x4b => load_store_move::ld_c_e,
|
||||
0x4c => load_store_move::ld_c_h,
|
||||
0x4d => load_store_move::ld_c_l,
|
||||
0x4e => load_store_move::ld_c_deref_hl,
|
||||
0x4f => load_store_move::ld_c_a,
|
||||
0x50 => load_store_move::ld_d_b,
|
||||
0x51 => load_store_move::ld_d_c,
|
||||
0x52 => load_store_move::ld_d_d,
|
||||
0x53 => load_store_move::ld_d_e,
|
||||
0x54 => load_store_move::ld_d_h,
|
||||
0x55 => load_store_move::ld_d_l,
|
||||
0x56 => load_store_move::ld_d_deref_hl,
|
||||
0x57 => load_store_move::ld_d_a,
|
||||
0x58 => load_store_move::ld_e_b,
|
||||
0x59 => load_store_move::ld_e_c,
|
||||
0x5a => load_store_move::ld_e_d,
|
||||
0x5b => load_store_move::ld_e_e,
|
||||
0x5c => load_store_move::ld_e_h,
|
||||
0x5d => load_store_move::ld_e_l,
|
||||
0x5e => load_store_move::ld_e_deref_hl,
|
||||
0x5f => load_store_move::ld_e_a,
|
||||
0x60 => load_store_move::ld_h_b,
|
||||
0x61 => load_store_move::ld_h_c,
|
||||
0x62 => load_store_move::ld_h_d,
|
||||
0x63 => load_store_move::ld_h_e,
|
||||
0x64 => load_store_move::ld_h_h,
|
||||
0x65 => load_store_move::ld_h_l,
|
||||
0x66 => load_store_move::ld_h_deref_hl,
|
||||
0x67 => load_store_move::ld_h_a,
|
||||
0x68 => load_store_move::ld_l_b,
|
||||
0x69 => load_store_move::ld_l_c,
|
||||
0x6a => load_store_move::ld_l_d,
|
||||
0x6b => load_store_move::ld_l_e,
|
||||
0x6c => load_store_move::ld_l_h,
|
||||
0x6d => load_store_move::ld_l_l,
|
||||
0x6e => load_store_move::ld_l_deref_hl,
|
||||
0x6f => load_store_move::ld_l_a,
|
||||
0x70 => load_store_move::ld_deref_hl_b,
|
||||
0x71 => load_store_move::ld_deref_hl_c,
|
||||
0x72 => load_store_move::ld_deref_hl_d,
|
||||
0x73 => load_store_move::ld_deref_hl_e,
|
||||
0x74 => load_store_move::ld_deref_hl_h,
|
||||
0x75 => load_store_move::ld_deref_hl_l,
|
||||
0x77 => load_store_move::ld_deref_hl_a,
|
||||
0x78 => load_store_move::ld_a_b,
|
||||
0x79 => load_store_move::ld_a_c,
|
||||
0x7a => load_store_move::ld_a_d,
|
||||
0x7b => load_store_move::ld_a_e,
|
||||
0x7c => load_store_move::ld_a_h,
|
||||
0x7d => load_store_move::ld_a_l,
|
||||
0x7e => load_store_move::ld_a_deref_hl,
|
||||
0x7f => load_store_move::ld_a_a,
|
||||
0x90 => alu::sub_a_b,
|
||||
0x91 => alu::sub_a_c,
|
||||
0x92 => alu::sub_a_d,
|
||||
0x93 => alu::sub_a_e,
|
||||
0x94 => alu::sub_a_h,
|
||||
0x95 => alu::sub_a_l,
|
||||
0x96 => alu::sub_a_deref_hl,
|
||||
0x97 => alu::sub_a_a,
|
||||
0x98 => alu::sbc_a_b,
|
||||
0x99 => alu::sbc_a_c,
|
||||
0x9A => alu::sbc_a_d,
|
||||
0x9B => alu::sbc_a_e,
|
||||
0x9C => alu::sbc_a_h,
|
||||
0x9D => alu::sbc_a_l,
|
||||
0x9E => alu::sbc_a_deref_hl,
|
||||
0x9F => alu::sbc_a_a,
|
||||
0xA8 => alu::xor_a_b,
|
||||
0xA9 => alu::xor_a_c,
|
||||
0xAA => alu::xor_a_d,
|
||||
0xAB => alu::xor_a_e,
|
||||
0xAC => alu::xor_a_h,
|
||||
0xAD => alu::xor_a_l,
|
||||
0xAE => alu::xor_a_deref_hl,
|
||||
0xAF => alu::xor_a_a,
|
||||
0xC0 => flow::ret_nz,
|
||||
0xC1 => load_store_move::pop_bc,
|
||||
0xC2 => flow::jp_nz_u16,
|
||||
0xC3 => flow::jp_u16,
|
||||
0xC4 => flow::call_nz_u16,
|
||||
0xC5 => load_store_move::push_bc,
|
||||
0xC8 => flow::ret_z,
|
||||
0xC9 => flow::ret,
|
||||
0xCA => flow::jp_z_u16,
|
||||
0xCB => prefixed::prefixed_handler,
|
||||
0xCC => flow::call_z_u16,
|
||||
0xCD => flow::call_u16,
|
||||
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,
|
||||
0xD8 => flow::ret_c,
|
||||
0xD9 => flow::reti,
|
||||
0xDA => flow::jp_c_u16,
|
||||
0xDC => flow::call_c_u16,
|
||||
0xDE => alu::sbc_a_imm_u8,
|
||||
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,
|
||||
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,
|
||||
0xF1 => load_store_move::pop_af,
|
||||
0xF2 => load_store_move::ldh_a_deref_c,
|
||||
0xF5 => load_store_move::push_af,
|
||||
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);
|
||||
|
||||
if instruction_result == CycleResult::Finished {
|
||||
if result == CycleResult::Finished {
|
||||
match state.registers.opcode_bytecount {
|
||||
Some(len) => state.registers.pc += len as u16,
|
||||
None => panic!("Forgot to set opcode len for {:#X}", opcode),
|
||||
None => panic!("Forgot to set opcode len"),
|
||||
}
|
||||
|
||||
if !state.registers.mem_op_happened {
|
||||
|
|
|
@ -202,6 +202,162 @@ pub fn sbc_a_imm_u8(state: &mut Gameboy) -> CycleResult {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! define_add_reg {
|
||||
($reg:ident) => {
|
||||
paste::paste! {
|
||||
pub fn [<add_a_ $reg>](state: &mut Gameboy) -> CycleResult {
|
||||
match state.registers.cycle {
|
||||
0 => {
|
||||
let CarryResult { result, half_carry, carry } = add(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(half_carry);
|
||||
state.registers.set_carry(carry);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_add_reg!(a);
|
||||
define_add_reg!(b);
|
||||
define_add_reg!(c);
|
||||
define_add_reg!(d);
|
||||
define_add_reg!(e);
|
||||
define_add_reg!(h);
|
||||
define_add_reg!(l);
|
||||
|
||||
pub fn add_a_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, carry } =
|
||||
add(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(half_carry);
|
||||
state.registers.set_carry(carry);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_a_imm_u8(state: &mut Gameboy) -> CycleResult {
|
||||
match state.registers.cycle {
|
||||
0 => {
|
||||
state.cpu_read_u8(state.registers.pc + 1);
|
||||
CycleResult::NeedsMore
|
||||
}
|
||||
1 => {
|
||||
let CarryResult { result, half_carry, carry } =
|
||||
add(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(half_carry);
|
||||
state.registers.set_carry(carry);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_adc_reg {
|
||||
($reg:ident) => {
|
||||
paste::paste! {
|
||||
pub fn [<adc_a_ $reg>](state: &mut Gameboy) -> CycleResult {
|
||||
match state.registers.cycle {
|
||||
0 => {
|
||||
let CarryResult { result, half_carry, carry } = add_with_carry(state.registers.a, state.registers.$reg, state.registers.get_carry());
|
||||
|
||||
state.registers.a = result;
|
||||
state.registers.set_zero(result == 0);
|
||||
state.registers.set_subtract(false);
|
||||
state.registers.set_half_carry(half_carry);
|
||||
state.registers.set_carry(carry);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_adc_reg!(a);
|
||||
define_adc_reg!(b);
|
||||
define_adc_reg!(c);
|
||||
define_adc_reg!(d);
|
||||
define_adc_reg!(e);
|
||||
define_adc_reg!(h);
|
||||
define_adc_reg!(l);
|
||||
|
||||
pub fn adc_a_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, carry } = add_with_carry(
|
||||
state.registers.a,
|
||||
state.registers.take_mem(),
|
||||
state.registers.get_carry(),
|
||||
);
|
||||
|
||||
state.registers.a = result;
|
||||
state.registers.set_zero(result == 0);
|
||||
state.registers.set_subtract(false);
|
||||
state.registers.set_half_carry(half_carry);
|
||||
state.registers.set_carry(carry);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adc_a_imm_u8(state: &mut Gameboy) -> CycleResult {
|
||||
match state.registers.cycle {
|
||||
0 => {
|
||||
state.cpu_read_u8(state.registers.pc + 1);
|
||||
CycleResult::NeedsMore
|
||||
}
|
||||
1 => {
|
||||
let CarryResult { result, half_carry, carry } = add_with_carry(
|
||||
state.registers.a,
|
||||
state.registers.take_mem(),
|
||||
state.registers.get_carry(),
|
||||
);
|
||||
|
||||
state.registers.a = result;
|
||||
state.registers.set_zero(result == 0);
|
||||
state.registers.set_subtract(false);
|
||||
state.registers.set_half_carry(half_carry);
|
||||
state.registers.set_carry(carry);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_sub_reg {
|
||||
($reg:ident) => {
|
||||
paste::paste! {
|
||||
|
@ -295,7 +451,7 @@ macro_rules! define_inc_reg {
|
|||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
},
|
||||
_ => unimplemented!(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -327,7 +483,7 @@ pub fn inc_deref_hl(state: &mut Gameboy) -> CycleResult {
|
|||
CycleResult::NeedsMore
|
||||
}
|
||||
2 => CycleResult::Finished,
|
||||
_ => unimplemented!(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -349,7 +505,7 @@ macro_rules! define_dec_reg {
|
|||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
},
|
||||
_ => unimplemented!(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -381,7 +537,7 @@ pub fn dec_deref_hl(state: &mut Gameboy) -> CycleResult {
|
|||
CycleResult::NeedsMore
|
||||
}
|
||||
2 => CycleResult::Finished,
|
||||
_ => unimplemented!(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,3 +562,115 @@ pub fn rla(state: &mut Gameboy) -> CycleResult {
|
|||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_inc_u16_reg {
|
||||
($lreg:ident, $rreg:ident) => {
|
||||
paste::paste! {
|
||||
pub fn [<inc_ $lreg $rreg>](state: &mut Gameboy) -> CycleResult {
|
||||
match state.registers.cycle {
|
||||
0 => {
|
||||
let (res, carry) = state.registers.$rreg.overflowing_add(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_add(1);
|
||||
state.registers.$lreg = res;
|
||||
}
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_inc_u16_reg!(b, c);
|
||||
define_inc_u16_reg!(d, e);
|
||||
define_inc_u16_reg!(h, l);
|
||||
|
||||
pub fn inc_sp(state: &mut Gameboy) -> CycleResult {
|
||||
match state.registers.cycle {
|
||||
0 => CycleResult::NeedsMore,
|
||||
1 => {
|
||||
let (res, _) = state.registers.sp.overflowing_add(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! {
|
||||
pub fn [<cp_ $lreg _ $rreg>](state: &mut Gameboy) -> CycleResult {
|
||||
match state.registers.cycle {
|
||||
0 => {
|
||||
let CarryResult { result, half_carry, carry } = sub(state.registers.$lreg, state.registers.$rreg);
|
||||
state.registers.set_zero(result == 0);
|
||||
state.registers.set_subtract(true);
|
||||
state.registers.set_half_carry(half_carry);
|
||||
state.registers.set_carry(carry);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_cp_reg_reg!(a, b);
|
||||
define_cp_reg_reg!(a, c);
|
||||
define_cp_reg_reg!(a, d);
|
||||
define_cp_reg_reg!(a, e);
|
||||
define_cp_reg_reg!(a, h);
|
||||
define_cp_reg_reg!(a, l);
|
||||
define_cp_reg_reg!(a, a);
|
||||
|
||||
pub fn cp_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 CarryResult { result, half_carry, carry } =
|
||||
sub(state.registers.a, state.registers.take_mem());
|
||||
state.registers.set_zero(result == 0);
|
||||
state.registers.set_subtract(true);
|
||||
state.registers.set_half_carry(half_carry);
|
||||
state.registers.set_carry(carry);
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cp_a_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, carry } =
|
||||
sub(state.registers.a, state.registers.take_mem());
|
||||
state.registers.set_zero(result == 0);
|
||||
state.registers.set_subtract(true);
|
||||
state.registers.set_half_carry(half_carry);
|
||||
state.registers.set_carry(carry);
|
||||
state.registers.opcode_bytecount = Some(1);
|
||||
CycleResult::Finished
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,14 +48,14 @@ pub fn jr_nc_i8(state: &mut Gameboy) -> CycleResult {
|
|||
}
|
||||
}
|
||||
2 => {
|
||||
let relative = state.registers.take_mem();
|
||||
const TC_BITFLAG: u8 = 1 << 7;
|
||||
if relative & TC_BITFLAG == 0 {
|
||||
let relative = state.registers.take_mem() as i8;
|
||||
|
||||
if relative >= 0 {
|
||||
state.registers.pc = state.registers.pc.overflowing_add(relative as u16).0;
|
||||
} else {
|
||||
state.registers.pc =
|
||||
state.registers.pc.overflowing_sub((relative & !TC_BITFLAG) as u16).0;
|
||||
state.registers.pc = state.registers.pc.overflowing_sub(relative.abs() as u16).0;
|
||||
}
|
||||
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
|
@ -79,14 +79,14 @@ pub fn jr_z_i8(state: &mut Gameboy) -> CycleResult {
|
|||
}
|
||||
}
|
||||
2 => {
|
||||
let relative = state.registers.take_mem();
|
||||
const TC_BITFLAG: u8 = 1 << 7;
|
||||
if relative & TC_BITFLAG == 0 {
|
||||
let relative = state.registers.take_mem() as i8;
|
||||
|
||||
if relative >= 0 {
|
||||
state.registers.pc = state.registers.pc.overflowing_add(relative as u16).0;
|
||||
} else {
|
||||
state.registers.pc =
|
||||
state.registers.pc.overflowing_sub((relative & !TC_BITFLAG) as u16).0;
|
||||
state.registers.pc = state.registers.pc.overflowing_sub(relative.abs() as u16).0;
|
||||
}
|
||||
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
|
@ -110,14 +110,14 @@ pub fn jr_c_i8(state: &mut Gameboy) -> CycleResult {
|
|||
}
|
||||
}
|
||||
2 => {
|
||||
let relative = state.registers.take_mem();
|
||||
const TC_BITFLAG: u8 = 1 << 7;
|
||||
if relative & TC_BITFLAG == 0 {
|
||||
let relative = state.registers.take_mem() as i8;
|
||||
|
||||
if relative >= 0 {
|
||||
state.registers.pc = state.registers.pc.overflowing_add(relative as u16).0;
|
||||
} else {
|
||||
state.registers.pc =
|
||||
state.registers.pc.overflowing_sub((relative & !TC_BITFLAG) as u16).0;
|
||||
state.registers.pc = state.registers.pc.overflowing_sub(relative.abs() as u16).0;
|
||||
}
|
||||
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
|
@ -133,14 +133,14 @@ pub fn jr_i8(state: &mut Gameboy) -> CycleResult {
|
|||
}
|
||||
1 => CycleResult::NeedsMore,
|
||||
2 => {
|
||||
let relative = state.registers.take_mem();
|
||||
const TC_BITFLAG: u8 = 1 << 7;
|
||||
if relative & TC_BITFLAG == 0 {
|
||||
let relative = state.registers.take_mem() as i8;
|
||||
|
||||
if relative >= 0 {
|
||||
state.registers.pc = state.registers.pc.overflowing_add(relative as u16).0;
|
||||
} else {
|
||||
state.registers.pc =
|
||||
state.registers.pc.overflowing_sub((relative & !TC_BITFLAG) as u16).0;
|
||||
state.registers.pc = state.registers.pc.overflowing_sub(relative.abs() as u16).0;
|
||||
}
|
||||
|
||||
state.registers.opcode_bytecount = Some(2);
|
||||
CycleResult::Finished
|
||||
}
|
||||
|
|
|
@ -11,6 +11,24 @@ pub struct NoMBC {
|
|||
ram: Option<[u8; 0x2000]>,
|
||||
}
|
||||
|
||||
impl NoMBC {
|
||||
pub fn new(data: Vec<u8>) -> Self {
|
||||
let mut out = Self { rom: [0; 0x8000], ram: None };
|
||||
|
||||
match data[0x149] {
|
||||
0 => {}
|
||||
2 => out.ram = Some([0; 0x2000]),
|
||||
other => unreachable!("RAM Type of {} on NoMBC", other),
|
||||
}
|
||||
|
||||
for (idx, data) in data.iter().enumerate() {
|
||||
out.rom[idx] = *data;
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl Mapper for NoMBC {
|
||||
fn read_rom_u8(&self, address: u16) -> u8 {
|
||||
self.rom[address as usize]
|
||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -84,6 +84,17 @@ pub fn run_gameboy(
|
|||
}
|
||||
|
||||
let mut gameboy = Gameboy::new(bootrom.as_slice().try_into().unwrap());
|
||||
|
||||
if let Some(rom) = args.rom {
|
||||
if !rom.is_file() {
|
||||
return Err(DmgError::GameNotFound);
|
||||
}
|
||||
|
||||
let rom = std::fs::read(rom.as_path())?;
|
||||
|
||||
gameboy.load_cartridge(rom)
|
||||
}
|
||||
|
||||
let mut last = chrono::Local::now();
|
||||
let mut paused = false;
|
||||
let mut frame_counter = 0;
|
||||
|
@ -112,6 +123,7 @@ pub fn run_gameboy(
|
|||
if frame_counter == 60 {
|
||||
let now = chrono::Local::now();
|
||||
log::info!("Rendered 60 frames in {}", now - last);
|
||||
std::thread::sleep(std::time::Duration::from_millis(900));
|
||||
last = now;
|
||||
frame_counter = 0;
|
||||
|
||||
|
|
Loading…
Reference in a new issue