refactor(cpu): share instruction fetch samples with bus history
This commit is contained in:
parent
ea055270f4
commit
e630d7a0ea
18 changed files with 538 additions and 117 deletions
|
|
@ -38,6 +38,8 @@ mod dma_resume_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod exception_tests;
|
mod exception_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
mod fetch_history_tests;
|
||||||
|
#[cfg(test)]
|
||||||
mod instruction_tests;
|
mod instruction_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod iwram_dma_tests;
|
mod iwram_dma_tests;
|
||||||
|
|
@ -155,7 +157,7 @@ pub enum InstructionSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstructionSet {
|
impl InstructionSet {
|
||||||
fn width(self) -> u32 {
|
pub(crate) fn width(self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Arm => 4,
|
Self::Arm => 4,
|
||||||
Self::Thumb => 2,
|
Self::Thumb => 2,
|
||||||
|
|
@ -235,6 +237,7 @@ impl Cpu {
|
||||||
let pipeline::Fetched {
|
let pipeline::Fetched {
|
||||||
instruction,
|
instruction,
|
||||||
continuation,
|
continuation,
|
||||||
|
lookahead,
|
||||||
} = fetched;
|
} = fetched;
|
||||||
// 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.
|
||||||
|
|
@ -242,7 +245,7 @@ impl Cpu {
|
||||||
let instruction_set = self.instruction_set;
|
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, &lookahead);
|
||||||
let result = self.execute_instruction(instruction, memory);
|
let result = self.execute_instruction(instruction, memory);
|
||||||
let sequential = sequential_thumb
|
let sequential = sequential_thumb
|
||||||
&& self.instruction_set == InstructionSet::Thumb
|
&& self.instruction_set == InstructionSet::Thumb
|
||||||
|
|
@ -250,7 +253,6 @@ impl Cpu {
|
||||||
memory.end_cpu_access(result.is_ok(), sequential);
|
memory.end_cpu_access(result.is_ok(), sequential);
|
||||||
if result.is_ok() {
|
if result.is_ok() {
|
||||||
if refill {
|
if refill {
|
||||||
memory.refill_cpu_bus_history(self.pc(), self.instruction_set);
|
|
||||||
self.refill_pipeline(memory);
|
self.refill_pipeline(memory);
|
||||||
} else {
|
} else {
|
||||||
// A state change without an ordinary refill starts a cold sequence.
|
// A state change without an ordinary refill starts a cold sequence.
|
||||||
|
|
|
||||||
114
crates/gba-core/src/cpu/fetch_history_tests.rs
Normal file
114
crates/gba-core/src/cpu/fetch_history_tests.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
//! Original sample-consumption tests. The split fetch/execute is a test-only mutation seam,
|
||||||
|
//! not a claim that DMA can interrupt an instruction in the current scheduler.
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const UNUSED: u32 = 0x8000_0000;
|
||||||
|
const DATA: u32 = 0x0300_1000;
|
||||||
|
const OLD: u32 = 0x1234_5678;
|
||||||
|
const NEW: u32 = 0x9abc_def0;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arm_open_bus_uses_the_sample_already_taken_for_the_pipeline() {
|
||||||
|
for pc in [0x0200_0100, 0x0300_0100, 0x0700_0100] {
|
||||||
|
let mut bus = Memory::new(vec![]).unwrap();
|
||||||
|
bus.write32(pc, 0xe590_6000).unwrap(); // LDR r6,[r0].
|
||||||
|
bus.write32(pc + 8, OLD).unwrap();
|
||||||
|
let mut cpu = Cpu::new(pc);
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
let fetched = cpu.fetch(&bus).unwrap();
|
||||||
|
bus.write32(pc + 8, NEW).unwrap();
|
||||||
|
cpu.execute_fetched(fetched, &mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[6], OLD);
|
||||||
|
assert_eq!(bus.read32(pc + 8).unwrap(), NEW);
|
||||||
|
assert_eq!(bus.cycles(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn thumb_narrow_open_bus_uses_the_sample_already_taken_for_the_pipeline() {
|
||||||
|
for pc in [0x0200_0100, 0x0500_0100, 0x0600_0100] {
|
||||||
|
for alignment in [0, 2] {
|
||||||
|
let pc = pc + alignment;
|
||||||
|
let mut bus = Memory::new(vec![]).unwrap();
|
||||||
|
bus.write16(pc, 0x6806).unwrap(); // LDR r6,[r0].
|
||||||
|
bus.write16(pc + 4, OLD as u16).unwrap();
|
||||||
|
let mut cpu = Cpu::new(pc);
|
||||||
|
cpu.instruction_set = InstructionSet::Thumb;
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
let fetched = cpu.fetch(&bus).unwrap();
|
||||||
|
bus.write16(pc + 4, NEW as u16).unwrap();
|
||||||
|
cpu.execute_fetched(fetched, &mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[6], 0x5678_5678);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn thumb_wide_open_bus_retains_both_lanes_of_the_fetch_sample() {
|
||||||
|
for pc in [0x0700_0100, 0x0700_0102] {
|
||||||
|
let mut bus = Memory::new(vec![]).unwrap();
|
||||||
|
bus.write16(pc, 0x6806).unwrap();
|
||||||
|
bus.write32((pc + 4) & !3, OLD).unwrap();
|
||||||
|
let mut cpu = Cpu::new(pc);
|
||||||
|
cpu.instruction_set = InstructionSet::Thumb;
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
let fetched = cpu.fetch(&bus).unwrap();
|
||||||
|
bus.write32((pc + 4) & !3, NEW).unwrap();
|
||||||
|
cpu.execute_fetched(fetched, &mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[6], OLD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iwram_lane_history_uses_new_fetch_data_not_retained_opcodes_or_reread_bytes() {
|
||||||
|
for pc in [0x0300_0100, 0x0300_0102] {
|
||||||
|
let mut bus = Memory::new(vec![]).unwrap();
|
||||||
|
for (index, half) in [0x680a, 0x6806, 0x46c0, 0x5678].into_iter().enumerate() {
|
||||||
|
bus.write16(pc + index as u32 * 2, half).unwrap();
|
||||||
|
}
|
||||||
|
bus.write32(DATA, 0x1122_3344).unwrap();
|
||||||
|
let mut cpu = Cpu::new(pc);
|
||||||
|
cpu.instruction_set = InstructionSet::Thumb;
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
cpu.registers[1] = DATA;
|
||||||
|
cpu.step(&mut bus).unwrap(); // Data read establishes all local bus lanes.
|
||||||
|
let fetched = cpu.fetch(&bus).unwrap();
|
||||||
|
bus.write16(pc + 6, 0xdef0).unwrap();
|
||||||
|
cpu.execute_fetched(fetched, &mut bus).unwrap();
|
||||||
|
let expected = if pc & 2 == 0 {
|
||||||
|
0x5678_3344
|
||||||
|
} else {
|
||||||
|
0x1122_5678
|
||||||
|
};
|
||||||
|
assert_eq!(cpu.registers[6], expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_execution_discards_its_sample_and_retry_takes_a_new_one() {
|
||||||
|
let pc = 0x0300_0100;
|
||||||
|
let mut bus = Memory::new(vec![]).unwrap();
|
||||||
|
for (index, half) in [0x680a, 0x6823, 0x6806, 0x5678, 0x9abc]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
bus.write16(pc + index as u32 * 2, half).unwrap();
|
||||||
|
}
|
||||||
|
bus.write32(DATA, 0x1122_3344).unwrap();
|
||||||
|
let mut cpu = Cpu::new(pc);
|
||||||
|
cpu.instruction_set = InstructionSet::Thumb;
|
||||||
|
cpu.registers[0] = UNUSED;
|
||||||
|
cpu.registers[1] = DATA;
|
||||||
|
cpu.registers[4] = 0x0e00_0000;
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
let before = cpu.clone();
|
||||||
|
let fetched = cpu.fetch(&bus).unwrap();
|
||||||
|
assert!(cpu.execute_fetched(fetched, &mut bus).is_err());
|
||||||
|
assert_eq!(cpu, before);
|
||||||
|
bus.write16(pc + 6, 0xdef0).unwrap();
|
||||||
|
cpu.registers[4] = UNUSED; // Retry reads open bus instead of unsupported save memory.
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[3], 0xdef0_3344);
|
||||||
|
cpu.step(&mut bus).unwrap();
|
||||||
|
assert_eq!(cpu.registers[6], 0xdef0_9abc);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! ARM/Thumb instruction buffering. 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::{InstructionFetch, Memory, MemoryError};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub(super) struct Pipeline {
|
pub(super) struct Pipeline {
|
||||||
|
|
@ -14,6 +14,7 @@ pub(super) struct Pipeline {
|
||||||
pub(super) struct Fetched {
|
pub(super) struct Fetched {
|
||||||
pub instruction: u32,
|
pub instruction: u32,
|
||||||
pub continuation: Pipeline,
|
pub continuation: Pipeline,
|
||||||
|
pub lookahead: InstructionFetch,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
|
|
@ -34,11 +35,15 @@ impl Cpu {
|
||||||
.filter(|pipe| pipe.pc == pc && pipe.instruction_set == instruction_set);
|
.filter(|pipe| pipe.pc == pc && pipe.instruction_set == instruction_set);
|
||||||
let instruction = match retained {
|
let instruction = match retained {
|
||||||
Some(pipe) => pipe.instructions[0].clone(),
|
Some(pipe) => pipe.instructions[0].clone(),
|
||||||
None => memory.fetch_instruction(pc, instruction_set),
|
None => memory.fetch_instruction(pc, instruction_set).instruction,
|
||||||
}?;
|
}?;
|
||||||
let decode = match retained {
|
let decode = match retained {
|
||||||
Some(pipe) => pipe.instructions[1].clone(),
|
Some(pipe) => pipe.instructions[1].clone(),
|
||||||
None => memory.fetch_instruction(pc.wrapping_add(width), instruction_set),
|
None => {
|
||||||
|
memory
|
||||||
|
.fetch_instruction(pc.wrapping_add(width), instruction_set)
|
||||||
|
.instruction
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let fetched = memory.fetch_instruction(pc.wrapping_add(2 * width), instruction_set);
|
let fetched = memory.fetch_instruction(pc.wrapping_add(2 * width), instruction_set);
|
||||||
Ok(Fetched {
|
Ok(Fetched {
|
||||||
|
|
@ -46,23 +51,26 @@ impl Cpu {
|
||||||
continuation: Pipeline {
|
continuation: Pipeline {
|
||||||
pc: pc.wrapping_add(width),
|
pc: pc.wrapping_add(width),
|
||||||
instruction_set,
|
instruction_set,
|
||||||
instructions: [decode, fetched],
|
instructions: [decode, fetched.instruction.clone()],
|
||||||
},
|
},
|
||||||
|
lookahead: fetched,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Capture the target pair using the resulting state, 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_pipeline(&mut self, memory: &Memory) {
|
pub(crate) fn refill_pipeline(&mut self, memory: &mut Memory) {
|
||||||
let pc = self.pc();
|
let pc = self.pc();
|
||||||
let instruction_set = self.instruction_set;
|
let instruction_set = self.instruction_set;
|
||||||
|
let fetches = [
|
||||||
|
memory.fetch_instruction(pc, instruction_set),
|
||||||
|
memory.fetch_instruction(pc.wrapping_add(instruction_set.width()), instruction_set),
|
||||||
|
];
|
||||||
|
memory.refill_cpu_bus_history(&fetches);
|
||||||
self.pipeline = Some(Pipeline {
|
self.pipeline = Some(Pipeline {
|
||||||
pc,
|
pc,
|
||||||
instruction_set,
|
instruction_set,
|
||||||
instructions: [
|
instructions: fetches.map(|fetch| fetch.instruction),
|
||||||
memory.fetch_instruction(pc, instruction_set),
|
|
||||||
memory.fetch_instruction(pc.wrapping_add(instruction_set.width()), instruction_set),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -178,8 +178,7 @@ impl Machine {
|
||||||
return Ok(StepKind::HaltIdle);
|
return Ok(StepKind::HaltIdle);
|
||||||
}
|
}
|
||||||
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.cpu.refill_pipeline(&mut 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 {
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
use std::{cell::Cell, error::Error, fmt};
|
use std::{cell::Cell, error::Error, fmt};
|
||||||
|
|
||||||
|
mod fetch;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod fetch_tests;
|
||||||
mod iwram_bus;
|
mod iwram_bus;
|
||||||
|
pub(crate) use fetch::InstructionFetch;
|
||||||
use iwram_bus::IwramBus;
|
use iwram_bus::IwramBus;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -69,7 +73,8 @@ 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/Thumb instruction buffering does not yet drive complete bus history or timing.
|
/// Instruction buffering and supported bus history consume shared fetch samples.
|
||||||
|
/// Complete bus ownership and per-access timing remain unmodeled.
|
||||||
pub struct Memory {
|
pub struct Memory {
|
||||||
external_ram: Vec<u8>,
|
external_ram: Vec<u8>,
|
||||||
internal_ram: Vec<u8>,
|
internal_ram: Vec<u8>,
|
||||||
|
|
@ -365,80 +370,32 @@ impl Memory {
|
||||||
self.io.next_event_cycles()
|
self.io.next_event_cycles()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn begin_cpu_access(&mut self, pc: u32, instruction_set: InstructionSet) {
|
pub(crate) fn begin_cpu_access(
|
||||||
|
&mut self,
|
||||||
|
pc: u32,
|
||||||
|
instruction_set: InstructionSet,
|
||||||
|
fetch: &InstructionFetch,
|
||||||
|
) {
|
||||||
debug_assert!(self.cpu_access.is_none());
|
debug_assert!(self.cpu_access.is_none());
|
||||||
// Sample before execution, without data timing or open-bus recursion.
|
debug_assert_eq!(fetch.state, instruction_set);
|
||||||
// Missing lookahead bytes must not fail an instruction that never uses them.
|
debug_assert_eq!(fetch.address, pc.wrapping_add(2 * instruction_set.width()));
|
||||||
|
// Consume the pipeline's new fetch, never a retained opcode or a second read.
|
||||||
|
// Missing lookahead must not fail an instruction that never uses it.
|
||||||
|
let same_region = fetch.address >> 24 == pc >> 24;
|
||||||
let iwram_pc = (instruction_set == InstructionSet::Thumb && pc >> 24 == 3).then_some(pc);
|
let iwram_pc = (instruction_set == InstructionSet::Thumb && pc >> 24 == 3).then_some(pc);
|
||||||
let fetched = iwram_pc
|
let fetched = same_region
|
||||||
.filter(|pc| pc.wrapping_add(4) >> 24 == 3)
|
.then(|| fetch.instruction.as_ref().ok().map(|value| *value as u16))
|
||||||
.and_then(|pc| self.snapshot_mapped_halfword(pc + 4));
|
.flatten();
|
||||||
self.iwram_bus.begin(iwram_pc, fetched);
|
self.iwram_bus.begin(iwram_pc, fetched);
|
||||||
let prefetch = match instruction_set {
|
let prefetch = match instruction_set {
|
||||||
InstructionSet::Arm => self.snapshot_mapped_word(pc.wrapping_add(8)),
|
InstructionSet::Arm => fetch.bus_word,
|
||||||
InstructionSet::Thumb => self.snapshot_thumb_prefetch(pc),
|
InstructionSet::Thumb if !same_region => None,
|
||||||
|
InstructionSet::Thumb if pc >> 24 == 3 => self.iwram_bus.snapshot(),
|
||||||
|
InstructionSet::Thumb => fetch.bus_word,
|
||||||
};
|
};
|
||||||
self.cpu_access = Some(CpuAccess { pc, prefetch });
|
self.cpu_access = Some(CpuAccess { pc, prefetch });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot_thumb_prefetch(&self, pc: u32) -> Option<u32> {
|
|
||||||
let address = pc.wrapping_add(4);
|
|
||||||
// Region-crossing fetches need pipeline history, not a rule chosen from the old PC.
|
|
||||||
if address >> 24 != pc >> 24 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
match pc >> 24 {
|
|
||||||
// BIOS and OAM drive a full word even for a halfword instruction fetch.
|
|
||||||
0x00 | 0x07 => self.snapshot_mapped_word(address & !3),
|
|
||||||
// The IWRAM halfword fetch replaces one lane of its retained bus word.
|
|
||||||
0x03 => self.iwram_bus.snapshot(),
|
|
||||||
// These 16-bit regions repeat the fetched halfword in both word lanes.
|
|
||||||
0x02 | 0x05 | 0x06 | 0x08..=0x0d => {
|
|
||||||
Some(u32::from(self.snapshot_mapped_halfword(address)?) * 0x0001_0001)
|
|
||||||
}
|
|
||||||
// Unsupported code regions must not manufacture an open-bus snapshot.
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn snapshot_mapped_halfword(&self, address: u32) -> Option<u16> {
|
|
||||||
Some(u16::from_le_bytes([
|
|
||||||
self.read_mapped_byte(address).ok()?,
|
|
||||||
self.read_mapped_byte(address + 1).ok()?,
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Strict instruction fetch without data context, timing, or bus-history changes.
|
|
||||||
/// Thumb reads exactly two bytes, even on a region with a wider data-bus latch.
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
let mut bytes = [0; 4];
|
|
||||||
for (offset, byte) in bytes[..width].iter_mut().enumerate() {
|
|
||||||
*byte = self.read_mapped_byte(address.wrapping_add(offset as u32))?;
|
|
||||||
}
|
|
||||||
Ok(u32::from_le_bytes(bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn snapshot_mapped_word(&self, address: u32) -> Option<u32> {
|
|
||||||
let mut bytes = [0; 4];
|
|
||||||
for (offset, byte) in bytes.iter_mut().enumerate() {
|
|
||||||
*byte = self
|
|
||||||
.read_mapped_byte(address.wrapping_add(offset as u32))
|
|
||||||
.ok()?;
|
|
||||||
}
|
|
||||||
Some(u32::from_le_bytes(bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn end_cpu_access(&mut self, succeeded: bool, sequential: bool) {
|
pub(crate) fn end_cpu_access(&mut self, succeeded: bool, sequential: bool) {
|
||||||
if succeeded {
|
if succeeded {
|
||||||
// Timed and untimed CPU execution both consume a resume. Errors do not.
|
// Timed and untimed CPU execution both consume a resume. Errors do not.
|
||||||
|
|
@ -454,28 +411,27 @@ impl Memory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sample only the two target fetches of a completed refill into Thumb IWRAM.
|
/// Consume the same target samples that fill the CPU's instruction buffer.
|
||||||
/// This updates bus history, not the CPU's instruction buffer or nominal costs.
|
|
||||||
/// Incomplete or unsupported refills stay unknown without failing the branch early.
|
/// Incomplete or unsupported refills stay unknown without failing the branch early.
|
||||||
pub(crate) fn refill_cpu_bus_history(&mut self, pc: u32, instruction_set: InstructionSet) {
|
pub(crate) fn refill_cpu_bus_history(&mut self, fetches: &[InstructionFetch; 2]) {
|
||||||
self.iwram_bus.invalidate();
|
self.iwram_bus.invalidate();
|
||||||
if instruction_set == InstructionSet::Thumb
|
let [first, second] = fetches;
|
||||||
&& pc >> 24 == 3
|
debug_assert_eq!(first.state, second.state);
|
||||||
&& pc.wrapping_add(2) >> 24 == 3
|
debug_assert_eq!(
|
||||||
|
second.address,
|
||||||
|
first.address.wrapping_add(first.state.width())
|
||||||
|
);
|
||||||
|
if first.state == InstructionSet::Thumb
|
||||||
|
&& first.address >> 24 == 3
|
||||||
|
&& second.address >> 24 == 3
|
||||||
{
|
{
|
||||||
if let (Some(first), Some(second)) = (
|
if let (Ok(first_value), Ok(second_value)) = (&first.instruction, &second.instruction) {
|
||||||
self.snapshot_mapped_halfword(pc),
|
self.iwram_bus
|
||||||
self.snapshot_mapped_halfword(pc + 2),
|
.refill(first.address, [*first_value as u16, *second_value as u16]);
|
||||||
) {
|
|
||||||
self.iwram_bus.refill(pc, [first, second]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn invalidate_cpu_bus_history(&mut self) {
|
|
||||||
self.iwram_bus.invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_write_power_control(&self) -> bool {
|
fn can_write_power_control(&self) -> bool {
|
||||||
// Bare memory writes are host/debug setup. CPU code must execute in BIOS.
|
// Bare memory writes are host/debug setup. CPU code must execute in BIOS.
|
||||||
self.cpu_access
|
self.cpu_access
|
||||||
|
|
|
||||||
65
crates/gba-core/src/memory/fetch.rs
Normal file
65
crates/gba-core/src/memory/fetch.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
//! One mapped sample supplies instruction bits and the supported bus observation.
|
||||||
|
//! Sampling is side-effect-free; execution commits history under the diagnostic policy.
|
||||||
|
use super::{InstructionSet, Memory, MemoryError};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub(crate) struct InstructionFetch {
|
||||||
|
pub(super) address: u32,
|
||||||
|
pub(super) state: InstructionSet,
|
||||||
|
pub(crate) instruction: Result<u32, MemoryError>,
|
||||||
|
// None for unsupported regions, failed reads, or lane-based Thumb IWRAM history.
|
||||||
|
pub(super) bus_word: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Memory {
|
||||||
|
pub(crate) fn fetch_instruction(
|
||||||
|
&self,
|
||||||
|
address: u32,
|
||||||
|
state: InstructionSet,
|
||||||
|
) -> InstructionFetch {
|
||||||
|
let instruction = self.read_instruction(address, state);
|
||||||
|
let bus_word = instruction.as_ref().ok().copied().and_then(|value| {
|
||||||
|
if state == InstructionSet::Arm {
|
||||||
|
return Some(value);
|
||||||
|
}
|
||||||
|
match address >> 24 {
|
||||||
|
// BIOS/OAM drive the aligned word. Read only the other halfword;
|
||||||
|
// do not sample the instruction bytes a second time.
|
||||||
|
0x00 | 0x07 => {
|
||||||
|
let other = self
|
||||||
|
.read_instruction(address ^ 2, InstructionSet::Thumb)
|
||||||
|
.ok()?;
|
||||||
|
Some(if address & 2 == 0 {
|
||||||
|
value | (other << 16)
|
||||||
|
} else {
|
||||||
|
other | (value << 16)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// IWRAM updates its existing addressed lanes at execution entry.
|
||||||
|
0x03 => None,
|
||||||
|
0x02 | 0x05 | 0x06 | 0x08..=0x0d => Some(value * 0x0001_0001),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
InstructionFetch {
|
||||||
|
address,
|
||||||
|
state,
|
||||||
|
instruction,
|
||||||
|
bus_word,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Strict mapped bytes, without data context, timing, or bus-history changes.
|
||||||
|
/// An unavailable wider bus observation must not invalidate a valid instruction.
|
||||||
|
fn read_instruction(&self, address: u32, state: InstructionSet) -> Result<u32, MemoryError> {
|
||||||
|
let width = state.width() as usize;
|
||||||
|
if address & (width as u32 - 1) != 0 {
|
||||||
|
return Err(MemoryError::Unaligned(address));
|
||||||
|
}
|
||||||
|
let mut bytes = [0; 4];
|
||||||
|
for (offset, byte) in bytes[..width].iter_mut().enumerate() {
|
||||||
|
*byte = self.read_mapped_byte(address.wrapping_add(offset as u32))?;
|
||||||
|
}
|
||||||
|
Ok(u32::from_le_bytes(bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
178
crates/gba-core/src/memory/fetch_tests.rs
Normal file
178
crates/gba-core/src/memory/fetch_tests.rs
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
//! Independent fetch-observation checks. Mutation between sample and consumption
|
||||||
|
//! checks ownership of captured values, not sub-instruction hardware arbitration.
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const WORD: u32 = 0x9abc_1234;
|
||||||
|
const CHANGED: u32 = 0x5678_def0;
|
||||||
|
const UNUSED: u32 = 0x8000_0000;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mapped_fetches_separate_instruction_width_from_driven_bus_lanes() {
|
||||||
|
let mut bios = vec![0; BIOS_SIZE];
|
||||||
|
bios[..4].copy_from_slice(&WORD.to_le_bytes());
|
||||||
|
let mut memory = Memory::with_bios(WORD.to_le_bytes().to_vec(), bios).unwrap();
|
||||||
|
// Explicit expected bus words for each Thumb alignment; IWRAM needs prior lanes.
|
||||||
|
for (address, expected) in [
|
||||||
|
(0, [Some(WORD), Some(WORD)]),
|
||||||
|
(0x0200_0000, [Some(0x1234_1234), Some(0x9abc_9abc)]),
|
||||||
|
(0x0300_0000, [None, None]),
|
||||||
|
(0x0500_0000, [Some(0x1234_1234), Some(0x9abc_9abc)]),
|
||||||
|
(0x0600_0000, [Some(0x1234_1234), Some(0x9abc_9abc)]),
|
||||||
|
(0x0700_0000, [Some(WORD), Some(WORD)]),
|
||||||
|
(0x0800_0000, [Some(0x1234_1234), Some(0x9abc_9abc)]),
|
||||||
|
(0x0a00_0000, [Some(0x1234_1234), Some(0x9abc_9abc)]),
|
||||||
|
(0x0c00_0000, [Some(0x1234_1234), Some(0x9abc_9abc)]),
|
||||||
|
] {
|
||||||
|
if (0x0200_0000..0x0800_0000).contains(&address) {
|
||||||
|
memory.write32(address, WORD).unwrap();
|
||||||
|
}
|
||||||
|
let arm = memory.fetch_instruction(address, InstructionSet::Arm);
|
||||||
|
assert_eq!(arm.instruction, Ok(WORD));
|
||||||
|
assert_eq!(arm.bus_word, Some(WORD));
|
||||||
|
for (lane, expected) in expected.into_iter().enumerate() {
|
||||||
|
let thumb = memory.fetch_instruction(address + lane as u32 * 2, InstructionSet::Thumb);
|
||||||
|
assert_eq!(
|
||||||
|
thumb.instruction,
|
||||||
|
Ok(if lane == 0 { 0x1234 } else { 0x9abc })
|
||||||
|
);
|
||||||
|
assert_eq!(thumb.bus_word, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fetch_errors_remain_strict_and_narrow_rom_fetches_need_only_two_bytes() {
|
||||||
|
let memory = Memory::new(vec![0x34, 0x12]).unwrap();
|
||||||
|
let thumb = memory.fetch_instruction(ROM_START, InstructionSet::Thumb);
|
||||||
|
assert_eq!(thumb.instruction, Ok(0x1234));
|
||||||
|
assert_eq!(thumb.bus_word, Some(0x1234_1234));
|
||||||
|
for (address, state, error) in [
|
||||||
|
(
|
||||||
|
ROM_START,
|
||||||
|
InstructionSet::Arm,
|
||||||
|
MemoryError::Unmapped(ROM_START + 2),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ROM_START + 2,
|
||||||
|
InstructionSet::Thumb,
|
||||||
|
MemoryError::Unmapped(ROM_START + 2),
|
||||||
|
),
|
||||||
|
(0, InstructionSet::Thumb, MemoryError::Unmapped(0)),
|
||||||
|
(UNUSED, InstructionSet::Arm, MemoryError::Unmapped(UNUSED)),
|
||||||
|
(
|
||||||
|
ROM_START + 1,
|
||||||
|
InstructionSet::Thumb,
|
||||||
|
MemoryError::Unaligned(ROM_START + 1),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ROM_START + 2,
|
||||||
|
InstructionSet::Arm,
|
||||||
|
MemoryError::Unaligned(ROM_START + 2),
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let fetch = memory.fetch_instruction(address, state);
|
||||||
|
assert_eq!(fetch.instruction, Err(error));
|
||||||
|
assert_eq!(fetch.bus_word, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn refill_history_consumes_captured_target_halfwords_without_rereading_memory() {
|
||||||
|
for target in [0x0300_0100, 0x0300_0102] {
|
||||||
|
let mut memory = Memory::new(vec![]).unwrap();
|
||||||
|
memory.write16(target, 0x1234).unwrap();
|
||||||
|
memory.write16(target + 2, 0x9abc).unwrap();
|
||||||
|
memory.write16(target + 4, 0x5678).unwrap();
|
||||||
|
let fetches = [
|
||||||
|
memory.fetch_instruction(target, InstructionSet::Thumb),
|
||||||
|
memory.fetch_instruction(target + 2, InstructionSet::Thumb),
|
||||||
|
];
|
||||||
|
memory.write16(target, 0xffff).unwrap();
|
||||||
|
memory.write16(target + 2, 0xffff).unwrap();
|
||||||
|
memory.refill_cpu_bus_history(&fetches);
|
||||||
|
let lookahead = memory.fetch_instruction(target + 4, InstructionSet::Thumb);
|
||||||
|
memory.begin_cpu_access(target, InstructionSet::Thumb, &lookahead);
|
||||||
|
assert_eq!(
|
||||||
|
memory.read32(UNUSED).unwrap(),
|
||||||
|
if target & 2 == 0 {
|
||||||
|
0x9abc_5678
|
||||||
|
} else {
|
||||||
|
0x5678_9abc
|
||||||
|
}
|
||||||
|
);
|
||||||
|
memory.end_cpu_access(true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bios_history_commits_the_captured_bus_word_not_later_image_bytes() {
|
||||||
|
for (pc, state, fetch_address) in [
|
||||||
|
(0x100, InstructionSet::Arm, 0x108),
|
||||||
|
(0x100, InstructionSet::Thumb, 0x104),
|
||||||
|
(0x102, InstructionSet::Thumb, 0x106),
|
||||||
|
] {
|
||||||
|
let mut bios = vec![0; BIOS_SIZE];
|
||||||
|
let word_address = (fetch_address & !3) as usize;
|
||||||
|
bios[word_address..word_address + 4].copy_from_slice(&WORD.to_le_bytes());
|
||||||
|
let mut memory = Memory::with_bios(vec![], bios).unwrap();
|
||||||
|
let fetch = memory.fetch_instruction(fetch_address, state);
|
||||||
|
// Test-only image mutation: BIOS remains read-only through normal APIs.
|
||||||
|
memory.bios.as_mut().unwrap()[word_address..word_address + 4]
|
||||||
|
.copy_from_slice(&CHANGED.to_le_bytes());
|
||||||
|
memory.begin_cpu_access(pc, state, &fetch);
|
||||||
|
memory.end_cpu_access(true, false);
|
||||||
|
assert_eq!(memory.bios_prefetch, Some(WORD));
|
||||||
|
let outside = memory.fetch_instruction(0x0200_0008, InstructionSet::Arm);
|
||||||
|
memory.begin_cpu_access(0x0200_0000, InstructionSet::Arm, &outside);
|
||||||
|
assert_eq!(memory.read32(word_address as u32).unwrap(), WORD);
|
||||||
|
// Instruction reads still bypass the protected data value.
|
||||||
|
assert_eq!(
|
||||||
|
memory
|
||||||
|
.fetch_instruction(word_address as u32, InstructionSet::Arm)
|
||||||
|
.instruction,
|
||||||
|
Ok(CHANGED)
|
||||||
|
);
|
||||||
|
memory.end_cpu_access(true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sampling_has_no_timing_or_history_side_effects_and_never_uses_data_fallback() {
|
||||||
|
let mut bios = vec![0; BIOS_SIZE];
|
||||||
|
bios[8..12].copy_from_slice(&WORD.to_le_bytes());
|
||||||
|
let mut memory = Memory::with_bios(vec![], bios).unwrap();
|
||||||
|
memory.cpu_resume_nonsequential = true;
|
||||||
|
memory.begin_data_timing();
|
||||||
|
let fetch = memory.fetch_instruction(8, InstructionSet::Arm);
|
||||||
|
memory.fetch_instruction(0x0300_0000, InstructionSet::Thumb);
|
||||||
|
assert_eq!(memory.end_data_timing(), 0);
|
||||||
|
assert_eq!(memory.bios_prefetch, None);
|
||||||
|
assert!(memory.cpu_resume_nonsequential);
|
||||||
|
assert_eq!(memory.cycles(), 0);
|
||||||
|
memory.begin_cpu_access(0, InstructionSet::Arm, &fetch);
|
||||||
|
assert_eq!(memory.read32(UNUSED).unwrap(), WORD);
|
||||||
|
let missing = memory.fetch_instruction(UNUSED, InstructionSet::Arm);
|
||||||
|
assert_eq!(missing.instruction, Err(MemoryError::Unmapped(UNUSED)));
|
||||||
|
assert_eq!(missing.bus_word, None);
|
||||||
|
memory.end_cpu_access(false, false);
|
||||||
|
assert_eq!(memory.bios_prefetch, None);
|
||||||
|
assert!(memory.cpu_resume_nonsequential);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_fetch_observation_invalidates_bios_history_only_on_success() {
|
||||||
|
let mut memory = Memory::with_bios(vec![], vec![0; BIOS_SIZE]).unwrap();
|
||||||
|
memory.bios_prefetch = Some(WORD);
|
||||||
|
for state in [InstructionSet::Arm, InstructionSet::Thumb] {
|
||||||
|
let pc = BIOS_SIZE as u32 - state.width();
|
||||||
|
let fetch = memory.fetch_instruction(pc + 2 * state.width(), state);
|
||||||
|
assert!(fetch.instruction.is_err());
|
||||||
|
memory.begin_cpu_access(pc, state, &fetch);
|
||||||
|
memory.end_cpu_access(false, false);
|
||||||
|
assert_eq!(memory.bios_prefetch, Some(WORD));
|
||||||
|
}
|
||||||
|
let fetch = memory.fetch_instruction(BIOS_SIZE as u32, InstructionSet::Thumb);
|
||||||
|
memory.begin_cpu_access(BIOS_SIZE as u32 - 4, InstructionSet::Thumb, &fetch);
|
||||||
|
memory.end_cpu_access(true, false);
|
||||||
|
assert_eq!(memory.bios_prefetch, None);
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,9 @@ The repository is a Cargo workspace with three crates:
|
||||||
| `crates/gba-core/src/cpu/tests.rs` | Condition truth tables and immediate arithmetic tests |
|
| `crates/gba-core/src/cpu/tests.rs` | Condition truth tables and immediate arithmetic tests |
|
||||||
| `crates/gba-core/src/cpu/instruction_tests.rs` | Register operations, transfers, branches, and edge cases |
|
| `crates/gba-core/src/cpu/instruction_tests.rs` | Register operations, transfers, branches, and edge cases |
|
||||||
| `crates/gba-core/src/memory.rs` | Memory mapping, I/O routing, write validation, and device clock |
|
| `crates/gba-core/src/memory.rs` | Memory mapping, I/O routing, write validation, and device clock |
|
||||||
|
| `crates/gba-core/src/memory/fetch.rs` | Strict instruction samples with captured region-dependent bus observations |
|
||||||
|
| `crates/gba-core/src/memory/fetch_tests.rs` | Fetch widths, bus lanes, captured refill/BIOS values, strict errors, and side-effect isolation |
|
||||||
|
| `crates/gba-core/src/cpu/fetch_history_tests.rs` | Shared sample consumption, no execution-entry rereads, and failed-step retry |
|
||||||
| `crates/gba-core/src/io.rs` | I/O registers, timers, HALT/STOP wake-up, next-event bounds, and IRQ latches |
|
| `crates/gba-core/src/io.rs` | I/O registers, timers, HALT/STOP wake-up, next-event bounds, and IRQ latches |
|
||||||
| `crates/gba-core/src/display.rs` | Display clock, scanline status, comparison edges, and display IRQ events |
|
| `crates/gba-core/src/display.rs` | Display clock, scanline status, comparison edges, and display IRQ events |
|
||||||
| `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 |
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ Normal word rotation, odd-halfword behavior, sign extension, and load timing sti
|
||||||
|
|
||||||
The current interpreter implements bounded ARM and Thumb BIOS prefetch snapshots:
|
The current interpreter implements bounded ARM and Thumb BIOS prefetch snapshots:
|
||||||
|
|
||||||
- Before an ARM BIOS instruction executes, sample its mapped PC+8 word without charging another data access.
|
- Before an ARM BIOS instruction executes, use its pipeline's new mapped PC+8 fetch without charging another data access.
|
||||||
- Before a Thumb BIOS instruction executes, sample the full mapped word at `(PC+4) & ~3`.
|
- Before a Thumb BIOS instruction executes, sample the full mapped word at `(PC+4) & ~3`.
|
||||||
BIOS drives a 32-bit word even for a halfword fetch; both distinct lanes are retained.
|
BIOS drives a 32-bit word even for a halfword fetch; both distinct lanes are retained.
|
||||||
- After successful execution, retain that snapshot for later protected reads. Skipped conditional instructions count as successful execution.
|
- After successful execution, retain that snapshot for later protected reads. Skipped conditional instructions count as successful execution.
|
||||||
|
|
@ -134,7 +134,8 @@ 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/Thumb instruction buffer.
|
The BIOS latch remains separate from the CPU's ARM/Thumb instruction buffer.
|
||||||
|
Both now consume a shared fetch sample; BIOS history no longer rereads lookahead memory at execution entry.
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,10 @@ Do not call it for ordinary CPU/DMA stores: that would hide self-modifying-code
|
||||||
|
|
||||||
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.
|
||||||
Fetch-driven bus history, exact refill costs, per-access device updates, and DMA arbitration remain incomplete.
|
Instruction buffering and supported bus history now consume the same mapped fetch samples.
|
||||||
See the [ARM evidence and diagnostic policy](../research/arm-instruction-buffer.md) and [Thumb extension](../research/thumb-instruction-buffer.md).
|
Complete fetch-driven bus history, exact refill costs, per-access device updates, and DMA arbitration remain incomplete.
|
||||||
|
See the [ARM evidence and diagnostic policy](../research/arm-instruction-buffer.md), [Thumb extension](../research/thumb-instruction-buffer.md),
|
||||||
|
and [shared fetch samples](../research/shared-fetch-samples.md).
|
||||||
|
|
||||||
## ARM unused-memory data reads
|
## ARM unused-memory data reads
|
||||||
|
|
||||||
|
|
@ -118,7 +120,8 @@ During ARM execution, data reads from `0x00004000..0x01ffffff` and `0x10000000..
|
||||||
Open bus means the bus retains previously driven data. GBATEK identifies this ARM value as the instruction word at PC+8.
|
Open bus means the bus retains previously driven data. GBATEK identifies this ARM value as the instruction word at PC+8.
|
||||||
These high addresses do not mirror BIOS, RAM, or cartridge addresses.
|
These high addresses do not mirror BIOS, RAM, or cartridge addresses.
|
||||||
|
|
||||||
The interpreter snapshots mapped bytes at PC+8 before executing each ARM instruction.
|
The interpreter samples mapped bytes at PC+8 before executing each ARM instruction.
|
||||||
|
That one fetch supplies both the new instruction-buffer entry and the unused-memory snapshot.
|
||||||
All unused-memory reads within that instruction use the same snapshot.
|
All unused-memory reads within that instruction use the same snapshot.
|
||||||
Byte and halfword reads select their addressed lanes; the CPU applies normal rotation and sign extension.
|
Byte and halfword reads select their addressed lanes; the CPU applies normal rotation and sign extension.
|
||||||
Base writeback, register aliases, block loads, PC loads, and flags retain their existing instruction semantics.
|
Base writeback, register aliases, block loads, PC loads, and flags retain their existing instruction semantics.
|
||||||
|
|
@ -162,6 +165,8 @@ For BIOS/OAM, aligned instructions expose halfwords at P+4 and P+6.
|
||||||
Instructions at two-byte-only alignment expose halfwords at P+2 and P+4.
|
Instructions at two-byte-only alignment expose halfwords at P+2 and P+4.
|
||||||
The two lanes can differ. A universal repeated-halfword rule would be incorrect.
|
The two lanes can differ. A universal repeated-halfword rule would be incorrect.
|
||||||
|
|
||||||
|
The pipeline's new fetch supplies the bus snapshot without rereading its instruction bytes.
|
||||||
|
BIOS/OAM samples include the other halfword of the aligned bus word, but retain only the selected halfword as an instruction.
|
||||||
Only the required mapped bytes are sampled. A 16-bit region needs two lookahead bytes, not four.
|
Only the required mapped bytes are sampled. A 16-bit region needs two lookahead bytes, not four.
|
||||||
Normal memory mirrors apply to these bytes, including physical RAM/video-memory wrap within a mapped region.
|
Normal memory mirrors apply to these bytes, including physical RAM/video-memory wrap within a mapped region.
|
||||||
If P+4 crosses a 16 MiB address-region boundary, the snapshot remains unknown.
|
If P+4 crosses a 16 MiB address-region boundary, the snapshot remains unknown.
|
||||||
|
|
@ -178,8 +183,8 @@ IWRAM needs prior bus lanes, including IWRAM data-read/write changes and possibl
|
||||||
Do not substitute P+2 for that history: it is only the usual case, not a general rule.
|
Do not substitute P+2 for that history: it is only the usual case, not a general rule.
|
||||||
The original Nintendo DS also differs from GBA-family IWRAM behavior; this core targets GBA.
|
The original Nintendo DS also differs from GBA-family IWRAM behavior; this core targets GBA.
|
||||||
|
|
||||||
This remains a bounded snapshot implementation, not a pipeline or a complete bus-history model.
|
Bus history remains bounded despite sharing samples with the persistent instruction buffer.
|
||||||
Refill timing, self-modifying code, DMA-to-CPU transitions, cross-state IWRAM history, unused/write-only I/O, and disabled RAM remain incomplete.
|
Refill timing, general DMA-to-CPU transitions, cross-state IWRAM history, unused/write-only I/O, and disabled RAM remain incomplete.
|
||||||
Original regressions validate the documented formulas. No physical-hardware or independent public Thumb open-bus pass is claimed.
|
Original regressions validate the documented formulas. No physical-hardware or independent public Thumb open-bus pass is claimed.
|
||||||
|
|
||||||
### Sequential IWRAM history
|
### Sequential IWRAM history
|
||||||
|
|
@ -221,8 +226,8 @@ Saved Thumb state, not the target's low bit alone, controls exception-return sam
|
||||||
Stack and block-load returns finish their data accesses before the target samples.
|
Stack and block-load returns finish their data accesses before the target samples.
|
||||||
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.
|
Target samples use strict mapped reads and add no nominal data or device cycles.
|
||||||
They update bus history only; a separate CPU buffer retains the target instructions for execution.
|
The same captured target pair fills the CPU buffer and updates supported IWRAM history; no second target read occurs.
|
||||||
Host inspection does not change captured lanes. A failed target fetch remains a later instruction diagnostic.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -152,11 +152,12 @@ 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/Thumb PC offsets.
|
- [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`, `ReloadPipeline32`, and `ReloadPipeline16` helpers above, for retained instructions, 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.
|
||||||
|
|
||||||
- [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.
|
- [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).
|
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.
|
Instruction retention and supported bus snapshots now use [shared fetch samples](research/shared-fetch-samples.md).
|
||||||
|
That refactor reuses the pinned NanoBoyAdvance loop/refill and ares local-lane evidence above.
|
||||||
|
Complete bus history and per-access timing remain incomplete; no new hardware conformance result is claimed.
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,8 @@ The existing destination-based refill summaries and one-shot DMA resume timing r
|
||||||
The current instruction address still selects nominal code cost rather than a separately scheduled fetch address.
|
The current instruction address still selects nominal code cost rather than a separately scheduled fetch address.
|
||||||
|
|
||||||
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 [shared fetch sample](shared-fetch-samples.md) supplies the instruction-buffer entry and supported bus snapshots without duplicate reads.
|
||||||
|
No unified per-access bus model exists yet.
|
||||||
The target samples do not establish complete ARM IWRAM or BIOS bus history.
|
The target samples do not establish complete ARM IWRAM or BIOS bus history.
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@ No physical-hardware measurement or external emulator differential run was perfo
|
||||||
## Bounded implementation
|
## Bounded implementation
|
||||||
|
|
||||||
`memory/iwram_bus.rs` records lane values and a known-bit mask during consecutive Thumb instructions in IWRAM.
|
`memory/iwram_bus.rs` records lane values and a known-bit mask during consecutive Thumb instructions in IWRAM.
|
||||||
Before execution, the mapped PC+4 halfword updates its addressed lanes.
|
Before execution, the pipeline's new mapped PC+4 fetch updates its addressed lanes.
|
||||||
|
The bus-history consumer does not reread memory or reuse the current/decode instruction.
|
||||||
During execution, successful IWRAM bus reads and writes update the staged lanes at their actual aligned addresses and widths.
|
During execution, successful IWRAM bus reads and writes update the staged lanes at their actual aligned addresses and widths.
|
||||||
The latch sees raw bus data before CPU load rotation and sign extension.
|
The latch sees raw bus data before CPU load rotation and sign extension.
|
||||||
Other-region accesses and host inspection do not change it.
|
Other-region accesses and host inspection do not change it.
|
||||||
|
|
@ -65,7 +66,8 @@ For Thumb destinations, those addresses are T and T+2.
|
||||||
[NanoBoyAdvance's refill helpers](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/arm/arm7tdmi.hh) use that order.
|
[NanoBoyAdvance's refill helpers](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/arm/arm7tdmi.hh) use that order.
|
||||||
Its execution loop then fetches T+4 before executing the first Thumb instruction.
|
Its execution loop then fetches T+4 before executing the first Thumb instruction.
|
||||||
|
|
||||||
After a successful refill instruction, we sample the two mapped destination halfwords if both remain in Thumb IWRAM.
|
After a successful refill instruction, the pipeline samples the two mapped destination instructions.
|
||||||
|
The same captured halfwords update bus history if both remain in Thumb IWRAM.
|
||||||
The samples replace the old lane history and establish the next expected PC as T.
|
The samples replace the old lane history and establish the next expected PC as T.
|
||||||
The next instruction's normal PC+4 sample updates one lane while the other retains the captured T+2 halfword.
|
The next instruction's normal PC+4 sample updates one lane while the other retains the captured T+2 halfword.
|
||||||
The samples occur after data accesses and status restoration, so loaded PC values and saved Thumb state select the destination.
|
The samples occur after data accesses and status restoration, so loaded PC values and saved Thumb state select the destination.
|
||||||
|
|
@ -76,7 +78,7 @@ Failed instructions retain the previous committed history. An unsupported or inc
|
||||||
Unmapped instruction targets still fail on the following fetch.
|
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 captured values now supply both bus history and instruction-buffer entries, without extra emulated cycles.
|
||||||
ARM and Thumb execution now use a separate instruction buffer. Cold buffer filling does not seed this bus history.
|
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.
|
||||||
|
|
|
||||||
78
docs/research/shared-fetch-samples.md
Normal file
78
docs/research/shared-fetch-samples.md
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Shared instruction-fetch samples
|
||||||
|
|
||||||
|
## Finding and evidence
|
||||||
|
|
||||||
|
Persistent ARM/Thumb instruction buffering exposed duplicate memory reads in the interpreter.
|
||||||
|
The pipeline sampled the new lookahead instruction, then `Memory::begin_cpu_access` reread those bytes for bus history.
|
||||||
|
Thumb IWRAM refills also read their target pair once for history and again for instruction buffering.
|
||||||
|
With instruction-boundary scheduling, those reads normally saw identical bytes. They still represented the same fetch twice.
|
||||||
|
|
||||||
|
This refactor gives instruction buffering and supported bus-history updates a common captured sample.
|
||||||
|
It does not introduce new hardware formulas or change nominal cycle costs.
|
||||||
|
|
||||||
|
Previously reviewed evidence supports this separation:
|
||||||
|
|
||||||
|
- [NanoBoyAdvance at 55b5cf0a](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/arm/arm7tdmi.hh): `Run` fetches the next instruction before execution; refill helpers fetch the target pair.
|
||||||
|
- [ares at 6f6786e0](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/memory.cpp): IWRAM updates local addressed lanes; EWRAM halfword reads drive duplicated halves.
|
||||||
|
- [Thumb BIOS research](thumb-bios-prefetch.md) and [Thumb open-bus rules](../hardware/cpu.md#thumb-unused-memory-data-reads): BIOS/OAM bus observations use an aligned word, not a universally duplicated halfword.
|
||||||
|
|
||||||
|
These sources were reviewed, not copied into the emulator.
|
||||||
|
No physical-hardware measurements or external-emulator differential runs were performed.
|
||||||
|
|
||||||
|
## Sample and consumers
|
||||||
|
|
||||||
|
`memory/fetch.rs` returns an `InstructionFetch` with:
|
||||||
|
|
||||||
|
- Fetch address and instruction state.
|
||||||
|
- Strict instruction result or deferred mapped-read error.
|
||||||
|
- Supported bus-word observation, when it does not require retained IWRAM lanes.
|
||||||
|
|
||||||
|
ARM samples use the fetched word for both instruction bits and bus observation.
|
||||||
|
Thumb samples retain only their selected halfword as instruction bits.
|
||||||
|
Supported 16-bit regions duplicate that halfword for bus observation.
|
||||||
|
BIOS/OAM samples read the adjacent halfword too, without rereading the instruction bytes.
|
||||||
|
Thumb IWRAM supplies the fetched halfword to the existing lane-retaining transaction instead of creating a full bus word.
|
||||||
|
|
||||||
|
The CPU passes only its newly sampled P+8 or P+4 observation to `begin_cpu_access`.
|
||||||
|
It does not replay the bus observations of retained current/decode instructions.
|
||||||
|
The consumer performs no mapped memory read.
|
||||||
|
After successful control flow, the same target samples fill the instruction buffer and supported Thumb IWRAM refill history.
|
||||||
|
Machine IRQ entry uses this refill path; the ARM target still invalidates Thumb IWRAM continuation history.
|
||||||
|
|
||||||
|
Instruction buffers and BIOS/IWRAM latches remain separate retained state.
|
||||||
|
Sampling alone changes no CPU access context, latch, device clock, DMA resume marker, or data-timing trace.
|
||||||
|
Instruction errors discard staged history and speculative buffer advances.
|
||||||
|
A retry takes a new lookahead sample while retaining the previously committed instruction pair.
|
||||||
|
Unavailable lookahead remains diagnostic only when consumed by an instruction or a data read that needs it.
|
||||||
|
Strict instruction reads never use protected BIOS or unused-memory data fallback.
|
||||||
|
|
||||||
|
## Deliberately unchanged limits
|
||||||
|
|
||||||
|
- Cold pipeline filling does not establish previously unknown IWRAM history.
|
||||||
|
- Thumb 16 MiB region crossings remain unsupported for bus snapshots.
|
||||||
|
- ARM-wide local IWRAM history and cross-state latch ownership remain incomplete.
|
||||||
|
- BIOS history commits after successful BIOS instruction execution; target refills do not establish additional BIOS history.
|
||||||
|
- All unused-memory data reads within an instruction still use its entry snapshot.
|
||||||
|
- DMA remains scheduled between whole instructions. Sampling does not add an interleaving point.
|
||||||
|
- Current-instruction and destination-based nominal timing remain unchanged.
|
||||||
|
- Per-access device updates, Game Pak prefetch, and exact refill/arbitration timing remain incomplete.
|
||||||
|
|
||||||
|
## Original tests
|
||||||
|
|
||||||
|
CPU tests split the existing internal fetch/execute helpers and mutate lookahead bytes between them.
|
||||||
|
ARM, narrow Thumb, wide Thumb, and IWRAM tests then require the originally captured values.
|
||||||
|
Those consumption tests failed with the previous duplicate-read path.
|
||||||
|
The split is a test-only ownership check, not a hardware DMA experiment.
|
||||||
|
|
||||||
|
Memory tests use explicit expected lane values across mapped regions and both Thumb alignments.
|
||||||
|
They also check target-pair consumption after mutation, BIOS snapshot retention, strict errors, short ROMs, and data-context isolation.
|
||||||
|
Tests verify that sampling adds no timing or history effects and that failed execution preserves committed history.
|
||||||
|
Existing instruction-buffer, bus-history, exception, DMA, and nominal timing tests remain regression coverage.
|
||||||
|
|
||||||
|
## 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 Thumb-buffer reports exactly, including nominal cycle counts.
|
||||||
|
Debug and release reports also match for the same fixture paths.
|
||||||
|
These checks validate the refactor against existing behavior, not complete hardware bus or timing conformance.
|
||||||
|
|
@ -40,7 +40,8 @@ Do not treat agreement between all emulators, or exact hardware conformance, as
|
||||||
|
|
||||||
## Implementation scope
|
## Implementation scope
|
||||||
|
|
||||||
`Memory::begin_cpu_access` samples before execution, using the incoming instruction state and strict mapped bytes.
|
`Memory::begin_cpu_access` now consumes the pipeline's new fetch sample, using the incoming instruction state.
|
||||||
|
The [shared sampler](shared-fetch-samples.md) captures strict mapped bytes before execution.
|
||||||
`end_cpu_access` commits BIOS history only after success. Failed instructions preserve previous history.
|
`end_cpu_access` commits BIOS history only after success. Failed instructions preserve previous history.
|
||||||
Thumb execution outside BIOS neither initializes nor replaces BIOS history.
|
Thumb execution outside BIOS neither initializes nor replaces BIOS history.
|
||||||
Host reads remain raw inspection. CPU data loads outside BIOS select lanes from retained history as before.
|
Host reads remain raw inspection. CPU data loads outside BIOS select lanes from retained history as before.
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@ ARM-to-Thumb BX and saved-state exception returns therefore capture T and T+2, n
|
||||||
Thumb-to-ARM transitions and SWI/IRQ vectors capture ARM pairs.
|
Thumb-to-ARM transitions and SWI/IRQ vectors capture ARM pairs.
|
||||||
|
|
||||||
`Memory::fetch_instruction` uses strict mapped reads with the selected instruction width.
|
`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.
|
Its instruction result needs only two bytes in Thumb state.
|
||||||
|
The later [shared fetch sampler](shared-fetch-samples.md) also captures the adjacent halfword for BIOS/OAM bus observations.
|
||||||
Memory mirrors apply. Unmapped and misaligned instruction reads retain diagnostics rather than using data open bus.
|
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.
|
Lookahead errors remain deferred until their slot executes; a branch can discard them.
|
||||||
|
|
||||||
|
|
@ -51,12 +52,12 @@ CPU clones own independent buffers; full CPU equality includes them.
|
||||||
Instruction retention and data-bus snapshots remain separate models.
|
Instruction retention and data-bus snapshots remain separate models.
|
||||||
Cold instruction-buffer filling does not make unknown IWRAM bus lanes known.
|
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.
|
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.
|
Refill bus history and instruction buffering now consume the same captured target pair without a second mapped read.
|
||||||
|
|
||||||
Buffer samples add no nominal cycles or data accesses.
|
Buffer samples add no nominal cycles or data accesses.
|
||||||
Timed and untimed execution use the same buffer logic.
|
Timed and untimed execution use the same buffer logic.
|
||||||
Destination-based refill summaries and one-shot non-sequential CPU resume costs remain unchanged.
|
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.
|
Complete 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.
|
Instruction-state-changing compare quirks also remain hardware-unverified.
|
||||||
|
|
||||||
## Original regression coverage
|
## Original regression coverage
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ Update this file when a feature lands or a limit is removed.
|
||||||
- Original ARM graphics demo that uses DMA, BIOS VBlank waiting, and a ROM-side IRQ callback.
|
- 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/Thumb 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.
|
||||||
|
- Shared mapped fetch samples for instruction retention, supported open-bus values, BIOS snapshots, and Thumb IWRAM refill history.
|
||||||
- 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,7 +142,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/Thumb instruction buffering retains sequential instructions and refill target pairs, independently of these bus snapshots.
|
ARM/Thumb instruction buffering retains sequential instructions and refill target pairs.
|
||||||
|
Instruction buffering and supported bus history now consume the same mapped fetch samples, without sharing retained storage.
|
||||||
Complete ARM bus history, exact region-crossing bus behavior, general DMA handoff, and unused/write-only I/O open bus remain incomplete.
|
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).
|
||||||
|
|
@ -153,7 +155,7 @@ Writes to cartridge addresses return errors instead of modeling cartridge hardwa
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
1. Connect ARM/Thumb instruction fetches to bus history with independent per-access tests.
|
1. Extend the shared fetch-sample model to verified cross-state bus history and region crossings.
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,10 @@ The display-runner error test allows an already buffered branch to complete befo
|
||||||
Thumb instruction-buffer tests verify retained P+2/P+4 halfwords, BL prefix/suffix behavior, and state-tagged target pairs.
|
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.
|
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.
|
Bus-history repair tests explicitly invalidate buffered code or repair a data address; failure checks retain full CPU equality.
|
||||||
|
Shared fetch-sample tests mutate memory between internal sampling and consumption to reject duplicate reads.
|
||||||
|
They cover ARM words, Thumb narrow/wide buses, IWRAM lanes, target pairs, BIOS retention, and retry after failure.
|
||||||
|
This test-only split is not a DMA scheduling feature. Separate tests verify strict fetch errors and no timing/history side effects.
|
||||||
|
See [shared fetch samples](research/shared-fetch-samples.md) for the unchanged hardware limits.
|
||||||
See [ARM instruction buffering](research/arm-instruction-buffer.md) and its [Thumb extension](research/thumb-instruction-buffer.md) for remaining limits.
|
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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue