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
|
|
@ -80,7 +80,6 @@ fn idle_normal_serial_data_and_control_have_distinct_widths() {
|
|||
fn unsupported_operations_have_specific_retryable_diagnostics() {
|
||||
let mut bus = memory();
|
||||
for (address, value, description) in [
|
||||
(SIOCNT, 0x81, "internally clocked serial transfer"),
|
||||
(SIOCNT + 1, 0x20, "multiplayer/UART"),
|
||||
(RCNT + 1, 0xc0, "Joybus serial mode"),
|
||||
(RCNT + 1, 0x81, "GPIO serial interrupt"),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use gba_core::{
|
|||
dma::DMA_BASE,
|
||||
io::{HALTCNT, IE, IF, IME, RCNT, SIOCNT, SIODATA32, SIODATA8},
|
||||
machine::{Machine, StepKind},
|
||||
memory::{Memory, MemoryError, ROM_START},
|
||||
memory::{Memory, ROM_START},
|
||||
};
|
||||
|
||||
fn memory() -> Memory {
|
||||
|
|
@ -12,36 +12,22 @@ fn memory() -> Memory {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn every_low_control_byte_retains_external_busy_but_rejects_internal_start_atomically() {
|
||||
fn every_low_control_byte_retains_normal_control_bits_without_immediate_completion() {
|
||||
let mut bus = memory();
|
||||
for high in [0, 0x10, 0x40, 0x50] {
|
||||
for previous in [0x0003, 0x1000, 0x4080, 0x5082] {
|
||||
for low in 0..=u8::MAX {
|
||||
bus.write16(SIOCNT, previous).unwrap();
|
||||
let before = bus.read16(SIOCNT).unwrap();
|
||||
let result = bus.write8(SIOCNT, low);
|
||||
if low & 0x80 != 0 && low & 1 != 0 {
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(MemoryError::UnsupportedIo {
|
||||
address: SIOCNT,
|
||||
value: low,
|
||||
operation: "internally clocked serial transfer",
|
||||
})
|
||||
);
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), before);
|
||||
} else {
|
||||
result.unwrap();
|
||||
assert_eq!(
|
||||
bus.read16(SIOCNT).unwrap(),
|
||||
(previous & 0x5000) | u16::from(low & 0x8b) | 4
|
||||
);
|
||||
bus.write8(SIOCNT + 1, high).unwrap();
|
||||
assert_eq!(
|
||||
bus.read16(SIOCNT).unwrap(),
|
||||
(u16::from(high) << 8) | u16::from(low & 0x8b) | 4
|
||||
);
|
||||
}
|
||||
bus.write8(SIOCNT, low).unwrap();
|
||||
assert_eq!(
|
||||
bus.read16(SIOCNT).unwrap(),
|
||||
(previous & 0x5000) | u16::from(low & 0x8b) | 4
|
||||
);
|
||||
bus.write8(SIOCNT + 1, high).unwrap();
|
||||
assert_eq!(
|
||||
bus.read16(SIOCNT).unwrap(),
|
||||
(u16::from(high) << 8) | u16::from(low & 0x8b) | 4
|
||||
);
|
||||
assert_eq!(bus.read16(IF).unwrap() & 0x80, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -85,9 +71,9 @@ fn gpio_selection_does_not_turn_a_retained_start_bit_into_a_transfer_or_irq() {
|
|||
bus.advance_cycles(1000000);
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x5084);
|
||||
assert_eq!(bus.read16(IF).unwrap() & 0x80, 0);
|
||||
// Clock-source changes with start still set remain explicit diagnostics.
|
||||
assert!(bus.write8(SIOCNT, 0x81).is_err());
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x5084);
|
||||
// Selecting internal clock starts the pending request, but not instant completion.
|
||||
bus.write8(SIOCNT, 0x81).unwrap();
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x5085);
|
||||
bus.write16(SIOCNT, 0x5001).unwrap(); // Cancel first, then select internal clock while idle.
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x5005);
|
||||
}
|
||||
|
|
@ -146,7 +132,7 @@ fn prepared(thumb: bool, instruction: u32, value: u32) -> Machine {
|
|||
#[test]
|
||||
fn arm_and_thumb_external_starts_complete_as_instructions_not_as_serial_transfers() {
|
||||
for thumb in [false, true] {
|
||||
for value in [0x5084, 0x5081] {
|
||||
for value in [0x5084, 0x7080] {
|
||||
let mut machine = prepared(thumb, 0xe1c010b0, value); // STRH r1,[r0]
|
||||
let cpu = machine.cpu().clone();
|
||||
if value == 0x5084 {
|
||||
|
|
@ -160,9 +146,7 @@ fn arm_and_thumb_external_starts_complete_as_instructions_not_as_serial_transfer
|
|||
assert_eq!(machine.memory().read16(IF).unwrap() & 0x80, 0);
|
||||
} else {
|
||||
let error = machine.step().unwrap_err();
|
||||
assert!(error
|
||||
.to_string()
|
||||
.contains("internally clocked serial transfer"));
|
||||
assert!(error.to_string().contains("multiplayer/UART serial mode"));
|
||||
assert_eq!(machine.cpu(), &cpu);
|
||||
assert_eq!(machine.cycles(), 0);
|
||||
assert_eq!(machine.memory().read16(SIOCNT).unwrap(), 0x1007);
|
||||
|
|
@ -198,15 +182,13 @@ fn invalid_word_and_block_writes_do_not_leave_a_partial_external_start() {
|
|||
#[test]
|
||||
fn dma_start_preserves_serial_wait_and_reports_only_dma_completion() {
|
||||
let mut bus = memory();
|
||||
bus.write32(0x02000000, 0x00ab5081).unwrap();
|
||||
bus.write32(0x02000000, 0x00ab7080).unwrap();
|
||||
bus.write32(DMA_BASE, 0x02000000).unwrap();
|
||||
bus.write32(DMA_BASE + 4, SIOCNT).unwrap();
|
||||
bus.write32(DMA_BASE + 8, 0xc4000001).unwrap();
|
||||
let mut machine = Machine::new(Cpu::new(ROM_START), bus);
|
||||
let error = machine.step().unwrap_err();
|
||||
assert!(error
|
||||
.to_string()
|
||||
.contains("internally clocked serial transfer"));
|
||||
assert!(error.to_string().contains("multiplayer/UART serial mode"));
|
||||
assert_eq!(machine.step(), Err(error));
|
||||
assert_eq!(machine.cycles(), 0);
|
||||
assert_eq!(machine.memory().read16(SIOCNT).unwrap(), 4);
|
||||
|
|
|
|||
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