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;
pub mod mapper;
mod memory;
mod ppu;
pub mod ppu;
mod serial;
mod sound;
mod timer;
@ -25,18 +25,16 @@ use self::{
pub struct DmaState {
pub base: u8,
pub remaining_cycles: u8,
pub remaining_delay: u8,
}
impl DmaState {
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) {
self.base = base;
self.remaining_cycles = 0xA0;
self.remaining_delay = 2;
}
}
@ -135,6 +133,8 @@ pub struct Gameboy {
pub stop: bool,
pub pc_history: RingBuffer<u16, 0x200>,
pub tick_count: u8,
}
impl Gameboy {
@ -147,7 +147,7 @@ impl Gameboy {
joypad: Joypad::new(),
serial: Serial::new(),
dma: DmaState::new(),
ppu: Ppu::new(),
ppu: Ppu::new(bootrom.is_some()),
registers: match bootrom.is_some() {
true => Registers::default(),
false => Registers::post_rom(),
@ -164,6 +164,7 @@ impl Gameboy {
used_halt_bug: false,
stop: false,
pc_history: RingBuffer::new(),
tick_count: 0,
}
}
@ -188,6 +189,9 @@ impl Gameboy {
}
pub fn load_cartridge(&mut self, bytes: Vec<u8>) {
if bytes.len() < 0x150 {
panic!("Bad cartridge (len < 0x150)");
}
match bytes[0x147] {
0 => self.cartridge = Some(Box::new(NoMBC::new(bytes))),
1 => self.cartridge = Some(Box::new(MBC1::new(bytes))),
@ -201,173 +205,217 @@ 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());
}
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>) {
if self.breakpoints[self.registers.pc as usize] && !self.single_step {
self.single_step = true;
log::info!("Breakpoint hit @ {:#X}", self.registers.pc);
}
if self.tick_count == 0 {
if self.breakpoints[self.registers.pc as usize] && !self.single_step {
self.single_step = true;
log::info!("Breakpoint hit @ {:#X}", self.registers.pc);
}
let mut diff = None;
let mut diff = None;
if self.trigger_bp || (self.single_step && self.registers.cycle == 0) {
let entered_step = chrono::Utc::now();
self.trigger_bp = false;
self.single_step = true;
let mut input = String::new();
let mut exit = true;
match std::io::stdin().read_line(&mut input) {
Ok(_) => {
let lower = input.trim_end().to_lowercase();
let (lhs, rhs) = lower.split_once(' ').unwrap_or_else(|| (lower.as_str(), ""));
match lhs {
"read" => match u16::from_str_radix(rhs, 16) {
Ok(address) => {
let res = self.internal_cpu_read_u8(address);
log::info!("{:#X}: {:#X} ({:#b})", address, res, res);
}
Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"),
},
"regs" => self.log_state(),
"op" => {
self.log_next_opcode();
}
"bp" => match u16::from_str_radix(rhs, 16) {
Ok(address) => {
let bp = &mut self.breakpoints[address as usize];
*bp = !*bp;
match *bp {
true => log::info!("Set breakpoint @ {:#X}", address),
false => log::info!("Cleared breakpoint @ {:#X}", address),
if self.trigger_bp || (self.single_step && self.registers.cycle == 0) {
let entered_step = chrono::Utc::now();
self.trigger_bp = false;
self.single_step = true;
let mut input = String::new();
let mut exit = true;
match std::io::stdin().read_line(&mut input) {
Ok(_) => {
let lower = input.trim_end().to_lowercase();
let (lhs, rhs) =
lower.split_once(' ').unwrap_or_else(|| (lower.as_str(), ""));
match lhs {
"read" => match u16::from_str_radix(rhs, 16) {
Ok(address) => {
let res = self.internal_cpu_read_u8(address);
log::info!("{:#X}: {:#X} ({:#b})", address, res, res);
}
Err(_) => {
log::error!("Failed to parse input as hex u16 (f.ex 420C)")
}
},
"regs" => self.log_state(),
"op" => {
self.log_next_opcode();
}
Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"),
},
"bpr" => match u16::from_str_radix(rhs, 16) {
Ok(address) => {
let bp = &mut self.mem_read_breakpoints[address as usize];
*bp = !*bp;
match *bp {
true => log::info!("Set breakpoint on read @ {:#X}", address),
false => {
log::info!("Cleared breakpoint on read @ {:#X}", address)
"bp" => match u16::from_str_radix(rhs, 16) {
Ok(address) => {
let bp = &mut self.breakpoints[address as usize];
*bp = !*bp;
match *bp {
true => log::info!("Set breakpoint @ {:#X}", address),
false => log::info!("Cleared breakpoint @ {:#X}", address),
}
}
}
Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"),
},
"bpw" => match u16::from_str_radix(rhs, 16) {
Ok(address) => {
let bp = &mut self.mem_write_breakpoints[address as usize];
*bp = !*bp;
match *bp {
true => log::info!("Set breakpoint on write @ {:#X}", address),
false => {
log::info!("Cleared breakpoint on write @ {:#X}", address)
Err(_) => {
log::error!("Failed to parse input as hex u16 (f.ex 420C)")
}
},
"bpr" => match u16::from_str_radix(rhs, 16) {
Ok(address) => {
let bp = &mut self.mem_read_breakpoints[address as usize];
*bp = !*bp;
match *bp {
true => {
log::info!("Set breakpoint on read @ {:#X}", address)
}
false => {
log::info!(
"Cleared breakpoint on read @ {:#X}",
address
)
}
}
}
}
Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"),
},
"c" => {
self.single_step = false;
log::info!("Continuing");
exit = false;
}
"timer" => {
println!("-- Timer Info --\n{:#?}\n-- End of Timer Info --", self.timer)
}
"p" | "pause" => {
self.single_step = true;
log::info!("Single step activated");
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" | "" => {
self.log_next_opcode();
exit = false;
}
"ls" => {
self.log_state();
exit = false;
}
"dumpbgtiles" => {
self.ppu.dump_bg_tiles();
}
"dumpfb" => {
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)
Err(_) => {
log::error!("Failed to parse input as hex u16 (f.ex 420C)")
}
let mem_val = self.ppu.oam[x];
print!("{:02X} ", mem_val);
}
println!();
}
"dumpvram" => {
for x in 0..0x200 {
if x % 0x10 == 0 {
print!("\n{:X}: ", 0x8000 + x)
},
"bpw" => match u16::from_str_radix(rhs, 16) {
Ok(address) => {
let bp = &mut self.mem_write_breakpoints[address as usize];
*bp = !*bp;
match *bp {
true => {
log::info!("Set breakpoint on write @ {:#X}", address)
}
false => {
log::info!(
"Cleared breakpoint on write @ {:#X}",
address
)
}
}
}
let mem_val = self.ppu.vram[x];
print!("{:02X} ", mem_val);
}
println!();
}
"dumptilemap" => {
let base = match (self.ppu.lcdc >> 3) & 0b1 == 1 {
true => 0x1C00,
false => 0x1800,
};
for x in 0..0x400 {
if x % 0x10 == 0 {
print!("\n{:X}: ", 0x8000 + base + x)
Err(_) => {
log::error!("Failed to parse input as hex u16 (f.ex 420C)")
}
let mem_val = self.ppu.vram[base + x];
print!("{:02X} ", mem_val);
},
"c" => {
self.single_step = false;
log::info!("Continuing");
exit = false;
}
println!();
"timer" => {
println!(
"-- Timer Info --\n{:#?}\n-- End of Timer Info --",
self.timer
)
}
"p" | "pause" => {
self.single_step = true;
log::info!("Single step activated");
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" | "" => {
self.log_next_opcode();
exit = false;
}
"ls" => {
self.log_state();
exit = false;
}
"dumpbgtiles" => {
self.ppu.dump_bg_tiles();
}
"dumpfb" => {
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" => {
for x in 0..0x200 {
if x % 0x10 == 0 {
print!("\n{:X}: ", 0x8000 + x)
}
let mem_val = self.ppu.vram[x];
print!("{:02X} ", mem_val);
}
println!();
}
"dumptilemap" => {
let base = match (self.ppu.lcdc >> 3) & 0b1 == 1 {
true => 0x1C00,
false => 0x1800,
};
for x in 0..0x400 {
if x % 0x10 == 0 {
print!("\n{:X}: ", 0x8000 + base + x)
}
let mem_val = self.ppu.vram[base + x];
print!("{:02X} ", mem_val);
}
println!();
}
_ => {}
}
_ => {}
}
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 {
return (false, diff);
}
}
if self.timer.tick() {
self.interrupts.write_if_timer(true);
}
diff = Some((chrono::Utc::now() - entered_step).num_milliseconds());
if exit {
return (false, diff);
cpu::tick_cpu(self);
let redraw_requested = self.ppu.tick(&mut self.interrupts);
self.tick_dma();
if self.serial.tick() {
self.interrupts.write_if_serial(true);
}
self.tick_count += 1;
(redraw_requested, diff)
} else {
let redraw_requested = self.ppu.tick(&mut self.interrupts);
self.tick_count += 1;
self.tick_count %= 4;
(redraw_requested, None)
}
if self.timer.tick() {
self.interrupts.write_if_timer(true);
}
cpu::tick_cpu(self);
let redraw_requested = self.ppu.tick(&mut self.interrupts);
self.tick_dma();
if self.serial.tick() {
self.interrupts.write_if_serial(true);
}
(redraw_requested, diff)
}
fn tick_dma(&mut self) {
if self.dma.remaining_delay > 0 {
self.dma.remaining_delay -= 1;
} else if self.dma.remaining_cycles > 0 {
self.ppu.dma_occuring = self.dma.remaining_cycles > 0;
if self.dma.remaining_cycles > 0 {
let offset = 0xA0 - self.dma.remaining_cycles;
let value = if self.dma.base <= 0x7F {
@ -376,13 +424,14 @@ impl Gameboy {
None => 0xFF,
}
} 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 {
let address = (self.dma.base as u16) << 8 | offset as u16;
self.memory.wram[address as usize - 0xC000]
let address = ((self.dma.base as usize) << 8 | offset as usize) - 0xC000;
self.memory.wram[address]
} else if self.dma.base <= 0xFD {
let address = (self.dma.base as u16) << 8 | offset as u16;
self.memory.wram[address as usize - 0xE000]
let address = ((self.dma.base as usize) << 8 | offset as usize) - 0xE000;
self.memory.wram[address]
} else {
0xFF
};
@ -430,13 +479,15 @@ impl Gameboy {
0xFF27..=0xFF2F => 0xFF,
0xFF30..=0xFF3F => self.sound.wave_pattern_ram[address as usize - 0xFF30],
0xFF40 => self.ppu.lcdc,
0xFF41 => self.ppu.stat,
0xFF41 => self.ppu.get_stat(),
0xFF42 => self.ppu.scy,
0xFF43 => self.ppu.scx,
0xFF44 => self.ppu.ly,
0xFF45 => self.ppu.lyc,
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,
0xFF4B => self.ppu.wx,
0xFF4C..=0xFF4E => 0xFF, // Unused
@ -489,18 +540,29 @@ impl Gameboy {
0xFF26 => self.sound.nr52 = value,
0xFF27..=0xFF2F => {}
0xFF30..=0xFF3F => self.sound.wave_pattern_ram[address as usize - 0xFF30] = value,
0xFF40 => self.ppu.lcdc = value,
0xFF41 => self.ppu.cpu_write_stat(value),
0xFF40 => {
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,
0xFF43 => self.ppu.scx = value,
0xFF44 => {} // LY is read only
0xFF45 => self.ppu.lyc = value,
0xFF45 => self.ppu.set_lyc(&mut self.interrupts, value),
0xFF46 => {
if self.dma.remaining_cycles == 0 {
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,
0xFF4B => self.ppu.wx = value,
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 {
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 {
@ -527,12 +528,6 @@ pub fn tick_cpu(state: &mut Gameboy) {
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 {
match state.registers.opcode_bytecount {
Some(len) => state.registers.pc = state.registers.pc.overflowing_add(len as u16).0,

View file

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

View file

@ -63,7 +63,7 @@ impl MBC1 {
}
fn is_large_rom(&self) -> bool {
self.rom_bank_count > 4
self.rom_bank_count >= 64
}
#[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.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_eq!(gameboy.registers.cycle, 0); // Assert tick really did just prefetch instruction and not run the opcode at
// 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 {
goal = goal + Duration::milliseconds(diff);

View file

@ -50,9 +50,6 @@ pub enum GameboyEvent {
Framebuffer(Vec<u8>),
}
pub const FB_HEIGHT: u32 = 144;
pub const FB_WIDTH: u32 = 160;
#[derive(Debug, Default)]
struct Keymap {
pub down: bool,
@ -108,7 +105,12 @@ pub fn run_window(
let mut pixels = {
let window_size = window.inner_size();
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;

View file

@ -9,11 +9,11 @@ macro_rules! conditional_jump_relative_testgen {
emulator.registers.[<set_ $flag>](false);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x103);
}
@ -23,9 +23,9 @@ macro_rules! conditional_jump_relative_testgen {
emulator.registers.[<set_ $flag>](true);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x102);
}
@ -35,11 +35,11 @@ macro_rules! conditional_jump_relative_testgen {
emulator.registers.[<set_ $flag>](true);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x103);
}
@ -49,9 +49,9 @@ macro_rules! conditional_jump_relative_testgen {
emulator.registers.[<set_ $flag>](false);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x102);
}
}
@ -65,11 +65,11 @@ conditional_jump_relative_testgen!(carry, 0x30, 0x38);
fn test_jr_i8() {
let mut emulator = setup_test_emulator([0x18, 0x1]);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x103);
}
@ -77,13 +77,13 @@ fn test_jr_i8() {
fn test_jp_u16() {
let mut emulator = setup_test_emulator([0xC3, 0xFE, 0xCA]);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
@ -93,7 +93,7 @@ fn test_jp_hl() {
emulator.registers.set_hl(0xCAFE);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
@ -106,13 +106,13 @@ macro_rules! conditional_jump_testgen {
emulator.registers.[<set_ $flag>](false);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
@ -122,11 +122,11 @@ macro_rules! conditional_jump_testgen {
emulator.registers.[<set_ $flag>](true);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x103);
}
@ -136,13 +136,13 @@ macro_rules! conditional_jump_testgen {
emulator.registers.[<set_ $flag>](true);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
@ -152,11 +152,11 @@ macro_rules! conditional_jump_testgen {
emulator.registers.[<set_ $flag>](false);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x103);
}
}
@ -172,19 +172,19 @@ fn test_call_u16() {
let orignal_sp = emulator.registers.sp;
emulator.tick(); // <-- Read first u8
emulator.tick_4(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8
emulator.tick_4(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
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.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.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE);
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.tick(); // <-- Read first u8
emulator.tick_4(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8
emulator.tick_4(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Check flag
emulator.tick_4(); // <-- Check flag
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.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.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE);
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.tick(); // <-- Read first u8
emulator.tick_4(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8
emulator.tick_4(); // <-- Read second u8
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.sp, orignal_sp);
@ -247,19 +247,19 @@ macro_rules! conditional_call_testgen {
emulator.registers.[<set_ $flag>](true);
emulator.tick(); // <-- Read first u8
emulator.tick_4(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8
emulator.tick_4(); // <-- Read second u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Check flag
emulator.tick_4(); // <-- Check flag
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.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.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE);
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.tick(); // <-- Read first u8
emulator.tick_4(); // <-- Read first u8
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick(); // <-- Read second u8
emulator.tick_4(); // <-- Read second u8
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.sp, orignal_sp);
@ -301,15 +301,15 @@ fn test_ret() {
let orignal_sp = emulator.registers.sp;
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
@ -326,15 +326,15 @@ fn test_reti() {
let orignal_sp = emulator.registers.sp;
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE);
assert_eq!(emulator.interrupts.ime, true);
}
@ -355,17 +355,17 @@ macro_rules! conditional_ret_testgen {
let orignal_sp = emulator.registers.sp;
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
@ -382,9 +382,9 @@ macro_rules! conditional_ret_testgen {
let orignal_sp = emulator.registers.sp;
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x101);
assert_eq!(emulator.registers.sp, orignal_sp);
}
@ -402,17 +402,17 @@ macro_rules! conditional_ret_testgen {
let orignal_sp = emulator.registers.sp;
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 1);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
assert_eq!(emulator.registers.sp, orignal_sp + 2);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0xCAFE);
}
@ -429,9 +429,9 @@ macro_rules! conditional_ret_testgen {
let orignal_sp = emulator.registers.sp;
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, 0x101);
assert_eq!(emulator.registers.sp, orignal_sp);
}
@ -451,16 +451,16 @@ macro_rules! rst_testgen {
let original_sp = emulator.registers.sp;
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.sp, original_sp);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.sp, original_sp - 1);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.sp, original_sp - 2);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.pc, $addr);
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.$loreg = 0x00;
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.$hireg, 0x00);
assert_eq!(emulator.registers.$loreg, 0x00);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.$hireg, 0x00);
assert_eq!(emulator.registers.$loreg, 0xFE);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.$hireg, 0xCA);
assert_eq!(emulator.registers.$loreg, 0xFE);
assert_eq!(emulator.registers.pc, 0x103);
@ -37,13 +37,13 @@ fn test_ld_reg_sp_imm_u16() {
emulator.registers.sp = 0x0000;
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.sp, 0x0000);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.sp, 0x00FE);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.sp, 0xCAFE);
assert_eq!(emulator.registers.pc, 0x103);
}
@ -55,10 +55,10 @@ fn test_ld_sp_hl() {
emulator.registers.sp = 0x0000;
emulator.registers.set_hl(0xCAFE);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.sp, 0x00FE);
assert_eq!(emulator.registers.pc, 0x100);
emulator.tick();
emulator.tick_4();
assert_eq!(emulator.registers.sp, 0xCAFE);
assert_eq!(emulator.registers.pc, 0x101);
}

View file

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