feat: missing opcodes, pass all blargg cpu tests
This commit is contained in:
parent
a9c1ac65b3
commit
ce077f9069
20 changed files with 1476 additions and 214 deletions
145
Cargo.lock
generated
145
Cargo.lock
generated
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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! {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -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! {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
24
deemgee/src/lib.rs
Normal 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
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
480
deemgee/tests/flow_opcodes.rs
Normal file
480
deemgee/tests/flow_opcodes.rs
Normal 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);
|
64
deemgee/tests/load_store_move_opcodes.rs
Normal file
64
deemgee/tests/load_store_move_opcodes.rs
Normal 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);
|
||||||
|
}
|
69
deemgee/tests/misc_opcodes.rs
Normal file
69
deemgee/tests/misc_opcodes.rs
Normal 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);
|
||||||
|
}
|
Loading…
Reference in a new issue