100 lines
3.9 KiB
Rust
100 lines
3.9 KiB
Rust
//! ARM/Thumb instruction buffering and the next CPU fetch access kind.
|
|
use super::{Cpu, CpuError, InstructionSet};
|
|
use crate::{
|
|
memory::{InstructionFetch, Memory, MemoryError},
|
|
timing::AccessKind,
|
|
};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub(super) struct Pipeline {
|
|
pc: u32,
|
|
instruction_set: InstructionSet,
|
|
// Deferred strict-fetch results for the next execute and decode positions.
|
|
// Thumb halfwords are zero-extended; their upper bits are not another opcode.
|
|
instructions: [Result<u32, MemoryError>; 2],
|
|
pub(super) next_access: AccessKind,
|
|
}
|
|
|
|
pub(super) struct Fetched {
|
|
pub instruction: u32,
|
|
pub continuation: Pipeline,
|
|
pub lookahead: InstructionFetch,
|
|
pub fill: Option<[InstructionFetch; 2]>,
|
|
}
|
|
|
|
impl Cpu {
|
|
/// Discard buffered instructions after debugger code edits or rebinding memory.
|
|
/// Ordinary CPU/DMA stores and host inspection must not call this automatically.
|
|
/// The next step uses nominal S startup with no separate fill charge.
|
|
/// This also discards the prior CPU fetch kind, but not Memory's DMA override.
|
|
pub fn invalidate_pipeline(&mut self) {
|
|
self.pipeline = None;
|
|
}
|
|
|
|
/// Cold/debugger entry keeps the existing nominal S startup policy.
|
|
/// A mismatched PC/state cannot reuse another sequence's access kind.
|
|
pub(super) fn next_fetch_kind(&self) -> AccessKind {
|
|
self.pipeline
|
|
.as_ref()
|
|
.filter(|pipe| pipe.pc == self.pc() && pipe.instruction_set == self.instruction_set)
|
|
.map_or(AccessKind::Sequential, |pipe| pipe.next_access)
|
|
}
|
|
|
|
pub(super) fn fetch(&self, memory: &Memory) -> Result<Fetched, CpuError> {
|
|
let pc = self.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, decode, fill) = if let Some(pipe) = retained {
|
|
(
|
|
pipe.instructions[0].clone()?,
|
|
pipe.instructions[1].clone(),
|
|
None,
|
|
)
|
|
} else {
|
|
let current = memory.fetch_instruction(pc, instruction_set);
|
|
let instruction = current.instruction.clone()?;
|
|
let next = memory.fetch_instruction(pc.wrapping_add(width), instruction_set);
|
|
(instruction, next.instruction.clone(), Some([current, next]))
|
|
};
|
|
let fetched = memory.fetch_instruction(pc.wrapping_add(2 * width), instruction_set);
|
|
Ok(Fetched {
|
|
instruction,
|
|
continuation: Pipeline {
|
|
pc: pc.wrapping_add(width),
|
|
instruction_set,
|
|
instructions: [decode, fetched.instruction.clone()],
|
|
next_access: AccessKind::Sequential, // Set from execution only on success.
|
|
},
|
|
lookahead: fetched,
|
|
fill,
|
|
})
|
|
}
|
|
|
|
/// Capture the target pair using the resulting state, without early errors.
|
|
/// Explicit exception entry without Memory leaves a cold buffer instead.
|
|
pub(crate) fn refill_pipeline(&mut self, memory: &mut Memory) {
|
|
let pc = self.pc();
|
|
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),
|
|
];
|
|
for (sample, kind) in fetches
|
|
.iter()
|
|
.zip([AccessKind::NonSequential, AccessKind::Sequential])
|
|
{
|
|
memory.record_code_access(sample.address(), instruction_set.access_width(), kind);
|
|
}
|
|
memory.refill_cpu_bus_history(&fetches);
|
|
self.pipeline = Some(Pipeline {
|
|
pc,
|
|
instruction_set,
|
|
instructions: fetches.map(|fetch| fetch.instruction),
|
|
next_access: AccessKind::Sequential,
|
|
});
|
|
}
|
|
}
|