feat: majorly improved PPU
This commit is contained in:
parent
ce077f9069
commit
85d61764e8
12 changed files with 1092 additions and 623 deletions
|
@ -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,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());
|
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.breakpoints[self.registers.pc as usize] && !self.single_step {
|
if self.tick_count == 0 {
|
||||||
self.single_step = true;
|
if self.breakpoints[self.registers.pc as usize] && !self.single_step {
|
||||||
log::info!("Breakpoint hit @ {:#X}", self.registers.pc);
|
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) {
|
if self.trigger_bp || (self.single_step && self.registers.cycle == 0) {
|
||||||
let entered_step = chrono::Utc::now();
|
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();
|
||||||
let mut exit = true;
|
let mut exit = true;
|
||||||
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) =
|
||||||
match lhs {
|
lower.split_once(' ').unwrap_or_else(|| (lower.as_str(), ""));
|
||||||
"read" => match u16::from_str_radix(rhs, 16) {
|
match lhs {
|
||||||
Ok(address) => {
|
"read" => match u16::from_str_radix(rhs, 16) {
|
||||||
let res = self.internal_cpu_read_u8(address);
|
Ok(address) => {
|
||||||
log::info!("{:#X}: {:#X} ({:#b})", address, res, res);
|
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),
|
|
||||||
}
|
}
|
||||||
|
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)"),
|
"bp" => match u16::from_str_radix(rhs, 16) {
|
||||||
},
|
Ok(address) => {
|
||||||
"bpr" => match u16::from_str_radix(rhs, 16) {
|
let bp = &mut self.breakpoints[address as usize];
|
||||||
Ok(address) => {
|
*bp = !*bp;
|
||||||
let bp = &mut self.mem_read_breakpoints[address as usize];
|
match *bp {
|
||||||
*bp = !*bp;
|
true => log::info!("Set breakpoint @ {:#X}", address),
|
||||||
match *bp {
|
false => log::info!("Cleared breakpoint @ {:#X}", address),
|
||||||
true => log::info!("Set breakpoint on read @ {:#X}", address),
|
|
||||||
false => {
|
|
||||||
log::info!("Cleared breakpoint on read @ {:#X}", address)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Err(_) => {
|
||||||
Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"),
|
log::error!("Failed to parse input as hex u16 (f.ex 420C)")
|
||||||
},
|
}
|
||||||
"bpw" => match u16::from_str_radix(rhs, 16) {
|
},
|
||||||
Ok(address) => {
|
"bpr" => match u16::from_str_radix(rhs, 16) {
|
||||||
let bp = &mut self.mem_write_breakpoints[address as usize];
|
Ok(address) => {
|
||||||
*bp = !*bp;
|
let bp = &mut self.mem_read_breakpoints[address as usize];
|
||||||
match *bp {
|
*bp = !*bp;
|
||||||
true => log::info!("Set breakpoint on write @ {:#X}", address),
|
match *bp {
|
||||||
false => {
|
true => {
|
||||||
log::info!("Cleared breakpoint on write @ {:#X}", address)
|
log::info!("Set breakpoint on read @ {:#X}", address)
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
log::info!(
|
||||||
|
"Cleared breakpoint on read @ {:#X}",
|
||||||
|
address
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Err(_) => {
|
||||||
Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"),
|
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)
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
let mem_val = self.ppu.oam[x];
|
"bpw" => match u16::from_str_radix(rhs, 16) {
|
||||||
print!("{:02X} ", mem_val);
|
Ok(address) => {
|
||||||
}
|
let bp = &mut self.mem_write_breakpoints[address as usize];
|
||||||
println!();
|
*bp = !*bp;
|
||||||
}
|
match *bp {
|
||||||
"dumpvram" => {
|
true => {
|
||||||
for x in 0..0x200 {
|
log::info!("Set breakpoint on write @ {:#X}", address)
|
||||||
if x % 0x10 == 0 {
|
}
|
||||||
print!("\n{:X}: ", 0x8000 + x)
|
false => {
|
||||||
|
log::info!(
|
||||||
|
"Cleared breakpoint on write @ {:#X}",
|
||||||
|
address
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Err(_) => {
|
||||||
let mem_val = self.ppu.vram[x];
|
log::error!("Failed to parse input as hex u16 (f.ex 420C)")
|
||||||
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];
|
"c" => {
|
||||||
print!("{:02X} ", mem_val);
|
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());
|
cpu::tick_cpu(self);
|
||||||
if exit {
|
let redraw_requested = self.ppu.tick(&mut self.interrupts);
|
||||||
return (false, diff);
|
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) {
|
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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,11 +43,11 @@ 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
9
tests.md
Normal 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)
|
Loading…
Reference in a new issue