feat: fix a few instructions, add halt

This commit is contained in:
EliseZeroTwo 2021-12-19 12:27:41 +01:00
parent 96bc5e117d
commit 00850645cd
No known key found for this signature in database
GPG key ID: E6D56A6F7B7991DE
16 changed files with 766 additions and 60 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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;
}

View file

@ -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

View file

@ -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 => {

View file

@ -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! {

View file

@ -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
}
});

View file

@ -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);

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -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);

View 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 => {}
}
}
}

View file

@ -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;

View file

@ -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
}
}

View file

@ -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!(),
}
}

View file

@ -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