diff --git a/src/consts.rs b/src/consts.rs index c2f19b3..c11ba5d 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -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 { diff --git a/src/cpu/interrupts.rs b/src/cpu/interrupts.rs index b9c7a09..c109de9 100644 --- a/src/cpu/interrupts.rs +++ b/src/cpu/interrupts.rs @@ -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, }; @@ -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; } } @@ -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; } } @@ -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 diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 866a6d4..b47a787 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -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; @@ -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 { @@ -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); @@ -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); + } + } + } } diff --git a/src/cpu/opcodes.rs b/src/cpu/opcodes.rs index 36c0efb..ffccf1a 100644 --- a/src/cpu/opcodes.rs +++ b/src/cpu/opcodes.rs @@ -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 diff --git a/src/gpu/blanks.rs b/src/gpu/blanks.rs index 2d71c5f..5a57182 100644 --- a/src/gpu/blanks.rs +++ b/src/gpu/blanks.rs @@ -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; @@ -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 diff --git a/src/gpu/mod.rs b/src/gpu/mod.rs index 3641242..6a996ba 100644 --- a/src/gpu/mod.rs +++ b/src/gpu/mod.rs @@ -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, @@ -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, /// This is filled during OAM Search diff --git a/src/gpu/oam_search.rs b/src/gpu/oam_search.rs index 9c94a51..f3b99b0 100644 --- a/src/gpu/oam_search.rs +++ b/src/gpu/oam_search.rs @@ -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}, @@ -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; diff --git a/src/lib.rs b/src/lib.rs index d4a28fa..22bc56b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,7 @@ 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 { @@ -89,8 +89,9 @@ impl GameBoy { 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) {