feat: majorly improved PPU

This commit is contained in:
EliseZeroTwo 2024-01-01 22:11:26 -07:00
parent ce077f9069
commit 85d61764e8
Signed by: elise
GPG key ID: FA8F56FFFE6E8B3E
12 changed files with 1092 additions and 623 deletions

View file

@ -3,7 +3,7 @@ mod interrupts;
mod joypad; mod joypad;
pub mod mapper; pub mod mapper;
mod memory; mod memory;
mod ppu; pub mod ppu;
mod serial; mod serial;
mod sound; mod sound;
mod timer; mod timer;
@ -25,18 +25,16 @@ use self::{
pub struct DmaState { pub struct DmaState {
pub base: u8, pub base: u8,
pub remaining_cycles: u8, pub remaining_cycles: u8,
pub remaining_delay: u8,
} }
impl DmaState { impl DmaState {
pub fn new() -> Self { pub fn new() -> Self {
Self { base: 0, remaining_cycles: 0, remaining_delay: 0 } Self { base: 0, remaining_cycles: 0 }
} }
pub fn init_request(&mut self, base: u8) { pub fn init_request(&mut self, base: u8) {
self.base = base; self.base = base;
self.remaining_cycles = 0xA0; self.remaining_cycles = 0xA0;
self.remaining_delay = 2;
} }
} }
@ -135,6 +133,8 @@ pub struct Gameboy {
pub stop: bool, pub stop: bool,
pub pc_history: RingBuffer<u16, 0x200>, pub pc_history: RingBuffer<u16, 0x200>,
pub tick_count: u8,
} }
impl Gameboy { impl Gameboy {
@ -147,7 +147,7 @@ impl Gameboy {
joypad: Joypad::new(), joypad: Joypad::new(),
serial: Serial::new(), serial: Serial::new(),
dma: DmaState::new(), dma: DmaState::new(),
ppu: Ppu::new(), ppu: Ppu::new(bootrom.is_some()),
registers: match bootrom.is_some() { registers: match bootrom.is_some() {
true => Registers::default(), true => Registers::default(),
false => Registers::post_rom(), false => Registers::post_rom(),
@ -164,6 +164,7 @@ impl Gameboy {
used_halt_bug: false, used_halt_bug: false,
stop: false, stop: false,
pc_history: RingBuffer::new(), pc_history: RingBuffer::new(),
tick_count: 0,
} }
} }
@ -188,6 +189,9 @@ impl Gameboy {
} }
pub fn load_cartridge(&mut self, bytes: Vec<u8>) { pub fn load_cartridge(&mut self, bytes: Vec<u8>) {
if bytes.len() < 0x150 {
panic!("Bad cartridge (len < 0x150)");
}
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))),
@ -201,7 +205,23 @@ 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_4(&mut self) -> (bool, Option<i64>) {
let mut request_redraw = false;
let mut debug_time = None;
for _ in 0..4 {
let (t_request_redraw, t_debug_time) = self.tick();
request_redraw |= t_request_redraw;
if t_debug_time.is_some() {
assert!(debug_time.is_none());
debug_time = t_debug_time;
}
}
(request_redraw, debug_time)
}
pub fn tick(&mut self) -> (bool, Option<i64>) { pub fn tick(&mut self) -> (bool, Option<i64>) {
if self.tick_count == 0 {
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);
@ -218,14 +238,17 @@ impl Gameboy {
match std::io::stdin().read_line(&mut input) { match std::io::stdin().read_line(&mut input) {
Ok(_) => { Ok(_) => {
let lower = input.trim_end().to_lowercase(); let lower = input.trim_end().to_lowercase();
let (lhs, rhs) = lower.split_once(' ').unwrap_or_else(|| (lower.as_str(), "")); let (lhs, rhs) =
lower.split_once(' ').unwrap_or_else(|| (lower.as_str(), ""));
match lhs { match lhs {
"read" => match u16::from_str_radix(rhs, 16) { "read" => match u16::from_str_radix(rhs, 16) {
Ok(address) => { Ok(address) => {
let res = self.internal_cpu_read_u8(address); let res = self.internal_cpu_read_u8(address);
log::info!("{:#X}: {:#X} ({:#b})", address, res, res); log::info!("{:#X}: {:#X} ({:#b})", address, res, res);
} }
Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"), Err(_) => {
log::error!("Failed to parse input as hex u16 (f.ex 420C)")
}
}, },
"regs" => self.log_state(), "regs" => self.log_state(),
"op" => { "op" => {
@ -240,33 +263,49 @@ impl Gameboy {
false => log::info!("Cleared breakpoint @ {:#X}", address), false => log::info!("Cleared breakpoint @ {:#X}", address),
} }
} }
Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"), Err(_) => {
log::error!("Failed to parse input as hex u16 (f.ex 420C)")
}
}, },
"bpr" => match u16::from_str_radix(rhs, 16) { "bpr" => match u16::from_str_radix(rhs, 16) {
Ok(address) => { Ok(address) => {
let bp = &mut self.mem_read_breakpoints[address as usize]; let bp = &mut self.mem_read_breakpoints[address as usize];
*bp = !*bp; *bp = !*bp;
match *bp { match *bp {
true => log::info!("Set breakpoint on read @ {:#X}", address), true => {
log::info!("Set breakpoint on read @ {:#X}", address)
}
false => { false => {
log::info!("Cleared breakpoint on read @ {:#X}", address) log::info!(
"Cleared breakpoint on read @ {:#X}",
address
)
} }
} }
} }
Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"), Err(_) => {
log::error!("Failed to parse input as hex u16 (f.ex 420C)")
}
}, },
"bpw" => match u16::from_str_radix(rhs, 16) { "bpw" => match u16::from_str_radix(rhs, 16) {
Ok(address) => { Ok(address) => {
let bp = &mut self.mem_write_breakpoints[address as usize]; let bp = &mut self.mem_write_breakpoints[address as usize];
*bp = !*bp; *bp = !*bp;
match *bp { match *bp {
true => log::info!("Set breakpoint on write @ {:#X}", address), true => {
log::info!("Set breakpoint on write @ {:#X}", address)
}
false => { false => {
log::info!("Cleared breakpoint on write @ {:#X}", address) log::info!(
"Cleared breakpoint on write @ {:#X}",
address
)
} }
} }
} }
Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"), Err(_) => {
log::error!("Failed to parse input as hex u16 (f.ex 420C)")
}
}, },
"c" => { "c" => {
self.single_step = false; self.single_step = false;
@ -274,7 +313,10 @@ impl Gameboy {
exit = false; exit = false;
} }
"timer" => { "timer" => {
println!("-- Timer Info --\n{:#?}\n-- End of Timer Info --", self.timer) println!(
"-- Timer Info --\n{:#?}\n-- End of Timer Info --",
self.timer
)
} }
"p" | "pause" => { "p" | "pause" => {
self.single_step = true; self.single_step = true;
@ -361,13 +403,19 @@ impl Gameboy {
if self.serial.tick() { if self.serial.tick() {
self.interrupts.write_if_serial(true); self.interrupts.write_if_serial(true);
} }
self.tick_count += 1;
(redraw_requested, diff) (redraw_requested, diff)
} else {
let redraw_requested = self.ppu.tick(&mut self.interrupts);
self.tick_count += 1;
self.tick_count %= 4;
(redraw_requested, None)
}
} }
fn tick_dma(&mut self) { fn tick_dma(&mut self) {
if self.dma.remaining_delay > 0 { self.ppu.dma_occuring = self.dma.remaining_cycles > 0;
self.dma.remaining_delay -= 1; if self.dma.remaining_cycles > 0 {
} else if self.dma.remaining_cycles > 0 {
let offset = 0xA0 - self.dma.remaining_cycles; let offset = 0xA0 - self.dma.remaining_cycles;
let value = if self.dma.base <= 0x7F { let value = if self.dma.base <= 0x7F {
@ -376,13 +424,14 @@ impl Gameboy {
None => 0xFF, None => 0xFF,
} }
} else if self.dma.base <= 0x9F { } else if self.dma.base <= 0x9F {
self.ppu.dma_read_vram(offset) let address = (((self.dma.base as usize) << 8) | offset as usize) - 0x8000;
self.ppu.vram[address]
} else if self.dma.base <= 0xDF { } else if self.dma.base <= 0xDF {
let address = (self.dma.base as u16) << 8 | offset as u16; let address = ((self.dma.base as usize) << 8 | offset as usize) - 0xC000;
self.memory.wram[address as usize - 0xC000] self.memory.wram[address]
} else if self.dma.base <= 0xFD { } else if self.dma.base <= 0xFD {
let address = (self.dma.base as u16) << 8 | offset as u16; let address = ((self.dma.base as usize) << 8 | offset as usize) - 0xE000;
self.memory.wram[address as usize - 0xE000] self.memory.wram[address]
} else { } else {
0xFF 0xFF
}; };
@ -430,13 +479,15 @@ impl Gameboy {
0xFF27..=0xFF2F => 0xFF, 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.get_stat(),
0xFF42 => self.ppu.scy, 0xFF42 => self.ppu.scy,
0xFF43 => self.ppu.scx, 0xFF43 => self.ppu.scx,
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 => 0xFF, 0xFF47 => self.ppu.bgp.value(),
0xFF48 => self.ppu.obp[0].value(),
0xFF49 => self.ppu.obp[1].value(),
0xFF4A => self.ppu.wy, 0xFF4A => self.ppu.wy,
0xFF4B => self.ppu.wx, 0xFF4B => self.ppu.wx,
0xFF4C..=0xFF4E => 0xFF, // Unused 0xFF4C..=0xFF4E => 0xFF, // Unused
@ -489,18 +540,29 @@ impl Gameboy {
0xFF26 => self.sound.nr52 = value, 0xFF26 => self.sound.nr52 = value,
0xFF27..=0xFF2F => {} 0xFF27..=0xFF2F => {}
0xFF30..=0xFF3F => self.sound.wave_pattern_ram[address as usize - 0xFF30] = value, 0xFF30..=0xFF3F => self.sound.wave_pattern_ram[address as usize - 0xFF30] = value,
0xFF40 => self.ppu.lcdc = value, 0xFF40 => {
0xFF41 => self.ppu.cpu_write_stat(value), let old_value = self.ppu.lcdc;
self.ppu.lcdc = value;
if value >> 7 == 0 && old_value >> 7 == 1 {
self.ppu.stop();
} else if value >> 7 == 1 && old_value >> 7 == 0 {
self.ppu.start(&mut self.interrupts);
}
}
0xFF41 => self.ppu.set_stat(&mut self.interrupts, value),
0xFF42 => self.ppu.scy = value, 0xFF42 => self.ppu.scy = value,
0xFF43 => self.ppu.scx = value, 0xFF43 => self.ppu.scx = value,
0xFF44 => {} // LY is read only 0xFF44 => {} // LY is read only
0xFF45 => self.ppu.lyc = value, 0xFF45 => self.ppu.set_lyc(&mut self.interrupts, value),
0xFF46 => { 0xFF46 => {
if self.dma.remaining_cycles == 0 { if self.dma.remaining_cycles == 0 {
self.dma.init_request(value); self.dma.init_request(value);
} }
} }
0xFF47..=0xFF49 => {} 0xFF47 => self.ppu.bgp.write_bgp(value),
0xFF48 => self.ppu.obp[0].write_obp(value),
0xFF49 => self.ppu.obp[1].write_obp(value),
0xFF4A => self.ppu.wy = value, 0xFF4A => self.ppu.wy = value,
0xFF4B => self.ppu.wx = value, 0xFF4B => self.ppu.wx = value,
0xFF4C..=0xFF4E => {} // Unused 0xFF4C..=0xFF4E => {} // Unused

View file

@ -188,7 +188,8 @@ 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.cycle_passed = true; state.interrupts.ime = state.interrupts.ei_queued;
state.interrupts.ei_queued = false;
} }
if state.registers.cycle == 0 && state.halt_bug { if state.registers.cycle == 0 && state.halt_bug {
@ -527,12 +528,6 @@ pub fn tick_cpu(state: &mut Gameboy) {
state.registers.pc = state.registers.pc.overflowing_add(1).0; state.registers.pc = state.registers.pc.overflowing_add(1).0;
} }
if state.interrupts.cycle_passed && state.interrupts.ei_queued {
state.interrupts.cycle_passed = false;
state.interrupts.ei_queued = false;
state.interrupts.ime = true;
}
if result == CycleResult::Finished { if result == CycleResult::Finished {
match state.registers.opcode_bytecount { match state.registers.opcode_bytecount {
Some(len) => state.registers.pc = state.registers.pc.overflowing_add(len as u16).0, Some(len) => state.registers.pc = state.registers.pc.overflowing_add(len as u16).0,

View file

@ -2,6 +2,7 @@
pub enum JoypadMode { pub enum JoypadMode {
Action, Action,
Direction, Direction,
Both,
} }
macro_rules! joypad_input { macro_rules! joypad_input {
@ -17,6 +18,7 @@ macro_rules! joypad_input {
}; };
} }
#[derive(Debug)]
pub struct Joypad { pub struct Joypad {
mode: JoypadMode, mode: JoypadMode,
pub down: bool, pub down: bool,
@ -33,7 +35,7 @@ pub struct Joypad {
impl Joypad { impl Joypad {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
mode: JoypadMode::Action, mode: JoypadMode::Direction,
down: false, down: false,
up: false, up: false,
left: false, left: false,
@ -62,6 +64,7 @@ impl Joypad {
| ((!self.left as u8) << 1) | ((!self.left as u8) << 1)
| (!self.right as u8) | (!self.right as u8)
} }
JoypadMode::Both => 0x3F,
} }
} }
@ -77,10 +80,10 @@ impl Joypad {
pub fn cpu_write(&mut self, content: u8) { pub fn cpu_write(&mut self, content: u8) {
if (content >> 5) & 0b1 == 0 { if (content >> 5) & 0b1 == 0 {
self.mode = JoypadMode::Action; self.mode = JoypadMode::Action;
} } else if (content >> 4) & 0b1 == 0 {
if (content >> 4) & 0b1 == 0 {
self.mode = JoypadMode::Direction; self.mode = JoypadMode::Direction;
} else {
self.mode = JoypadMode::Both;
} }
} }
} }

View file

@ -63,7 +63,7 @@ impl MBC1 {
} }
fn is_large_rom(&self) -> bool { fn is_large_rom(&self) -> bool {
self.rom_bank_count > 4 self.rom_bank_count >= 64
} }
#[allow(unused)] #[allow(unused)]

File diff suppressed because it is too large Load diff

View file

@ -15,7 +15,7 @@ pub fn setup_test_emulator<const ROM_LENGTH: usize>(
gameboy.cartridge = Some(Box::new(cartridge)); gameboy.cartridge = Some(Box::new(cartridge));
gameboy.tick(); // Prefetch instruction gameboy.tick_4(); // Prefetch instruction
assert!(gameboy.registers.mem_read_hold.is_some()); // Assert prefetch happened and opcode is now sitting in the memory bus 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 assert_eq!(gameboy.registers.cycle, 0); // Assert tick really did just prefetch instruction and not run the opcode at
// all // all

View file

@ -171,7 +171,7 @@ pub fn run_gameboy(
} }
} }
let (redraw_needed, time_spent_debugging) = gameboy.tick(); let (redraw_needed, time_spent_debugging) = gameboy.tick_4();
if let Some(diff) = time_spent_debugging { if let Some(diff) = time_spent_debugging {
goal = goal + Duration::milliseconds(diff); goal = goal + Duration::milliseconds(diff);

View file

@ -50,9 +50,6 @@ pub enum GameboyEvent {
Framebuffer(Vec<u8>), Framebuffer(Vec<u8>),
} }
pub const FB_HEIGHT: u32 = 144;
pub const FB_WIDTH: u32 = 160;
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct Keymap { struct Keymap {
pub down: bool, pub down: bool,
@ -108,7 +105,12 @@ pub fn run_window(
let mut pixels = { let mut pixels = {
let window_size = window.inner_size(); let window_size = window.inner_size();
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
Pixels::new(FB_WIDTH, FB_HEIGHT, surface_texture).unwrap() Pixels::new(
crate::gameboy::ppu::FB_WIDTH,
crate::gameboy::ppu::FB_HEIGHT,
surface_texture,
)
.unwrap()
}; };
let mut redraw_happened = true; let mut redraw_happened = true;

View file

@ -9,11 +9,11 @@ macro_rules! conditional_jump_relative_testgen {
emulator.registers.[<set_ $flag>](false); emulator.registers.[<set_ $flag>](false);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x103); assert_eq!(emulator.registers.pc, 0x103);
} }
@ -23,9 +23,9 @@ macro_rules! conditional_jump_relative_testgen {
emulator.registers.[<set_ $flag>](true); emulator.registers.[<set_ $flag>](true);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x102); assert_eq!(emulator.registers.pc, 0x102);
} }
@ -35,11 +35,11 @@ macro_rules! conditional_jump_relative_testgen {
emulator.registers.[<set_ $flag>](true); emulator.registers.[<set_ $flag>](true);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x103); assert_eq!(emulator.registers.pc, 0x103);
} }
@ -49,9 +49,9 @@ macro_rules! conditional_jump_relative_testgen {
emulator.registers.[<set_ $flag>](false); emulator.registers.[<set_ $flag>](false);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x102); assert_eq!(emulator.registers.pc, 0x102);
} }
} }
@ -65,11 +65,11 @@ conditional_jump_relative_testgen!(carry, 0x30, 0x38);
fn test_jr_i8() { fn test_jr_i8() {
let mut emulator = setup_test_emulator([0x18, 0x1]); let mut emulator = setup_test_emulator([0x18, 0x1]);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x103); assert_eq!(emulator.registers.pc, 0x103);
} }
@ -77,13 +77,13 @@ fn test_jr_i8() {
fn test_jp_u16() { fn test_jp_u16() {
let mut emulator = setup_test_emulator([0xC3, 0xFE, 0xCA]); let mut emulator = setup_test_emulator([0xC3, 0xFE, 0xCA]);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.registers.pc, 0xCAFE);
} }
@ -93,7 +93,7 @@ fn test_jp_hl() {
emulator.registers.set_hl(0xCAFE); emulator.registers.set_hl(0xCAFE);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.registers.pc, 0xCAFE);
} }
@ -106,13 +106,13 @@ macro_rules! conditional_jump_testgen {
emulator.registers.[<set_ $flag>](false); emulator.registers.[<set_ $flag>](false);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.registers.pc, 0xCAFE);
} }
@ -122,11 +122,11 @@ macro_rules! conditional_jump_testgen {
emulator.registers.[<set_ $flag>](true); emulator.registers.[<set_ $flag>](true);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x103); assert_eq!(emulator.registers.pc, 0x103);
} }
@ -136,13 +136,13 @@ macro_rules! conditional_jump_testgen {
emulator.registers.[<set_ $flag>](true); emulator.registers.[<set_ $flag>](true);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.registers.pc, 0xCAFE);
} }
@ -152,11 +152,11 @@ macro_rules! conditional_jump_testgen {
emulator.registers.[<set_ $flag>](false); emulator.registers.[<set_ $flag>](false);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x103); assert_eq!(emulator.registers.pc, 0x103);
} }
} }
@ -172,19 +172,19 @@ fn test_call_u16() {
let orignal_sp = emulator.registers.sp; let orignal_sp = emulator.registers.sp;
emulator.tick(); // <-- Read first u8 emulator.tick_4(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8 emulator.tick_4(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC hi to stack emulator.tick_4(); // <-- Push next instruction PC hi to stack
assert_eq!(emulator.registers.sp, orignal_sp - 1); assert_eq!(emulator.registers.sp, orignal_sp - 1);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC lo to stack emulator.tick_4(); // <-- Push next instruction PC lo to stack
assert_eq!(emulator.registers.sp, orignal_sp - 2); assert_eq!(emulator.registers.sp, orignal_sp - 2);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.registers.pc, 0xCAFE);
assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01); assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01);
@ -202,19 +202,19 @@ macro_rules! conditional_call_testgen {
emulator.registers.[<set_ $flag>](false); emulator.registers.[<set_ $flag>](false);
emulator.tick(); // <-- Read first u8 emulator.tick_4(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8 emulator.tick_4(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Check flag emulator.tick_4(); // <-- Check flag
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC hi to stack emulator.tick_4(); // <-- Push next instruction PC hi to stack
assert_eq!(emulator.registers.sp, orignal_sp - 1); assert_eq!(emulator.registers.sp, orignal_sp - 1);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC lo to stack emulator.tick_4(); // <-- Push next instruction PC lo to stack
assert_eq!(emulator.registers.sp, orignal_sp - 2); assert_eq!(emulator.registers.sp, orignal_sp - 2);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.registers.pc, 0xCAFE);
assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01); assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01);
@ -229,11 +229,11 @@ macro_rules! conditional_call_testgen {
emulator.registers.[<set_ $flag>](true); emulator.registers.[<set_ $flag>](true);
emulator.tick(); // <-- Read first u8 emulator.tick_4(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8 emulator.tick_4(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Check flag emulator.tick_4(); // <-- Check flag
assert_eq!(emulator.registers.pc, 0x103); assert_eq!(emulator.registers.pc, 0x103);
assert_eq!(emulator.registers.sp, orignal_sp); assert_eq!(emulator.registers.sp, orignal_sp);
@ -247,19 +247,19 @@ macro_rules! conditional_call_testgen {
emulator.registers.[<set_ $flag>](true); emulator.registers.[<set_ $flag>](true);
emulator.tick(); // <-- Read first u8 emulator.tick_4(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8 emulator.tick_4(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Check flag emulator.tick_4(); // <-- Check flag
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC hi to stack emulator.tick_4(); // <-- Push next instruction PC hi to stack
assert_eq!(emulator.registers.sp, orignal_sp - 1); assert_eq!(emulator.registers.sp, orignal_sp - 1);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Push next instruction PC lo to stack emulator.tick_4(); // <-- Push next instruction PC lo to stack
assert_eq!(emulator.registers.sp, orignal_sp - 2); assert_eq!(emulator.registers.sp, orignal_sp - 2);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.registers.pc, 0xCAFE);
assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01); assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01);
@ -274,11 +274,11 @@ macro_rules! conditional_call_testgen {
emulator.registers.[<set_ $flag>](false); emulator.registers.[<set_ $flag>](false);
emulator.tick(); // <-- Read first u8 emulator.tick_4(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8 emulator.tick_4(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Check flag emulator.tick_4(); // <-- Check flag
assert_eq!(emulator.registers.pc, 0x103); assert_eq!(emulator.registers.pc, 0x103);
assert_eq!(emulator.registers.sp, orignal_sp); assert_eq!(emulator.registers.sp, orignal_sp);
@ -301,15 +301,15 @@ fn test_ret() {
let orignal_sp = emulator.registers.sp; let orignal_sp = emulator.registers.sp;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1); assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2); assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.registers.pc, 0xCAFE);
} }
@ -326,15 +326,15 @@ fn test_reti() {
let orignal_sp = emulator.registers.sp; let orignal_sp = emulator.registers.sp;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1); assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2); assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.registers.pc, 0xCAFE);
assert_eq!(emulator.interrupts.ime, true); assert_eq!(emulator.interrupts.ime, true);
} }
@ -355,17 +355,17 @@ macro_rules! conditional_ret_testgen {
let orignal_sp = emulator.registers.sp; let orignal_sp = emulator.registers.sp;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1); assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2); assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.registers.pc, 0xCAFE);
} }
@ -382,9 +382,9 @@ macro_rules! conditional_ret_testgen {
let orignal_sp = emulator.registers.sp; let orignal_sp = emulator.registers.sp;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x101); assert_eq!(emulator.registers.pc, 0x101);
assert_eq!(emulator.registers.sp, orignal_sp); assert_eq!(emulator.registers.sp, orignal_sp);
} }
@ -402,17 +402,17 @@ macro_rules! conditional_ret_testgen {
let orignal_sp = emulator.registers.sp; let orignal_sp = emulator.registers.sp;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1); assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2); assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.registers.pc, 0xCAFE);
} }
@ -429,9 +429,9 @@ macro_rules! conditional_ret_testgen {
let orignal_sp = emulator.registers.sp; let orignal_sp = emulator.registers.sp;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x101); assert_eq!(emulator.registers.pc, 0x101);
assert_eq!(emulator.registers.sp, orignal_sp); assert_eq!(emulator.registers.sp, orignal_sp);
} }
@ -451,16 +451,16 @@ macro_rules! rst_testgen {
let original_sp = emulator.registers.sp; let original_sp = emulator.registers.sp;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.sp, original_sp); assert_eq!(emulator.registers.sp, original_sp);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.sp, original_sp - 1); assert_eq!(emulator.registers.sp, original_sp - 1);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.sp, original_sp - 2); assert_eq!(emulator.registers.sp, original_sp - 2);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.pc, $addr); assert_eq!(emulator.registers.pc, $addr);
assert_eq!(emulator.debug_read_u8(original_sp - 1), 0x01); assert_eq!(emulator.debug_read_u8(original_sp - 1), 0x01);

View file

@ -10,15 +10,15 @@ macro_rules! ld_reg_imm_u16_testgen {
emulator.registers.$hireg = 0x00; emulator.registers.$hireg = 0x00;
emulator.registers.$loreg = 0x00; emulator.registers.$loreg = 0x00;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.$hireg, 0x00); assert_eq!(emulator.registers.$hireg, 0x00);
assert_eq!(emulator.registers.$loreg, 0x00); assert_eq!(emulator.registers.$loreg, 0x00);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.$hireg, 0x00); assert_eq!(emulator.registers.$hireg, 0x00);
assert_eq!(emulator.registers.$loreg, 0xFE); assert_eq!(emulator.registers.$loreg, 0xFE);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.$hireg, 0xCA); assert_eq!(emulator.registers.$hireg, 0xCA);
assert_eq!(emulator.registers.$loreg, 0xFE); assert_eq!(emulator.registers.$loreg, 0xFE);
assert_eq!(emulator.registers.pc, 0x103); assert_eq!(emulator.registers.pc, 0x103);
@ -37,13 +37,13 @@ fn test_ld_reg_sp_imm_u16() {
emulator.registers.sp = 0x0000; emulator.registers.sp = 0x0000;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.sp, 0x0000); assert_eq!(emulator.registers.sp, 0x0000);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.sp, 0x00FE); assert_eq!(emulator.registers.sp, 0x00FE);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.sp, 0xCAFE); assert_eq!(emulator.registers.sp, 0xCAFE);
assert_eq!(emulator.registers.pc, 0x103); assert_eq!(emulator.registers.pc, 0x103);
} }
@ -55,10 +55,10 @@ fn test_ld_sp_hl() {
emulator.registers.sp = 0x0000; emulator.registers.sp = 0x0000;
emulator.registers.set_hl(0xCAFE); emulator.registers.set_hl(0xCAFE);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.sp, 0x00FE); assert_eq!(emulator.registers.sp, 0x00FE);
assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers.sp, 0xCAFE); assert_eq!(emulator.registers.sp, 0xCAFE);
assert_eq!(emulator.registers.pc, 0x101); assert_eq!(emulator.registers.pc, 0x101);
} }

View file

@ -10,7 +10,7 @@ fn test_nop() {
state state
}; };
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers, expected_register_state); assert_eq!(emulator.registers, expected_register_state);
} }
@ -26,7 +26,7 @@ fn test_di() {
emulator.interrupts.ime = true; emulator.interrupts.ime = true;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers, expected_register_state); assert_eq!(emulator.registers, expected_register_state);
assert!(!emulator.interrupts.ime); assert!(!emulator.interrupts.ime);
} }
@ -43,10 +43,10 @@ fn test_ei() {
emulator.interrupts.ime = false; emulator.interrupts.ime = false;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers, expected_register_state); assert_eq!(emulator.registers, expected_register_state);
assert!(!emulator.interrupts.ime); assert!(!emulator.interrupts.ime);
emulator.tick(); // <-- Execute the NOP that comes after as the `EI` instruction only takes emulator.tick_4(); // <-- Execute the NOP that comes after as the `EI` instruction only takes
// effect a cycle after // effect a cycle after
assert!(emulator.interrupts.ime); assert!(emulator.interrupts.ime);
} }
@ -63,7 +63,7 @@ fn test_halt() {
emulator.interrupts.ime = true; emulator.interrupts.ime = true;
emulator.tick(); emulator.tick_4();
assert_eq!(emulator.registers, expected_register_state); assert_eq!(emulator.registers, expected_register_state);
assert!(emulator.halt); assert!(emulator.halt);
} }

9
tests.md Normal file
View file

@ -0,0 +1,9 @@
# Passing Tests
* blargg/cpu_instrs.gb
* blargg/instr_timing.gb
* blargg/mem_timing.gb
* mts/acceptance/ppu/intr_1_2_timing-GS.gb (VBlank intr -> OAM intr timing)
* mts/acceptance/ppu/intr_2_0_timing.gb (VBlank intr -> HBlank intr timing) (I think this shouldn't pass, i believe i'm triggering the HBlank IRQ one CPU cycle late)
* mts/acceptance/ppu/stat_lyc_onoff.gb (LY==LYC handling with PPU being enabled and disabled)
* mts/acceptance/ppu/stat_irq_blocking.gb (LCD status IRQ blocking)