Skip to content

Commit

Permalink
Experimental timer interrupt + revamp of interrupt logic
Browse files Browse the repository at this point in the history
  • Loading branch information
velllu committed Sep 23, 2024
1 parent 5250169 commit c0ae2d8
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 56 deletions.
4 changes: 3 additions & 1 deletion src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ pub mod gpu {
}

pub mod cpu {
pub const IE: u16 = 0xFFFF;
pub const IF: u16 = 0xFF0F;
pub const DIV: u16 = 0xFF04;
pub const TIMA: u16 = 0xFF05;
pub const TMA: u16 = 0xFF06;
pub const TAC: u16 = 0xFF07;
}

pub mod display {
Expand Down
57 changes: 20 additions & 37 deletions src/cpu/interrupts.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
use crate::{
bus::Bus,
common::{split_u16_into_two_u8s, Bit},
consts::{
cpu::{IE, IF},
gpu::{LY, LYC, STAT},
},
gpu::Gpu,
consts::cpu::IF,
registers::Registers,
};

Expand All @@ -22,47 +18,30 @@ pub(crate) enum Interrupt {
/// - STAT.5: Just entered PPU mode 2
/// - STAT.6: LYC == LY
Stat = 1,

/// Triggers when TIMA register overflows
Timer = 2,
}

impl Cpu {
/// 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.read(IE);
/// We start executing an interrupt when both the interrupt enable and interrupt flag
/// are enabled
pub(crate) fn execute_interrupts(&mut self, registers: &mut Registers, bus: &mut Bus) {
let interrupt_enable = bus.ie;
let interrupt_flag = bus.read(IF);

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

if interrupt_enable.get_bit(1) {
let stat = bus.read(STAT);

if stat.get_bit(3) && gpu.has_just_entered_hblank {
self.handle_interrupt(Interrupt::Stat, registers, bus);
return;
}

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

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.read(LY) == bus.read(LYC) {
self.handle_interrupt(Interrupt::Stat, registers, bus);
return;
}
if interrupt_enable.get_bit(1) && interrupt_flag.get_bit(1) {
self.handle_interrupt(Interrupt::Stat, registers, bus);
return;
}

if interrupt_enable.get_bit(2) && interrupt_flag.get_bit(2) {
self.handle_interrupt(Interrupt::Timer, registers, bus);
return;
}
}
Expand All @@ -80,6 +59,9 @@ impl Cpu {
// We disable IME so interrupts are not called immediately, interrupts
// typically end with a `RETI` instruction that turns this back on
self.ime = false;

// And we also un-halt the CPU
self.halt = false;
}
}

Expand All @@ -88,6 +70,7 @@ impl Cpu {
let return_address = match interrupt {
Interrupt::VBlank => 0x40,
Interrupt::Stat => 0x48,
Interrupt::Timer => 0x50,
};

// This is like the call instruction but we don't subtract three
Expand Down
50 changes: 49 additions & 1 deletion src/cpu/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::{bus::Bus, consts::cpu::DIV};
use crate::{
bus::Bus,
common::Bit,
consts::cpu::{DIV, IF, TAC, TIMA, TMA},
};

mod interrupts;
mod opcodes;
Expand All @@ -21,6 +25,9 @@ pub struct Cpu {

/// This is increased after each cycle, it's used for the timers
pub(crate) div_register: u16,

/// This is increased based on the TAC (timer control) register
pub(crate) tima_register: u16,
}

impl Cpu {
Expand All @@ -29,9 +36,11 @@ impl Cpu {
ime: false,
halt: false,
div_register: 0,
tima_register: 0,
}
}

/// Increase the div register
pub(crate) fn update_div_register(&mut self, bus: &mut Bus, cycles_num: u8) {
self.div_register = self.div_register.wrapping_add(cycles_num as u16);

Expand All @@ -40,4 +49,43 @@ impl Cpu {
let higher_byte = (self.div_register >> 8) as u8;
bus.write(DIV, higher_byte);
}

/// Increse the tima register based on the contents of the timer control register
pub(crate) fn update_tima_register(&mut self, bus: &mut Bus, cycles_num: u8) {
let timer_counter = bus.read(TIMA);
let timer_control = bus.read(TAC);
let timer_module = bus.read(TMA);

// The second byte of timer control indicates wheter or not we are counting
if !timer_control.get_bit(2) {
return;
}

self.tima_register += cycles_num as u16;

// This is the threshold at which we increment the timer counter
let increment_every = match (timer_control.get_bit(1), timer_control.get_bit(0)) {
(false, false) => 256,
(false, true) => 4,
(true, false) => 16,
(true, true) => 64,
};

if self.tima_register > increment_every {
self.tima_register -= increment_every;

// When the timer counter overflows we have to run a timer interrupt
let (result, has_overflown) = timer_counter.overflowing_add(1);

if has_overflown {
// When overflowing, we both trigger a timer interrupt and we reset the
// value of the timer counter to that of the timer module
bus.write(IF, bus.read(IF) | 0b00000100); // Enabling timer interrupt
bus.write(TIMA, timer_module);
} else {
// When not overflowing, we just increment the timer
bus.write(TAC, result);
}
}
}
}
3 changes: 1 addition & 2 deletions src/cpu/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ impl Cpu {
bus: &mut Bus,
) -> (Bytes, Cycles) {
if self.halt {
self.halt = false;
return (0, 1);
}

// TODO: Fix timing on instruction with register `6`, they should have a clock more
match opcode {
// Instruction `NOP`
0x00 => (1, 0),
0x00 => (1, 1),

// Instruction `LD rr, immediate data` - 00rr0001
// Loads immediate data into given register couple
Expand Down
41 changes: 38 additions & 3 deletions src/gpu/blanks.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
use crate::GameBoy;
use crate::{
common::Bit,
consts::{
cpu::IF,
gpu::{LY, LYC, STAT},
},
GameBoy,
};

use super::GpuState;

impl GameBoy {
pub(super) fn hblank(&mut self) {
self.gpu.has_just_entered_hblank = self.gpu.ticks == 0;
// Setting interrupts
if self.gpu.ticks == 0 {
let interrupt_flag = self.bus.read(IF);
let stat = self.bus.read(STAT);

// Stat interrupt. Stat.3 indicates HBlank
if stat.get_bit(3) {
self.bus.write(IF, interrupt_flag | 0b00000010);
}
}

if self.gpu.ticks == 0 {
self.gpu.x = 0;
Expand All @@ -31,7 +47,26 @@ 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;
// Setting interrupts
if self.gpu.ticks == 0 {
let mut interrupt_flag = self.bus.read(IF);
let stat = self.bus.read(STAT);

// VBlank interrupt
interrupt_flag |= 0b00000001;

// Stat interrupt. Stat.3 indicates VBlank
if stat.get_bit(4) {
interrupt_flag |= 0b00000010;
}

// Stat interrupt. We also need to check for LYC == LY
if stat.get_bit(6) && self.bus.read(LY) == self.bus.read(LYC) {
interrupt_flag |= 0b00000010;
}

self.bus.write(IF, interrupt_flag);
}

if self.gpu.ticks == 0 {
self.layers
Expand Down
8 changes: 0 additions & 8 deletions src/gpu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ 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 @@ -64,11 +61,6 @@ 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
17 changes: 15 additions & 2 deletions src/gpu/oam_search.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::{common::Bit, GameBoy};
use crate::{
common::Bit,
consts::{cpu::IF, gpu::STAT},
GameBoy,
};

use super::{
pixel_transfer::sprite::{Palette, SpriteData},
Expand All @@ -7,7 +11,16 @@ use super::{

impl GameBoy {
pub(super) fn oam_search(&mut self) {
self.gpu.has_just_entered_oam_scan = self.gpu.ticks == 0;
// Setting interrupts
if self.gpu.ticks == 0 {
let interrupt_flag = self.bus.read(IF);
let stat = self.bus.read(STAT);

// Stat interrupt. Stat.3 indicates OAM Search
if stat.get_bit(5) {
self.bus.write(IF, interrupt_flag | 0b00000010);
}
}

if self.gpu.ticks == 0 {
self.gpu.y = 0;
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,17 @@ impl GameBoy {

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

// CPU - OAM DMA Transfer
if self.bus.needs_to_dispatch_oam_dma {
self.bus.dispatch_oam_transfer();
self.bus.needs_to_dispatch_oam_dma = false;
}

// CPU - DIV Register
// CPU - Timer registers
self.cpu.update_div_register(&mut self.bus, cycles);
self.cpu.update_tima_register(&mut self.bus, cycles);

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

0 comments on commit c0ae2d8

Please sign in to comment.