gameboy-rust/tests/timers.rs
0xrsydn 156f1eb839 feat(memory): map GBA memory and device registers
Add memory mirrors, video-bus write rules, I/O latches, timers, and interrupt registers.
2026-10-05 12:58:38 +07:00

360 lines
13 KiB
Rust

use gba_rust::{
io::{IE, IF, IME, TIMER_BASE},
memory::{Memory, MemoryError},
};
fn memory() -> Memory {
Memory::new(vec![]).unwrap()
}
#[test]
fn io_reset_values_masks_and_unused_bytes() {
let mut bus = memory();
for address in (TIMER_BASE..TIMER_BASE + 16)
.chain(IE..IE + 4)
.chain(IME..IME + 4)
{
assert_eq!(bus.read8(address).unwrap(), 0);
}
bus.write16(IE, 0xffff).unwrap();
assert_eq!(bus.read16(IE).unwrap(), 0x3fff);
bus.write32(IME, u32::MAX).unwrap();
assert_eq!(bus.read32(IME).unwrap(), 1);
bus.write32(IME, 0xffff_fffe).unwrap();
assert_eq!(bus.read32(IME).unwrap(), 0);
for index in 0..4 {
let address = TIMER_BASE + index * 4 + 2;
bus.write16(address, 0xffff).unwrap();
assert_eq!(
bus.read16(address).unwrap(),
if index == 0 { 0xc3 } else { 0xc7 }
);
}
}
#[test]
fn timer_data_reads_counter_not_reload_and_start_loads_reload() {
let mut bus = memory();
bus.write16(TIMER_BASE, 0x1234).unwrap();
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0);
bus.advance_cycles(100);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0);
bus.write16(TIMER_BASE + 2, 0x80).unwrap();
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0x1234);
bus.advance_cycles(5);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0x1239);
bus.write16(TIMER_BASE, 0xabcd).unwrap();
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0x1239);
bus.write16(TIMER_BASE + 2, 0x80).unwrap(); // No rising enable edge
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0x1239);
bus.write16(TIMER_BASE + 2, 0).unwrap();
bus.advance_cycles(100);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0x1239);
bus.write16(TIMER_BASE + 2, 0x80).unwrap();
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0xabcd);
}
#[test]
fn combined_word_write_uses_new_reload_before_enabling() {
let mut bus = memory();
for index in 0..4 {
let address = TIMER_BASE + index * 4;
bus.write32(address, 0x0080_abcd).unwrap();
assert_eq!(bus.read32(address).unwrap(), 0x0080_abcd);
}
bus.advance_cycles(1);
for index in 0..4 {
assert_eq!(bus.read16(TIMER_BASE + index * 4).unwrap(), 0xabce);
}
}
#[test]
fn reload_byte_writes_merge_with_hidden_latch_not_counter() {
let mut bus = memory();
bus.write16(TIMER_BASE, 0x1234).unwrap();
bus.write8(TIMER_BASE, 0x56).unwrap();
bus.write8(TIMER_BASE + 2, 0x80).unwrap();
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0x1256);
bus.advance_cycles(1000);
bus.write8(TIMER_BASE + 1, 0xab).unwrap();
bus.write8(TIMER_BASE + 2, 0).unwrap();
bus.write8(TIMER_BASE + 2, 0x80).unwrap();
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0xab56);
}
#[test]
fn all_prescalers_retain_fractional_cycles_between_advances() {
for (selection, divisor) in [1, 64, 256, 1024].into_iter().enumerate() {
let mut bus = memory();
bus.write32(TIMER_BASE, ((0x80 | selection as u32) << 16) | 123)
.unwrap();
bus.advance_cycles(divisor - 1);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 123);
bus.advance_cycles(1);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 124);
bus.advance_cycles(divisor * 3 + divisor - 1);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 127);
bus.advance_cycles(1);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 128);
}
}
#[test]
fn clock_source_changes_and_restart_reset_provisional_phase() {
let mut bus = memory();
bus.write32(TIMER_BASE, 0x0081_0000).unwrap(); // /64
bus.advance_cycles(63);
bus.write16(TIMER_BASE + 2, 0xc1).unwrap(); // IRQ change preserves 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);
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.write16(TIMER_BASE + 2, 0x82).unwrap();
bus.advance_cycles(255);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0);
}
#[test]
fn overflow_reloads_and_latches_request_without_delivery_enables() {
let mut bus = memory();
for index in 0..4 {
bus.write32(TIMER_BASE + index * 4, 0x00c0_fffe).unwrap();
}
bus.advance_cycles(1);
assert_eq!(bus.read16(IF).unwrap(), 0);
bus.advance_cycles(1);
assert_eq!(bus.read16(IF).unwrap(), 0x78);
assert!(!bus.irq_pending()); // IE and IME remain zero
for index in 0..4 {
assert_eq!(bus.read16(TIMER_BASE + index * 4).unwrap(), 0xfffe);
}
bus.advance_cycles(5);
assert_eq!(bus.read16(IF).unwrap(), 0x78);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0xffff);
}
#[test]
fn overflow_uses_updated_reload_without_resetting_current_counter() {
let mut bus = memory();
bus.write32(TIMER_BASE, 0x00c0_fffe).unwrap();
bus.write16(TIMER_BASE, 0x1234).unwrap();
bus.advance_cycles(1);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0xffff);
bus.advance_cycles(1);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0x1234);
assert_eq!(bus.read16(IF).unwrap(), 8);
}
#[test]
fn overflow_with_local_irq_disabled_does_not_set_request() {
let mut bus = memory();
bus.write32(TIMER_BASE, 0x0080_ffff).unwrap();
bus.advance_cycles(100);
assert_eq!(bus.read16(IF).unwrap(), 0);
bus.write16(TIMER_BASE + 2, 0xc0).unwrap();
assert_eq!(bus.read16(IF).unwrap(), 0); // No retroactive request
bus.advance_cycles(1);
assert_eq!(bus.read16(IF).unwrap(), 8);
}
#[test]
fn cascades_count_all_overflows_including_multiple_levels() {
let mut bus = memory();
bus.write32(TIMER_BASE, 0x00c0_fffe).unwrap(); // Overflow every 2 cycles
for index in 1..4 {
bus.write32(TIMER_BASE + index * 4, 0x00c7_fffe).unwrap(); // /1024 ignored
}
bus.advance_cycles(15);
assert_eq!(bus.read16(IF).unwrap(), 0x38);
for index in 0..4 {
assert_eq!(bus.read16(TIMER_BASE + index * 4).unwrap(), 0xffff);
}
bus.advance_cycles(1);
assert_eq!(bus.read16(IF).unwrap(), 0x78);
for index in 0..4 {
assert_eq!(bus.read16(TIMER_BASE + index * 4).unwrap(), 0xfffe);
}
}
#[test]
fn disabled_cascade_stage_blocks_downstream_pulses() {
let mut bus = memory();
bus.write32(TIMER_BASE, 0x0080_ffff).unwrap();
bus.write32(TIMER_BASE + 4, 0x0004_ffff).unwrap(); // Disabled timer 1
bus.write32(TIMER_BASE + 8, 0x00c4_1234).unwrap();
bus.advance_cycles(100_000);
assert_eq!(bus.read16(TIMER_BASE + 8).unwrap(), 0x1234);
assert_eq!(bus.read16(IF).unwrap(), 0);
}
#[test]
fn normal_timer_breaks_cascade_dependency_and_timer_zero_ignores_count_up() {
let mut bus = memory();
bus.write32(TIMER_BASE, 0x0084_ffff).unwrap(); // Bit 2 ignored
bus.write32(TIMER_BASE + 8, 0x0080_fffe).unwrap(); // Independent timer 2
bus.write32(TIMER_BASE + 12, 0x00c4_ffff).unwrap();
bus.advance_cycles(2);
assert_eq!(bus.read16(TIMER_BASE + 2).unwrap(), 0x80);
assert_eq!(bus.read16(TIMER_BASE).unwrap(), 0xffff);
assert_eq!(bus.read16(IF).unwrap(), 0x40);
}
#[test]
fn bulk_advance_handles_maximum_cycle_count_and_period_one() {
let mut bus = memory();
bus.write32(TIMER_BASE, 0x00c0_ffff).unwrap();
for index in 1..4 {
bus.write32(TIMER_BASE + index * 4, 0x00c4_ffff).unwrap();
}
bus.advance_cycles(u32::MAX);
assert_eq!(bus.cycles(), u64::from(u32::MAX));
for index in 0..4 {
assert_eq!(bus.read16(TIMER_BASE + index * 4).unwrap(), 0xffff);
}
assert_eq!(bus.read16(IF).unwrap(), 0x78);
bus.advance_cycles(u32::MAX);
assert_eq!(bus.cycles(), 2 * u64::from(u32::MAX));
}
#[test]
fn bulk_advance_matches_independent_cycle_by_cycle_reference() {
// A small literal reference: count prescaler ticks and propagate one pulse
// per cycle. Unlike the implementation, this does not use overflow division.
for selection in 0..4 {
for cascade_mask in 0..8 {
let mut bus = memory();
let reloads = [0xfffd_u16, 0xfffe, 0xffff, 0xfffc];
let mut counters = reloads;
let mut phases = [0_u32; 4];
let mut pending = 0_u16;
for (index, reload) in reloads.into_iter().enumerate() {
let cascade = index > 0 && cascade_mask & (1 << (index - 1)) != 0;
let control = 0xc0 | selection | if cascade { 4 } else { 0 };
bus.write32(
TIMER_BASE + index as u32 * 4,
(control << 16) | u32::from(reload),
)
.unwrap();
}
for chunk in [0, 1, 17, 63, 1000, 8193] {
bus.advance_cycles(chunk);
for _ in 0..chunk {
let mut overflow = false;
for index in 0..4 {
let cascade = index > 0 && cascade_mask & (1 << (index - 1)) != 0;
let tick = if cascade {
overflow
} else {
phases[index] += 1;
if phases[index] == [1, 64, 256, 1024][selection as usize] {
phases[index] = 0;
true
} else {
false
}
};
overflow = tick && counters[index] == u16::MAX;
if overflow {
counters[index] = reloads[index];
pending |= 1 << (index + 3);
} else if tick {
counters[index] += 1;
}
}
}
for (index, counter) in counters.into_iter().enumerate() {
assert_eq!(bus.read16(TIMER_BASE + index as u32 * 4).unwrap(), counter);
}
assert_eq!(bus.read16(IF).unwrap(), pending);
}
}
}
}
#[test]
fn interrupt_delivery_requires_both_ie_and_ime_without_clearing_if() {
let mut bus = memory();
bus.write32(TIMER_BASE, 0x00c0_ffff).unwrap();
bus.advance_cycles(1);
for (enable, master, expected) in [
(0, 0, false),
(8, 0, false),
(0, 1, false),
(16, 1, false),
(8, 1, true),
] {
bus.write16(IE, enable).unwrap();
bus.write32(IME, master).unwrap();
assert_eq!(bus.irq_pending(), expected);
assert_eq!(bus.read16(IF).unwrap(), 8);
}
}
#[test]
fn interrupt_acknowledgement_is_write_one_to_clear_for_all_widths() {
let mut bus = memory();
for index in 0..4 {
bus.write32(TIMER_BASE + index * 4, 0x00c0_ffff).unwrap();
}
bus.advance_cycles(1);
bus.write16(IF, 0).unwrap();
assert_eq!(bus.read16(IF).unwrap(), 0x78);
bus.write8(IF + 1, 0xff).unwrap(); // High byte must not clear low flags
assert_eq!(bus.read16(IF).unwrap(), 0x78);
bus.write8(IF, 8).unwrap();
assert_eq!(bus.read16(IF).unwrap(), 0x70);
bus.write16(IF, 0x20).unwrap();
assert_eq!(bus.read16(IF).unwrap(), 0x50);
bus.write32(IE, 0x0010_0078).unwrap(); // IE=0x78, clear only Timer 1
assert_eq!(bus.read32(IE).unwrap(), 0x0040_0078);
bus.write16(IF, 0xffff).unwrap();
assert_eq!(bus.read16(IF).unwrap(), 0);
bus.advance_cycles(1);
assert_eq!(bus.read16(IF).unwrap(), 0x78); // Later events relatch
}
#[test]
fn high_byte_writes_do_not_reset_control_or_modify_low_enable_bits() {
let mut bus = memory();
bus.write32(TIMER_BASE, 0x0080_1234).unwrap();
bus.write8(TIMER_BASE + 3, 0xff).unwrap();
bus.write8(IE, 8).unwrap();
bus.write8(IE + 1, 0xff).unwrap();
assert_eq!(bus.read16(IE).unwrap(), 0x3f08);
bus.write8(IME, 1).unwrap();
for offset in 1..4 {
bus.write8(IME + offset, 0xff).unwrap();
}
assert_eq!(bus.read32(IME).unwrap(), 1);
bus.advance_cycles(1);
assert_eq!(bus.read32(TIMER_BASE).unwrap(), 0x0080_1235);
}
#[test]
fn unknown_io_and_mirrors_stay_unmapped_and_unaligned_writes_have_no_effect() {
let mut bus = memory();
for address in [0x0400_0058, TIMER_BASE + 16, IE + 12, IME + 4, 0x0401_0100] {
assert_eq!(bus.read8(address), Err(MemoryError::Unmapped(address)));
assert_eq!(
bus.write32(address, u32::MAX),
Err(MemoryError::Unmapped(address))
);
}
assert_eq!(
bus.write32(TIMER_BASE + 2, u32::MAX),
Err(MemoryError::Unaligned(TIMER_BASE + 2))
);
assert_eq!(
bus.write16(IF + 1, u16::MAX),
Err(MemoryError::Unaligned(IF + 1))
);
assert_eq!(bus.read32(TIMER_BASE).unwrap(), 0);
assert_eq!(bus.read32(IE).unwrap(), 0);
assert_eq!(bus.cycles(), 0);
}