diff --git a/src/consts.rs b/src/consts.rs index 7c62944..bd76279 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -11,12 +11,16 @@ pub mod bus { } pub mod gpu { + pub const IF: u16 = 0xFF0F; pub const LCDC: u16 = 0xFF40; + pub const STAT: u16 = 0xFF41; pub const SCY: u16 = 0xFF42; pub const SCX: u16 = 0xFF43; pub const LY: u16 = 0xFF44; + pub const LYC: u16 = 0xFF45; pub const OBP0: u16 = 0xFF48; pub const OBP1: u16 = 0xFF49; + pub const IE: u16 = 0xFFFF; } pub mod display { diff --git a/src/interrupts.rs b/src/interrupts.rs index 45b76bc..a49d657 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -1,54 +1,62 @@ -use crate::{common::Bit, GameBoy}; - -struct Interrupts { - vblank: bool, - lcd: bool, - timer: bool, - serial: bool, - joypad: bool, +use crate::{ + common::Bit, + consts::gpu::{IE, IF, LY, LYC, STAT}, + GameBoy, +}; + +#[derive(Debug, PartialEq)] +pub enum Interrupt { + /// https://gbdev.io/pandocs/STAT.html?highlight=ff41#ff41--stat-lcd-status + /// This will get triggered if: + /// - STAT.3: Just entered PPU mode 0 (TODO) + /// - STAT.4: Just entered PPU mode 1 (TODO) + /// - STAT.5: Just entered PPU mode 2 (TODO) + /// - STAT.6: LYC == LY + Stat, } -impl From for Interrupts { - fn from(value: u8) -> Self { - Self { - vblank: value.get_bit(0), - lcd: value.get_bit(1), - timer: value.get_bit(2), - serial: value.get_bit(3), - joypad: value.get_bit(4), +impl GameBoy { + pub(crate) fn execute_interrupts(&mut self) { + if !self.flags.ime { + return; } - } -} -impl From for u8 { - fn from(value: Interrupts) -> Self { - let mut result: u8 = 0; + let stat = self.bus[STAT]; + if stat.get_bit(6) && self.bus[LYC] == self.bus[LY] { + self.execute_interrupt(Interrupt::Stat); + } + } - result.set_bit(0, value.vblank); - result.set_bit(1, value.lcd); - result.set_bit(2, value.timer); - result.set_bit(3, value.serial); - result.set_bit(4, value.joypad); + fn execute_interrupt(&mut self, interrupt: Interrupt) { + // We don't want to call the same interrupt twice + if let Some(previous_interrupt) = &self.previous_interrupt { + if *previous_interrupt == interrupt { + return; + } + } - result - } -} + // The bit corresponding to the correct interrupt, both in Interrupt Enable, and + // Interrupt Flag bytes + let if_bit: u8 = match interrupt { + Interrupt::Stat => 1, + }; -impl GameBoy { - pub(crate) fn execute_interrupts(&mut self) { - if !self.flags.ime { + // We can only execute an interrupt if it's turned on in the Interrupt Enable and + // Interrupt Flag bytes + if self.bus[IE].get_bit(if_bit) && self.bus[IF].get_bit(if_bit) { return; } - let is_enabled: Interrupts = self.bus[0xFFFF].into(); - let mut value: Interrupts = self.bus[0xFF0F].into(); + let return_address: u16 = match interrupt { + Interrupt::Stat => 0x48, + }; - // TODO: Make code DRYer - if is_enabled.vblank && value.vblank { - self.call(0x40); + self.call(return_address); - value.vblank = false; - self.bus[0xFF0F] = value.into(); - } + let mut input_flags = self.bus[IF]; + input_flags.set_bit(if_bit, false); + self.bus[IF] = input_flags; + + self.previous_interrupt = Some(interrupt); } } diff --git a/src/lib.rs b/src/lib.rs index 3c6ff3c..f13f0c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ use common::merge_two_u8s_into_u16; use consts::bus::ROM_SIZE; use flags::Flags; use gpu::states::Gpu; +use interrupts::Interrupt; use registers::Registers; mod bus; @@ -24,6 +25,10 @@ pub struct GameBoy { pub registers: Registers, pub flags: Flags, pub gpu: Gpu, + + /// This is needed because we cannot fire the same interrupt twice in a row, so we + /// have to keep track of the last one + previous_interrupt: Option, } impl GameBoy { @@ -33,6 +38,7 @@ impl GameBoy { registers: Registers::new(), flags: Flags::new(), gpu: Gpu::new(), + previous_interrupt: None, }) } @@ -42,6 +48,7 @@ impl GameBoy { registers: Registers::new(), flags: Flags::new(), gpu: Gpu::new(), + previous_interrupt: None, } }