use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Timer {
div_counter: u16,
pub tima: u8,
pub tma: u8,
pub tac: u8,
pub interrupt_pending: bool,
tima_overflow_pending: bool,
tima_load_active: bool,
#[serde(default = "default_div_apu_bit")]
div_apu_bit: u16,
}
const MUX_BIT: [u16; 4] = [1 << 9, 1 << 3, 1 << 5, 1 << 7];
pub const DIV_APU_BIT_NORMAL: u16 = 1 << 12;
pub const DIV_APU_BIT_DOUBLE: u16 = 1 << 13;
fn default_div_apu_bit() -> u16 {
DIV_APU_BIT_NORMAL
}
impl Timer {
pub fn new() -> Self {
Self::with_div_counter(0)
}
pub(crate) fn with_div_counter(div_counter: u16) -> Self {
Self {
div_counter,
tima: 0,
tma: 0,
tac: 0,
interrupt_pending: false,
tima_overflow_pending: false,
tima_load_active: false,
div_apu_bit: DIV_APU_BIT_NORMAL,
}
}
pub fn set_div_apu_bit(&mut self, bit: u16) {
self.div_apu_bit = bit;
}
pub fn is_div_apu_bit_high(&self) -> bool {
self.div_counter & self.div_apu_bit != 0
}
pub fn read(&self, addr: u16) -> u8 {
match addr {
0xFF04 => (self.div_counter >> 8) as u8,
0xFF05 => self.tima,
0xFF06 => self.tma,
0xFF07 => self.tac | 0xF8, _ => 0xFF, }
}
pub fn raw_counter(&self) -> u16 {
self.div_counter
}
pub(crate) fn set_div_counter(&mut self, value: u16) {
self.div_counter = value;
}
pub fn write(&mut self, addr: u16, val: u8) -> bool {
match addr {
0xFF04 => {
if self.mux_and_high() {
self.do_tima_increment();
}
let div_apu_falling_edge = self.div_counter & self.div_apu_bit != 0;
self.div_counter = 0;
div_apu_falling_edge
}
0xFF05 => {
self.tima_overflow_pending = false;
if !self.tima_load_active {
self.tima = val;
}
false
}
0xFF06 => {
self.tma = val;
if self.tima_load_active {
self.tima = val;
}
false
}
0xFF07 => {
let was_high = self.mux_and_high();
self.tac = val & 0x07;
if was_high && !self.mux_and_high() {
self.do_tima_increment();
}
false
}
_ => false, }
}
pub fn tick(&mut self, m_cycles: u8) -> (u8, u8) {
let mut div_apu_falling: u8 = 0;
let mut div_apu_rising: u8 = 0;
for _ in 0..m_cycles {
self.tima_load_active = false;
if self.tima_overflow_pending {
self.tima = self.tma;
self.interrupt_pending = true;
self.tima_overflow_pending = false;
self.tima_load_active = true;
}
let enabled = self.tac & 0x04 != 0;
let bit = MUX_BIT[(self.tac & 0x03) as usize];
for _ in 0..4 {
let old = self.div_counter;
self.div_counter = self.div_counter.wrapping_add(1);
if enabled && old & bit != 0 && self.div_counter & bit == 0 {
self.do_tima_increment();
}
if old & self.div_apu_bit != 0 && self.div_counter & self.div_apu_bit == 0 {
div_apu_falling += 1;
}
if old & self.div_apu_bit == 0 && self.div_counter & self.div_apu_bit != 0 {
div_apu_rising += 1;
}
}
}
(div_apu_falling, div_apu_rising)
}
fn mux_and_high(&self) -> bool {
self.tac & 0x04 != 0 && self.div_counter & MUX_BIT[(self.tac & 0x03) as usize] != 0
}
fn do_tima_increment(&mut self) {
let (new_val, overflow) = self.tima.overflowing_add(1);
if overflow {
self.tima = 0x00;
self.tima_overflow_pending = true;
} else {
self.tima = new_val;
}
}
pub(crate) fn fire_write_overflow_if_pending(&mut self) -> bool {
if self.tima_overflow_pending {
self.tima = self.tma;
self.tima_overflow_pending = false;
self.tima_load_active = true;
self.interrupt_pending = true;
return true;
}
false
}
pub fn take_interrupt(&mut self) -> bool {
let p = self.interrupt_pending;
self.interrupt_pending = false;
p
}
}
impl Default for Timer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_div_increments_every_64_m_cycles() {
let mut timer = Timer::new();
for _ in 0..63 {
timer.tick(1);
}
assert_eq!(
timer.read(0xFF04),
0,
"DIV should not increment before 64 M-cycles"
);
timer.tick(1); assert_eq!(timer.read(0xFF04), 1, "DIV should be 1 after 64 M-cycles");
}
#[test]
fn test_ff03_read_returns_open_bus() {
let timer = Timer::new();
assert_eq!(
timer.read(0xFF03),
0xFF,
"$FF03 is unmapped and should return 0xFF"
);
}
#[test]
fn test_writing_div_resets_it_to_zero() {
let mut timer = Timer::new();
for _ in 0..64 {
timer.tick(1);
}
assert_eq!(timer.read(0xFF04), 1);
timer.write(0xFF04, 0x42); assert_eq!(timer.read(0xFF04), 0, "DIV should be 0 after any write");
}
#[test]
fn test_tima_does_not_increment_when_timer_disabled() {
let mut timer = Timer::new();
timer.write(0xFF07, 0x00); for _ in 0..1024 {
timer.tick(1);
}
assert_eq!(
timer.tima, 0,
"TIMA should not increment when timer is disabled"
);
}
#[test]
fn test_tima_increments_at_rate_set_by_tac_00() {
let mut timer = Timer::new();
timer.write(0xFF07, 0x04);
for _ in 0..255 {
timer.tick(1);
}
assert_eq!(timer.tima, 0, "TIMA should not increment before threshold");
timer.tick(1); assert_eq!(
timer.tima, 1,
"TIMA should be 1 after 256 M-cycles (1024 T) with TAC=0x04"
);
}
#[test]
fn test_tima_increments_at_rate_set_by_tac_01() {
let mut timer = Timer::new();
timer.write(0xFF07, 0x05);
for _ in 0..3 {
timer.tick(1);
}
assert_eq!(timer.tima, 0);
timer.tick(1); assert_eq!(timer.tima, 1);
}
#[test]
fn test_tima_overflow_reloads_tma_and_sets_interrupt() {
let mut timer = Timer::new();
timer.write(0xFF07, 0x05);
timer.write(0xFF06, 0x10); timer.tima = 0xFF;
timer.tick(4); assert_eq!(
timer.tima, 0x00,
"TIMA should be 0x00 immediately after overflow"
);
assert!(
!timer.interrupt_pending,
"interrupt fires on the next M-cycle"
);
timer.tick(1); assert_eq!(timer.tima, 0x10, "TIMA should reload from TMA on cycle B");
assert!(
timer.interrupt_pending,
"Interrupt should be pending after TIMA reload"
);
}
#[test]
fn test_take_interrupt_clears_pending_flag() {
let mut timer = Timer::new();
timer.interrupt_pending = true;
assert!(timer.take_interrupt());
assert!(!timer.interrupt_pending);
}
}