pub struct Timer {
div_counter: u16,
pub tima: u8,
pub tma: u8,
pub tac: u8,
pub interrupt_pending: bool,
}
const CLOCK_DIVS_T: [u16; 4] = [1024, 16, 64, 256];
impl Timer {
pub fn new() -> Self {
Self {
div_counter: 0,
tima: 0,
tma: 0,
tac: 0,
interrupt_pending: false,
}
}
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 write(&mut self, addr: u16, val: u8) {
match addr {
0xFF04 => self.div_counter = 0, 0xFF05 => self.tima = val,
0xFF06 => self.tma = val,
0xFF07 => self.tac = val & 0x07,
_ => {} }
}
pub fn tick(&mut self, m_cycles: u8) {
for _ in 0..m_cycles {
self.div_counter = self.div_counter.wrapping_add(4);
let enabled = self.tac & 0x04 != 0;
if !enabled {
continue;
}
let threshold = CLOCK_DIVS_T[(self.tac & 0x03) as usize];
if self.div_counter.is_multiple_of(threshold) {
let (new_tima, overflow) = self.tima.overflowing_add(1);
if overflow {
self.tima = self.tma;
self.interrupt_pending = true;
} else {
self.tima = new_tima;
}
}
}
}
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, 0x10, "TIMA should reload from TMA on overflow");
assert!(
timer.interrupt_pending,
"Interrupt should be pending after TIMA overflow"
);
}
#[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);
}
}