pub const TICKS_PER_SECOND: u32 = 8192;
pub const T2_TICKS_PER_T1_TICK: u32 = 512;
pub const CTRL_XTRA_OR_RUN: u8 = 1;
pub const CTRL_INT: u8 = 2;
pub const CTRL_WAKE: u8 = 4;
pub const CTRL_SRQ: u8 = 8;
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Timers {
pub t1: u8,
pub t2: u32,
pub t1_ctrl: u8,
pub t2_ctrl: u8,
pub(crate) t1_phase: u32,
pub(crate) t1_irq: bool,
pub(crate) t2_irq: bool,
pub(crate) irq_edge: bool,
}
impl Timers {
pub fn new() -> Self {
Self::default()
}
pub fn t2_running(&self) -> bool {
self.t2_ctrl & CTRL_XTRA_OR_RUN != 0
}
fn t1_msb(&self) -> bool {
self.t1 & 0x8 != 0
}
fn t2_msb(&self) -> bool {
self.t2 & 0x8000_0000 != 0
}
pub fn tick(&mut self, ticks: u32) {
let mut remaining = ticks;
while remaining > 0 && self.t2_running() {
let step = remaining.min(self.ticks_until_event());
self.advance(step);
self.update_irq();
remaining -= step;
}
}
fn advance(&mut self, n: u32) {
self.t2 = self.t2.wrapping_sub(n);
let total = self.t1_phase + n % T2_TICKS_PER_T1_TICK;
let decrements = n / T2_TICKS_PER_T1_TICK + total / T2_TICKS_PER_T1_TICK;
self.t1_phase = total % T2_TICKS_PER_T1_TICK;
self.t1 = (self.t1.wrapping_sub((decrements % 16) as u8)) & 0xF;
}
fn update_irq(&mut self) {
let t1_level = self.t1_msb() && self.t1_ctrl & CTRL_INT != 0;
let t2_level = self.t2_msb() && self.t2_ctrl & CTRL_INT != 0;
if t1_level && !self.t1_irq {
self.irq_edge = true;
}
if t2_level && !self.t2_irq {
self.irq_edge = true;
}
self.t1_irq = t1_level;
self.t2_irq = t2_level;
}
pub fn write_t1(&mut self, v: u8) {
let v = v & 0xF;
if v != self.t1 {
self.t1_phase = 0;
}
self.t1 = v;
self.update_irq();
}
pub fn write_t2_nibble(&mut self, idx: u8, v: u8) {
let shift = u32::from(idx & 7) * 4;
self.t2 = (self.t2 & !(0xF << shift)) | (u32::from(v & 0xF) << shift);
self.update_irq();
}
pub fn read_t2_nibble(&self, idx: u8) -> u8 {
((self.t2 >> (u32::from(idx & 7) * 4)) & 0xF) as u8
}
pub fn write_t1_ctrl(&mut self, v: u8) {
self.t1_ctrl = v & 0x7;
self.update_irq();
}
pub fn write_t2_ctrl(&mut self, v: u8) {
self.t2_ctrl = v & 0x7;
self.update_irq();
}
fn srq(msb: bool, ctrl: u8) -> u8 {
if msb && ctrl & (CTRL_INT | CTRL_WAKE) != 0 {
CTRL_SRQ
} else {
0
}
}
pub fn read_t1_ctrl(&self) -> u8 {
self.t1_ctrl | Self::srq(self.t1_msb(), self.t1_ctrl)
}
pub fn read_t2_ctrl(&self) -> u8 {
self.t2_ctrl | Self::srq(self.t2_msb(), self.t2_ctrl)
}
pub fn take_interrupt(&mut self) -> bool {
std::mem::take(&mut self.irq_edge)
}
pub fn wake(&self) -> bool {
(self.t1_msb() && self.t1_ctrl & CTRL_WAKE != 0)
|| (self.t2_msb() && self.t2_ctrl & CTRL_WAKE != 0)
}
pub fn ticks_until_event(&self) -> u32 {
if !self.t2_running() {
return u32::MAX;
}
let t2 = if self.t2_msb() {
self.t2 - 0x7FFF_FFFF
} else {
self.t2 + 1
};
let t1_steps = if self.t1_msb() {
u32::from(self.t1) - 7
} else {
u32::from(self.t1) + 1
};
let t1 = t1_steps * T2_TICKS_PER_T1_TICK - self.t1_phase;
t1.min(t2)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn running(int: bool) -> Timers {
let mut t = Timers::new();
let ctrl = CTRL_XTRA_OR_RUN | if int { CTRL_INT } else { 0 };
t.write_t2_ctrl(ctrl);
t
}
#[test]
fn t2_expires_through_zero() {
let mut t = running(true);
t.t2 = 2;
t.tick(2);
assert_eq!(t.t2, 0);
assert!(!t.take_interrupt());
assert_eq!(t.read_t2_ctrl() & CTRL_SRQ, 0);
t.tick(1);
assert_eq!(t.t2, 0xFFFF_FFFF);
assert!(t.take_interrupt());
assert!(!t.take_interrupt());
assert_eq!(t.read_t2_ctrl(), CTRL_XTRA_OR_RUN | CTRL_INT | CTRL_SRQ);
}
#[test]
fn bulk_tick_fires_one_edge() {
let mut t = running(true);
t.t2 = 10;
t.tick(1_000_000);
assert_eq!(t.t2, 10u32.wrapping_sub(1_000_000));
assert!(t.take_interrupt());
assert!(!t.take_interrupt());
}
#[test]
fn bulk_tick_matches_single_steps() {
let mut a = running(true);
a.t2 = 3000;
a.t1 = 2;
a.write_t1_ctrl(CTRL_INT);
let mut b = a.clone();
a.tick(5000);
for _ in 0..5000 {
b.tick(1);
}
assert_eq!(a, b);
}
#[test]
fn t1_stops_without_trun() {
let mut t = Timers::new();
t.write_t1(5);
t.tick(10_000);
assert_eq!(t.t1, 5);
assert_eq!(t.t2, 0);
}
#[test]
fn t1_decrements_every_512_ticks() {
let mut t = running(false);
t.t2 = 0x7000_0000;
t.write_t1(5);
t.tick(511);
assert_eq!(t.t1, 5);
t.tick(1);
assert_eq!(t.t1, 4);
t.tick(512 * 4);
assert_eq!(t.t1, 0);
t.tick(512);
assert_eq!(t.t1, 0xF);
}
#[test]
fn t1_reload_same_value_keeps_phase() {
let mut t = running(false);
t.t2 = 0x7000_0000;
t.write_t1(5);
t.tick(300);
t.write_t1(5);
t.tick(212);
assert_eq!(t.t1, 4);
t.tick(300);
t.write_t1(6);
t.tick(212);
assert_eq!(t.t1, 6);
t.tick(300);
assert_eq!(t.t1, 5);
}
#[test]
fn ticks_until_event_simple() {
let mut t = Timers::new();
assert_eq!(t.ticks_until_event(), u32::MAX);
t.write_t2_ctrl(CTRL_XTRA_OR_RUN);
t.t2 = 100;
t.t1 = 3;
assert_eq!(t.ticks_until_event(), 101);
t.t2 = 0x8000_0005;
assert_eq!(t.ticks_until_event(), 6);
t.t2 = 0x7000_0000;
t.tick(12);
assert_eq!(t.ticks_until_event(), 4 * 512 - 12);
}
#[test]
fn wake_needs_wake_bit() {
let mut t = running(false);
t.t2 = 0;
t.tick(1);
assert!(!t.wake());
assert_eq!(t.read_t2_ctrl() & CTRL_SRQ, 0);
t.write_t2_ctrl(CTRL_XTRA_OR_RUN | CTRL_WAKE);
assert!(t.wake());
assert_eq!(t.read_t2_ctrl() & CTRL_SRQ, CTRL_SRQ);
assert!(!t.take_interrupt());
}
#[test]
fn enabling_int_on_expired_timer_is_an_edge() {
let mut t = Timers::new();
t.write_t1(0x8);
assert!(!t.take_interrupt());
t.write_t1_ctrl(CTRL_INT);
assert!(t.take_interrupt());
t.write_t1_ctrl(CTRL_INT | 0x8);
assert_eq!(t.t1_ctrl, CTRL_INT);
assert!(!t.take_interrupt());
}
}
#[cfg(test)]
mod read_during_service_tests {
use super::*;
#[test]
fn t2_reads_keep_counting_after_an_untaken_expiry() {
let mut t = Timers::new();
t.write_t2_ctrl(CTRL_XTRA_OR_RUN | CTRL_INT);
t.t2 = 1;
t.tick(2);
assert!(t.take_interrupt());
let read = |t: &Timers| {
(0..8)
.rev()
.fold(0u32, |a, i| (a << 4) | u32::from(t.read_t2_nibble(i)))
};
assert_eq!(read(&t), 0xFFFF_FFFF);
assert_eq!(
t.read_t2_ctrl() & CTRL_SRQ,
CTRL_SRQ,
"service request stays up"
);
let nibble1 = t.read_t2_nibble(1);
t.tick(16);
assert_eq!(read(&t), 0xFFFF_FFEF);
assert_ne!(t.read_t2_nibble(1), nibble1);
t.write_t2_nibble(7, 0);
assert_eq!(t.read_t2_nibble(7), 0);
}
}