feat(memory): support ARM unused-memory open-bus reads
Snapshot mapped PC+8 bytes for ARM data reads in the documented unused ranges. Preserve strict host/fetch/Thumb/DMA diagnostics, missing-ROM errors, existing write rules, and nominal timing. Add original regressions and document the bounded model. The unmodified pinned jsmolka ARM ROM now reaches its verified checkpoint with r12=0 in debug and release: 1344 steps and 11257 nominal cycles. No ROM, manifest, or assertion changes. Validated on Darwin arm64: 842 workspace tests in debug and release; 773 core/demo tests separately; Python adapter tests; native ROM windows; all ten demo window smoke tests; fmt, clippy, and rustdoc.
This commit is contained in:
parent
ab625dc55d
commit
a9493d4cc6
11 changed files with 418 additions and 26 deletions
|
|
@ -34,7 +34,7 @@ Window smoke tests (`--*-smoke-test`) need a logged-in desktop session. Details
|
||||||
| Working on | Read |
|
| Working on | Read |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| ARM/Thumb instructions, modes, exceptions, timing | [docs/hardware/cpu.md](docs/hardware/cpu.md) |
|
| ARM/Thumb instructions, modes, exceptions, timing | [docs/hardware/cpu.md](docs/hardware/cpu.md) |
|
||||||
| Pinned public ARM tests and known compatibility failures | [docs/public-arm-tests.md](docs/public-arm-tests.md) |
|
| Pinned public ARM tests, results, and compatibility limits | [docs/public-arm-tests.md](docs/public-arm-tests.md) |
|
||||||
| ROM test suites, assertions, and JSON reports | [docs/rom-tests.md](docs/rom-tests.md) |
|
| ROM test suites, assertions, and JSON reports | [docs/rom-tests.md](docs/rom-tests.md) |
|
||||||
| ROM files, window execution, and terminal diagnostics | [docs/hardware/cartridge.md](docs/hardware/cartridge.md) |
|
| ROM files, window execution, and terminal diagnostics | [docs/hardware/cartridge.md](docs/hardware/cartridge.md) |
|
||||||
| BIOS services (SWI) | [docs/hardware/bios.md](docs/hardware/bios.md) |
|
| BIOS services (SWI) | [docs/hardware/bios.md](docs/hardware/bios.md) |
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ Use `--test-suite suite.json` for [repeatable headless ROM assertions](docs/rom-
|
||||||
| [docs/development.md](docs/development.md) | Nix, direnv, and macOS build notes |
|
| [docs/development.md](docs/development.md) | Nix, direnv, and macOS build notes |
|
||||||
| [docs/testing.md](docs/testing.md) | Validation commands and what the test suites cover |
|
| [docs/testing.md](docs/testing.md) | Validation commands and what the test suites cover |
|
||||||
| [docs/rom-tests.md](docs/rom-tests.md) | Headless ROM suites, completion checks, assertions, and JSON reports |
|
| [docs/rom-tests.md](docs/rom-tests.md) | Headless ROM suites, completion checks, assertions, and JSON reports |
|
||||||
| [docs/public-arm-tests.md](docs/public-arm-tests.md) | Pinned public ARM test, preparation command, and known failures |
|
| [docs/public-arm-tests.md](docs/public-arm-tests.md) | Pinned public ARM test, preparation command, results, and limits |
|
||||||
| [docs/architecture.md](docs/architecture.md) | Crate boundaries and a file-by-file map |
|
| [docs/architecture.md](docs/architecture.md) | Crate boundaries and a file-by-file map |
|
||||||
| [docs/hardware/cpu.md](docs/hardware/cpu.md) | ARM/Thumb execution rules, modes, exceptions, and timing |
|
| [docs/hardware/cpu.md](docs/hardware/cpu.md) | ARM/Thumb execution rules, modes, exceptions, and timing |
|
||||||
| [docs/hardware/cartridge.md](docs/hardware/cartridge.md) | ROM loading, window/input controls, original ROM generator, and diagnostics |
|
| [docs/hardware/cartridge.md](docs/hardware/cartridge.md) | ROM loading, window/input controls, original ROM generator, and diagnostics |
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ mod instruction_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod load_alias_tests;
|
mod load_alias_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
mod open_bus_tests;
|
||||||
|
#[cfg(test)]
|
||||||
mod status_tests;
|
mod status_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
@ -200,7 +202,7 @@ impl Cpu {
|
||||||
fn execute_fetched(&mut self, instruction: u32, memory: &mut Memory) -> Result<(), CpuError> {
|
fn execute_fetched(&mut self, instruction: u32, memory: &mut Memory) -> Result<(), CpuError> {
|
||||||
// Record the executing instruction, not an operand's pipelined PC value.
|
// Record the executing instruction, not an operand's pipelined PC value.
|
||||||
// Always clear the access context, including on diagnostic errors.
|
// Always clear the access context, including on diagnostic errors.
|
||||||
memory.begin_cpu_access(self.pc());
|
memory.begin_cpu_access(self.pc(), self.instruction_set);
|
||||||
let result = self.execute_instruction(instruction, memory);
|
let result = self.execute_instruction(instruction, memory);
|
||||||
memory.end_cpu_access();
|
memory.end_cpu_access();
|
||||||
result
|
result
|
||||||
|
|
|
||||||
304
crates/gba-core/src/cpu/open_bus_tests.rs
Normal file
304
crates/gba-core/src/cpu/open_bus_tests.rs
Normal file
|
|
@ -0,0 +1,304 @@
|
||||||
|
//! Original regressions for the ARM unused-memory open-bus subset.
|
||||||
|
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: u32 = 0xe590_1000; // LDR r1, [r0]
|
||||||
|
|
||||||
|
fn memory(instruction: u32) -> Memory {
|
||||||
|
let words = [instruction, 0xe1a0_0000, DATA];
|
||||||
|
Memory::new(words.into_iter().flat_map(u32::to_le_bytes).collect()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arm_unused_ranges_return_pc_plus_eight_not_address_mirrors() {
|
||||||
|
for address in [
|
||||||
|
0x0000_4000,
|
||||||
|
0x0000_fffc,
|
||||||
|
0x00ff_fffc,
|
||||||
|
0x0100_0000,
|
||||||
|
0x01ff_fffc,
|
||||||
|
0x1000_0000,
|
||||||
|
0x1800_0000,
|
||||||
|
0x8000_0000,
|
||||||
|
0xffff_fffc,
|
||||||
|
] {
|
||||||
|
let mut bus = memory(LDR);
|
||||||
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
|
cpu.registers[0] = address;
|
||||||
|
let mut expected = cpu.clone();
|
||||||
|
expected.registers[1] = DATA;
|
||||||
|
expected.registers[15] += 4;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu, expected, "{address:#010x}");
|
||||||
|
// Inspection outside CPU execution remains strict, including every lane.
|
||||||
|
for low in 0..4 {
|
||||||
|
assert_eq!(
|
||||||
|
bus.read8(address + low),
|
||||||
|
Err(MemoryError::Unmapped(address + low))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(bus.read16(address), Err(MemoryError::Unmapped(address)));
|
||||||
|
assert_eq!(bus.read32(address), Err(MemoryError::Unmapped(address)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn open_bus_loads_select_lanes_rotate_and_sign_extend_normally() {
|
||||||
|
// Independent expectations for bytes [a5,80,33,f2].
|
||||||
|
for (instruction, values) in [
|
||||||
|
(LDR, [0xf233_80a5, 0xa5f2_3380, 0x80a5_f233, 0x3380_a5f2]),
|
||||||
|
(0xe5d0_1000, [0xa5, 0x80, 0x33, 0xf2]), // LDRB
|
||||||
|
(0xe1d0_10b0, [0x80a5, 0xa500_0080, 0xf233, 0x3300_00f2]), // LDRH
|
||||||
|
(0xe1d0_10d0, [0xffff_ffa5, 0xffff_ff80, 0x33, 0xffff_fff2]), // LDRSB
|
||||||
|
(
|
||||||
|
0xe1d0_10f0,
|
||||||
|
[0xffff_80a5, 0xffff_ff80, 0xffff_f233, 0xffff_fff2],
|
||||||
|
), // LDRSH
|
||||||
|
] {
|
||||||
|
for (low, value) in values.into_iter().enumerate() {
|
||||||
|
let mut bus = memory(instruction);
|
||||||
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
|
cpu.registers[0] = UNUSED + low as u32;
|
||||||
|
cpu.apply_status(0xb000_00df, Mode::System);
|
||||||
|
let mut expected = cpu.clone();
|
||||||
|
expected.registers[1] = value;
|
||||||
|
expected.registers[15] += 4;
|
||||||
|
let timing = cpu.step_timed(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu, expected, "{instruction:#010x}, lane {low}");
|
||||||
|
assert_eq!(timing.data_cycles, 1);
|
||||||
|
assert_eq!(timing.internal_cycles, 1);
|
||||||
|
assert_eq!(timing.code_cycles, 8); // ROM boundary; no extra lookahead cost
|
||||||
|
assert_eq!(bus.cycles(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefetch_snapshot_uses_the_executing_region_and_rom_window() {
|
||||||
|
for pc in [
|
||||||
|
0x100,
|
||||||
|
RAM,
|
||||||
|
0x0300_0100,
|
||||||
|
0x0500_0100,
|
||||||
|
0x0600_0100,
|
||||||
|
0x0700_0100,
|
||||||
|
ROM_START,
|
||||||
|
0x0a00_0000,
|
||||||
|
0x0c00_0000,
|
||||||
|
] {
|
||||||
|
let mut bus = if pc < BIOS_SIZE as u32 {
|
||||||
|
let mut bios = vec![0; BIOS_SIZE];
|
||||||
|
bios[pc as usize..pc as usize + 4].copy_from_slice(&LDR.to_le_bytes());
|
||||||
|
bios[pc as usize + 8..pc as usize + 12].copy_from_slice(&DATA.to_le_bytes());
|
||||||
|
Memory::with_bios(vec![], bios).unwrap()
|
||||||
|
} else if pc >= ROM_START {
|
||||||
|
memory(LDR)
|
||||||
|
} else {
|
||||||
|
let mut bus = Memory::new(vec![]).unwrap();
|
||||||
|
bus.write32(pc, LDR).unwrap();
|
||||||
|
bus.write32(pc + 8, DATA).unwrap();
|
||||||
|
bus
|
||||||
|
};
|
||||||
|
let mut cpu = Cpu::new(pc);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
let timing = cpu.step_timed(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[1], DATA, "{pc:#010x}");
|
||||||
|
assert_eq!(timing.data_cycles, 1);
|
||||||
|
assert_eq!(
|
||||||
|
timing.code_cycles,
|
||||||
|
bus_cycles(0, pc, AccessWidth::Word, AccessKind::Sequential)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rrx_offset_load_preserves_carry_and_writes_back_the_high_address() {
|
||||||
|
let mut bus = memory(0xe7b1_2060); // LDR r2, [r1, r0, RRX]!
|
||||||
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
|
cpu.flags.carry = true;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], 0);
|
||||||
|
assert_eq!(cpu.registers[1], UNUSED);
|
||||||
|
assert_eq!(cpu.registers[2], DATA);
|
||||||
|
assert_eq!(cpu.cpsr(), 0x2000_001f);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_loads_share_one_prefetch_word_and_single_load_aliases_still_work() {
|
||||||
|
let mut bus = memory(0xe8b0_000e); // LDMIA r0!, {r1-r3}
|
||||||
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
let timing = cpu.step_timed(&mut bus).unwrap();
|
||||||
|
assert_eq!(&cpu.registers[1..4], &[DATA; 3]);
|
||||||
|
assert_eq!(cpu.registers[0], UNUSED + 12);
|
||||||
|
assert_eq!(timing.data_cycles, 3);
|
||||||
|
|
||||||
|
let mut bus = memory(0xe5b0_0004); // LDR r0, [r0, #4]!
|
||||||
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pc_load_uses_old_pc_snapshot_then_resamples_at_the_branch_target() {
|
||||||
|
let mut bus = memory(0xe590_f000); // LDR pc, [r0]
|
||||||
|
bus.write32(RAM, 0xe590_f000).unwrap();
|
||||||
|
bus.write32(RAM + 8, RAM + 0x40).unwrap();
|
||||||
|
bus.write32(RAM + 0x40, LDR).unwrap();
|
||||||
|
bus.write32(RAM + 0x48, DATA).unwrap();
|
||||||
|
let mut cpu = Cpu::new(RAM);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.pc(), RAM + 0x40);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[1], DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_prefetch_only_errors_when_an_unused_read_needs_it() {
|
||||||
|
for size in 4..12 {
|
||||||
|
let mut rom = vec![0; size];
|
||||||
|
rom[..4].copy_from_slice(&LDR.to_le_bytes());
|
||||||
|
let mut bus = Memory::new(rom).unwrap();
|
||||||
|
bus.write32(RAM, DATA).unwrap();
|
||||||
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
let before = cpu.clone();
|
||||||
|
assert_eq!(
|
||||||
|
cpu.step_timed(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||||
|
);
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
assert_eq!(bus.read32(UNUSED), Err(MemoryError::Unmapped(UNUSED)));
|
||||||
|
cpu.registers[0] = RAM;
|
||||||
|
let timing = cpu.step_timed(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[1], DATA);
|
||||||
|
assert_eq!(timing.data_cycles, 6); // No trace leaked from failed step.
|
||||||
|
}
|
||||||
|
// PC+8 is itself unused. Do not recursively manufacture a prefetch word.
|
||||||
|
let mut bios = vec![0; BIOS_SIZE];
|
||||||
|
bios[BIOS_SIZE - 8..BIOS_SIZE - 4].copy_from_slice(&LDR.to_le_bytes());
|
||||||
|
let mut bus = Memory::with_bios(vec![], bios).unwrap();
|
||||||
|
let mut cpu = Cpu::new(BIOS_SIZE as u32 - 8);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
assert_eq!(
|
||||||
|
cpu.step(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn skipped_loads_need_no_prefetch_and_charge_no_data_cycles() {
|
||||||
|
let mut bus = Memory::new((LDR & 0x0fff_ffff).to_le_bytes().to_vec()).unwrap(); // LDREQ
|
||||||
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
let timing = cpu.step_timed(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.pc(), ROM_START + 4);
|
||||||
|
assert_eq!(cpu.registers[1], 0);
|
||||||
|
assert_eq!(timing.data_cycles, 0);
|
||||||
|
assert_eq!(timing.internal_cycles, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_bios_rom_save_and_unsupported_io_reads_remain_errors() {
|
||||||
|
for address in [
|
||||||
|
0,
|
||||||
|
0x3ffc,
|
||||||
|
0x0400_00e0,
|
||||||
|
ROM_START + 12,
|
||||||
|
0x0e00_0000,
|
||||||
|
0x0fff_fffc,
|
||||||
|
] {
|
||||||
|
let mut bus = memory(LDR);
|
||||||
|
let mut cpu = Cpu::new(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);
|
||||||
|
assert_eq!(bus.read32(UNUSED), Err(MemoryError::Unmapped(UNUSED)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn thumb_reads_and_unused_instruction_fetches_do_not_reuse_arm_context() {
|
||||||
|
let mut bus = memory(LDR);
|
||||||
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
// A successful ARM read must not leave a context for later fetches or Thumb.
|
||||||
|
let mut invalid = Cpu::new(UNUSED);
|
||||||
|
assert_eq!(
|
||||||
|
invalid.step(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||||
|
);
|
||||||
|
bus.write16(RAM, 0x6801).unwrap(); // Thumb LDR r1, [r0]
|
||||||
|
cpu.registers[15] = RAM;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unused_stores_and_swaps_remain_atomic_diagnostics() {
|
||||||
|
for instruction in [0xe580_1000, 0xe5c0_1000, 0xe1c0_10b0, 0xe100_1092] {
|
||||||
|
let mut bus = memory(instruction); // STR, STRB, STRH, SWP
|
||||||
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
cpu.registers[1] = 0x1234;
|
||||||
|
let before = cpu.clone();
|
||||||
|
assert_eq!(
|
||||||
|
cpu.step(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(UNUSED)))
|
||||||
|
);
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
assert_eq!(bus.read32(UNUSED), Err(MemoryError::Unmapped(UNUSED)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn machine_charges_one_data_access_and_dma_does_not_inherit_cpu_context() {
|
||||||
|
let mut bus = memory(LDR);
|
||||||
|
bus.write32(TIMER_BASE, 0x0080_0000).unwrap();
|
||||||
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
let mut machine = Machine::new(cpu, bus);
|
||||||
|
assert_eq!(machine.step().unwrap(), StepKind::Instruction);
|
||||||
|
assert_eq!(machine.cpu().registers[1], DATA);
|
||||||
|
assert_eq!(machine.cycles(), 10); // 8 code + 1 data + 1 internal
|
||||||
|
assert_eq!(machine.memory().read16(TIMER_BASE).unwrap(), 10);
|
||||||
|
let before = machine.cpu().clone();
|
||||||
|
let timing = machine.last_timing();
|
||||||
|
// DMA3 exposes all 28 source address bits. Save memory is still unsupported.
|
||||||
|
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(), 10);
|
||||||
|
assert_eq!(machine.last_timing(), timing);
|
||||||
|
assert_eq!(machine.memory().read32(RAM).unwrap(), 0);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{cell::Cell, error::Error, fmt};
|
use std::{cell::Cell, error::Error, fmt};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
cpu::InstructionSet,
|
||||||
display::{DisplayPosition, CYCLES_PER_LINE, HBLANK_START, VISIBLE_LINES},
|
display::{DisplayPosition, CYCLES_PER_LINE, HBLANK_START, VISIBLE_LINES},
|
||||||
dma::{DmaError, DMA_BASE, DMA_END},
|
dma::{DmaError, DMA_BASE, DMA_END},
|
||||||
input::Buttons,
|
input::Buttons,
|
||||||
|
|
@ -52,8 +53,15 @@ fn vram_index(address: u32) -> usize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct CpuAccess {
|
||||||
|
pc: u32,
|
||||||
|
arm_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.
|
||||||
/// BIOS read protection and open-bus behavior are not modeled.
|
/// ARM data reads from unused memory return a PC+8 snapshot when available.
|
||||||
|
/// BIOS protection, Thumb open bus, 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>,
|
||||||
|
|
@ -65,7 +73,7 @@ pub struct Memory {
|
||||||
io: Io,
|
io: Io,
|
||||||
cycles: u64,
|
cycles: u64,
|
||||||
data_timing: Cell<Option<DataTiming>>,
|
data_timing: Cell<Option<DataTiming>>,
|
||||||
cpu_access: Option<u32>,
|
cpu_access: Option<CpuAccess>,
|
||||||
scanline_capture: Option<video::capture::Capture>,
|
scanline_capture: Option<video::capture::Capture>,
|
||||||
sprite_pipeline: pipeline::SpritePipeline,
|
sprite_pipeline: pipeline::SpritePipeline,
|
||||||
}
|
}
|
||||||
|
|
@ -317,9 +325,25 @@ impl Memory {
|
||||||
self.io.next_event_cycles()
|
self.io.next_event_cycles()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn begin_cpu_access(&mut self, pc: u32) {
|
pub(crate) fn begin_cpu_access(&mut self, pc: u32, instruction_set: InstructionSet) {
|
||||||
debug_assert!(self.cpu_access.is_none());
|
debug_assert!(self.cpu_access.is_none());
|
||||||
self.cpu_access = Some(pc);
|
// Sample before execution, without data timing or open-bus recursion.
|
||||||
|
// Missing lookahead bytes must not fail an instruction that never uses them.
|
||||||
|
let arm_prefetch = match instruction_set {
|
||||||
|
InstructionSet::Arm => self.snapshot_arm_prefetch(pc),
|
||||||
|
InstructionSet::Thumb => None,
|
||||||
|
};
|
||||||
|
self.cpu_access = Some(CpuAccess { pc, arm_prefetch });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snapshot_arm_prefetch(&self, pc: u32) -> Option<u32> {
|
||||||
|
let mut bytes = [0; 4];
|
||||||
|
for (offset, byte) in bytes.iter_mut().enumerate() {
|
||||||
|
*byte = self
|
||||||
|
.read_mapped_byte(pc.wrapping_add(8 + offset as u32))
|
||||||
|
.ok()?;
|
||||||
|
}
|
||||||
|
Some(u32::from_le_bytes(bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn end_cpu_access(&mut self) {
|
pub(crate) fn end_cpu_access(&mut self) {
|
||||||
|
|
@ -328,7 +352,8 @@ impl Memory {
|
||||||
|
|
||||||
fn can_write_power_control(&self) -> bool {
|
fn can_write_power_control(&self) -> bool {
|
||||||
// Bare memory writes are host/debug setup. CPU code must execute in BIOS.
|
// Bare memory writes are host/debug setup. CPU code must execute in BIOS.
|
||||||
self.cpu_access.is_none_or(|pc| pc < BIOS_SIZE as u32)
|
self.cpu_access
|
||||||
|
.is_none_or(|access| access.pc < BIOS_SIZE as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Current scanline position and frame/VBlank event counters.
|
/// Current scanline position and frame/VBlank event counters.
|
||||||
|
|
@ -390,6 +415,16 @@ impl Memory {
|
||||||
|
|
||||||
// Multi-byte reads use this helper to avoid charging each byte as a bus access.
|
// Multi-byte reads use this helper to avoid charging each byte as a bus access.
|
||||||
fn read_byte(&self, address: u32) -> Result<u8, MemoryError> {
|
fn read_byte(&self, address: u32) -> Result<u8, MemoryError> {
|
||||||
|
if matches!(address, 0x0000_4000..=0x01ff_ffff | 0x1000_0000..=0xffff_ffff) {
|
||||||
|
if let Some(word) = self.cpu_access.and_then(|access| access.arm_prefetch) {
|
||||||
|
return Ok((word >> ((address & 3) * 8)) as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.read_mapped_byte(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strict lookup for host access, instruction fetches, and prefetch snapshots.
|
||||||
|
fn read_mapped_byte(&self, address: u32) -> Result<u8, MemoryError> {
|
||||||
match address >> 24 {
|
match address >> 24 {
|
||||||
0x00 => self
|
0x00 => self
|
||||||
.bios
|
.bios
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,36 @@ On ARM7, an empty register list transfers PC but uses a 64-byte span for address
|
||||||
With writeback enabled, `STM` stores the old base only when that register is first in the list.
|
With writeback enabled, `STM` stores the old base only when that register is first in the list.
|
||||||
Otherwise, `STM` stores the updated base.
|
Otherwise, `STM` stores the updated base.
|
||||||
|
|
||||||
|
## ARM unused-memory data reads
|
||||||
|
|
||||||
|
During ARM execution, data reads from `0x00004000..0x01ffffff` and `0x10000000..0xffffffff` return an open-bus value.
|
||||||
|
Open bus means the bus retains previously driven data. GBATEK identifies this ARM value as the instruction word at PC+8.
|
||||||
|
These high addresses do not mirror BIOS, RAM, or cartridge addresses.
|
||||||
|
|
||||||
|
The interpreter snapshots mapped bytes at PC+8 before executing each ARM instruction.
|
||||||
|
All unused-memory reads within that instruction use the same snapshot.
|
||||||
|
Byte and halfword reads select their addressed lanes; the CPU applies normal rotation and sign extension.
|
||||||
|
Base writeback, register aliases, block loads, PC loads, and flags retain their existing instruction semantics.
|
||||||
|
Each data access pays the nominal unused-region cost of one cycle, plus normal code and internal costs.
|
||||||
|
Snapshot reads add no data accesses or device cycles. This is not a simulated prefetch pipeline or Game Pak prefetch.
|
||||||
|
|
||||||
|
The snapshot uses strict mapped reads, without recursive open-bus fallback.
|
||||||
|
If any snapshot byte is unavailable, an unused-memory data read retains its `Unmapped` diagnostic.
|
||||||
|
Missing lookahead does not fail ordinary instructions, mapped loads, or skipped conditional loads.
|
||||||
|
The CPU clears the access context after each instruction, including errors.
|
||||||
|
Host inspection, ROM-suite memory assertions, instruction fetches, and DMA do not inherit this context.
|
||||||
|
|
||||||
|
Limits remain explicit:
|
||||||
|
|
||||||
|
- Thumb open bus depends on instruction region, alignment, and prior bus data; it remains unsupported.
|
||||||
|
- BIOS-protected reads, unused/write-only I/O reads, DMA latches, and disabled-RAM reads are not modeled here.
|
||||||
|
- Missing BIOS, truncated ROM, unsupported I/O, and save-memory accesses retain their existing diagnostics.
|
||||||
|
- Unused-memory writes remain diagnostics rather than ignored hardware writes. Swaps cannot silently discard their write.
|
||||||
|
- No persistent pipeline or bus history exists. Self-modifying code and DMA-to-CPU bus transitions are not hardware-accurate.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
## 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.
|
||||||
|
|
@ -144,7 +174,7 @@ The new settings apply to subsequent code accesses.
|
||||||
Important timing limits:
|
Important timing limits:
|
||||||
|
|
||||||
- Code S/N counts follow instruction summaries, not a simulated fetch pipeline.
|
- Code S/N counts follow instruction summaries, not a simulated fetch pipeline.
|
||||||
- Ordinary code costs use the current instruction address. No speculative PC+8 fetch or startup pipeline fill is performed.
|
- 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.
|
- PC writes use destination-region costs and the restored instruction width for nominal refill accesses.
|
||||||
- Refill cost calculation does not read target bytes. An invalid branch target fails on the following instruction fetch.
|
- Refill cost calculation does not read target bytes. An invalid branch target fails on the following instruction fetch.
|
||||||
- Game Pak prefetch is not implemented. WAITCNT bit 14 is stored but does not accelerate execution.
|
- Game Pak prefetch is not implemented. WAITCNT bit 14 is stored but does not accelerate execution.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
The headless runner now executes the unmodified ARM ROM from [jsmolka/gba-tests](https://github.com/jsmolka/gba-tests).
|
The headless runner now executes the unmodified ARM ROM from [jsmolka/gba-tests](https://github.com/jsmolka/gba-tests).
|
||||||
This is independent public test code, not a complete ARM7TDMI conformance suite or a new hardware measurement.
|
This is independent public test code, not a complete ARM7TDMI conformance suite or a new hardware measurement.
|
||||||
The full public ARM test still fails. Compare/status and load/writeback alias errors are fixed; the next memory-access failure is recorded below.
|
The pinned public ARM ROM now passes its result checkpoint on Darwin arm64 in debug and release builds.
|
||||||
|
Compare/status, load/writeback aliases, and ARM unused-memory reads are fixed. Earlier failures are retained below.
|
||||||
|
|
||||||
## Prepare and run
|
## Prepare and run
|
||||||
|
|
||||||
|
|
@ -61,7 +62,7 @@ The instruction at the checkpoint is checked during preparation, in addition to
|
||||||
|
|
||||||
The case budget is 1,000,000 successful machine steps, including our original BIOS boot.
|
The case budget is 1,000,000 successful machine steps, including our original BIOS boot.
|
||||||
Timeouts, emulator diagnostics, and nonzero r12 all remain failures under the [normal suite rules](rom-tests.md).
|
Timeouts, emulator diagnostics, and nonzero r12 all remain failures under the [normal suite rules](rom-tests.md).
|
||||||
Preparation does not mean the test passed. The current run exits nonzero and writes a failed JSON report.
|
Preparation does not mean the test passed. Check the runner's exit status and JSON assertions separately.
|
||||||
|
|
||||||
## Observed results on Darwin arm64
|
## Observed results on Darwin arm64
|
||||||
|
|
||||||
|
|
@ -85,7 +86,7 @@ After the compare/status fix, before load/writeback alias support:
|
||||||
- This is `LDR r0, [r0, #4]!` in upstream test 360, with base and destination equal.
|
- This is `LDR r0, [r0, #4]!` in upstream test 360, with base and destination equal.
|
||||||
- The CPU is in System/ARM state, and r12 remains zero. The final completion checkpoint has not been reached.
|
- The CPU is in System/ARM state, and r12 remains zero. The final completion checkpoint has not been reached.
|
||||||
|
|
||||||
After load/writeback alias support:
|
After load/writeback alias support, before ARM unused-memory reads:
|
||||||
|
|
||||||
- Tests 360 and 361 complete without taking their failure paths.
|
- Tests 360 and 361 complete without taking their failure paths.
|
||||||
- After 877 successful steps, the runner reports `unmapped memory at 0x80000000`.
|
- After 877 successful steps, the runner reports `unmapped memory at 0x80000000`.
|
||||||
|
|
@ -95,9 +96,20 @@ After load/writeback alias support:
|
||||||
|
|
||||||
Single-load aliases now retain the loaded value rather than the updated base.
|
Single-load aliases now retain the loaded value rather than the updated base.
|
||||||
Original regressions also cover byte, halfword, and signed loads, every non-PC register bank, index modes, offsets, alignment, timing, and failures.
|
Original regressions also cover byte, halfword, and signed loads, every non-PC register bank, index modes, offsets, alignment, timing, and failures.
|
||||||
The public halfword alias tests later in the ROM have not yet been reached; their source and mGBA's load ordering support the same rule.
|
|
||||||
The next compatibility task is to verify high-address read behavior and implement the appropriate memory mapping or open-bus behavior.
|
After ARM unused-memory open-bus support:
|
||||||
Do not bypass test 362, invent a return value, or weaken the result assertion.
|
|
||||||
|
- Test 362 completes. The read returns the ARM word at the executing PC plus eight, not a high-address mirror.
|
||||||
|
- Execution also reaches the later halfword alias and block-transfer tests without taking their failure exits.
|
||||||
|
- The runner reaches `eval` at `0x08001d4c` after 1,344 successful steps, including original BIOS boot.
|
||||||
|
- The CPU is in System/ARM state, with CPSR `0x6000001f`, r12 = 0, and 11,257 nominal cycles.
|
||||||
|
- Debug and release runs exit zero, with `reason: checkpoint`, no error, and the result assertion passing.
|
||||||
|
- The pinned ROM, manifest checkpoint, budget, and expected result are unchanged.
|
||||||
|
|
||||||
|
Test 362 checks shifted addressing, writeback, and carry, but does not check the loaded value.
|
||||||
|
Original regressions separately check the returned word, byte lanes, rotation, sign extension, timing, and retained diagnostics.
|
||||||
|
See the bounded [ARM open-bus implementation](hardware/cpu.md#arm-unused-memory-data-reads).
|
||||||
|
This is a passing result for this pinned ARM ROM, not proof of complete instruction, memory, or timing compatibility.
|
||||||
No public Thumb, timing, memory, BIOS, graphics, or unsafe suite has been claimed as passing.
|
No public Thumb, timing, memory, BIOS, graphics, or unsafe suite has been claimed as passing.
|
||||||
These results do not establish Pokémon Emerald compatibility.
|
These results do not establish Pokémon Emerald compatibility.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,9 @@ 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 and video byte writes](https://problemkaputt.de/gbatek-gba-unpredictable-things.htm).
|
- [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, and the distinct Thumb region/history rules.
|
||||||
|
- [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.
|
||||||
|
- [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.
|
||||||
- [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).
|
||||||
- [mGBA DMA implementation](https://github.com/mgba-emu/mgba/blob/master/src/gba/dma.c), used to cross-check masks, repeat behavior, and Game Pak source increments.
|
- [mGBA DMA implementation](https://github.com/mgba-emu/mgba/blob/master/src/gba/dma.c), used to cross-check masks, repeat behavior, and Game Pak source increments.
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,9 @@ They do not hash ROM contents; record source revisions and file checksums alongs
|
||||||
|
|
||||||
## Next validation stage
|
## Next validation stage
|
||||||
|
|
||||||
The first public integration is [jsmolka/gba-tests ARM](public-arm-tests.md), with a verified result-register checkpoint and a known failing baseline.
|
The first public integration is [jsmolka/gba-tests ARM](public-arm-tests.md), with a verified result-register checkpoint.
|
||||||
Investigate its next unsupported high-address read at test 362 before claiming the full ARM ROM passes.
|
The pinned ARM ROM now passes this checkpoint on Darwin arm64. Historical failures remain documented.
|
||||||
|
Next, integrate a pinned public Thumb suite with a verified result protocol.
|
||||||
For further public ARM7TDMI/GBA tests, inspect their source, license, entry assumptions, and result protocol.
|
For further public ARM7TDMI/GBA tests, inspect their source, license, entry assumptions, and result protocol.
|
||||||
Pin source revisions and build instructions before comparing emulator changes.
|
Pin source revisions and build instructions before comparing emulator changes.
|
||||||
Adapt suites to verified completion addresses and result locations; do not guess them or treat timeouts as passes.
|
Adapt suites to verified completion addresses and result locations; do not guess them or treat timeouts as passes.
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,9 @@ Update this file when a feature lands or a limit is removed.
|
||||||
- ROM windows with scanline capture, keyboard input, focus-loss release, bounded execution slices, and interactive keypad wake from STOP.
|
- ROM windows with scanline capture, keyboard input, focus-loss release, bounded execution slices, and interactive keypad wake from STOP.
|
||||||
- Optional ROM-window frame limits and an original input-test ROM generator.
|
- Optional ROM-window frame limits and an original input-test ROM generator.
|
||||||
- Headless JSON ROM suites with per-case checkpoints, register/CPSR/memory assertions, bounded budgets, and structured failure reports.
|
- Headless JSON ROM suites with per-case checkpoints, register/CPSR/memory assertions, bounded budgets, and structured failure reports.
|
||||||
- Hash-pinned preparation of the public `jsmolka/gba-tests` ARM ROM, with an explicit failing compatibility baseline.
|
- Hash-pinned preparation of the public `jsmolka/gba-tests` ARM ROM, passing its result checkpoint on Darwin arm64.
|
||||||
- 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.
|
||||||
- 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.
|
||||||
|
|
@ -114,10 +115,12 @@ ROM windows have no demo-specific startup checks. Input is sampled between bound
|
||||||
Audio, saves, and external BIOS loading remain unavailable. ROM loading does not establish commercial-game compatibility.
|
Audio, saves, and external BIOS loading remain unavailable. ROM loading does not establish commercial-game compatibility.
|
||||||
See [cartridge loading](hardware/cartridge.md) for file limits, step budgets, and exit status.
|
See [cartridge loading](hardware/cartridge.md) for file limits, step budgets, and exit status.
|
||||||
The [ROM suite runner](rom-tests.md) has original regressions and a [pinned public ARM baseline](public-arm-tests.md).
|
The [ROM suite runner](rom-tests.md) has original regressions and a [pinned public ARM baseline](public-arm-tests.md).
|
||||||
The public ARM ROM now gets past its compare/status and word-load alias tests but stops at test 362 on an unmapped read from `0x80000000`.
|
The pinned public ARM ROM passes its result checkpoint in debug and release builds. Other public suites remain unverified.
|
||||||
It 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.
|
||||||
|
|
||||||
Unmapped reads return errors instead of hardware open-bus values.
|
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. Thumb, DMA, BIOS-protected reads, and unused/write-only I/O open bus remain unmodeled.
|
||||||
|
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.
|
||||||
Direct memory-bus halfword and word accesses require alignment.
|
Direct memory-bus halfword and word accesses require alignment.
|
||||||
CPU load/store instructions apply ARM7TDMI alignment and rotation rules before accessing the bus.
|
CPU load/store instructions apply ARM7TDMI alignment and rotation rules before accessing the bus.
|
||||||
|
|
@ -125,8 +128,8 @@ Writes to cartridge addresses return errors instead of modeling cartridge hardwa
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
1. Verify high-address read behavior exposed by public ARM test 362; implement the appropriate memory mapping or open-bus behavior and rerun the pinned suite.
|
1. Add a pinned public Thumb suite with a verified result protocol, then address its first compatibility failure.
|
||||||
Add further public suites only with verified result protocols before claiming instruction compatibility.
|
Extend open-bus support with verified Thumb region/alignment rules and bus history, rather than a blanket unmapped-read fallback.
|
||||||
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.
|
||||||
3. Validate keypad retrigger behavior with hardware tests and add remaining device registers; extend BIOS reset coverage as sound/serial support becomes available.
|
3. Validate keypad retrigger behavior with hardware tests and add remaining device registers; extend BIOS reset coverage as sound/serial support becomes available.
|
||||||
Validate STOP entry/wake edges and add external wake sources and remaining DMA device modes.
|
Validate STOP entry/wake edges and add external wake sources and remaining DMA device modes.
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ Do not set a Linux cross-compilation target for this validation.
|
||||||
The [headless ROM suite runner](rom-tests.md) provides explicit checkpoint and assertion results through `--test-suite PATH.json`.
|
The [headless ROM suite runner](rom-tests.md) provides explicit checkpoint and assertion results through `--test-suite PATH.json`.
|
||||||
Its tests verify manifest limits, whole-suite validation, exact step boundaries, ARM/Thumb checkpoints, and register/CPSR/memory checks.
|
Its tests verify manifest limits, whole-suite validation, exact step boundaries, ARM/Thumb checkpoints, and register/CPSR/memory checks.
|
||||||
Additional tests cover DMA/IRQ accounting, HALT/STOP, diagnostic state, case isolation, repeatable JSON output, and failure exit status.
|
Additional tests cover DMA/IRQ accounting, HALT/STOP, diagnostic state, case isolation, repeatable JSON output, and failure exit status.
|
||||||
These original tests validate the runner. The [public ARM adapter](public-arm-tests.md) is a separate, currently failing compatibility baseline.
|
These original tests validate the runner. The [public ARM adapter](public-arm-tests.md) provides a separate passing checkpoint result for its pinned ROM.
|
||||||
Its preparation tests use only synthetic original bytes and no network:
|
Its preparation tests use only synthetic original bytes and no network:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
@ -60,7 +60,10 @@ They verify pinned URLs, hashes, sizes, checkpoint validation, offline copies, d
|
||||||
Compare/status CPU regressions verify saved flags/masks, bank switching, sequential PC/timing, User/System fallback, and atomic diagnostics.
|
Compare/status CPU regressions verify saved flags/masks, bank switching, sequential PC/timing, User/System fallback, and atomic diagnostics.
|
||||||
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.
|
||||||
The public ARM ROM progresses past tests 360–361 but still fails on the high-address read in test 362.
|
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.
|
||||||
|
The pinned public ARM ROM now reaches its completion checkpoint with r12 = 0 in debug and release builds.
|
||||||
|
This does not establish public Thumb, timing, graphics, BIOS, or commercial-game compatibility.
|
||||||
|
|
||||||
ROM-runner tests use temporary files containing original instructions, without Nintendo assets or a desktop session.
|
ROM-runner tests use temporary files containing original instructions, without Nintendo assets or a desktop session.
|
||||||
They check file-size boundaries, bounded reads, short reads, I/O errors, paths, symlinks, and read-only loading.
|
They check file-size boundaries, bounded reads, short reads, I/O errors, paths, symlinks, and read-only loading.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue