feat(memory): sample Thumb IWRAM refill targets
After successful refill instructions, sample the Thumb IWRAM target and target+2 halfwords to establish bus history. Share ARM/Thumb refill classification with nominal timing. Respect restored instruction state, completed data accesses, region limits, and diagnostic rollback; keep instruction execution and nominal costs unchanged. Add original regressions for ARM/Thumb BX, Thumb branches and BL, PC writes, stack/block PC loads, status-restoring returns, real SWI/IRQ returns, both target alignments, mirrors/wrap, captured sample ordering, load lanes, failure retention, BIOS isolation, and DMA invalidation. Document official ARM7TDMI refill evidence and the distinction from a full pipeline. Darwin arm64 validation: 917 workspace tests and 848 core/demo tests per debug/release profile; 39 preparation tests; native ROM windows in both profiles; ten graphics/window smoke modes; fmt, clippy, and rustdoc. Public ARM, Thumb, memory, and BIOS reports remain identical to prior passing results and between profiles. Cold startup, ARM-target refill history, and DMA-to-CPU ordering remain incomplete; no hardware-conformance or commercial-game claim.
This commit is contained in:
parent
46af481487
commit
8b0f1a5ead
12 changed files with 566 additions and 35 deletions
|
|
@ -24,6 +24,8 @@ mod instruction_tests;
|
|||
#[cfg(test)]
|
||||
mod iwram_open_bus_tests;
|
||||
#[cfg(test)]
|
||||
mod iwram_refill_tests;
|
||||
#[cfg(test)]
|
||||
mod load_alias_tests;
|
||||
#[cfg(test)]
|
||||
mod open_bus_tests;
|
||||
|
|
@ -211,14 +213,17 @@ impl Cpu {
|
|||
// Record the executing instruction, not an operand's pipelined PC value.
|
||||
// Always clear the access context, including on diagnostic errors.
|
||||
let pc = self.pc();
|
||||
let sequential_thumb =
|
||||
self.instruction_set == InstructionSet::Thumb && !self.thumb_refills(instruction);
|
||||
let refill = self.instruction_refills(instruction);
|
||||
let sequential_thumb = self.instruction_set == InstructionSet::Thumb && !refill;
|
||||
memory.begin_cpu_access(pc, self.instruction_set);
|
||||
let result = self.execute_instruction(instruction, memory);
|
||||
let sequential = sequential_thumb
|
||||
&& self.instruction_set == InstructionSet::Thumb
|
||||
&& self.pc() == pc.wrapping_add(2);
|
||||
memory.end_cpu_access(result.is_ok(), sequential);
|
||||
if result.is_ok() && refill {
|
||||
memory.refill_cpu_bus_history(self.pc(), self.instruction_set);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ fn failed_steps_discard_prefetch_history_before_retry() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn taken_control_flow_invalidates_history_even_when_target_is_fallthrough() {
|
||||
fn taken_control_flow_refills_history_even_when_target_is_fallthrough() {
|
||||
for branch in [0x4720, 0xe7ff, 0x46a7, 0xd0ff, 0xbd00] {
|
||||
// BX, B, MOV pc, BEQ, POP pc.
|
||||
let (mut cpu, mut bus) = program(CODE, &[SEED, branch, PROBE, 0x5566, 0x99aa]);
|
||||
|
|
@ -306,12 +306,8 @@ fn taken_control_flow_invalidates_history_even_when_target_is_fallthrough() {
|
|||
cpu.step(&mut bus).unwrap();
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.pc(), CODE + 4);
|
||||
let before = cpu.clone();
|
||||
assert_eq!(
|
||||
cpu.step(&mut bus),
|
||||
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||
);
|
||||
assert_eq!(cpu, before);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.registers[6], 0x5566_99aa);
|
||||
}
|
||||
let (mut cpu, mut bus) = program(CODE, &[SEED, 0xd0ff, PROBE, 0x5566, 0x99aa]);
|
||||
cpu.flags.zero = false; // Untaken BEQ has no refill.
|
||||
|
|
|
|||
425
crates/gba-core/src/cpu/iwram_refill_tests.rs
Normal file
425
crates/gba-core/src/cpu/iwram_refill_tests.rs
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
//! Original control-flow programs for Thumb IWRAM refill-lane snapshots.
|
||||
use super::*;
|
||||
use crate::{
|
||||
dma::{DMA_BASE, DMA_STRIDE},
|
||||
io::{IE, IME, TIMER_BASE},
|
||||
machine::{Machine, MachineError, StepKind},
|
||||
memory::{BIOS_SIZE, ROM_START},
|
||||
};
|
||||
|
||||
const SOURCE: u32 = 0x0300_0100;
|
||||
const TARGET: u32 = 0x0300_0200;
|
||||
const STACK: u32 = 0x0300_1000;
|
||||
const UNUSED: u32 = 0x8000_0000;
|
||||
const PROBE: u16 = 0x6806; // LDR r6,[r0]
|
||||
const MARKER: u16 = 0x91ab;
|
||||
const LOOKAHEAD: u16 = 0xd234;
|
||||
|
||||
fn target(bus: &mut Memory, pc: u32, instruction: u16) {
|
||||
for (index, half) in [instruction, MARKER, LOOKAHEAD, 0x5678]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
bus.write16(pc + index as u32 * 2, half).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn expected(pc: u32) -> u32 {
|
||||
if pc & 2 == 0 {
|
||||
0x91ab_d234
|
||||
} else {
|
||||
0xd234_91ab
|
||||
}
|
||||
}
|
||||
|
||||
fn branch(source: u32, thumb: bool, destination: u32) -> (Cpu, Memory) {
|
||||
let instruction: u32 = if thumb { 0x46c0_4720 } else { 0xe12f_ff14 }; // BX r4
|
||||
let mut bus = if source < BIOS_SIZE as u32 {
|
||||
let mut bios = vec![0; BIOS_SIZE];
|
||||
bios[source as usize..source as usize + 4].copy_from_slice(&instruction.to_le_bytes());
|
||||
Memory::with_bios(vec![], bios).unwrap()
|
||||
} else if source == ROM_START {
|
||||
Memory::new(instruction.to_le_bytes().to_vec()).unwrap()
|
||||
} else {
|
||||
let mut bus = Memory::new(vec![]).unwrap();
|
||||
bus.write32(source, instruction).unwrap();
|
||||
bus
|
||||
};
|
||||
target(&mut bus, destination, PROBE);
|
||||
let mut cpu = Cpu::new(source);
|
||||
cpu.instruction_set = if thumb {
|
||||
InstructionSet::Thumb
|
||||
} else {
|
||||
InstructionSet::Arm
|
||||
};
|
||||
cpu.registers[0] = UNUSED;
|
||||
cpu.registers[4] = destination | 1;
|
||||
(cpu, bus)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arm_and_thumb_bx_establish_history_at_both_target_alignments_and_mirrors() {
|
||||
for source in [0x100, 0x0200_0100, SOURCE, ROM_START] {
|
||||
for thumb in [false, true] {
|
||||
for destination in [
|
||||
TARGET,
|
||||
TARGET + 2,
|
||||
TARGET + 0x8000,
|
||||
0x0300_7ffc,
|
||||
0x0300_7ffe,
|
||||
] {
|
||||
let (mut cpu, mut bus) = branch(source, thumb, destination);
|
||||
let status = cpu.cpsr() | 0xb000_00c0;
|
||||
cpu.apply_status(status, Mode::System);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.pc(), destination);
|
||||
assert_eq!(cpu.instruction_set(), InstructionSet::Thumb);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.registers[6], expected(destination));
|
||||
assert_eq!(cpu.cpsr(), status | 0x20);
|
||||
assert_eq!(cpu.registers[4], destination | 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refill_samples_target_plus_two_at_branch_time_but_plus_four_on_arrival() {
|
||||
for destination in [TARGET, TARGET + 2] {
|
||||
let (mut cpu, mut bus) = branch(ROM_START, false, destination);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
bus.write16(destination + 2, 0xeeee).unwrap();
|
||||
bus.write16(destination + 4, 0xabcd).unwrap();
|
||||
assert_eq!(bus.read16(destination + 2).unwrap(), 0xeeee);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(
|
||||
cpu.registers[6],
|
||||
if destination & 2 == 0 {
|
||||
0x91ab_abcd
|
||||
} else {
|
||||
0xabcd_91ab
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refill_history_supports_normal_load_lanes_rotation_and_sign_extension() {
|
||||
for destination in [TARGET, TARGET + 2] {
|
||||
let word = expected(destination);
|
||||
for (instruction, width, signed) in [
|
||||
(PROBE, 4, false),
|
||||
(0x7806, 1, false),
|
||||
(0x8806, 2, false),
|
||||
(0x5746, 1, true),
|
||||
(0x5f46, 2, true), // r5=0 offset, r6 result.
|
||||
] {
|
||||
for lane in 0..4 {
|
||||
let (mut cpu, mut bus) = branch(ROM_START, false, destination);
|
||||
target(&mut bus, destination, instruction);
|
||||
cpu.registers[0] += lane;
|
||||
cpu.step(&mut bus).unwrap();
|
||||
let byte = (word >> (lane * 8)) as u8;
|
||||
let half = (word >> ((lane & 2) * 8)) as u16;
|
||||
let value = match (width, signed) {
|
||||
(4, _) => word.rotate_right(lane * 8),
|
||||
(1, false) => u32::from(byte),
|
||||
(1, true) => byte as i8 as i32 as u32,
|
||||
(2, false) => u32::from(half).rotate_right((lane & 1) * 8),
|
||||
(2, true) if lane & 1 != 0 => byte as i8 as i32 as u32,
|
||||
(2, true) => half as i16 as i32 as u32,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut after = cpu.clone();
|
||||
after.registers[6] = value;
|
||||
after.registers[15] += 2;
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu, after);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thumb_pc_writes_refill_after_data_loads_and_preserve_transfer_semantics() {
|
||||
for destination in [TARGET, TARGET + 2] {
|
||||
for instruction in [0x4720, 0x46a7, 0x44a7, 0xbd00, 0xbc00, 0xcc00] {
|
||||
// BX r4, MOV pc,r4, ADD pc,r4, POP {pc}, empty POP, empty LDM r4!.
|
||||
let (mut cpu, mut bus) = branch(SOURCE, true, destination);
|
||||
bus.write16(SOURCE, instruction).unwrap();
|
||||
bus.write32(STACK, destination).unwrap();
|
||||
cpu.registers[13] = STACK;
|
||||
cpu.registers[4] = match instruction {
|
||||
0xcc00 => STACK,
|
||||
0x44a7 => destination.wrapping_sub(SOURCE + 4),
|
||||
_ => destination | 1,
|
||||
};
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.pc(), destination);
|
||||
assert_eq!(cpu.instruction_set(), InstructionSet::Thumb);
|
||||
if instruction == 0xbd00 {
|
||||
assert_eq!(cpu.registers[13], STACK + 4);
|
||||
}
|
||||
if instruction == 0xbc00 {
|
||||
assert_eq!(cpu.registers[13], STACK + 64);
|
||||
}
|
||||
if instruction == 0xcc00 {
|
||||
assert_eq!(cpu.registers[4], STACK + 64);
|
||||
}
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(
|
||||
cpu.registers[6],
|
||||
expected(destination),
|
||||
"instruction={instruction:#x}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thumb_taken_branches_and_bl_suffix_establish_target_history() {
|
||||
for destination in [TARGET, TARGET + 2] {
|
||||
for kind in 0..3 {
|
||||
let (mut cpu, mut bus) = branch(SOURCE, true, destination);
|
||||
if kind == 2 {
|
||||
bus.write16(SOURCE, 0xf000).unwrap(); // BL prefix: high offset zero.
|
||||
bus.write16(SOURCE + 2, 0xf800 | ((destination - SOURCE - 4) / 2) as u16)
|
||||
.unwrap();
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.pc(), SOURCE + 2);
|
||||
} else {
|
||||
let offset = ((destination - SOURCE - 4) / 2) as u16;
|
||||
bus.write16(
|
||||
SOURCE,
|
||||
if kind == 0 {
|
||||
0xe000 | offset
|
||||
} else {
|
||||
0xd000 | offset
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
cpu.flags.zero = true;
|
||||
}
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.pc(), destination);
|
||||
if kind == 2 {
|
||||
assert_eq!(cpu.registers[14], (SOURCE + 4) | 1);
|
||||
}
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.registers[6], expected(destination));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arm_exception_returns_use_saved_thumb_state_and_restored_banks() {
|
||||
for destination in [TARGET, TARGET + 2] {
|
||||
for mode in [Mode::User, Mode::System] {
|
||||
for instruction in [0xe1b0_f00e_u32, 0xe25e_f004, 0xe8fd_8004] {
|
||||
// MOVS pc,lr; SUBS pc,lr,#4; LDMIA sp!,{r2,pc}^.
|
||||
let mut bus = Memory::new(instruction.to_le_bytes().to_vec()).unwrap();
|
||||
target(&mut bus, destination, PROBE);
|
||||
bus.write32(STACK, 0xfeed_beef).unwrap();
|
||||
bus.write32(STACK + 4, destination).unwrap();
|
||||
let mut cpu = Cpu::new(ROM_START);
|
||||
cpu.registers[0] = UNUSED;
|
||||
cpu.registers[13] = 0x0300_7000;
|
||||
cpu.registers[14] = 0x1234;
|
||||
cpu.enter_exception(Exception::SoftwareInterrupt);
|
||||
cpu.registers[15] = ROM_START;
|
||||
cpu.registers[13] = STACK;
|
||||
cpu.registers[14] = destination + if instruction == 0xe25e_f004 { 4 } else { 0 };
|
||||
let status = 0xa000_00e0 | mode as u32;
|
||||
cpu.set_spsr(status);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.pc(), destination);
|
||||
assert_eq!(cpu.cpsr(), status);
|
||||
assert_eq!(cpu.registers[13], 0x0300_7000);
|
||||
assert_eq!(cpu.registers[14], 0x1234);
|
||||
if instruction == 0xe8fd_8004 {
|
||||
assert_eq!(cpu.registers[2], 0xfeed_beef);
|
||||
}
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.registers[6], expected(destination));
|
||||
cpu.switch_mode(Mode::Supervisor);
|
||||
assert_eq!(
|
||||
cpu.registers[13],
|
||||
STACK + if instruction == 0xe8fd_8004 { 8 } else { 0 }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_swi_and_irq_returns_can_resume_thumb_iwram_with_known_lanes() {
|
||||
for irq in [false, true] {
|
||||
let destination = if irq { TARGET } else { TARGET + 2 };
|
||||
let mut bios = vec![0; BIOS_SIZE];
|
||||
bios[8..12].copy_from_slice(&0xe1b0_f00e_u32.to_le_bytes());
|
||||
bios[0x18..0x1c].copy_from_slice(&0xe25e_f004_u32.to_le_bytes());
|
||||
let mut bus = Memory::with_bios(vec![], bios).unwrap();
|
||||
target(&mut bus, destination, PROBE);
|
||||
if !irq {
|
||||
bus.write16(TARGET, 0xdf00).unwrap();
|
||||
}
|
||||
let mut cpu = Cpu::new(TARGET);
|
||||
cpu.instruction_set = InstructionSet::Thumb;
|
||||
cpu.registers[0] = UNUSED;
|
||||
let status = cpu.cpsr();
|
||||
let mut machine = Machine::new(cpu, bus);
|
||||
if irq {
|
||||
machine.memory_mut().write16(IE, 8).unwrap();
|
||||
machine.memory_mut().write16(IME, 1).unwrap();
|
||||
machine
|
||||
.memory_mut()
|
||||
.write32(TIMER_BASE, 0x00c0_ffff)
|
||||
.unwrap();
|
||||
machine.memory_mut().advance_cycles(1);
|
||||
assert_eq!(machine.step().unwrap(), StepKind::IrqEntry);
|
||||
machine.memory_mut().write16(IME, 0).unwrap(); // Keep this test to one IRQ entry.
|
||||
} else {
|
||||
machine.step().unwrap();
|
||||
}
|
||||
machine.step().unwrap(); // Execute the original ARM return handler.
|
||||
assert_eq!(machine.cpu().pc(), destination);
|
||||
assert_eq!(machine.cpu().cpsr(), status);
|
||||
machine.step().unwrap();
|
||||
assert_eq!(machine.cpu().registers()[6], expected(destination));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_refill_instructions_preserve_cpu_and_preexisting_history() {
|
||||
for instruction in [0xe1b0_f00e_u32, 0xe8fd_8004] {
|
||||
let mut bus = Memory::new(instruction.to_le_bytes().to_vec()).unwrap();
|
||||
for (index, half) in [0x680a_u16, PROBE, 0x1111, LOOKAHEAD]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
bus.write16(SOURCE + index as u32 * 2, half).unwrap();
|
||||
}
|
||||
bus.write32(STACK, 0x1234_abcd).unwrap();
|
||||
let mut paused = Cpu::new(SOURCE);
|
||||
paused.instruction_set = InstructionSet::Thumb;
|
||||
paused.registers[0] = UNUSED;
|
||||
paused.registers[1] = STACK;
|
||||
paused.step(&mut bus).unwrap(); // Seed sequential history with a word load.
|
||||
let mut failing = Cpu::at_reset();
|
||||
failing.registers[15] = ROM_START;
|
||||
failing.registers[13] = STACK;
|
||||
failing.registers[14] = TARGET;
|
||||
failing.set_spsr(0); // Invalid return mode; no refill must commit.
|
||||
let before = failing.clone();
|
||||
assert!(failing.step_timed(&mut bus).is_err());
|
||||
assert_eq!(failing, before);
|
||||
paused.step(&mut bus).unwrap();
|
||||
assert_eq!(paused.registers[6], 0xd234_abcd);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cold_entry_region_crossings_and_unmapped_targets_do_not_invent_refill_history() {
|
||||
let mut bus = Memory::new(vec![]).unwrap();
|
||||
target(&mut bus, TARGET, PROBE);
|
||||
let mut cpu = Cpu::new(TARGET);
|
||||
cpu.instruction_set = InstructionSet::Thumb;
|
||||
cpu.registers[0] = UNUSED;
|
||||
assert_eq!(
|
||||
cpu.step(&mut bus),
|
||||
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||
);
|
||||
|
||||
for destination in [0x03ff_fffc, 0x03ff_fffe, 0x0e00_0000] {
|
||||
let mut bus = Memory::new(0xe12f_ff14_u32.to_le_bytes().to_vec()).unwrap();
|
||||
if destination >> 24 == 3 {
|
||||
bus.write16(destination, PROBE).unwrap();
|
||||
}
|
||||
let mut cpu = Cpu::new(ROM_START);
|
||||
cpu.registers[0] = UNUSED;
|
||||
cpu.registers[4] = destination | 1;
|
||||
cpu.step(&mut bus).unwrap(); // An unavailable target/refill never fails BX early.
|
||||
assert_eq!(cpu.pc(), destination);
|
||||
let before = cpu.clone();
|
||||
assert_eq!(
|
||||
cpu.step(&mut bus),
|
||||
Err(CpuError::Memory(MemoryError::Unmapped(
|
||||
if destination >> 24 == 3 {
|
||||
UNUSED
|
||||
} else {
|
||||
destination
|
||||
}
|
||||
)))
|
||||
);
|
||||
assert_eq!(cpu, before);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_pair_refill_does_not_replace_the_separate_bios_retained_word() {
|
||||
for destination in [TARGET, TARGET + 2] {
|
||||
let mut bios = vec![0; BIOS_SIZE];
|
||||
bios[0x100..0x104].copy_from_slice(&0xe12f_ff14_u32.to_le_bytes());
|
||||
bios[0x108..0x10c].copy_from_slice(&0x89ab_cdef_u32.to_le_bytes());
|
||||
let mut bus = Memory::with_bios(vec![], bios).unwrap();
|
||||
target(&mut bus, destination, PROBE);
|
||||
let mut cpu = Cpu::new(0x100);
|
||||
cpu.registers[4] = destination | 1;
|
||||
cpu.step(&mut bus).unwrap();
|
||||
cpu.step(&mut bus).unwrap(); // r0=0: protected BIOS read, not unused memory.
|
||||
assert_eq!(cpu.registers[6], 0x89ab_cdef);
|
||||
assert_eq!(bus.read32(0x108).unwrap(), 0x89ab_cdef);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_dma_between_refill_and_target_execution_invalidates_refill_history() {
|
||||
let (cpu, mut bus) = branch(ROM_START, false, TARGET);
|
||||
bus.write32(STACK, 0x1234_abcd).unwrap();
|
||||
let mut machine = Machine::new(cpu, bus);
|
||||
machine.step().unwrap();
|
||||
let dma = DMA_BASE + 3 * DMA_STRIDE;
|
||||
machine.memory_mut().write32(dma, STACK).unwrap();
|
||||
machine.memory_mut().write32(dma + 4, 0x0200_0000).unwrap();
|
||||
machine.memory_mut().write32(dma + 8, 0x8400_0001).unwrap();
|
||||
assert_eq!(machine.step().unwrap(), StepKind::Dma { channel: 3 });
|
||||
let before = machine.cpu().clone();
|
||||
let timing = machine.last_timing();
|
||||
let cycles = machine.cycles();
|
||||
assert_eq!(
|
||||
machine.step(),
|
||||
Err(MachineError::Cpu(CpuError::Memory(MemoryError::Unmapped(
|
||||
UNUSED
|
||||
))))
|
||||
);
|
||||
assert_eq!(machine.cpu(), &before);
|
||||
assert_eq!(machine.last_timing(), timing);
|
||||
assert_eq!(machine.cycles(), cycles);
|
||||
assert_eq!(machine.cpu().pc(), TARGET);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refill_snapshots_keep_existing_nominal_costs_and_timed_untimed_results() {
|
||||
for thumb in [false, true] {
|
||||
for destination in [TARGET, TARGET + 2] {
|
||||
let (cpu, mut bus) = branch(ROM_START, thumb, destination);
|
||||
bus.write32(TIMER_BASE, 0x0080_0000).unwrap();
|
||||
let mut machine = Machine::new(cpu, bus);
|
||||
let (mut untimed, mut bus) = branch(ROM_START, thumb, destination);
|
||||
for index in 0..2 {
|
||||
untimed.step(&mut bus).unwrap();
|
||||
machine.step().unwrap();
|
||||
assert_eq!(machine.cpu(), &untimed);
|
||||
assert_eq!(
|
||||
machine.last_timing().code_cycles,
|
||||
if index == 0 { 3 } else { 1 }
|
||||
);
|
||||
assert_eq!(machine.last_timing().data_cycles, index);
|
||||
assert_eq!(machine.last_timing().internal_cycles, index);
|
||||
}
|
||||
assert_eq!(machine.cycles(), 6);
|
||||
assert_eq!(machine.memory().read16(TIMER_BASE).unwrap(), 6);
|
||||
assert_eq!(bus.cycles(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
//! Nominal ARM7 instruction-cycle summaries, separate from instruction semantics.
|
||||
//! Data costs come from actual bus accesses. No speculative pipeline reads occur.
|
||||
//! Data costs come from actual bus accesses. Bus-history samples are separate from timing calculations.
|
||||
//! PC writes use target-region code costs, following GBATEK's cycle summary.
|
||||
|
||||
use super::{Cpu, CpuError, InstructionSet};
|
||||
|
|
@ -151,8 +151,11 @@ impl Cpu {
|
|||
|
||||
// Share the existing control-flow classification with bounded bus history.
|
||||
// A PC write can refill even when its target equals the sequential address.
|
||||
pub(super) fn thumb_refills(&self, instruction: u32) -> bool {
|
||||
self.thumb_summary(instruction).refill
|
||||
pub(super) fn instruction_refills(&self, instruction: u32) -> bool {
|
||||
match self.instruction_set {
|
||||
InstructionSet::Arm => self.arm_summary(instruction).refill,
|
||||
InstructionSet::Thumb => self.thumb_summary(instruction).refill,
|
||||
}
|
||||
}
|
||||
|
||||
fn thumb_summary(&self, instruction: u32) -> Summary {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@ struct CpuAccess {
|
|||
/// Unused-memory reads use ARM PC+8 or supported region-dependent Thumb snapshots.
|
||||
/// Protected BIOS reads separately retain the snapshot from BIOS execution.
|
||||
/// Sequential Thumb IWRAM accesses retain addressed bus lanes transactionally.
|
||||
/// Refill history, a full fetch pipeline, and DMA bus latches are not modeled.
|
||||
/// Successful refills into Thumb IWRAM sample the target pair to establish history.
|
||||
/// A full fetch pipeline, ARM-target refill history, and DMA bus latches are not modeled.
|
||||
pub struct Memory {
|
||||
external_ram: Vec<u8>,
|
||||
internal_ram: Vec<u8>,
|
||||
|
|
@ -400,6 +401,24 @@ impl Memory {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sample only the two target fetches of a completed refill into Thumb IWRAM.
|
||||
/// This updates bus history, not the CPU's instruction buffer or nominal costs.
|
||||
/// Incomplete or unsupported refills stay unknown without failing the branch early.
|
||||
pub(crate) fn refill_cpu_bus_history(&mut self, pc: u32, instruction_set: InstructionSet) {
|
||||
self.iwram_bus.invalidate();
|
||||
if instruction_set == InstructionSet::Thumb
|
||||
&& pc >> 24 == 3
|
||||
&& pc.wrapping_add(2) >> 24 == 3
|
||||
{
|
||||
if let (Some(first), Some(second)) = (
|
||||
self.snapshot_mapped_halfword(pc),
|
||||
self.snapshot_mapped_halfword(pc + 2),
|
||||
) {
|
||||
self.iwram_bus.refill(pc, [first, second]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn invalidate_cpu_bus_history(&mut self) {
|
||||
self.iwram_bus.invalidate();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//! Transactional lane history for consecutive Thumb instructions in IWRAM.
|
||||
//! Unknown startup/refill/DMA history is not replaced with current memory bytes.
|
||||
//! Transactional lane history for Thumb IWRAM execution and target-pair refills.
|
||||
//! Unknown startup/DMA history is not replaced with guessed memory bytes.
|
||||
use std::cell::Cell;
|
||||
|
||||
use crate::timing::AccessWidth;
|
||||
|
|
@ -87,6 +87,20 @@ impl IwramBus {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn refill(&mut self, pc: u32, fetched: [u16; 2]) {
|
||||
debug_assert!(self.pending.get().is_none());
|
||||
debug_assert_eq!(pc & 1, 0);
|
||||
let mut lanes = Lanes::default();
|
||||
for (index, half) in fetched.into_iter().enumerate() {
|
||||
lanes.drive(
|
||||
pc + index as u32 * 2,
|
||||
AccessWidth::Halfword,
|
||||
u32::from(half),
|
||||
);
|
||||
}
|
||||
self.committed = Some(History { next_pc: pc, lanes });
|
||||
}
|
||||
|
||||
pub(super) fn invalidate(&mut self) {
|
||||
debug_assert!(self.pending.get().is_none());
|
||||
self.committed = None;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ The repository is a Cargo workspace with three crates:
|
|||
| `crates/gba-core/src/cpu/thumb_bios_access_tests.rs` | Thumb BIOS aligned-word snapshots, transitions, load lanes, boundaries, diagnostics, and nominal timing |
|
||||
| `crates/gba-core/src/cpu/thumb_open_bus_tests.rs` | Region-dependent Thumb open bus, mirrors, widths, transfers, source boundaries, isolation, and nominal timing |
|
||||
| `crates/gba-core/src/cpu/iwram_open_bus_tests.rs` | Sequential IWRAM lane history, access widths, isolation, errors, refills, IRQ/DMA invalidation, and nominal timing |
|
||||
| `crates/gba-core/src/memory/iwram_bus.rs` | Transactional known-lane history for sequential Thumb IWRAM execution |
|
||||
| `crates/gba-core/src/cpu/iwram_refill_tests.rs` | Thumb IWRAM target-pair refills, state changes, exception returns, boundaries, isolation, and nominal costs |
|
||||
| `crates/gba-core/src/memory/iwram_bus.rs` | Transactional known-lane history for Thumb IWRAM execution and target-pair refills |
|
||||
| `crates/gba-core/src/cpu/compare_psr_tests.rs` | Original Rd=15 test/compare status restoration, flags, bank, and timing regressions |
|
||||
| `crates/gba-core/src/cpu/status_tests.rs`, `crates/gba-core/src/cpu/exception_tests.rs` | Status, banking, exception, and return tests |
|
||||
| `crates/gba-core/src/cpu/thumb_tests.rs` | Thumb instructions, state switching, and decoder checks |
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ Limits remain explicit:
|
|||
- Unused/write-only I/O reads, DMA latches, and disabled-RAM reads are not modeled here.
|
||||
- Missing BIOS, truncated ROM, unsupported I/O, and save-memory accesses retain their existing diagnostics.
|
||||
- Unused-memory writes remain diagnostics rather than ignored hardware writes. Swaps cannot silently discard their write.
|
||||
- There is no persistent instruction pipeline. Sequential Thumb IWRAM history does not model refills or DMA-to-CPU bus transitions.
|
||||
- There is no persistent instruction pipeline. Thumb IWRAM refills sample their target pair, but DMA-to-CPU transitions remain unmodeled.
|
||||
|
||||
This subset lets public ARM test 362 complete. That test does not assert the loaded data value.
|
||||
Original regressions check the values separately. See [the public ARM result](../public-arm-tests.md).
|
||||
|
|
@ -162,11 +162,33 @@ Success commits history only for the next sequential Thumb IWRAM PC. Failure pre
|
|||
Block-transfer data accesses stage updates in order; unused-memory reads still use one entry snapshot per instruction.
|
||||
This preserves the existing atomic diagnostic policy without adding bus or device cycles.
|
||||
|
||||
Taken branches and PC-writing refills invalidate continuation history, even when their target is the fallthrough address.
|
||||
Taken branches and PC-writing refills end the old continuation history, even when their target is the fallthrough address.
|
||||
A successful refill into Thumb IWRAM establishes new history from its target pair, as described below.
|
||||
Untaken conditional branches keep sequential history.
|
||||
Other instruction states/regions, accepted machine IRQs, and successful DMA units end the supported history sequence.
|
||||
Failed DMA units leave history unchanged. Discontinuous entry starts unknown instead of borrowing stale lanes.
|
||||
Refill fetches, ARM-to-Thumb history, and DMA latch contents remain unmodeled.
|
||||
Other execution states/regions, accepted machine IRQs, and successful DMA units invalidate continuation history.
|
||||
A later exception return into Thumb IWRAM can establish new history through its refill.
|
||||
Failed DMA units leave history unchanged. Discontinuous entry without an executed refill starts unknown.
|
||||
|
||||
### Thumb IWRAM refill history
|
||||
|
||||
After a successful refill instruction, the emulator uses the resulting PC and instruction state.
|
||||
If the destination is Thumb IWRAM, it samples two halfwords in order: target T, then T+2.
|
||||
Both accesses must remain in IWRAM. Normal physical RAM mirrors apply.
|
||||
These samples establish both word lanes for execution at T. The first instruction then samples T+4 as usual.
|
||||
|
||||
This applies to ARM/Thumb BX, taken Thumb branches, BL suffixes, Thumb PC writes, and ARM status-restoring returns.
|
||||
Saved Thumb state, not the target's low bit alone, controls exception-return sampling.
|
||||
Stack and block-load returns finish their data accesses before the target samples.
|
||||
Failed instructions do not sample a target or replace committed history.
|
||||
|
||||
Snapshots use strict mapped reads and add no nominal data or device cycles.
|
||||
They update bus history only; the interpreter does not cache these instructions for execution.
|
||||
Host inspection does not change captured lanes. A failed target fetch remains a later instruction diagnostic.
|
||||
Cold direct startup, unsupported target states/regions, and region-crossing lookahead still have no inferred refill history.
|
||||
DMA between refill and arrival still invalidates this history rather than supplying a guessed DMA value.
|
||||
|
||||
The [refill evidence and scope](../research/iwram-bus-history.md#thumb-iwram-refill-extension) distinguish these samples from a full pipeline.
|
||||
ARM-target refill history, DMA ordering, self-modifying instruction execution, and exact refill timing remain incomplete.
|
||||
|
||||
## Processor status and exceptions
|
||||
|
||||
|
|
@ -246,7 +268,8 @@ Important timing limits:
|
|||
- Code S/N counts follow instruction summaries, not a simulated fetch pipeline.
|
||||
- Ordinary code costs use the current instruction address. The ARM open-bus PC+8 snapshot has no fetch timing or startup pipeline fill.
|
||||
- PC writes use destination-region costs and the restored instruction width for nominal refill accesses.
|
||||
- Refill cost calculation does not read target bytes. An invalid branch target fails on the following instruction fetch.
|
||||
- Refill cost calculation does not read target bytes. Separate Thumb IWRAM target-pair samples update bus history without extra cycles.
|
||||
- Target-pair sampling cannot fail the branch early. An invalid branch target still fails on the following instruction fetch.
|
||||
- Game Pak prefetch is not implemented. WAITCNT bit 14 is stored but does not accelerate execution.
|
||||
- PHI and SRAM wait fields are stored; PHI output and SRAM mapping are not implemented.
|
||||
- External work RAM timing is fixed. The undocumented memory-control register is not implemented.
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ Hardware references used for the core:
|
|||
- [NanoBoyAdvance keypad implementation](https://github.com/nba-emu/NanoBoyAdvance/blob/master/src/nba/src/hw/keypad/keypad.cc), compared for control access widths; its AND/retrigger behavior differs from the chosen polling model.
|
||||
- [mGBA keypad timing issue](https://github.com/mgba-emu/mgba/issues/2490), for frontend frame-based input timing limits.
|
||||
- [Tonc hardware interrupts](https://gbadev.net/tonc/interrupts.html), for keypad source enable and IF/IE bit 12.
|
||||
- [GBATEK memory mirrors, video byte writes, and unused-memory reads](https://problemkaputt.de/gbatek-gba-unpredictable-things.htm), for unused address ranges, ARM PC+8 open bus, byte lanes, the distinct Thumb region/history rules, and PC-dependent BIOS read protection. The Thumb implementation follows its 16-bit-region and BIOS/OAM formulas. IWRAM has bounded sequential lane history; refills and region transitions remain unsupported.
|
||||
- [GBATEK memory mirrors, video byte writes, and unused-memory reads](https://problemkaputt.de/gbatek-gba-unpredictable-things.htm), for unused address ranges, ARM PC+8 open bus, byte lanes, the distinct Thumb region/history rules, and PC-dependent BIOS read protection. The Thumb implementation follows its 16-bit-region and BIOS/OAM formulas. IWRAM has bounded sequential lane history and Thumb target-pair refill samples; region transitions remain unsupported.
|
||||
- [nocash GBA open-bus discussion](https://www.ngemu.com/threads/gba-open-bus.170809/), for ARM prefetch, repeated Thumb halfwords in 16-bit regions, aligned BIOS/OAM words, and history-dependent IWRAM lanes. Its data-load overwrite note prevents treating P+2 as a universal IWRAM rule.
|
||||
- [mGBA memory implementation](https://github.com/mgba-emu/mgba/blob/master/src/gba/memory.c), reviewed for `GBALoadBad`, ARM prefetch sourcing, byte/halfword lane selection, and separate DMA/history behavior. Also reviewed its BIOS region-exit prefetch retention and protected-load lane selection.
|
||||
- [GBATEK GBA memory map and bus widths](https://problemkaputt.de/gbatek-gba-memory-map.htm).
|
||||
|
|
@ -112,6 +112,15 @@ IWRAM lane-history research:
|
|||
|
||||
See [IWRAM Thumb bus history](research/iwram-bus-history.md). These were source reviews, not local hardware or external-emulator test runs.
|
||||
|
||||
IWRAM refill research:
|
||||
|
||||
- [ARM7TDMI branch cycle sequence](https://support.arm.com/documentation/ddi0029/g/instruction-cycle-timings/branch-and-branch-with-link), for destination and destination+instruction-width fetches.
|
||||
- [ARM7TDMI Thumb BL sequence](https://support.arm.com/documentation/ddi0210/c/Instruction-Cycle-Timings/Thumb-branch-with-link), for the prefix/suffix distinction.
|
||||
- [ARM7TDMI load-multiple sequence](https://support.arm.com/documentation/ddi0029/g/instruction-cycle-timings/load-multiple-registers), for data-transfer ordering before PC-load refills.
|
||||
- [NanoBoyAdvance refill helpers](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/arm/arm7tdmi.hh), reviewed for Thumb target/target+2 fetch order and the following execution fetch.
|
||||
|
||||
These sources support bounded refill sampling, not a claim of cycle-accurate pipeline or DMA behavior.
|
||||
|
||||
Use [GBATEK](https://problemkaputt.de/gbatek.htm) and ARM7TDMI documentation for further hardware work.
|
||||
Only use game ROMs that you may lawfully use. Do not commit game ROMs, BIOS files, or game assets.
|
||||
The ignore file excludes `roms/`, common game ROM extensions, and save files.
|
||||
|
|
|
|||
|
|
@ -48,15 +48,43 @@ Ordinary instructions and mapped data accesses can establish history without req
|
|||
Consecutive halfword fetches or an IWRAM word access can make every lane known.
|
||||
Missing lookahead and 16 MiB region-crossing fetches retain diagnostics.
|
||||
|
||||
Successful branches and other refill instructions invalidate continuation history, even when the target equals the next sequential address.
|
||||
The existing Thumb timing classifier identifies these refills for both timed and untimed CPU stepping.
|
||||
Execution outside Thumb IWRAM also ends this bounded history sequence.
|
||||
An accepted machine IRQ and each successful DMA unit invalidate the history. Failed DMA units preserve it.
|
||||
The implementation does not guess refill or DMA latch contents.
|
||||
The initial sequential-only implementation invalidated history after all refill instructions.
|
||||
The extension below now establishes target-pair history for successful refills into Thumb IWRAM.
|
||||
The existing ARM/Thumb timing classifiers identify these refills for both timed and untimed CPU stepping.
|
||||
Execution outside Thumb IWRAM still ends a sequential history sequence.
|
||||
An accepted machine IRQ and each successful DMA unit invalidate history. Failed DMA units preserve it.
|
||||
The implementation does not guess DMA latch contents.
|
||||
|
||||
This is not a full IWRAM bus model across all CPU states. ARM access history, refill fetches, and DMA-to-CPU ordering remain incomplete.
|
||||
This is not a full IWRAM bus model across all CPU states. ARM-target history and DMA-to-CPU ordering remain incomplete.
|
||||
It is not a persistent instruction pipeline. Self-modifying instruction execution and exact per-access device timing remain unverified.
|
||||
|
||||
## Thumb IWRAM refill extension
|
||||
|
||||
The [ARM7TDMI branch sequence](https://support.arm.com/documentation/ddi0029/g/instruction-cycle-timings/branch-and-branch-with-link) fetches the destination, then destination plus instruction width.
|
||||
For Thumb destinations, those addresses are T and T+2.
|
||||
[NanoBoyAdvance's refill helpers](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/arm/arm7tdmi.hh) use that order.
|
||||
Its execution loop then fetches T+4 before executing the first Thumb instruction.
|
||||
|
||||
After a successful refill instruction, we sample the two mapped destination halfwords if both remain in Thumb IWRAM.
|
||||
The samples replace the old lane history and establish the next expected PC as T.
|
||||
The next instruction's normal PC+4 sample updates one lane while the other retains the captured T+2 halfword.
|
||||
The samples occur after data accesses and status restoration, so loaded PC values and saved Thumb state select the destination.
|
||||
|
||||
This covers actual ARM/Thumb BX, taken Thumb branches, BL suffixes, PC-writing Thumb operations, and ARM status-restoring returns.
|
||||
The Thumb BL prefix alone does not refill.
|
||||
Failed instructions retain the previous committed history. An unsupported or incomplete target sample does not fail the branch early.
|
||||
Unmapped instruction targets still fail on the following fetch.
|
||||
Cold direct startup and arbitrary PC changes do not manufacture a refill.
|
||||
|
||||
These are bus-history samples, not instruction-buffer contents or extra emulated cycles.
|
||||
The interpreter still reads instructions when it executes them.
|
||||
Changing T+2 after the branch does not replace its captured bus value; changing T+4 before arrival affects the later sample.
|
||||
Those original tests verify sample ordering, not hardware-accurate self-modifying instruction execution.
|
||||
|
||||
Successful DMA still invalidates history, including between refill and arrival.
|
||||
ARM-target refill history, exact pipeline timing, and DMA-to-CPU ordering remain outside this extension.
|
||||
No external emulator differential run or physical-hardware test was performed.
|
||||
|
||||
## Original regression coverage
|
||||
|
||||
- Both code alignments, IWRAM mirrors, and physical RAM wrap.
|
||||
|
|
@ -66,11 +94,14 @@ It is not a persistent instruction pipeline. Self-modifying instruction executio
|
|||
- Multiple-register transfers, final-word retention, and writeback.
|
||||
- Unknown lanes, establishment of known history, staged-access rollback, and retry.
|
||||
- Taken and untaken branches, PC writes to fallthrough, discontinuities, IRQ entry, and DMA success/failure.
|
||||
- ARM/Thumb source regions, both target alignments, refill sample ordering, stack/block returns, and saved User/System banks.
|
||||
- Actual SWI/IRQ return handlers, failed refills, unmapped targets, separate BIOS history, and DMA between refill and arrival.
|
||||
- Equal timed/untimed CPU results and unchanged nominal data/device costs.
|
||||
|
||||
## Validation result
|
||||
|
||||
The sequential-history regressions reproduced the old unsupported-read failures before implementation.
|
||||
The refill regressions also failed on the prior invalidation-only path, then passed with target-pair sampling.
|
||||
Workspace and core/demo tests pass on Darwin arm64 in debug and release builds.
|
||||
Formatting, lint checks, rustdoc, preparation tests, native ROM windows, and graphics smoke modes also pass.
|
||||
Public ARM, Thumb, memory, and BIOS reports match their previous passing results exactly.
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ Update this file when a feature lands or a limit is removed.
|
|||
- ARM unused-memory data reads return a PC+8 word snapshot, with normal byte lanes and load rotation.
|
||||
- Thumb unused-memory reads use repeated PC+4 halfwords in 16-bit code regions and aligned PC+4 words in BIOS/OAM.
|
||||
- Sequential Thumb IWRAM history retains fetched and data-access lanes, with known-bit tracking and diagnostic rollback.
|
||||
- Successful refills into Thumb IWRAM establish bus history from the target and target+2 halfwords, including exception returns.
|
||||
- Little-endian byte, halfword, and word reads/writes.
|
||||
- ARM7TDMI word-load rotation, aligned stores, and odd-address halfword-load behavior.
|
||||
- Explicit errors for unsupported instructions when their condition passes, and for unsupported memory accesses.
|
||||
|
|
@ -134,8 +135,9 @@ The runner requires known completion addresses and has no debug-port protocol, s
|
|||
ARM data reads from unused address ranges use a bounded [PC+8 open-bus snapshot](hardware/cpu.md#arm-unused-memory-data-reads).
|
||||
Thumb reads use [region-dependent snapshots](hardware/cpu.md#thumb-unused-memory-data-reads) and bounded sequential IWRAM lane history.
|
||||
Cold or invalidated IWRAM history remains diagnostic until all lanes are known.
|
||||
Refill instructions, accepted machine IRQs, successful DMA, and execution outside Thumb IWRAM end the supported history sequence.
|
||||
This is not a fetch pipeline. Refill history, region-crossing fetches, DMA latches, and unused/write-only I/O open bus remain unsupported.
|
||||
Refills into Thumb IWRAM establish new target-pair history after the instruction succeeds.
|
||||
Accepted machine IRQs, successful DMA, unsupported refills, and other execution states/regions invalidate continuation history.
|
||||
This is not a fetch pipeline. ARM-target refill history, region-crossing fetches, DMA latches, and unused/write-only I/O open bus remain unsupported.
|
||||
BIOS protection uses separate retained BIOS fetch snapshots as described in [BIOS behavior](hardware/bios.md#cpu-bios-read-protection).
|
||||
Host inspection and instruction fetches remain strict. Other unmapped reads return errors.
|
||||
Reads beyond the supplied cartridge bytes also return errors.
|
||||
|
|
@ -145,7 +147,7 @@ Writes to cartridge addresses return errors instead of modeling cartridge hardwa
|
|||
|
||||
## Next steps
|
||||
|
||||
1. Extend IWRAM history across validated refill and DMA-to-CPU transitions.
|
||||
1. Research and validate DMA-to-CPU bus history, including latch ownership and IWRAM effects.
|
||||
Keep unknown history diagnostic until fetch ordering and latch ownership have independent coverage.
|
||||
Validate BIOS snapshots with independent hardware tests before claiming exact refill or data-access history.
|
||||
The desktop runner still uses only the original BIOS. Keep protected reads image-derived for all supplied images.
|
||||
|
|
|
|||
|
|
@ -72,7 +72,10 @@ Short-ROM tests require only the fetched halfword in 16-bit regions. Cold IWRAM
|
|||
Sequential IWRAM regressions check retained lanes across fetches, reads, writes, mirrors, and physical RAM wrap.
|
||||
They cover raw bus values before load rotation/sign extension, all modes, block transfers, and isolation from other memory regions.
|
||||
Known-bit tests reject incomplete words. Transaction tests verify discarded partial updates and preserved history after errors.
|
||||
Control-flow tests distinguish taken/untaken branches and invalidate PC writes even when the target equals fallthrough.
|
||||
Control-flow tests distinguish taken/untaken branches and refill Thumb IWRAM targets even when the target equals fallthrough.
|
||||
Refill tests cover ARM/Thumb BX, Thumb branches and BL, high-register PC writes, stack/block PC loads, and saved-state returns.
|
||||
They check both target alignments, mirrors/wrap, captured target+2 versus later target+4, widths, sign extension, and rotation.
|
||||
Original SWI/IRQ handlers verify return-time history. Failed returns, cold entry, region boundaries, DMA invalidation, and BIOS isolation retain checks.
|
||||
DMA and IRQ tests check conservative invalidation; timed/untimed tests verify CPU results and nominal clock costs.
|
||||
Host/fetch isolation, separate BIOS history, unsupported stores/data regions, nominal device progression, and DMA diagnostics also have checks.
|
||||
The pinned public ARM ROM now reaches its completion checkpoint with r12 = 0 in debug and release builds.
|
||||
|
|
@ -233,7 +236,7 @@ They cover all processor modes, load widths, lanes, rotation, both Thumb code al
|
|||
They verify direct reads while executing inside BIOS, re-entry, SWI/IRQ returns, host-inspection isolation, and original-boot provenance.
|
||||
Thumb tests cover sequential snapshots, instruction-state transitions, SWI entry, and RAM-backed POP exits.
|
||||
Unknown history, last valid fetch words, missing lookahead, and diagnostic-step retention have explicit checks.
|
||||
Unknown Thumb IWRAM history remains diagnostic. Separate tests cover known sequential history, general Thumb snapshots, and retained BIOS history.
|
||||
Unknown Thumb IWRAM history remains diagnostic. Separate tests cover sequential/refill history, general Thumb snapshots, and retained BIOS history.
|
||||
Protected loads keep normal nominal costs and advance devices only through machine stepping.
|
||||
Firmware readback regressions separately verify documented boot, SoftReset, returning SWI, IRQ callback, and IRQ return words.
|
||||
They execute ARM/Thumb loads across widths and lanes, check caller state, and trace actual exits to their image data.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue