gameboy-rust/crates/gba-core/tests/serial_internal.rs

231 lines
9 KiB
Rust

//! Original nominal serial clocks and disconnected-input regressions.
use gba_core::{
cpu::Cpu,
input::Buttons,
io::{HALTCNT, IE, IF, IME, KEYCNT, RCNT, SIOCNT, SIODATA32, SIODATA8},
machine::{Machine, StepKind},
memory::{Memory, ROM_START},
};
fn memory() -> Memory {
Memory::new(0xeaff_fffe_u32.to_le_bytes().to_vec()).unwrap()
}
fn value(bus: &Memory, wide: bool) -> u32 {
if wide {
bus.read32(SIODATA32).unwrap()
} else {
u32::from(bus.read8(SIODATA8).unwrap())
}
}
#[test]
fn every_shift_edge_samples_high_and_completion_occurs_after_exactly_eight_or_thirty_two_bits() {
for wide in [false, true] {
for fast in [false, true] {
for irq in [false, true] {
for seed in [0, u32::MAX, 0x12345678, 0x80000001] {
let mut bus = memory();
bus.write32(SIODATA32, seed).unwrap();
bus.write8(SIODATA8, seed as u8).unwrap();
let control = 0x81
| if wide { 0x1000 } else { 0 }
| if fast { 2 } else { 0 }
| if irq { 0x4000 } else { 0 };
bus.write16(SIOCNT, control).unwrap();
let width = if wide { 32 } else { 8 };
let period = if fast { 8 } else { 64 };
let mut bits: Vec<bool> =
(0..width).rev().map(|bit| seed & (1 << bit) != 0).collect();
for index in 0..width {
let expected = bits
.iter()
.fold(0u32, |word, bit| (word << 1) | u32::from(*bit));
bus.advance_cycles(period - 1);
assert_eq!(value(&bus, wide), expected);
assert_ne!(bus.read16(SIOCNT).unwrap() & 0x80, 0);
assert_eq!(bus.read16(IF).unwrap() & 0x80, 0);
bus.advance_cycles(1);
bits.remove(0);
bits.push(true);
let expected = bits
.iter()
.fold(0u32, |word, bit| (word << 1) | u32::from(*bit));
assert_eq!(value(&bus, wide), expected);
assert_eq!(bus.read16(SIOCNT).unwrap() & 0x80 != 0, index + 1 < width);
}
assert_eq!(bus.read16(IF).unwrap() & 0x80, if irq { 0x80 } else { 0 });
assert_eq!(bus.read16(SIOCNT).unwrap(), (control & !0x80) | 4);
if wide {
assert_eq!(bus.read8(SIODATA8).unwrap(), seed as u8);
} else {
assert_eq!(bus.read32(SIODATA32).unwrap(), seed);
}
bus.write16(IF, 0x80).unwrap();
bus.advance_cycles(u32::MAX);
assert_eq!(bus.read16(IF).unwrap() & 0x80, 0);
}
}
}
}
}
#[test]
fn bulk_and_single_cycle_advancement_match_at_every_phase_and_large_batches() {
for control in [0x4081, 0x4083, 0x5081, 0x5083] {
for cycles in [0, 1, 7, 8, 63, 64, 65, 255, 256, 511, 512, 2047, 2048, 4096] {
let mut bulk = memory();
let mut single = memory();
for bus in [&mut bulk, &mut single] {
bus.write32(SIODATA32, 0x12345678).unwrap();
bus.write8(SIODATA8, 0x96).unwrap();
bus.write16(SIOCNT, control).unwrap();
}
bulk.advance_cycles(cycles);
for _ in 0..cycles {
single.advance_cycles(1);
}
for address in [SIOCNT, SIODATA8, IF] {
assert_eq!(bulk.read16(address), single.read16(address));
}
assert_eq!(bulk.read32(SIODATA32), single.read32(SIODATA32));
}
}
}
#[test]
fn external_to_internal_transition_starts_a_fresh_period_and_same_start_writes_preserve_phase() {
let mut bus = memory();
bus.write16(SIOCNT, 0x5080).unwrap();
bus.advance_cycles(1000000);
bus.write8(SIOCNT, 0x81).unwrap();
bus.advance_cycles(63);
assert_eq!(bus.read32(SIODATA32).unwrap(), 0);
bus.write16(SIOCNT, 0x5085).unwrap(); // Read/modify/write includes read-only SI.
bus.advance_cycles(1);
assert_eq!(bus.read32(SIODATA32).unwrap(), 1);
bus.advance_cycles(64 * 31 - 1);
assert_ne!(bus.read16(SIOCNT).unwrap() & 0x80, 0);
bus.advance_cycles(1);
assert_eq!(bus.read32(SIODATA32).unwrap(), u32::MAX);
assert_eq!(bus.read16(IF).unwrap() & 0x80, 0x80);
}
#[test]
fn cancellation_allows_reconfiguration_and_irq_enable_is_sampled_at_completion() {
let mut bus = memory();
bus.write16(SIOCNT, 0x81).unwrap();
bus.advance_cycles(65);
assert_eq!(bus.read8(SIODATA8).unwrap(), 1);
let before = bus.read16(SIOCNT).unwrap();
for (address, data) in [
(SIOCNT, 0x83),
(SIOCNT + 1, 0x10),
(SIODATA8, 0x42),
(RCNT + 1, 0x80),
] {
let error = bus.write8(address, data).unwrap_err();
assert!(error.to_string().contains("serial reconfiguration"));
assert_eq!(bus.read16(SIOCNT).unwrap(), before);
assert_eq!(bus.read8(SIODATA8).unwrap(), 1);
}
bus.write8(SIOCNT + 1, 0x40).unwrap(); // IRQ enable does not restart the current bit.
bus.advance_cycles(447);
assert_eq!(bus.read16(IF).unwrap() & 0x80, 0x80);
bus.write16(IF, 0x80).unwrap();
bus.write16(SIOCNT, 0x4083).unwrap();
bus.advance_cycles(8);
bus.write32(SIOCNT, 0x00aa0001).unwrap(); // Cancel and replace data atomically.
bus.advance_cycles(1000000);
assert_eq!(bus.read8(SIODATA8).unwrap(), 0xaa);
assert_eq!(bus.read16(IF).unwrap() & 0x80, 0);
bus.write16(SIOCNT, 0x4083).unwrap();
bus.advance_cycles(63);
bus.write8(SIOCNT + 1, 0).unwrap(); // Disable IRQ one cycle before completion.
bus.advance_cycles(1);
assert_eq!(bus.read8(SIODATA8).unwrap(), 0xff);
assert_eq!(bus.read16(IF).unwrap() & 0x80, 0);
}
#[test]
fn gpio_gates_the_clock_and_leaving_normal_mode_requires_cancellation() {
let mut bus = memory();
bus.write16(RCNT, 0x8000).unwrap();
bus.write16(SIOCNT, 0x4083).unwrap();
bus.advance_cycles(1000000);
assert_eq!(bus.read8(SIODATA8).unwrap(), 0);
assert_eq!(bus.read16(IF).unwrap(), 0);
bus.write16(RCNT, 0).unwrap();
assert!(bus.write16(RCNT, 0x8000).is_err());
bus.advance_cycles(7);
assert_eq!(bus.read8(SIODATA8).unwrap(), 0);
bus.advance_cycles(1);
assert_eq!(bus.read8(SIODATA8).unwrap(), 1);
bus.write16(SIOCNT, 0).unwrap();
bus.write16(RCNT, 0x8000).unwrap();
bus.advance_cycles(1000000);
assert_eq!(bus.read8(SIODATA8).unwrap(), 1);
assert_eq!(bus.read16(IF).unwrap(), 0);
}
#[test]
fn halt_stops_at_serial_completion_and_delivery_obeys_ime() {
for ime in [0, 1] {
let mut bus = memory();
bus.write16(IE, 0x80).unwrap();
bus.write16(IME, ime).unwrap();
bus.write16(SIOCNT, 0x4083).unwrap();
bus.write8(HALTCNT, 0).unwrap();
let mut machine = Machine::new(Cpu::new(ROM_START), bus);
assert_eq!(machine.step().unwrap(), StepKind::HaltIdle);
assert_eq!(machine.cycles(), 64);
assert!(!machine.halted());
assert_eq!(machine.memory().read16(IF).unwrap(), 0x80);
assert_eq!(
machine.step().unwrap(),
if ime == 0 {
StepKind::Instruction
} else {
StepKind::IrqEntry
}
);
}
}
#[test]
fn serial_and_timer_requests_latch_and_acknowledge_independently() {
let mut bus = memory();
bus.write32(gba_core::io::TIMER_BASE, 0x00c0ffc0).unwrap();
bus.write16(SIOCNT, 0x4083).unwrap();
bus.advance_cycles(64);
assert_eq!(bus.read16(IF).unwrap(), 0x88);
bus.write16(IF, 0x80).unwrap();
assert_eq!(bus.read16(IF).unwrap(), 8);
bus.write16(IF, 8).unwrap();
assert_eq!(bus.read16(IF).unwrap(), 0);
bus.write16(gba_core::io::TIMER_BASE + 2, 0).unwrap();
bus.write16(SIOCNT, 0x5081).unwrap();
bus.advance_cycles(u32::MAX);
assert_eq!(bus.read32(SIODATA32).unwrap(), u32::MAX);
assert_eq!(bus.read16(IF).unwrap(), 0x80);
}
#[test]
fn stop_freezes_a_partial_bit_until_keypad_wake() {
let mut bus = memory();
bus.write16(IE, 0x1080).unwrap();
bus.write16(KEYCNT, 0x4001).unwrap();
bus.write16(SIOCNT, 0x4083).unwrap();
bus.advance_cycles(63);
bus.write8(HALTCNT, 0x80).unwrap();
let mut machine = Machine::new(Cpu::new(ROM_START), bus);
assert_eq!(machine.step().unwrap(), StepKind::StopIdle);
machine.memory_mut().advance_cycles(u32::MAX);
assert_eq!(machine.cycles(), 63);
assert_eq!(machine.memory().read8(SIODATA8).unwrap(), 0x7f);
assert_eq!(machine.memory().read16(IF).unwrap() & 0x80, 0);
machine.memory_mut().set_buttons(Buttons::from_bits(1));
assert!(!machine.stopped());
machine.memory_mut().advance_cycles(1);
assert_eq!(machine.memory().read8(SIODATA8).unwrap(), 0xff);
assert_eq!(machine.memory().read16(IF).unwrap() & 0x80, 0x80);
}