use super::interrupt::{InterruptController, bits};
use serde::{Deserialize, Serialize};
const TIMER_IRQ_BITS: [u16; 4] = [bits::TIMER0, bits::TIMER1, bits::TIMER2, bits::TIMER3];
const PRESCALERS: [u32; 4] = [1, 64, 256, 1024];
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
pub struct Timer {
pub counter: u16,
pub reload: u16,
pub control: u16,
prescaler_acc: u32,
}
impl Timer {
pub fn enabled(&self) -> bool {
self.control & 0x80 != 0
}
pub fn irq_on_overflow(&self) -> bool {
self.control & 0x40 != 0
}
pub fn cascade(&self) -> bool {
self.control & 0x04 != 0
}
pub fn prescaler(&self) -> u32 {
PRESCALERS[(self.control & 0x3) as usize]
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Timers {
pub channels: [Timer; 4],
}
impl Timers {
pub fn new() -> Self {
Self::default()
}
pub fn read_cnt_l(&self, i: usize) -> u16 {
self.channels[i].counter
}
pub fn read_cnt_h(&self, i: usize) -> u16 {
self.channels[i].control & 0x00C7
}
pub fn write_cnt_l(&mut self, i: usize, value: u16) {
self.channels[i].reload = value;
}
pub fn write_cnt_h(&mut self, i: usize, value: u16) {
let value = value & 0x00C7;
let was_enabled = self.channels[i].enabled();
self.channels[i].control = value;
let now_enabled = self.channels[i].enabled();
if !was_enabled && now_enabled {
self.channels[i].counter = self.channels[i].reload;
self.channels[i].prescaler_acc = 0;
}
}
pub fn align_prescaler_phase(&mut self, i: usize, global_phase: u32) {
let period = self.channels[i].prescaler();
self.channels[i].prescaler_acc = global_phase % period;
}
pub fn step(&mut self, cycles: u32, ic: &mut InterruptController) -> [u32; 4] {
let mut overflows = [0u32; 4];
for (i, t) in self.channels.iter_mut().enumerate() {
if !t.enabled() {
continue;
}
if i > 0 && t.cascade() {
continue;
}
let cycles_to_first_overflow = cycles_to_first_overflow(t, cycles);
t.prescaler_acc += cycles;
let period = t.prescaler();
let ticks = t.prescaler_acc / period;
t.prescaler_acc %= period;
overflows[i] = advance_ticks(t, ticks);
if overflows[i] > 0
&& t.irq_on_overflow()
&& let Some(cycles_to_first_overflow) = cycles_to_first_overflow
{
let cycles_late = cycles.saturating_sub(cycles_to_first_overflow);
ic.raise_late(TIMER_IRQ_BITS[i], cycles_late);
}
}
for i in 1..4 {
let cascade_ticks = overflows[i - 1];
if cascade_ticks == 0 {
continue;
}
let t = &mut self.channels[i];
if !t.enabled() || !t.cascade() {
continue;
}
overflows[i] = advance_ticks(t, cascade_ticks);
}
for i in 0..4 {
if overflows[i] > 0 && self.channels[i].irq_on_overflow() {
ic.raise(TIMER_IRQ_BITS[i]);
}
}
overflows
}
}
fn cycles_to_first_overflow(timer: &Timer, cycles: u32) -> Option<u32> {
if cycles == 0 {
return None;
}
let period = timer.prescaler();
let cycles_to_next_tick = if timer.prescaler_acc == 0 {
period
} else {
period - timer.prescaler_acc
};
let room = 0x1_0000u32 - u32::from(timer.counter);
let cycles_to_overflow = cycles_to_next_tick + (room - 1) * period;
(cycles_to_overflow <= cycles).then_some(cycles_to_overflow)
}
fn advance_ticks(timer: &mut Timer, mut ticks: u32) -> u32 {
let mut overflows = 0u32;
while ticks > 0 {
let room = 0x1_0000u32 - timer.counter as u32;
if ticks >= room {
ticks -= room;
timer.counter = timer.reload;
overflows += 1;
} else {
timer.counter = timer.counter.wrapping_add(ticks as u16);
ticks = 0;
}
}
overflows
}
#[cfg(test)]
mod tests {
use super::*;
fn enable(prescaler: u16, irq: bool, cascade: bool) -> u16 {
let mut c = 0x80 | (prescaler & 0x3);
if irq {
c |= 0x40;
}
if cascade {
c |= 0x04;
}
c
}
#[test]
fn prescaler_zero_ticks_every_cycle() {
let mut t = Timers::new();
let mut ic = InterruptController::new();
t.write_cnt_l(0, 0);
t.write_cnt_h(0, enable(0, false, false));
t.step(1024, &mut ic);
assert_eq!(t.read_cnt_l(0), 1024);
}
#[test]
fn prescaler_64() {
let mut t = Timers::new();
let mut ic = InterruptController::new();
t.write_cnt_h(0, enable(1, false, false));
t.step(64 * 5, &mut ic);
assert_eq!(t.read_cnt_l(0), 5);
t.step(63, &mut ic);
assert_eq!(t.read_cnt_l(0), 5);
t.step(1, &mut ic);
assert_eq!(t.read_cnt_l(0), 6);
}
#[test]
fn prescaler_256_and_1024() {
let mut t = Timers::new();
let mut ic = InterruptController::new();
t.write_cnt_h(1, enable(2, false, false));
t.write_cnt_h(2, enable(3, false, false));
t.step(256 * 3, &mut ic);
assert_eq!(t.read_cnt_l(1), 3);
t.step(1024 * 2 - 256 * 3, &mut ic);
assert!(t.read_cnt_l(1) > 3);
let t2 = t.read_cnt_l(2);
assert_eq!(t2, 2);
}
#[test]
fn enable_rising_edge_loads_reload() {
let mut t = Timers::new();
t.write_cnt_l(0, 0x1234);
assert_eq!(t.read_cnt_l(0), 0);
t.write_cnt_h(0, enable(0, false, false));
assert_eq!(t.read_cnt_l(0), 0x1234);
}
#[test]
fn overflow_reloads_from_reload_value() {
let mut t = Timers::new();
let mut ic = InterruptController::new();
t.write_cnt_l(0, 0xFFF0);
t.write_cnt_h(0, enable(0, false, false));
t.step(0x10, &mut ic);
assert_eq!(t.read_cnt_l(0), 0xFFF0);
}
#[test]
fn overflow_raises_irq_when_enabled() {
let mut t = Timers::new();
let mut ic = InterruptController::new();
ic.write_ie(bits::TIMER0);
ic.write_ime(1);
t.write_cnt_l(0, 0xFFFF);
t.write_cnt_h(0, enable(0, true, false));
t.step(1, &mut ic);
assert_eq!(t.read_cnt_l(0), 0xFFFF); assert!(ic.irq_line());
}
#[test]
fn no_irq_when_irq_bit_clear() {
let mut t = Timers::new();
let mut ic = InterruptController::new();
ic.write_ie(bits::TIMER0);
ic.write_ime(1);
t.write_cnt_l(0, 0xFFFF);
t.write_cnt_h(0, enable(0, false, false));
t.step(1, &mut ic);
assert!(!ic.irq_line());
}
#[test]
fn cascade_increments_on_previous_overflow() {
let mut t = Timers::new();
let mut ic = InterruptController::new();
t.write_cnt_l(0, 0xFFFF);
t.write_cnt_h(0, enable(0, false, false));
t.write_cnt_l(1, 0);
t.write_cnt_h(1, enable(0, false, true)); t.step(1, &mut ic);
assert_eq!(t.read_cnt_l(1), 1);
}
#[test]
fn cascade_does_not_apply_to_timer0() {
let mut t = Timers::new();
let mut ic = InterruptController::new();
t.write_cnt_h(0, enable(0, false, true));
t.step(100, &mut ic);
assert_eq!(
t.read_cnt_l(0),
100,
"TM0 should tick normally even when the cascade flag is set"
);
}
#[test]
fn full_overflow_cycle() {
let mut t = Timers::new();
let mut ic = InterruptController::new();
ic.write_ie(bits::TIMER0);
ic.write_ime(1);
t.write_cnt_l(0, 0); t.write_cnt_h(0, enable(0, true, false));
t.step(0x1_0000, &mut ic);
assert_eq!(t.read_cnt_l(0), 0);
assert!(ic.irq_line());
}
}