diff --git a/src/bus.rs b/src/bus.rs index f0ed21f..a85713c 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,7 +1,7 @@ use super::cartridge::Cartridge; use super::high_ram::HighRam; use super::instruction::Cycles; -use super::interrupt::Interrupt; +use super::interrupt::{Interrupt, InterruptFlag}; use super::ppu::Ppu; use super::serial::Serial; use super::sound::Sound; @@ -118,7 +118,7 @@ impl Bus { 0xFF01 => self.serial.next, 0xFF02 => self.serial.control.into(), 0xFF07 => self.timer.control.into(), - 0xFF0F => self.interrupt.flag.into(), + 0xFF0F => self.interrupt_flag().into(), 0xFF11 => self.sound.ch1.sound_duty.into(), 0xFF12 => self.sound.ch1.vol_envelope.into(), 0xFF14 => self.sound.ch1.freq_hi.into(), @@ -203,7 +203,7 @@ impl Bus { 0xFF01 => self.serial.next = byte, 0xFF02 => self.serial.control = byte.into(), 0xFF07 => self.timer.control = byte.into(), - 0xFF0F => self.interrupt.flag = byte.into(), + 0xFF0F => self.set_interrupt_flag(byte), 0xFF11 => self.sound.ch1.sound_duty = byte.into(), 0xFF12 => self.sound.ch1.vol_envelope = byte.into(), 0xFF13 => self.sound.ch1.freq_lo = byte.into(), @@ -251,3 +251,39 @@ impl Bus { self.write_byte(addr, (word & 0x00FF) as u8); } } + +impl Bus { + fn interrupt_flag(&self) -> InterruptFlag { + // Read the current Interrupt status from the PPU + let ppu_vblank = self.ppu.interrupt.vblank(); + let ppu_lcd_stat = self.ppu.interrupt.lcd_stat(); + + // We actually don't care about what the InterruptFlag currently says + // about vblank and lcd_stat, because the PPU has the more accurate + // knowledge about what state these interrupt flags are in. + + // In order to have the PPU be the source of truth + // (and accounting for the fact that we aren't able to update) + // the interrupt flag register 0xFF0F in the method, we can + // mask over those two interruptss + + // Copy the Interrupt Flag register 0xFF0F + let mut flag = self.interrupt.flag; + flag.set_vblank(ppu_vblank); + flag.set_lcd_stat(ppu_lcd_stat); + + flag + } + + fn set_interrupt_flag(&mut self, byte: u8) { + // Update the Interrupt register 0xFF0F + self.interrupt.flag = byte.into(); + + let vblank = self.interrupt.flag.vblank(); + let lcd_stat = self.interrupt.flag.lcd_stat(); + + // Update the PPU's internal tracking of the interrupt register 0xFF0F + self.ppu.interrupt.set_vblank(vblank); + self.ppu.interrupt.set_lcd_stat(lcd_stat); + } +} diff --git a/src/ppu.rs b/src/ppu.rs index e53d645..7474fc5 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -5,6 +5,7 @@ const GB_WIDTH: usize = 160; const GB_HEIGHT: usize = 144; #[derive(Debug, Clone)] pub struct Ppu { + pub interrupt: Interrupt, pub lcd_control: LCDControl, pub monochrome: Monochrome, pub pos: ScreenPosition, @@ -60,6 +61,7 @@ impl Ppu { self.pos.line_y += 1; let next_mode = if self.pos.line_y >= 144 { + self.interrupt.set_vblank(true); Mode::VBlank } else { Mode::OamScan @@ -138,6 +140,7 @@ impl Ppu { impl Default for Ppu { fn default() -> Self { Self { + interrupt: Interrupt::default(), lcd_control: Default::default(), monochrome: Default::default(), pos: Default::default(), @@ -149,6 +152,31 @@ impl Default for Ppu { } } } + +#[derive(Debug, Clone, Copy, Default)] +pub struct Interrupt { + _vblank: bool, + _lcd_stat: bool, +} + +impl Interrupt { + pub fn vblank(&self) -> bool { + self._vblank + } + + pub fn set_vblank(&mut self, enabled: bool) { + self._vblank = enabled; + } + + pub fn lcd_stat(&self) -> bool { + self._lcd_stat + } + + pub fn set_lcd_stat(&mut self, enabled: bool) { + self._lcd_stat = enabled; + } +} + bitfield! { pub struct LCDStatus(u8); impl Debug;