fix(timers): retain shared free-running prescaler phase

This commit is contained in:
0xrsydn 2026-10-06 22:04:59 +07:00
commit 27806ccf9e
13 changed files with 459 additions and 32 deletions

View file

@ -1,6 +1,6 @@
//! GBA I/O subset: DMA, timers, interrupts, WAITCNT, display, and keypad input.
//! Timer phase resets on enable or clock-source changes. Hardware startup delays,
//! shared prescaler phase, audio events, and interrupt delivery delays are not modeled.
//! Timers share a free-running prescaler phase. Hardware startup/write delays,
//! audio events, and interrupt delivery delays are not modeled.
mod timer_step;
pub(crate) use timer_step::TimerStep;
@ -67,7 +67,6 @@ struct Timer {
reload: u16,
counter: u16,
control: u8,
phase: u32,
}
impl Timer {
@ -102,25 +101,22 @@ impl Timer {
if start {
self.counter = self.reload;
}
if start || (self.control ^ value) & 7 != 0 {
self.phase = 0;
}
self.control = value;
}
/// Next independently clocked overflow. Cascaded timers receive their
/// pulses at a predecessor's event and cannot overflow earlier.
fn next_overflow(&self) -> Option<u32> {
fn next_overflow(&self, phase: u16) -> Option<u32> {
if !self.enabled() || self.count_up() {
return None;
}
let divisor = [1, 64, 256, 1024][usize::from(self.control & 3)];
Some((0x1_0000 - u32::from(self.counter)) * divisor - self.phase)
Some((0x1_0000 - u32::from(self.counter)) * divisor - u32::from(phase) % divisor)
}
/// Advance without looping once per cycle or overflow. Return the number of
/// overflows, not just a boolean, so cascaded timers receive every pulse.
fn advance(&mut self, cycles: u32, previous_overflows: u64) -> u64 {
fn advance(&mut self, cycles: u32, previous_overflows: u64, phase: u16) -> u64 {
if !self.enabled() {
return 0;
}
@ -128,9 +124,9 @@ impl Timer {
previous_overflows
} else {
let divisor = [1, 64, 256, 1024][usize::from(self.control & 3)];
let elapsed = u64::from(self.phase) + u64::from(cycles);
self.phase = (elapsed % divisor) as u32;
elapsed / divisor
// Count shared divider edges in (start, end], independently of enable time.
let start = u64::from(phase);
(start + u64::from(cycles)) / divisor - start / divisor
};
let until_overflow = 0x1_0000 - u64::from(self.counter);
if ticks < until_overflow {
@ -155,6 +151,8 @@ enum PowerState {
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub(crate) struct Io {
timers: [Timer; 4],
// Low ten system-clock bits cover every supported divider. STOP freezes this phase.
timer_phase: u16,
enable: u16,
pending: u16,
master_enable: bool,
@ -320,7 +318,7 @@ impl Io {
pub(crate) fn next_event_cycles(&self) -> u32 {
self.timers
.iter()
.filter_map(Timer::next_overflow)
.filter_map(|timer| timer.next_overflow(self.timer_phase))
.fold(self.display.next_event_cycles(), u32::min)
}
@ -404,19 +402,22 @@ impl Io {
pub(crate) fn commit_timer_step(&mut self, step: TimerStep) {
self.timers = step.timers;
self.timer_phase = step.phase;
self.pending = (self.pending & !TIMER_IRQ_MASK) | step.pending;
self.wake_if_requested();
}
fn advance_timer_bank(timers: &mut [Timer; 4], cycles: u32) -> u16 {
fn advance_timer_bank(timers: &mut [Timer; 4], phase: &mut u16, cycles: u32) -> u16 {
let mut pending = 0;
let mut overflows = 0;
for (index, timer) in timers.iter_mut().enumerate() {
overflows = timer.advance(cycles, overflows);
overflows = timer.advance(cycles, overflows, *phase);
if overflows != 0 && timer.control & 0x40 != 0 {
pending |= 1 << (3 + index);
}
}
// Advance even when every timer is disabled; writes never reset this clock.
*phase = ((u64::from(*phase) + u64::from(cycles)) & 1023) as u16;
pending
}
@ -453,7 +454,8 @@ impl Io {
self.dma.trigger(vblank, hblank);
self.pending |= self.display.advance(cycles);
if advance_timers {
self.pending |= Self::advance_timer_bank(&mut self.timers, cycles);
self.pending |=
Self::advance_timer_bank(&mut self.timers, &mut self.timer_phase, cycles);
}
self.wake_if_requested();
}

View file

@ -7,6 +7,7 @@ pub(super) const TIMER_IRQ_MASK: u16 = 0x78;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct TimerStep {
pub(super) timers: [Timer; 4],
pub(super) phase: u16,
pub(super) pending: u16,
elapsed: u32,
}
@ -15,6 +16,7 @@ impl TimerStep {
pub(super) fn new(io: &Io) -> Self {
Self {
timers: io.timers,
phase: io.timer_phase,
pending: io.pending & TIMER_IRQ_MASK,
elapsed: 0,
}
@ -22,7 +24,8 @@ impl TimerStep {
pub(crate) fn advance_to(&mut self, elapsed: u32) {
assert!(elapsed >= self.elapsed, "timer step time must be monotonic");
self.pending |= Io::advance_timer_bank(&mut self.timers, elapsed - self.elapsed);
self.pending |=
Io::advance_timer_bank(&mut self.timers, &mut self.phase, elapsed - self.elapsed);
self.elapsed = elapsed;
}

View file

@ -322,6 +322,78 @@ fn bios_power_store_pays_its_cycles_and_new_timer_irq_wakes_only_halt() {
}
}
#[test]
fn machine_enable_joins_the_shared_phase_and_commits_it_without_duplicate_ticks() {
let (cpu, mut memory) = prepared(
false,
&[0xe581_0000, 0xe591_2000],
&[(0, 0x0081_0000), (1, TIMER_BASE)],
);
memory.advance_cycles(56); // The disabled divider still runs.
let mut machine = Machine::new(cpu, memory);
machine.step().unwrap(); // Source 6 + store 1: enable at t=63.
assert_eq!(machine.cycles(), 63);
assert_eq!(machine.memory().read16(TIMER_BASE).unwrap(), 0);
machine.step().unwrap(); // N source 8 + read 1 + internal 1: t=73.
assert_eq!(machine.cpu().registers()[2], 0x0081_0001);
assert_eq!(machine.cycles(), 73);
machine.memory_mut().advance_cycles(54);
assert_eq!(machine.memory().read16(TIMER_BASE).unwrap(), 1);
machine.memory_mut().advance_cycles(1);
assert_eq!(machine.memory().read16(TIMER_BASE).unwrap(), 2);
}
#[test]
fn failed_cpu_and_dma_work_cannot_consume_a_shared_prescaler_edge() {
let (cpu, mut memory) = prepared(false, &[0xe891_0005], &[(1, TIMER_BASE + 12)]);
start(&mut memory, 0, 0xffff, 0xc1);
memory.advance_cycles(63);
let before = memory.io.timer_step();
let mut machine = Machine::new(cpu, memory);
assert!(machine.step().is_err()); // First data read succeeds after the t=64 overflow; second fails.
assert_eq!(machine.memory().io.timer_step(), before);
assert_eq!(machine.cycles(), 63);
dma(machine.memory_mut(), 0x0e00_0000, RAM, 1);
assert!(machine.step().is_err());
assert_eq!(machine.memory().io.timer_step(), before);
machine.memory_mut().advance_cycles(1);
assert_eq!(machine.memory().read16(IF).unwrap(), 8);
}
#[test]
fn dma_timer_phases_share_the_divider_with_later_host_clock_advances() {
let mut memory = Memory::new(Vec::new()).unwrap();
memory.advance_cycles(60);
start(&mut memory, 0, 0, 0x81);
dma(&mut memory, TIMER_BASE, RAM, 1);
memory.step_dma().unwrap().unwrap(); // Read t=63, destination t=64.
assert_eq!(memory.read32(RAM).unwrap(), 0x0081_0000);
assert_eq!(memory.read16(TIMER_BASE).unwrap(), 1);
memory.write32(RAM, 0x0081_0000).unwrap();
dma(&mut memory, RAM, TIMER_BASE + 4, 1);
memory.step_dma().unwrap().unwrap(); // Enable timer1 at t=68, not at a new divider origin.
memory.advance_cycles(59);
assert_eq!(memory.cycles(), 127);
assert_eq!(memory.read16(TIMER_BASE + 4).unwrap(), 0);
memory.advance_cycles(1);
assert_eq!(memory.read16(TIMER_BASE).unwrap(), 2);
assert_eq!(memory.read16(TIMER_BASE + 4).unwrap(), 1);
}
#[test]
fn halt_after_a_machine_timer_start_uses_the_committed_shared_phase() {
let (cpu, mut memory) = prepared(false, &[0xe581_0000], &[(0, 0x00c1_ffff), (1, TIMER_BASE)]);
memory.advance_cycles(56);
memory.write16(IE, 8).unwrap();
let mut machine = Machine::new(cpu, memory);
machine.step().unwrap(); // Enable at t=63.
machine.memory_mut().write8(HALTCNT, 0).unwrap();
assert_eq!(machine.step().unwrap(), StepKind::HaltIdle);
assert_eq!(machine.last_timing().idle_cycles, 1);
assert_eq!(machine.cycles(), 64);
assert!(!machine.halted());
}
#[test]
fn halt_and_stop_keep_their_clock_ownership() {
for stop in [false, true] {

View file

@ -0,0 +1,230 @@
//! Original shared-divider checks. Startup and register-write delays remain outside this model.
use gba_core::{
cpu::Cpu,
input::Buttons,
io::{HALTCNT, IE, IF, KEYCNT, TIMER_BASE},
machine::{Machine, StepKind},
memory::{Memory, ROM_START},
};
const DIVISORS: [u64; 4] = [1, 64, 256, 1024];
fn start(memory: &mut Memory, timer: u32, reload: u16, control: u16) {
memory
.write32(
TIMER_BASE + timer * 4,
u32::from(reload) | u32::from(control) << 16,
)
.unwrap();
}
#[test]
fn every_shared_phase_selects_the_next_edge_without_replaying_the_current_edge() {
for phase in 0..1024_u32 {
let mut memory = Memory::new(Vec::new()).unwrap();
memory.advance_cycles(phase); // The divider runs with all timers disabled.
for selection in 0..4 {
start(&mut memory, selection, 0, 0x80 | selection as u16);
}
memory.advance_cycles(0);
for selection in 0..4 {
assert_eq!(memory.read16(TIMER_BASE + selection * 4).unwrap(), 0);
}
// Cover two complete divider periods after every possible enable phase.
let mut elapsed = 0;
for delta in [1, 62, 1, 191, 1, 767, 1, 1024] {
memory.advance_cycles(delta);
elapsed += delta;
for (selection, divisor) in DIVISORS.into_iter().enumerate() {
let expected = (u64::from(phase + elapsed) / divisor) - u64::from(phase) / divisor;
assert_eq!(
memory.read16(TIMER_BASE + selection as u32 * 4).unwrap(),
expected as u16,
"phase={phase}, elapsed={elapsed}, divisor={divisor}"
);
}
}
}
}
#[test]
fn staggered_timers_share_ticks_and_cascade_ignores_prescaler_selection() {
let mut memory = Memory::new(Vec::new()).unwrap();
memory.advance_cycles(10);
start(&mut memory, 0, 0xfffe, 0xc1);
memory.advance_cycles(15);
start(&mut memory, 1, 0xfffe, 0xc1);
memory.advance_cycles(38);
start(&mut memory, 2, 0xfffe, 0xc1);
start(&mut memory, 3, 0xffff, 0xc7); // Cascaded, /1024 field ignored.
memory.advance_cycles(1); // All independent timers tick at system cycle 64.
for index in 0..3 {
assert_eq!(memory.read16(TIMER_BASE + index * 4).unwrap(), 0xffff);
}
assert_eq!(memory.read16(IF).unwrap(), 0);
memory.advance_cycles(64);
for index in 0..3 {
assert_eq!(memory.read16(TIMER_BASE + index * 4).unwrap(), 0xfffe);
}
assert_eq!(memory.read16(TIMER_BASE + 12).unwrap(), 0xffff);
assert_eq!(memory.read16(IF).unwrap(), 0x78);
}
#[test]
fn leaving_cascade_joins_the_existing_divider_instead_of_starting_a_new_period() {
let mut memory = Memory::new(Vec::new()).unwrap();
start(&mut memory, 1, 0, 0x85); // Timer0 disabled: no cascade pulses.
memory.advance_cycles(63);
memory.write16(TIMER_BASE + 6, 0x81).unwrap();
memory.advance_cycles(1);
assert_eq!(memory.read16(TIMER_BASE + 4).unwrap(), 1);
memory.write16(TIMER_BASE + 6, 0x85).unwrap();
memory.advance_cycles(63);
memory.write16(TIMER_BASE + 6, 0x81).unwrap();
memory.advance_cycles(1);
assert_eq!(memory.read16(TIMER_BASE + 4).unwrap(), 2);
}
#[test]
fn halt_stops_at_the_shared_edge_and_stop_freezes_the_divider_until_keypad_wake() {
let mut memory = Memory::new(Vec::new()).unwrap();
memory.advance_cycles(63);
start(&mut memory, 0, 0xffff, 0xc1);
memory.write16(IE, 8).unwrap();
memory.write8(HALTCNT, 0).unwrap();
let mut machine = Machine::new(Cpu::new(ROM_START), memory);
assert_eq!(machine.step().unwrap(), StepKind::HaltIdle);
assert_eq!(machine.last_timing().idle_cycles, 1);
assert_eq!(machine.cycles(), 64);
assert_eq!(machine.memory().read16(IF).unwrap(), 8);
assert!(!machine.halted());
let mut memory = Memory::new(Vec::new()).unwrap();
memory.advance_cycles(63);
start(&mut memory, 0, 0, 0x81);
memory.write16(IE, 0x1000).unwrap();
memory.write16(KEYCNT, 0x4001).unwrap();
memory.write8(HALTCNT, 0x80).unwrap();
memory.advance_cycles(1000);
assert_eq!(memory.cycles(), 63);
memory.set_buttons(Buttons::from_bits(1));
assert!(!memory.stopped());
memory.advance_cycles(1);
assert_eq!(memory.cycles(), 64);
assert_eq!(memory.read16(TIMER_BASE).unwrap(), 1);
}
#[test]
fn maximum_batches_keep_low_divider_bits_without_per_cycle_iteration() {
let mut memory = Memory::new(Vec::new()).unwrap();
memory.advance_cycles(u32::MAX); // Low ten bits are all set, even with no enabled timer.
start(&mut memory, 0, 0, 0x83);
memory.advance_cycles(1);
assert_eq!(memory.read16(TIMER_BASE).unwrap(), 1);
memory.advance_cycles(u32::MAX); // 4,194,303 ticks, wrapping the 16-bit counter to zero.
assert_eq!(memory.read16(TIMER_BASE).unwrap(), 0);
memory.advance_cycles(1);
assert_eq!(memory.read16(TIMER_BASE).unwrap(), 1);
assert_eq!(memory.cycles(), 2 * (u64::from(u32::MAX) + 1));
}
#[derive(Clone, Copy, Default)]
struct ReferenceTimer {
counter: u16,
reload: u16,
control: u16,
}
struct Reference {
timers: [ReferenceTimer; 4],
clock: u64,
pending: u16,
}
impl Reference {
fn control(&mut self, index: usize, value: u16) {
let timer = &mut self.timers[index];
let value = value & if index == 0 { 0xc3 } else { 0xc7 };
if timer.control & 0x80 == 0 && value & 0x80 != 0 {
timer.counter = timer.reload;
}
timer.control = value;
}
fn advance(&mut self, cycles: u32) {
// Independent literal clock-edge simulation. No phase accumulator or bulk division.
for _ in 0..cycles {
self.clock += 1;
let mut overflow = false;
for (index, timer) in self.timers.iter_mut().enumerate() {
let tick = timer.control & 0x80 != 0
&& if timer.control & 4 != 0 {
overflow
} else {
self.clock
.is_multiple_of(DIVISORS[usize::from(timer.control & 3)])
};
overflow = tick && timer.counter == 0xffff;
if overflow {
timer.counter = timer.reload;
if timer.control & 0x40 != 0 {
self.pending |= 1 << (index + 3);
}
} else if tick {
timer.counter += 1;
}
}
}
}
}
#[test]
fn dynamic_control_reload_and_clock_sequences_match_independent_edge_simulation() {
for mut seed in [0x1020_3040_u32, 0xdead_beef, 0x7654_3210] {
let mut memory = Memory::new(Vec::new()).unwrap();
let mut reference = Reference {
timers: [ReferenceTimer::default(); 4],
clock: 0,
pending: 0,
};
for _ in 0..2000 {
seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
let index = ((seed >> 8) & 3) as usize;
let address = TIMER_BASE + index as u32 * 4;
match seed >> 29 {
0 | 1 => {
let value = ((seed >> 16) as u16 & 0xc7) | 0x80;
memory.write16(address + 2, value).unwrap();
reference.control(index, value);
}
2 => {
memory.write16(address + 2, 0).unwrap();
reference.control(index, 0);
}
3 => {
let reload = 0xfff0 | ((seed >> 16) as u16 & 15);
memory.write16(address, reload).unwrap();
reference.timers[index].reload = reload;
}
4 => {
let mask = (seed >> 16) as u16 & 0x78;
memory.write16(IF, mask).unwrap();
reference.pending &= !mask;
}
_ => {
let cycles = (seed >> 16) & 2047;
memory.advance_cycles(cycles);
reference.advance(cycles);
}
}
for (index, timer) in reference.timers.iter().enumerate() {
assert_eq!(
memory.read32(TIMER_BASE + index as u32 * 4).unwrap(),
u32::from(timer.counter) | u32::from(timer.control) << 16
);
}
assert_eq!(memory.read16(IF).unwrap(), reference.pending);
assert_eq!(memory.cycles(), reference.clock);
}
}
}

View file

@ -100,7 +100,7 @@ fn all_prescalers_retain_fractional_cycles_between_advances() {
}
#[test]
fn clock_source_changes_and_restart_reset_provisional_phase() {
fn clock_source_changes_and_restart_keep_the_shared_phase() {
let mut bus = memory();
bus.write32(TIMER_BASE, 0x0081_0000).unwrap(); // /64
bus.advance_cycles(63);
@ -108,16 +108,17 @@ fn clock_source_changes_and_restart_reset_provisional_phase() {
bus.advance_cycles(1);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 1);
bus.advance_cycles(63);
bus.write16(TIMER_BASE + 2, 0x82).unwrap(); // /256 resets phase
bus.advance_cycles(255);
bus.write16(TIMER_BASE + 2, 0x82).unwrap(); // t=127: next shared /256 edge is t=256.
bus.advance_cycles(128);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 1);
bus.advance_cycles(1);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 2);
bus.write16(TIMER_BASE + 2, 0).unwrap();
bus.advance_cycles(255);
bus.advance_cycles(255); // t=511; disabled time still advances the divider.
bus.write16(TIMER_BASE + 2, 0x82).unwrap();
bus.advance_cycles(255);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0);
bus.advance_cycles(1);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 1);
}
#[test]