feat(cpu): retain IWRAM bus history across execution states
This commit is contained in:
parent
e630d7a0ea
commit
f2c532f8f6
24 changed files with 522 additions and 359 deletions
|
|
@ -238,19 +238,16 @@ impl Cpu {
|
|||
instruction,
|
||||
continuation,
|
||||
lookahead,
|
||||
fill,
|
||||
} = fetched;
|
||||
// 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 instruction_set = self.instruction_set;
|
||||
let refill = self.instruction_refills(instruction);
|
||||
let sequential_thumb = self.instruction_set == InstructionSet::Thumb && !refill;
|
||||
memory.begin_cpu_access(pc, self.instruction_set, &lookahead);
|
||||
memory.begin_cpu_access(pc, self.instruction_set, fill.as_ref(), &lookahead);
|
||||
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);
|
||||
memory.end_cpu_access(result.is_ok());
|
||||
if result.is_ok() {
|
||||
if refill {
|
||||
self.refill_pipeline(memory);
|
||||
|
|
|
|||
|
|
@ -205,8 +205,8 @@ fn failed_dma_and_failed_resumed_instruction_preserve_last_completed_dma_history
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn partial_continuation_can_gain_known_lanes_but_cold_direct_entry_stays_unknown() {
|
||||
let (mut cpu, mut bus) = program(CODE, 0x46c0); // NOP fills only the low lane.
|
||||
fn dma_updates_retained_lanes_but_a_cold_pipeline_fill_drives_them_again() {
|
||||
let (mut cpu, mut bus) = program(CODE, 0x46c0); // Cold NOP fills both lanes.
|
||||
cpu.step(&mut bus).unwrap();
|
||||
bus.write32(EWRAM, DATA).unwrap();
|
||||
configure(&mut bus, 3, EWRAM, IWRAM, 1, true);
|
||||
|
|
@ -214,19 +214,17 @@ fn partial_continuation_can_gain_known_lanes_but_cold_direct_entry_stays_unknown
|
|||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.registers[6], resumed(DATA.to_le_bytes(), CODE));
|
||||
|
||||
// No executed instruction or refill established an expected continuation PC.
|
||||
// DMA establishes the latch, but the later cold fill drives both instruction lanes.
|
||||
let (mut cpu, mut bus) = program(CODE, PROBE);
|
||||
bus.write32(EWRAM, DATA).unwrap();
|
||||
configure(&mut bus, 3, EWRAM, IWRAM, 1, true);
|
||||
bus.step_dma().unwrap().unwrap();
|
||||
assert_eq!(
|
||||
cpu.step(&mut bus),
|
||||
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||
);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.registers[6], 0x6806_4321);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn irq_and_pc_discontinuity_do_not_reuse_dma_updated_continuations() {
|
||||
fn irq_preserves_dma_history_while_pc_discontinuity_performs_a_new_fill() {
|
||||
for irq in [false, true] {
|
||||
let mut machine = ready(CODE);
|
||||
machine.memory_mut().write32(EWRAM, DATA).unwrap();
|
||||
|
|
@ -245,9 +243,14 @@ fn irq_and_pc_discontinuity_do_not_reuse_dma_updated_continuations() {
|
|||
cpu.registers[15] = CODE + 0x100;
|
||||
machine.memory_mut().write16(cpu.pc(), PROBE).unwrap();
|
||||
}
|
||||
cpu.step(machine.memory_mut()).unwrap();
|
||||
assert_eq!(
|
||||
cpu.step(machine.memory_mut()),
|
||||
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||
cpu.registers[6],
|
||||
if irq {
|
||||
resumed(DATA.to_le_bytes(), CODE)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -257,7 +260,7 @@ fn arm_and_other_thumb_regions_keep_their_fetch_snapshot_not_dma_data() {
|
|||
for thumb in [false, true] {
|
||||
for pc in [0x0200_0100, 0x0300_0100, 0x0700_0100] {
|
||||
if thumb && pc >> 24 == 3 {
|
||||
continue; // The bounded IWRAM continuation is tested separately.
|
||||
continue; // Persistent IWRAM lanes are tested separately.
|
||||
}
|
||||
let mut bus = Memory::new(vec![]).unwrap();
|
||||
let mut cpu = Cpu::new(pc);
|
||||
|
|
|
|||
|
|
@ -249,17 +249,18 @@ fn block_transfers_leave_the_last_iwram_word_and_keep_writeback() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn cold_history_is_unknown_but_successful_fetches_or_word_accesses_can_establish_it() {
|
||||
fn cold_pipeline_fills_establish_history_before_the_first_thumb_data_read() {
|
||||
for pc in [CODE, CODE + 2] {
|
||||
let (mut cpu, mut bus) = program(pc, &[PROBE, PROBE, 0x1111, 0x2222]);
|
||||
let before = cpu.clone();
|
||||
assert_eq!(
|
||||
cpu.step(&mut bus),
|
||||
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||
);
|
||||
assert_eq!(cpu, before);
|
||||
bus.write16(pc, NOP).unwrap();
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(
|
||||
cpu.registers[6],
|
||||
if pc & 2 == 0 {
|
||||
0x6806_1111
|
||||
} else {
|
||||
0x1111_6806
|
||||
}
|
||||
);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(
|
||||
cpu.registers[6],
|
||||
|
|
@ -288,7 +289,12 @@ fn failed_steps_discard_prefetch_history_before_retry() {
|
|||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(
|
||||
cpu.registers[6],
|
||||
drive(DATA, pc + 6, &0x9bcd_u16.to_le_bytes())
|
||||
// Debugger invalidation starts a fresh fill: PC+2 is now also fetched.
|
||||
if pc & 2 == 0 {
|
||||
0x9bcd_1111
|
||||
} else {
|
||||
0x1111_9bcd
|
||||
}
|
||||
);
|
||||
assert_eq!(bus.cycles(), 0);
|
||||
}
|
||||
|
|
@ -319,23 +325,19 @@ fn taken_control_flow_refills_history_even_when_target_is_fallthrough() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn pc_discontinuities_and_non_iwram_execution_do_not_reuse_old_history() {
|
||||
fn pc_discontinuities_fill_from_actual_target_bytes_not_stale_instruction_slots() {
|
||||
let (mut cpu, mut bus) = program(CODE, &[SEED, PROBE, 0x1111, 0x2222]);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
bus.write16(CODE + 0x100, PROBE).unwrap();
|
||||
cpu.registers[15] = CODE + 0x100;
|
||||
assert_eq!(
|
||||
cpu.step(&mut bus),
|
||||
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||
);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.registers[6], 0); // New target's next two halfwords are zero.
|
||||
bus.write16(0x0200_0000, NOP).unwrap();
|
||||
cpu.registers[15] = 0x0200_0000;
|
||||
cpu.step(&mut bus).unwrap();
|
||||
cpu.registers[15] = CODE + 2;
|
||||
assert_eq!(
|
||||
cpu.step(&mut bus),
|
||||
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||
);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.registers[6], 0x2222_1111);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -399,7 +401,7 @@ fn history_updates_add_no_data_accesses_or_device_cycles() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn accepted_irq_invalidates_history_before_any_handler_instruction() {
|
||||
fn accepted_irq_preserves_local_history_before_any_handler_iwram_access() {
|
||||
let (cpu, bus) = program(CODE, &[SEED, PROBE, 0x1111, 0x9bcd]);
|
||||
let mut machine = Machine::new(cpu, bus);
|
||||
machine.step().unwrap();
|
||||
|
|
@ -413,8 +415,6 @@ fn accepted_irq_invalidates_history_before_any_handler_instruction() {
|
|||
machine.memory_mut().advance_cycles(1);
|
||||
assert_eq!(machine.step().unwrap(), StepKind::IrqEntry);
|
||||
// Probe the paused instruction with a separate CPU, without executing BIOS.
|
||||
assert_eq!(
|
||||
interrupted.step(machine.memory_mut()),
|
||||
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||
);
|
||||
interrupted.step(machine.memory_mut()).unwrap();
|
||||
assert_eq!(interrupted.registers[6], 0x9bcd_80a5);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,16 +319,14 @@ fn failed_refill_instructions_preserve_cpu_and_preexisting_history() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn cold_entry_region_crossings_and_unmapped_targets_do_not_invent_refill_history() {
|
||||
fn cold_fill_establishes_lanes_but_region_crossings_and_unmapped_targets_stay_diagnostic() {
|
||||
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)))
|
||||
);
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(cpu.registers[6], expected(TARGET));
|
||||
|
||||
for destination in [0x03ff_fffc, 0x03ff_fffe, 0x0e00_0000] {
|
||||
let mut bus = Memory::new(0xe12f_ff14_u32.to_le_bytes().to_vec()).unwrap();
|
||||
|
|
|
|||
|
|
@ -247,12 +247,8 @@ fn cold_iwram_thumb_reads_and_unused_fetches_do_not_reuse_arm_context() {
|
|||
bus.write16(0x0300_0100, 0x6801).unwrap(); // IWRAM Thumb LDR r1,[r0]
|
||||
cpu.registers[15] = 0x0300_0100;
|
||||
cpu.instruction_set = InstructionSet::Thumb;
|
||||
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[1], 0); // Actual cold-fill lanes, not the prior ARM snapshot.
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ pub(super) struct Fetched {
|
|||
pub instruction: u32,
|
||||
pub continuation: Pipeline,
|
||||
pub lookahead: InstructionFetch,
|
||||
pub fill: Option<[InstructionFetch; 2]>,
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
|
|
@ -33,17 +34,17 @@ impl Cpu {
|
|||
.pipeline
|
||||
.as_ref()
|
||||
.filter(|pipe| pipe.pc == pc && pipe.instruction_set == instruction_set);
|
||||
let instruction = match retained {
|
||||
Some(pipe) => pipe.instructions[0].clone(),
|
||||
None => memory.fetch_instruction(pc, instruction_set).instruction,
|
||||
}?;
|
||||
let decode = match retained {
|
||||
Some(pipe) => pipe.instructions[1].clone(),
|
||||
None => {
|
||||
memory
|
||||
.fetch_instruction(pc.wrapping_add(width), instruction_set)
|
||||
.instruction
|
||||
}
|
||||
let (instruction, decode, fill) = if let Some(pipe) = retained {
|
||||
(
|
||||
pipe.instructions[0].clone()?,
|
||||
pipe.instructions[1].clone(),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
let current = memory.fetch_instruction(pc, instruction_set);
|
||||
let instruction = current.instruction.clone()?;
|
||||
let next = memory.fetch_instruction(pc.wrapping_add(width), instruction_set);
|
||||
(instruction, next.instruction.clone(), Some([current, next]))
|
||||
};
|
||||
let fetched = memory.fetch_instruction(pc.wrapping_add(2 * width), instruction_set);
|
||||
Ok(Fetched {
|
||||
|
|
@ -54,6 +55,7 @@ impl Cpu {
|
|||
instructions: [decode, fetched.instruction.clone()],
|
||||
},
|
||||
lookahead: fetched,
|
||||
fill,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -242,20 +242,18 @@ fn failed_thumb_steps_keep_previous_history_and_clear_access_context() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn outside_thumb_execution_keeps_bios_history_without_seeding_iwram_history() {
|
||||
fn outside_thumb_execution_keeps_bios_history_separate_from_cold_iwram_fills() {
|
||||
let mut memory = bus(0x6801, thumb_image(THUMB_ENTRY, BX_R5));
|
||||
let mut cpu = enter_thumb(&mut memory, THUMB_ENTRY, true);
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(protected_word(&mut memory), OTHER);
|
||||
memory.write16(0x0300_0100, 0x6801).unwrap(); // No preceding sequential IWRAM history.
|
||||
memory.write16(0x0300_0100, 0x6801).unwrap(); // Cold IWRAM entry.
|
||||
memory.write16(0x0300_0102, 0x6801).unwrap(); // Next protected BIOS read.
|
||||
memory.write16(0x0300_0104, 0x1357).unwrap();
|
||||
cpu.registers[15] = 0x0300_0100;
|
||||
cpu.registers[0] = 0x4000;
|
||||
let before = cpu.clone();
|
||||
assert_eq!(
|
||||
cpu.step(&mut memory),
|
||||
Err(CpuError::Memory(MemoryError::Unmapped(0x4000)))
|
||||
);
|
||||
assert_eq!(cpu, before);
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(cpu.registers[1], 0x6801_1357);
|
||||
assert_eq!(memory.read32(0x4000), Err(MemoryError::Unmapped(0x4000)));
|
||||
assert_eq!(protected_word(&mut memory), OTHER);
|
||||
cpu.registers[0] = 0;
|
||||
|
|
|
|||
|
|
@ -294,12 +294,21 @@ fn short_rom_needs_only_the_fetched_halfword_and_errors_only_when_used() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn cold_iwram_history_and_region_crossing_fetches_remain_diagnostics() {
|
||||
fn cold_iwram_fills_establish_lanes_but_region_crossing_fetches_remain_diagnostics() {
|
||||
for base in [0x0300_0100, 0x0300_8100, 0x02ff_fffc, 0x05ff_fffc] {
|
||||
for offset in [0, 2] {
|
||||
let pc = base + offset;
|
||||
let mut bus = program(pc, LDR);
|
||||
let mut cpu = cpu(pc);
|
||||
if pc >> 24 == 3 {
|
||||
cpu.step(&mut bus).unwrap();
|
||||
assert_eq!(
|
||||
cpu.registers[1],
|
||||
if offset == 0 { 0x46c0_80a5 } else { DATA }
|
||||
);
|
||||
assert_eq!(bus.read32(UNUSED), Err(MemoryError::Unmapped(UNUSED)));
|
||||
continue;
|
||||
}
|
||||
let before = cpu.clone();
|
||||
assert_eq!(
|
||||
cpu.step(&mut bus),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ mod fetch;
|
|||
#[cfg(test)]
|
||||
mod fetch_tests;
|
||||
mod iwram_bus;
|
||||
#[cfg(test)]
|
||||
mod iwram_history_tests;
|
||||
pub(crate) use fetch::InstructionFetch;
|
||||
use iwram_bus::IwramBus;
|
||||
|
||||
|
|
@ -69,9 +71,9 @@ struct CpuAccess {
|
|||
/// Work/video/palette RAM, OAM, cartridge ROM, optional BIOS, and supported I/O.
|
||||
/// 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.
|
||||
/// Successful refills into Thumb IWRAM sample the target pair to establish history.
|
||||
/// DMA accesses drive existing Thumb IWRAM continuation lanes before the next fetch.
|
||||
/// ARM/Thumb IWRAM fetches and data accesses retain local bus lanes transactionally.
|
||||
/// Cold fills and target pairs drive the same latch, independently of executing region.
|
||||
/// Successful DMA accesses drive local lanes even before the first CPU instruction.
|
||||
/// Channel data remains separate; general and sub-instruction CPU bus handoff is unmodeled.
|
||||
/// Instruction buffering and supported bus history consume shared fetch samples.
|
||||
/// Complete bus ownership and per-access timing remain unmodeled.
|
||||
|
|
@ -374,19 +376,27 @@ impl Memory {
|
|||
&mut self,
|
||||
pc: u32,
|
||||
instruction_set: InstructionSet,
|
||||
fill: Option<&[InstructionFetch; 2]>,
|
||||
fetch: &InstructionFetch,
|
||||
) {
|
||||
debug_assert!(self.cpu_access.is_none());
|
||||
debug_assert_eq!(fetch.state, instruction_set);
|
||||
debug_assert_eq!(fetch.address, pc.wrapping_add(2 * instruction_set.width()));
|
||||
// Consume the pipeline's new fetch, never a retained opcode or a second read.
|
||||
// Missing lookahead must not fail an instruction that never uses it.
|
||||
// Cold fills drive current/decode fetches; retained instructions never replay them.
|
||||
// Stage the new lookahead last, before data accesses and the entry snapshot.
|
||||
self.iwram_bus.begin();
|
||||
if let Some(fill) = fill {
|
||||
for (index, sample) in fill.iter().enumerate() {
|
||||
debug_assert_eq!(sample.state, instruction_set);
|
||||
debug_assert_eq!(
|
||||
sample.address,
|
||||
pc.wrapping_add(index as u32 * instruction_set.width())
|
||||
);
|
||||
self.drive_iwram_fetch(sample);
|
||||
}
|
||||
}
|
||||
self.drive_iwram_fetch(fetch);
|
||||
let same_region = fetch.address >> 24 == pc >> 24;
|
||||
let iwram_pc = (instruction_set == InstructionSet::Thumb && pc >> 24 == 3).then_some(pc);
|
||||
let fetched = same_region
|
||||
.then(|| fetch.instruction.as_ref().ok().map(|value| *value as u16))
|
||||
.flatten();
|
||||
self.iwram_bus.begin(iwram_pc, fetched);
|
||||
let prefetch = match instruction_set {
|
||||
InstructionSet::Arm => fetch.bus_word,
|
||||
InstructionSet::Thumb if !same_region => None,
|
||||
|
|
@ -396,12 +406,12 @@ impl Memory {
|
|||
self.cpu_access = Some(CpuAccess { pc, prefetch });
|
||||
}
|
||||
|
||||
pub(crate) fn end_cpu_access(&mut self, succeeded: bool, sequential: bool) {
|
||||
pub(crate) fn end_cpu_access(&mut self, succeeded: bool) {
|
||||
if succeeded {
|
||||
// Timed and untimed CPU execution both consume a resume. Errors do not.
|
||||
self.cpu_resume_nonsequential = false;
|
||||
}
|
||||
self.iwram_bus.finish(succeeded, sequential);
|
||||
self.iwram_bus.finish(succeeded);
|
||||
if let Some(access) = self.cpu_access.take() {
|
||||
if succeeded && access.pc < BIOS_SIZE as u32 {
|
||||
// Commit only successful instruction snapshots under our diagnostic policy.
|
||||
|
|
@ -411,25 +421,30 @@ impl Memory {
|
|||
}
|
||||
}
|
||||
|
||||
/// Consume the same target samples that fill the CPU's instruction buffer.
|
||||
/// Incomplete or unsupported refills stay unknown without failing the branch early.
|
||||
fn drive_iwram_fetch(&self, fetch: &InstructionFetch) {
|
||||
if let Ok(value) = fetch.instruction {
|
||||
let width = match fetch.state {
|
||||
InstructionSet::Arm => AccessWidth::Word,
|
||||
InstructionSet::Thumb => AccessWidth::Halfword,
|
||||
};
|
||||
self.iwram_bus.access(fetch.address, width, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Successful IWRAM target fetches drive their lanes in order, in either CPU state.
|
||||
/// Other-region and unavailable target fetches leave the local latch unchanged.
|
||||
pub(crate) fn refill_cpu_bus_history(&mut self, fetches: &[InstructionFetch; 2]) {
|
||||
self.iwram_bus.invalidate();
|
||||
self.iwram_bus.begin();
|
||||
let [first, second] = fetches;
|
||||
debug_assert_eq!(first.state, second.state);
|
||||
debug_assert_eq!(
|
||||
second.address,
|
||||
first.address.wrapping_add(first.state.width())
|
||||
);
|
||||
if first.state == InstructionSet::Thumb
|
||||
&& first.address >> 24 == 3
|
||||
&& second.address >> 24 == 3
|
||||
{
|
||||
if let (Ok(first_value), Ok(second_value)) = (&first.instruction, &second.instruction) {
|
||||
self.iwram_bus
|
||||
.refill(first.address, [*first_value as u16, *second_value as u16]);
|
||||
}
|
||||
for fetch in fetches {
|
||||
self.drive_iwram_fetch(fetch);
|
||||
}
|
||||
self.iwram_bus.finish(true);
|
||||
}
|
||||
|
||||
fn can_write_power_control(&self) -> bool {
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ fn refill_history_consumes_captured_target_halfwords_without_rereading_memory()
|
|||
memory.write16(target + 2, 0xffff).unwrap();
|
||||
memory.refill_cpu_bus_history(&fetches);
|
||||
let lookahead = memory.fetch_instruction(target + 4, InstructionSet::Thumb);
|
||||
memory.begin_cpu_access(target, InstructionSet::Thumb, &lookahead);
|
||||
memory.begin_cpu_access(target, InstructionSet::Thumb, None, &lookahead);
|
||||
assert_eq!(
|
||||
memory.read32(UNUSED).unwrap(),
|
||||
if target & 2 == 0 {
|
||||
|
|
@ -100,7 +100,7 @@ fn refill_history_consumes_captured_target_halfwords_without_rereading_memory()
|
|||
0x5678_9abc
|
||||
}
|
||||
);
|
||||
memory.end_cpu_access(true, true);
|
||||
memory.end_cpu_access(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,11 +119,11 @@ fn bios_history_commits_the_captured_bus_word_not_later_image_bytes() {
|
|||
// Test-only image mutation: BIOS remains read-only through normal APIs.
|
||||
memory.bios.as_mut().unwrap()[word_address..word_address + 4]
|
||||
.copy_from_slice(&CHANGED.to_le_bytes());
|
||||
memory.begin_cpu_access(pc, state, &fetch);
|
||||
memory.end_cpu_access(true, false);
|
||||
memory.begin_cpu_access(pc, state, None, &fetch);
|
||||
memory.end_cpu_access(true);
|
||||
assert_eq!(memory.bios_prefetch, Some(WORD));
|
||||
let outside = memory.fetch_instruction(0x0200_0008, InstructionSet::Arm);
|
||||
memory.begin_cpu_access(0x0200_0000, InstructionSet::Arm, &outside);
|
||||
memory.begin_cpu_access(0x0200_0000, InstructionSet::Arm, None, &outside);
|
||||
assert_eq!(memory.read32(word_address as u32).unwrap(), WORD);
|
||||
// Instruction reads still bypass the protected data value.
|
||||
assert_eq!(
|
||||
|
|
@ -132,7 +132,7 @@ fn bios_history_commits_the_captured_bus_word_not_later_image_bytes() {
|
|||
.instruction,
|
||||
Ok(CHANGED)
|
||||
);
|
||||
memory.end_cpu_access(true, false);
|
||||
memory.end_cpu_access(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -149,12 +149,12 @@ fn sampling_has_no_timing_or_history_side_effects_and_never_uses_data_fallback()
|
|||
assert_eq!(memory.bios_prefetch, None);
|
||||
assert!(memory.cpu_resume_nonsequential);
|
||||
assert_eq!(memory.cycles(), 0);
|
||||
memory.begin_cpu_access(0, InstructionSet::Arm, &fetch);
|
||||
memory.begin_cpu_access(0, InstructionSet::Arm, None, &fetch);
|
||||
assert_eq!(memory.read32(UNUSED).unwrap(), WORD);
|
||||
let missing = memory.fetch_instruction(UNUSED, InstructionSet::Arm);
|
||||
assert_eq!(missing.instruction, Err(MemoryError::Unmapped(UNUSED)));
|
||||
assert_eq!(missing.bus_word, None);
|
||||
memory.end_cpu_access(false, false);
|
||||
memory.end_cpu_access(false);
|
||||
assert_eq!(memory.bios_prefetch, None);
|
||||
assert!(memory.cpu_resume_nonsequential);
|
||||
}
|
||||
|
|
@ -167,12 +167,12 @@ fn missing_fetch_observation_invalidates_bios_history_only_on_success() {
|
|||
let pc = BIOS_SIZE as u32 - state.width();
|
||||
let fetch = memory.fetch_instruction(pc + 2 * state.width(), state);
|
||||
assert!(fetch.instruction.is_err());
|
||||
memory.begin_cpu_access(pc, state, &fetch);
|
||||
memory.end_cpu_access(false, false);
|
||||
memory.begin_cpu_access(pc, state, None, &fetch);
|
||||
memory.end_cpu_access(false);
|
||||
assert_eq!(memory.bios_prefetch, Some(WORD));
|
||||
}
|
||||
let fetch = memory.fetch_instruction(BIOS_SIZE as u32, InstructionSet::Thumb);
|
||||
memory.begin_cpu_access(BIOS_SIZE as u32 - 4, InstructionSet::Thumb, &fetch);
|
||||
memory.end_cpu_access(true, false);
|
||||
memory.begin_cpu_access(BIOS_SIZE as u32 - 4, InstructionSet::Thumb, None, &fetch);
|
||||
memory.end_cpu_access(true);
|
||||
assert_eq!(memory.bios_prefetch, None);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
//! Transactional lane history for Thumb IWRAM execution and target-pair refills.
|
||||
//! Completed DMA accesses update existing continuation lanes before the next fetch.
|
||||
//! Unknown startup history is not replaced with guessed memory bytes.
|
||||
//! Persistent local IWRAM lanes, independent of CPU state and executing region.
|
||||
//! CPU instructions stage changes; successful DMA units commit accesses in order.
|
||||
use std::cell::Cell;
|
||||
|
||||
use crate::timing::AccessWidth;
|
||||
|
|
@ -13,6 +12,9 @@ struct Lanes {
|
|||
|
||||
impl Lanes {
|
||||
fn drive(&mut self, address: u32, width: AccessWidth, value: u32) {
|
||||
if address >> 24 != 3 {
|
||||
return;
|
||||
}
|
||||
debug_assert_eq!(address & (width.bytes() - 1), 0);
|
||||
let mask = match width {
|
||||
AccessWidth::Byte => 0xff,
|
||||
|
|
@ -30,93 +32,47 @@ impl Lanes {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct History {
|
||||
next_pc: u32,
|
||||
lanes: Lanes,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct IwramBus {
|
||||
committed: Option<History>,
|
||||
committed: Lanes,
|
||||
// Reads use &Memory. Stage their lane changes without changing committed history.
|
||||
pending: Cell<Option<History>>,
|
||||
pending: Cell<Option<Lanes>>,
|
||||
}
|
||||
|
||||
impl IwramBus {
|
||||
pub(super) fn begin(&mut self, pc: Option<u32>, fetched: Option<u16>) {
|
||||
#[cfg(test)]
|
||||
pub(super) fn committed_word(&self) -> Option<u32> {
|
||||
self.committed.word()
|
||||
}
|
||||
|
||||
pub(super) fn begin(&mut self) {
|
||||
debug_assert!(self.pending.get().is_none());
|
||||
self.pending.set(pc.map(|pc| {
|
||||
let mut lanes = self
|
||||
.committed
|
||||
.filter(|history| history.next_pc == pc)
|
||||
.map_or_else(Lanes::default, |history| history.lanes);
|
||||
if let Some(half) = fetched {
|
||||
lanes.drive(pc + 4, AccessWidth::Halfword, u32::from(half));
|
||||
} else {
|
||||
lanes = Lanes::default(); // Unavailable or region-crossing prefetch.
|
||||
}
|
||||
History {
|
||||
next_pc: pc.wrapping_add(2),
|
||||
lanes,
|
||||
}
|
||||
}));
|
||||
self.pending.set(Some(self.committed));
|
||||
}
|
||||
|
||||
pub(super) fn snapshot(&self) -> Option<u32> {
|
||||
self.pending.get().and_then(|history| history.lanes.word())
|
||||
self.pending.get().and_then(Lanes::word)
|
||||
}
|
||||
|
||||
pub(super) fn access(&self, address: u32, width: AccessWidth, value: u32) {
|
||||
if address >> 24 != 3 {
|
||||
return;
|
||||
}
|
||||
if let Some(mut history) = self.pending.get() {
|
||||
history.lanes.drive(address, width, value);
|
||||
self.pending.set(Some(history));
|
||||
if let Some(mut lanes) = self.pending.get() {
|
||||
lanes.drive(address, width, value);
|
||||
self.pending.set(Some(lanes));
|
||||
}
|
||||
}
|
||||
|
||||
/// DMA runs between instructions. Commit only after the entire unit succeeds.
|
||||
/// Keep the expected CPU continuation PC; do not invent one at cold startup.
|
||||
/// DMA runs between instructions. Call only after the entire unit succeeds.
|
||||
pub(super) fn dma_access(&mut self, address: u32, width: AccessWidth, value: u32) {
|
||||
debug_assert!(self.pending.get().is_none());
|
||||
if address >> 24 == 3 {
|
||||
if let Some(history) = &mut self.committed {
|
||||
history.lanes.drive(address, width, value);
|
||||
}
|
||||
}
|
||||
self.committed.drive(address, width, value);
|
||||
}
|
||||
|
||||
pub(super) fn finish(&mut self, succeeded: bool, sequential: bool) {
|
||||
pub(super) fn finish(&mut self, succeeded: bool) {
|
||||
let pending = self.pending.take();
|
||||
if succeeded {
|
||||
self.committed = if sequential {
|
||||
pending.filter(|history| history.next_pc >> 24 == 3)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.committed = pending.expect("IWRAM transaction must have started");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -126,31 +82,36 @@ mod tests {
|
|||
#[test]
|
||||
fn incomplete_lanes_stay_unknown_until_all_bytes_have_been_driven() {
|
||||
let mut bus = IwramBus::default();
|
||||
bus.begin(Some(0x0300_0100), Some(0x1234));
|
||||
bus.begin();
|
||||
bus.access(0x0300_0104, AccessWidth::Halfword, 0x1234);
|
||||
assert_eq!(bus.snapshot(), None);
|
||||
bus.access(0x0300_2002, AccessWidth::Byte, 0x56);
|
||||
assert_eq!(bus.snapshot(), None);
|
||||
bus.access(0x0200_2003, AccessWidth::Byte, 0xff); // Other memory cannot fill a lane.
|
||||
bus.access(0x0200_2003, AccessWidth::Byte, 0xff);
|
||||
assert_eq!(bus.snapshot(), None);
|
||||
bus.access(0x0300_2003, AccessWidth::Byte, 0x78);
|
||||
assert_eq!(bus.snapshot(), Some(0x7856_1234));
|
||||
bus.finish(true, true);
|
||||
bus.begin(Some(0x0300_0102), Some(0xabcd));
|
||||
bus.finish(true);
|
||||
bus.begin();
|
||||
bus.access(0x0300_0106, AccessWidth::Halfword, 0xabcd);
|
||||
assert_eq!(bus.snapshot(), Some(0xabcd_1234));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_transaction_discards_all_staged_accesses_not_only_prefetch() {
|
||||
let mut bus = IwramBus::default();
|
||||
bus.begin(Some(0x0300_0100), Some(0x1234));
|
||||
bus.begin();
|
||||
bus.access(0x0300_0104, AccessWidth::Halfword, 0x1234);
|
||||
bus.access(0x0300_2000, AccessWidth::Word, 0x1122_3344);
|
||||
bus.finish(true, true);
|
||||
bus.begin(Some(0x0300_0102), Some(0xabcd));
|
||||
bus.finish(true);
|
||||
bus.begin();
|
||||
bus.access(0x0300_0106, AccessWidth::Halfword, 0xabcd);
|
||||
bus.access(0x0300_2000, AccessWidth::Word, 0xdead_beef);
|
||||
bus.access(0x0300_2001, AccessWidth::Byte, 0x55);
|
||||
assert_eq!(bus.snapshot(), Some(0xdead_55ef));
|
||||
bus.finish(false, false);
|
||||
bus.begin(Some(0x0300_0102), Some(0x5678));
|
||||
bus.finish(false);
|
||||
bus.begin();
|
||||
bus.access(0x0300_0106, AccessWidth::Halfword, 0x5678);
|
||||
assert_eq!(bus.snapshot(), Some(0x5678_3344));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
192
crates/gba-core/src/memory/iwram_history_tests.rs
Normal file
192
crates/gba-core/src/memory/iwram_history_tests.rs
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
//! Original local-bus tests. Expected lanes are independent of the CPU instruction buffer.
|
||||
use super::*;
|
||||
use crate::{
|
||||
cpu::Cpu,
|
||||
dma::DMA_STRIDE,
|
||||
io::{IE, IME, TIMER_BASE},
|
||||
machine::{Machine, StepKind},
|
||||
};
|
||||
|
||||
const CODE: u32 = 0x0200_0100;
|
||||
const IWRAM: u32 = 0x0300_0000;
|
||||
const WORD: u32 = 0x9abc_1234;
|
||||
const NOP: u32 = 0xe1a0_0000;
|
||||
|
||||
fn arm(code: &[u32]) -> (Cpu, Memory) {
|
||||
let mut memory = Memory::new(vec![]).unwrap();
|
||||
for (i, word) in code.iter().enumerate() {
|
||||
memory.write32(CODE + i as u32 * 4, *word).unwrap();
|
||||
}
|
||||
memory.write32(IWRAM, WORD).unwrap();
|
||||
(Cpu::new(CODE), memory)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arm_data_accesses_from_other_code_regions_drive_only_addressed_iwram_lanes() {
|
||||
for (instruction, expected) in [
|
||||
(0xe5d0_1000, None), // LDRB low byte, not a complete word.
|
||||
(0xe1d0_10b2, None), // LDRH high half, not a complete word.
|
||||
(0xe590_1000, Some(WORD)), // LDR word.
|
||||
] {
|
||||
let (mut cpu, mut memory) = arm(&[0xe3a0_0403, instruction, NOP, NOP]); // MOV r0,#0x03000000.
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(memory.iwram_bus.committed_word(), None);
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(memory.iwram_bus.committed_word(), expected);
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(memory.iwram_bus.committed_word(), expected); // EWRAM code does not erase the local latch.
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arm_byte_and_halfword_reads_and_stores_preserve_other_lanes_across_nonlocal_code() {
|
||||
for (instruction, expected) in [
|
||||
(0xe5d0_1001, 0x9abc_5634), // LDRB r1,[r0,#1].
|
||||
(0xe1d0_10b2, 0x5678_1234), // LDRH r1,[r0,#2].
|
||||
(0xe5c0_2001, 0x9abc_0034), // STRB r2,[r0,#1], r2=0.
|
||||
(0xe1c0_20b2, 0x0000_1234), // STRH r2,[r0,#2].
|
||||
] {
|
||||
let (mut cpu, mut memory) = arm(&[0xe3a0_0403, 0xe590_1000, NOP, instruction, NOP, NOP]);
|
||||
cpu.step(&mut memory).unwrap();
|
||||
cpu.step(&mut memory).unwrap();
|
||||
memory.write32(IWRAM, 0x5678_5678).unwrap(); // Host writes do not drive the latch.
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(WORD));
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(expected));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arm_and_thumb_target_pairs_drive_the_same_local_bus_in_fetch_order() {
|
||||
// EWRAM ARM -> IWRAM ARM -> IWRAM Thumb -> unavailable BIOS ARM.
|
||||
let (mut cpu, mut memory) = arm(&[0xe3a0_0403, 0xe12f_ff10, NOP, NOP]);
|
||||
memory.write32(IWRAM, 0xe280_1011).unwrap(); // ADD r1,r0,#0x11.
|
||||
memory.write32(IWRAM + 4, 0xe12f_ff11).unwrap(); // BX r1.
|
||||
memory.write32(IWRAM + 8, WORD).unwrap();
|
||||
memory.write32(IWRAM + 12, 0x8765_4321).unwrap();
|
||||
memory.write16(IWRAM + 16, 0x46c0).unwrap(); // Thumb NOP.
|
||||
memory.write16(IWRAM + 18, 0x4710).unwrap(); // BX r2 (zero -> BIOS; target error deferred).
|
||||
memory.write32(IWRAM + 20, 0x5678_1234).unwrap();
|
||||
cpu.step(&mut memory).unwrap();
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(0xe12f_ff11));
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(WORD));
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(cpu.instruction_set(), InstructionSet::Thumb);
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(0x4710_46c0));
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(0x4710_1234));
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(cpu.instruction_set(), InstructionSet::Arm);
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(0x5678_1234));
|
||||
let before = cpu.clone();
|
||||
assert!(cpu.step(&mut memory).is_err());
|
||||
assert_eq!(cpu, before);
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(0x5678_1234));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thumb_data_access_from_ewram_drives_local_iwram_history() {
|
||||
let (mut cpu, mut memory) = arm(&[
|
||||
0xe3a0_0403, // MOV r0,#0x03000000.
|
||||
0xe28f_1001, // ADD r1,pc,#1 -> CODE+13.
|
||||
0xe12f_ff11, // BX r1.
|
||||
0x46c0_6802, // Thumb LDR r2,[r0]; NOP.
|
||||
0x46c0_46c0,
|
||||
]);
|
||||
for _ in 0..3 {
|
||||
cpu.step(&mut memory).unwrap();
|
||||
}
|
||||
assert_eq!(cpu.instruction_set(), InstructionSet::Thumb);
|
||||
assert_eq!(memory.iwram_bus.committed_word(), None);
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(cpu.registers()[2], WORD);
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(WORD));
|
||||
memory.write32(IWRAM, 0).unwrap();
|
||||
cpu.step(&mut memory).unwrap();
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(WORD));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dma_can_establish_lanes_before_any_cpu_instruction() {
|
||||
for word in [false, true] {
|
||||
let (_, mut memory) = arm(&[]);
|
||||
let dma = DMA_BASE + 3 * DMA_STRIDE;
|
||||
memory.write32(dma, IWRAM).unwrap();
|
||||
memory.write32(dma + 4, CODE).unwrap();
|
||||
memory
|
||||
.write32(dma + 8, if word { 0x8400_0001 } else { 0x8000_0002 })
|
||||
.unwrap();
|
||||
memory.step_dma().unwrap().unwrap();
|
||||
assert_eq!(
|
||||
memory.iwram_bus.committed_word(),
|
||||
if word { Some(WORD) } else { None }
|
||||
);
|
||||
if !word {
|
||||
memory.step_dma().unwrap().unwrap();
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(WORD));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepted_irq_and_bios_fetches_preserve_local_lanes_until_actual_iwram_access() {
|
||||
let mut bios = vec![0; BIOS_SIZE];
|
||||
bios[0x18..0x1c].copy_from_slice(&NOP.to_le_bytes());
|
||||
bios[0x1c..0x20].copy_from_slice(&0xe590_1000_u32.to_le_bytes()); // LDR r1,[r0].
|
||||
let mut memory = Memory::with_bios(vec![], bios).unwrap();
|
||||
for (i, word) in [0xe3a0_0403, 0xe590_1000, NOP, NOP].into_iter().enumerate() {
|
||||
memory.write32(CODE + i as u32 * 4, word).unwrap();
|
||||
}
|
||||
memory.write32(IWRAM, WORD).unwrap();
|
||||
let mut machine = Machine::new(Cpu::new(CODE), memory);
|
||||
machine.step().unwrap();
|
||||
machine.step().unwrap();
|
||||
assert_eq!(machine.memory().iwram_bus.committed_word(), Some(WORD));
|
||||
machine.memory_mut().write32(IWRAM, 0x8765_4321).unwrap();
|
||||
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);
|
||||
assert_eq!(machine.memory().iwram_bus.committed_word(), Some(WORD));
|
||||
machine.step().unwrap();
|
||||
assert_eq!(machine.memory().iwram_bus.committed_word(), Some(WORD));
|
||||
machine.step().unwrap();
|
||||
assert_eq!(
|
||||
machine.memory().iwram_bus.committed_word(),
|
||||
Some(0x8765_4321)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_instruction_discards_its_iwram_fetch_and_failed_dma_preserves_committed_lanes() {
|
||||
let (mut cpu, mut memory) = arm(&[0xe3a0_0403, 0xe590_1000, 0xe12f_ff10, NOP, NOP]);
|
||||
cpu.step(&mut memory).unwrap();
|
||||
cpu.step(&mut memory).unwrap();
|
||||
memory.write32(IWRAM, 0xe7f0_00f0).unwrap(); // Unsupported ARM instruction.
|
||||
memory.write32(IWRAM + 4, WORD).unwrap();
|
||||
memory.write32(IWRAM + 8, 0x8765_4321).unwrap();
|
||||
cpu.step(&mut memory).unwrap(); // Target pair ends with WORD.
|
||||
let before = cpu.clone();
|
||||
assert!(cpu.step_timed(&mut memory).is_err());
|
||||
assert_eq!(cpu, before);
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(WORD));
|
||||
// A failing cold entry also discards its current/decode/lookahead lane updates.
|
||||
let mut cold = Cpu::new(IWRAM);
|
||||
let before = cold.clone();
|
||||
assert!(cold.step(&mut memory).is_err());
|
||||
assert_eq!(cold, before);
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(WORD));
|
||||
let dma = DMA_BASE + 3 * DMA_STRIDE;
|
||||
memory.write32(dma, IWRAM + 8).unwrap();
|
||||
memory.write32(dma + 4, ROM_START).unwrap(); // Read-only destination.
|
||||
memory.write32(dma + 8, 0x8400_0001).unwrap();
|
||||
assert!(memory.step_dma().is_err());
|
||||
assert_eq!(memory.iwram_bus.committed_word(), Some(WORD));
|
||||
}
|
||||
|
|
@ -30,10 +30,11 @@ The repository is a Cargo workspace with three crates:
|
|||
| `crates/gba-core/src/cpu/bios_access_tests.rs` | Original CPU BIOS protection regressions for retained history, modes, widths, exception returns, and diagnostics |
|
||||
| `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 invalidation, and nominal timing |
|
||||
| `crates/gba-core/src/cpu/iwram_dma_tests.rs` | Bounded DMA continuation lanes, access ordering, channel isolation, diagnostics, and resume snapshots |
|
||||
| `crates/gba-core/src/cpu/iwram_open_bus_tests.rs` | Persistent IWRAM lanes, cold fills, access widths, isolation, errors, refills, IRQ preservation, and nominal timing |
|
||||
| `crates/gba-core/src/cpu/iwram_dma_tests.rs` | DMA lane updates, cold/resumed fetch ordering, channel isolation, diagnostics, and IRQ preservation |
|
||||
| `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/memory/iwram_bus.rs` | Transactional persistent IWRAM lanes for ARM/Thumb fetches, CPU data accesses, and DMA |
|
||||
| `crates/gba-core/src/memory/iwram_history_tests.rs` | Cross-state latch ownership, ARM accesses/refills, nonlocal code, pre-start DMA, IRQ preservation, and rollback |
|
||||
| `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 |
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ Host inspection, ROM-suite memory assertions, instruction fetches, and DMA do no
|
|||
|
||||
Limits remain explicit:
|
||||
|
||||
- Thumb open bus uses the supported region rules below, including bounded sequential internal-RAM lane history.
|
||||
- Thumb open bus uses the supported region rules below, including persistent internal-RAM lane history.
|
||||
- [BIOS-protected reads](bios.md#cpu-bios-read-protection) use separate retained ARM PC+8 or aligned Thumb PC+4 word snapshots.
|
||||
- Unused/write-only I/O reads, general DMA-to-CPU bus handoff, and disabled-RAM reads are not modeled here.
|
||||
[DMA channel data](dma.md#retained-channel-data) is separate and never overrides a CPU snapshot.
|
||||
|
|
@ -144,7 +144,7 @@ Limits remain explicit:
|
|||
- Unused-memory writes remain diagnostics rather than ignored hardware writes. Swaps cannot silently discard their write.
|
||||
- ARM/Thumb instruction buffering is persistent, but these data snapshots remain a separate bounded model.
|
||||
Thumb IWRAM bus-history samples do not share storage with retained instructions.
|
||||
DMA updates existing IWRAM continuation lanes at instruction boundaries, not during CPU accesses.
|
||||
DMA updates IWRAM lanes at instruction boundaries, not during CPU accesses.
|
||||
|
||||
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).
|
||||
|
|
@ -158,7 +158,7 @@ The snapshot depends on the executing code region. P below is the executing inst
|
|||
| --- | --- |
|
||||
| External work RAM, palette RAM, video RAM, all three ROM windows | Halfword at P+4, repeated in both word lanes |
|
||||
| BIOS and object attribute memory (OAM) | Full word at `(P+4) & ~3` |
|
||||
| Internal work RAM (IWRAM) | PC+4 replaces one halfword in known sequential IWRAM history; the other lanes retain their values |
|
||||
| Internal work RAM (IWRAM) | PC+4 replaces one halfword in the persistent IWRAM latch; the other lanes retain their values |
|
||||
| Other code regions | Unsupported |
|
||||
|
||||
For BIOS/OAM, aligned instructions expose halfwords at P+4 and P+6.
|
||||
|
|
@ -184,77 +184,75 @@ Do not substitute P+2 for that history: it is only the usual case, not a general
|
|||
The original Nintendo DS also differs from GBA-family IWRAM behavior; this core targets GBA.
|
||||
|
||||
Bus history remains bounded despite sharing samples with the persistent instruction buffer.
|
||||
Refill timing, general DMA-to-CPU transitions, cross-state IWRAM history, unused/write-only I/O, and disabled RAM remain incomplete.
|
||||
Refill timing, general DMA-to-CPU transitions, unused/write-only I/O, and disabled RAM remain incomplete.
|
||||
Original regressions validate the documented formulas. No physical-hardware or independent public Thumb open-bus pass is claimed.
|
||||
|
||||
### Sequential IWRAM history
|
||||
|
||||
For consecutive Thumb instructions in IWRAM, the emulator retains a separate lane value and known-bit mask.
|
||||
The PC+4 halfword fetch updates its addressed halfword before execution.
|
||||
IWRAM data reads and writes then update only their addressed byte, halfword, or word lanes.
|
||||
The emulator retains a separate IWRAM lane value and known-bit mask, independent of CPU state or executing region.
|
||||
Every successful ARM IWRAM fetch drives a word; every Thumb IWRAM fetch drives its addressed halfword.
|
||||
IWRAM data reads and writes update their addressed byte, halfword, or word lanes, including accesses from non-IWRAM code.
|
||||
The recorded value is raw aligned bus data, before CPU rotation or sign extension.
|
||||
Accesses to other regions do not replace the IWRAM latch.
|
||||
Accesses to other regions, state changes, and exception entry do not erase this local latch.
|
||||
See [the evidence and implementation scope](../research/iwram-bus-history.md).
|
||||
|
||||
History starts unknown. An unused-memory load requires all word lanes to be known, including for byte/halfword loads.
|
||||
Ordinary instructions and mapped loads still execute with incomplete history.
|
||||
Consecutive fetches can establish both halves. An IWRAM word read or write establishes the entire word.
|
||||
Cold pipeline filling samples the current instruction, next instruction, and lookahead in order.
|
||||
A mapped Thumb IWRAM fill therefore establishes both halves before the first data read.
|
||||
A retained pipeline advances only its new lookahead; old instruction slots do not drive the bus again.
|
||||
Debugger invalidation does not erase the latch, but the following cold fill updates it from current mapped bytes.
|
||||
Host inspection and setup writes do not seed or change this emulated history.
|
||||
|
||||
Each instruction stages its fetch and data-access changes.
|
||||
Success commits history only for the next sequential Thumb IWRAM PC. Failure preserves the previous committed history.
|
||||
Each instruction stages all new fetches and data-access changes.
|
||||
Success commits history regardless of the resulting PC, instruction state, or region. Failure preserves the previous committed history.
|
||||
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 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 execution states/regions and accepted machine IRQs invalidate continuation history.
|
||||
Successful DMA units update existing continuation lanes as described below.
|
||||
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.
|
||||
Missing or region-crossing Thumb lookahead still leaves that instruction's unused-memory snapshot unsupported.
|
||||
It does not erase independently known IWRAM lanes.
|
||||
|
||||
### 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.
|
||||
Thumb target fetches occur at T and T+2; ARM target fetches occur at T and T+4.
|
||||
Each successful IWRAM fetch drives its own lanes, in order. Other-region or unavailable fetches do not erase local history.
|
||||
Normal physical RAM mirrors apply.
|
||||
A complete Thumb IWRAM target pair establishes both word lanes; the first target instruction then samples T+4.
|
||||
An ARM IWRAM target pair leaves the second fetched word in the latch.
|
||||
|
||||
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.
|
||||
This applies to BX, taken branches, BL suffixes, PC writes, PC loads, and status-restoring returns.
|
||||
Saved instruction 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.
|
||||
|
||||
Target samples use strict mapped reads and add no nominal data or device cycles.
|
||||
The same captured target pair fills the CPU buffer and updates supported IWRAM history; no second target read occurs.
|
||||
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 updates this continuation's actual IWRAM access lanes before the target+4 sample.
|
||||
The same captured target pair fills the CPU buffer and updates IWRAM history; no second target read occurs.
|
||||
A failed target fetch remains a later instruction diagnostic.
|
||||
IRQ entry samples its ARM BIOS vector pair without erasing IWRAM lanes.
|
||||
A BIOS handler can still change those lanes through actual IWRAM data accesses.
|
||||
|
||||
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, sub-instruction DMA ordering, and exact refill timing remain incomplete.
|
||||
The [refill evidence and scope](../research/iwram-bus-history.md#thumb-iwram-refill-extension) distinguish these samples from a fully timed pipeline.
|
||||
Sub-instruction DMA ordering and exact refill timing remain incomplete.
|
||||
|
||||
### DMA effects on Thumb IWRAM continuations
|
||||
|
||||
Within the instruction-boundary scheduler, DMA finishes before the next CPU PC+4 sample.
|
||||
For an existing sequential or refill continuation, each successful DMA unit updates IWRAM source lanes, then IWRAM destination lanes.
|
||||
Within the instruction-boundary scheduler, DMA finishes before the next CPU fetch.
|
||||
Each successful DMA unit updates IWRAM source lanes, then IWRAM destination lanes, even before the first CPU instruction.
|
||||
Word accesses replace all lanes. Halfword accesses replace only the addressed halfword.
|
||||
Other memory regions do not replace the local IWRAM value, even when DMA's channel data changes.
|
||||
The resumed PC+4 fetch then replaces one halfword before the instruction's unused-memory snapshot is taken.
|
||||
A resumed Thumb PC+4 fetch then replaces one halfword before the instruction's unused-memory snapshot is taken.
|
||||
A cold fill instead drives all its newly fetched instructions.
|
||||
|
||||
Channel halfword duplication is not a full-word IWRAM access.
|
||||
A blocked source below work RAM does not drive IWRAM; an IWRAM destination still drives the halfword or word actually written.
|
||||
A blocked source below work RAM does not drive IWRAM; an IWRAM destination drives the actual halfword or word written.
|
||||
Multiple units and channel preemption update lanes in access order, not channel-number order.
|
||||
Host inspection/setup cannot change them.
|
||||
|
||||
History changes commit only after a unit succeeds. A failed unit retains the previous successful unit's history.
|
||||
A failed resumed instruction discards its staged changes, not the completed DMA history.
|
||||
DMA does not establish an expected PC at cold direct entry. Existing partially known continuations can gain known lanes.
|
||||
PC discontinuities, unsupported state changes, IRQ entry, and region-crossing lookahead keep their existing conservative rules.
|
||||
No expected PC or Thumb continuation is needed to record DMA activity.
|
||||
|
||||
This implements local lane effects, not a general last-DMA-value override or a persistent pipeline.
|
||||
Sub-instruction arbitration, DMA during CPU internal cycles, cold/cross-state IWRAM history, and exact resume timing remain incomplete.
|
||||
This implements local lane effects, not a general last-DMA-value override.
|
||||
Sub-instruction arbitration, DMA during CPU internal cycles, and exact resume timing remain incomplete.
|
||||
See [the source evidence and limits](../research/iwram-bus-history.md#dma-continuation-extension).
|
||||
|
||||
## Processor status and exceptions
|
||||
|
|
@ -354,7 +352,7 @@ 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. ARM instruction-buffer and Thumb IWRAM bus-history samples add no extra cycles.
|
||||
- Refill cost calculation does not read target bytes. ARM/Thumb instruction-buffer and local IWRAM bus-history samples add no 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.
|
||||
|
|
|
|||
|
|
@ -58,10 +58,12 @@ DMA writes to DMA registers return `DmaError::RegisterDestination`; self-modifyi
|
|||
Other accesses use the existing memory map, alignment checks, mirrors, and I/O write rules.
|
||||
Other unmapped reads and cartridge writes remain diagnostics, not general DMA open-bus behavior.
|
||||
A failed unit changes no memory, device state, or clock. Earlier units remain committed; the failed unit remains pending.
|
||||
Successful units update existing Thumb IWRAM continuation lanes for their actual IWRAM reads and writes, in that order.
|
||||
Successful units update local IWRAM lanes for their actual IWRAM reads and writes, in that order.
|
||||
No CPU continuation is required; DMA can establish lanes before the first CPU instruction.
|
||||
Accesses elsewhere leave those local lanes unchanged. Failed units preserve history.
|
||||
The resumed CPU's PC+4 fetch then updates its addressed halfword. No universal DMA-value override is used.
|
||||
See [bounded IWRAM DMA history](cpu.md#dma-effects-on-thumb-iwram-continuations) for the scope.
|
||||
A resumed Thumb PC+4 fetch updates its addressed halfword; a cold pipeline fill drives all its new samples.
|
||||
No universal DMA-value override is used.
|
||||
See [local IWRAM DMA history](cpu.md#dma-effects-on-thumb-iwram-continuations) for the scope.
|
||||
|
||||
## Retained channel data
|
||||
|
||||
|
|
@ -88,5 +90,5 @@ Earlier successful units remain committed. Normal destination validation, nomina
|
|||
This error policy is not a model of hardware data aborts.
|
||||
|
||||
See [the latch evidence and scope](../research/dma-data-latches.md).
|
||||
General DMA open-bus reads, cross-state IWRAM history, and sub-instruction bus ownership remain unimplemented.
|
||||
General DMA open-bus reads and sub-instruction bus ownership remain unimplemented.
|
||||
In particular, the known channel value does not supply unused/write-only I/O reads or CPU open-bus snapshots.
|
||||
|
|
|
|||
|
|
@ -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 and Thumb target-pair refill samples; 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 persistent local lanes across ARM/Thumb fetches, data accesses, and DMA; region-crossing snapshots 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).
|
||||
|
|
@ -110,7 +110,10 @@ IWRAM lane-history research:
|
|||
- [ares IWRAM accesses](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/memory.cpp) and [bus dispatch](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/bus.cpp), for separate byte-lane retention and debugger isolation.
|
||||
- [jgenesis issue 676](https://github.com/jsgroth/jgenesis/issues/676) and [its correction](https://github.com/jsgroth/jgenesis/commit/fab6e2ccc60e492dd68b7f1e927b0829a6d80195), for distinguishing the IWRAM latch from reads to other regions.
|
||||
|
||||
See [IWRAM Thumb bus history](research/iwram-bus-history.md). These were source reviews, not local hardware or external-emulator test runs.
|
||||
See [persistent IWRAM bus history](research/iwram-bus-history.md).
|
||||
The pinned ares helpers also support state-independent local ownership: they do not gate updates on the executing PC or CPU state.
|
||||
The pinned NanoBoyAdvance loop/refill helpers below support driving actual new samples, without replaying retained instruction slots.
|
||||
These were source reviews, not local hardware or external-emulator test runs.
|
||||
|
||||
IWRAM refill research:
|
||||
|
||||
|
|
@ -136,7 +139,7 @@ DMA IWRAM continuation research:
|
|||
Its [DMA controller](https://github.com/jsgroth/jgenesis/blob/fab6e2ccc60e492dd68b7f1e927b0829a6d80195/backend/gba-core/src/dma.rs) and [memory storage](https://github.com/jsgroth/jgenesis/blob/fab6e2ccc60e492dd68b7f1e927b0829a6d80195/backend/gba-core/src/memory.rs) distinguish channel data from local bus lanes.
|
||||
- The pinned ares DMA, bus dispatch, and IWRAM helpers above independently use the same local lane ownership.
|
||||
|
||||
See [the bounded DMA continuation extension](research/iwram-bus-history.md#dma-continuation-extension).
|
||||
See [the DMA local-lane extension](research/iwram-bus-history.md#dma-continuation-extension).
|
||||
These source reviews do not establish sub-instruction arbitration, exact CPU resume timing, or hardware conformance.
|
||||
|
||||
DMA resume-timing research:
|
||||
|
|
|
|||
|
|
@ -75,7 +75,8 @@ The current instruction address still selects nominal code cost rather than a se
|
|||
ARM PC+8 open bus and protected BIOS readback remain separate snapshots.
|
||||
The [shared fetch sample](shared-fetch-samples.md) supplies the instruction-buffer entry and supported bus snapshots without duplicate reads.
|
||||
No unified per-access bus model exists yet.
|
||||
The target samples do not establish complete ARM IWRAM or BIOS bus history.
|
||||
ARM IWRAM target samples now drive the [persistent local latch](iwram-bus-history.md).
|
||||
Complete BIOS and general-bus history remain unimplemented.
|
||||
Instruction-state-changing compare quirks, Game Pak prefetch, and DMA arbitration remain incomplete.
|
||||
DMA still runs between whole instructions, not during their data accesses or internal cycles.
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ Stores and 128 KiB ROM boundaries already use N and receive no duplicate cost.
|
|||
Game Pak prefetch remains unimplemented; WAITCNT bit 14 does not alter this nominal timing path.
|
||||
|
||||
This change does not select a DMA value for CPU open-bus reads.
|
||||
DMA channel data, bounded IWRAM lane history, and protected BIOS history remain separate.
|
||||
DMA channel data, persistent local IWRAM lanes, and protected BIOS history remain separate.
|
||||
General bus-data ownership and exact resume timing still need independent per-access validation.
|
||||
|
||||
## Original regression coverage
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
# IWRAM Thumb bus history
|
||||
# Persistent IWRAM bus history
|
||||
|
||||
## Finding
|
||||
|
||||
Internal work RAM (IWRAM) retains its own bus lanes. A previous CPU load result is not a universal replacement.
|
||||
Internal work RAM (IWRAM) retains its own bus lanes, independently of the CPU's instruction state and executing region.
|
||||
An IWRAM byte, halfword, or word access updates only the addressed lanes of that latch.
|
||||
A Thumb fetch updates one halfword. Reads and writes elsewhere do not replace the IWRAM latch.
|
||||
This distinction matters when Thumb code reads another memory region before an unused-memory read.
|
||||
ARM instruction fetches drive words. Thumb instruction fetches drive halfwords.
|
||||
Reads and writes elsewhere do not replace the IWRAM latch.
|
||||
Neither a prior CPU destination register nor a DMA channel value is a universal replacement.
|
||||
|
||||
For sequential Thumb execution at P, the fetch at P+4 updates:
|
||||
|
||||
|
|
@ -21,127 +22,107 @@ Using current bytes at P+2 would lose observed history and ignore data-access ch
|
|||
- [GBATEK](https://problemkaputt.de/gbatek-gba-unpredictable-things.htm) documents the two Thumb IWRAM lanes and warns about overwritten history.
|
||||
- [mGBA issue 1575](https://github.com/mgba-emu/mgba/issues/1575) reports hardware-based alignment expectations and read/write effects on IWRAM open bus.
|
||||
- [ares IWRAM access implementation](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/memory.cpp) retains separate IWRAM lanes for byte, halfword, and word reads and writes.
|
||||
Its [bus implementation](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/bus.cpp) distinguishes this latch from the general data bus and debugger access.
|
||||
Its helpers update the same latch without testing CPU state, executing PC, or sequential continuation.
|
||||
Its [bus implementation](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/bus.cpp) selects those helpers for CPU and DMA accesses and isolates debugger reads.
|
||||
- [jgenesis issue 676](https://github.com/jsgroth/jgenesis/issues/676) identifies failures after byte/halfword reads from other regions.
|
||||
The [correction at fab6e2cc](https://github.com/jsgroth/jgenesis/commit/fab6e2ccc60e492dd68b7f1e927b0829a6d80195) adds a separate IWRAM latch.
|
||||
The commit reports that its openbuster tests pass. That report is upstream evidence, not a test run by this project.
|
||||
- [NanoBoyAdvance's CPU loop and refill helpers](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/arm/arm7tdmi.hh) fetch the new instruction before execution and sample target pairs in instruction-width order.
|
||||
|
||||
These sources support a separate lane-retaining latch, rather than a copy of the last CPU destination register.
|
||||
These sources support local bus ownership rather than a latch restricted to consecutive Thumb instructions.
|
||||
Our implementation and synthetic programs are original. No external source code or test ROM was imported.
|
||||
No physical-hardware measurement or external emulator differential run was performed here.
|
||||
|
||||
## Bounded implementation
|
||||
## Implementation and cold fills
|
||||
|
||||
`memory/iwram_bus.rs` records lane values and a known-bit mask during consecutive Thumb instructions in IWRAM.
|
||||
Before execution, the pipeline's new mapped PC+4 fetch updates its addressed lanes.
|
||||
The bus-history consumer does not reread memory or reuse the current/decode instruction.
|
||||
During execution, successful IWRAM bus reads and writes update the staged lanes at their actual aligned addresses and widths.
|
||||
The latch sees raw bus data before CPU load rotation and sign extension.
|
||||
Other-region accesses and host inspection do not change it.
|
||||
`memory/iwram_bus.rs` stores persistent lane values and a known-bit mask.
|
||||
There is no expected PC or CPU-state tag on this latch. The instruction buffer retains its own PC/state tags.
|
||||
Every instruction stages changes from the previously committed lanes.
|
||||
The [shared fetch samples](shared-fetch-samples.md) drive successful IWRAM fetches at their actual address and instruction width.
|
||||
Successful IWRAM data accesses then drive raw bus values before load rotation or sign extension.
|
||||
CPU code can run in BIOS, ROM, EWRAM, or IWRAM without changing that rule.
|
||||
|
||||
A completed sequential instruction commits the staged history for the next exact virtual PC.
|
||||
A failed instruction discards all staged changes and preserves the last committed history.
|
||||
The existing whole-instruction diagnostic policy still applies; this is not hardware data-abort behavior.
|
||||
All unused-memory reads within one instruction use its entry snapshot.
|
||||
A retained pipeline drives only its newly fetched lookahead. It never replays current/decode bus samples.
|
||||
A cold or discontinuous pipeline supplies current, next, and lookahead samples in order.
|
||||
A valid Thumb IWRAM cold fill therefore establishes both lanes before the first data read.
|
||||
This follows actual samples already performed by the interpreter; it is not an invented previous P+2 memory read.
|
||||
Debugger invalidation clears only instruction retention, but the following cold fill drives its new samples.
|
||||
|
||||
The model starts with unknown lanes. An unused-memory load requires a complete known word, even for a narrower load.
|
||||
Ordinary instructions and mapped data accesses can establish history without requiring a prior complete word.
|
||||
Consecutive halfword fetches or an IWRAM word access can make every lane known.
|
||||
Missing lookahead and 16 MiB region-crossing fetches retain diagnostics.
|
||||
A successful instruction commits its staged lanes regardless of its resulting state, PC, or region.
|
||||
A failed instruction discards every staged change, including cold-fill samples.
|
||||
Successful PC-writing instructions then apply target fetches after their data accesses.
|
||||
Other-region execution, BIOS IRQ entry, and unavailable targets do not erase known local lanes.
|
||||
Host inspection and setup never drive the latch.
|
||||
|
||||
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 invalidates history. Failed DMA units preserve it.
|
||||
The DMA extension below updates existing continuation lanes from successful IWRAM accesses.
|
||||
|
||||
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. Exact per-access device timing remains unverified.
|
||||
Initial lanes remain unknown until observed accesses establish them.
|
||||
Byte/halfword accesses may establish only part of the word; a word access establishes all lanes.
|
||||
All unused-memory reads within an instruction still use one entry snapshot.
|
||||
A Thumb fetch crossing a 16 MiB region boundary leaves that instruction's unused-memory snapshot unsupported.
|
||||
Successful IWRAM accesses still update the local latch independently of this conservative snapshot policy.
|
||||
|
||||
## 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.
|
||||
Thumb destinations use T and T+2; ARM destinations use T and T+4.
|
||||
The same captured samples fill the instruction buffer and drive local IWRAM lanes, with no duplicate memory reads.
|
||||
Each successful IWRAM sample drives its lanes independently. Other-region or unavailable samples leave them unchanged.
|
||||
|
||||
After a successful refill instruction, the pipeline samples the two mapped destination instructions.
|
||||
The same captured halfwords update bus history 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.
|
||||
A complete Thumb IWRAM target pair replaces both halfwords.
|
||||
The first target instruction fetches T+4, replacing one halfword while retaining the captured T+2 lane.
|
||||
An ARM IWRAM target pair leaves T+4's word in the latch.
|
||||
Data loads and status restoration finish first, so saved instruction state determines the refill width.
|
||||
|
||||
This covers actual ARM/Thumb BX, taken Thumb branches, BL suffixes, PC-writing Thumb operations, and ARM status-restoring returns.
|
||||
This covers BX, taken branches, BL suffixes, PC writes/loads, and 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 captured values now supply both bus history and instruction-buffer entries, without extra emulated cycles.
|
||||
ARM and Thumb execution now use a separate instruction buffer. Cold buffer filling does not seed this bus history.
|
||||
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.
|
||||
|
||||
The refill-only implementation invalidated history on successful DMA, including between refill and arrival.
|
||||
The DMA extension below replaces that invalidation within existing continuations.
|
||||
ARM-target refill history, exact pipeline timing, and sub-instruction DMA ordering remain incomplete.
|
||||
No external emulator differential run or physical-hardware test was performed.
|
||||
A branch with an unavailable target can complete; the later instruction fetch retains its strict diagnostic.
|
||||
A failed branch or return does not commit data or target lane changes.
|
||||
IRQ vector samples in BIOS leave IWRAM unchanged, but an IRQ handler's IWRAM data access updates it normally.
|
||||
|
||||
## DMA continuation extension
|
||||
|
||||
Two independent implementations route DMA through the same local IWRAM lane updates as CPU accesses:
|
||||
|
||||
- [ares bus dispatch](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/bus.cpp) selects its IWRAM helpers for both CPU and DMA.
|
||||
[Its DMA controller](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/dma.cpp) performs a source read before its destination write.
|
||||
- [ares DMA](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/dma.cpp) performs a source read before its destination write through the shared bus dispatch above.
|
||||
- [jgenesis bus at fab6e2cc](https://github.com/jsgroth/jgenesis/blob/fab6e2ccc60e492dd68b7f1e927b0829a6d80195/backend/gba-core/src/bus.rs) routes `AccessCtx::DMA` through `read_iwram` and `write_iwram`.
|
||||
Both call `iwram_update_open_bus`, which changes only addressed lanes and drives the general bus from the local word.
|
||||
Its `try_progress_dma` performs source reads and destination writes in order.
|
||||
Both call `iwram_update_open_bus`. Its DMA controller performs source reads and destination writes in order.
|
||||
|
||||
Both run pending DMA before a CPU bus access. The access that resumes determines the subsequent bus value.
|
||||
These source reviews support local lane effects, not a universal first-instruction DMA override.
|
||||
They do not determine every hardware arbitration edge or validate our instruction-boundary scheduling.
|
||||
Our instruction-boundary scheduler commits source lanes, then destination lanes, only after a complete unit succeeds.
|
||||
A blocked source below work RAM drives no IWRAM read lanes.
|
||||
A halfword destination drives its actual selected half, not both halves of a duplicated channel word.
|
||||
DMA can establish local lanes before any CPU instruction, without an expected continuation PC.
|
||||
Failed DMA units preserve the previous committed latch.
|
||||
|
||||
Our bounded model applies those effects only when an existing Thumb IWRAM continuation has an expected next PC.
|
||||
After a successful unit, the source and destination independently update their addressed IWRAM lanes.
|
||||
DMA elsewhere leaves those lanes unchanged. A blocked source drives no IWRAM read lanes.
|
||||
The destination sees the actual selected halfword, not an assumed duplicated full word.
|
||||
The next PC+4 sample updates one halfword before the resumed instruction takes its snapshot.
|
||||
This also applies between a completed refill and its first target instruction.
|
||||
A retained Thumb pipeline resumes with one PC+4 sample; a cold pipeline resumes with its full fill sequence.
|
||||
These actual fetches determine how much DMA history remains visible.
|
||||
DMA channel values never overwrite CPU instruction-buffer slots or protected BIOS history automatically.
|
||||
Sub-instruction arbitration and DMA during CPU internal cycles remain unmodeled.
|
||||
|
||||
Only complete successful units commit history, preserving the project's atomic diagnostic policy.
|
||||
Cold direct entry still has no expected continuation PC. DMA does not manufacture one, even after a word access.
|
||||
An existing partially known continuation can gain lanes. IRQ entry and CPU discontinuities retain their invalidation rules.
|
||||
General DMA open-bus reads, cross-state history, sub-instruction arbitration, and DMA during CPU internal cycles remain outside this subset.
|
||||
This extension models bus lanes, not self-modifying instruction execution.
|
||||
The later [ARM](arm-instruction-buffer.md) and [Thumb](thumb-instruction-buffer.md) instruction buffers separately retain instructions across DMA.
|
||||
## Limits and original coverage
|
||||
|
||||
No physical-hardware measurements or external emulator differential runs were performed.
|
||||
All regression programs are original, with a separate byte-array lane reference in the DMA matrix tests.
|
||||
This is persistent local IWRAM history, not a complete general-bus implementation or cycle-accurate pipeline.
|
||||
ARM unused-memory reads still use their supported PC+8 snapshot.
|
||||
BIOS protection remains separately retained and image-derived.
|
||||
General DMA open bus, unused/write-only I/O, disabled RAM, exact region-crossing snapshots, and per-access timing remain incomplete.
|
||||
Nominal CPU/DMA costs do not change.
|
||||
|
||||
## Original regression coverage
|
||||
Original tests cover:
|
||||
|
||||
- Both code alignments, IWRAM mirrors, and physical RAM wrap.
|
||||
- Captured halfwords that remain unchanged by host inspection or later host writes.
|
||||
- Byte/halfword/word data reads and writes, sign extension, load rotation, and all processor modes.
|
||||
- Other-region accesses that leave IWRAM lanes unchanged.
|
||||
- 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.
|
||||
- DMA source/destination regions and alignments, word/halfword widths, mirrors, blocked-source lane selection, and other-region isolation.
|
||||
- Channel preemption, completed-unit order, DMA/instruction failure retention, partial/cold continuations, and unchanged other CPU snapshot rules.
|
||||
- Equal timed/untimed CPU results and unchanged nominal data/device costs.
|
||||
- Cold fills, both Thumb alignments, mirrors, physical RAM wrap, and entry snapshots.
|
||||
- ARM word fetches and ARM/Thumb target pairs, without replaying retained instruction slots.
|
||||
- ARM byte/halfword/word reads and stores from EWRAM, plus Thumb data access from EWRAM.
|
||||
- Preserved lanes through state changes, non-IWRAM execution, IRQ entry, and BIOS handler fetches.
|
||||
- DMA before CPU startup, source/destination ordering, channel preemption, and blocked-source lane selection.
|
||||
- Failed sequential instructions, failed cold fills, failed refills, and failed DMA units.
|
||||
- Separate BIOS values, host isolation, debugger refill effects, timed/untimed equality, and unchanged nominal costs.
|
||||
|
||||
The persistent-history regressions failed under the previous Thumb-continuation-only implementation.
|
||||
Existing cold-entry tests now assert captured lane values instead of the removed unsupported-history diagnostic.
|
||||
Region-crossing and strict unmapped-fetch diagnostics remain covered.
|
||||
|
||||
## 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.
|
||||
The DMA continuation regressions reproduced unsupported-read failures under unconditional DMA invalidation, then passed with local lane updates.
|
||||
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.
|
||||
Workspace and core/demo tests pass in debug and release on Darwin arm64.
|
||||
Formatting, lint checks, rustdoc, preparation tests, native ROM windows, and graphics smoke modes pass.
|
||||
Public ARM, Thumb, memory, and BIOS reports match the prior shared-fetch reports exactly, including nominal cycle counts.
|
||||
Debug and release reports also match for the same fixture paths.
|
||||
|
||||
Those public suites remain regression checks, not independent IWRAM-history conformance tests.
|
||||
These public suites remain regression checks, not independent IWRAM-history or hardware-timing conformance tests.
|
||||
|
|
|
|||
|
|
@ -33,11 +33,12 @@ Supported 16-bit regions duplicate that halfword for bus observation.
|
|||
BIOS/OAM samples read the adjacent halfword too, without rereading the instruction bytes.
|
||||
Thumb IWRAM supplies the fetched halfword to the existing lane-retaining transaction instead of creating a full bus word.
|
||||
|
||||
The CPU passes only its newly sampled P+8 or P+4 observation to `begin_cpu_access`.
|
||||
For retained pipelines, the CPU passes only its new P+8 or P+4 sample to `begin_cpu_access`.
|
||||
The later persistent IWRAM extension also passes newly sampled current/decode entries for cold fills.
|
||||
It does not replay the bus observations of retained current/decode instructions.
|
||||
The consumer performs no mapped memory read.
|
||||
After successful control flow, the same target samples fill the instruction buffer and supported Thumb IWRAM refill history.
|
||||
Machine IRQ entry uses this refill path; the ARM target still invalidates Thumb IWRAM continuation history.
|
||||
After successful control flow, the same target samples fill the instruction buffer and update local IWRAM history in either state.
|
||||
Machine IRQ entry uses this refill path; BIOS vector fetches leave IWRAM lanes unchanged.
|
||||
|
||||
Instruction buffers and BIOS/IWRAM latches remain separate retained state.
|
||||
Sampling alone changes no CPU access context, latch, device clock, DMA resume marker, or data-timing trace.
|
||||
|
|
@ -46,11 +47,10 @@ A retry takes a new lookahead sample while retaining the previously committed in
|
|||
Unavailable lookahead remains diagnostic only when consumed by an instruction or a data read that needs it.
|
||||
Strict instruction reads never use protected BIOS or unused-memory data fallback.
|
||||
|
||||
## Deliberately unchanged limits
|
||||
## Current scope and limits
|
||||
|
||||
- Cold pipeline filling does not establish previously unknown IWRAM history.
|
||||
- Cold fills and cross-state local IWRAM history are now implemented in the [persistent-latch extension](iwram-bus-history.md).
|
||||
- Thumb 16 MiB region crossings remain unsupported for bus snapshots.
|
||||
- ARM-wide local IWRAM history and cross-state latch ownership remain incomplete.
|
||||
- BIOS history commits after successful BIOS instruction execution; target refills do not establish additional BIOS history.
|
||||
- All unused-memory data reads within an instruction still use its entry snapshot.
|
||||
- DMA remains scheduled between whole instructions. Sampling does not add an interleaving point.
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ Original tests in `crates/gba-core/src/cpu/thumb_bios_access_tests.rs` cover:
|
|||
- Sequential history, ARM/Thumb transitions, SWI entry, and POP-to-PC exits with RAM stack data.
|
||||
- Raw reads inside BIOS and protected reads outside it.
|
||||
- The last complete fetch word, unavailable lookahead, and failure retention.
|
||||
- Separate host reads, retained cold-IWRAM Thumb open-bus diagnostics, nominal costs, and device progression.
|
||||
- Separate host reads, independent cold-IWRAM fetch history, nominal costs, and device progression.
|
||||
|
||||
## Validation result
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ CPU clones own independent buffers; full CPU equality includes them.
|
|||
## Separation from bus history and timing
|
||||
|
||||
Instruction retention and data-bus snapshots remain separate models.
|
||||
Cold instruction-buffer filling does not make unknown IWRAM bus lanes known.
|
||||
Successful DMA can update existing IWRAM bus lanes without changing retained CPU instructions.
|
||||
Cold instruction-buffer filling now drives its actual IWRAM samples under the [persistent-latch model](iwram-bus-history.md).
|
||||
Successful DMA can establish or update IWRAM bus lanes without changing retained CPU instructions.
|
||||
Refill bus history and instruction buffering now consume the same captured target pair without a second mapped read.
|
||||
|
||||
Buffer samples add no nominal cycles or data accesses.
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ Update this file when a feature lands or a limit is removed.
|
|||
- Original ARM graphics demo that uses DMA, BIOS VBlank waiting, and a ROM-side IRQ callback.
|
||||
- Sixteen active 32-bit CPU registers, with ARM7 mode-specific register banks.
|
||||
- Persistent ARM/Thumb instruction buffering, sequential fetch retention, branch/vector target pairs, and deferred fetch diagnostics.
|
||||
- Shared mapped fetch samples for instruction retention, supported open-bus values, BIOS snapshots, and Thumb IWRAM refill history.
|
||||
- Shared mapped fetch samples for instruction retention, supported open-bus values, BIOS snapshots, and IWRAM refill history.
|
||||
- User, System, Supervisor, IRQ, FIQ, Abort, and Undefined modes.
|
||||
- Current and saved program status registers (`CPSR`/`SPSR`), with `MRS`/`MSR` transfers.
|
||||
- ARM/Thumb software interrupts (`SWI`), exception vectors, and status-restoring returns.
|
||||
|
|
@ -77,8 +77,8 @@ Update this file when a feature lands or a limit is removed.
|
|||
- ARM test/compare Rd=15 status restoration, without a PC result write or nominal refill.
|
||||
- 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.
|
||||
- Persistent IWRAM lanes track ARM/Thumb fetches, CPU data accesses, and successful DMA, with known-bit tracking and diagnostic rollback.
|
||||
- Cold fills and ARM/Thumb target pairs update local history; state changes and BIOS IRQ entry do not erase it.
|
||||
- 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.
|
||||
|
|
@ -137,11 +137,11 @@ Other public suites remain unverified. These results do not establish full CPU o
|
|||
The runner requires known completion addresses and has no debug-port protocol, scripted input, or rendered-image assertions.
|
||||
|
||||
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.
|
||||
Refills into Thumb IWRAM establish new target-pair history after the instruction succeeds.
|
||||
Accepted machine IRQs, unsupported refills, and other execution states/regions invalidate continuation history.
|
||||
Successful DMA updates existing Thumb IWRAM continuation lanes for actual IWRAM accesses, before the resumed PC+4 sample.
|
||||
Thumb reads use [region-dependent snapshots](hardware/cpu.md#thumb-unused-memory-data-reads) and persistent local IWRAM lanes.
|
||||
Only actual IWRAM fetches and data accesses drive those lanes, independently of CPU state and executing region.
|
||||
Cold fills and target pairs establish both lanes when all required samples are mapped.
|
||||
State changes, other-region execution, and BIOS IRQ entry preserve local history; failed instructions discard staged updates.
|
||||
Successful DMA can establish lanes before CPU startup. Resumed fetches then update their addressed lanes.
|
||||
ARM/Thumb instruction buffering retains sequential instructions and refill target pairs.
|
||||
Instruction buffering and supported bus history now consume the same mapped fetch samples, without sharing retained storage.
|
||||
Complete ARM bus history, exact region-crossing bus behavior, general DMA handoff, and unused/write-only I/O open bus remain incomplete.
|
||||
|
|
@ -155,7 +155,7 @@ Writes to cartridge addresses return errors instead of modeling cartridge hardwa
|
|||
|
||||
## Next steps
|
||||
|
||||
1. Extend the shared fetch-sample model to verified cross-state bus history and region crossings.
|
||||
1. Extend shared fetch samples to verified general-bus history and region-crossing snapshots.
|
||||
General DMA open-bus reads and sub-instruction arbitration remain separate from retained channel data and local IWRAM lanes.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -65,10 +65,11 @@ Compare/status CPU regressions verify saved flags/masks, bank switching, sequent
|
|||
Load-alias regressions cover widths, signed/unaligned values, immediate/register offsets, pre/post-indexing, every non-PC register bank, and address wrapping.
|
||||
They verify loaded-value precedence, preserved flags, unchanged stores, nominal timing, I/O reads before device progress, deferred IRQ delivery, and atomic errors.
|
||||
ARM open-bus regressions cover unused-range boundaries, PC+8 source regions, byte lanes, signed/unaligned loads, writeback, and block loads.
|
||||
They check branch-target resampling, missing lookahead, skipped conditions, nominal timing, device progress, and retained host/fetch/cold-IWRAM/DMA diagnostics.
|
||||
They check branch-target resampling, missing lookahead, skipped conditions, nominal timing, device progress, and retained host/fetch/DMA diagnostics.
|
||||
Thumb open-bus tests cover supported code regions, both instruction alignments, ROM windows, mirrors, and physical memory wrap.
|
||||
They check widths, sign extension, rotation, block/stack/PC loads, aliases, modes, state preservation, and sequential/branch resampling.
|
||||
Short-ROM tests require only the fetched halfword in 16-bit regions. Cold IWRAM and region-crossing tests retain explicit diagnostics.
|
||||
Short-ROM tests require only the fetched halfword in 16-bit regions. Region-crossing tests retain explicit diagnostics.
|
||||
Cold IWRAM tests now assert actual current/decode/lookahead lane updates before the first data read.
|
||||
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.
|
||||
|
|
@ -77,8 +78,9 @@ Refill tests cover ARM/Thumb BX, Thumb branches and BL, high-register PC writes,
|
|||
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 lane effects, and BIOS isolation retain checks.
|
||||
DMA tests compare source/destination lane updates against a byte-array reference, then apply the resumed PC+4 fetch.
|
||||
They cover channel preemption, blocked sources, failures, mirrors, partial/cold continuations, and other-region snapshot isolation.
|
||||
IRQ and PC-discontinuity tests retain conservative invalidation; timed/untimed tests verify CPU results and nominal clock costs.
|
||||
They cover channel preemption, blocked sources, failures, mirrors, pre-start lane establishment, cold fills, and other-region snapshot isolation.
|
||||
IRQ tests verify local-latch preservation; PC discontinuities verify new pipeline-fill samples.
|
||||
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.
|
||||
ARM instruction-buffer tests verify retained P+4/P+8 words across CPU stores, DMA transfers, and host writes.
|
||||
They cover refill target pairs, PC writes to fallthrough, conditional branches, ARM/Thumb transitions, exception entry/return, and deferred fetch errors.
|
||||
|
|
@ -88,6 +90,9 @@ The display-runner error test allows an already buffered branch to complete befo
|
|||
Thumb instruction-buffer tests verify retained P+2/P+4 halfwords, BL prefix/suffix behavior, and state-tagged target pairs.
|
||||
They also cover stores, host/DMA writes, saved-state returns, deferred errors, mirrors, debugger repair, and nominal timing.
|
||||
Bus-history repair tests explicitly invalidate buffered code or repair a data address; failure checks retain full CPU equality.
|
||||
Debugger invalidation itself preserves local lanes, but the next cold fill drives current mapped instruction bytes.
|
||||
Persistent-latch tests cover ARM data accesses, ARM/Thumb target pairs, non-IWRAM code, IRQ handlers, and pre-start DMA.
|
||||
They also verify partial lanes and rollback of both cold-fill and resumed-instruction samples.
|
||||
Shared fetch-sample tests mutate memory between internal sampling and consumption to reject duplicate reads.
|
||||
They cover ARM words, Thumb narrow/wide buses, IWRAM lanes, target pairs, BIOS retention, and retry after failure.
|
||||
This test-only split is not a DMA scheduling feature. Separate tests verify strict fetch errors and no timing/history side effects.
|
||||
|
|
@ -257,7 +262,8 @@ 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 sequential/refill history, general Thumb snapshots, and retained BIOS history.
|
||||
Unknown IWRAM lanes remain diagnostic until observed accesses establish them; mapped cold pipeline fills now establish both halves.
|
||||
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