feat(input): implement KEYCNT keypad interrupt control

Add a dependency-free keypad state machine in gba-core, map KEYCNT, latch IF bit 12, and wake HALT through the existing interrupt gates. Sample complete CPU/DMA control writes once so transient byte values cannot create false requests.

Document the mGBA-style polling policy and its unverified hardware retrigger limits. Cover every button-state/selection pair, register masks, repeated requests, byte/word writes, failed-store isolation, DMA priority, ARM/Thumb IRQ return, BIOS IntrWait, and retained BIOS reset state.

Follow the restructured workspace and modular documentation. Validated on Darwin arm64: workspace and core/demo tests pass in debug and release, with Clippy, rustdoc, ten native window smoke tests, and terminal demos passing.
This commit is contained in:
0xrsydn 2026-10-05 18:00:46 +07:00
commit 597b02881b
14 changed files with 710 additions and 21 deletions

View file

@ -1,6 +1,9 @@
//! Platform-independent GBA button state. Bits are pressed-high here;
//! the KEYINPUT register exposes their active-low hardware representation.
mod keypad;
pub(crate) use keypad::Keypad;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum Button {

View file

@ -0,0 +1,88 @@
//! Functional keypad IRQ sampling from input snapshots and control writes.
//! OR requests on every matching sample. AND suppresses identical matching
//! snapshots, following mGBA's polling model. Exact hardware edges are unverified.
use super::Buttons;
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub(crate) struct Keypad {
buttons: Buttons,
control: u16,
last_match: Option<u16>,
}
impl Keypad {
pub(crate) fn keyinput(&self) -> u16 {
self.buttons.keyinput()
}
pub(crate) fn control(&self) -> u16 {
self.control
}
pub(crate) fn set_buttons(&mut self, buttons: Buttons) -> bool {
self.buttons = buttons;
self.sample()
}
pub(crate) fn write_control(&mut self, value: u16) -> bool {
let value = value & 0xc3ff;
let newly_selected = (value & !self.control) & 0x03ff;
// A newly selected held key must not be hidden by an earlier AND sample.
if let Some(previous) = &mut self.last_match {
*previous &= !newly_selected;
}
self.control = value;
self.sample()
}
fn sample(&mut self) -> bool {
if self.control & 0x4000 == 0 {
return false;
}
let selected = self.control & 0x03ff;
let pressed = self.buttons.bits();
let all = self.control & 0x8000 != 0;
let matched = if all {
pressed & selected == selected
} else {
pressed & selected != 0
};
if !matched {
self.last_match = None;
return false;
}
let previous = self.last_match.replace(pressed);
!all || previous != Some(pressed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_masks_and_button_states_match_independent_per_button_logic() {
for selected in 0..1024 {
for pressed in 0..1024 {
let mut any = false;
let mut all = true;
for bit in 0..10 {
if selected & (1 << bit) != 0 {
any |= pressed & (1 << bit) != 0;
all &= pressed & (1 << bit) != 0;
}
}
for (condition, expected) in [(0, any), (0x8000, all)] {
let mut keypad = Keypad::default();
assert!(!keypad.set_buttons(Buttons::from_bits(pressed)));
assert_eq!(
keypad.write_control(selected | condition | 0x4000),
expected
);
assert_eq!(keypad.keyinput(), !pressed & 0x03ff);
}
}
}
}
}

View file

@ -1,11 +1,11 @@
//! GBA I/O subset: DMA, timers, interrupts, WAITCNT, display, and polled input.
//! 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.
use crate::{
display::{Display, DisplayPosition, CYCLES_PER_LINE, HBLANK_START, VISIBLE_LINES},
dma::{Dma, DmaError, Transfer, DMA_BASE, DMA_END},
input::Buttons,
input::{Buttons, Keypad},
};
pub const DISPCNT: u32 = 0x0400_0000;
@ -47,6 +47,7 @@ pub const BLDCNT: u32 = 0x0400_0050;
pub const BLDALPHA: u32 = 0x0400_0052;
pub const BLDY: u32 = 0x0400_0054;
pub const KEYINPUT: u32 = 0x0400_0130;
pub const KEYCNT: u32 = 0x0400_0132;
pub const TIMER_BASE: u32 = 0x0400_0100;
pub const IE: u32 = 0x0400_0200;
pub const IF: u32 = 0x0400_0202;
@ -137,7 +138,7 @@ pub(crate) struct Io {
effects: crate::video::effects::Effects,
window_edges: crate::video::windows::WindowEdges,
horizontal_windows: crate::video::windows::horizontal::HorizontalWindows,
buttons: Buttons,
keypad: Keypad,
display: Display,
dma: Dma,
postflg: bool,
@ -146,7 +147,7 @@ pub(crate) struct Io {
impl Io {
pub(crate) fn mapped(address: u32) -> bool {
matches!(address, 0x0400_0000..=0x0400_0057 | DMA_BASE..=DMA_END | 0x0400_0100..=0x0400_010f | 0x0400_0130..=0x0400_0131 | 0x0400_0200..=0x0400_020b | POSTFLG..=0x0400_0303)
matches!(address, 0x0400_0000..=0x0400_0057 | DMA_BASE..=DMA_END | 0x0400_0100..=0x0400_010f | 0x0400_0130..=0x0400_0133 | 0x0400_0200..=0x0400_020b | POSTFLG..=0x0400_0303)
}
pub(crate) fn read8(&self, address: u32) -> Option<u8> {
@ -165,7 +166,8 @@ impl Io {
WINOUT => self.effects.outside,
BLDCNT => self.effects.control,
BLDALPHA => self.effects.alpha,
KEYINPUT => self.buttons.keyinput(),
KEYINPUT => self.keypad.keyinput(),
KEYCNT => self.keypad.control(),
0x0400_0100..=0x0400_010e => {
let timer = &self.timers[((address - TIMER_BASE) / 4) as usize];
if address & 2 == 0 {
@ -188,8 +190,10 @@ impl Io {
}
/// The memory bus validates every byte before calling this method.
/// Keypad accesses use write_keypad instead, preserving transfer boundaries.
pub(crate) fn write8(&mut self, address: u32, value: u8) {
debug_assert!(Self::mapped(address));
debug_assert!(!(KEYINPUT..=KEYCNT + 1).contains(&address));
if (DMA_BASE..=DMA_END).contains(&address) {
self.dma.write8(address, value);
return;
@ -198,7 +202,7 @@ impl Io {
DISPCNT => self.dispcnt = replace_byte(self.dispcnt, address, value) & 0xfff7,
GREENSWAP if address & 1 == 0 => self.greenswap = value & 1 != 0,
DISPSTAT => self.pending |= self.display.write_status(address & 1 != 0, value),
VCOUNT | KEYINPUT => {} // Read-only; writes are ignored.
VCOUNT => {} // Read-only; writes are ignored.
BG0CNT..=BG3CNT => {
let index = ((address - BG0CNT) / 2) as usize;
let control = &mut self.backgrounds[index].control;
@ -294,7 +298,30 @@ impl Io {
}
pub(crate) fn set_buttons(&mut self, buttons: Buttons) {
self.buttons = buttons;
if self.keypad.set_buttons(buttons) {
self.pending |= 1 << 12;
}
self.wake_if_requested();
}
/// Commit one validated byte/halfword/word access in the keypad block.
/// KEYINPUT writes are ignored. Sample once after both KEYCNT bytes merge,
/// never on a transient low-byte value during a halfword or word write.
pub(crate) fn write_keypad(&mut self, address: u32, bytes: &[u8]) {
debug_assert!(address >= KEYINPUT && address + bytes.len() as u32 <= KEYCNT + 2);
let mut control = self.keypad.control();
let mut touched = false;
for (offset, byte) in bytes.iter().enumerate() {
let address = address + offset as u32;
if (KEYCNT..=KEYCNT + 1).contains(&address) {
control = replace_byte(control, address, *byte);
touched = true;
}
}
if touched && self.keypad.write_control(control) {
self.pending |= 1 << 12;
}
self.wake_if_requested();
}
pub(crate) fn display_control(&self) -> u16 {

View file

@ -4,7 +4,7 @@ use crate::{
display::{DisplayPosition, CYCLES_PER_LINE, HBLANK_START, VISIBLE_LINES},
dma::{DmaError, DMA_BASE, DMA_END},
input::Buttons,
io::{Io, HALTCNT, POSTFLG},
io::{Io, HALTCNT, KEYINPUT, POSTFLG},
timing::{bus_cycles, AccessKind, AccessWidth, DataTiming, StepTiming},
video::{self, sprites::pipeline, Framebuffer, VideoError},
};
@ -325,7 +325,8 @@ impl Memory {
}
/// Replace the current pressed-button state. This does not advance time.
/// KEYCNT and keypad interrupts are not implemented yet.
/// Sample KEYCNT and latch a matching keypad IRQ immediately, without cycles.
/// This can wake HALT; CPU IRQ delivery occurs on a later Machine::step.
pub fn set_buttons(&mut self, buttons: Buttons) {
self.io.set_buttons(buttons);
}
@ -424,6 +425,10 @@ impl Memory {
},
);
if address >> 24 == 0x04 {
if address & !3 == KEYINPUT {
self.io.write_keypad(address, &bytes);
return Ok(());
}
for (offset, byte) in bytes.into_iter().enumerate() {
let byte_address = address + offset as u32;
if matches!(byte_address, POSTFLG | HALTCNT) && !self.can_write_power_control() {

View file

@ -4,7 +4,7 @@ use gba_core::{
dma::{DmaError, DMA_BASE, DMA_STRIDE},
input::Buttons,
io::{
BG0CNT, BG2PA, BG2PD, BG3PA, BG3PD, BLDALPHA, BLDCNT, DISPCNT, GREENSWAP, KEYINPUT,
BG0CNT, BG2PA, BG2PD, BG3PA, BG3PD, BLDALPHA, BLDCNT, DISPCNT, GREENSWAP, KEYCNT, KEYINPUT,
WAITCNT, WININ, WINOUT,
},
memory::{OAM_START, PALETTE_START},
@ -236,6 +236,7 @@ fn io_reset_clears_readable_controls_but_keeps_input_postflg_and_green_swap() {
let (mut m, pc) = prepare(flags, false, 0x9f);
m.memory_mut().set_buttons(Buttons::from_bits(0x251));
let keys = m.memory().read16(KEYINPUT).unwrap();
m.memory_mut().write16(KEYCNT, 0x4001).unwrap();
m.memory_mut().write16(GREENSWAP, 1).unwrap();
for address in [
BG0CNT,
@ -277,6 +278,7 @@ fn io_reset_clears_readable_controls_but_keeps_input_postflg_and_green_swap() {
}
assert_eq!(m.memory().read16(DISPSTAT).unwrap() & 0xff38, 0);
assert_eq!(m.memory().read16(KEYINPUT).unwrap(), keys);
assert_eq!(m.memory().read16(KEYCNT).unwrap(), 0x4001); // Current reset subset preserves KEYCNT.
assert_eq!(m.memory().read16(POSTFLG).unwrap(), 1);
assert_eq!(m.memory().read16(GREENSWAP).unwrap(), 1);
assert!(m.cycles() > cycles);

View file

@ -279,10 +279,7 @@ fn keyinput_exposes_all_ten_buttons_active_low_and_ignores_writes() {
memory.set_buttons(Buttons::default());
assert_eq!(memory.read16(KEYINPUT).unwrap(), 0x3ff);
assert_eq!(memory.cycles(), 0);
assert_eq!(
memory.read16(KEYINPUT + 2),
Err(MemoryError::Unmapped(KEYINPUT + 2))
); // KEYCNT deferred.
assert_eq!(memory.read16(KEYINPUT + 2).unwrap(), 0); // KEYCNT resets disabled.
}
#[test]

View file

@ -0,0 +1,498 @@
use gba_core::{
bios,
cpu::{Cpu, CpuError, InstructionSet, Mode},
dma::DMA_BASE,
input::Buttons,
io::{HALTCNT, IE, IF, IME, KEYCNT, KEYINPUT, TIMER_BASE},
machine::{Machine, MachineError, StepKind},
memory::{Memory, MemoryError, BIOS_SIZE, ROM_START},
};
const IRQ: u16 = 1 << 12;
fn words(code: &[u32]) -> Vec<u8> {
code.iter().flat_map(|v| v.to_le_bytes()).collect()
}
fn bus() -> Memory {
Memory::new(vec![]).unwrap()
}
fn press(m: &mut Memory, buttons: u16) {
m.set_buttons(Buttons::from_bits(buttons));
}
fn pending(m: &Memory) -> bool {
m.read16(IF).unwrap() & IRQ != 0
}
fn ack(m: &mut Memory) {
m.write16(IF, IRQ).unwrap();
assert!(!pending(m));
}
#[test]
fn register_reset_masks_byte_merges_and_combined_word_access() {
let mut m = bus();
assert_eq!(m.read32(KEYINPUT).unwrap(), 0x03ff);
for value in 0..=u16::MAX {
m.write16(KEYCNT, value).unwrap();
assert_eq!(m.read16(KEYCNT).unwrap(), value & 0xc3ff);
}
m.write16(KEYCNT, 0).unwrap();
m.write8(KEYCNT + 1, 0xff).unwrap();
assert_eq!(m.read16(KEYCNT).unwrap(), 0xc300);
m.write8(KEYCNT, 0xa5).unwrap();
assert_eq!(m.read16(KEYCNT).unwrap(), 0xc3a5);
assert_eq!(m.read8(KEYCNT).unwrap(), 0xa5);
assert_eq!(m.read8(KEYCNT + 1).unwrap(), 0xc3);
press(&mut m, 0x201);
m.write32(KEYINPUT, 0x0123_ffff).unwrap();
assert_eq!(m.read32(KEYINPUT).unwrap(), 0x0123_01fe);
m.write16(KEYINPUT, 0).unwrap();
assert_eq!(m.read32(KEYINPUT).unwrap(), 0x0123_01fe);
assert_eq!(m.cycles(), 0);
assert_eq!(m.read32(KEYCNT), Err(MemoryError::Unaligned(KEYCNT)));
for address in [KEYCNT + 2, KEYCNT + 0x400] {
assert_eq!(m.read8(address), Err(MemoryError::Unmapped(address)));
assert_eq!(m.write8(address, 0), Err(MemoryError::Unmapped(address)));
}
}
#[test]
fn every_button_can_request_irq_independently_of_ie_and_ime() {
for bit in 0..10 {
for mode in [0, 0x8000] {
let mut m = bus();
m.write16(KEYCNT, 0x4000 | mode | (1 << bit)).unwrap();
for other in 0..10 {
press(&mut m, if other == bit { 0 } else { 1 << other });
assert!(!pending(&m));
}
press(&mut m, 1 << bit);
assert!(pending(&m));
assert!(!m.irq_pending());
m.write16(IE, IRQ).unwrap();
assert!(!m.irq_pending());
m.write16(IME, 1).unwrap();
assert!(m.irq_pending());
}
}
}
#[test]
fn or_mode_requests_on_each_matching_input_sample_but_not_on_clock_updates() {
let mut m = bus();
m.write16(KEYCNT, 0x4003).unwrap();
for buttons in [1, 1, 3, 2, 2] {
press(&mut m, buttons);
assert!(pending(&m));
ack(&mut m);
m.advance_cycles(12345);
assert!(!pending(&m));
assert_eq!(m.read16(KEYCNT).unwrap(), 0x4003);
}
press(&mut m, 4);
assert!(!pending(&m));
press(&mut m, 0);
assert!(!pending(&m));
}
#[test]
fn and_mode_requires_selected_keys_but_allows_extra_keys_and_suppresses_identical_samples() {
let mut m = bus();
m.write16(KEYCNT, 0xc003).unwrap();
for buttons in [0, 1, 2, 4] {
press(&mut m, buttons);
assert!(!pending(&m));
}
for buttons in [3, 7, 3] {
press(&mut m, buttons);
assert!(pending(&m)); // Includes changes to unselected keys in the polling model.
ack(&mut m);
press(&mut m, buttons);
assert!(!pending(&m));
m.write16(KEYCNT, 0xc003).unwrap();
assert!(!pending(&m));
}
press(&mut m, 1);
assert!(!pending(&m));
press(&mut m, 3);
assert!(pending(&m));
}
#[test]
fn empty_masks_follow_boolean_or_and_rules() {
for buttons in [0, 1, 0x3ff] {
let mut m = bus();
press(&mut m, buttons);
m.write16(KEYCNT, 0x4000).unwrap();
assert!(!pending(&m));
m.write16(KEYCNT, 0xc000).unwrap();
assert!(pending(&m));
ack(&mut m);
press(&mut m, buttons);
assert!(!pending(&m));
}
}
#[test]
fn held_keys_are_sampled_on_control_writes_and_new_selection_rearms_and() {
let mut m = bus();
press(&mut m, 3);
m.write16(KEYCNT, 0x0001).unwrap();
assert!(!pending(&m));
m.write16(KEYCNT, 0xc001).unwrap();
assert!(pending(&m));
ack(&mut m);
m.write16(KEYCNT, 0xc003).unwrap();
assert!(pending(&m)); // B was newly selected while already held.
ack(&mut m);
m.write16(KEYCNT, 0xc003).unwrap();
assert!(!pending(&m));
m.write16(KEYCNT, 0xc007).unwrap();
assert!(!pending(&m)); // Newly selected, but not all selected keys held.
m.write16(KEYCNT, 0xc003).unwrap();
assert!(pending(&m));
}
#[test]
fn disabling_or_releasing_keys_does_not_acknowledge_if_and_disabled_sampling_keeps_history() {
let mut m = bus();
press(&mut m, 1);
m.write16(KEYCNT, 0xc001).unwrap();
assert!(pending(&m));
m.write16(KEYCNT, 0x8001).unwrap();
press(&mut m, 0);
assert!(pending(&m));
ack(&mut m);
press(&mut m, 1);
m.write16(KEYCNT, 0xc001).unwrap();
assert!(!pending(&m)); // No matching-state history update while disabled.
press(&mut m, 0);
press(&mut m, 1);
assert!(pending(&m));
}
#[test]
fn halfword_and_word_control_writes_never_sample_intermediate_byte_values() {
for word in [false, true] {
for (old, new, buttons) in [(0x4002, 0x0001, 1), (0xc001, 0x4000, 0)] {
let mut m = bus();
press(&mut m, buttons);
m.write16(KEYCNT, old).unwrap();
assert!(!pending(&m));
if word {
m.write32(KEYINPUT, u32::from(new) << 16).unwrap();
} else {
m.write16(KEYCNT, new).unwrap();
}
assert!(!pending(&m));
assert_eq!(m.read16(KEYCNT).unwrap(), new);
}
}
// Separate byte stores really do expose the intermediate enabled mask.
let mut m = bus();
press(&mut m, 1);
m.write16(KEYCNT, 0x4002).unwrap();
m.write8(KEYCNT, 1).unwrap();
assert!(pending(&m));
m.write8(KEYCNT + 1, 0).unwrap();
assert!(pending(&m));
}
#[test]
fn keyinput_writes_and_reads_do_not_resample_an_acknowledged_request() {
let mut m = bus();
m.write16(KEYCNT, 0x4001).unwrap();
press(&mut m, 1);
ack(&mut m);
m.write8(KEYINPUT, 0).unwrap();
m.write8(KEYINPUT + 1, 0).unwrap();
m.write16(KEYINPUT, 0).unwrap();
assert_eq!(m.read16(KEYINPUT).unwrap(), 0x3fe);
assert_eq!(m.read32(KEYINPUT).unwrap(), 0x4001_03fe);
assert!(!pending(&m));
// An actual KEYCNT write is a new sample, even if its value is unchanged.
m.write16(KEYCNT, 0x4001).unwrap();
assert!(pending(&m));
}
fn machine(masked: bool) -> Machine {
let mut m = Machine::new(
Cpu::new(ROM_START),
Memory::new(words(&[
if masked { 0xe321_f09f } else { 0xe321_f01f },
0xe280_0001,
0xeaff_fffe,
]))
.unwrap(),
);
m.step().unwrap();
m
}
#[test]
fn keypad_wakes_halt_only_through_ie_and_ignores_ime_and_cpu_masks_for_wake() {
for masked in [false, true] {
for ie in [0, IRQ] {
for ime in [0, 1] {
let mut m = machine(masked);
m.memory_mut().write16(KEYCNT, 0x4001).unwrap();
m.memory_mut().write16(IE, ie).unwrap();
m.memory_mut().write16(IME, ime).unwrap();
m.memory_mut().write8(HALTCNT, 0).unwrap();
let before = m.cpu().clone();
let cycles = m.cycles();
let timing = m.last_timing();
press(m.memory_mut(), 1);
assert!(pending(m.memory()));
assert_eq!(m.cpu(), &before);
assert_eq!(m.cycles(), cycles);
assert_eq!(m.last_timing(), timing);
assert_eq!(m.halted(), ie == 0);
let expected = if ie == 0 {
StepKind::HaltIdle
} else if ime != 0 && !masked {
StepKind::IrqEntry
} else {
StepKind::Instruction
};
assert_eq!(m.step().unwrap(), expected);
}
}
}
}
#[test]
fn pending_keypad_request_wakes_on_ie_enable_and_ack_does_not_restore_halt() {
let mut m = machine(false);
m.memory_mut().write16(KEYCNT, 0x4001).unwrap();
m.memory_mut().write8(HALTCNT, 0).unwrap();
press(m.memory_mut(), 1);
assert!(m.halted());
m.memory_mut().write16(IE, IRQ).unwrap();
assert!(!m.halted());
ack(m.memory_mut());
assert!(!m.halted());
assert_eq!(m.step().unwrap(), StepKind::Instruction);
}
#[test]
fn control_write_can_wake_halt_with_an_already_held_key() {
let mut m = machine(false);
press(m.memory_mut(), 1);
m.memory_mut().write16(IE, IRQ).unwrap();
m.memory_mut().write8(HALTCNT, 0).unwrap();
m.memory_mut().write16(KEYCNT, 0x4001).unwrap();
assert!(!m.halted());
assert_eq!(m.step().unwrap(), StepKind::Instruction); // IME remains zero.
}
#[test]
fn keypad_irq_entry_and_return_restore_arm_and_thumb_instruction_state() {
for thumb in [false, true] {
let rom = if thumb {
let mut code = words(&[0xe28f_0001, 0xe12f_ff10]);
code.extend(0x2107_u16.to_le_bytes());
code
} else {
words(&[0xe3a0_1007])
};
let bios = 0xe25e_f004_u32.to_le_bytes().repeat(BIOS_SIZE / 4);
let mut mem = Memory::with_bios(rom, bios).unwrap();
let mut cpu = Cpu::new(ROM_START);
if thumb {
cpu.step(&mut mem).unwrap();
cpu.step(&mut mem).unwrap();
}
let pc = cpu.pc();
let status = cpu.cpsr();
mem.write16(KEYCNT, 0x4001).unwrap();
mem.write16(IE, IRQ).unwrap();
mem.write16(IME, 1).unwrap();
press(&mut mem, 1);
let mut m = Machine::new(cpu, mem);
assert_eq!(m.step().unwrap(), StepKind::IrqEntry);
assert_eq!(m.cpu().mode(), Mode::Irq);
assert_eq!(m.cpu().spsr(), Some(status));
assert_eq!(m.cpu().registers()[14], pc + 4);
assert!(pending(m.memory()));
ack(m.memory_mut());
m.step().unwrap();
assert_eq!(m.cpu().pc(), pc);
assert_eq!(m.cpu().cpsr(), status);
assert_eq!(
m.cpu().instruction_set(),
if thumb {
InstructionSet::Thumb
} else {
InstructionSet::Arm
}
);
m.step().unwrap();
assert_eq!(m.cpu().registers()[1], 7);
}
}
fn prepared(instruction: u32, destination: u32, value: u32) -> Machine {
let mut mem = Memory::new(words(&[
0xe59f_0008,
0xe59f_1008,
instruction,
0xeaff_fffe,
destination,
value,
]))
.unwrap();
let mut cpu = Cpu::new(ROM_START);
cpu.step(&mut mem).unwrap();
cpu.step(&mut mem).unwrap();
Machine::new(cpu, mem)
}
#[test]
fn cpu_store_samples_keys_then_irq_enters_on_the_following_step() {
for (instruction, address, value) in [
(0xe1c0_10b0, KEYCNT, 0x4001),
(0xe580_1000, KEYINPUT, 0x4001_ffff),
] {
let mut m = prepared(instruction, address, value);
press(m.memory_mut(), 1);
m.memory_mut().write16(IE, IRQ).unwrap();
m.memory_mut().write16(IME, 1).unwrap();
assert_eq!(m.step().unwrap(), StepKind::Instruction);
assert_eq!(m.cpu().pc(), ROM_START + 12);
assert_eq!(m.memory().read16(KEYCNT).unwrap(), 0x4001);
assert_eq!(m.memory().read16(KEYINPUT).unwrap(), 0x3fe);
assert_eq!(m.step().unwrap(), StepKind::IrqEntry);
}
}
#[test]
fn failed_block_store_does_not_commit_key_control_irq_history_or_halt_wake() {
// STMIA r0!,{r1,r2}: keypad word is valid, next word (serial RCNT) is not mapped.
let mut m = prepared(0xe8a0_0006, KEYINPUT, 0x4001_0000);
press(m.memory_mut(), 1);
m.memory_mut().write16(IE, IRQ).unwrap();
let before = m.cpu().clone();
let cycles = m.cycles();
let timing = m.last_timing();
assert_eq!(
m.step(),
Err(MachineError::Cpu(CpuError::Memory(MemoryError::Unmapped(
KEYINPUT + 4
))))
);
assert_eq!(m.cpu(), &before);
assert_eq!(m.cycles(), cycles);
assert_eq!(m.last_timing(), timing);
assert_eq!(m.memory().read16(KEYCNT).unwrap(), 0);
assert!(!pending(m.memory()));
m.memory_mut().write8(HALTCNT, 0).unwrap();
assert!(m.halted());
m.memory_mut().write16(KEYCNT, 0xc001).unwrap();
assert!(pending(m.memory())); // Failure did not consume the first AND sample.
assert!(!m.halted());
}
#[test]
fn dma_control_write_samples_once_and_ready_dma_precedes_irq() {
for word in [false, true] {
let mut m = machine(false);
press(m.memory_mut(), 1);
m.memory_mut().write16(IE, IRQ).unwrap();
m.memory_mut().write16(IME, 1).unwrap();
let value = if word { 0x4001_ffff } else { 0x4001 };
m.memory_mut().write32(0x0200_0000, value).unwrap();
m.memory_mut().write32(DMA_BASE, 0x0200_0000).unwrap();
m.memory_mut()
.write32(DMA_BASE + 4, if word { KEYINPUT } else { KEYCNT })
.unwrap();
// Fixed destination, two units. IRQ from first unit must wait for second.
m.memory_mut()
.write32(DMA_BASE + 8, if word { 0x8440_0002 } else { 0x8040_0002 })
.unwrap();
let before = m.cpu().clone();
assert_eq!(m.step().unwrap(), StepKind::Dma { channel: 0 });
assert!(pending(m.memory()));
assert_eq!(m.cpu(), &before);
assert_eq!(m.step().unwrap(), StepKind::Dma { channel: 0 });
assert_eq!(m.cpu(), &before);
assert_eq!(m.step().unwrap(), StepKind::IrqEntry);
}
}
#[test]
fn keypad_and_timer_requests_coexist_and_acknowledge_independently() {
let mut m = bus();
m.write32(TIMER_BASE, 0x00c0_ffff).unwrap();
m.advance_cycles(1);
m.write16(TIMER_BASE + 2, 0).unwrap();
m.write16(KEYCNT, 0x4001).unwrap();
press(&mut m, 1);
assert_eq!(m.read16(IF).unwrap(), IRQ | 8);
ack(&mut m);
assert_eq!(m.read16(IF).unwrap(), 8);
press(&mut m, 1);
m.write16(IF, 8).unwrap();
assert_eq!(m.read16(IF).unwrap(), IRQ);
}
#[test]
fn original_bios_intrwait_can_sleep_until_keypad_callback_records_the_request() {
let rom = words(&[0xe3a0_0001, 0xe3a0_1a01, 0xef04_0000, 0xeaff_fffe]);
let mut m = bios::boot(rom).unwrap();
for _ in 0..100 {
if m.cpu().pc() == ROM_START + 8 {
break;
}
m.step().unwrap();
}
assert_eq!(m.cpu().pc(), ROM_START + 8);
let before = m.cpu().clone();
let callback = words(&[
0xe280_0c02,
0xe1d0_10b2,
0xe1c0_10b2,
0xe59f_2010,
0xe1d2_30b0,
0xe183_3001,
0xe1c2_30b0,
0xe12f_ff1e,
0xe1a0_0000,
bios::IRQ_FLAGS,
]);
for (i, byte) in callback.into_iter().enumerate() {
m.memory_mut().write8(0x0300_1000 + i as u32, byte).unwrap();
}
m.memory_mut()
.write32(bios::IRQ_HANDLER, 0x0300_1000)
.unwrap();
m.memory_mut().write16(KEYCNT, 0x4001).unwrap();
m.memory_mut().write16(IE, IRQ).unwrap();
for _ in 0..200 {
if m.halted() {
break;
}
m.step().unwrap();
}
assert!(m.halted());
press(m.memory_mut(), 1);
assert!(!m.halted());
let mut irq_entries = 0;
for _ in 0..300 {
if m.cpu().pc() == ROM_START + 12 {
break;
}
if m.step().unwrap() == StepKind::IrqEntry {
irq_entries += 1;
}
}
assert_eq!(irq_entries, 1);
assert_eq!(m.cpu().pc(), ROM_START + 12);
assert_eq!(m.cpu().cpsr(), before.cpsr());
assert_eq!(&m.cpu().registers()[..15], &before.registers()[..15]);
assert_eq!(m.memory().read16(IF).unwrap(), 0);
assert_eq!(m.memory().read16(bios::IRQ_FLAGS).unwrap(), 0);
}