Skip to content

Commit

Permalink
Reworked interrupt code, added VBlank, and STATs interrupts
Browse files Browse the repository at this point in the history
  • Loading branch information
velllu committed Sep 7, 2024
1 parent 74d1c68 commit d855d23
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 48 deletions.
101 changes: 62 additions & 39 deletions src/cpu/interrupts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,88 @@ use crate::{
bus::Bus,
common::{split_u16_into_two_u8s, Bit},
consts::gpu::{IE, IF, LY, LYC, STAT},
gpu::Gpu,
registers::Registers,
};

use super::Cpu;

#[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)
#[derive(Clone, Copy)]
pub(crate) enum Interrupt {
/// Triggers when vblank is entered
VBlank = 0,

/// Triggers if:
/// - STAT.3: Just entered PPU mode 0
/// - STAT.4: Just entered PPU mode 1
/// - STAT.5: Just entered PPU mode 2
/// - STAT.6: LYC == LY
Stat,
Stat = 1,
}

impl Cpu {
pub(crate) fn execute_interrupts(&mut self, registers: &mut Registers, bus: &mut Bus) {
if !self.ime {
/// Each interrupt has a condition on wheter it gets activated or not, when its bit
/// in IE is activated and the condition applies, we start Interrupt Handling
pub(crate) fn execute_interrupts(
&mut self,
gpu: &Gpu,
registers: &mut Registers,
bus: &mut Bus,
) {
let interrupt_enable = bus[IE];

if interrupt_enable.get_bit(0) && gpu.has_just_entered_vblank {
self.handle_interrupt(Interrupt::VBlank, registers, bus);
return;
}

let stat = bus[STAT];
let is_lcd_enabled = stat.get_bit(6) && bus[LYC] == bus[LY];
if interrupt_enable.get_bit(1) {
let stat = bus[STAT];

// All this thing is done because an interrupt can only be triggered if it was
// previously off
if let Some(is_previous_lcd_enabled) = self.previous_lcd {
if is_lcd_enabled && !is_previous_lcd_enabled {
self.execute_interrupt(Interrupt::Stat, registers, bus);
if stat.get_bit(3) && gpu.has_just_entered_hblank {
self.handle_interrupt(Interrupt::Stat, registers, bus);
return;
}
}

self.previous_lcd = Some(is_lcd_enabled);
}
if stat.get_bit(4) && gpu.has_just_entered_vblank {
self.handle_interrupt(Interrupt::Stat, registers, bus);
return;
}

fn execute_interrupt(
&mut self,
interrupt: Interrupt,
registers: &mut Registers,
bus: &mut Bus,
) {
// The bit corresponding to the correct interrupt, both in Interrupt Enable, and
// Interrupt Flag bytes
let if_bit: u8 = match interrupt {
Interrupt::Stat => 1,
};
if stat.get_bit(5) && gpu.has_just_entered_oam_scan {
self.handle_interrupt(Interrupt::Stat, registers, bus);
return;
}

if stat.get_bit(6) && bus[LY] == bus[LYC] {
self.handle_interrupt(Interrupt::Stat, registers, bus);
return;
}

// We can only execute an interrupt if it's turned on in the Interrupt Enable and
// Interrupt Flag bytes
if bus[IE].get_bit(if_bit) && bus[IF].get_bit(if_bit) {
return;
}
}

/// We only dispatch an interrupt if IME is true, but regardless of that we reset the
/// interrupt bit in IF, this is not used by the emulator but by the program itself
fn handle_interrupt(&mut self, interrupt: Interrupt, registers: &mut Registers, bus: &mut Bus) {
let mut input_flags = bus[IF];
input_flags.set_bit(interrupt as u8, false);
bus[IF] = input_flags;

if self.ime {
self.dispatch_interrupt(interrupt, registers, bus);

// We disable IME so interrupts are not called immediately, interrupts
// typically end with a `RETI` instruction that turns this back on
self.ime = false;
}
}

let return_address: u16 = match interrupt {
/// We `CALL` the arbitrary address specified by the interrupt
fn dispatch_interrupt(&self, interrupt: Interrupt, registers: &mut Registers, bus: &mut Bus) {
let return_address = match interrupt {
Interrupt::VBlank => 0x40,
Interrupt::Stat => 0x48,
};

Expand All @@ -67,9 +94,5 @@ impl Cpu {
registers.sp = registers.sp.wrapping_sub(1);
bus[registers.sp] = c;
registers.pc = return_address;

let mut input_flags = bus[IF];
input_flags.set_bit(if_bit, false);
bus[IF] = input_flags;
}
}
9 changes: 1 addition & 8 deletions src/cpu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,13 @@ pub type Bytes = u8;
pub type Cycles = u8;

pub struct Cpu {
// TODO: Remove this, to make the code better. Check `interrupts.rs` for more
// information on why this is needed
pub(crate) previous_lcd: Option<bool>,

/// IME, standing for "Interrupt Master Enable" is basically a switch on whether
/// interrupts should be handled or not
pub ime: bool,
}

impl Cpu {
pub(crate) fn new() -> Self {
Self {
previous_lcd: None,
ime: false,
}
Self { ime: false }
}
}
4 changes: 4 additions & 0 deletions src/gpu/blanks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use super::GpuState;

impl GameBoy {
pub(super) fn hblank(&mut self) {
self.gpu.has_just_entered_hblank = self.gpu.ticks == 0;

if self.gpu.ticks == 0 {
self.gpu.x = 0;
self.gpu.fifo.clear();
Expand All @@ -29,6 +31,8 @@ impl GameBoy {
/// Amount of ticks needed to render a vblank line
const VBLANK_LINE_TICKS: u16 = 456;

self.gpu.has_just_entered_vblank = self.gpu.ticks == 0;

if self.gpu.ticks == 0 {
self.layers
.iter_mut()
Expand Down
8 changes: 8 additions & 0 deletions src/gpu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ impl Gpu {
state: GpuState::OamSearch,
x: 0,
y: 0,
has_just_entered_hblank: false,
has_just_entered_vblank: false,
has_just_entered_oam_scan: false,
fifo: Vec::new(),
sprites: Vec::new(),
pixel_transfer_state: PixelTransferState::GetTile,
Expand All @@ -61,6 +64,11 @@ pub struct Gpu {
pub x: u8,
pub y: u8,

// These are used in the interrupts
pub(crate) has_just_entered_hblank: bool,
pub(crate) has_just_entered_vblank: bool,
pub(crate) has_just_entered_oam_scan: bool,

fifo: Vec<PixelData>,

/// This is filled during OAM Search
Expand Down
2 changes: 2 additions & 0 deletions src/gpu/oam_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use super::{

impl GameBoy {
pub(super) fn oam_search(&mut self) {
self.gpu.has_just_entered_oam_scan = self.gpu.ticks == 0;

if self.gpu.ticks == 0 {
self.gpu.y = 0;
self.gpu.sprites.clear();
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl GameBoy {

// CPU - Interrupts
self.cpu
.execute_interrupts(&mut self.registers, &mut self.bus);
.execute_interrupts(&self.gpu, &mut self.registers, &mut self.bus);

// GPU
for _ in 0..(cycles * 4) {
Expand Down

0 comments on commit d855d23

Please sign in to comment.