feat(cpu): retain Thumb instruction pipeline halfwords
This commit is contained in:
parent
47df9d05a7
commit
ea055270f4
21 changed files with 535 additions and 97 deletions
|
|
@ -58,6 +58,8 @@ mod thumb_empty_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod thumb_open_bus_tests;
|
mod thumb_open_bus_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
mod thumb_pipeline_tests;
|
||||||
|
#[cfg(test)]
|
||||||
mod thumb_tests;
|
mod thumb_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod timing_tests;
|
mod timing_tests;
|
||||||
|
|
@ -165,8 +167,8 @@ impl InstructionSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ARM/Thumb interpreter with register banks, ARM instruction buffering, and nominal costs.
|
/// ARM/Thumb interpreter with register banks, instruction buffering, and nominal costs.
|
||||||
/// Clone/equality include buffered fetch results. Thumb instruction buffering is not modeled.
|
/// Clone/equality include buffered fetch results and their instruction-set tag.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Cpu {
|
pub struct Cpu {
|
||||||
registers: [u32; 16],
|
registers: [u32; 16],
|
||||||
|
|
@ -176,7 +178,7 @@ pub struct Cpu {
|
||||||
irq_disabled: bool,
|
irq_disabled: bool,
|
||||||
fiq_disabled: bool,
|
fiq_disabled: bool,
|
||||||
banks: status::Banks,
|
banks: status::Banks,
|
||||||
arm_pipeline: Option<pipeline::ArmPipeline>,
|
pipeline: Option<pipeline::Pipeline>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
|
|
@ -193,7 +195,7 @@ impl Cpu {
|
||||||
irq_disabled: false,
|
irq_disabled: false,
|
||||||
fiq_disabled: false,
|
fiq_disabled: false,
|
||||||
banks: status::Banks::default(),
|
banks: status::Banks::default(),
|
||||||
arm_pipeline: None,
|
pipeline: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,6 +239,7 @@ impl Cpu {
|
||||||
// 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.
|
||||||
let pc = self.pc();
|
let pc = self.pc();
|
||||||
|
let instruction_set = self.instruction_set;
|
||||||
let refill = self.instruction_refills(instruction);
|
let refill = self.instruction_refills(instruction);
|
||||||
let sequential_thumb = self.instruction_set == InstructionSet::Thumb && !refill;
|
let sequential_thumb = self.instruction_set == InstructionSet::Thumb && !refill;
|
||||||
memory.begin_cpu_access(pc, self.instruction_set);
|
memory.begin_cpu_access(pc, self.instruction_set);
|
||||||
|
|
@ -248,13 +251,13 @@ impl Cpu {
|
||||||
if result.is_ok() {
|
if result.is_ok() {
|
||||||
if refill {
|
if refill {
|
||||||
memory.refill_cpu_bus_history(self.pc(), self.instruction_set);
|
memory.refill_cpu_bus_history(self.pc(), self.instruction_set);
|
||||||
self.refill_arm_pipeline(memory);
|
self.refill_pipeline(memory);
|
||||||
} else {
|
} else {
|
||||||
// Never carry an ARM buffer through a Thumb-state transition.
|
// A state change without an ordinary refill starts a cold sequence.
|
||||||
self.arm_pipeline = if self.instruction_set == InstructionSet::Arm
|
self.pipeline = if self.instruction_set == instruction_set
|
||||||
&& self.pc() == pc.wrapping_add(4)
|
&& self.pc() == pc.wrapping_add(instruction_set.width())
|
||||||
{
|
{
|
||||||
continuation
|
Some(continuation)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ fn swi_and_machine_irq_refill_arm_vectors_before_handler_execution() {
|
||||||
StepKind::Instruction
|
StepKind::Instruction
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert!(machine.cpu().arm_pipeline.is_some()); // Entry itself captured the ARM vector pair.
|
assert!(machine.cpu().pipeline.is_some()); // Entry itself captured the ARM vector pair.
|
||||||
machine.step().unwrap(); // Vector BX refills target pair.
|
machine.step().unwrap(); // Vector BX refills target pair.
|
||||||
machine.memory_mut().write32(TARGET, NEW).unwrap();
|
machine.memory_mut().write32(TARGET, NEW).unwrap();
|
||||||
machine.step().unwrap();
|
machine.step().unwrap();
|
||||||
|
|
@ -244,21 +244,21 @@ fn explicit_exception_and_pc_discontinuity_discard_the_old_execution_sequence()
|
||||||
cpu.step(&mut bus).unwrap();
|
cpu.step(&mut bus).unwrap();
|
||||||
assert_eq!(cpu.registers[0], 2);
|
assert_eq!(cpu.registers[0], 2);
|
||||||
cpu.enter_exception(Exception::Irq);
|
cpu.enter_exception(Exception::Irq);
|
||||||
assert!(cpu.arm_pipeline.is_none()); // This API has no Memory to sample.
|
assert!(cpu.pipeline.is_none()); // This API has no Memory to sample.
|
||||||
let before = cpu.clone();
|
let before = cpu.clone();
|
||||||
assert!(cpu.step(&mut bus).is_err()); // Missing BIOS remains a strict error.
|
assert!(cpu.step(&mut bus).is_err()); // Missing BIOS remains a strict error.
|
||||||
assert_eq!(cpu, before);
|
assert_eq!(cpu, before);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn leaving_arm_discards_its_buffer_and_returning_refills_from_current_memory() {
|
fn state_changes_replace_the_buffer_and_returning_refills_from_current_memory() {
|
||||||
let (mut cpu, mut bus) = program(&[0xe12f_ff11, OLD, OLD]);
|
let (mut cpu, mut bus) = program(&[0xe12f_ff11, OLD, OLD]);
|
||||||
bus.write16(TARGET, 0x4710).unwrap(); // Thumb BX r2.
|
bus.write16(TARGET, 0x4710).unwrap(); // Thumb BX r2.
|
||||||
cpu.registers[1] = TARGET | 1;
|
cpu.registers[1] = TARGET | 1;
|
||||||
cpu.registers[2] = CODE + 4;
|
cpu.registers[2] = CODE + 4;
|
||||||
cpu.step(&mut bus).unwrap();
|
cpu.step(&mut bus).unwrap();
|
||||||
assert_eq!(cpu.instruction_set(), InstructionSet::Thumb);
|
assert_eq!(cpu.instruction_set(), InstructionSet::Thumb);
|
||||||
assert!(cpu.arm_pipeline.is_none());
|
assert!(cpu.pipeline.is_some()); // BX captured the Thumb target pair.
|
||||||
bus.write32(CODE + 4, NEW).unwrap();
|
bus.write32(CODE + 4, NEW).unwrap();
|
||||||
cpu.step(&mut bus).unwrap();
|
cpu.step(&mut bus).unwrap();
|
||||||
bus.write32(CODE + 4, OLD).unwrap();
|
bus.write32(CODE + 4, OLD).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,10 @@ fn completed_units_and_priority_changes_use_access_order_not_channel_number() {
|
||||||
#[test]
|
#[test]
|
||||||
fn failed_dma_and_failed_resumed_instruction_preserve_last_completed_dma_history() {
|
fn failed_dma_and_failed_resumed_instruction_preserve_last_completed_dma_history() {
|
||||||
for fail_destination in [false, true] {
|
for fail_destination in [false, true] {
|
||||||
let mut machine = ready(CODE);
|
let (mut cpu, bus) = program(CODE, 0x680a);
|
||||||
|
cpu.registers[0] = 0x0e00_0000; // Buffered PROBE will fail on unsupported save memory.
|
||||||
|
let mut machine = Machine::new(cpu, bus);
|
||||||
|
machine.step().unwrap();
|
||||||
machine.memory_mut().write32(EWRAM, DATA).unwrap();
|
machine.memory_mut().write32(EWRAM, DATA).unwrap();
|
||||||
transfer(&mut machine, EWRAM, IWRAM, true);
|
transfer(&mut machine, EWRAM, IWRAM, true);
|
||||||
// A valid IWRAM source must not drive history if its destination is invalid.
|
// A valid IWRAM source must not drive history if its destination is invalid.
|
||||||
|
|
@ -189,16 +192,15 @@ fn failed_dma_and_failed_resumed_instruction_preserve_last_completed_dma_history
|
||||||
.memory_mut()
|
.memory_mut()
|
||||||
.write16(DMA_BASE + 3 * DMA_STRIDE + 10, 0)
|
.write16(DMA_BASE + 3 * DMA_STRIDE + 10, 0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
machine.memory_mut().write16(CODE + 2, 0xde00).unwrap(); // Unsupported instruction.
|
|
||||||
assert!(machine.step().is_err());
|
assert!(machine.step().is_err());
|
||||||
assert_eq!(machine.cpu(), &before);
|
assert_eq!(machine.cpu(), &before);
|
||||||
assert_eq!(machine.cycles(), cycles);
|
assert_eq!(machine.cycles(), cycles);
|
||||||
machine.memory_mut().write16(CODE + 2, PROBE).unwrap();
|
assert_eq!(machine.last_timing(), timing);
|
||||||
machine.step().unwrap();
|
// Repair the data address, not the retained instruction or DMA history.
|
||||||
assert_eq!(
|
let mut cpu = machine.cpu().clone();
|
||||||
machine.cpu().registers()[6],
|
cpu.registers[0] = UNUSED;
|
||||||
resumed(DATA.to_le_bytes(), CODE)
|
cpu.step_timed(machine.memory_mut()).unwrap();
|
||||||
);
|
assert_eq!(cpu.registers()[6], resumed(DATA.to_le_bytes(), CODE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ fn known_history_supports_widths_sign_extension_and_unaligned_loads_in_all_modes
|
||||||
expected.registers[6] = value;
|
expected.registers[6] = value;
|
||||||
expected.registers[15] += 2;
|
expected.registers[15] += 2;
|
||||||
cpu.step(&mut bus).unwrap();
|
cpu.step(&mut bus).unwrap();
|
||||||
assert_eq!(cpu, expected);
|
assert_cpu_arch_eq!(cpu, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -284,6 +284,7 @@ fn failed_steps_discard_prefetch_history_before_retry() {
|
||||||
assert!(cpu.step_timed(&mut bus).is_err());
|
assert!(cpu.step_timed(&mut bus).is_err());
|
||||||
assert_eq!(cpu, before);
|
assert_eq!(cpu, before);
|
||||||
bus.write16(pc + 2, PROBE).unwrap();
|
bus.write16(pc + 2, PROBE).unwrap();
|
||||||
|
cpu.invalidate_pipeline(); // Debugger repair leaves separate bus history intact.
|
||||||
cpu.step(&mut bus).unwrap();
|
cpu.step(&mut bus).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cpu.registers[6],
|
cpu.registers[6],
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ fn refill_history_supports_normal_load_lanes_rotation_and_sign_extension() {
|
||||||
after.registers[6] = value;
|
after.registers[6] = value;
|
||||||
after.registers[15] += 2;
|
after.registers[15] += 2;
|
||||||
cpu.step(&mut bus).unwrap();
|
cpu.step(&mut bus).unwrap();
|
||||||
assert_eq!(cpu, after);
|
assert_cpu_arch_eq!(cpu, after);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,67 @@
|
||||||
//! ARM instruction buffering only. Timing and bus ownership remain separate.
|
//! ARM/Thumb instruction buffering. Timing and bus ownership remain separate.
|
||||||
use super::{Cpu, CpuError, InstructionSet};
|
use super::{Cpu, CpuError, InstructionSet};
|
||||||
use crate::memory::{Memory, MemoryError};
|
use crate::memory::{Memory, MemoryError};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub(super) struct ArmPipeline {
|
pub(super) struct Pipeline {
|
||||||
pc: u32,
|
pc: u32,
|
||||||
|
instruction_set: InstructionSet,
|
||||||
// Deferred strict-fetch results for the next execute and decode positions.
|
// Deferred strict-fetch results for the next execute and decode positions.
|
||||||
words: [Result<u32, MemoryError>; 2],
|
// Thumb halfwords are zero-extended; their upper bits are not another opcode.
|
||||||
|
instructions: [Result<u32, MemoryError>; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct Fetched {
|
pub(super) struct Fetched {
|
||||||
pub instruction: u32,
|
pub instruction: u32,
|
||||||
pub continuation: Option<ArmPipeline>,
|
pub continuation: Pipeline,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
/// Discard buffered instructions after debugger code edits or rebinding memory.
|
/// Discard buffered instructions after debugger code edits or rebinding memory.
|
||||||
/// Ordinary CPU/DMA stores and host inspection must not call this automatically.
|
/// Ordinary CPU/DMA stores and host inspection must not call this automatically.
|
||||||
/// The next ARM step fills from its current PC without adding nominal cycles.
|
/// The next step fills from its current PC/state without adding nominal cycles.
|
||||||
pub fn invalidate_pipeline(&mut self) {
|
pub fn invalidate_pipeline(&mut self) {
|
||||||
self.arm_pipeline = None;
|
self.pipeline = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn fetch(&self, memory: &Memory) -> Result<Fetched, CpuError> {
|
pub(super) fn fetch(&self, memory: &Memory) -> Result<Fetched, CpuError> {
|
||||||
if self.instruction_set == InstructionSet::Thumb {
|
|
||||||
return Ok(Fetched {
|
|
||||||
instruction: u32::from(memory.read16(self.pc())?),
|
|
||||||
continuation: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let pc = self.pc();
|
let pc = self.pc();
|
||||||
let retained = self.arm_pipeline.as_ref().filter(|pipe| pipe.pc == pc);
|
let instruction_set = self.instruction_set;
|
||||||
|
let width = instruction_set.width();
|
||||||
|
let retained = self
|
||||||
|
.pipeline
|
||||||
|
.as_ref()
|
||||||
|
.filter(|pipe| pipe.pc == pc && pipe.instruction_set == instruction_set);
|
||||||
let instruction = match retained {
|
let instruction = match retained {
|
||||||
Some(pipe) => pipe.words[0].clone(),
|
Some(pipe) => pipe.instructions[0].clone(),
|
||||||
None => memory.fetch_arm_word(pc),
|
None => memory.fetch_instruction(pc, instruction_set),
|
||||||
}?;
|
}?;
|
||||||
let decode = match retained {
|
let decode = match retained {
|
||||||
Some(pipe) => pipe.words[1].clone(),
|
Some(pipe) => pipe.instructions[1].clone(),
|
||||||
None => memory.fetch_arm_word(pc.wrapping_add(4)),
|
None => memory.fetch_instruction(pc.wrapping_add(width), instruction_set),
|
||||||
};
|
};
|
||||||
let fetched = memory.fetch_arm_word(pc.wrapping_add(8));
|
let fetched = memory.fetch_instruction(pc.wrapping_add(2 * width), instruction_set);
|
||||||
Ok(Fetched {
|
Ok(Fetched {
|
||||||
instruction,
|
instruction,
|
||||||
continuation: Some(ArmPipeline {
|
continuation: Pipeline {
|
||||||
pc: pc.wrapping_add(4),
|
pc: pc.wrapping_add(width),
|
||||||
words: [decode, fetched],
|
instruction_set,
|
||||||
}),
|
instructions: [decode, fetched],
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Capture target and target+4 after a successful refill, without early errors.
|
/// Capture the target pair using the resulting state, without early errors.
|
||||||
/// Explicit exception entry without Memory leaves a cold buffer instead.
|
/// Explicit exception entry without Memory leaves a cold buffer instead.
|
||||||
pub(crate) fn refill_arm_pipeline(&mut self, memory: &Memory) {
|
pub(crate) fn refill_pipeline(&mut self, memory: &Memory) {
|
||||||
self.arm_pipeline = (self.instruction_set == InstructionSet::Arm).then(|| ArmPipeline {
|
let pc = self.pc();
|
||||||
pc: self.pc(),
|
let instruction_set = self.instruction_set;
|
||||||
words: [
|
self.pipeline = Some(Pipeline {
|
||||||
memory.fetch_arm_word(self.pc()),
|
pc,
|
||||||
memory.fetch_arm_word(self.pc().wrapping_add(4)),
|
instruction_set,
|
||||||
|
instructions: [
|
||||||
|
memory.fetch_instruction(pc, instruction_set),
|
||||||
|
memory.fetch_instruction(pc.wrapping_add(instruction_set.width()), instruction_set),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,9 @@ fn empty_thumb_stores_use_pc_plus_six_for_all_bases_banks_and_alignments() {
|
||||||
} else {
|
} else {
|
||||||
cpu.step(&mut memory).unwrap();
|
cpu.step(&mut memory).unwrap();
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_cpu_arch_eq!(
|
||||||
cpu, after,
|
cpu,
|
||||||
|
after,
|
||||||
"{instruction:#06x} {mode:?} code={code_low} data={data_low}"
|
"{instruction:#06x} {mode:?} code={code_low} data={data_low}"
|
||||||
);
|
);
|
||||||
assert_eq!(memory.read32(DATA).unwrap(), pc + 6);
|
assert_eq!(memory.read32(DATA).unwrap(), pc + 6);
|
||||||
|
|
|
||||||
|
|
@ -127,8 +127,9 @@ fn loads_select_lanes_rotate_and_sign_extend_without_changing_flags() {
|
||||||
after.registers[1] = value;
|
after.registers[1] = value;
|
||||||
after.registers[15] += 2;
|
after.registers[15] += 2;
|
||||||
cpu.step(&mut bus).unwrap();
|
cpu.step(&mut bus).unwrap();
|
||||||
assert_eq!(
|
assert_cpu_arch_eq!(
|
||||||
cpu, after,
|
cpu,
|
||||||
|
after,
|
||||||
"pc={pc:#x} instruction={instruction:#x} lane={lane}"
|
"pc={pc:#x} instruction={instruction:#x} lane={lane}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
321
crates/gba-core/src/cpu/thumb_pipeline_tests.rs
Normal file
321
crates/gba-core/src/cpu/thumb_pipeline_tests.rs
Normal file
|
|
@ -0,0 +1,321 @@
|
||||||
|
//! Original Thumb instruction retention tests; bus history and timing remain separate.
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
dma::{DMA_BASE, DMA_STRIDE},
|
||||||
|
io::{IE, IME, TIMER_BASE},
|
||||||
|
machine::{Machine, StepKind},
|
||||||
|
memory::{BIOS_SIZE, ROM_START},
|
||||||
|
};
|
||||||
|
|
||||||
|
const CODE: u32 = 0x0300_0100;
|
||||||
|
const TARGET: u32 = 0x0300_0200;
|
||||||
|
const DATA: u32 = 0x0200_1000;
|
||||||
|
const NOP: u16 = 0x46c0;
|
||||||
|
const OLD: u16 = 0x2001; // MOV r0,#1.
|
||||||
|
const NEW: u16 = 0x2002; // MOV r0,#2.
|
||||||
|
|
||||||
|
fn program(pc: u32, code: &[u16]) -> (Cpu, Memory) {
|
||||||
|
let mut bus = Memory::new(vec![]).unwrap();
|
||||||
|
for (index, half) in code.iter().enumerate() {
|
||||||
|
bus.write16(pc + index as u32 * 2, *half).unwrap();
|
||||||
|
}
|
||||||
|
let mut cpu = Cpu::new(pc);
|
||||||
|
cpu.instruction_set = InstructionSet::Thumb;
|
||||||
|
(cpu, bus)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn thumb_stores_retain_the_two_prefetched_halfwords_but_not_the_next_one() {
|
||||||
|
for instruction in [0x800a, 0x700a] {
|
||||||
|
// STRH/STRB r2,[r1].
|
||||||
|
for pc in [CODE, CODE + 2] {
|
||||||
|
for offset in [2, 4, 6] {
|
||||||
|
let (mut cpu, mut bus) = program(pc, &[instruction, OLD, OLD, OLD]);
|
||||||
|
cpu.registers[1] = pc + offset;
|
||||||
|
cpu.registers[2] = u32::from(NEW);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(bus.read16(pc + offset).unwrap(), NEW);
|
||||||
|
for _ in 0..offset / 2 {
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(cpu.registers[0], if offset <= 4 { 1 } else { 2 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn host_writes_and_memory_mirrors_do_not_replace_buffered_halfwords() {
|
||||||
|
for pc in [
|
||||||
|
CODE,
|
||||||
|
CODE + 2,
|
||||||
|
0x0300_fffe,
|
||||||
|
0x0203_fffe,
|
||||||
|
0x0500_03fe,
|
||||||
|
0x0600_fffe,
|
||||||
|
0x0700_03fe,
|
||||||
|
] {
|
||||||
|
let (mut cpu, mut bus) = program(pc, &[NOP, OLD, OLD, OLD]);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
for offset in [2, 4, 6] {
|
||||||
|
bus.write16(pc + offset, NEW).unwrap();
|
||||||
|
}
|
||||||
|
for expected in [1, 1, 2] {
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pc_writes_to_fallthrough_refill_the_thumb_target_pair() {
|
||||||
|
for instruction in [
|
||||||
|
0xe7ff, 0xd0ff, 0x468f, 0x448f, 0x4708, 0xbd00, 0xbc00, 0xc900, 0xf800,
|
||||||
|
] {
|
||||||
|
// B, BEQ, MOV pc,r1, ADD pc,r1, BX r1, POP pc, empty POP/LDM, BL suffix.
|
||||||
|
let (mut cpu, mut bus) = program(CODE, &[NOP, instruction, OLD, OLD]);
|
||||||
|
cpu.flags.zero = true;
|
||||||
|
cpu.registers[1] = match instruction {
|
||||||
|
0x448f => u32::MAX - 1, // Visible PC=CODE+6, target=CODE+4.
|
||||||
|
0xc900 => DATA,
|
||||||
|
_ => (CODE + 4) | 1,
|
||||||
|
};
|
||||||
|
cpu.registers[13] = DATA;
|
||||||
|
cpu.registers[14] = CODE + 4;
|
||||||
|
bus.write32(DATA, CODE + 4).unwrap(); // POP keeps Thumb even with bit0 clear.
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
bus.write16(CODE + 4, NEW).unwrap();
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.pc(), CODE + 4);
|
||||||
|
assert_eq!(cpu.instruction_set(), InstructionSet::Thumb);
|
||||||
|
bus.write16(CODE + 4, OLD).unwrap();
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], 2, "{instruction:#06x}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn untaken_branch_keeps_the_old_pair() {
|
||||||
|
let (mut cpu, mut bus) = program(CODE, &[NOP, 0xd0ff, OLD]);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
bus.write16(CODE + 4, NEW).unwrap();
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bl_prefix_retains_the_suffix_and_only_the_suffix_refills() {
|
||||||
|
let (mut cpu, mut bus) = program(CODE, &[0xf000, 0xf800, OLD, OLD]);
|
||||||
|
assert_eq!(cpu.step_timed(&mut bus).unwrap().code_cycles, 1);
|
||||||
|
assert_eq!(cpu.registers[14], CODE + 4);
|
||||||
|
bus.write16(CODE + 2, NOP).unwrap(); // Does not replace the retained suffix.
|
||||||
|
bus.write16(CODE + 4, NEW).unwrap();
|
||||||
|
assert_eq!(cpu.step_timed(&mut bus).unwrap().code_cycles, 3);
|
||||||
|
assert_eq!(cpu.registers[14], CODE + 5);
|
||||||
|
bus.write16(CODE + 4, OLD).unwrap(); // Suffix already captured the updated target.
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arm_to_thumb_refill_captures_target_and_plus_two_but_not_plus_four() {
|
||||||
|
for target in [TARGET, TARGET + 2] {
|
||||||
|
let (mut cpu, mut bus) = program(CODE, &[0, 0]);
|
||||||
|
cpu.instruction_set = InstructionSet::Arm;
|
||||||
|
bus.write32(CODE, 0xe12f_ff11).unwrap(); // BX r1.
|
||||||
|
cpu.registers[1] = target | 1;
|
||||||
|
for offset in [0, 2, 4] {
|
||||||
|
bus.write16(target + offset, OLD).unwrap();
|
||||||
|
}
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
for offset in [0, 2, 4] {
|
||||||
|
bus.write16(target + offset, NEW).unwrap();
|
||||||
|
}
|
||||||
|
for expected in [1, 1, 2] {
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dma_halfword_and_word_writes_do_not_replace_thumb_instruction_slots() {
|
||||||
|
for word in [false, true] {
|
||||||
|
let (cpu, mut bus) = program(CODE, &[NOP, OLD, OLD, OLD]);
|
||||||
|
for offset in [0, 2, 4] {
|
||||||
|
bus.write16(DATA + offset, NEW).unwrap();
|
||||||
|
}
|
||||||
|
let mut machine = Machine::new(cpu, bus);
|
||||||
|
machine.step().unwrap();
|
||||||
|
let before = machine.cpu().clone();
|
||||||
|
let dma = DMA_BASE + 3 * DMA_STRIDE;
|
||||||
|
machine.memory_mut().write32(dma, DATA).unwrap();
|
||||||
|
machine
|
||||||
|
.memory_mut()
|
||||||
|
.write32(dma + 4, CODE + if word { 4 } else { 2 })
|
||||||
|
.unwrap();
|
||||||
|
machine
|
||||||
|
.memory_mut()
|
||||||
|
.write32(dma + 8, if word { 0x8400_0001 } else { 0x8000_0003 })
|
||||||
|
.unwrap();
|
||||||
|
for _ in 0..if word { 1 } else { 3 } {
|
||||||
|
assert_eq!(machine.step().unwrap(), StepKind::Dma { channel: 3 });
|
||||||
|
assert_eq!(machine.cpu(), &before);
|
||||||
|
}
|
||||||
|
for expected in [1, 1, 2] {
|
||||||
|
machine.step().unwrap();
|
||||||
|
assert_eq!(machine.cpu().registers()[0], expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_thumb_load_rolls_back_buffer_progress_not_earlier_retention() {
|
||||||
|
let (mut cpu, mut bus) = program(CODE, &[NOP, 0x680a, OLD, OLD]); // LDR r2,[r1].
|
||||||
|
cpu.registers[1] = 0x0e00_0000;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
let before = cpu.clone();
|
||||||
|
assert!(cpu.step_timed(&mut bus).is_err());
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
bus.write16(CODE + 2, NOP).unwrap();
|
||||||
|
bus.write16(CODE + 4, NEW).unwrap();
|
||||||
|
bus.write16(CODE + 6, NEW).unwrap();
|
||||||
|
bus.write32(DATA, 0x1234).unwrap();
|
||||||
|
cpu.registers[1] = DATA;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[2], 0x1234);
|
||||||
|
for expected in [1, 2] {
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn short_rom_and_refill_errors_are_deferred_and_never_use_open_bus_instructions() {
|
||||||
|
for half in [NOP, 0x4708, 0xf000] {
|
||||||
|
// NOP, BX r1, BL prefix.
|
||||||
|
let mut bus = Memory::new(half.to_le_bytes().to_vec()).unwrap();
|
||||||
|
bus.write16(TARGET, OLD).unwrap();
|
||||||
|
let mut cpu = Cpu::new(ROM_START);
|
||||||
|
cpu.instruction_set = InstructionSet::Thumb;
|
||||||
|
cpu.registers[1] = TARGET | 1;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
if half == 0x4708 {
|
||||||
|
bus.write16(TARGET, NEW).unwrap();
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], 1);
|
||||||
|
} else {
|
||||||
|
let before = cpu.clone();
|
||||||
|
for _ in 0..2 {
|
||||||
|
assert_eq!(
|
||||||
|
cpu.step(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(ROM_START + 2)))
|
||||||
|
);
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (mut cpu, mut bus) = program(CODE, &[0x4708]);
|
||||||
|
cpu.registers[1] = 0x8000_0001;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
let before = cpu.clone();
|
||||||
|
assert_eq!(
|
||||||
|
cpu.step(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unmapped(0x8000_0000)))
|
||||||
|
);
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn explicit_invalidation_repairs_buffered_thumb_code_and_clones_stay_independent() {
|
||||||
|
let (mut cpu, mut bus) = program(CODE, &[NOP, 0xde00, OLD]);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
let before = cpu.clone();
|
||||||
|
let mut fork = cpu.clone();
|
||||||
|
bus.write16(CODE + 2, NEW).unwrap();
|
||||||
|
assert!(cpu.step(&mut bus).is_err());
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
cpu.invalidate_pipeline();
|
||||||
|
assert_cpu_arch_eq!(cpu, before);
|
||||||
|
assert_ne!(cpu, before);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], 2);
|
||||||
|
assert!(fork.step(&mut bus).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn instruction_state_and_pc_both_select_the_retained_buffer() {
|
||||||
|
let (mut cpu, mut bus) = program(CODE, &[NOP, OLD, OLD]);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
// A PC discontinuity starts a new Thumb sequence from mapped bytes.
|
||||||
|
cpu.registers[15] = TARGET + 2;
|
||||||
|
bus.write16(TARGET + 2, NOP).unwrap();
|
||||||
|
bus.write16(TARGET + 4, OLD).unwrap();
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
// Expected next Thumb PC is now TARGET+4. Keep that PC but change state without a refill.
|
||||||
|
cpu.instruction_set = InstructionSet::Arm;
|
||||||
|
bus.write32(TARGET + 4, 0xe3a0_0007).unwrap();
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[0], 7); // Must not decode the old Thumb halfword as ARM.
|
||||||
|
cpu.registers[15] = CODE + 1;
|
||||||
|
cpu.instruction_set = InstructionSet::Thumb;
|
||||||
|
let before = cpu.clone();
|
||||||
|
assert_eq!(
|
||||||
|
cpu.step(&mut bus),
|
||||||
|
Err(CpuError::Memory(MemoryError::Unaligned(CODE + 1)))
|
||||||
|
);
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn swi_and_irq_returns_refill_thumb_targets_using_saved_state() {
|
||||||
|
for irq in [false, true] {
|
||||||
|
let mut bios = vec![0; BIOS_SIZE];
|
||||||
|
let vector = if irq { 0x18 } else { 8 };
|
||||||
|
let instruction: u32 = if irq { 0xe25e_f004 } else { 0xe1b0_f00e };
|
||||||
|
bios[vector..vector + 4].copy_from_slice(&instruction.to_le_bytes());
|
||||||
|
let mut bus = Memory::with_bios(vec![], bios).unwrap();
|
||||||
|
bus.write16(CODE, if irq { NOP } else { 0xdf00 }).unwrap();
|
||||||
|
for offset in [2, 4] {
|
||||||
|
bus.write16(CODE + offset, OLD).unwrap();
|
||||||
|
}
|
||||||
|
let mut cpu = Cpu::new(CODE);
|
||||||
|
cpu.instruction_set = InstructionSet::Thumb;
|
||||||
|
let mut machine = Machine::new(cpu, bus);
|
||||||
|
machine.step().unwrap();
|
||||||
|
if irq {
|
||||||
|
machine.memory_mut().write16(IE, 8).unwrap();
|
||||||
|
machine.memory_mut().write16(IME, 1).unwrap();
|
||||||
|
machine
|
||||||
|
.memory_mut()
|
||||||
|
.write32(TIMER_BASE, 0x00c0_ffff)
|
||||||
|
.unwrap();
|
||||||
|
machine.memory_mut().advance_cycles(1);
|
||||||
|
assert_eq!(machine.step().unwrap(), StepKind::IrqEntry);
|
||||||
|
machine.memory_mut().write16(IME, 0).unwrap();
|
||||||
|
}
|
||||||
|
machine.memory_mut().write16(CODE + 2, NEW).unwrap();
|
||||||
|
machine.step().unwrap(); // ARM exception return captures updated Thumb target.
|
||||||
|
machine.memory_mut().write16(CODE + 2, OLD).unwrap();
|
||||||
|
machine.step().unwrap();
|
||||||
|
assert_eq!(machine.cpu().registers()[0], 2);
|
||||||
|
assert_eq!(machine.cpu().instruction_set(), InstructionSet::Thumb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn timed_and_untimed_thumb_buffering_has_no_additional_nominal_costs() {
|
||||||
|
let code = [NOP, 0xe7ff, OLD, NOP];
|
||||||
|
let (mut timed, mut timed_bus) = program(CODE, &code);
|
||||||
|
let (mut untimed, mut bus) = program(CODE, &code);
|
||||||
|
for cost in [1, 3, 1] {
|
||||||
|
let timing = timed.step_timed(&mut timed_bus).unwrap();
|
||||||
|
untimed.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(timed, untimed);
|
||||||
|
assert_eq!(timing.code_cycles, cost);
|
||||||
|
assert_eq!(timing.data_cycles, 0);
|
||||||
|
assert_eq!(timing.internal_cycles, 0);
|
||||||
|
assert_eq!(timed_bus.cycles(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -179,7 +179,7 @@ impl Machine {
|
||||||
}
|
}
|
||||||
let (kind, timing) = if self.cpu.take_interrupt(self.memory.irq_pending(), false) {
|
let (kind, timing) = if self.cpu.take_interrupt(self.memory.irq_pending(), false) {
|
||||||
self.memory.invalidate_cpu_bus_history();
|
self.memory.invalidate_cpu_bus_history();
|
||||||
self.cpu.refill_arm_pipeline(&self.memory);
|
self.cpu.refill_pipeline(&self.memory);
|
||||||
(StepKind::IrqEntry, self.cpu.exception_timing(&self.memory))
|
(StepKind::IrqEntry, self.cpu.exception_timing(&self.memory))
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ struct CpuAccess {
|
||||||
/// Successful refills into Thumb IWRAM sample the target pair to establish history.
|
/// Successful refills into Thumb IWRAM sample the target pair to establish history.
|
||||||
/// DMA accesses drive existing Thumb IWRAM continuation lanes before the next fetch.
|
/// DMA accesses drive existing Thumb IWRAM continuation lanes before the next fetch.
|
||||||
/// Channel data remains separate; general and sub-instruction CPU bus handoff is unmodeled.
|
/// Channel data remains separate; general and sub-instruction CPU bus handoff is unmodeled.
|
||||||
/// CPU-owned ARM instruction buffering does not yet drive complete bus history or timing.
|
/// CPU-owned ARM/Thumb instruction buffering does not yet drive complete bus history or timing.
|
||||||
pub struct Memory {
|
pub struct Memory {
|
||||||
external_ram: Vec<u8>,
|
external_ram: Vec<u8>,
|
||||||
internal_ram: Vec<u8>,
|
internal_ram: Vec<u8>,
|
||||||
|
|
@ -408,13 +408,22 @@ impl Memory {
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Strict ARM fetch without CPU data context, timing trace, or bus-history changes.
|
/// Strict instruction fetch without data context, timing, or bus-history changes.
|
||||||
pub(crate) fn fetch_arm_word(&self, address: u32) -> Result<u32, MemoryError> {
|
/// Thumb reads exactly two bytes, even on a region with a wider data-bus latch.
|
||||||
if address & 3 != 0 {
|
pub(crate) fn fetch_instruction(
|
||||||
|
&self,
|
||||||
|
address: u32,
|
||||||
|
state: InstructionSet,
|
||||||
|
) -> Result<u32, MemoryError> {
|
||||||
|
let width = match state {
|
||||||
|
InstructionSet::Arm => 4,
|
||||||
|
InstructionSet::Thumb => 2,
|
||||||
|
};
|
||||||
|
if address & (width as u32 - 1) != 0 {
|
||||||
return Err(MemoryError::Unaligned(address));
|
return Err(MemoryError::Unaligned(address));
|
||||||
}
|
}
|
||||||
let mut bytes = [0; 4];
|
let mut bytes = [0; 4];
|
||||||
for (offset, byte) in bytes.iter_mut().enumerate() {
|
for (offset, byte) in bytes[..width].iter_mut().enumerate() {
|
||||||
*byte = self.read_mapped_byte(address.wrapping_add(offset as u32))?;
|
*byte = self.read_mapped_byte(address.wrapping_add(offset as u32))?;
|
||||||
}
|
}
|
||||||
Ok(u32::from_le_bytes(bytes))
|
Ok(u32::from_le_bytes(bytes))
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,9 @@ The repository is a Cargo workspace with three crates:
|
||||||
| `crates/gba-core/src/dma.rs` | DMA registers, internal pointers, retained channel data, trigger state, priority, and completion IRQs |
|
| `crates/gba-core/src/dma.rs` | DMA registers, internal pointers, retained channel data, trigger state, priority, and completion IRQs |
|
||||||
| `crates/gba-core/src/machine.rs` | Timed CPU/DMA/device stepping and IRQ delivery |
|
| `crates/gba-core/src/machine.rs` | Timed CPU/DMA/device stepping and IRQ delivery |
|
||||||
| `crates/gba-core/src/timing.rs` | Bus widths, wait-state costs, and timing breakdowns |
|
| `crates/gba-core/src/timing.rs` | Bus widths, wait-state costs, and timing breakdowns |
|
||||||
| `crates/gba-core/src/cpu/pipeline.rs` | CPU-owned ARM instruction words, deferred fetch diagnostics, refill sampling, and explicit invalidation |
|
| `crates/gba-core/src/cpu/pipeline.rs` | CPU-owned ARM/Thumb instructions, PC/state tags, deferred fetch diagnostics, refill sampling, and explicit invalidation |
|
||||||
| `crates/gba-core/src/cpu/arm_pipeline_tests.rs` | ARM self-modifying code, branches, target pairs, DMA isolation, exceptions, errors, and timing equivalence |
|
| `crates/gba-core/src/cpu/arm_pipeline_tests.rs` | ARM self-modifying code, branches, target pairs, DMA isolation, exceptions, errors, and timing equivalence |
|
||||||
|
| `crates/gba-core/src/cpu/thumb_pipeline_tests.rs` | Thumb retained halfwords, BL prefix/suffix, state-tagged refills, DMA isolation, rollback, and timing equivalence |
|
||||||
| `crates/gba-core/src/cpu/timing.rs` | ARM/Thumb cycle summaries, nominal DMA resume, and data-access accounting |
|
| `crates/gba-core/src/cpu/timing.rs` | ARM/Thumb cycle summaries, nominal DMA resume, and data-access accounting |
|
||||||
| `crates/gba-core/src/cpu/dma_resume_tests.rs` | One-shot non-sequential DMA resume costs, wait settings, failures, idle, IRQs, and API isolation |
|
| `crates/gba-core/src/cpu/dma_resume_tests.rs` | One-shot non-sequential DMA resume costs, wait settings, failures, idle, IRQs, and API isolation |
|
||||||
| `crates/gba-core/src/cpu/timing_tests.rs` | Instruction timing and semantic-equivalence checks |
|
| `crates/gba-core/src/cpu/timing_tests.rs` | Instruction timing and semantic-equivalence checks |
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ Missing BIOS images remain unmapped. The unused-memory ARM open-bus snapshot is
|
||||||
|
|
||||||
The Thumb rule follows documented BIOS bus lanes and a source comparison with NanoBoyAdvance.
|
The Thumb rule follows documented BIOS bus lanes and a source comparison with NanoBoyAdvance.
|
||||||
See [Thumb BIOS prefetch research](../research/thumb-bios-prefetch.md) for evidence, emulator differences, and test scope.
|
See [Thumb BIOS prefetch research](../research/thumb-bios-prefetch.md) for evidence, emulator differences, and test scope.
|
||||||
This BIOS readback model remains separate from the CPU's ARM instruction buffer.
|
This BIOS readback model remains separate from the CPU's ARM/Thumb instruction buffer.
|
||||||
It does not model exact refill, BIOS data-access, or DMA bus history.
|
It does not model exact refill, BIOS data-access, or DMA bus history.
|
||||||
The boundary diagnostic policy and precise bus timing remain hardware-unverified.
|
The boundary diagnostic policy and precise bus timing remain hardware-unverified.
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -76,19 +76,21 @@ These forms retain nominal single-word access costs and the existing atomic-erro
|
||||||
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 instruction buffering
|
## ARM and Thumb instruction buffering
|
||||||
|
|
||||||
ARM execution retains fetched instructions in a CPU-owned buffer, independent of later RAM contents.
|
ARM and Thumb execution retain fetched instructions in a CPU-owned buffer, independent of later RAM contents.
|
||||||
At cold entry, a successful step reads the current word P and samples P+4 and P+8 before instruction effects.
|
Let W be the instruction width: four bytes for ARM, two for Thumb.
|
||||||
During sequential execution, the current and next words come from retained results; only P+8 is newly sampled.
|
At cold entry, a successful step reads the current instruction P and samples P+W and P+2W before instruction effects.
|
||||||
|
During sequential execution, the current and next instructions come from retained results; only P+2W is newly sampled.
|
||||||
Success advances the buffer. CPU/DMA stores and host writes do not replace already buffered instructions.
|
Success advances the buffer. CPU/DMA stores and host writes do not replace already buffered instructions.
|
||||||
The existing r15 convention is unchanged: it stores the next executing address, not the hardware fetch address.
|
The existing r15 convention is unchanged: it stores the next executing address, not the hardware fetch address.
|
||||||
|
|
||||||
A successful branch or PC-writing refill into ARM samples target T and T+4 after instruction effects.
|
A successful branch or PC-writing refill samples target T and T+W after instruction effects, using the resulting instruction state.
|
||||||
This includes taken branches, BX, PC loads, status-restoring returns, and software interrupts.
|
This includes taken branches, BX, PC loads, status-restoring returns, and software interrupts.
|
||||||
PC writes refill even when the destination equals fallthrough. Untaken conditional branches retain sequential buffering.
|
PC writes refill even when the destination equals fallthrough. Untaken conditional branches retain sequential buffering.
|
||||||
The first target instruction samples T+8 before execution. Thumb-to-ARM transitions also capture the ARM target pair.
|
The first target instruction samples T+2W before execution. State transitions replace the buffer with the destination pair.
|
||||||
Leaving ARM discards its buffer. Thumb still fetches directly and uses its separate bus-history snapshots.
|
Thumb BL's prefix advances sequentially; only its suffix refills. Ordinary Thumb PC writes preserve Thumb state.
|
||||||
|
The buffer records both expected PC and instruction state. A state mismatch cannot reinterpret retained ARM words as Thumb halfwords.
|
||||||
|
|
||||||
Machine IRQ entry captures the ARM vector pair before any handler instruction runs.
|
Machine IRQ entry captures the ARM vector pair before any handler instruction runs.
|
||||||
`Cpu::enter_exception` and `Cpu::take_interrupt` have no memory parameter: they invalidate the buffer and leave vector sampling to the next step.
|
`Cpu::enter_exception` and `Cpu::take_interrupt` have no memory parameter: they invalidate the buffer and leave vector sampling to the next step.
|
||||||
|
|
@ -101,14 +103,14 @@ A failed instruction preserves all CPU state, including the old buffer; its spec
|
||||||
A successful branch discards abandoned-path errors and replaces them with target fetch results.
|
A successful branch discards abandoned-path errors and replaces them with target fetch results.
|
||||||
|
|
||||||
CPU clones and equality include the buffer. Architectural state checks are separate from full-state rollback checks in tests.
|
CPU clones and equality include the buffer. Architectural state checks are separate from full-state rollback checks in tests.
|
||||||
`Cpu::invalidate_pipeline()` explicitly discards buffered words without changing registers, flags, or device clocks.
|
`Cpu::invalidate_pipeline()` explicitly discards buffered instructions without changing registers, flags, or device clocks.
|
||||||
Use it after debugger code repair or when attaching different memory to a retained CPU.
|
Use it after debugger code repair or when attaching different memory to a retained CPU.
|
||||||
Do not call it for ordinary CPU/DMA stores: that would hide self-modifying-code behavior.
|
Do not call it for ordinary CPU/DMA stores: that would hide self-modifying-code behavior.
|
||||||
|
|
||||||
This is instruction buffering, not a complete timed fetch pipeline.
|
This is instruction buffering, not a complete timed fetch pipeline.
|
||||||
Samples add no nominal data accesses or device cycles. Existing refill costs and one-shot DMA resume costs remain unchanged.
|
Samples add no nominal data accesses or device cycles. Existing refill costs and one-shot DMA resume costs remain unchanged.
|
||||||
Thumb buffering, fetch-driven bus history, exact refill costs, per-access device updates, and DMA arbitration remain incomplete.
|
Fetch-driven bus history, exact refill costs, per-access device updates, and DMA arbitration remain incomplete.
|
||||||
See [the evidence, diagnostic policy, and tests](../research/arm-instruction-buffer.md).
|
See the [ARM evidence and diagnostic policy](../research/arm-instruction-buffer.md) and [Thumb extension](../research/thumb-instruction-buffer.md).
|
||||||
|
|
||||||
## ARM unused-memory data reads
|
## ARM unused-memory data reads
|
||||||
|
|
||||||
|
|
@ -137,8 +139,8 @@ Limits remain explicit:
|
||||||
[DMA channel data](dma.md#retained-channel-data) is separate and never overrides a CPU snapshot.
|
[DMA channel data](dma.md#retained-channel-data) is separate and never overrides a CPU snapshot.
|
||||||
- Missing BIOS, truncated ROM, unsupported I/O, and save-memory accesses retain their existing diagnostics.
|
- Missing BIOS, truncated ROM, unsupported I/O, and save-memory accesses retain their existing diagnostics.
|
||||||
- Unused-memory writes remain diagnostics rather than ignored hardware writes. Swaps cannot silently discard their write.
|
- Unused-memory writes remain diagnostics rather than ignored hardware writes. Swaps cannot silently discard their write.
|
||||||
- ARM instruction buffering is persistent, but these data snapshots remain a separate bounded model.
|
- ARM/Thumb instruction buffering is persistent, but these data snapshots remain a separate bounded model.
|
||||||
Thumb IWRAM refills sample their target pair without buffering Thumb instructions.
|
Thumb IWRAM bus-history samples do not share storage with retained instructions.
|
||||||
DMA updates existing IWRAM continuation lanes at instruction boundaries, not during CPU accesses.
|
DMA updates existing IWRAM continuation lanes at instruction boundaries, not during CPU accesses.
|
||||||
|
|
||||||
This subset lets public ARM test 362 complete. That test does not assert the loaded data value.
|
This subset lets public ARM test 362 complete. That test does not assert the loaded data value.
|
||||||
|
|
@ -220,13 +222,13 @@ Stack and block-load returns finish their data accesses before the target sample
|
||||||
Failed instructions do not sample a target or replace committed history.
|
Failed instructions do not sample a target or replace committed history.
|
||||||
|
|
||||||
Snapshots use strict mapped reads and add no nominal data or device cycles.
|
Snapshots use strict mapped reads and add no nominal data or device cycles.
|
||||||
They update bus history only; the interpreter does not cache these instructions for execution.
|
They update bus history only; a separate CPU buffer retains the target instructions for execution.
|
||||||
Host inspection does not change captured lanes. A failed target fetch remains a later instruction diagnostic.
|
Host inspection does not change captured lanes. A failed target fetch remains a later instruction diagnostic.
|
||||||
Cold direct startup, unsupported target states/regions, and region-crossing lookahead still have no inferred refill history.
|
Cold direct startup, unsupported target states/regions, and region-crossing lookahead still have no inferred refill history.
|
||||||
DMA between refill and arrival updates this continuation's actual IWRAM access lanes before the target+4 sample.
|
DMA between refill and arrival updates this continuation's actual IWRAM access lanes before the target+4 sample.
|
||||||
|
|
||||||
The [refill evidence and scope](../research/iwram-bus-history.md#thumb-iwram-refill-extension) distinguish these samples from a full pipeline.
|
The [refill evidence and scope](../research/iwram-bus-history.md#thumb-iwram-refill-extension) distinguish these samples from a full pipeline.
|
||||||
ARM-target refill history, sub-instruction DMA ordering, self-modifying instruction execution, and exact refill timing remain incomplete.
|
ARM-target refill history, sub-instruction DMA ordering, and exact refill timing remain incomplete.
|
||||||
|
|
||||||
### DMA effects on Thumb IWRAM continuations
|
### DMA effects on Thumb IWRAM continuations
|
||||||
|
|
||||||
|
|
@ -273,7 +275,7 @@ In User/System modes, they perform ordinary test/compare flag updates without a
|
||||||
Invalid saved modes still produce an atomic diagnostic. Failed conditions do not restore or validate status.
|
Invalid saved modes still produce an atomic diagnostic. Failed conditions do not restore or validate status.
|
||||||
Nominal timing remains one sequential code access, plus an internal cycle for register-specified shifts; there is no refill cost.
|
Nominal timing remains one sequential code access, plus an internal cycle for register-specified shifts; there is no refill cost.
|
||||||
This fixes the mode-switch expectation in public `jsmolka/gba-tests` ARM test 234 and agrees with mGBA's shared ALU flag handling.
|
This fixes the mode-switch expectation in public `jsmolka/gba-tests` ARM test 234 and agrees with mGBA's shared ALU flag handling.
|
||||||
ARM instruction buffering is discarded on a state change without an ordinary refill.
|
Instruction buffering is discarded on a state change without an ordinary refill.
|
||||||
Instruction-state-changing compare forms and exact pipeline behavior remain hardware-unverified.
|
Instruction-state-changing compare forms and exact pipeline behavior remain hardware-unverified.
|
||||||
|
|
||||||
Other S-bit block transfers access User registers while using the current mode's base register.
|
Other S-bit block transfers access User registers while using the current mode's base register.
|
||||||
|
|
|
||||||
|
|
@ -147,13 +147,16 @@ DMA resume-timing research:
|
||||||
See [non-sequential CPU resume](research/dma-resume-timing.md) for the nominal subset and unmodeled refill/prefetch details.
|
See [non-sequential CPU resume](research/dma-resume-timing.md) for the nominal subset and unmodeled refill/prefetch details.
|
||||||
The public test was reviewed, not executed. No hardware timing pass is claimed.
|
The public test was reviewed, not executed. No hardware timing pass is claimed.
|
||||||
|
|
||||||
ARM instruction-buffer research:
|
ARM/Thumb instruction-buffer research:
|
||||||
|
|
||||||
- [ARM7TDMI instruction pipeline](https://developer.arm.com/documentation/dvi0027/b/arm7tdmi/instruction-pipeline) and [technical reference manual](https://documentation-service.arm.com/static/5e8e1323fd977155116a3129), for fetch/decode/execute stages and the visible ARM PC offset.
|
- [ARM7TDMI instruction pipeline](https://developer.arm.com/documentation/dvi0027/b/arm7tdmi/instruction-pipeline) and [technical reference manual](https://documentation-service.arm.com/static/5e8e1323fd977155116a3129), for fetch/decode/execute stages and the visible ARM/Thumb PC offsets.
|
||||||
- The pinned NanoBoyAdvance ARM7TDMI `Run` and `ReloadPipeline32` helpers above, for retained words, pre-execution fetching, and target-pair refill order.
|
- The pinned NanoBoyAdvance ARM7TDMI `Run`, `ReloadPipeline32`, and `ReloadPipeline16` helpers above, for retained instructions, pre-execution fetching, and target-pair refill order.
|
||||||
- The ARM branch cycle sequence above, for target and target+instruction-width fetches.
|
- The ARM branch cycle sequence above, for target and target+instruction-width fetches.
|
||||||
|
|
||||||
See [persistent ARM instruction buffering](research/arm-instruction-buffer.md). Instruction retention is implemented; per-access timing and Thumb buffering remain incomplete.
|
- [ARM Thumb branch-with-link timing](https://support.arm.com/documentation/ddi0210/c/Instruction-Cycle-Timings/Thumb-branch-with-link), for sequential prefix execution and suffix target refill.
|
||||||
|
|
||||||
|
See [persistent ARM instruction buffering](research/arm-instruction-buffer.md) and its [Thumb extension](research/thumb-instruction-buffer.md).
|
||||||
|
Instruction retention is implemented; unified fetch-driven bus history and per-access timing remain incomplete.
|
||||||
|
|
||||||
Use [GBATEK](https://problemkaputt.de/gbatek.htm) and ARM7TDMI documentation for further hardware work.
|
Use [GBATEK](https://problemkaputt.de/gbatek.htm) and ARM7TDMI documentation for further hardware work.
|
||||||
Only use game ROMs that you may lawfully use. Do not commit game ROMs, BIOS files, or game assets.
|
Only use game ROMs that you may lawfully use. Do not commit game ROMs, BIOS files, or game assets.
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,8 @@ No external source code or test ROM was imported. No physical-hardware or extern
|
||||||
|
|
||||||
## Implementation
|
## Implementation
|
||||||
|
|
||||||
`cpu/pipeline.rs` owns a private two-slot ARM continuation with its expected execution PC.
|
`cpu/pipeline.rs` owns a private two-slot continuation with its expected execution PC and instruction state.
|
||||||
|
The later [Thumb extension](thumb-instruction-buffer.md) uses this same structure for halfwords.
|
||||||
Each slot retains either a mapped word or its strict fetch error.
|
Each slot retains either a mapped word or its strict fetch error.
|
||||||
`Cpu` owns and clones this state; full CPU equality includes it.
|
`Cpu` owns and clones this state; full CPU equality includes it.
|
||||||
|
|
||||||
|
|
@ -37,13 +38,13 @@ Successful non-refill ARM execution commits the next pair.
|
||||||
A successful refill into ARM instead samples T and T+4 after data transfers and status restoration.
|
A successful refill into ARM instead samples T and T+4 after data transfers and status restoration.
|
||||||
This includes ARM/Thumb BX, ARM PC writes and loads, taken branches, exception returns, and SWI vectors.
|
This includes ARM/Thumb BX, ARM PC writes and loads, taken branches, exception returns, and SWI vectors.
|
||||||
A refill to the sequential address still discards the previous sequential pair.
|
A refill to the sequential address still discards the previous sequential pair.
|
||||||
Untaken branches retain the pair. Leaving ARM clears it; Thumb continues to use direct instruction reads.
|
Untaken branches retain the pair. A transition to Thumb now replaces it with a Thumb target pair.
|
||||||
|
|
||||||
Machine IRQ entry captures its vector pair immediately after exception entry.
|
Machine IRQ entry captures its vector pair immediately after exception entry.
|
||||||
The standalone exception APIs have no Memory argument, so they invalidate the old pair and leave the vector cold.
|
The standalone exception APIs have no Memory argument, so they invalidate the old pair and leave the vector cold.
|
||||||
This distinction is explicit rather than retaining unrelated words at a coincident PC.
|
This distinction is explicit rather than retaining unrelated words at a coincident PC.
|
||||||
|
|
||||||
`Memory::fetch_arm_word` reads aligned mapped words without CPU data context, timing traces, or bus-history changes.
|
`Memory::fetch_instruction` reads aligned mapped instructions without CPU data context, timing traces, or bus-history changes.
|
||||||
Normal memory mirrors apply. Unmapped, truncated, and misaligned fetches retain their existing errors.
|
Normal memory mirrors apply. Unmapped, truncated, and misaligned fetches retain their existing errors.
|
||||||
No BIOS-protected data value or general open-bus value can become a synthetic instruction through this path.
|
No BIOS-protected data value or general open-bus value can become a synthetic instruction through this path.
|
||||||
|
|
||||||
|
|
@ -74,7 +75,7 @@ The current instruction address still selects nominal code cost rather than a se
|
||||||
ARM PC+8 open bus and protected BIOS readback remain separate snapshots.
|
ARM PC+8 open bus and protected BIOS readback remain separate snapshots.
|
||||||
The instruction-buffer sample and those snapshots see mapped memory before instruction effects, but no unified per-access bus model exists yet.
|
The instruction-buffer sample and those snapshots see mapped memory before instruction effects, but no unified per-access bus model exists yet.
|
||||||
The target samples do not establish complete ARM IWRAM or BIOS bus history.
|
The target samples do not establish complete ARM IWRAM or BIOS bus history.
|
||||||
Thumb buffering, instruction-state-changing compare quirks, Game Pak prefetch, and DMA arbitration remain incomplete.
|
Instruction-state-changing compare quirks, Game Pak prefetch, and DMA arbitration remain incomplete.
|
||||||
DMA still runs between whole instructions, not during their data accesses or internal cycles.
|
DMA still runs between whole instructions, not during their data accesses or internal cycles.
|
||||||
|
|
||||||
## Original regression coverage
|
## Original regression coverage
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ A completed DMA WAITCNT store changes the settings before CPU resume.
|
||||||
## Limits
|
## Limits
|
||||||
|
|
||||||
The timing model has no per-access fetch scheduling or DMA arbitration.
|
The timing model has no per-access fetch scheduling or DMA arbitration.
|
||||||
The later [ARM instruction buffer](arm-instruction-buffer.md) retains instruction words but does not change these nominal costs.
|
The later [ARM](arm-instruction-buffer.md) and [Thumb](thumb-instruction-buffer.md) instruction buffers retain instructions without changing these nominal costs.
|
||||||
It charges the current instruction address, not a separately scheduled PC+4/PC+8 pipeline fetch.
|
It charges the current instruction address, not a separately scheduled PC+4/PC+8 pipeline fetch.
|
||||||
DMA still executes only between instructions, not between data accesses or during CPU internal cycles.
|
DMA still executes only between instructions, not between data accesses or during CPU internal cycles.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ An accepted machine IRQ invalidates history. Failed DMA units preserve it.
|
||||||
The DMA extension below updates existing continuation lanes from successful IWRAM accesses.
|
The DMA extension below updates existing continuation lanes from successful IWRAM accesses.
|
||||||
|
|
||||||
This is not a full IWRAM bus model across all CPU states. ARM-target history and DMA-to-CPU ordering remain incomplete.
|
This is not a full IWRAM bus model across all CPU states. ARM-target history and DMA-to-CPU ordering remain incomplete.
|
||||||
It is not a persistent instruction pipeline. Self-modifying instruction execution and exact per-access device timing remain unverified.
|
It is not a persistent instruction pipeline. Exact per-access device timing remains unverified.
|
||||||
|
|
||||||
## Thumb IWRAM refill extension
|
## Thumb IWRAM refill extension
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ Unmapped instruction targets still fail on the following fetch.
|
||||||
Cold direct startup and arbitrary PC changes do not manufacture a refill.
|
Cold direct startup and arbitrary PC changes do not manufacture a refill.
|
||||||
|
|
||||||
These are bus-history samples, not instruction-buffer contents or extra emulated cycles.
|
These are bus-history samples, not instruction-buffer contents or extra emulated cycles.
|
||||||
The interpreter still reads Thumb instructions when it executes them; ARM now has separate instruction buffering.
|
ARM and Thumb execution now use a separate instruction buffer. Cold buffer filling does not seed this bus history.
|
||||||
Changing T+2 after the branch does not replace its captured bus value; changing T+4 before arrival affects the later sample.
|
Changing T+2 after the branch does not replace its captured bus value; changing T+4 before arrival affects the later sample.
|
||||||
Those original tests verify sample ordering, not hardware-accurate self-modifying instruction execution.
|
Those original tests verify sample ordering, not hardware-accurate self-modifying instruction execution.
|
||||||
|
|
||||||
|
|
@ -111,8 +111,8 @@ Only complete successful units commit history, preserving the project's atomic d
|
||||||
Cold direct entry still has no expected continuation PC. DMA does not manufacture one, even after a word access.
|
Cold direct entry still has no expected continuation PC. DMA does not manufacture one, even after a word access.
|
||||||
An existing partially known continuation can gain lanes. IRQ entry and CPU discontinuities retain their invalidation rules.
|
An existing partially known continuation can gain lanes. IRQ entry and CPU discontinuities retain their invalidation rules.
|
||||||
General DMA open-bus reads, cross-state history, sub-instruction arbitration, and DMA during CPU internal cycles remain outside this subset.
|
General DMA open-bus reads, cross-state history, sub-instruction arbitration, and DMA during CPU internal cycles remain outside this subset.
|
||||||
This extension does not fix self-modifying Thumb instruction-buffer behavior.
|
This extension models bus lanes, not self-modifying instruction execution.
|
||||||
The later [ARM instruction buffer](arm-instruction-buffer.md) separately retains ARM words across DMA.
|
The later [ARM](arm-instruction-buffer.md) and [Thumb](thumb-instruction-buffer.md) instruction buffers separately retain instructions across DMA.
|
||||||
|
|
||||||
No physical-hardware measurements or external emulator differential runs were performed.
|
No physical-hardware measurements or external emulator differential runs were performed.
|
||||||
All regression programs are original, with a separate byte-array lane reference in the DMA matrix tests.
|
All regression programs are original, with a separate byte-array lane reference in the DMA matrix tests.
|
||||||
|
|
|
||||||
85
docs/research/thumb-instruction-buffer.md
Normal file
85
docs/research/thumb-instruction-buffer.md
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
# Persistent Thumb instruction buffering
|
||||||
|
|
||||||
|
## Evidence and scope
|
||||||
|
|
||||||
|
The [ARM7TDMI pipeline description](https://developer.arm.com/documentation/dvi0027/b/arm7tdmi/instruction-pipeline) identifies fetch, decode, and execute stages.
|
||||||
|
The [technical reference manual](https://documentation-service.arm.com/static/5e8e1323fd977155116a3129) describes Thumb's two-byte instruction width and visible PC offset of four bytes.
|
||||||
|
The [Thumb BL cycle sequence](https://support.arm.com/documentation/ddi0210/c/Instruction-Cycle-Timings/Thumb-branch-with-link) distinguishes the sequential prefix from the branching suffix.
|
||||||
|
|
||||||
|
[NanoBoyAdvance at 55b5cf0a](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/arm/arm7tdmi.hh) provides an implementation cross-check:
|
||||||
|
|
||||||
|
- `Run` takes the retained current instruction and shifts the next instruction forward.
|
||||||
|
- In Thumb state, it reads the next halfword before executing the current instruction.
|
||||||
|
- `ReloadPipeline16` reads target T, then T+2. The target instruction later fetches T+4.
|
||||||
|
- `ReloadPipeline32` uses ARM's four-byte width instead.
|
||||||
|
|
||||||
|
Thus, execution at P retains the halfwords at P+2 and P+4 before instruction effects.
|
||||||
|
A store cannot replace these retained instructions. It can affect P+6, which is fetched later.
|
||||||
|
A taken PC write discards the sequential pair, even when its destination equals fallthrough.
|
||||||
|
The BL prefix retains its suffix; only the suffix refills the target pair.
|
||||||
|
|
||||||
|
This extends the [ARM instruction buffer](arm-instruction-buffer.md), not the bus scheduler.
|
||||||
|
No external source code or test ROM was imported. No physical-hardware or external-emulator differential run was performed.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
`cpu/pipeline.rs` shares one two-slot buffer between ARM and Thumb.
|
||||||
|
Each buffer records its expected execution PC and instruction state.
|
||||||
|
Both must match before retained instructions can be reused.
|
||||||
|
Thumb halfwords are zero-extended in the slots; upper bits do not hold another instruction.
|
||||||
|
|
||||||
|
Cold execution reads the current instruction and samples the next two instructions.
|
||||||
|
Sequential execution uses retained results and samples only P+4 in Thumb state.
|
||||||
|
The prospective continuation commits only after successful execution.
|
||||||
|
Refills sample the target pair after data transfers and status restoration, using the resulting instruction state.
|
||||||
|
ARM-to-Thumb BX and saved-state exception returns therefore capture T and T+2, not T and T+4.
|
||||||
|
Thumb-to-ARM transitions and SWI/IRQ vectors capture ARM pairs.
|
||||||
|
|
||||||
|
`Memory::fetch_instruction` uses strict mapped reads with the selected instruction width.
|
||||||
|
A Thumb fetch reads exactly two bytes, even in a region whose separate bus snapshot uses a full word.
|
||||||
|
Memory mirrors apply. Unmapped and misaligned instruction reads retain diagnostics rather than using data open bus.
|
||||||
|
Lookahead errors remain deferred until their slot executes; a branch can discard them.
|
||||||
|
|
||||||
|
Failed execution preserves the previous buffer exactly. Newly sampled lookahead does not commit.
|
||||||
|
CPU stores, DMA transfers, and host writes never replace retained instructions automatically.
|
||||||
|
`Cpu::invalidate_pipeline()` explicitly discards retained instructions for debugger repair or attachment to different memory.
|
||||||
|
It does not clear separate BIOS or IWRAM bus history.
|
||||||
|
CPU clones own independent buffers; full CPU equality includes them.
|
||||||
|
|
||||||
|
## Separation from bus history and timing
|
||||||
|
|
||||||
|
Instruction retention and data-bus snapshots remain separate models.
|
||||||
|
Cold instruction-buffer filling does not make unknown IWRAM bus lanes known.
|
||||||
|
Successful DMA can update existing IWRAM bus lanes without changing retained CPU instructions.
|
||||||
|
Refill bus-history sampling and instruction sampling currently perform separate strict mapped reads.
|
||||||
|
|
||||||
|
Buffer samples add no nominal cycles or data accesses.
|
||||||
|
Timed and untimed execution use the same buffer logic.
|
||||||
|
Destination-based refill summaries and one-shot non-sequential CPU resume costs remain unchanged.
|
||||||
|
Unified fetch-driven bus history, exact region crossings, per-access timing, Game Pak prefetch, and sub-instruction DMA arbitration remain incomplete.
|
||||||
|
Instruction-state-changing compare quirks also remain hardware-unverified.
|
||||||
|
|
||||||
|
## Original regression coverage
|
||||||
|
|
||||||
|
Original programs cover:
|
||||||
|
|
||||||
|
- CPU byte/halfword stores to P+2, P+4, and the later P+6 instruction.
|
||||||
|
- Host writes, RAM/video mirrors, and halfword/word DMA writes.
|
||||||
|
- Taken and untaken branches, PC writes to fallthrough, and standalone BL suffixes.
|
||||||
|
- BL prefix retention and suffix refill, with unchanged nominal costs.
|
||||||
|
- ARM-to-Thumb target pairs at both halfword alignments and SWI/IRQ saved-state returns.
|
||||||
|
- Failed data loads, discarded speculative advances, deferred short-ROM errors, and strict target diagnostics.
|
||||||
|
- Debugger invalidation, independent clones, PC/state mismatches, and alignment errors.
|
||||||
|
- Equal timed/untimed buffers without additional nominal cycles.
|
||||||
|
|
||||||
|
The retention regressions failed with the previous direct-fetch Thumb interpreter before implementation.
|
||||||
|
Existing bus-history tests now invalidate deliberately repaired code or repair a data address without changing the retained instruction.
|
||||||
|
Successful instruction-semantic tests compare architectural state separately; failed-step tests still compare complete CPU state.
|
||||||
|
|
||||||
|
## Validation result
|
||||||
|
|
||||||
|
Workspace and core/demo tests pass in debug and release on Darwin arm64.
|
||||||
|
Formatting, lint checks, rustdoc, preparation tests, native ROM windows, and graphics smoke modes pass.
|
||||||
|
Public ARM, Thumb, memory, and BIOS reports match the previous ARM-buffer reports exactly, including nominal cycle counts.
|
||||||
|
Debug and release reports also match for the same fixture paths.
|
||||||
|
These checks do not establish full hardware pipeline or timing conformance.
|
||||||
|
|
@ -27,7 +27,7 @@ Update this file when a feature lands or a limit is removed.
|
||||||
- `KEYCNT` selection and OR/AND matching, keypad IRQ latching, and HALT wake-up through IE bit 12.
|
- `KEYCNT` selection and OR/AND matching, keypad IRQ latching, and HALT wake-up through IE bit 12.
|
||||||
- Original ARM graphics demo that uses DMA, BIOS VBlank waiting, and a ROM-side IRQ callback.
|
- Original ARM graphics demo that uses DMA, BIOS VBlank waiting, and a ROM-side IRQ callback.
|
||||||
- Sixteen active 32-bit CPU registers, with ARM7 mode-specific register banks.
|
- Sixteen active 32-bit CPU registers, with ARM7 mode-specific register banks.
|
||||||
- Persistent ARM instruction buffering, sequential fetch retention, branch/vector target pairs, and deferred fetch diagnostics.
|
- Persistent ARM/Thumb instruction buffering, sequential fetch retention, branch/vector target pairs, and deferred fetch diagnostics.
|
||||||
- User, System, Supervisor, IRQ, FIQ, Abort, and Undefined modes.
|
- User, System, Supervisor, IRQ, FIQ, Abort, and Undefined modes.
|
||||||
- Current and saved program status registers (`CPSR`/`SPSR`), with `MRS`/`MSR` transfers.
|
- Current and saved program status registers (`CPSR`/`SPSR`), with `MRS`/`MSR` transfers.
|
||||||
- ARM/Thumb software interrupts (`SWI`), exception vectors, and status-restoring returns.
|
- ARM/Thumb software interrupts (`SWI`), exception vectors, and status-restoring returns.
|
||||||
|
|
@ -141,8 +141,8 @@ Cold or invalidated IWRAM history remains diagnostic until all lanes are known.
|
||||||
Refills into Thumb IWRAM establish new target-pair history after the instruction succeeds.
|
Refills into Thumb IWRAM establish new target-pair history after the instruction succeeds.
|
||||||
Accepted machine IRQs, unsupported refills, and other execution states/regions invalidate continuation history.
|
Accepted machine IRQs, unsupported refills, and other execution states/regions invalidate continuation history.
|
||||||
Successful DMA updates existing Thumb IWRAM continuation lanes for actual IWRAM accesses, before the resumed PC+4 sample.
|
Successful DMA updates existing Thumb IWRAM continuation lanes for actual IWRAM accesses, before the resumed PC+4 sample.
|
||||||
ARM instruction buffering now retains sequential words and refill target pairs, independently of these bus snapshots.
|
ARM/Thumb instruction buffering retains sequential instructions and refill target pairs, independently of these bus snapshots.
|
||||||
Thumb buffering, complete ARM bus history, exact region-crossing bus behavior, general DMA handoff, and unused/write-only I/O open bus remain incomplete.
|
Complete ARM bus history, exact region-crossing bus behavior, general DMA handoff, and unused/write-only I/O open bus remain incomplete.
|
||||||
[DMA channel data latches](hardware/dma.md#retained-channel-data) support blocked-source reuse, but never replace CPU bus history.
|
[DMA channel data latches](hardware/dma.md#retained-channel-data) support blocked-source reuse, but never replace CPU bus history.
|
||||||
BIOS protection uses separate retained BIOS fetch snapshots as described in [BIOS behavior](hardware/bios.md#cpu-bios-read-protection).
|
BIOS protection uses separate retained BIOS fetch snapshots as described in [BIOS behavior](hardware/bios.md#cpu-bios-read-protection).
|
||||||
Host inspection and instruction fetches remain strict. Other unmapped reads return errors.
|
Host inspection and instruction fetches remain strict. Other unmapped reads return errors.
|
||||||
|
|
@ -153,7 +153,7 @@ Writes to cartridge addresses return errors instead of modeling cartridge hardwa
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
1. Extend persistent ARM instruction buffering to Thumb, then connect fetches to bus history with independent per-access tests.
|
1. Connect ARM/Thumb instruction fetches to bus history with independent per-access tests.
|
||||||
General DMA open-bus reads and sub-instruction arbitration remain separate from retained channel data and local IWRAM lanes.
|
General DMA open-bus reads and sub-instruction arbitration remain separate from retained channel data and local IWRAM lanes.
|
||||||
Keep unknown history diagnostic until fetch ordering and latch ownership have independent coverage.
|
Keep unknown history diagnostic until fetch ordering and latch ownership have independent coverage.
|
||||||
Validate BIOS snapshots with independent hardware tests before claiming exact refill or data-access history.
|
Validate BIOS snapshots with independent hardware tests before claiming exact refill or data-access history.
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,10 @@ They cover refill target pairs, PC writes to fallthrough, conditional branches,
|
||||||
Full CPU equality checks buffer rollback and cloning. Successful instruction-semantic tests compare architectural state separately.
|
Full CPU equality checks buffer rollback and cloning. Successful instruction-semantic tests compare architectural state separately.
|
||||||
Explicit invalidation tests cover debugger repair. Timed/untimed tests verify equal buffers without added nominal cycles.
|
Explicit invalidation tests cover debugger repair. Timed/untimed tests verify equal buffers without added nominal cycles.
|
||||||
The display-runner error test allows an already buffered branch to complete before the patched unsupported word executes.
|
The display-runner error test allows an already buffered branch to complete before the patched unsupported word executes.
|
||||||
See [ARM instruction buffering](research/arm-instruction-buffer.md) for scope and remaining timing limits.
|
Thumb instruction-buffer tests verify retained P+2/P+4 halfwords, BL prefix/suffix behavior, and state-tagged target pairs.
|
||||||
|
They also cover stores, host/DMA writes, saved-state returns, deferred errors, mirrors, debugger repair, and nominal timing.
|
||||||
|
Bus-history repair tests explicitly invalidate buffered code or repair a data address; failure checks retain full CPU equality.
|
||||||
|
See [ARM instruction buffering](research/arm-instruction-buffer.md) and its [Thumb extension](research/thumb-instruction-buffer.md) for remaining limits.
|
||||||
The pinned public ARM ROM now reaches its completion checkpoint with r12 = 0 in debug and release builds.
|
The pinned public ARM ROM now reaches its completion checkpoint with r12 = 0 in debug and release builds.
|
||||||
The pinned public Thumb ROM also reaches its ARM-state checkpoint with r7 = 0 in both builds.
|
The pinned public Thumb ROM also reaches its ARM-state checkpoint with r7 = 0 in both builds.
|
||||||
Empty-list Thumb regressions cover PC+6, all low bases and processor modes, banked stacks, both code alignments, and unaligned data.
|
Empty-list Thumb regressions cover PC+6, all low bases and processor modes, banked stacks, both code alignments, and unaligned data.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue