fix(bios): preserve documented protected-read words at firmware exits
Place skipped compatibility data at boot, SoftReset, SWI, and IRQ exit PC+8 locations. Preserve the real IRQ callback return instruction as its readback word. Keep CPU and memory behavior image-derived, including caller-supplied images. Add original boundary, load-lane, caller-state, wait, supplied-image, and builder-fixup regressions. Document the historical failures, research decision, passing public BIOS checkpoint, and remaining fetch-history limits. Validated on Darwin arm64: 868 workspace tests in debug and release; 799 core/demo tests separately in both profiles; 39 preparation tests; native ROM window tests in both profiles; ten graphics/window smoke modes; fmt, clippy, and rustdoc. Hash-verified public ARM, Thumb, memory, and BIOS suites pass unchanged with identical debug/release reports. BIOS: r12=0, 17606 steps, 197456 nominal cycles, one IRQ entry.
This commit is contained in:
parent
b16588aa4c
commit
efdc288db6
15 changed files with 387 additions and 34 deletions
|
|
@ -44,7 +44,7 @@ Use `--test-suite suite.json` for [repeatable headless ROM assertions](docs/rom-
|
||||||
| [docs/public-arm-tests.md](docs/public-arm-tests.md) | Pinned public ARM test, preparation command, results, and limits |
|
| [docs/public-arm-tests.md](docs/public-arm-tests.md) | Pinned public ARM test, preparation command, results, and limits |
|
||||||
| [docs/public-thumb-tests.md](docs/public-thumb-tests.md) | Pinned public Thumb test, result protocol, and compatibility baseline |
|
| [docs/public-thumb-tests.md](docs/public-thumb-tests.md) | Pinned public Thumb test, result protocol, and compatibility baseline |
|
||||||
| [docs/public-memory-tests.md](docs/public-memory-tests.md) | Pinned public memory test, results, and video-byte coverage limits |
|
| [docs/public-memory-tests.md](docs/public-memory-tests.md) | Pinned public memory test, results, and video-byte coverage limits |
|
||||||
| [docs/public-bios-tests.md](docs/public-bios-tests.md) | Pinned BIOS read-protection test and firmware-specific failure |
|
| [docs/public-bios-tests.md](docs/public-bios-tests.md) | Pinned BIOS read-protection result and firmware 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 |
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@ pub fn boot(rom: Vec<u8>) -> Result<Machine, MemoryError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a deterministic 16 KiB image from original ARM instructions and a
|
/// Build a deterministic 16 KiB image from original ARM instructions and a
|
||||||
/// mathematically generated sine table. No Nintendo firmware bytes are included.
|
/// mathematically generated sine table. Documented protected-read compatibility
|
||||||
|
/// words are explicit exit data; no Nintendo firmware routines are included.
|
||||||
/// SWIs use bits 16–23 of the ARM immediate, or the Thumb immediate byte.
|
/// SWIs use bits 16–23 of the ARM immediate, or the Thumb immediate byte.
|
||||||
/// Services support User/System callers. Returning services preserve caller status
|
/// Services support User/System callers. Returning services preserve caller status
|
||||||
/// and registers other than each arithmetic service's documented outputs.
|
/// and registers other than each arithmetic service's documented outputs.
|
||||||
|
|
@ -90,6 +91,7 @@ pub fn image() -> Vec<u8> {
|
||||||
a.emit(0xe5c1_0000); // STRB r0,[r1]
|
a.emit(0xe5c1_0000); // STRB r0,[r1]
|
||||||
a.emit(0xe321_f01f); // MSR CPSR_c,#System
|
a.emit(0xe321_f01f); // MSR CPSR_c,#System
|
||||||
a.literal(15, ROM_START); // LDR pc,=ROM_START
|
a.literal(15, ROM_START); // LDR pc,=ROM_START
|
||||||
|
a.prefetch_tail(0xe129_f000); // Boot / SoftReset protected-read word.
|
||||||
|
|
||||||
a.label("irq");
|
a.label("irq");
|
||||||
a.emit(0xe92d_500f); // STMDB sp!,{r0-r3,r12,lr}
|
a.emit(0xe92d_500f); // STMDB sp!,{r0-r3,r12,lr}
|
||||||
|
|
@ -103,7 +105,10 @@ pub fn image() -> Vec<u8> {
|
||||||
a.emit(0xe1a0_e00f); // MOV lr,pc (return after BX)
|
a.emit(0xe1a0_e00f); // MOV lr,pc (return after BX)
|
||||||
a.emit(0xe12f_ff11); // BX r1
|
a.emit(0xe12f_ff11); // BX r1
|
||||||
a.emit(0xe8bd_500f); // LDMIA sp!,{r0-r3,r12,lr}
|
a.emit(0xe8bd_500f); // LDMIA sp!,{r0-r3,r12,lr}
|
||||||
|
|
||||||
|
// This instruction is also the callback branch's PC+8 readback word.
|
||||||
a.emit(0xe25e_f004); // SUBS pc,lr,#4
|
a.emit(0xe25e_f004); // SUBS pc,lr,#4
|
||||||
|
a.prefetch_tail(0xe55e_c002); // IRQ return protected-read word.
|
||||||
|
|
||||||
a.label("swi");
|
a.label("swi");
|
||||||
a.emit(0xe92d_500f); // STMDB sp!,{r0-r3,r12,lr}
|
a.emit(0xe92d_500f); // STMDB sp!,{r0-r3,r12,lr}
|
||||||
|
|
@ -268,6 +273,7 @@ pub fn image() -> Vec<u8> {
|
||||||
a.emit(0xe169_f003); // MSR SPSR_fc,r3
|
a.emit(0xe169_f003); // MSR SPSR_fc,r3
|
||||||
a.emit(0xe8bd_500f); // LDMIA sp!,{r0-r3,r12,lr}
|
a.emit(0xe8bd_500f); // LDMIA sp!,{r0-r3,r12,lr}
|
||||||
a.emit(0xe1b0_f00e); // MOVS pc,lr
|
a.emit(0xe1b0_f00e); // MOVS pc,lr
|
||||||
|
a.prefetch_tail(0xe3a0_2004); // SWI return protected-read word.
|
||||||
a.label("unsupported");
|
a.label("unsupported");
|
||||||
a.emit(UNSUPPORTED_TRAP);
|
a.emit(UNSUPPORTED_TRAP);
|
||||||
a.label("invalid_argument");
|
a.label("invalid_argument");
|
||||||
|
|
@ -331,6 +337,14 @@ impl ArmImage {
|
||||||
self.code.push(instruction);
|
self.code.push(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Place only after a non-fallthrough exit. Neither word is executed.
|
||||||
|
/// The normal ARM PC+8 snapshot exposes this documented compatibility data.
|
||||||
|
/// Keep the bus image-derived, including for caller-supplied BIOS images.
|
||||||
|
fn prefetch_tail(&mut self, value: u32) {
|
||||||
|
self.emit(UNSUPPORTED_TRAP); // PC+4: diagnose accidental fallthrough.
|
||||||
|
self.emit(value); // PC+8: retained BIOS word after the exit.
|
||||||
|
}
|
||||||
|
|
||||||
fn label(&mut self, name: &'static str) {
|
fn label(&mut self, name: &'static str) {
|
||||||
assert!(
|
assert!(
|
||||||
self.labels.insert(name, self.code.len()).is_none(),
|
self.labels.insert(name, self.code.len()).is_none(),
|
||||||
|
|
@ -418,6 +432,32 @@ mod image_tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefetch_tail_keeps_branch_and_literal_fixups_outside_compatibility_data() {
|
||||||
|
let mut a = ArmImage::default();
|
||||||
|
a.literal(0, 0x1122_3344);
|
||||||
|
a.branch(14, "after_tail");
|
||||||
|
a.prefetch_tail(0xe129_f000);
|
||||||
|
a.label("after_tail");
|
||||||
|
a.emit(0xe12f_ff1e); // BX lr
|
||||||
|
let image = a.finish(BIOS_SIZE);
|
||||||
|
let actual: Vec<_> = image[..24]
|
||||||
|
.chunks_exact(4)
|
||||||
|
.map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap()))
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
actual,
|
||||||
|
[
|
||||||
|
0xe59f_000c, // Literal at 20, relative to PC+8.
|
||||||
|
0xea00_0001, // Branch at 4 skips both tail words to 16.
|
||||||
|
UNSUPPORTED_TRAP,
|
||||||
|
0xe129_f000,
|
||||||
|
0xe12f_ff1e,
|
||||||
|
0x1122_3344,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "BIOS code overlaps reserved data")]
|
#[should_panic(expected = "BIOS code overlaps reserved data")]
|
||||||
fn code_cannot_overlap_reserved_tables() {
|
fn code_cannot_overlap_reserved_tables() {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ pub(super) fn emit(a: &mut ArmImage) {
|
||||||
// IME, IE, IF, devices, or unrelated CPU banks. IRQ stays masked throughout.
|
// IME, IE, IF, devices, or unrelated CPU banks. IRQ stays masked throughout.
|
||||||
a.emit(0xe329_f09f); // MSR CPSR_fc,#System|I
|
a.emit(0xe329_f09f); // MSR CPSR_fc,#System|I
|
||||||
a.emit(0xe12f_ff1e); // BX lr (ARM target, no return through erased SWI frame)
|
a.emit(0xe12f_ff1e); // BX lr (ARM target, no return through erased SWI frame)
|
||||||
|
a.prefetch_tail(0xe129_f000); // Same protected-read word as cold boot.
|
||||||
|
|
||||||
emit_ram_reset(a);
|
emit_ram_reset(a);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ mod huffman;
|
||||||
mod lz77;
|
mod lz77;
|
||||||
#[path = "bios/ram_reset.rs"]
|
#[path = "bios/ram_reset.rs"]
|
||||||
mod ram_reset;
|
mod ram_reset;
|
||||||
|
#[path = "bios/readback.rs"]
|
||||||
|
mod readback;
|
||||||
#[path = "bios/reset.rs"]
|
#[path = "bios/reset.rs"]
|
||||||
mod reset;
|
mod reset;
|
||||||
#[path = "bios/run_length.rs"]
|
#[path = "bios/run_length.rs"]
|
||||||
|
|
|
||||||
253
crates/gba-core/tests/bios/readback.rs
Normal file
253
crates/gba-core/tests/bios/readback.rs
Normal file
|
|
@ -0,0 +1,253 @@
|
||||||
|
//! Firmware boundary outputs, checked through CPU loads rather than host reads.
|
||||||
|
use super::*;
|
||||||
|
use gba_core::cpu::Cpu;
|
||||||
|
|
||||||
|
const PROBE: u32 = 0x0203_0000;
|
||||||
|
|
||||||
|
// A separate CPU leaves the caller's registers and device clocks unchanged.
|
||||||
|
// All probe instructions run outside BIOS, so they cannot replace its retained word.
|
||||||
|
fn probe(bus: &mut Memory, thumb: bool, address: u32, arm: u32, half: u16) -> u32 {
|
||||||
|
let code = if thumb {
|
||||||
|
vec![
|
||||||
|
0xe28f_3001, // ADD r3,pc,#1
|
||||||
|
0xe12f_ff13, // BX r3
|
||||||
|
u32::from(half) << 16 | 0x4801, // LDR r0,[pc,#4]; load r1
|
||||||
|
0xe7fe_e7fe,
|
||||||
|
address,
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![0xe59f_0004, arm, 0xeaff_fffe, address]
|
||||||
|
};
|
||||||
|
for (index, word) in code.into_iter().enumerate() {
|
||||||
|
bus.write32(PROBE + index as u32 * 4, word).unwrap();
|
||||||
|
}
|
||||||
|
let mut cpu = Cpu::new(PROBE);
|
||||||
|
for _ in 0..if thumb { 4 } else { 2 } {
|
||||||
|
cpu.step(bus).unwrap();
|
||||||
|
}
|
||||||
|
cpu.registers()[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_readback(machine: &mut Machine, expected: u32) {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
for base in [0, 0x3ffc] {
|
||||||
|
assert_eq!(
|
||||||
|
probe(machine.memory_mut(), thumb, base, 0xe590_1000, 0x6801),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
for lane in 0..4 {
|
||||||
|
let byte = (expected >> (lane * 8)) & 0xff;
|
||||||
|
assert_eq!(
|
||||||
|
probe(
|
||||||
|
machine.memory_mut(),
|
||||||
|
thumb,
|
||||||
|
base + lane,
|
||||||
|
0xe5d0_1000,
|
||||||
|
0x7801
|
||||||
|
),
|
||||||
|
byte
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
probe(
|
||||||
|
machine.memory_mut(),
|
||||||
|
thumb,
|
||||||
|
base + lane,
|
||||||
|
0xe1d0_10d0,
|
||||||
|
0x5681
|
||||||
|
),
|
||||||
|
byte as u8 as i8 as i32 as u32
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for lane in [0, 2] {
|
||||||
|
let half = (expected >> (lane * 8)) & 0xffff;
|
||||||
|
assert_eq!(
|
||||||
|
probe(
|
||||||
|
machine.memory_mut(),
|
||||||
|
thumb,
|
||||||
|
base + lane,
|
||||||
|
0xe1d0_10b0,
|
||||||
|
0x8801
|
||||||
|
),
|
||||||
|
half
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
probe(
|
||||||
|
machine.memory_mut(),
|
||||||
|
thumb,
|
||||||
|
base + lane,
|
||||||
|
0xe1d0_10f0,
|
||||||
|
0x5e81
|
||||||
|
),
|
||||||
|
half as u16 as i16 as i32 as u32
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the actual last BIOS instruction, not a fixed firmware address.
|
||||||
|
fn reach_exit(machine: &mut Machine, target: u32, limit: usize) -> u32 {
|
||||||
|
let mut exit = None;
|
||||||
|
for _ in 0..limit {
|
||||||
|
if machine.cpu().pc() == target {
|
||||||
|
return exit.expect("must leave BIOS before target");
|
||||||
|
}
|
||||||
|
let pc = machine.cpu().pc();
|
||||||
|
machine.step().unwrap();
|
||||||
|
if pc < BIOS_SIZE as u32 && machine.cpu().pc() >= BIOS_SIZE as u32 {
|
||||||
|
exit = Some(pc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("did not reach {target:#x}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_tail(machine: &Machine, exit: u32, expected: u32) {
|
||||||
|
assert_eq!(machine.memory().read32(exit + 4).unwrap(), UNSUPPORTED_TRAP);
|
||||||
|
assert_eq!(machine.memory().read32(exit + 8).unwrap(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn boot_readback_comes_from_guarded_firmware_tail() {
|
||||||
|
let mut machine = bios::boot(words(&[0xeaff_fffe])).unwrap();
|
||||||
|
let exit = reach_exit(&mut machine, ROM_START, 100);
|
||||||
|
assert_tail(&machine, exit, 0xe129_f000);
|
||||||
|
assert_readback(&mut machine, 0xe129_f000);
|
||||||
|
assert_eq!(machine.cpu().cpsr(), 0x1f);
|
||||||
|
assert_eq!(machine.cpu().registers()[13], SYSTEM_STACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn soft_reset_readback_matches_both_restart_targets_and_caller_states() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
for status in [0x10, 0x1f] {
|
||||||
|
for flag in [0, 1] {
|
||||||
|
let (mut machine, _) = call_status(0, thumb, [1, 2, 3], status);
|
||||||
|
machine.memory_mut().write8(0x0300_7ffa, flag).unwrap();
|
||||||
|
machine.step().unwrap();
|
||||||
|
let target = if flag == 0 { ROM_START } else { SOURCE };
|
||||||
|
let exit = reach_exit(&mut machine, target, 2000);
|
||||||
|
assert_tail(&machine, exit, 0xe129_f000);
|
||||||
|
assert_readback(&mut machine, 0xe129_f000);
|
||||||
|
assert_eq!(machine.cpu().cpsr(), 0x9f);
|
||||||
|
assert_eq!(&machine.cpu().registers()[..13], &[0; 13]);
|
||||||
|
assert_eq!(machine.cpu().registers()[13], SYSTEM_STACK);
|
||||||
|
assert_eq!(machine.cpu().registers()[14], target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn returning_services_share_readback_without_changing_caller_status() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
for status in [0x10, 0x1f, 0x9f] {
|
||||||
|
for service in [1, 8, 0x0b, 0x0c] {
|
||||||
|
let (mut machine, target) = call_status(service, thumb, [0, 0, 0], status);
|
||||||
|
let before = machine.cpu().clone();
|
||||||
|
machine.step().unwrap();
|
||||||
|
let exit = reach_exit(&mut machine, target, 1000);
|
||||||
|
assert_tail(&machine, exit, 0xe3a0_2004);
|
||||||
|
assert_readback(&mut machine, 0xe3a0_2004);
|
||||||
|
assert_eq!(machine.cpu().cpsr(), before.cpsr());
|
||||||
|
assert_eq!(&machine.cpu().registers()[..15], &before.registers()[..15]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn irq_callback_and_return_have_distinct_image_derived_readback() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
for status in [0x10, 0x1f] {
|
||||||
|
let (mut machine, _) = call_status(8, thumb, [25, 2, 3], status);
|
||||||
|
let before = machine.cpu().clone();
|
||||||
|
callback(machine.memory_mut());
|
||||||
|
let prefix = CALLBACK + 0x80;
|
||||||
|
for (index, word) in [
|
||||||
|
0xe3a0_0000, // MOV r0,#0
|
||||||
|
0xe590_1000, // LDR r1,[r0] (protected BIOS read)
|
||||||
|
0xe59f_2008, // LDR r2,[pc,#8]
|
||||||
|
0xe582_1000, // STR r1,[r2]
|
||||||
|
0xe3a0_0301, // MOV r0,#I/O base
|
||||||
|
0xeaff_ffd9, // B CALLBACK (keep lr)
|
||||||
|
DEST,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
machine
|
||||||
|
.memory_mut()
|
||||||
|
.write32(prefix + index as u32 * 4, word)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
machine.memory_mut().write32(IRQ_HANDLER, prefix).unwrap();
|
||||||
|
machine.memory_mut().write16(DISPSTAT, 8).unwrap();
|
||||||
|
machine.memory_mut().write16(IE, 1).unwrap();
|
||||||
|
machine.memory_mut().write16(IME, 1).unwrap();
|
||||||
|
machine.memory_mut().advance_cycles(VBLANK_START);
|
||||||
|
assert_eq!(machine.step().unwrap(), StepKind::IrqEntry);
|
||||||
|
let dispatch = reach_exit(&mut machine, prefix, 100);
|
||||||
|
assert_eq!(machine.memory().read32(dispatch + 4).unwrap(), 0xe8bd_500f);
|
||||||
|
assert_eq!(machine.memory().read32(dispatch + 8).unwrap(), 0xe25e_f004);
|
||||||
|
assert_readback(&mut machine, 0xe25e_f004);
|
||||||
|
let exit = reach_exit(&mut machine, before.pc(), 100);
|
||||||
|
assert_tail(&machine, exit, 0xe55e_c002);
|
||||||
|
assert_eq!(machine.memory().read32(DEST).unwrap(), 0xe25e_f004);
|
||||||
|
assert_readback(&mut machine, 0xe55e_c002);
|
||||||
|
assert_eq!(machine.cpu().cpsr(), before.cpsr());
|
||||||
|
assert_eq!(machine.cpu().registers(), before.registers());
|
||||||
|
assert_eq!(machine.memory().read16(IF).unwrap(), 0);
|
||||||
|
assert_eq!(machine.memory().read16(IRQ_FLAGS).unwrap(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interrupt_wait_replaces_irq_readback_with_swi_readback_on_return() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
let (mut machine, target) = call(5, thumb, [1, 2, 3]);
|
||||||
|
let before = machine.cpu().clone();
|
||||||
|
callback(machine.memory_mut());
|
||||||
|
machine.memory_mut().write16(DISPSTAT, 8).unwrap();
|
||||||
|
machine.memory_mut().write16(IE, 1).unwrap();
|
||||||
|
machine.step().unwrap();
|
||||||
|
let mut slept = false;
|
||||||
|
let mut irqs = 0;
|
||||||
|
let mut callback_seen = false;
|
||||||
|
for _ in 0..1000 {
|
||||||
|
if machine.cpu().pc() == target {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if machine.cpu().pc() == CALLBACK {
|
||||||
|
callback_seen = true;
|
||||||
|
assert_readback(&mut machine, 0xe25e_f004);
|
||||||
|
}
|
||||||
|
match machine.step().unwrap() {
|
||||||
|
StepKind::HaltIdle => slept = true,
|
||||||
|
StepKind::IrqEntry => irqs += 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(slept && callback_seen);
|
||||||
|
assert_eq!(irqs, 1);
|
||||||
|
assert_eq!(machine.cpu().pc(), target);
|
||||||
|
assert_readback(&mut machine, 0xe3a0_2004);
|
||||||
|
assert_eq!(machine.cpu().cpsr(), before.cpsr());
|
||||||
|
assert_eq!(&machine.cpu().registers()[..15], &before.registers()[..15]);
|
||||||
|
assert_eq!(machine.memory().read16(IRQ_FLAGS).unwrap(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn supplied_image_tail_is_not_overridden_by_compatibility_constants() {
|
||||||
|
let rom = words(&[0xeaff_fffe]);
|
||||||
|
let mut original = bios::boot(rom.clone()).unwrap();
|
||||||
|
let exit = reach_exit(&mut original, ROM_START, 100);
|
||||||
|
let mut image = bios::image();
|
||||||
|
let value: u32 = 0x89ab_cdef;
|
||||||
|
image[exit as usize + 8..exit as usize + 12].copy_from_slice(&value.to_le_bytes());
|
||||||
|
let mut machine = Machine::new(Cpu::at_reset(), Memory::with_bios(rom, image).unwrap());
|
||||||
|
assert_eq!(reach_exit(&mut machine, ROM_START, 100), exit);
|
||||||
|
assert_readback(&mut machine, value);
|
||||||
|
assert_ne!(machine.memory().read32(0).unwrap(), value); // Host reads remain raw.
|
||||||
|
}
|
||||||
|
|
@ -125,6 +125,7 @@ The repository is a Cargo workspace with three crates:
|
||||||
| `crates/gba-core/tests/bios/stop.rs` | ARM/Thumb Stop calls, caller preservation, keypad wake, and stopped frame running |
|
| `crates/gba-core/tests/bios/stop.rs` | ARM/Thumb Stop calls, caller preservation, keypad wake, and stopped frame running |
|
||||||
| `crates/gba-core/tests/halt.rs` | HALT wake masks, idle timing, BIOS-only power writes, DMA progress, and validation |
|
| `crates/gba-core/tests/halt.rs` | HALT wake masks, idle timing, BIOS-only power writes, DMA progress, and validation |
|
||||||
| `crates/gba-core/tests/bios.rs` | ARM/Thumb service calls, copy/fill boundaries, wait races, callback contracts, and boot |
|
| `crates/gba-core/tests/bios.rs` | ARM/Thumb service calls, copy/fill boundaries, wait races, callback contracts, and boot |
|
||||||
|
| `crates/gba-core/tests/bios/readback.rs` | Image-derived boot/reset/SWI/IRQ compatibility words, load lanes, caller state, and supplied-image isolation |
|
||||||
| `crates/gba-core/tests/bios/ram_reset.rs` | Selective RAM boundaries, flag combinations, I/O reset, display rendering, DMA/timer latches, and diagnostics |
|
| `crates/gba-core/tests/bios/ram_reset.rs` | Selective RAM boundaries, flag combinations, I/O reset, display rendering, DMA/timer latches, and diagnostics |
|
||||||
| `crates/gba-core/tests/bios/reset.rs` | All restart flags, exact RAM boundaries, CPU banks, restart execution, device continuity, and IRQ masking |
|
| `crates/gba-core/tests/bios/reset.rs` | All restart flags, exact RAM boundaries, CPU banks, restart execution, device continuity, and IRQ masking |
|
||||||
| `crates/gba-core/tests/bios/arithmetic.rs` | Arithmetic boundaries, wide-integer references, status restoration, and zero-division diagnostics |
|
| `crates/gba-core/tests/bios/arithmetic.rs` | Arithmetic boundaries, wide-integer references, status restoration, and zero-division diagnostics |
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# BIOS replacement
|
# BIOS replacement
|
||||||
|
|
||||||
The original, Nintendo-free BIOS image built by `crates/gba-core/src/bios/` and the services it implements.
|
The original BIOS replacement built by `crates/gba-core/src/bios/` and the services it implements.
|
||||||
|
|
||||||
`crates/gba-core/src/bios.rs` builds a deterministic 16 KiB image from original ARM instructions and mathematically generated data.
|
`crates/gba-core/src/bios.rs` builds a deterministic 16 KiB image from original ARM instructions and mathematically generated data.
|
||||||
No Nintendo firmware bytes are included.
|
No Nintendo firmware routines or dumped image are included. Documented protected-read words are explicit compatibility data.
|
||||||
A compile-time integer calculation generates the 512-byte sine table at `0x3e00..0x3fff`.
|
A compile-time integer calculation generates the 512-byte sine table at `0x3e00..0x3fff`.
|
||||||
The image builder checks that code and literal pools do not overlap this table.
|
The image builder checks that code and literal pools do not overlap this table.
|
||||||
Literal pools follow unconditional branches and stay within each load instruction's address range.
|
Literal pools follow unconditional branches and stay within each load instruction's address range.
|
||||||
|
|
@ -108,8 +108,21 @@ The current interpreter implements a bounded ARM prefetch model:
|
||||||
|
|
||||||
The initial history is unknown. Direct cartridge startup with `Cpu::new` does not manufacture boot prefetch data.
|
The initial history is unknown. Direct cartridge startup with `Cpu::new` does not manufacture boot prefetch data.
|
||||||
Use BIOS execution, such as `bios::boot`, to establish supported history.
|
Use BIOS execution, such as `bios::boot`, to establish supported history.
|
||||||
All retained words come from the supplied image. No Nintendo opcode constants are substituted.
|
All retained words come from the supplied image. The bus never substitutes a firmware-specific constant.
|
||||||
The original replacement's boot and return paths therefore produce different values from Nintendo firmware.
|
Our generated image places documented compatibility data at PC+8 of its non-fallthrough exits:
|
||||||
|
|
||||||
|
| Boundary | Retained word |
|
||||||
|
| --- | --- |
|
||||||
|
| Boot and SoftReset | `0xe129f000` |
|
||||||
|
| SWI return | `0xe3a02004` |
|
||||||
|
| IRQ callback entry | `0xe25ef004` |
|
||||||
|
| IRQ return | `0xe55ec002` |
|
||||||
|
|
||||||
|
Boot, SoftReset, SWI return, and IRQ return skip a diagnostic trap at PC+4 and the data word at PC+8.
|
||||||
|
The callback branch instead has the real `SUBS pc,lr,#4` return instruction at PC+8.
|
||||||
|
Builder fixups preserve this layout without adding executed instructions or host-side service hooks.
|
||||||
|
Other supplied BIOS images retain their own values; their data is not rewritten.
|
||||||
|
See the [root-cause research](../research/bios-readback.md) for evidence and the firmware-versus-bus distinction.
|
||||||
|
|
||||||
Host/debug reads and ROM-suite memory assertions still inspect raw mapped bytes and do not initialize or change the history.
|
Host/debug reads and ROM-suite memory assertions still inspect raw mapped bytes and do not initialize or change the history.
|
||||||
Instruction fetches remain strict mapped reads outside the CPU data-access context.
|
Instruction fetches remain strict mapped reads outside the CPU data-access context.
|
||||||
|
|
@ -118,7 +131,8 @@ Missing BIOS images remain unmapped. The unused-memory ARM open-bus snapshot is
|
||||||
|
|
||||||
This models neither a persistent fetch pipeline nor exact bus history across Thumb BIOS code and DMA.
|
This models neither a persistent fetch pipeline nor exact bus history across Thumb BIOS code and DMA.
|
||||||
Original regressions cover synthetic images, boot, exception returns, access widths, alignment, modes, and diagnostics.
|
Original regressions cover synthetic images, boot, exception returns, access widths, alignment, modes, and diagnostics.
|
||||||
The [public BIOS ROM](../public-bios-tests.md) still fails its firmware-specific test 1; this feature is not a public BIOS pass.
|
The [public BIOS ROM](../public-bios-tests.md) passes its unchanged protected-read assertions in debug and release builds.
|
||||||
|
This result covers these firmware boundaries, not full BIOS service compatibility.
|
||||||
|
|
||||||
## Stop
|
## Stop
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,8 @@ See the bounded [ARM open-bus implementation](hardware/cpu.md#arm-unused-memory-
|
||||||
This is a passing result for this pinned ARM ROM, not proof of complete instruction, memory, or timing compatibility.
|
This is a passing result for this pinned ARM ROM, not proof of complete instruction, memory, or timing compatibility.
|
||||||
The [pinned public Thumb ROM](public-thumb-tests.md) also passes its verified checkpoint.
|
The [pinned public Thumb ROM](public-thumb-tests.md) also passes its verified checkpoint.
|
||||||
The [pinned public memory ROM](public-memory-tests.md) passes its limited mirror and video-byte checks.
|
The [pinned public memory ROM](public-memory-tests.md) passes its limited mirror and video-byte checks.
|
||||||
No public timing, BIOS, graphics, save, or unsafe suite has been claimed as passing.
|
The [public BIOS read-protection ROM](public-bios-tests.md) has a separate bounded passing result.
|
||||||
|
No public timing, graphics, save, 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.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Pinned public BIOS read-protection test
|
# Pinned public BIOS read-protection test
|
||||||
|
|
||||||
The headless runner executes the unmodified BIOS test ROM from [jsmolka/gba-tests](https://github.com/jsmolka/gba-tests).
|
The headless runner executes the unmodified BIOS test ROM from [jsmolka/gba-tests](https://github.com/jsmolka/gba-tests).
|
||||||
It currently fails test 1 on Darwin arm64 in debug and release builds.
|
It passes its unchanged protected-read assertions on Darwin arm64 in debug and release builds.
|
||||||
This ROM tests protected BIOS reads against Nintendo firmware opcode values, not the complete BIOS service set.
|
This ROM tests protected BIOS reads against Nintendo firmware opcode values, not the complete BIOS service set.
|
||||||
Our runner uses the original replacement BIOS. It neither supplies nor loads Nintendo firmware.
|
Our runner uses the original replacement BIOS. It neither supplies nor loads Nintendo firmware.
|
||||||
|
|
||||||
|
|
@ -25,7 +25,7 @@ direnv exec . python3 tools/prepare_gba_tests.py /tmp/gba-public-bios-offline --
|
||||||
```
|
```
|
||||||
|
|
||||||
ARM remains the default suite. The Thumb and memory selections remain unchanged.
|
ARM remains the default suite. The Thumb and memory selections remain unchanged.
|
||||||
Preparation is not a passing result. The BIOS run currently exits nonzero and writes a failed assertion report.
|
Preparation is not a passing result. A successful BIOS run exits zero and reports `r12 == 0` at the verified checkpoint.
|
||||||
|
|
||||||
## Provenance and checkpoint
|
## Provenance and checkpoint
|
||||||
|
|
||||||
|
|
@ -51,6 +51,8 @@ Timeouts, diagnostics, STOP, and nonzero r12 remain failures under the [normal r
|
||||||
|
|
||||||
## Observed result on Darwin arm64
|
## Observed result on Darwin arm64
|
||||||
|
|
||||||
|
### Historical failures
|
||||||
|
|
||||||
Before BIOS read protection:
|
Before BIOS read protection:
|
||||||
|
|
||||||
- Both builds reach `eval` after 64 successful steps and 543 nominal cycles.
|
- Both builds reach `eval` after 64 successful steps and 543 nominal cycles.
|
||||||
|
|
@ -68,12 +70,27 @@ After the bounded ARM BIOS read-protection implementation:
|
||||||
- The test still expects `0xe129f000`. No assertion, checkpoint, ROM byte, or returned constant was changed to force a pass.
|
- The test still expects `0xe129f000`. No assertion, checkpoint, ROM byte, or returned constant was changed to force a pass.
|
||||||
- Tests 2–4 remain unreached. Their SWI and IRQ cases have not passed independently.
|
- Tests 2–4 remain unreached. Their SWI and IRQ cases have not passed independently.
|
||||||
|
|
||||||
The initial failure exposed both a missing bus-protection rule and a firmware-specific expectation.
|
### Current passing result
|
||||||
The supported protection subset now derives data from the actual supplied image.
|
|
||||||
The original BIOS has a different layout, so its retained prefetch data differs from Nintendo firmware.
|
After correcting the replacement firmware's exit layout:
|
||||||
See [CPU BIOS read protection and its limits](hardware/bios.md#cpu-bios-read-protection).
|
|
||||||
No public BIOS pass is claimed. The ARM, Thumb, and memory suites remain separate passing results.
|
- Both builds reach `eval` at `0x08000248` after 17,606 successful steps and 197,456 nominal cycles.
|
||||||
Exact fetch-pipeline behavior and full BIOS compatibility remain unverified.
|
- The CPU is in System/ARM state, with CPSR `0x6000001f` and r12 = 0.
|
||||||
|
- The report says `reason: checkpoint`, with no emulator diagnostic and one IRQ entry.
|
||||||
|
- Boot, returning Sqrt, external IRQ callback, and IRQ return checks all execute and pass.
|
||||||
|
- The ROM, checkpoint, r12 assertion, and step budget remain unchanged.
|
||||||
|
- Debug and release reports match exactly for the same fixture path.
|
||||||
|
|
||||||
|
The initial failure exposed both a missing bus-protection rule and a firmware layout difference.
|
||||||
|
The protected-read bus still derives its retained word from the actual supplied image.
|
||||||
|
Our generated firmware now places documented compatibility words at its exit instructions' PC+8 locations.
|
||||||
|
These words are skipped data, not copied firmware routines. No bus override or ROM-specific condition was added.
|
||||||
|
The IRQ callback uses its existing real return instruction as the readback word.
|
||||||
|
See the [root-cause research](research/bios-readback.md) and [CPU BIOS read-protection limits](hardware/bios.md#cpu-bios-read-protection).
|
||||||
|
|
||||||
|
The ARM, Thumb, and memory suites remain separate passing results.
|
||||||
|
This BIOS result covers boundary readback values, not complete BIOS services, exact fetch history, or commercial-game compatibility.
|
||||||
|
Thumb BIOS execution, full pipeline behavior, and exact firmware timing remain unverified.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,8 @@ Adjacent halfwords must remain unchanged. Ignored stores still pay their nominal
|
||||||
Halfword and word stores still modify object video RAM and object attribute memory.
|
Halfword and word stores still modify object video RAM and object attribute memory.
|
||||||
These are implementation regressions based on documented rules, not new hardware measurements.
|
These are implementation regressions based on documented rules, not new hardware measurements.
|
||||||
|
|
||||||
No public BIOS, timing, graphics, save, or unsafe suite is claimed as passing.
|
The [public BIOS read-protection ROM](public-bios-tests.md) has a separate bounded passing result.
|
||||||
|
No public timing, graphics, save, or unsafe suite is claimed as passing.
|
||||||
Pokémon Emerald compatibility remains unverified.
|
Pokémon Emerald compatibility remains unverified.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,8 @@ Existing empty-list expectations that incorrectly required PC+4 were corrected;
|
||||||
See [CPU transfer semantics](hardware/cpu.md#instruction-demo-and-execution-rules).
|
See [CPU transfer semantics](hardware/cpu.md#instruction-demo-and-execution-rules).
|
||||||
This proves only the pinned suites' checkpoints and assertions, not complete CPU, bus, or timing compatibility.
|
This proves only the pinned suites' checkpoints and assertions, not complete CPU, bus, or timing compatibility.
|
||||||
The [pinned public memory ROM](public-memory-tests.md) also passes, with documented video-byte coverage limits.
|
The [pinned public memory ROM](public-memory-tests.md) also passes, with documented video-byte coverage limits.
|
||||||
No public timing, BIOS, graphics, save, or unsafe suite is claimed as passing.
|
The [public BIOS read-protection ROM](public-bios-tests.md) has a separate bounded passing result.
|
||||||
|
No public timing, graphics, save, or unsafe suite is claimed as passing.
|
||||||
These results do not establish Pokémon Emerald compatibility.
|
These results do not establish Pokémon Emerald compatibility.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
|
|
||||||
## Finding
|
## Finding
|
||||||
|
|
||||||
The remaining public BIOS failure comes from the replacement firmware's exit layout, not its protected-load byte lanes.
|
The public BIOS failure came from the replacement firmware's exit layout, not its protected-load byte lanes.
|
||||||
The memory bus correctly returns the replacement image's retained ARM PC+8 word.
|
The memory bus correctly returned the replacement image's retained ARM PC+8 word.
|
||||||
That word differs from the documented value left by the standard GBA firmware.
|
Before the layout fix, that word differed from the documented value left by the standard GBA firmware.
|
||||||
Matching the bus mechanism alone does not make replacement firmware's observable outputs compatible.
|
Matching the bus mechanism alone does not make replacement firmware's observable outputs compatible.
|
||||||
|
|
||||||
At the initial failing checkpoint:
|
Before the firmware-layout fix, at the initial failing checkpoint:
|
||||||
|
|
||||||
- The unmodified public ROM expects `0xe129f000` after boot.
|
- The unmodified public ROM expects `0xe129f000` after boot.
|
||||||
- Our replacement's final boot instruction is at `0x60`; its PC+8 word at `0x68` is `0xe59f1320`.
|
- Our replacement's final boot instruction is at `0x60`; its PC+8 word at `0x68` is `0xe59f1320`.
|
||||||
|
|
@ -68,6 +68,22 @@ This corrects the earlier overly broad claim that reproducing any documented opc
|
||||||
Arbitrarily overriding a read to satisfy an assertion would be wrong.
|
Arbitrarily overriding a read to satisfy an assertion would be wrong.
|
||||||
Reproducing documented firmware boundary outputs for all callers is a legitimate compatibility feature, provided its scope is explicit.
|
Reproducing documented firmware boundary outputs for all callers is a legitimate compatibility feature, provided its scope is explicit.
|
||||||
|
|
||||||
|
## Implemented result
|
||||||
|
|
||||||
|
`ArmImage::prefetch_tail` adds skipped compatibility data after boot, SoftReset, SWI return, and IRQ return.
|
||||||
|
The change adds 32 image bytes without adding normally executed instructions.
|
||||||
|
The callback path retains its actual `SUBS pc,lr,#4` at the branch's PC+8.
|
||||||
|
No CPU or memory-bus implementation changed.
|
||||||
|
|
||||||
|
Original regressions in `crates/gba-core/tests/bios/readback.rs` reproduced the boundary failures before the fix.
|
||||||
|
They now pass for ARM/Thumb callers, load lanes and widths, User/System state, both reset targets, and interrupt waits.
|
||||||
|
A supplied-image variant keeps its own changed word, proving that no compatibility constant overrides image data.
|
||||||
|
Builder regressions verify branch and literal relocation around the skipped data.
|
||||||
|
|
||||||
|
The [unchanged public BIOS ROM](../public-bios-tests.md) passes in debug and release on Darwin arm64.
|
||||||
|
It reaches its checkpoint with r12 = 0 and one IRQ entry. Public ARM, Thumb, and memory results remain passing.
|
||||||
|
Workspace tests, preparation tests, lint checks, rustdoc, native ROM windows, and graphics smoke tests also pass.
|
||||||
|
|
||||||
## Validation requirements and limits
|
## Validation requirements and limits
|
||||||
|
|
||||||
- Run the pinned BIOS ROM unchanged, with its original budget, checkpoint, and r12 assertion.
|
- Run the pinned BIOS ROM unchanged, with its original budget, checkpoint, and r12 assertion.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Use `--test-suite PATH.json` to run repeatable ROM checks without opening a wind
|
||||||
Each case has a machine-step budget, a completion address, and explicit assertions.
|
Each case has a machine-step budget, a completion address, and explicit assertions.
|
||||||
A timeout never counts as a pass. Existing `--rom ... --steps ...` remains a diagnostic mode, not a test verdict.
|
A timeout never counts as a pass. Existing `--rom ... --steps ...` remains a diagnostic mode, not a test verdict.
|
||||||
|
|
||||||
Original programs cover the runner itself. Pinned public [ARM](public-arm-tests.md), [Thumb](public-thumb-tests.md), and [memory](public-memory-tests.md) ROMs provide independent compatibility results.
|
Original programs cover the runner itself. Pinned public [ARM](public-arm-tests.md), [Thumb](public-thumb-tests.md), [memory](public-memory-tests.md), and [BIOS](public-bios-tests.md) ROMs provide independent compatibility results.
|
||||||
These pinned ROMs pass their checkpoints on Darwin arm64 in debug and release builds.
|
These pinned ROMs pass their checkpoints on Darwin arm64 in debug and release builds.
|
||||||
The memory ROM has documented assertion and mode-selection limits; a pass is not exhaustive memory validation.
|
The memory ROM has documented assertion and mode-selection limits; a pass is not exhaustive memory validation.
|
||||||
Passing the runner's unit tests does not mean an external suite passes.
|
Passing the runner's unit tests does not mean an external suite passes.
|
||||||
|
|
@ -137,8 +137,9 @@ The first public integration is [jsmolka/gba-tests ARM](public-arm-tests.md), wi
|
||||||
The pinned ARM ROM now passes this checkpoint on Darwin arm64. Historical failures remain documented.
|
The pinned ARM ROM now passes this checkpoint on Darwin arm64. Historical failures remain documented.
|
||||||
The [pinned Thumb ROM](public-thumb-tests.md) also passes its verified r7 checkpoint after the empty-list store correction.
|
The [pinned Thumb ROM](public-thumb-tests.md) also passes its verified r7 checkpoint after the empty-list store correction.
|
||||||
The [pinned memory ROM](public-memory-tests.md) also passes, without a core change and with explicit coverage limits.
|
The [pinned memory ROM](public-memory-tests.md) also passes, without a core change and with explicit coverage limits.
|
||||||
The [pinned BIOS read-protection ROM](public-bios-tests.md) reaches its checkpoint but fails test 1 with the original replacement BIOS.
|
The [pinned BIOS read-protection ROM](public-bios-tests.md) also passes with the original replacement BIOS.
|
||||||
Its expected values depend on Nintendo firmware. Record that failure rather than changing the assertion or returning guessed constants.
|
Its documented boundary values come from compatibility data in our firmware's exit layout, not a bus override.
|
||||||
|
The ROM, assertions, checkpoint, and budget remain unchanged. This is not a complete BIOS service test.
|
||||||
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,9 +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 public `jsmolka/gba-tests` ARM, Thumb, and memory ROMs, with verified result-register checkpoints.
|
- Hash-pinned preparation of public `jsmolka/gba-tests` ARM, Thumb, memory, and BIOS ROMs, with verified result-register checkpoints.
|
||||||
- These pinned ROMs pass their checkpoints on Darwin arm64 in debug and release builds; coverage limits remain documented.
|
- These pinned ROMs pass their checkpoints on Darwin arm64 in debug and release builds; coverage limits remain documented.
|
||||||
- A pinned public BIOS read-protection ROM has a verified checkpoint and an explicit failing baseline with the original BIOS.
|
- 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.
|
||||||
- Little-endian byte, halfword, and word reads/writes.
|
- Little-endian byte, halfword, and word reads/writes.
|
||||||
|
|
@ -96,7 +96,7 @@ The ARMv4T high-register format requires at least one high register; low-to-low
|
||||||
`LDRT`/`STRT` single-transfer variants and invalid register combinations return errors.
|
`LDRT`/`STRT` single-transfer variants and invalid register combinations return errors.
|
||||||
CPU BIOS reads are protected by the executing address using a bounded ARM prefetch snapshot.
|
CPU BIOS reads are protected by the executing address using a bounded ARM prefetch snapshot.
|
||||||
Thumb BIOS execution and missing lookahead invalidate that history; protected reads with unknown history remain diagnostics.
|
Thumb BIOS execution and missing lookahead invalidate that history; protected reads with unknown history remain diagnostics.
|
||||||
This is not a full fetch pipeline, and the original BIOS does not reproduce Nintendo's opcode-specific retained values.
|
This is not a full fetch pipeline. The original BIOS reproduces documented boundary readback words, not all firmware side effects.
|
||||||
Multiply flags N/Z are implemented. Unspecified multiply C/V outputs are preserved deterministically, not hardware-verified.
|
Multiply flags N/Z are implemented. Unspecified multiply C/V outputs are preserved deterministically, not hardware-verified.
|
||||||
Instruction and memory errors are development diagnostics, not emulated CPU exceptions.
|
Instruction and memory errors are development diagnostics, not emulated CPU exceptions.
|
||||||
Block transfers validate all accesses before committing register, RAM, or I/O changes.
|
Block transfers validate all accesses before committing register, RAM, or I/O changes.
|
||||||
|
|
@ -122,7 +122,8 @@ The [ROM suite runner](rom-tests.md) has original regressions and a [pinned publ
|
||||||
The pinned public ARM ROM passes its result checkpoint in debug and release builds.
|
The pinned public ARM ROM passes its result checkpoint in debug and release builds.
|
||||||
The [pinned public Thumb ROM](public-thumb-tests.md) also passes after correcting its empty-list store PC value.
|
The [pinned public Thumb ROM](public-thumb-tests.md) also passes after correcting its empty-list store PC value.
|
||||||
The [pinned public memory ROM](public-memory-tests.md) passes without a core change; its video-byte assertions have documented limits.
|
The [pinned public memory ROM](public-memory-tests.md) passes without a core change; its video-byte assertions have documented limits.
|
||||||
The [pinned BIOS read-protection ROM](public-bios-tests.md) fails test 1; it expects Nintendo firmware opcode values.
|
The [pinned BIOS read-protection ROM](public-bios-tests.md) passes after correcting the replacement firmware's exit layout.
|
||||||
|
The bus still derives retained words from the actual image; no test-specific override or ROM patch is used.
|
||||||
Other public suites remain unverified. These results do not establish full CPU or memory compatibility.
|
Other public suites remain unverified. These results do not establish full CPU or memory compatibility.
|
||||||
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.
|
||||||
|
|
||||||
|
|
@ -137,8 +138,8 @@ Writes to cartridge addresses return errors instead of modeling cartridge hardwa
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
1. Extend BIOS prefetch history to Thumb execution and validate firmware-specific reads with a lawfully supplied BIOS.
|
1. Extend BIOS prefetch history to Thumb execution and validate broader fetch-history behavior with independent tests.
|
||||||
The desktop runner still uses only the original BIOS. Do not force the public test to pass with guessed opcode constants.
|
The desktop runner still uses only the original BIOS. Keep protected reads image-derived for all supplied images.
|
||||||
Extend general open-bus support with verified Thumb region/alignment rules and bus history.
|
Extend general open-bus support with verified Thumb region/alignment rules and bus history.
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ Additional tests cover DMA/IRQ accounting, HALT/STOP, diagnostic state, case iso
|
||||||
These original tests validate the runner. The [public ARM adapter](public-arm-tests.md) provides a separate passing checkpoint result for its pinned ROM.
|
These original tests validate the runner. The [public ARM adapter](public-arm-tests.md) provides a separate passing checkpoint result for its pinned ROM.
|
||||||
The [public Thumb adapter](public-thumb-tests.md) uses a separate lock and r7 assertion after its return to ARM state.
|
The [public Thumb adapter](public-thumb-tests.md) uses a separate lock and r7 assertion after its return to ARM state.
|
||||||
The [public memory adapter](public-memory-tests.md) uses its own lock and an ARM-state r12 checkpoint.
|
The [public memory adapter](public-memory-tests.md) uses its own lock and an ARM-state r12 checkpoint.
|
||||||
The [public BIOS adapter](public-bios-tests.md) also uses an ARM-state r12 checkpoint, but currently reports test 1 as failed.
|
The [public BIOS adapter](public-bios-tests.md) also passes its ARM-state r12 checkpoint in debug and release builds.
|
||||||
Preparation tests use only synthetic original bytes and no network:
|
Preparation tests use only synthetic original bytes and no network:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
@ -76,7 +76,7 @@ Separate original CPU tests check exact video-byte results across supported mode
|
||||||
They require unchanged nonzero sentinels for ignored writes and unchanged adjacent halfwords for duplicated writes.
|
They require unchanged nonzero sentinels for ignored writes and unchanged adjacent halfwords for duplicated writes.
|
||||||
They also check forced blank, full source-value preservation, CPU halfword readback, nominal data costs, and device progress.
|
They also check forced blank, full source-value preservation, CPU halfword readback, nominal data costs, and device progress.
|
||||||
Halfword/word stores remain writable in object regions. No emulator behavior change was needed for these tests.
|
Halfword/word stores remain writable in object regions. No emulator behavior change was needed for these tests.
|
||||||
These results do not establish public timing, graphics, BIOS, save, or commercial-game compatibility.
|
These results do not establish full BIOS, timing, graphics, save, 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.
|
||||||
|
|
@ -223,7 +223,11 @@ Original BIOS-access regressions check retained ARM PC+8 data for ARM/Thumb call
|
||||||
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.
|
||||||
Unknown history, Thumb BIOS execution, missing lookahead, and diagnostic-step retention have explicit checks.
|
Unknown history, Thumb BIOS execution, missing lookahead, and diagnostic-step retention have explicit checks.
|
||||||
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.
|
||||||
The pinned public BIOS ROM remains a failed assertion, not a conformance pass.
|
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.
|
||||||
|
Interrupt waits replace callback readback with SWI readback on return. Builder tests check branch/literal relocation around exit data.
|
||||||
|
A modified original image verifies that the bus retains supplied data rather than forcing compatibility constants.
|
||||||
|
The pinned public BIOS ROM passes unchanged; this is a bounded protected-read result, not complete BIOS conformance.
|
||||||
Display tests cover color conversion, framebuffer bounds, cursor movement, pause behavior, and animation wraparound.
|
Display tests cover color conversion, framebuffer bounds, cursor movement, pause behavior, and animation wraparound.
|
||||||
Input regression tests check focused arrow keys, focused Space presses, and ignored input when unfocused.
|
Input regression tests check focused arrow keys, focused Space presses, and ignored input when unfocused.
|
||||||
Command-line tests cover mode selection and invalid arguments.
|
Command-line tests cover mode selection and invalid arguments.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue