feat(serial): clock disconnected normal transfers and completion IRQs
This commit is contained in:
parent
d329c76dc3
commit
0b36ef1e24
17 changed files with 860 additions and 196 deletions
231
crates/gba-core/tests/serial_internal.rs
Normal file
231
crates/gba-core/tests/serial_internal.rs
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
//! 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue