feat: missing opcodes, pass all blargg cpu tests

This commit is contained in:
EliseZeroTwo 2023-12-31 00:23:21 -07:00
parent a9c1ac65b3
commit ce077f9069
Signed by: elise
GPG key ID: FA8F56FFFE6E8B3E
20 changed files with 1476 additions and 214 deletions

145
Cargo.lock generated
View file

@ -74,33 +74,52 @@ dependencies = [
] ]
[[package]] [[package]]
name = "argh" name = "anstream"
version = "0.1.6" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f023c76cd7975f9969f8e29f0e461decbdc7f51048ce43427107a3d192f1c9bf" checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
dependencies = [ dependencies = [
"argh_derive", "anstyle",
"argh_shared", "anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
] ]
[[package]] [[package]]
name = "argh_derive" name = "anstyle"
version = "0.1.6" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48ad219abc0c06ca788aface2e3a1970587e3413ab70acd20e54b6ec524c1f8f" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [ dependencies = [
"argh_shared", "utf8parse",
"heck",
"proc-macro2",
"quote",
"syn 1.0.82",
] ]
[[package]] [[package]]
name = "argh_shared" name = "anstyle-query"
version = "0.1.6" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38de00daab4eac7d753e97697066238d67ce9d7e2d823ab4f72fe14af29f3f33" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
@ -125,7 +144,7 @@ checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.43", "syn",
] ]
[[package]] [[package]]
@ -309,6 +328,46 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "clap"
version = "4.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]] [[package]]
name = "codespan-reporting" name = "codespan-reporting"
version = "0.11.1" version = "0.11.1"
@ -319,6 +378,12 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]] [[package]]
name = "com-rs" name = "com-rs"
version = "0.2.1" version = "0.2.1"
@ -473,7 +538,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.43", "syn",
] ]
[[package]] [[package]]
@ -484,16 +549,16 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.43", "syn",
] ]
[[package]] [[package]]
name = "deemgee" name = "deemgee"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"argh",
"bmp", "bmp",
"chrono", "chrono",
"clap",
"config", "config",
"deemgee-opcode", "deemgee-opcode",
"env_logger", "env_logger",
@ -514,7 +579,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.43", "syn",
] ]
[[package]] [[package]]
@ -604,7 +669,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.43", "syn",
] ]
[[package]] [[package]]
@ -745,12 +810,9 @@ dependencies = [
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.3.3" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
@ -1100,7 +1162,7 @@ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.43", "syn",
] ]
[[package]] [[package]]
@ -1246,7 +1308,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.43", "syn",
] ]
[[package]] [[package]]
@ -1495,7 +1557,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.43", "syn",
] ]
[[package]] [[package]]
@ -1586,17 +1648,6 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.43" version = "2.0.43"
@ -1634,7 +1685,7 @@ checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.43", "syn",
] ]
[[package]] [[package]]
@ -1718,6 +1769,12 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
@ -1768,7 +1825,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.43", "syn",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -1802,7 +1859,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.43", "syn",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]

View file

@ -76,7 +76,7 @@ pub fn opcode(item: TokenStream) -> TokenStream {
}*/ }*/
let regs = quote::quote! { let regs = quote::quote! {
log::info!("\nSTART OF {}\n-- Registers --\nAF: {:04X}\nBC: {:04X}\nDE: {:04X}\nHL: {:04X}\nSP: {:04X}\nPC: {:04X}\nZero: {}\nSubtract: {}\nHalf-Carry: {}\nCarry: {}\n-- Interrupts --\nIME: {}\nIE VBlank: {}\nIE LCD Stat: {}\nIE Timer: {}\nIE Serial: {}\nIE Joypad: {}\nIF VBlank: {}\nIF LCD Stat: {}\nIF Timer: {}\nIF Serial: {}\nIF Joypad: {}\nEND OF {}", #name_s, state.registers.get_af(), state.registers.get_bc(), state.registers.get_de(), state.registers.get_hl(), state.registers.get_sp(), state.registers.pc, state.registers.get_zero(), state.registers.get_subtract(), state.registers.get_half_carry(), state.registers.get_carry(), state.interrupts.ime, state.interrupts.read_ie_vblank(), state.interrupts.read_ie_lcd_stat(), state.interrupts.read_ie_timer(), state.interrupts.read_ie_serial(), state.interrupts.read_ie_joypad(), state.interrupts.read_if_vblank(), state.interrupts.read_if_lcd_stat(), state.interrupts.read_if_timer(), state.interrupts.read_if_serial(), state.interrupts.read_if_joypad(), #name_s); log::debug!("\nSTART OF {}\n-- Registers --\nAF: {:04X}\nBC: {:04X}\nDE: {:04X}\nHL: {:04X}\nSP: {:04X}\nPC: {:04X}\nZero: {}\nSubtract: {}\nHalf-Carry: {}\nCarry: {}\n-- Interrupts --\nIME: {}\nIE VBlank: {}\nIE LCD Stat: {}\nIE Timer: {}\nIE Serial: {}\nIE Joypad: {}\nIF VBlank: {}\nIF LCD Stat: {}\nIF Timer: {}\nIF Serial: {}\nIF Joypad: {}\nEND OF {}", #name_s, state.registers.get_af(), state.registers.get_bc(), state.registers.get_de(), state.registers.get_hl(), state.registers.get_sp(), state.registers.pc, state.registers.get_zero(), state.registers.get_subtract(), state.registers.get_half_carry(), state.registers.get_carry(), state.interrupts.ime, state.interrupts.read_ie_vblank(), state.interrupts.read_ie_lcd_stat(), state.interrupts.read_ie_timer(), state.interrupts.read_ie_serial(), state.interrupts.read_ie_joypad(), state.interrupts.read_if_vblank(), state.interrupts.read_if_lcd_stat(), state.interrupts.read_if_timer(), state.interrupts.read_if_serial(), state.interrupts.read_if_joypad(), #name_s);
}; };
let match_statement = quote::quote! { let match_statement = quote::quote! {

View file

@ -6,9 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
argh = "0.1.6"
bmp = "0.5.0" bmp = "0.5.0"
chrono = "0.4.19" chrono = "0.4.19"
clap = { version = "4.4.12", features = ["derive"] }
config = "0.13.4" config = "0.13.4"
deemgee-opcode = { path = "../deemgee-opcode" } deemgee-opcode = { path = "../deemgee-opcode" }
env_logger = "0.10.1" env_logger = "0.10.1"

View file

@ -1,7 +1,7 @@
mod cpu; mod cpu;
mod interrupts; mod interrupts;
mod joypad; mod joypad;
mod mapper; pub mod mapper;
mod memory; mod memory;
mod ppu; mod ppu;
mod serial; mod serial;
@ -40,12 +40,83 @@ impl DmaState {
} }
} }
pub struct RingBuffer<T: std::fmt::Debug + Copy + Default, const SIZE: usize> {
buffer: [T; SIZE],
size: usize,
write_ptr: usize,
read_ptr: usize,
}
impl<T: std::fmt::Debug + Copy + Default, const SIZE: usize> RingBuffer<T, SIZE> {
pub fn new() -> Self {
RingBuffer { buffer: [T::default(); SIZE], size: 0, write_ptr: 0, read_ptr: 0 }
}
pub fn push(&mut self, value: T) {
self.buffer[self.write_ptr] = value;
if self.size < SIZE {
self.size += 1;
} else {
self.read_ptr += 1;
self.read_ptr %= SIZE;
}
self.write_ptr += 1;
self.write_ptr %= SIZE;
}
pub fn to_vec(&self) -> Vec<T> {
let mut out = Vec::new();
let mut offset = self.read_ptr;
for _ in 0..self.size {
out.push(self.buffer[offset]);
offset += 1;
offset %= SIZE;
}
out
}
}
#[test]
fn test_ringbuffer() {
let mut ringbuffer: RingBuffer<u8, 16> = RingBuffer::new();
for x in 0..16 {
ringbuffer.push(x);
}
assert_eq!(
ringbuffer.to_vec().as_slice(),
&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
);
ringbuffer.push(16);
assert_eq!(
ringbuffer.to_vec().as_slice(),
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
);
ringbuffer.push(17);
assert_eq!(
ringbuffer.to_vec().as_slice(),
&[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
);
for x in 18..32 {
ringbuffer.push(x);
}
assert_eq!(
ringbuffer.to_vec().as_slice(),
&[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
);
}
pub struct Gameboy { pub struct Gameboy {
pub ppu: Ppu, pub ppu: Ppu,
memory: Memory, pub memory: Memory,
cartridge: Option<Box<dyn Mapper>>, pub cartridge: Option<Box<dyn Mapper>>,
interrupts: Interrupts, pub interrupts: Interrupts,
timer: Timer, pub timer: Timer,
pub registers: Registers, pub registers: Registers,
pub joypad: Joypad, pub joypad: Joypad,
pub serial: Serial, pub serial: Serial,
@ -61,10 +132,13 @@ pub struct Gameboy {
pub halt: bool, pub halt: bool,
pub halt_bug: bool, pub halt_bug: bool,
pub used_halt_bug: bool, pub used_halt_bug: bool,
pub stop: bool,
pub pc_history: RingBuffer<u16, 0x200>,
} }
impl Gameboy { impl Gameboy {
pub fn new(bootrom: [u8; 0x100]) -> Self { pub fn new(bootrom: Option<[u8; 0x100]>) -> Self {
Self { Self {
memory: Memory::new(bootrom), memory: Memory::new(bootrom),
cartridge: None, cartridge: None,
@ -74,7 +148,10 @@ impl Gameboy {
serial: Serial::new(), serial: Serial::new(),
dma: DmaState::new(), dma: DmaState::new(),
ppu: Ppu::new(), ppu: Ppu::new(),
registers: Registers::default(), registers: match bootrom.is_some() {
true => Registers::default(),
false => Registers::post_rom(),
},
sound: Sound::default(), sound: Sound::default(),
single_step: false, single_step: false,
breakpoints: [false; u16::MAX as usize + 1], breakpoints: [false; u16::MAX as usize + 1],
@ -85,6 +162,8 @@ impl Gameboy {
halt: false, halt: false,
halt_bug: false, halt_bug: false,
used_halt_bug: false, used_halt_bug: false,
stop: false,
pc_history: RingBuffer::new(),
} }
} }
@ -112,6 +191,8 @@ impl Gameboy {
match bytes[0x147] { match bytes[0x147] {
0 => self.cartridge = Some(Box::new(NoMBC::new(bytes))), 0 => self.cartridge = Some(Box::new(NoMBC::new(bytes))),
1 => self.cartridge = Some(Box::new(MBC1::new(bytes))), 1 => self.cartridge = Some(Box::new(MBC1::new(bytes))),
2 => self.cartridge = Some(Box::new(MBC1::new(bytes))),
3 => self.cartridge = Some(Box::new(MBC1::new(bytes))),
other => unimplemented!("Cartidge type: {:#X}", other), other => unimplemented!("Cartidge type: {:#X}", other),
} }
} }
@ -120,13 +201,16 @@ impl Gameboy {
log::info!("\n-- Registers --\nAF: {:04X}\nBC: {:04X}\nDE: {:04X}\nHL: {:04X}\nSP: {:04X}\nPC: {:04X}\nZero: {}\nSubtract: {}\nHalf-Carry: {}\nCarry: {}\n-- Interrupts --\nIME: {}\nIE VBlank: {}\nIE LCD Stat: {}\nIE Timer: {}\nIE Serial: {}\nIE Joypad: {}\nIF VBlank: {}\nIF LCD Stat: {}\nIF Timer: {}\nIF Serial: {}\nIF Joypad: {}\n", self.registers.get_af(), self.registers.get_bc(), self.registers.get_de(), self.registers.get_hl(), self.registers.get_sp(), self.registers.pc, self.registers.get_zero(), self.registers.get_subtract(), self.registers.get_half_carry(), self.registers.get_carry(), self.interrupts.ime, self.interrupts.read_ie_vblank(), self.interrupts.read_ie_lcd_stat(), self.interrupts.read_ie_timer(), self.interrupts.read_ie_serial(), self.interrupts.read_ie_joypad(), self.interrupts.read_if_vblank(), self.interrupts.read_if_lcd_stat(), self.interrupts.read_if_timer(), self.interrupts.read_if_serial(), self.interrupts.read_if_joypad()); log::info!("\n-- Registers --\nAF: {:04X}\nBC: {:04X}\nDE: {:04X}\nHL: {:04X}\nSP: {:04X}\nPC: {:04X}\nZero: {}\nSubtract: {}\nHalf-Carry: {}\nCarry: {}\n-- Interrupts --\nIME: {}\nIE VBlank: {}\nIE LCD Stat: {}\nIE Timer: {}\nIE Serial: {}\nIE Joypad: {}\nIF VBlank: {}\nIF LCD Stat: {}\nIF Timer: {}\nIF Serial: {}\nIF Joypad: {}\n", self.registers.get_af(), self.registers.get_bc(), self.registers.get_de(), self.registers.get_hl(), self.registers.get_sp(), self.registers.pc, self.registers.get_zero(), self.registers.get_subtract(), self.registers.get_half_carry(), self.registers.get_carry(), self.interrupts.ime, self.interrupts.read_ie_vblank(), self.interrupts.read_ie_lcd_stat(), self.interrupts.read_ie_timer(), self.interrupts.read_ie_serial(), self.interrupts.read_ie_joypad(), self.interrupts.read_if_vblank(), self.interrupts.read_if_lcd_stat(), self.interrupts.read_if_timer(), self.interrupts.read_if_serial(), self.interrupts.read_if_joypad());
} }
pub fn tick(&mut self) -> bool { pub fn tick(&mut self) -> (bool, Option<i64>) {
if self.breakpoints[self.registers.pc as usize] && !self.single_step { if self.breakpoints[self.registers.pc as usize] && !self.single_step {
self.single_step = true; self.single_step = true;
log::info!("Breakpoint hit @ {:#X}", self.registers.pc); log::info!("Breakpoint hit @ {:#X}", self.registers.pc);
} }
let mut diff = None;
if self.trigger_bp || (self.single_step && self.registers.cycle == 0) { if self.trigger_bp || (self.single_step && self.registers.cycle == 0) {
let entered_step = chrono::Utc::now();
self.trigger_bp = false; self.trigger_bp = false;
self.single_step = true; self.single_step = true;
let mut input = String::new(); let mut input = String::new();
@ -189,11 +273,21 @@ impl Gameboy {
log::info!("Continuing"); log::info!("Continuing");
exit = false; exit = false;
} }
"timer" => {
println!("-- Timer Info --\n{:#?}\n-- End of Timer Info --", self.timer)
}
"p" | "pause" => { "p" | "pause" => {
self.single_step = true; self.single_step = true;
log::info!("Single step activated"); log::info!("Single step activated");
exit = false; exit = false;
} }
"pch" => {
println!("-- Start of PC History (new to old) --");
for (idx, pc) in self.pc_history.to_vec().iter().rev().enumerate() {
println!("{}: {:#04X}", idx + 1, pc);
}
println!("-- End of PC History --");
}
"s" | "step" | "" => { "s" | "step" | "" => {
self.log_next_opcode(); self.log_next_opcode();
exit = false; exit = false;
@ -208,6 +302,17 @@ impl Gameboy {
"dumpfb" => { "dumpfb" => {
println!("Written to: {}", self.ppu.dump_fb()); println!("Written to: {}", self.ppu.dump_fb());
} }
"dumpoam" => {
for x in 0..self.ppu.oam.len() {
if x % 0x10 == 0 {
print!("\n{:X}: ", 0xFE00 + x)
}
let mem_val = self.ppu.oam[x];
print!("{:02X} ", mem_val);
}
println!();
}
"dumpvram" => { "dumpvram" => {
for x in 0..0x200 { for x in 0..0x200 {
if x % 0x10 == 0 { if x % 0x10 == 0 {
@ -241,8 +346,9 @@ impl Gameboy {
Err(stdin_err) => panic!("Failed to lock stdin: {:?}", stdin_err), Err(stdin_err) => panic!("Failed to lock stdin: {:?}", stdin_err),
} }
diff = Some((chrono::Utc::now() - entered_step).num_milliseconds());
if exit { if exit {
return false; return (false, diff);
} }
} }
if self.timer.tick() { if self.timer.tick() {
@ -255,7 +361,7 @@ impl Gameboy {
if self.serial.tick() { if self.serial.tick() {
self.interrupts.write_if_serial(true); self.interrupts.write_if_serial(true);
} }
redraw_requested (redraw_requested, diff)
} }
fn tick_dma(&mut self) { fn tick_dma(&mut self) {
@ -271,6 +377,12 @@ impl Gameboy {
} }
} else if self.dma.base <= 0x9F { } else if self.dma.base <= 0x9F {
self.ppu.dma_read_vram(offset) self.ppu.dma_read_vram(offset)
} else if self.dma.base <= 0xDF {
let address = (self.dma.base as u16) << 8 | offset as u16;
self.memory.wram[address as usize - 0xC000]
} else if self.dma.base <= 0xFD {
let address = (self.dma.base as u16) << 8 | offset as u16;
self.memory.wram[address as usize - 0xE000]
} else { } else {
0xFF 0xFF
}; };
@ -285,19 +397,19 @@ impl Gameboy {
0xFF00 => self.joypad.cpu_read(), 0xFF00 => self.joypad.cpu_read(),
0xFF01 => self.serial.sb, 0xFF01 => self.serial.sb,
0xFF02 => self.serial.sc, 0xFF02 => self.serial.sc,
0xFF03 => 0, // Unused 0xFF03 => 0xFF, // Unused
0xFF04 => self.timer.div, 0xFF04 => self.timer.div,
0xFF05 => self.timer.tima, 0xFF05 => self.timer.tima,
0xFF06 => self.timer.tma, 0xFF06 => self.timer.tma,
0xFF07 => self.timer.read_tac(), 0xFF07 => self.timer.read_tac(),
0xFF08..=0xFF0E => 0, // Unused 0xFF08..=0xFF0E => 0xFF, // Unused
0xFF0F => self.interrupts.interrupt_flag, 0xFF0F => self.interrupts.interrupt_flag,
0xFF10 => self.sound.nr10, 0xFF10 => self.sound.nr10,
0xFF11 => self.sound.nr11, 0xFF11 => self.sound.nr11,
0xFF12 => self.sound.nr12, 0xFF12 => self.sound.nr12,
0xFF13 => self.sound.nr13, 0xFF13 => self.sound.nr13,
0xFF14 => self.sound.nr14, 0xFF14 => self.sound.nr14,
0xFF15 => 0, 0xFF15 => 0xFF,
0xFF16 => self.sound.nr21, 0xFF16 => self.sound.nr21,
0xFF17 => self.sound.nr22, 0xFF17 => self.sound.nr22,
0xFF18 => self.sound.nr23, 0xFF18 => self.sound.nr23,
@ -307,7 +419,7 @@ impl Gameboy {
0xFF1C => self.sound.nr32, 0xFF1C => self.sound.nr32,
0xFF1D => self.sound.nr33, 0xFF1D => self.sound.nr33,
0xFF1E => self.sound.nr34, 0xFF1E => self.sound.nr34,
0xFF1F => 0, 0xFF1F => 0xFF,
0xFF20 => self.sound.nr41, 0xFF20 => self.sound.nr41,
0xFF21 => self.sound.nr42, 0xFF21 => self.sound.nr42,
0xFF22 => self.sound.nr43, 0xFF22 => self.sound.nr43,
@ -315,7 +427,7 @@ impl Gameboy {
0xFF24 => self.sound.nr50, 0xFF24 => self.sound.nr50,
0xFF25 => self.sound.nr51, 0xFF25 => self.sound.nr51,
0xFF26 => self.sound.nr52, 0xFF26 => self.sound.nr52,
0xFF27..=0xFF2F => 0, 0xFF27..=0xFF2F => 0xFF,
0xFF30..=0xFF3F => self.sound.wave_pattern_ram[address as usize - 0xFF30], 0xFF30..=0xFF3F => self.sound.wave_pattern_ram[address as usize - 0xFF30],
0xFF40 => self.ppu.lcdc, 0xFF40 => self.ppu.lcdc,
0xFF41 => self.ppu.stat, 0xFF41 => self.ppu.stat,
@ -324,18 +436,18 @@ impl Gameboy {
0xFF44 => self.ppu.ly, 0xFF44 => self.ppu.ly,
0xFF45 => self.ppu.lyc, 0xFF45 => self.ppu.lyc,
0xFF46 => self.dma.base, 0xFF46 => self.dma.base,
0xFF47..=0xFF49 => 0, 0xFF47..=0xFF49 => 0xFF,
0xFF4A => self.ppu.wy, 0xFF4A => self.ppu.wy,
0xFF4B => self.ppu.wx, 0xFF4B => self.ppu.wx,
0xFF4C..=0xFF4E => 0, // Unused 0xFF4C..=0xFF4E => 0xFF, // Unused
0xFF4F => 0, // CGB VRAM Bank Select 0xFF4F => 0xFF, // CGB VRAM Bank Select
0xFF50 => self.memory.bootrom_disabled as u8, 0xFF50 => self.memory.bootrom_disabled as u8,
0xFF51..=0xFF55 => 0, // CGB VRAM DMA 0xFF51..=0xFF55 => 0xFF, // CGB VRAM DMA
0xFF56..=0xFF67 => 0, // Unused 0xFF56..=0xFF67 => 0xFF, // Unused
0xFF68..=0xFF69 => 0, // BJ/OBJ Palettes 0xFF68..=0xFF69 => 0xFF, // BJ/OBJ Palettes
0xFF6A..=0xFF6F => 0, // Unused 0xFF6A..=0xFF6F => 0xFF, // Unused
0xFF70 => 0, // CGB WRAM Bank Select 0xFF70 => 0xFF, // CGB WRAM Bank Select
0xFF71..=0xFF7F => 0, // Unused 0xFF71..=0xFF7F => 0xFF, // Unused
_ => unreachable!("IO Read Invalid"), _ => unreachable!("IO Read Invalid"),
} }
} }
@ -346,12 +458,12 @@ impl Gameboy {
0xFF01 => self.serial.sb = value, 0xFF01 => self.serial.sb = value,
0xFF02 => self.serial.sc = value, 0xFF02 => self.serial.sc = value,
0xFF03 => {} // Unused 0xFF03 => {} // Unused
0xFF04 => self.timer.div = value, 0xFF04 => self.timer.div = 0,
0xFF05 => self.timer.tima = value, 0xFF05 => self.timer.tima = value,
0xFF06 => self.timer.tma = value, 0xFF06 => self.timer.tma = value,
0xFF07 => self.timer.write_tac(value), 0xFF07 => self.timer.write_tac(value),
0xFF08..=0xFF0E => {} // Unused 0xFF08..=0xFF0E => {} // Unused
0xFF0F => self.interrupts.interrupt_flag = value & 0b1_1111, 0xFF0F => self.interrupts.interrupt_flag = value | !0b1_1111,
0xFF10 => self.sound.nr10 = value, 0xFF10 => self.sound.nr10 = value,
0xFF11 => self.sound.nr11 = value, 0xFF11 => self.sound.nr11 = value,
0xFF12 => self.sound.nr12 = value, 0xFF12 => self.sound.nr12 = value,
@ -412,30 +524,63 @@ impl Gameboy {
let mut out = [0u8; 0xFFFF]; let mut out = [0u8; 0xFFFF];
for address in 0..0xFFFF { for address in 0..0xFFFF {
out[address as usize] = match address { out[address as usize] = self.debug_read_u8(address);
0..=0xFF if !self.memory.bootrom_disabled => self.memory.bootrom[address as usize],
0..=0x7FFF => match self.cartridge.as_ref() {
Some(mapper) => mapper.read_rom_u8(address),
None => 0,
},
0x8000..=0x9FFF => self.ppu.cpu_read_vram(address),
0xA000..=0xBFFF => match self.cartridge.as_ref() {
Some(mapper) => mapper.read_eram_u8(address),
None => 0,
},
0xC000..=0xDFFF => self.memory.wram[address as usize - 0xC000],
0xE000..=0xFDFF => self.memory.wram[address as usize - 0xE000],
0xFE00..=0xFE9F => self.ppu.cpu_read_oam(address),
0xFEA0..=0xFEFF => 0,
0xFF00..=0xFF7F => self.cpu_read_io(address),
0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80],
0xFFFF => self.interrupts.interrupt_enable,
};
} }
out out
} }
/// Warning: This bypasses the memory bus and only exists for
/// debugging/testing purposes
pub fn debug_read_u8(&self, address: u16) -> u8 {
match address {
0..=0xFF if !self.memory.bootrom_disabled => self.memory.bootrom[address as usize],
0..=0x7FFF => match self.cartridge.as_ref() {
Some(mapper) => mapper.read_rom_u8(address),
None => 0,
},
0x8000..=0x9FFF => self.ppu.cpu_read_vram(address),
0xA000..=0xBFFF => match self.cartridge.as_ref() {
Some(mapper) => mapper.read_eram_u8(address),
None => 0,
},
0xC000..=0xDFFF => self.memory.wram[address as usize - 0xC000],
0xE000..=0xFDFF => self.memory.wram[address as usize - 0xE000],
0xFE00..=0xFE9F => self.ppu.cpu_read_oam(address),
0xFEA0..=0xFEFF => 0,
0xFF00..=0xFF7F => self.cpu_read_io(address),
0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80],
0xFFFF => self.interrupts.interrupt_enable,
}
}
/// Warning: This bypasses the memory bus and only exists for
/// debugging/testing purposes
#[allow(unused)]
pub fn debug_write_u8(&mut self, address: u16, value: u8) {
match address {
0..=0xFF if !self.memory.bootrom_disabled => {}
0..=0x7FFF => {
if let Some(mapper) = self.cartridge.as_mut() {
mapper.write_rom_u8(address, value)
}
}
0x8000..=0x9FFF => self.ppu.cpu_write_vram(address, value),
0xA000..=0xBFFF => {
if let Some(mapper) = self.cartridge.as_mut() {
mapper.write_eram_u8(address, value)
}
}
0xC000..=0xDFFF => self.memory.wram[address as usize - 0xC000] = value,
0xE000..=0xFDFF => self.memory.wram[address as usize - 0xE000] = value,
0xFE00..=0xFE9F => self.ppu.cpu_write_oam(address, value),
0xFEA0..=0xFEFF => {}
0xFF00..=0xFF7F => self.cpu_write_io(address, value),
0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80] = value,
0xFFFF => self.interrupts.interrupt_enable = value & 0b1_1111,
}
}
fn internal_cpu_read_u8(&self, address: u16) -> u8 { fn internal_cpu_read_u8(&self, address: u16) -> u8 {
if self.dma.remaining_cycles == 0 { if self.dma.remaining_cycles == 0 {
match address { match address {

View file

@ -43,7 +43,7 @@ pub enum CycleResult {
FinishedKeepPc, FinishedKeepPc,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Clone)]
pub struct Registers { pub struct Registers {
pub a: u8, pub a: u8,
pub f: u8, pub f: u8,
@ -67,12 +67,50 @@ pub struct Registers {
pub in_interrupt_vector: Option<u8>, pub in_interrupt_vector: Option<u8>,
} }
impl PartialEq for Registers {
fn eq(&self, other: &Self) -> bool {
self.a == other.a
&& self.f == other.f
&& self.b == other.b
&& self.c == other.c
&& self.d == other.d
&& self.e == other.e
&& self.h == other.h
&& self.l == other.l
&& self.sp == other.sp
&& self.pc == other.pc
}
}
impl Registers { impl Registers {
define_register!(a, f); pub fn get_af(&self) -> u16 {
(self.a as u16) << 8 | self.f as u16
}
pub fn set_af(&mut self, value: u16) {
self.a = (value >> 8) as u8;
self.f = value as u8 & 0b1111_0000;
}
define_register!(b, c); define_register!(b, c);
define_register!(d, e); define_register!(d, e);
define_register!(h, l); define_register!(h, l);
pub fn post_rom() -> Self {
Self {
a: 0x01,
f: 0xB0,
b: 0x00,
c: 0x13,
d: 0x00,
e: 0xD8,
h: 0x01,
l: 0x4D,
sp: 0xFFFE,
pc: 0x0100,
..Default::default()
}
}
/// This is just a helper function for macros utilizing ident pasting /// This is just a helper function for macros utilizing ident pasting
pub fn get_sp(&self) -> u16 { pub fn get_sp(&self) -> u16 {
self.sp self.sp
@ -110,7 +148,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
state.interrupts.write_if_joypad(true); state.interrupts.write_if_joypad(true);
} }
if state.registers.cycle == 0 && state.halt { if state.registers.cycle == 0 && (state.halt || state.stop) {
if (state.interrupts.read_ie_vblank() && state.interrupts.read_if_vblank()) 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_lcd_stat() && state.interrupts.read_if_lcd_stat())
|| (state.interrupts.read_ie_timer() && state.interrupts.read_if_timer()) || (state.interrupts.read_ie_timer() && state.interrupts.read_if_timer())
@ -118,6 +156,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
|| (state.interrupts.read_ie_joypad() && state.interrupts.read_if_joypad()) || (state.interrupts.read_ie_joypad() && state.interrupts.read_if_joypad())
{ {
state.halt = false; state.halt = false;
state.stop = false;
} else { } else {
return; return;
} }
@ -149,8 +188,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
} }
if state.registers.cycle == 0 && state.interrupts.ei_queued { if state.registers.cycle == 0 && state.interrupts.ei_queued {
state.interrupts.ime = state.interrupts.ei_queued; state.interrupts.cycle_passed = true;
state.interrupts.ei_queued = false;
} }
if state.registers.cycle == 0 && state.halt_bug { if state.registers.cycle == 0 && state.halt_bug {
@ -176,6 +214,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
CycleResult::NeedsMore CycleResult::NeedsMore
} }
4 => { 4 => {
let original_pc = state.registers.pc;
state.registers.pc = match idx { state.registers.pc = match idx {
0 => 0x40, 0 => 0x40,
1 => 0x48, 1 => 0x48,
@ -186,7 +225,11 @@ pub fn tick_cpu(state: &mut Gameboy) {
}; };
state.registers.in_interrupt_vector = None; state.registers.in_interrupt_vector = None;
state.registers.opcode_bytecount = Some(0); state.registers.opcode_bytecount = Some(0);
log::info!("Triggering interrupt to {:#X}", state.registers.pc); log::debug!(
"Triggering interrupt to {:#X} from {:#X}",
state.registers.pc,
original_pc
);
CycleResult::Finished CycleResult::Finished
} }
_ => unreachable!(), _ => unreachable!(),
@ -194,16 +237,19 @@ pub fn tick_cpu(state: &mut Gameboy) {
} else { } else {
let opcode = match state.registers.current_opcode { let opcode = match state.registers.current_opcode {
Some(opcode) => opcode, Some(opcode) => opcode,
None => match state.registers.mem_read_hold.take() { None => {
Some(opcode) => { state.pc_history.push(state.registers.pc);
state.registers.current_opcode = Some(opcode); match state.registers.mem_read_hold.take() {
opcode Some(opcode) => {
state.registers.current_opcode = Some(opcode);
opcode
}
None => {
state.cpu_read_u8(state.registers.pc);
return;
}
} }
None => { }
state.cpu_read_u8(state.registers.pc);
return;
}
},
}; };
let result: CycleResult = match opcode { let result: CycleResult = match opcode {
@ -223,6 +269,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
0x0d => alu::dec_c, 0x0d => alu::dec_c,
0x0e => load_store_move::ld_c_imm_u8, 0x0e => load_store_move::ld_c_imm_u8,
0x0F => alu::rrca, 0x0F => alu::rrca,
0x10 => misc::halt,
0x11 => load_store_move::ld_de_imm_u16, 0x11 => load_store_move::ld_de_imm_u16,
0x12 => load_store_move::ld_deref_de_a, 0x12 => load_store_move::ld_deref_de_a,
0x13 => alu::inc_de, 0x13 => alu::inc_de,
@ -439,6 +486,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
0xE5 => load_store_move::push_hl, 0xE5 => load_store_move::push_hl,
0xE6 => alu::and_a_imm_u8, 0xE6 => alu::and_a_imm_u8,
0xE7 => flow::rst_0x20, 0xE7 => flow::rst_0x20,
0xE8 => alu::add_sp_imm_i8,
0xE9 => flow::jp_hl, 0xE9 => flow::jp_hl,
0xEA => load_store_move::ld_deref_imm_u16_a, 0xEA => load_store_move::ld_deref_imm_u16_a,
0xEB | 0xEC | 0xED => { 0xEB | 0xEC | 0xED => {
@ -479,6 +527,12 @@ pub fn tick_cpu(state: &mut Gameboy) {
state.registers.pc = state.registers.pc.overflowing_add(1).0; state.registers.pc = state.registers.pc.overflowing_add(1).0;
} }
if state.interrupts.cycle_passed && state.interrupts.ei_queued {
state.interrupts.cycle_passed = false;
state.interrupts.ei_queued = false;
state.interrupts.ime = true;
}
if result == CycleResult::Finished { if result == CycleResult::Finished {
match state.registers.opcode_bytecount { match state.registers.opcode_bytecount {
Some(len) => state.registers.pc = state.registers.pc.overflowing_add(len as u16).0, Some(len) => state.registers.pc = state.registers.pc.overflowing_add(len as u16).0,

View file

@ -17,7 +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 (result, second_carry) = first_res.overflowing_sub(carry_u8);
let carry = first_carry || second_carry; let carry = first_carry || second_carry;
let half_carry = (lhs & 0xF) < (rhs & 0xF) + carry_u8; let half_carry = (lhs & 0xF).wrapping_sub(rhs & 0xF).wrapping_sub(carry_u8) & 0x10 != 0;
CarryResult { result, carry, half_carry } CarryResult { result, carry, half_carry }
} }
@ -29,21 +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 (result, second_carry) = first_res.overflowing_add(carry_u8);
let carry = first_carry || second_carry; let carry = first_carry || second_carry;
let half_carry = (lhs & 0xF) + (rhs & 0xF) > 0xF; let half_carry = (lhs & 0xF) + (rhs & 0xF) + carry_u8 > 0xF;
CarryResult { result, carry, half_carry } CarryResult { result, carry, half_carry }
} }
pub fn add(lhs: u8, rhs: u8) -> CarryResult { pub fn add(lhs: u8, rhs: u8) -> CarryResult {
let (result, carry) = lhs.overflowing_add(rhs); let (result, carry) = lhs.overflowing_add(rhs);
let half_carry = (lhs & 0xF) + (rhs & 0xF) > 0xF; let half_carry = (lhs & 0xF) + (rhs & 0xF) & 0x10 != 0;
CarryResult { result, carry, half_carry } CarryResult { result, carry, half_carry }
} }
pub fn sub(lhs: u8, rhs: u8) -> CarryResult { pub fn sub(lhs: u8, rhs: u8) -> CarryResult {
let (result, carry) = lhs.overflowing_sub(rhs); let (result, carry) = lhs.overflowing_sub(rhs);
let half_carry = (lhs & 0xF) < (rhs & 0xF); let half_carry = (lhs & 0xF).wrapping_sub(rhs & 0xF) & 0x10 != 0;
CarryResult { result, carry, half_carry } CarryResult { result, carry, half_carry }
} }
@ -454,7 +454,7 @@ opcode!(dec_deref_hl, 0x35, "DEC (HL)", false, 1, {
state.cpu_write_u8(state.registers.get_hl(), result); state.cpu_write_u8(state.registers.get_hl(), result);
state.registers.set_zero(result == 0); state.registers.set_zero(result == 0);
state.registers.set_subtract(false); state.registers.set_subtract(true);
state.registers.set_half_carry(half_carry); state.registers.set_half_carry(half_carry);
CycleResult::NeedsMore CycleResult::NeedsMore
}, },
@ -469,7 +469,7 @@ opcode!(rla, 0x17, "RLA", false, 1, {
state.registers.a <<= 1; state.registers.a <<= 1;
if state.registers.get_carry() { if state.registers.get_carry() {
state.registers.a = state.registers.a.wrapping_add(1); state.registers.a |= 1;
} }
state.registers.set_zero(false); state.registers.set_zero(false);
@ -487,7 +487,7 @@ opcode!(rra, 0x1f, "RRA", false, 1, {
state.registers.a >>= 1; state.registers.a >>= 1;
if state.registers.get_carry() { if state.registers.get_carry() {
state.registers.a = state.registers.a.wrapping_add(1 << 7); state.registers.a |= 1 << 7;
} }
state.registers.set_zero(false); state.registers.set_zero(false);
@ -845,7 +845,7 @@ opcode!(rlca, 0x7, "RLCA", false, 1, {
state.registers.a <<= 1; state.registers.a <<= 1;
state.registers.a |= carry as u8; state.registers.a |= carry as u8;
state.registers.set_zero(state.registers.a == 0); state.registers.set_zero(false);
state.registers.set_subtract(false); state.registers.set_subtract(false);
state.registers.set_half_carry(false); state.registers.set_half_carry(false);
state.registers.set_carry(carry); state.registers.set_carry(carry);
@ -859,7 +859,7 @@ opcode!(rrca, 0xF, "RRCA", false, 1, {
state.registers.a >>= 1; state.registers.a >>= 1;
state.registers.a |= (carry as u8) << 7; state.registers.a |= (carry as u8) << 7;
state.registers.set_zero(state.registers.a == 0); state.registers.set_zero(false);
state.registers.set_subtract(false); state.registers.set_subtract(false);
state.registers.set_half_carry(false); state.registers.set_half_carry(false);
state.registers.set_carry(carry); state.registers.set_carry(carry);
@ -872,19 +872,32 @@ opcode!(daa, 0x27, "DAA", false, 1, {
let mut value = 0; let mut value = 0;
let mut carry = false; let mut carry = false;
if state.registers.get_half_carry() || (!state.registers.get_subtract() && (state.registers.a & 0xF) > 9) { match state.registers.get_subtract() {
value |= 0x06; false => {
} if state.registers.get_carry() || state.registers.a > 0x99 {
value |= 0x60;
carry = true;
}
if state.registers.get_carry() || (!state.registers.get_subtract() && state.registers.a > 0x99) { if state.registers.get_half_carry() || state.registers.a & 0xf > 0x9 {
value |= 0x60; value |= 0x06;
carry = true; }
}
state.registers.a = match state.registers.get_subtract() { state.registers.a = state.registers.a.wrapping_add(value);
true => state.registers.a.wrapping_sub(value), },
false => state.registers.a.wrapping_add(value) true => {
}; if state.registers.get_carry() {
value |= 0x60;
carry = true;
}
if state.registers.get_half_carry() {
value |= 0x06;
}
state.registers.a = state.registers.a.wrapping_sub(value);
}
}
state.registers.set_half_carry(false); state.registers.set_half_carry(false);
state.registers.set_carry(carry); state.registers.set_carry(carry);
@ -892,3 +905,57 @@ opcode!(daa, 0x27, "DAA", false, 1, {
CycleResult::Finished CycleResult::Finished
} }
}); });
opcode!(add_sp_imm_i8, 0xE8, "ADD sp,i8", false, 2, {
0 => {
state.cpu_read_u8(state.registers.pc + 1);
CycleResult::NeedsMore
},
1 => {
let value = state.registers.take_mem() as i8;
let CarryResult { carry, half_carry, .. } = add((state.registers.sp & 0xff) as u8, value as u8);
state.registers.set_half_carry(half_carry);
state.registers.set_carry(carry);
let CarryResult { result, carry, .. } = match value < 0 {
true => sub((state.registers.sp & 0xff) as u8, value.abs() as u8),
false => add((state.registers.sp & 0xff) as u8, value as u8),
};
state.registers.sp &= 0xFF00;
state.registers.sp |= result as u16;
state.registers.set_hold(match carry {
true => match value < 0 {
true => -1i8 as u16,
false => 1,
},
false => 0
});
CycleResult::NeedsMore
},
2 => {
CycleResult::NeedsMore
},
3 => {
let carry = state.registers.take_hold() as i8;
match carry {
1 => {
state.registers.sp = state.registers.sp.wrapping_add(1 << 8);
},
-1 => {
state.registers.sp = state.registers.sp.wrapping_sub(1 << 8);
},
_ => {}
}
state.registers.set_zero(false);
state.registers.set_subtract(false);
CycleResult::Finished
}
});

View file

@ -242,7 +242,7 @@ opcode!(ld_hl_sp_i8, 0xF8, "LD HL,SP+i8", false, 2, {
let val = state.registers.take_mem() as i8; let val = state.registers.take_mem() as i8;
let rhs = if val < 0 { let rhs = if val < 0 {
state.registers.sp.overflowing_sub((val as u8 & !(1u8 << 7)) as u16).0 state.registers.sp.overflowing_sub(val.abs() as u16).0
} else { } else {
state.registers.sp.overflowing_add(val as u16).0 state.registers.sp.overflowing_add(val as u16).0
}; };

View file

@ -33,3 +33,39 @@ opcode!(halt, 0x76, "HALT", false, 1, {
CycleResult::Finished CycleResult::Finished
} }
}); });
opcode!(stop, 0x10, "STOP", false, 1, {
0 => {
CycleResult::NeedsMore
},
1 => {
let button_held = state.joypad.cpu_read() & 0b1111 != 0;
let interrupt_pending = state.interrupts.interrupt_enable & state.interrupts.interrupt_flag != 0;
match button_held {
true => match interrupt_pending {
true => {
state.registers.pc = state.registers.pc.wrapping_add(1);
},
false => {
state.registers.pc = state.registers.pc.wrapping_add(2);
state.halt = true;
}
},
false => match interrupt_pending {
true => {
state.registers.pc = state.registers.pc.wrapping_add(1);
state.stop = true;
state.timer.div = 0;
},
false => {
state.registers.pc = state.registers.pc.wrapping_add(2);
state.stop = true;
state.timer.div = 0;
}
},
}
CycleResult::FinishedKeepPc
}
});

View file

@ -25,6 +25,7 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
0x03 => rlc_e, 0x03 => rlc_e,
0x04 => rlc_h, 0x04 => rlc_h,
0x05 => rlc_l, 0x05 => rlc_l,
0x06 => rlc_deref_hl,
0x07 => rlc_a, 0x07 => rlc_a,
0x08 => rrc_b, 0x08 => rrc_b,
0x09 => rrc_c, 0x09 => rrc_c,
@ -32,6 +33,7 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
0x0b => rrc_e, 0x0b => rrc_e,
0x0c => rrc_h, 0x0c => rrc_h,
0x0d => rrc_l, 0x0d => rrc_l,
0x0e => rrc_deref_hl,
0x0f => rrc_a, 0x0f => rrc_a,
0x10 => rl_b, 0x10 => rl_b,
0x11 => rl_c, 0x11 => rl_c,
@ -39,6 +41,7 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
0x13 => rl_e, 0x13 => rl_e,
0x14 => rl_h, 0x14 => rl_h,
0x15 => rl_l, 0x15 => rl_l,
0x16 => rl_deref_hl,
0x17 => rl_a, 0x17 => rl_a,
0x18 => rr_b, 0x18 => rr_b,
0x19 => rr_c, 0x19 => rr_c,
@ -46,27 +49,31 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
0x1b => rr_e, 0x1b => rr_e,
0x1c => rr_h, 0x1c => rr_h,
0x1d => rr_l, 0x1d => rr_l,
0x1e => rr_deref_hl,
0x1f => rr_a, 0x1f => rr_a,
0x20 => sra_b, 0x20 => sla_b,
0x21 => sra_c, 0x21 => sla_c,
0x22 => sra_d, 0x22 => sla_d,
0x23 => sra_e, 0x23 => sla_e,
0x24 => sra_h, 0x24 => sla_h,
0x25 => sra_l, 0x25 => sla_l,
0x27 => sra_a, 0x26 => sla_deref_hl,
0x28 => sla_b, 0x27 => sla_a,
0x29 => sla_c, 0x28 => sra_b,
0x2a => sla_d, 0x29 => sra_c,
0x2b => sla_e, 0x2a => sra_d,
0x2c => sla_h, 0x2b => sra_e,
0x2d => sla_l, 0x2c => sra_h,
0x2f => sla_a, 0x2d => sra_l,
0x2e => sra_deref_hl,
0x2f => sra_a,
0x30 => swap_b, 0x30 => swap_b,
0x31 => swap_c, 0x31 => swap_c,
0x32 => swap_d, 0x32 => swap_d,
0x33 => swap_e, 0x33 => swap_e,
0x34 => swap_h, 0x34 => swap_h,
0x35 => swap_l, 0x35 => swap_l,
0x36 => swap_deref_hl,
0x37 => swap_a, 0x37 => swap_a,
0x38 => srl_b, 0x38 => srl_b,
0x39 => srl_c, 0x39 => srl_c,
@ -74,6 +81,7 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
0x3b => srl_e, 0x3b => srl_e,
0x3c => srl_h, 0x3c => srl_h,
0x3d => srl_l, 0x3d => srl_l,
0x3e => srl_deref_hl,
0x3f => srl_a, 0x3f => srl_a,
0x40 => bit_0_b, 0x40 => bit_0_b,
0x41 => bit_0_c, 0x41 => bit_0_c,
@ -267,10 +275,6 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
0xFD => set_7_l, 0xFD => set_7_l,
0xFE => set_7_deref_hl, 0xFE => set_7_deref_hl,
0xFF => set_7_a, 0xFF => set_7_a,
unknown => panic!(
"Unrecognized prefixed opcode: {:#X}\nRegisters: {:#X?}",
unknown, state.registers
),
}(state); }(state);
res res
@ -405,6 +409,29 @@ define_rlc_reg!(0x04, h);
define_rlc_reg!(0x05, l); define_rlc_reg!(0x05, l);
define_rlc_reg!(0x07, a); define_rlc_reg!(0x07, a);
opcode!(rlc_deref_hl, 0x06, "RLC [HL]", true, 2, {
1 => {
state.cpu_read_u8(state.registers.get_hl());
CycleResult::NeedsMore
},
2 => {
let mut value = state.registers.take_mem();
let carry = value >> 7 == 1;
value <<= 1;
value |= carry as u8;
state.registers.set_zero(value == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.cpu_write_u8(state.registers.get_hl(), value);
CycleResult::NeedsMore
},
3 => {
CycleResult::Finished
}
});
macro_rules! define_rrc_reg { macro_rules! define_rrc_reg {
($op:literal, $reg:ident) => { ($op:literal, $reg:ident) => {
paste::paste! { paste::paste! {
@ -433,6 +460,30 @@ define_rrc_reg!(0x0C, h);
define_rrc_reg!(0x0D, l); define_rrc_reg!(0x0D, l);
define_rrc_reg!(0x0F, a); define_rrc_reg!(0x0F, a);
opcode!(rrc_deref_hl, 0x0E, "RRC [HL]", true, 2, {
1 => {
state.cpu_read_u8(state.registers.get_hl());
CycleResult::NeedsMore
},
2 => {
let mut value = state.registers.take_mem();
let carry = value & 0b1 == 1;
value >>= 1;
value |= (carry as u8) << 7;
state.registers.set_zero(value == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.cpu_write_u8(state.registers.get_hl(), value);
CycleResult::NeedsMore
},
3 => {
CycleResult::Finished
}
});
macro_rules! define_rl_reg { macro_rules! define_rl_reg {
($op:literal, $reg:ident) => { ($op:literal, $reg:ident) => {
paste::paste! { paste::paste! {
@ -464,13 +515,63 @@ define_rl_reg!(0x14, h);
define_rl_reg!(0x15, l); define_rl_reg!(0x15, l);
define_rl_reg!(0x17, a); define_rl_reg!(0x17, a);
opcode!(rl_deref_hl, 0x16, "RL [HL]", true, 2, {
1 => {
state.cpu_read_u8(state.registers.get_hl());
CycleResult::NeedsMore
},
2 => {
let mut value = state.registers.take_mem();
let carry = value >> 7 == 1;
value <<= 1;
if state.registers.get_carry() {
value |= 1;
}
state.registers.set_zero(value == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.cpu_write_u8(state.registers.get_hl(), value);
CycleResult::NeedsMore
},
3 => {
CycleResult::Finished
}
});
opcode!(sla_deref_hl, 0x26, "SLA [HL]", true, 2, {
1 => {
state.cpu_read_u8(state.registers.get_hl());
CycleResult::NeedsMore
},
2 => {
let mut value = state.registers.take_mem();
let carry = value >> 7 == 1;
value <<= 1;
state.registers.set_zero(value == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.cpu_write_u8(state.registers.get_hl(), value);
CycleResult::NeedsMore
},
3 => {
CycleResult::Finished
}
});
macro_rules! define_sla_reg { macro_rules! define_sla_reg {
($op:literal, $reg:ident) => { ($op:literal, $reg:ident) => {
paste::paste! { paste::paste! {
opcode!([<sla_ $reg>], $op, std::concat!("SLA ", std::stringify!($reg)), true, 2, { opcode!([<sla_ $reg>], $op, std::concat!("SLA ", std::stringify!($reg)), true, 2, {
1 => { 1 => {
let carry = state.registers.$reg & (0b1 << 7) == 1; let carry = state.registers.$reg >> 7 == 1;
state.registers.$reg = ((state.registers.$reg as i8) << 1) as u8; state.registers.$reg <<= 1;
state.registers.set_zero(state.registers.$reg == 0); state.registers.set_zero(state.registers.$reg == 0);
state.registers.set_subtract(false); state.registers.set_subtract(false);
@ -491,13 +592,40 @@ define_sla_reg!(0x24, h);
define_sla_reg!(0x25, l); define_sla_reg!(0x25, l);
define_sla_reg!(0x27, a); define_sla_reg!(0x27, a);
opcode!(sra_deref_hl, 0x2E, "SRA [HL]", true, 2, {
1 => {
state.cpu_read_u8(state.registers.get_hl());
CycleResult::NeedsMore
},
2 => {
let mut value = state.registers.take_mem();
let carry = value & 0b1 == 1;
let msb = value & (1 << 7);
value >>= 1;
value |= msb;
state.registers.set_zero(value == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.cpu_write_u8(state.registers.get_hl(), value);
CycleResult::NeedsMore
},
3 => {
CycleResult::Finished
}
});
macro_rules! define_sra_reg { macro_rules! define_sra_reg {
($op:literal, $reg:ident) => { ($op:literal, $reg:ident) => {
paste::paste! { paste::paste! {
opcode!([<sra_ $reg>], $op, std::concat!("SRA ", std::stringify!($reg)), true, 2, { opcode!([<sra_ $reg>], $op, std::concat!("SRA ", std::stringify!($reg)), true, 2, {
1 => { 1 => {
let carry = state.registers.$reg & 0b1 == 1; let carry = state.registers.$reg & 0b1 == 1;
state.registers.$reg = ((state.registers.$reg as i8) >> 1) as u8; let msb = state.registers.$reg & (1 << 7);
state.registers.$reg >>= 1;
state.registers.$reg |= msb;
state.registers.set_zero(state.registers.$reg == 0); state.registers.set_zero(state.registers.$reg == 0);
state.registers.set_subtract(false); state.registers.set_subtract(false);
@ -544,6 +672,51 @@ define_swap_reg!(0x34, h);
define_swap_reg!(0x35, l); define_swap_reg!(0x35, l);
define_swap_reg!(0x37, a); define_swap_reg!(0x37, a);
opcode!(swap_deref_hl, 0x36, "SWAP [HL]", true, 2, {
1 => {
state.cpu_read_u8(state.registers.get_hl());
CycleResult::NeedsMore
},
2 => {
let mut value = state.registers.take_mem();
value = (value >> 4) | (value << 4);
state.registers.set_zero(value == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(false);
state.cpu_write_u8(state.registers.get_hl(), value);
CycleResult::NeedsMore
},
3 => {
CycleResult::Finished
}
});
opcode!(srl_deref_hl, 0x3E, "SRL [HL]", true, 2, {
1 => {
state.cpu_read_u8(state.registers.get_hl());
CycleResult::NeedsMore
},
2 => {
let mut value = state.registers.take_mem();
let carry = value & 0b1 == 1;
value >>= 1;
state.registers.set_zero(value == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.cpu_write_u8(state.registers.get_hl(), value);
CycleResult::NeedsMore
},
3 => {
CycleResult::Finished
}
});
macro_rules! define_srl_reg { macro_rules! define_srl_reg {
($op:literal, $reg:ident) => { ($op:literal, $reg:ident) => {
paste::paste! { paste::paste! {
@ -579,6 +752,10 @@ macro_rules! define_rr_reg {
let carry = state.registers.$reg & 0b1 == 1; let carry = state.registers.$reg & 0b1 == 1;
state.registers.$reg >>= 1; state.registers.$reg >>= 1;
if state.registers.get_carry() {
state.registers.$reg |= 1 << 7;
}
state.registers.set_zero(state.registers.$reg == 0); state.registers.set_zero(state.registers.$reg == 0);
state.registers.set_subtract(false); state.registers.set_subtract(false);
state.registers.set_half_carry(false); state.registers.set_half_carry(false);
@ -598,6 +775,33 @@ define_rr_reg!(0x1c, h);
define_rr_reg!(0x1d, l); define_rr_reg!(0x1d, l);
define_rr_reg!(0x1f, a); define_rr_reg!(0x1f, a);
opcode!(rr_deref_hl, 0x1E, "RR [HL]", true, 2, {
1 => {
state.cpu_read_u8(state.registers.get_hl());
CycleResult::NeedsMore
},
2 => {
let mut value = state.registers.take_mem();
let carry = value & 0b1 == 1;
value >>= 1;
if state.registers.get_carry() {
value |= 1 << 7;
}
state.registers.set_zero(value == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.cpu_write_u8(state.registers.get_hl(), value);
CycleResult::NeedsMore
},
3 => {
CycleResult::Finished
}
});
macro_rules! define_res_idx_reg { macro_rules! define_res_idx_reg {
($op:literal, $idx:literal, $reg:ident) => { ($op:literal, $idx:literal, $reg:ident) => {
paste::paste! { paste::paste! {

View file

@ -18,13 +18,20 @@ macro_rules! define_bitfield_u8_gs {
pub struct Interrupts { pub struct Interrupts {
pub ime: bool, pub ime: bool,
pub ei_queued: bool, pub ei_queued: bool,
pub cycle_passed: bool,
pub interrupt_enable: u8, pub interrupt_enable: u8,
pub interrupt_flag: u8, pub interrupt_flag: u8,
} }
impl Interrupts { impl Interrupts {
pub fn new() -> Self { pub fn new() -> Self {
Self { ime: false, interrupt_enable: 0, interrupt_flag: 0, ei_queued: false } Self {
ime: false,
interrupt_enable: 0,
interrupt_flag: 0b11100000,
ei_queued: false,
cycle_passed: false,
}
} }
define_bitfield_u8_gs!(ie_vblank, 0, interrupt_enable); define_bitfield_u8_gs!(ie_vblank, 0, interrupt_enable);

View file

@ -9,8 +9,8 @@ pub trait Mapper {
} }
pub struct NoMBC { pub struct NoMBC {
rom: [u8; 0x8000], pub rom: [u8; 0x8000],
ram: Option<[u8; 0x2000]>, pub ram: Option<[u8; 0x2000]>,
} }
impl NoMBC { impl NoMBC {
@ -36,9 +36,7 @@ impl Mapper for NoMBC {
self.rom[address as usize] self.rom[address as usize]
} }
fn write_rom_u8(&mut self, address: u16, value: u8) { fn write_rom_u8(&mut self, _address: u16, _value: u8) {}
self.rom[address as usize] = value
}
fn read_eram_u8(&self, address: u16) -> u8 { fn read_eram_u8(&self, address: u16) -> u8 {
let decoded_address = address - 0xA000; let decoded_address = address - 0xA000;

View file

@ -85,7 +85,7 @@ impl Mapper for MBC1 {
(self.rom_bank_number | (self.extra_2_bit_reg << 5)) as usize * 0x4000 (self.rom_bank_number | (self.extra_2_bit_reg << 5)) as usize * 0x4000
} else { } else {
self.rom_bank_number as usize * 0x4000 self.rom_bank_number as usize * 0x4000
}] } + (address as usize - 0x4000)]
} }
} }
@ -101,14 +101,14 @@ impl Mapper for MBC1 {
fn read_eram_u8(&self, _address: u16) -> u8 { fn read_eram_u8(&self, _address: u16) -> u8 {
match self.ram.as_ref() { match self.ram.as_ref() {
Some(_ram) => unimplemented!(), Some(_ram) => 0,
None => 0, None => 0,
} }
} }
fn write_eram_u8(&mut self, _address: u16, _value: u8) { fn write_eram_u8(&mut self, _address: u16, _value: u8) {
match self.ram.as_ref() { match self.ram.as_ref() {
Some(_ram) => unimplemented!(), Some(_ram) => {}
None => {} None => {}
} }
} }

View file

@ -7,7 +7,12 @@ pub struct Memory {
} }
impl Memory { impl Memory {
pub fn new(bootrom: [u8; 0x100]) -> Self { pub fn new(bootrom: Option<[u8; 0x100]>) -> Self {
Self { wram: [0; 0x2000], hram: [0; 0xAF], bootrom, bootrom_disabled: false } Self {
wram: [0; 0x2000],
hram: [0; 0xAF],
bootrom: bootrom.unwrap_or_else(|| [0u8; 0x100]),
bootrom_disabled: bootrom.is_none(),
}
} }
} }

View file

@ -1,3 +1,4 @@
#[derive(Debug)]
pub struct Timer { pub struct Timer {
pub enable: bool, pub enable: bool,
pub clock: TimerClock, pub clock: TimerClock,
@ -6,6 +7,7 @@ pub struct Timer {
pub tima: u8, pub tima: u8,
pub tima_counter: u16, pub tima_counter: u16,
pub tma: u8, pub tma: u8,
overflow: bool,
} }
impl Timer { impl Timer {
@ -18,23 +20,32 @@ impl Timer {
div: 0, div: 0,
div_counter: 0, div_counter: 0,
tima_counter: 0, tima_counter: 0,
overflow: false,
} }
} }
pub fn tick(&mut self) -> bool { pub fn tick(&mut self) -> bool {
self.div_counter = self.div_counter.overflowing_add(1).0; self.div_counter = self.div_counter.wrapping_add(1);
if self.div_counter == 0 { if self.div_counter == 0 {
self.div = self.div.overflowing_add(1).0; self.div = self.div.wrapping_add(1);
} }
if self.enable { if self.enable {
self.tima_counter = self.tima_counter.overflowing_add(1).0; self.tima_counter = self.tima_counter.wrapping_add(4);
if self.tima_counter >= self.clock.cycles() { if self.tima_counter >= self.clock.cycles() {
self.tima = self.tima.overflowing_add(1).0; self.tima_counter = 0;
self.tima = self.tima.wrapping_add(1);
return self.tima == 0; self.overflow = self.tima == 0;
return false;
} }
} }
if self.overflow {
self.tima = self.tma;
self.overflow = false;
return true;
}
false false
} }
@ -44,7 +55,14 @@ impl Timer {
pub fn write_tac(&mut self, value: u8) { pub fn write_tac(&mut self, value: u8) {
self.enable = (value >> 2) & 0b1 == 1; self.enable = (value >> 2) & 0b1 == 1;
self.clock = TimerClock::from_tac_clock(value); self.tima_counter = 0;
let new_clock = TimerClock::from_tac_clock(value);
if self.clock == TimerClock::C16 && new_clock == TimerClock::C1024 && self.enable {
self.tima = self.tima.wrapping_add(1);
self.overflow = self.tima == 0;
}
self.clock = new_clock;
} }
} }

24
deemgee/src/lib.rs Normal file
View file

@ -0,0 +1,24 @@
pub mod gameboy;
#[allow(unused)]
mod settings;
#[allow(unused)]
mod window;
pub fn setup_test_emulator<const ROM_LENGTH: usize>(
test_opcodes: [u8; ROM_LENGTH],
) -> gameboy::Gameboy {
let mut gameboy = gameboy::Gameboy::new(None);
let mut cartridge = gameboy::mapper::NoMBC { rom: [0u8; 0x8000], ram: None };
(&mut cartridge.rom[0x100..ROM_LENGTH + 0x100]).copy_from_slice(&test_opcodes);
gameboy.cartridge = Some(Box::new(cartridge));
gameboy.tick(); // Prefetch instruction
assert!(gameboy.registers.mem_read_hold.is_some()); // Assert prefetch happened and opcode is now sitting in the memory bus
assert_eq!(gameboy.registers.cycle, 0); // Assert tick really did just prefetch instruction and not run the opcode at
// all
gameboy
}

View file

@ -3,12 +3,13 @@ mod settings;
mod window; mod window;
use std::{ use std::{
borrow::Cow,
path::PathBuf, path::PathBuf,
sync::mpsc::{channel, Receiver, Sender}, sync::mpsc::{channel, Receiver, Sender},
}; };
use argh::FromArgs;
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use clap::Parser;
use gameboy::Gameboy; use gameboy::Gameboy;
use settings::DeemgeeConfig; use settings::DeemgeeConfig;
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
@ -16,15 +17,18 @@ use window::EmulatorWindowEvent;
use crate::window::GameboyEvent; use crate::window::GameboyEvent;
#[derive(Debug, FromArgs)] #[derive(Debug, Parser)]
/// DMG Emulator /// DMG Emulator
pub struct CliArgs { pub struct CliArgs {
/// bootrom path /// bootrom path
#[argh(positional)] #[clap(long)]
pub bootrom: PathBuf, pub bootrom: Option<PathBuf>,
/// game path /// game path
#[argh(positional)] #[clap(long)]
pub rom: Option<PathBuf>, pub rom: Option<PathBuf>,
// enter in debu g mode
#[clap(short, long)]
pub debug: bool,
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
@ -44,18 +48,27 @@ pub enum DmgError {
fn main() { fn main() {
env_logger::init(); env_logger::init();
let args: CliArgs = argh::from_env(); let args: CliArgs = CliArgs::parse();
let config = DeemgeeConfig::from_file(); let config = DeemgeeConfig::from_file();
let (window_side_tx, gb_side_rx) = channel::<EmulatorWindowEvent>(); let (window_side_tx, gb_side_rx) = channel::<EmulatorWindowEvent>();
let (gb_side_tx, window_side_rx) = channel::<GameboyEvent>(); let (gb_side_tx, window_side_rx) = channel::<GameboyEvent>();
let rom_name = args.rom.as_ref().and_then(|path| {
path.file_name().and_then(|name| name.to_str().map(str::to_string).map(Cow::Owned))
});
let jh = std::thread::Builder::new() let jh = std::thread::Builder::new()
.name(String::from("mewmulator")) .name(String::from("mewmulator"))
.spawn(move || run_gameboy(config, args, gb_side_rx, gb_side_tx).unwrap()) .spawn(move || run_gameboy(config, args, gb_side_rx, gb_side_tx).unwrap())
.unwrap(); .unwrap();
window::run_window(config, window_side_rx, window_side_tx); window::run_window(
&rom_name.unwrap_or(Cow::Borrowed("NO GAME")),
config,
window_side_rx,
window_side_tx,
);
jh.join().unwrap(); jh.join().unwrap();
} }
@ -66,34 +79,48 @@ pub fn run_gameboy(
rx: Receiver<EmulatorWindowEvent>, rx: Receiver<EmulatorWindowEvent>,
tx: Sender<GameboyEvent>, tx: Sender<GameboyEvent>,
) -> Result<(), DmgError> { ) -> Result<(), DmgError> {
if !args.bootrom.is_file() { let mut bootrom = None;
return Err(DmgError::BootromNotFound); if let Some(bootrom_path) = args.bootrom {
if !bootrom_path.is_file() {
return Err(DmgError::BootromNotFound);
}
let brom_md = std::fs::metadata(bootrom_path.as_path())?;
if brom_md.len() != 256 {
return Err(DmgError::BootromInvalidSize(brom_md.len()));
}
let mut bootrom_slice = [0u8; 0x100];
let bootrom_vec = std::fs::read(bootrom_path)?;
if bootrom_vec.len() != 256 {
return Err(DmgError::BootromInvalidSize(bootrom_vec.len() as u64));
}
let mut hash_ctx = Sha1::new();
hash_ctx.update(&bootrom_vec);
let digest = hash_ctx.finalize();
if digest.as_slice()
!= b"\x4e\xd3\x1e\xc6\xb0\xb1\x75\xbb\x10\x9c\x0e\xb5\xfd\x3d\x19\x3d\xa8\x23\x33\x9f"
{
return Err(DmgError::BootromInvalidHash);
}
bootrom_slice.copy_from_slice(&bootrom_vec);
bootrom = Some(bootrom_slice)
} }
let brom_md = std::fs::metadata(args.bootrom.as_path())?; let mut gameboy = Gameboy::new(bootrom);
if brom_md.len() != 256 { if args.debug {
return Err(DmgError::BootromInvalidSize(brom_md.len())); gameboy.single_step = true;
tx.send(GameboyEvent::Framebuffer(gameboy.ppu.write_fb())).unwrap();
} }
let bootrom = std::fs::read(args.bootrom)?;
if bootrom.len() != 256 {
return Err(DmgError::BootromInvalidSize(bootrom.len() as u64));
}
let mut hash_ctx = Sha1::new();
hash_ctx.update(&bootrom);
let digest = hash_ctx.finalize();
if digest.as_slice()
!= b"\x4e\xd3\x1e\xc6\xb0\xb1\x75\xbb\x10\x9c\x0e\xb5\xfd\x3d\x19\x3d\xa8\x23\x33\x9f"
{
return Err(DmgError::BootromInvalidHash);
}
let mut gameboy = Gameboy::new(bootrom.as_slice().try_into().unwrap());
if let Some(rom) = args.rom { if let Some(rom) = args.rom {
if !rom.is_file() { if !rom.is_file() {
return Err(DmgError::GameNotFound); return Err(DmgError::GameNotFound);
@ -105,7 +132,6 @@ pub fn run_gameboy(
} }
let mut goal = chrono::Utc::now() + Duration::milliseconds(1000 / 60); let mut goal = chrono::Utc::now() + Duration::milliseconds(1000 / 60);
let mut paused = false;
let mut frame_counter = 0; let mut frame_counter = 0;
'outer: loop { 'outer: loop {
@ -129,7 +155,9 @@ pub fn run_gameboy(
window::EmulatorWindowEvent::RightToggle => { window::EmulatorWindowEvent::RightToggle => {
gameboy.joypad.set_right(!gameboy.joypad.right) gameboy.joypad.set_right(!gameboy.joypad.right)
} }
window::EmulatorWindowEvent::PauseToggle => paused = !paused, window::EmulatorWindowEvent::PauseToggle => {
gameboy.single_step = !gameboy.single_step
}
window::EmulatorWindowEvent::LogToggle => { window::EmulatorWindowEvent::LogToggle => {
gameboy.log_instructions = !gameboy.log_instructions gameboy.log_instructions = !gameboy.log_instructions
} }
@ -143,23 +171,26 @@ pub fn run_gameboy(
} }
} }
if !paused { let (redraw_needed, time_spent_debugging) = gameboy.tick();
let redraw_needed = gameboy.tick();
if redraw_needed {
let now = chrono::Utc::now();
frame_counter += 1;
tx.send(GameboyEvent::Framebuffer(gameboy.ppu.write_fb())).unwrap();
let delta = goal - now;
let delta_ms = delta.num_milliseconds();
if delta_ms > 0 {
std::thread::sleep(std::time::Duration::from_millis(delta_ms as u64));
}
goal = goal + Duration::milliseconds(1000 / 60);
if frame_counter == 60 { if let Some(diff) = time_spent_debugging {
log::info!("Rendered 60 frames"); goal = goal + Duration::milliseconds(diff);
frame_counter = 0; }
}
if redraw_needed {
let now = chrono::Utc::now();
frame_counter += 1;
tx.send(GameboyEvent::Framebuffer(gameboy.ppu.write_fb())).unwrap();
let delta = goal - now;
let delta_ms = delta.num_milliseconds();
if delta_ms > 0 {
std::thread::sleep(std::time::Duration::from_millis(delta_ms as u64));
}
goal = goal + Duration::milliseconds(1000 / 60);
if frame_counter == 60 {
log::debug!("Rendered 60 frames");
frame_counter = 0;
} }
} }
} }

View file

@ -93,6 +93,7 @@ impl Keymap {
} }
pub fn run_window( pub fn run_window(
rom_name: &str,
config: DeemgeeConfig, config: DeemgeeConfig,
rx: Receiver<GameboyEvent>, rx: Receiver<GameboyEvent>,
tx: Sender<EmulatorWindowEvent>, tx: Sender<EmulatorWindowEvent>,
@ -100,7 +101,9 @@ pub fn run_window(
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let mut input = WinitInputHelper::new(); let mut input = WinitInputHelper::new();
let window = { WindowBuilder::new().with_title("OwO").build(&event_loop).unwrap() }; let window = {
WindowBuilder::new().with_title(format!("Meow - {}", rom_name)).build(&event_loop).unwrap()
};
let mut pixels = { let mut pixels = {
let window_size = window.inner_size(); let window_size = window.inner_size();

View file

@ -0,0 +1,480 @@
use deemgee::setup_test_emulator;
macro_rules! conditional_jump_relative_testgen {
($flag:ident, $not_opcode:literal, $opcode:literal) => {
paste::paste! {
#[test]
fn [<test_jr_not_ $flag _i8_unset>]() {
let mut emulator = setup_test_emulator([$not_opcode, 0x1]);
emulator.registers.[<set_ $flag>](false);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x103);
}
#[test]
fn [<test_jr_not_ $flag _i8_set>]() {
let mut emulator = setup_test_emulator([$not_opcode, 0x1]);
emulator.registers.[<set_ $flag>](true);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x102);
}
#[test]
fn [<test_jr_ $flag _i8_set>]() {
let mut emulator = setup_test_emulator([$opcode, 0x1]);
emulator.registers.[<set_ $flag>](true);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x103);
}
#[test]
fn [<test_jr_ $flag _i8_unset>]() {
let mut emulator = setup_test_emulator([$opcode, 0x1]);
emulator.registers.[<set_ $flag>](false);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x102);
}
}
};
}
conditional_jump_relative_testgen!(zero, 0x20, 0x28);
conditional_jump_relative_testgen!(carry, 0x30, 0x38);
#[test]
fn test_jr_i8() {
let mut emulator = setup_test_emulator([0x18, 0x1]);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x103);
}
#[test]
fn test_jp_u16() {
let mut emulator = setup_test_emulator([0xC3, 0xFE, 0xCA]);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
#[test]
fn test_jp_hl() {
let mut emulator = setup_test_emulator([0xE9]);
emulator.registers.set_hl(0xCAFE);
emulator.tick();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
macro_rules! conditional_jump_testgen {
($flag:ident, $not_opcode:literal, $opcode:literal) => {
paste::paste! {
#[test]
fn [<test_jp_not_ $flag _u16_unset>]() {
let mut emulator = setup_test_emulator([$not_opcode, 0xFE, 0xCA]);
emulator.registers.[<set_ $flag>](false);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
#[test]
fn [<test_jp_not_ $flag _u16_set>]() {
let mut emulator = setup_test_emulator([$not_opcode, 0xFE, 0xCA]);
emulator.registers.[<set_ $flag>](true);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x103);
}
#[test]
fn [<test_jp_ $flag _u16_set>]() {
let mut emulator = setup_test_emulator([$opcode, 0xFE, 0xCA]);
emulator.registers.[<set_ $flag>](true);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
#[test]
fn [<test_jp_ $flag _u16_unset>]() {
let mut emulator = setup_test_emulator([$opcode, 0xFE, 0xCA]);
emulator.registers.[<set_ $flag>](false);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x103);
}
}
};
}
conditional_jump_testgen!(zero, 0xC2, 0xCA);
conditional_jump_testgen!(carry, 0xD2, 0xDA);
#[test]
fn test_call_u16() {
let mut emulator = setup_test_emulator([0xCD, 0xFE, 0xCA]);
let orignal_sp = emulator.registers.sp;
emulator.tick(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC hi to stack
assert_eq!(emulator.registers.sp, orignal_sp - 1);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC lo to stack
assert_eq!(emulator.registers.sp, orignal_sp - 2);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0xCAFE);
assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01);
assert_eq!(emulator.debug_read_u8(orignal_sp - 2), 0x03);
}
macro_rules! conditional_call_testgen {
($flag:ident, $not_opcode:literal, $opcode:literal) => {
paste::paste! {
#[test]
fn [<test_call_not_ $flag _u16_unset>]() {
let mut emulator = setup_test_emulator([$not_opcode, 0xFE, 0xCA]);
let orignal_sp = emulator.registers.sp;
emulator.registers.[<set_ $flag>](false);
emulator.tick(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Check flag
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC hi to stack
assert_eq!(emulator.registers.sp, orignal_sp - 1);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC lo to stack
assert_eq!(emulator.registers.sp, orignal_sp - 2);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0xCAFE);
assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01);
assert_eq!(emulator.debug_read_u8(orignal_sp - 2), 0x03);
}
#[test]
fn [<test_call_not_ $flag _u16_set>]() {
let mut emulator = setup_test_emulator([$not_opcode, 0xFE, 0xCA]);
let orignal_sp = emulator.registers.sp;
emulator.registers.[<set_ $flag>](true);
emulator.tick(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Check flag
assert_eq!(emulator.registers.pc, 0x103);
assert_eq!(emulator.registers.sp, orignal_sp);
}
#[test]
fn [<test_call_ $flag _u16_set>]() {
let mut emulator = setup_test_emulator([$opcode, 0xFE, 0xCA]);
let orignal_sp = emulator.registers.sp;
emulator.registers.[<set_ $flag>](true);
emulator.tick(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Check flag
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC hi to stack
assert_eq!(emulator.registers.sp, orignal_sp - 1);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC lo to stack
assert_eq!(emulator.registers.sp, orignal_sp - 2);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0xCAFE);
assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01);
assert_eq!(emulator.debug_read_u8(orignal_sp - 2), 0x03);
}
#[test]
fn [<test_call_ $flag _u16_unset>]() {
let mut emulator = setup_test_emulator([$opcode, 0xFE, 0xCA]);
let orignal_sp = emulator.registers.sp;
emulator.registers.[<set_ $flag>](false);
emulator.tick(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Check flag
assert_eq!(emulator.registers.pc, 0x103);
assert_eq!(emulator.registers.sp, orignal_sp);
}
}
};
}
conditional_call_testgen!(zero, 0xC4, 0xCC);
conditional_call_testgen!(carry, 0xD4, 0xDC);
#[test]
fn test_ret() {
let mut emulator = setup_test_emulator([0xC9]);
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xCA);
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xFE);
let orignal_sp = emulator.registers.sp;
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
#[test]
fn test_reti() {
let mut emulator = setup_test_emulator([0xD9]);
emulator.interrupts.ime = false;
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xCA);
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xFE);
let orignal_sp = emulator.registers.sp;
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0xCAFE);
assert_eq!(emulator.interrupts.ime, true);
}
macro_rules! conditional_ret_testgen {
($flag:ident, $not_opcode:literal, $opcode:literal) => {
paste::paste! {
#[test]
fn [<test_ret_not_ $flag _unset>]() {
let mut emulator = setup_test_emulator([$not_opcode]);
emulator.registers.[<set_ $flag>](false);
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xCA);
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xFE);
let orignal_sp = emulator.registers.sp;
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
#[test]
fn [<test_ret_not_ $flag _set>]() {
let mut emulator = setup_test_emulator([$not_opcode]);
emulator.registers.[<set_ $flag>](true);
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xCA);
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xFE);
let orignal_sp = emulator.registers.sp;
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x101);
assert_eq!(emulator.registers.sp, orignal_sp);
}
#[test]
fn [<test_ret_ $flag _set>]() {
let mut emulator = setup_test_emulator([$opcode]);
emulator.registers.[<set_ $flag>](true);
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xCA);
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xFE);
let orignal_sp = emulator.registers.sp;
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
#[test]
fn [<test_ret_ $flag _unset>]() {
let mut emulator = setup_test_emulator([$opcode]);
emulator.registers.[<set_ $flag>](false);
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xCA);
emulator.registers.sp = emulator.registers.sp.overflowing_sub(1).0;
emulator.debug_write_u8(emulator.registers.sp, 0xFE);
let orignal_sp = emulator.registers.sp;
emulator.tick();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, 0x101);
assert_eq!(emulator.registers.sp, orignal_sp);
}
}
};
}
conditional_ret_testgen!(zero, 0xC0, 0xC8);
conditional_ret_testgen!(carry, 0xD0, 0xD8);
macro_rules! rst_testgen {
($opcode:literal, $addr:literal) => {
paste::paste! {
#[test]
fn [<test_rst_ $addr>]() {
let mut emulator = setup_test_emulator([$opcode]);
let original_sp = emulator.registers.sp;
emulator.tick();
assert_eq!(emulator.registers.sp, original_sp);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.sp, original_sp - 1);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.sp, original_sp - 2);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.pc, $addr);
assert_eq!(emulator.debug_read_u8(original_sp - 1), 0x01);
assert_eq!(emulator.debug_read_u8(original_sp - 2), 0x01);
}
}
};
}
rst_testgen!(0xC7, 0x0);
rst_testgen!(0xCF, 0x08);
rst_testgen!(0xD7, 0x10);
rst_testgen!(0xDF, 0x18);
rst_testgen!(0xE7, 0x20);
rst_testgen!(0xEF, 0x28);
rst_testgen!(0xF7, 0x30);
rst_testgen!(0xFF, 0x38);

View file

@ -0,0 +1,64 @@
use deemgee::setup_test_emulator;
macro_rules! ld_reg_imm_u16_testgen {
($hireg:ident, $loreg:ident, $opcode:literal) => {
paste::paste! {
#[test]
fn [<test_ld_reg_ $hireg $loreg _imm_u16>]() {
let mut emulator = setup_test_emulator([$opcode, 0xFE, 0xCA]);
emulator.registers.$hireg = 0x00;
emulator.registers.$loreg = 0x00;
emulator.tick();
assert_eq!(emulator.registers.$hireg, 0x00);
assert_eq!(emulator.registers.$loreg, 0x00);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.$hireg, 0x00);
assert_eq!(emulator.registers.$loreg, 0xFE);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.$hireg, 0xCA);
assert_eq!(emulator.registers.$loreg, 0xFE);
assert_eq!(emulator.registers.pc, 0x103);
}
}
};
}
ld_reg_imm_u16_testgen!(b, c, 0x01);
ld_reg_imm_u16_testgen!(d, e, 0x11);
ld_reg_imm_u16_testgen!(h, l, 0x21);
#[test]
fn test_ld_reg_sp_imm_u16() {
let mut emulator = setup_test_emulator([0x31, 0xFE, 0xCA]);
emulator.registers.sp = 0x0000;
emulator.tick();
assert_eq!(emulator.registers.sp, 0x0000);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.sp, 0x00FE);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.sp, 0xCAFE);
assert_eq!(emulator.registers.pc, 0x103);
}
#[test]
fn test_ld_sp_hl() {
let mut emulator = setup_test_emulator([0xF9]);
emulator.registers.sp = 0x0000;
emulator.registers.set_hl(0xCAFE);
emulator.tick();
assert_eq!(emulator.registers.sp, 0x00FE);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
assert_eq!(emulator.registers.sp, 0xCAFE);
assert_eq!(emulator.registers.pc, 0x101);
}

View file

@ -0,0 +1,69 @@
use deemgee::setup_test_emulator;
#[test]
fn test_nop() {
let mut emulator = setup_test_emulator([0x00]);
let expected_register_state = {
let mut state = emulator.registers.clone();
state.pc += 1;
state
};
emulator.tick();
assert_eq!(emulator.registers, expected_register_state);
}
#[test]
fn test_di() {
let mut emulator = setup_test_emulator([0xF3]);
let expected_register_state = {
let mut state = emulator.registers.clone();
state.pc += 1;
state
};
emulator.interrupts.ime = true;
emulator.tick();
assert_eq!(emulator.registers, expected_register_state);
assert!(!emulator.interrupts.ime);
}
#[test]
fn test_ei() {
let mut emulator = setup_test_emulator([0xFB]);
let expected_register_state = {
let mut state = emulator.registers.clone();
state.pc += 1;
state
};
emulator.interrupts.ime = false;
emulator.tick();
assert_eq!(emulator.registers, expected_register_state);
assert!(!emulator.interrupts.ime);
emulator.tick(); // <-- Execute the NOP that comes after as the `EI` instruction only takes
// effect a cycle after
assert!(emulator.interrupts.ime);
}
#[test]
fn test_halt() {
let mut emulator = setup_test_emulator([0x76]);
let expected_register_state = {
let mut state = emulator.registers.clone();
state.pc += 1;
state
};
emulator.interrupts.ime = true;
emulator.tick();
assert_eq!(emulator.registers, expected_register_state);
assert!(emulator.halt);
}