feat: add some more opcodes

This commit is contained in:
EliseZeroTwo 2021-11-27 12:24:16 +01:00
parent 4abfe1ae90
commit 84f13b263b
No known key found for this signature in database
GPG key ID: E6D56A6F7B7991DE
6 changed files with 670 additions and 7 deletions

3
.gitignore vendored
View file

@ -1,3 +1,4 @@
/target /target
/roms/ /roms/
config.toml config.toml
/.vscode/

1
rust-toolchain Normal file
View file

@ -0,0 +1 @@
stable

View file

@ -1,3 +1,4 @@
mod cpu;
mod interrupts; mod interrupts;
mod joypad; mod joypad;
mod mapper; mod mapper;
@ -12,6 +13,8 @@ use memory::Memory;
use ppu::Ppu; use ppu::Ppu;
use timer::Timer; use timer::Timer;
use self::cpu::Registers;
pub struct DmaState { pub struct DmaState {
pub base: u8, pub base: u8,
pub remaining_cycles: u8, pub remaining_cycles: u8,
@ -36,6 +39,7 @@ pub struct Gameboy {
cartridge: Option<Box<dyn Mapper>>, cartridge: Option<Box<dyn Mapper>>,
interrupts: Interrupts, interrupts: Interrupts,
timer: Timer, timer: Timer,
pub registers: Registers,
pub joypad: Joypad, pub joypad: Joypad,
pub dma: DmaState, pub dma: DmaState,
} }
@ -50,6 +54,7 @@ impl Gameboy {
joypad: Joypad::new(), joypad: Joypad::new(),
dma: DmaState::new(), dma: DmaState::new(),
ppu: Ppu::new(), ppu: Ppu::new(),
registers: Registers::default(),
} }
} }
@ -58,14 +63,12 @@ impl Gameboy {
self.interrupts.write_if_timer(true); self.interrupts.write_if_timer(true);
} }
self.tick_cpu(); cpu::tick_cpu(self);
let redraw_requested = self.ppu.tick(&mut self.interrupts); let redraw_requested = self.ppu.tick(&mut self.interrupts);
self.tick_dma(); self.tick_dma();
redraw_requested redraw_requested
} }
fn tick_cpu(&mut self) {}
fn tick_dma(&mut self) { fn tick_dma(&mut self) {
if self.dma.remaining_delay > 0 { if self.dma.remaining_delay > 0 {
self.dma.remaining_delay -= 1; self.dma.remaining_delay -= 1;
@ -166,8 +169,11 @@ impl Gameboy {
} }
} }
pub fn cpu_read_u8(&self, address: u16) -> u8 { pub fn cpu_read_u8(&mut self, address: u16) {
if self.dma.remaining_cycles == 0 { assert!(!self.registers.mem_op_happened);
assert!(self.registers.mem_read_hold.is_none());
self.registers.mem_op_happened = true;
self.registers.mem_read_hold = Some(if self.dma.remaining_cycles == 0 {
match address { match address {
0..=0xFF if !self.memory.bootrom_disabled => self.memory.bootrom[address as usize], 0..=0xFF if !self.memory.bootrom_disabled => self.memory.bootrom[address as usize],
0..=0x7FFF => match self.cartridge.as_ref() { 0..=0x7FFF => match self.cartridge.as_ref() {
@ -194,10 +200,12 @@ impl Gameboy {
0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80], 0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80],
0xFFFF => self.interrupts.interrupt_enable, 0xFFFF => self.interrupts.interrupt_enable,
} }
} })
} }
pub fn cpu_write_u8(&mut self, address: u16, value: u8) { pub fn cpu_write_u8(&mut self, address: u16, value: u8) {
assert!(!self.registers.mem_op_happened);
self.registers.mem_op_happened = true;
if self.dma.remaining_cycles == 0 { if self.dma.remaining_cycles == 0 {
match address { match address {
0..=0xFF if !self.memory.bootrom_disabled => {} 0..=0xFF if !self.memory.bootrom_disabled => {}

226
src/gameboy/cpu.rs Normal file
View file

@ -0,0 +1,226 @@
mod alu;
mod load_store_move;
use super::Gameboy;
macro_rules! define_register {
($lident:ident, $rident:ident) => {
paste::paste! {
pub fn [<get_ $lident $rident>](&self) -> u16 {
(self.$lident as u16) << 8 | self.$rident as u16
}
pub fn [<set_ $lident $rident>](&mut self, value: u16) {
self.$lident = (value >> 8) as u8;
self.$rident = value as u8;
}
}
};
}
macro_rules! define_flag {
($flag:ident, $bit:literal) => {
paste::paste! {
pub fn [<get_ $flag>](&self) -> bool {
self.f >> $bit == 1
}
pub fn [<set_ $flag>](&mut self, value: bool) {
self.f &= !(1 << $bit);
self.f |= (value as u8) << $bit;
}
}
};
}
#[derive(Debug, PartialEq, Eq)]
pub enum CycleResult {
NeedsMore,
Finished,
}
#[derive(Debug, Default)]
pub struct Registers {
pub a: u8,
pub f: u8,
pub b: u8,
pub c: u8,
pub d: u8,
pub e: u8,
pub h: u8,
pub l: u8,
pub sp: u16,
pub pc: u16,
// Not actual registers
pub cycle: u8,
pub hold: Option<u16>,
pub opcode_len: Option<u8>,
pub current_opcode: Option<u8>,
pub mem_read_hold: Option<u8>,
pub mem_op_happened: bool,
}
impl Registers {
define_register!(a, f);
define_register!(b, c);
define_register!(d, e);
define_register!(h, l);
/// This is just a helper function for macros utilizing ident pasting
pub fn get_sp(&self) -> u16 {
self.sp
}
/// This is just a helper function for macros utilizing ident pasting
pub fn set_sp(&mut self, value: u16) {
self.sp = value;
}
define_flag!(zero, 7);
define_flag!(subtract, 7);
define_flag!(half_carry, 7);
define_flag!(carry, 7);
pub fn take_mem(&mut self) -> u8 {
self.mem_read_hold.take().unwrap()
}
pub fn take_hold(&mut self) -> u16 {
self.hold.take().unwrap()
}
pub fn set_hold(&mut self, value: u16) {
assert!(self.hold.is_none());
self.hold = Some(value);
}
}
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
}
None => {
state.cpu_read_u8(state.registers.pc);
return;
}
},
};
let instruction_result: CycleResult = match opcode {
0x01 => load_store_move::ld_bc_imm_u16,
0x08 => load_store_move::ld_deref_imm_u16_sp,
0x0a => load_store_move::ld_a_deref_bc,
0x11 => load_store_move::ld_de_imm_u16,
0x1a => load_store_move::ld_a_deref_de,
0x21 => load_store_move::ld_hl_imm_u16,
0x22 => load_store_move::ld_hl_plus_a,
0x2a => load_store_move::ld_a_hl_plus,
0x32 => load_store_move::ld_hl_minus_a,
0x3a => load_store_move::ld_a_hl_minus,
0x31 => load_store_move::ld_sp_imm_u16,
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,
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,
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,
0xDE => alu::sbc_a_imm_u8,
0xEE => alu::xor_a_imm_u8,
0xF9 => load_store_move::ld_sp_hl,
unknown => panic!("Unrecognized opcode: {:#X}\nRegisters: {:#?}", unknown, state.registers),
}(state);
if instruction_result == CycleResult::Finished {
match state.registers.opcode_len {
Some(len) => state.registers.pc += len as u16,
None => panic!("Forgot to set opcode len for {:#X}", opcode),
}
if !state.registers.mem_op_happened {
log::trace!("Memory bus clear, precaching next opcode");
state.cpu_read_u8(state.registers.pc);
}
state.registers.cycle = 0;
state.registers.current_opcode = None;
state.registers.opcode_len = None;
log::trace!("Cycle finished");
} else {
state.registers.cycle += 1;
}
}

173
src/gameboy/cpu/alu.rs Normal file
View file

@ -0,0 +1,173 @@
use super::CycleResult;
use crate::gameboy::Gameboy;
#[derive(Debug)]
pub struct CarryResult {
pub result: u8,
pub half_carry: bool,
pub carry: bool,
}
pub fn sub_with_carry(lhs: u8, rhs: u8, carry: bool) -> CarryResult {
let carry_u8 = carry as u8;
let (first_res, first_carry) = lhs.overflowing_sub(rhs);
let (result, second_carry) = first_res.overflowing_sub(carry_u8);
let carry = first_carry || second_carry;
let (first_hc_res, first_half_carry) = (lhs & 0xF).overflowing_sub(rhs & 0xF);
let (_, second_half_carry) = first_hc_res.overflowing_sub(carry_u8);
let half_carry = first_half_carry || second_half_carry;
CarryResult { result, carry, half_carry }
}
macro_rules! define_xor_reg {
($reg:ident) => {
paste::paste! {
pub fn [<xor_a_ $reg>](state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
state.registers.a ^= state.registers.$reg;
state.registers.set_zero(state.registers.a == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(false);
state.registers.opcode_len = Some(1);
CycleResult::Finished
}
_ => unreachable!(),
}
}
}
};
}
macro_rules! define_sbc_reg {
($reg:ident) => {
paste::paste! {
pub fn [<sbc_a_ $reg>](state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
let CarryResult { result, half_carry, carry } = sub_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(true);
state.registers.set_half_carry(half_carry);
state.registers.set_carry(carry);
state.registers.opcode_len = Some(1);
CycleResult::Finished
},
_ => unreachable!(),
}
}
}
};
}
define_xor_reg!(a);
define_xor_reg!(b);
define_xor_reg!(c);
define_xor_reg!(d);
define_xor_reg!(e);
define_xor_reg!(h);
define_xor_reg!(l);
pub fn xor_a_deref_hl(state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
state.cpu_read_u8(state.registers.get_hl());
CycleResult::NeedsMore
}
1 => {
state.registers.a ^= state.registers.take_mem();
state.registers.set_zero(state.registers.a == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(false);
state.registers.opcode_len = Some(1);
CycleResult::Finished
}
_ => unreachable!(),
}
}
pub fn xor_a_imm_u8(state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
state.cpu_read_u8(state.registers.pc + 1);
CycleResult::NeedsMore
}
1 => {
state.registers.a ^= state.registers.take_mem();
state.registers.set_zero(state.registers.a == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(false);
state.registers.opcode_len = Some(2);
CycleResult::Finished
}
_ => unreachable!(),
}
}
define_sbc_reg!(a);
define_sbc_reg!(b);
define_sbc_reg!(c);
define_sbc_reg!(d);
define_sbc_reg!(e);
define_sbc_reg!(h);
define_sbc_reg!(l);
pub fn sbc_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_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(true);
state.registers.set_half_carry(half_carry);
state.registers.set_carry(carry);
state.registers.opcode_len = Some(1);
CycleResult::Finished
}
_ => unreachable!(),
}
}
pub fn sbc_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 } = sub_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(true);
state.registers.set_half_carry(half_carry);
state.registers.set_carry(carry);
state.registers.opcode_len = Some(1);
CycleResult::Finished
}
_ => unreachable!(),
}
}

View file

@ -0,0 +1,254 @@
use crate::gameboy::{cpu::CycleResult, Gameboy};
macro_rules! define_ld_reg_imm_u16 {
($reg:ident) => {
paste::paste! {
pub fn [<ld_ $reg _imm_u16>](state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
state.cpu_read_u8(state.registers.pc.overflowing_add(1).0);
CycleResult::NeedsMore
},
1 => {
let mut reg = state.registers.[<get_ $reg>]();
reg &= 0xFF00;
reg |= state.registers.take_mem() as u16;
state.registers.[<set_ $reg>](reg);
state.cpu_read_u8(state.registers.pc.overflowing_add(2).0);
CycleResult::NeedsMore
},
2 => {
let mut reg = state.registers.[<get_ $reg>]();
reg &= 0xFF;
reg |= (state.registers.take_mem() as u16) << 8;
state.registers.[<set_ $reg>](reg);
state.registers.opcode_len = Some(3);
CycleResult::Finished
},
_ => unreachable!(),
}
}
}
};
}
define_ld_reg_imm_u16!(bc);
define_ld_reg_imm_u16!(de);
define_ld_reg_imm_u16!(hl);
define_ld_reg_imm_u16!(sp);
pub fn ld_sp_hl(state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
state.registers.sp &= 0xFF00;
state.registers.sp |= state.registers.l as u16;
CycleResult::NeedsMore
}
1 => {
state.registers.sp &= 0xFF;
state.registers.sp |= (state.registers.h as u16) << 8;
state.registers.opcode_len = Some(3);
CycleResult::Finished
}
_ => unreachable!(),
}
}
pub fn ld_deref_imm_u16_sp(state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
state.cpu_read_u8(state.registers.pc.overflowing_add(1).0);
CycleResult::NeedsMore
}
1 => {
let low_addr = state.registers.take_mem() as u16;
state.registers.set_hold(low_addr);
state.cpu_read_u8(state.registers.pc.overflowing_add(2).0);
CycleResult::NeedsMore
}
2 => {
let addr = ((state.registers.take_mem() as u16) << 8) | state.registers.take_hold();
state.registers.set_hold(addr);
state.cpu_write_u8(addr, state.registers.sp as u8);
CycleResult::NeedsMore
}
3 => {
let addr = state.registers.take_hold().overflowing_add(1).0;
state.cpu_write_u8(addr, (state.registers.sp >> 8) as u8);
state.registers.opcode_len = Some(3);
CycleResult::Finished
}
_ => unreachable!(),
}
}
macro_rules! define_ld_reg_reg {
($lreg:ident, $rreg:ident) => {
paste::paste! {
pub fn [<ld_ $lreg _ $rreg>](state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
let res = state.registers.$rreg;
state.registers.$lreg = res;
state.registers.opcode_len = Some(1);
CycleResult::Finished
},
_ => unreachable!(),
}
}
}
};
}
define_ld_reg_reg!(b, b);
define_ld_reg_reg!(b, c);
define_ld_reg_reg!(b, d);
define_ld_reg_reg!(b, e);
define_ld_reg_reg!(b, h);
define_ld_reg_reg!(b, l);
define_ld_reg_reg!(b, a);
define_ld_reg_reg!(c, b);
define_ld_reg_reg!(c, c);
define_ld_reg_reg!(c, d);
define_ld_reg_reg!(c, e);
define_ld_reg_reg!(c, h);
define_ld_reg_reg!(c, l);
define_ld_reg_reg!(c, a);
define_ld_reg_reg!(d, b);
define_ld_reg_reg!(d, c);
define_ld_reg_reg!(d, d);
define_ld_reg_reg!(d, e);
define_ld_reg_reg!(d, h);
define_ld_reg_reg!(d, l);
define_ld_reg_reg!(d, a);
define_ld_reg_reg!(e, b);
define_ld_reg_reg!(e, c);
define_ld_reg_reg!(e, d);
define_ld_reg_reg!(e, e);
define_ld_reg_reg!(e, h);
define_ld_reg_reg!(e, l);
define_ld_reg_reg!(e, a);
define_ld_reg_reg!(h, b);
define_ld_reg_reg!(h, c);
define_ld_reg_reg!(h, d);
define_ld_reg_reg!(h, e);
define_ld_reg_reg!(h, h);
define_ld_reg_reg!(h, l);
define_ld_reg_reg!(h, a);
define_ld_reg_reg!(l, b);
define_ld_reg_reg!(l, c);
define_ld_reg_reg!(l, d);
define_ld_reg_reg!(l, e);
define_ld_reg_reg!(l, h);
define_ld_reg_reg!(l, l);
define_ld_reg_reg!(l, a);
define_ld_reg_reg!(a, b);
define_ld_reg_reg!(a, c);
define_ld_reg_reg!(a, d);
define_ld_reg_reg!(a, e);
define_ld_reg_reg!(a, h);
define_ld_reg_reg!(a, l);
define_ld_reg_reg!(a, a);
macro_rules! define_ld_reg_deref {
($lreg:ident, $rreg:ident) => {
paste::paste! {
pub fn [<ld_ $lreg _deref_ $rreg>](state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
state.cpu_read_u8(state.registers.[<get_ $rreg>]());
CycleResult::NeedsMore
},
1 => {
state.registers.$lreg = state.registers.take_mem();
state.registers.opcode_len = Some(1);
CycleResult::Finished
},
_ => unreachable!(),
}
}
}
};
}
define_ld_reg_deref!(b, hl);
define_ld_reg_deref!(c, hl);
define_ld_reg_deref!(d, hl);
define_ld_reg_deref!(e, hl);
define_ld_reg_deref!(h, hl);
define_ld_reg_deref!(l, hl);
define_ld_reg_deref!(a, hl);
define_ld_reg_deref!(a, bc);
define_ld_reg_deref!(a, de);
pub fn ld_hl_minus_a(state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
state.cpu_write_u8(state.registers.get_hl(), state.registers.a);
CycleResult::NeedsMore
}
1 => {
let reg = state.registers.get_hl().overflowing_sub(1).0;
state.registers.set_hl(reg);
state.registers.opcode_len = Some(1);
CycleResult::Finished
}
_ => unreachable!(),
}
}
pub fn ld_a_hl_minus(state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
state.cpu_read_u8(state.registers.get_hl());
CycleResult::NeedsMore
}
1 => {
state.registers.a = state.registers.take_mem();
let reg = state.registers.get_hl().overflowing_sub(1).0;
state.registers.set_hl(reg);
state.registers.opcode_len = Some(1);
CycleResult::Finished
}
_ => unreachable!(),
}
}
pub fn ld_hl_plus_a(state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
state.cpu_write_u8(state.registers.get_hl(), state.registers.a);
CycleResult::NeedsMore
}
1 => {
let reg = state.registers.get_hl().overflowing_add(1).0;
state.registers.set_hl(reg);
state.registers.opcode_len = Some(1);
CycleResult::Finished
}
_ => unreachable!(),
}
}
pub fn ld_a_hl_plus(state: &mut Gameboy) -> CycleResult {
match state.registers.cycle {
0 => {
state.cpu_read_u8(state.registers.get_hl());
CycleResult::NeedsMore
}
1 => {
state.registers.a = state.registers.take_mem();
let reg = state.registers.get_hl().overflowing_add(1).0;
state.registers.set_hl(reg);
state.registers.opcode_len = Some(1);
CycleResult::Finished
}
_ => unreachable!(),
}
}