feat(memory): add region-dependent Thumb open-bus snapshots
Repeat the mapped PC+4 halfword for Thumb code in EWRAM, palette RAM, VRAM, and ROM. Use the aligned PC+4 word for BIOS/OAM. Keep retained BIOS history separate from current unused-memory reads. Reuse one instruction snapshot without extra data cycles or host/fetch/DMA fallbacks. Keep IWRAM and 16 MiB region-crossing fetches diagnostic until prior bus history is modeled. Add original regressions for regions, alignments, mirrors, physical wrap, signed and unaligned loads, block/stack/PC loads, aliases, modes, short ROMs, strict errors, BIOS isolation, and nominal device progression. Record documented rules and remaining limits. Darwin arm64 validation: 890 workspace tests and 821 core/demo tests in each of debug and release; 39 preparation tests; native ROM windows in both profiles; ten window/graphics smoke modes; fmt, clippy, and rustdoc. Public ARM, Thumb, memory, and BIOS reports remain identical to previous passing results and between profiles. No physical-hardware or independent public Thumb open-bus conformance claim.
This commit is contained in:
parent
cb4670c245
commit
8360ba4652
11 changed files with 528 additions and 36 deletions
|
|
@ -32,6 +32,8 @@ mod tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod thumb_empty_tests;
|
mod thumb_empty_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
mod thumb_open_bus_tests;
|
||||||
|
#[cfg(test)]
|
||||||
mod thumb_tests;
|
mod thumb_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod timing_tests;
|
mod timing_tests;
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ fn missing_bios_rom_save_and_unsupported_io_reads_remain_errors() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn thumb_reads_and_unused_instruction_fetches_do_not_reuse_arm_context() {
|
fn unsupported_iwram_thumb_reads_and_unused_fetches_do_not_reuse_arm_context() {
|
||||||
let mut bus = memory(LDR);
|
let mut bus = memory(LDR);
|
||||||
let mut cpu = Cpu::new(ROM_START);
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
cpu.registers[0] = UNUSED;
|
cpu.registers[0] = UNUSED;
|
||||||
|
|
@ -244,8 +244,8 @@ fn thumb_reads_and_unused_instruction_fetches_do_not_reuse_arm_context() {
|
||||||
invalid.step(&mut bus),
|
invalid.step(&mut bus),
|
||||||
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||||
);
|
);
|
||||||
bus.write16(RAM, 0x6801).unwrap(); // Thumb LDR r1, [r0]
|
bus.write16(0x0300_0100, 0x6801).unwrap(); // IWRAM Thumb LDR r1,[r0]
|
||||||
cpu.registers[15] = RAM;
|
cpu.registers[15] = 0x0300_0100;
|
||||||
cpu.instruction_set = InstructionSet::Thumb;
|
cpu.instruction_set = InstructionSet::Thumb;
|
||||||
let before = cpu.clone();
|
let before = cpu.clone();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
||||||
|
|
@ -238,11 +238,13 @@ fn failed_thumb_steps_keep_previous_history_and_clear_access_context() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn outside_thumb_execution_keeps_history_but_does_not_enable_general_open_bus() {
|
fn outside_thumb_execution_keeps_bios_history_without_enabling_iwram_open_bus() {
|
||||||
let mut memory = bus(0x6801, thumb_image(THUMB_ENTRY, BX_R5));
|
let mut memory = bus(0x6801, thumb_image(THUMB_ENTRY, BX_R5));
|
||||||
let mut cpu = enter_thumb(&mut memory, THUMB_ENTRY, true);
|
let mut cpu = enter_thumb(&mut memory, THUMB_ENTRY, true);
|
||||||
cpu.step(&mut memory).unwrap();
|
cpu.step(&mut memory).unwrap();
|
||||||
assert_eq!(protected_word(&mut memory), OTHER);
|
assert_eq!(protected_word(&mut memory), OTHER);
|
||||||
|
memory.write16(0x0300_0100, 0x6801).unwrap(); // IWRAM needs unmodeled history.
|
||||||
|
cpu.registers[15] = 0x0300_0100;
|
||||||
cpu.registers[0] = 0x4000;
|
cpu.registers[0] = 0x4000;
|
||||||
let before = cpu.clone();
|
let before = cpu.clone();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
||||||
435
crates/gba-core/src/cpu/thumb_open_bus_tests.rs
Normal file
435
crates/gba-core/src/cpu/thumb_open_bus_tests.rs
Normal file
|
|
@ -0,0 +1,435 @@
|
||||||
|
//! Original tests for region-dependent Thumb unused-memory snapshots.
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
dma::{DmaError, DMA_BASE, DMA_STRIDE},
|
||||||
|
io::TIMER_BASE,
|
||||||
|
machine::{Machine, MachineError, StepKind},
|
||||||
|
memory::{BIOS_SIZE, ROM_START},
|
||||||
|
timing::{bus_cycles, AccessKind, AccessWidth},
|
||||||
|
};
|
||||||
|
|
||||||
|
const DATA: u32 = 0xf233_80a5;
|
||||||
|
const UNUSED: u32 = 0x8000_0000;
|
||||||
|
const RAM: u32 = 0x0200_0100;
|
||||||
|
const LDR: u16 = 0x6801; // LDR r1,[r0]
|
||||||
|
const REGIONS: [u32; 11] = [
|
||||||
|
0x100,
|
||||||
|
RAM,
|
||||||
|
0x0500_0100,
|
||||||
|
0x0600_0100,
|
||||||
|
0x0700_0100,
|
||||||
|
ROM_START,
|
||||||
|
0x0900_0000,
|
||||||
|
0x0a00_0000,
|
||||||
|
0x0b00_0000,
|
||||||
|
0x0c00_0000,
|
||||||
|
0x0d00_0000,
|
||||||
|
];
|
||||||
|
|
||||||
|
fn cpu(pc: u32) -> Cpu {
|
||||||
|
let mut cpu = Cpu::new(pc);
|
||||||
|
cpu.instruction_set = InstructionSet::Thumb;
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
cpu
|
||||||
|
}
|
||||||
|
|
||||||
|
fn program(pc: u32, instruction: u16) -> Memory {
|
||||||
|
let base = pc & !3;
|
||||||
|
let mut code: Vec<u8> = [0x46c0_46c0_u32, DATA, 0xdead_beef]
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(u32::to_le_bytes)
|
||||||
|
.collect();
|
||||||
|
let offset = (pc & 2) as usize;
|
||||||
|
code[offset..offset + 2].copy_from_slice(&instruction.to_le_bytes());
|
||||||
|
if pc < BIOS_SIZE as u32 {
|
||||||
|
let mut bios = vec![0; BIOS_SIZE];
|
||||||
|
bios[base as usize..base as usize + code.len()].copy_from_slice(&code);
|
||||||
|
Memory::with_bios(vec![], bios).unwrap()
|
||||||
|
} else if pc >= ROM_START {
|
||||||
|
let offset = (base & 0x01ff_ffff) as usize;
|
||||||
|
let mut rom = vec![0; offset + code.len()];
|
||||||
|
rom[offset..].copy_from_slice(&code);
|
||||||
|
Memory::new(rom).unwrap()
|
||||||
|
} else {
|
||||||
|
let mut bus = Memory::new(vec![]).unwrap();
|
||||||
|
for (index, bytes) in code.chunks_exact(2).enumerate() {
|
||||||
|
bus.write16(
|
||||||
|
base + index as u32 * 2,
|
||||||
|
u16::from_le_bytes(bytes.try_into().unwrap()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
bus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Independent expected words for the bytes written above.
|
||||||
|
fn expected(pc: u32) -> u32 {
|
||||||
|
match pc >> 24 {
|
||||||
|
0 | 7 => DATA,
|
||||||
|
_ if pc & 2 == 0 => 0x80a5_80a5,
|
||||||
|
_ => 0xf233_f233,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_supported_region_and_alignment_supplies_its_own_bus_width() {
|
||||||
|
for base in REGIONS {
|
||||||
|
for offset in [0, 2] {
|
||||||
|
let pc = base + offset;
|
||||||
|
let mut bus = program(pc, LDR);
|
||||||
|
let mut cpu = cpu(pc);
|
||||||
|
let timing = cpu.step_timed(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[1], expected(pc), "pc={pc:#x}");
|
||||||
|
assert_eq!(cpu.pc(), pc + 2);
|
||||||
|
assert_eq!(
|
||||||
|
timing.code_cycles,
|
||||||
|
bus_cycles(0, pc, AccessWidth::Halfword, AccessKind::Sequential)
|
||||||
|
);
|
||||||
|
assert_eq!(timing.data_cycles, 1);
|
||||||
|
assert_eq!(timing.internal_cycles, 1);
|
||||||
|
assert_eq!(bus.cycles(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loads_select_lanes_rotate_and_sign_extend_without_changing_flags() {
|
||||||
|
for base in [0x100, RAM, 0x0700_0100, ROM_START] {
|
||||||
|
for offset in [0, 2] {
|
||||||
|
let pc = base + offset;
|
||||||
|
let word = expected(pc);
|
||||||
|
for (instruction, width, signed) in [
|
||||||
|
(LDR, 4, false),
|
||||||
|
(0x5881, 4, false), // Immediate/register word loads.
|
||||||
|
(0x7801, 1, false),
|
||||||
|
(0x8801, 2, false),
|
||||||
|
(0x5681, 1, true),
|
||||||
|
(0x5e81, 2, true),
|
||||||
|
] {
|
||||||
|
for lane in 0..4 {
|
||||||
|
let byte = (word >> (lane * 8)) as u8;
|
||||||
|
let half = (word >> ((lane & 2) * 8)) as u16;
|
||||||
|
let value = match (width, signed) {
|
||||||
|
(4, _) => word.rotate_right(lane * 8),
|
||||||
|
(1, false) => u32::from(byte),
|
||||||
|
(1, true) => byte as i8 as i32 as u32,
|
||||||
|
(2, false) => u32::from(half).rotate_right((lane & 1) * 8),
|
||||||
|
(2, true) if lane & 1 != 0 => byte as i8 as i32 as u32,
|
||||||
|
(2, true) => half as i16 as i32 as u32,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let mut bus = program(pc, instruction);
|
||||||
|
let mut cpu = cpu(pc);
|
||||||
|
cpu.apply_status(0xb000_00ff, Mode::System);
|
||||||
|
cpu.registers[0] = UNUSED + lane;
|
||||||
|
let mut after = cpu.clone();
|
||||||
|
after.registers[1] = value;
|
||||||
|
after.registers[15] += 2;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
cpu, after,
|
||||||
|
"pc={pc:#x} instruction={instruction:#x} lane={lane}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unused_range_edges_are_not_mirrors_and_host_reads_remain_strict() {
|
||||||
|
for address in [
|
||||||
|
0x4000,
|
||||||
|
0x00ff_fffc,
|
||||||
|
0x0100_0000,
|
||||||
|
0x01ff_fffc,
|
||||||
|
0x1000_0000,
|
||||||
|
0x1800_0000,
|
||||||
|
UNUSED,
|
||||||
|
0xffff_fffc,
|
||||||
|
] {
|
||||||
|
let mut bus = program(ROM_START, LDR);
|
||||||
|
let mut cpu = cpu(ROM_START);
|
||||||
|
cpu.registers[0] = address;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[1], 0x80a5_80a5);
|
||||||
|
for lane in 0..4 {
|
||||||
|
assert_eq!(
|
||||||
|
bus.read8(address + lane),
|
||||||
|
Err(MemoryError::Unmapped(address + lane))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(bus.read16(address), Err(MemoryError::Unmapped(address)));
|
||||||
|
assert_eq!(bus.read32(address), Err(MemoryError::Unmapped(address)));
|
||||||
|
let mut invalid = super::Cpu::new(address);
|
||||||
|
invalid.instruction_set = InstructionSet::Thumb;
|
||||||
|
assert_eq!(
|
||||||
|
invalid.step(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(address)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mirrored_code_and_physical_memory_wrap_use_mapped_fetch_bytes() {
|
||||||
|
for base in [
|
||||||
|
0x0204_0100,
|
||||||
|
0x0203_fffc,
|
||||||
|
0x0500_0500,
|
||||||
|
0x0500_03fc,
|
||||||
|
0x0601_8100,
|
||||||
|
0x0601_fffc,
|
||||||
|
0x0700_0500,
|
||||||
|
0x0700_03fc,
|
||||||
|
] {
|
||||||
|
for offset in [0, 2] {
|
||||||
|
let pc = base + offset;
|
||||||
|
let mut bus = program(pc, LDR);
|
||||||
|
let mut cpu = cpu(pc);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[1], expected(pc), "pc={pc:#x}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_and_stack_loads_share_snapshot_and_keep_writeback_semantics() {
|
||||||
|
for pc in [RAM, RAM + 2, 0x0700_0100, 0x0700_0102] {
|
||||||
|
for instruction in [0xc80e, 0xc803, 0xbc06, 0xbd00, 0xc800] {
|
||||||
|
let mut bus = program(pc, instruction);
|
||||||
|
let mut cpu = cpu(pc);
|
||||||
|
cpu.registers[0] = UNUSED + 3;
|
||||||
|
cpu.registers[13] = UNUSED + 3;
|
||||||
|
let timing = cpu.step_timed(&mut bus).unwrap();
|
||||||
|
let value = expected(pc);
|
||||||
|
match instruction {
|
||||||
|
0xc80e => {
|
||||||
|
assert_eq!(&cpu.registers[1..4], &[value; 3]);
|
||||||
|
assert_eq!(cpu.registers[0], UNUSED + 15);
|
||||||
|
assert_eq!(timing.data_cycles, 3);
|
||||||
|
}
|
||||||
|
0xc803 => {
|
||||||
|
assert_eq!(&cpu.registers[..2], &[value; 2]); // Base in list: no writeback.
|
||||||
|
assert_eq!(timing.data_cycles, 2);
|
||||||
|
}
|
||||||
|
0xbc06 => {
|
||||||
|
assert_eq!(&cpu.registers[1..3], &[value; 2]);
|
||||||
|
assert_eq!(cpu.registers[13], UNUSED + 11);
|
||||||
|
assert_eq!(timing.data_cycles, 2);
|
||||||
|
}
|
||||||
|
0xbd00 => {
|
||||||
|
assert_eq!(cpu.pc(), value & !1);
|
||||||
|
assert_eq!(cpu.registers[13], UNUSED + 7);
|
||||||
|
assert_eq!(timing.data_cycles, 1);
|
||||||
|
}
|
||||||
|
0xc800 => {
|
||||||
|
assert_eq!(cpu.pc(), value & !1);
|
||||||
|
assert_eq!(cpu.registers[0], UNUSED + 67);
|
||||||
|
assert_eq!(timing.data_cycles, 1);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
assert_eq!(cpu.instruction_set(), InstructionSet::Thumb);
|
||||||
|
}
|
||||||
|
let mut bus = program(pc, 0x6800); // LDR r0,[r0], base/destination alias.
|
||||||
|
let mut cpu = cpu(pc);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], expected(pc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sequential_execution_and_branches_resample_instead_of_reusing_old_context() {
|
||||||
|
let mut bus = program(RAM, LDR);
|
||||||
|
bus.write16(RAM + 2, 0x6802).unwrap(); // LDR r2,[r0]
|
||||||
|
bus.write16(RAM + 4, 0x4718).unwrap(); // BX r3
|
||||||
|
bus.write16(0x0700_0100, LDR).unwrap();
|
||||||
|
bus.write32(0x0700_0104, DATA).unwrap();
|
||||||
|
let mut cpu = cpu(RAM);
|
||||||
|
cpu.registers[3] = 0x0700_0101;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[1], 0x4718_4718);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[2], 0xf233_f233);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.pc(), 0x0700_0100);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[1], DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn short_rom_needs_only_the_fetched_halfword_and_errors_only_when_used() {
|
||||||
|
for offset in [0, 2] {
|
||||||
|
for size in offset + 2..=offset + 6 {
|
||||||
|
let mut rom = vec![0; size];
|
||||||
|
rom[offset..offset + 2].copy_from_slice(&LDR.to_le_bytes());
|
||||||
|
if size == offset + 6 {
|
||||||
|
rom[offset + 4..].copy_from_slice(&0xabcd_u16.to_le_bytes());
|
||||||
|
}
|
||||||
|
let mut bus = Memory::new(rom).unwrap();
|
||||||
|
bus.write32(RAM, DATA).unwrap();
|
||||||
|
let mut cpu = cpu(ROM_START + offset as u32);
|
||||||
|
if size == offset + 6 {
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[1], 0xabcd_abcd);
|
||||||
|
} else {
|
||||||
|
let before = cpu.clone();
|
||||||
|
assert_eq!(
|
||||||
|
cpu.step_timed(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||||
|
);
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
cpu.registers[0] = RAM;
|
||||||
|
let timing = cpu.step_timed(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[1], DATA);
|
||||||
|
assert_eq!(timing.data_cycles, 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut bus = Memory::new(0x46c0_u16.to_le_bytes().to_vec()).unwrap();
|
||||||
|
let mut cpu = cpu(ROM_START);
|
||||||
|
assert_eq!(cpu.step_timed(&mut bus).unwrap().data_cycles, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iwram_history_and_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);
|
||||||
|
let before = cpu.clone();
|
||||||
|
assert_eq!(
|
||||||
|
cpu.step(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||||
|
);
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
bus.write32(RAM, DATA).unwrap();
|
||||||
|
cpu.registers[0] = RAM;
|
||||||
|
cpu.step(&mut bus).unwrap(); // Missing snapshot must not break ordinary loads.
|
||||||
|
assert_eq!(cpu.registers[1], DATA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for pc in [0x3ffc, 0x3ffe] {
|
||||||
|
let mut bios = vec![0; BIOS_SIZE];
|
||||||
|
bios[pc..pc + 2].copy_from_slice(&LDR.to_le_bytes());
|
||||||
|
let mut bus = Memory::with_bios(vec![], bios).unwrap();
|
||||||
|
let mut cpu = cpu(pc as u32);
|
||||||
|
assert_eq!(
|
||||||
|
cpu.step(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unsupported_data_regions_and_unused_stores_do_not_gain_fallbacks() {
|
||||||
|
for address in [
|
||||||
|
0,
|
||||||
|
0x3ffc,
|
||||||
|
0x0400_00e0,
|
||||||
|
ROM_START + 12,
|
||||||
|
0x0e00_0000,
|
||||||
|
0x0fff_fffc,
|
||||||
|
] {
|
||||||
|
let mut bus = program(ROM_START, LDR);
|
||||||
|
let mut cpu = cpu(ROM_START);
|
||||||
|
cpu.registers[0] = address;
|
||||||
|
let before = cpu.clone();
|
||||||
|
assert_eq!(
|
||||||
|
cpu.step(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(address)))
|
||||||
|
);
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
}
|
||||||
|
for instruction in [0x6001, 0x7001, 0x8001, 0xc006] {
|
||||||
|
// STR, STRB, STRH, STMIA
|
||||||
|
let mut bus = program(ROM_START, instruction);
|
||||||
|
let mut cpu = cpu(ROM_START);
|
||||||
|
cpu.registers[1] = DATA;
|
||||||
|
let before = cpu.clone();
|
||||||
|
assert_eq!(
|
||||||
|
cpu.step(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||||
|
);
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mode_flags_and_sp_relative_addressing_do_not_select_the_open_bus_rule() {
|
||||||
|
for mode in [
|
||||||
|
Mode::User,
|
||||||
|
Mode::System,
|
||||||
|
Mode::Fiq,
|
||||||
|
Mode::Irq,
|
||||||
|
Mode::Supervisor,
|
||||||
|
Mode::Abort,
|
||||||
|
Mode::Undefined,
|
||||||
|
] {
|
||||||
|
for pc in [ROM_START, ROM_START + 2, 0x0700_0100] {
|
||||||
|
let mut bus = program(pc, 0x9900); // LDR r1,[sp]
|
||||||
|
let mut cpu = cpu(pc);
|
||||||
|
cpu.apply_status(0xb000_00e0 | mode as u32, mode);
|
||||||
|
cpu.registers[13] = UNUSED;
|
||||||
|
let status = cpu.cpsr();
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[1], expected(pc));
|
||||||
|
assert_eq!(cpu.cpsr(), status);
|
||||||
|
assert_eq!(cpu.registers[13], UNUSED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn retained_bios_word_is_separate_from_current_thumb_unused_memory_snapshot() {
|
||||||
|
let mut bios = vec![0; BIOS_SIZE];
|
||||||
|
bios[0x100..0x104].copy_from_slice(&0xe12f_ff14_u32.to_le_bytes()); // BX r4
|
||||||
|
bios[0x108..0x10c].copy_from_slice(&DATA.to_le_bytes());
|
||||||
|
let rom: Vec<_> = [0x6802_6801_u32, 0x1234_5678]
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(u32::to_le_bytes)
|
||||||
|
.collect();
|
||||||
|
let mut bus = Memory::with_bios(rom, bios).unwrap();
|
||||||
|
let mut cpu = Cpu::new(0x100);
|
||||||
|
cpu.registers[4] = ROM_START | 1;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
cpu.step(&mut bus).unwrap(); // LDR r1,[r0=0]: retained ARM BIOS word.
|
||||||
|
assert_eq!(cpu.registers[1], DATA);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
cpu.step(&mut bus).unwrap(); // LDR r2,[r0]: current Thumb ROM halfword.
|
||||||
|
assert_eq!(cpu.registers[2], 0x1234_1234);
|
||||||
|
assert_eq!(bus.read32(0x108).unwrap(), DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn machine_timing_and_dma_remain_separate_from_thumb_snapshot_reads() {
|
||||||
|
let mut bus = program(ROM_START, LDR);
|
||||||
|
bus.write32(TIMER_BASE, 0x0080_0000).unwrap();
|
||||||
|
let mut machine = Machine::new(cpu(ROM_START), bus);
|
||||||
|
assert_eq!(machine.step().unwrap(), StepKind::Instruction);
|
||||||
|
assert_eq!(machine.cpu().registers()[1], 0x80a5_80a5);
|
||||||
|
assert_eq!(machine.last_timing().code_cycles, 5); // ROM boundary: 1 + 4 wait cycles.
|
||||||
|
assert_eq!(machine.last_timing().data_cycles, 1);
|
||||||
|
assert_eq!(machine.last_timing().internal_cycles, 1);
|
||||||
|
assert_eq!(machine.cycles(), 7);
|
||||||
|
assert_eq!(machine.memory().read16(TIMER_BASE).unwrap(), 7);
|
||||||
|
let before = machine.cpu().clone();
|
||||||
|
let timing = machine.last_timing();
|
||||||
|
let dma = DMA_BASE + 3 * DMA_STRIDE;
|
||||||
|
machine.memory_mut().write32(dma, 0x0e00_0000).unwrap();
|
||||||
|
machine.memory_mut().write32(dma + 4, RAM).unwrap();
|
||||||
|
machine.memory_mut().write32(dma + 8, 0x8400_0001).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
machine.step(),
|
||||||
|
Err(MachineError::Dma(DmaError::Memory {
|
||||||
|
channel: 3,
|
||||||
|
error: MemoryError::Unmapped(0x0e00_0000),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
assert_eq!(machine.cpu(), &before);
|
||||||
|
assert_eq!(machine.cycles(), 7);
|
||||||
|
assert_eq!(machine.last_timing(), timing);
|
||||||
|
assert_eq!(machine.memory().read32(RAM).unwrap(), 0);
|
||||||
|
}
|
||||||
|
|
@ -56,14 +56,13 @@ fn vram_index(address: u32) -> usize {
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct CpuAccess {
|
struct CpuAccess {
|
||||||
pc: u32,
|
pc: u32,
|
||||||
arm_prefetch: Option<u32>,
|
prefetch: Option<u32>,
|
||||||
bios_prefetch: Option<u32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Work/video/palette RAM, OAM, cartridge ROM, optional BIOS, and supported I/O.
|
/// Work/video/palette RAM, OAM, cartridge ROM, optional BIOS, and supported I/O.
|
||||||
/// ARM data reads from unused memory return a PC+8 snapshot when available.
|
/// Unused-memory reads use ARM PC+8 or supported region-dependent Thumb snapshots.
|
||||||
/// Protected BIOS reads retain an ARM PC+8 or aligned Thumb PC+4 word snapshot.
|
/// Protected BIOS reads separately retain the snapshot from BIOS execution.
|
||||||
/// General Thumb open bus, a full fetch pipeline, and DMA bus latches are not modeled.
|
/// Thumb IWRAM history, a full fetch pipeline, and DMA bus latches are not modeled.
|
||||||
pub struct Memory {
|
pub struct Memory {
|
||||||
external_ram: Vec<u8>,
|
external_ram: Vec<u8>,
|
||||||
internal_ram: Vec<u8>,
|
internal_ram: Vec<u8>,
|
||||||
|
|
@ -333,25 +332,34 @@ impl Memory {
|
||||||
debug_assert!(self.cpu_access.is_none());
|
debug_assert!(self.cpu_access.is_none());
|
||||||
// Sample before execution, without data timing or open-bus recursion.
|
// Sample before execution, without data timing or open-bus recursion.
|
||||||
// Missing lookahead bytes must not fail an instruction that never uses them.
|
// Missing lookahead bytes must not fail an instruction that never uses them.
|
||||||
let arm_prefetch = match instruction_set {
|
let prefetch = match instruction_set {
|
||||||
InstructionSet::Arm => self.snapshot_mapped_word(pc.wrapping_add(8)),
|
InstructionSet::Arm => self.snapshot_mapped_word(pc.wrapping_add(8)),
|
||||||
InstructionSet::Thumb => None,
|
InstructionSet::Thumb => self.snapshot_thumb_prefetch(pc),
|
||||||
};
|
};
|
||||||
let bios_prefetch = if pc < BIOS_SIZE as u32 {
|
self.cpu_access = Some(CpuAccess { pc, prefetch });
|
||||||
match instruction_set {
|
}
|
||||||
InstructionSet::Arm => arm_prefetch,
|
|
||||||
// BIOS drives a full 32-bit word for a Thumb halfword fetch.
|
fn snapshot_thumb_prefetch(&self, pc: u32) -> Option<u32> {
|
||||||
// Preserve both lanes of the aligned PC+4 word, not a repeated halfword.
|
let address = pc.wrapping_add(4);
|
||||||
InstructionSet::Thumb => self.snapshot_mapped_word((pc + 4) & !3),
|
// Region-crossing fetches need pipeline history, not a rule chosen from the old PC.
|
||||||
|
if address >> 24 != pc >> 24 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match pc >> 24 {
|
||||||
|
// BIOS and OAM drive a full word even for a halfword instruction fetch.
|
||||||
|
0x00 | 0x07 => self.snapshot_mapped_word(address & !3),
|
||||||
|
// These 16-bit regions repeat the fetched halfword in both word lanes.
|
||||||
|
0x02 | 0x05 | 0x06 | 0x08..=0x0d => {
|
||||||
|
let half = u16::from_le_bytes([
|
||||||
|
self.read_mapped_byte(address).ok()?,
|
||||||
|
self.read_mapped_byte(address + 1).ok()?,
|
||||||
|
]);
|
||||||
|
Some(u32::from(half) * 0x0001_0001)
|
||||||
}
|
}
|
||||||
} else {
|
// IWRAM needs the previous bus value, including intervening data loads/DMA.
|
||||||
None
|
// Unsupported code regions must not manufacture an open-bus snapshot.
|
||||||
};
|
_ => None,
|
||||||
self.cpu_access = Some(CpuAccess {
|
}
|
||||||
pc,
|
|
||||||
arm_prefetch,
|
|
||||||
bios_prefetch,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot_mapped_word(&self, address: u32) -> Option<u32> {
|
fn snapshot_mapped_word(&self, address: u32) -> Option<u32> {
|
||||||
|
|
@ -369,7 +377,7 @@ impl Memory {
|
||||||
if succeeded && access.pc < BIOS_SIZE as u32 {
|
if succeeded && access.pc < BIOS_SIZE as u32 {
|
||||||
// Commit only successful instruction snapshots under our diagnostic policy.
|
// Commit only successful instruction snapshots under our diagnostic policy.
|
||||||
// Missing lookahead invalidates history rather than exposing stale bytes.
|
// Missing lookahead invalidates history rather than exposing stale bytes.
|
||||||
self.bios_prefetch = access.bios_prefetch;
|
self.bios_prefetch = access.prefetch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -452,7 +460,7 @@ impl Memory {
|
||||||
.ok_or(MemoryError::Unmapped(address));
|
.ok_or(MemoryError::Unmapped(address));
|
||||||
}
|
}
|
||||||
if matches!(address, 0x0000_4000..=0x01ff_ffff | 0x1000_0000..=0xffff_ffff) {
|
if matches!(address, 0x0000_4000..=0x01ff_ffff | 0x1000_0000..=0xffff_ffff) {
|
||||||
if let Some(word) = self.cpu_access.and_then(|access| access.arm_prefetch) {
|
if let Some(word) = self.cpu_access.and_then(|access| access.prefetch) {
|
||||||
return Ok((word >> ((address & 3) * 8)) as u8);
|
return Ok((word >> ((address & 3) * 8)) as u8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ The repository is a Cargo workspace with three crates:
|
||||||
| `crates/gba-core/src/cpu/load_alias_tests.rs` | Original single-load alias regressions for indexing, banks, widths, alignment, I/O, timing, and diagnostics |
|
| `crates/gba-core/src/cpu/load_alias_tests.rs` | Original single-load alias regressions for indexing, banks, widths, alignment, I/O, timing, and diagnostics |
|
||||||
| `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/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_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/compare_psr_tests.rs` | Original Rd=15 test/compare status restoration, flags, bank, and timing regressions |
|
| `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/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 |
|
| `crates/gba-core/src/cpu/thumb_tests.rs` | Thumb instructions, state switching, and decoder checks |
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ Host inspection, ROM-suite memory assertions, instruction fetches, and DMA do no
|
||||||
|
|
||||||
Limits remain explicit:
|
Limits remain explicit:
|
||||||
|
|
||||||
- Thumb open bus depends on instruction region, alignment, and prior bus data; it remains unsupported.
|
- Thumb open bus uses the supported region rules below. Internal work RAM still needs unmodeled bus history.
|
||||||
- [BIOS-protected reads](bios.md#cpu-bios-read-protection) use separate retained ARM PC+8 or aligned Thumb PC+4 word snapshots.
|
- [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, DMA latches, and disabled-RAM reads are not modeled here.
|
- Unused/write-only I/O reads, DMA latches, and disabled-RAM reads are not modeled here.
|
||||||
- Missing BIOS, truncated ROM, unsupported I/O, and save-memory accesses retain their existing diagnostics.
|
- Missing BIOS, truncated ROM, unsupported I/O, and save-memory accesses retain their existing diagnostics.
|
||||||
|
|
@ -107,6 +107,42 @@ Limits remain explicit:
|
||||||
This subset lets public ARM test 362 complete. That test does not assert the loaded data value.
|
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).
|
Original regressions check the values separately. See [the public ARM result](../public-arm-tests.md).
|
||||||
|
|
||||||
|
## Thumb unused-memory data reads
|
||||||
|
|
||||||
|
Thumb data reads use the same unused address ranges and lane rules as ARM reads above.
|
||||||
|
The snapshot depends on the executing code region. P below is the executing instruction address, not the visible r15 operand.
|
||||||
|
|
||||||
|
| Code region | Snapshot |
|
||||||
|
| --- | --- |
|
||||||
|
| 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) | Unsupported: one lane depends on previous bus activity |
|
||||||
|
| Other code regions | Unsupported |
|
||||||
|
|
||||||
|
For BIOS/OAM, aligned instructions expose halfwords at P+4 and P+6.
|
||||||
|
Instructions at two-byte-only alignment expose halfwords at P+2 and P+4.
|
||||||
|
The two lanes can differ. A universal repeated-halfword rule would be incorrect.
|
||||||
|
|
||||||
|
Only the required mapped bytes are sampled. A 16-bit region needs two lookahead bytes, not four.
|
||||||
|
Normal memory mirrors apply to these bytes, including physical RAM/video-memory wrap within a mapped region.
|
||||||
|
If P+4 crosses a 16 MiB address-region boundary, the snapshot remains unknown.
|
||||||
|
This conservative diagnostic policy avoids guessing pipeline behavior during a region transition.
|
||||||
|
Missing lookahead or unknown history fails only an unused-memory load; ordinary instructions and mapped loads can still execute.
|
||||||
|
|
||||||
|
All loads in one instruction share the snapshot, including block loads and stack loads.
|
||||||
|
Normal sign extension, unaligned-load rotation, writeback, register aliases, and PC-load semantics still apply.
|
||||||
|
Snapshots add no nominal cycles. Host reads, instruction fetches, DMA, and unsupported stores do not gain fallback behavior.
|
||||||
|
Protected BIOS reads use separate retained history, not the current ROM/RAM snapshot.
|
||||||
|
|
||||||
|
GBATEK and nocash's [open-bus findings](https://www.ngemu.com/threads/gba-open-bus.170809/) document these region differences.
|
||||||
|
IWRAM needs prior bus lanes, including data-load overwrites and possible DMA effects.
|
||||||
|
Do not substitute P+2 for that history: it is only the usual case, not a general rule.
|
||||||
|
The original Nintendo DS also differs from GBA-family IWRAM behavior; this core targets GBA.
|
||||||
|
|
||||||
|
This remains a bounded snapshot implementation, not a pipeline or a complete bus-history model.
|
||||||
|
Refill timing, self-modifying code, DMA-to-CPU transitions, IWRAM, 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.
|
||||||
|
|
||||||
## Processor status and exceptions
|
## Processor status and exceptions
|
||||||
|
|
||||||
User and System share registers. Each exception mode has its own stack pointer, link register, and SPSR.
|
User and System share registers. Each exception mode has its own stack pointer, link register, and SPSR.
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ 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.
|
- [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.
|
- [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.
|
- [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.
|
- [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 and region-transition history remain unsupported.
|
||||||
- [nocash GBA open-bus discussion](https://www.ngemu.com/threads/gba-open-bus.170809/), for the distinction between ARM prefetch and region-dependent Thumb values.
|
- [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.
|
- [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).
|
- [GBATEK GBA memory map and bus widths](https://problemkaputt.de/gbatek-gba-memory-map.htm).
|
||||||
- [GBATEK GBA DMA transfers](https://problemkaputt.de/gbatek-gba-dma-transfers.htm).
|
- [GBATEK GBA DMA transfers](https://problemkaputt.de/gbatek-gba-dma-transfers.htm).
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,8 @@ Host reads remain raw inspection. CPU data loads outside BIOS select lanes from
|
||||||
Snapshots add no data accesses or nominal cycles.
|
Snapshots add no data accesses or nominal cycles.
|
||||||
|
|
||||||
The snapshot does not create a persistent instruction pipeline.
|
The snapshot does not create a persistent instruction pipeline.
|
||||||
It does not model refill-time latches, BIOS data-access ordering within an instruction, DMA bus transitions, or general Thumb open bus.
|
It does not model refill-time latches, BIOS data-access ordering within an instruction, or DMA bus transitions.
|
||||||
|
A subsequent [Thumb open-bus subset](../hardware/cpu.md#thumb-unused-memory-data-reads) adds region-dependent snapshots outside protected BIOS reads.
|
||||||
At BIOS boundaries, unavailable lookahead invalidates history under the existing diagnostic policy.
|
At BIOS boundaries, unavailable lookahead invalidates history under the existing diagnostic policy.
|
||||||
A branch can still succeed; a later protected read reports unknown history rather than exposing stale bytes.
|
A branch can still succeed; a later protected read reports unknown history rather than exposing stale bytes.
|
||||||
This boundary policy is conservative, not a hardware claim.
|
This boundary policy is conservative, not a hardware claim.
|
||||||
|
|
@ -61,7 +62,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.
|
- 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.
|
- Raw reads inside BIOS and protected reads outside it.
|
||||||
- The last complete fetch word, unavailable lookahead, and failure retention.
|
- The last complete fetch word, unavailable lookahead, and failure retention.
|
||||||
- Separate host reads, unchanged general Thumb open-bus diagnostics, nominal costs, and device progression.
|
- Separate host reads, retained IWRAM Thumb open-bus diagnostics, nominal costs, and device progression.
|
||||||
|
|
||||||
## Validation result
|
## Validation result
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ Update this file when a feature lands or a limit is removed.
|
||||||
- The original BIOS exposes documented boot, SoftReset, SWI, and IRQ protected-read words through its actual exit layout.
|
- The original BIOS exposes documented boot, SoftReset, SWI, and IRQ protected-read words through its actual exit layout.
|
||||||
- ARM test/compare Rd=15 status restoration, without a PC result write or nominal refill.
|
- 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.
|
- 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.
|
||||||
- Little-endian byte, halfword, and word reads/writes.
|
- Little-endian byte, halfword, and word reads/writes.
|
||||||
- ARM7TDMI word-load rotation, aligned stores, and odd-address halfword-load behavior.
|
- 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.
|
- Explicit errors for unsupported instructions when their condition passes, and for unsupported memory accesses.
|
||||||
|
|
@ -130,7 +131,8 @@ 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.
|
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).
|
ARM data reads from unused address ranges use a bounded [PC+8 open-bus snapshot](hardware/cpu.md#arm-unused-memory-data-reads).
|
||||||
This is not a fetch pipeline. General Thumb, DMA, and unused/write-only I/O open bus remain unmodeled.
|
Thumb reads use [region-dependent snapshots](hardware/cpu.md#thumb-unused-memory-data-reads) where the documented value needs no prior bus history.
|
||||||
|
This is not a fetch pipeline. Thumb IWRAM, region-crossing fetches, DMA, and unused/write-only I/O open bus remain unsupported.
|
||||||
BIOS protection uses separate retained BIOS fetch snapshots as described in [BIOS behavior](hardware/bios.md#cpu-bios-read-protection).
|
BIOS protection uses separate retained BIOS fetch snapshots as described in [BIOS behavior](hardware/bios.md#cpu-bios-read-protection).
|
||||||
Host inspection and instruction fetches remain strict. Other unmapped reads return errors.
|
Host inspection and instruction fetches remain strict. Other unmapped reads return errors.
|
||||||
Reads beyond the supplied cartridge bytes also return errors.
|
Reads beyond the supplied cartridge bytes also return errors.
|
||||||
|
|
@ -140,7 +142,8 @@ Writes to cartridge addresses return errors instead of modeling cartridge hardwa
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
1. Extend general open-bus support with documented Thumb region/alignment rules and explicit limits for history-dependent cases.
|
1. Add validated prior-bus history for Thumb IWRAM open bus, including data-load overwrites and DMA transitions.
|
||||||
|
Keep unknown history diagnostic until those interactions have independent coverage.
|
||||||
Validate BIOS snapshots with independent hardware tests before claiming exact refill or data-access history.
|
Validate BIOS snapshots with independent hardware tests before claiming exact refill or data-access history.
|
||||||
The desktop runner still uses only the original BIOS. Keep protected reads image-derived for all supplied images.
|
The desktop runner still uses only the original BIOS. Keep protected reads image-derived for all supplied images.
|
||||||
2. Refine nominal timing with a fetch pipeline, Game Pak prefetch, per-access device updates, and verified timer/IRQ delays.
|
2. Refine nominal timing with a fetch pipeline, Game Pak prefetch, per-access device updates, and verified timer/IRQ delays.
|
||||||
|
|
|
||||||
|
|
@ -65,7 +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.
|
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.
|
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.
|
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/Thumb/DMA diagnostics.
|
They check branch-target resampling, missing lookahead, skipped conditions, nominal timing, device progress, and retained host/fetch/IWRAM-Thumb/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. IWRAM and region-crossing tests retain explicit diagnostics.
|
||||||
|
Host/fetch isolation, separate BIOS history, unsupported stores/data regions, nominal device progression, and DMA diagnostics also have checks.
|
||||||
The pinned public ARM ROM now reaches its completion checkpoint with r12 = 0 in debug and release builds.
|
The pinned public ARM ROM now reaches its completion checkpoint with r12 = 0 in debug and release builds.
|
||||||
The pinned public Thumb ROM also reaches its ARM-state checkpoint with r7 = 0 in both builds.
|
The pinned public Thumb ROM also reaches its ARM-state checkpoint with r7 = 0 in both builds.
|
||||||
Empty-list Thumb regressions cover PC+6, all low bases and processor modes, banked stacks, both code alignments, and unaligned data.
|
Empty-list Thumb regressions cover PC+6, all low bases and processor modes, banked stacks, both code alignments, and unaligned data.
|
||||||
|
|
@ -224,7 +228,7 @@ They cover all processor modes, load widths, lanes, rotation, both Thumb code al
|
||||||
They verify direct reads while executing inside BIOS, re-entry, SWI/IRQ returns, host-inspection isolation, and original-boot provenance.
|
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.
|
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 history, last valid fetch words, missing lookahead, and diagnostic-step retention have explicit checks.
|
||||||
General Thumb unused-memory reads remain diagnostics; supporting Thumb BIOS snapshots does not enable general open bus.
|
Thumb IWRAM unused-memory reads remain diagnostics. Separate tests cover supported general Thumb snapshots and retained BIOS history.
|
||||||
Protected loads keep normal nominal costs and advance devices only through machine stepping.
|
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.
|
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.
|
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