feat(timing): break CPU sequential access after DMA
Retain one pending non-sequential resume after successful DMA. Apply it to the next nominal non-refill code access and consume it only on successful CPU or IRQ progress. Preserve failed-step atomicity, host and idle isolation, existing refill costs, and WAITCNT ordering. Add 13 original regressions for ARM/Thumb widths, ROM wait settings, sources, preemption, retries, mixed APIs, idle states, and interrupt timing. Record pinned hardware-test source and emulator evidence without claiming physical-hardware or full-pipeline conformance. Validated on Darwin arm64: 952 workspace and 883 core/demo tests per debug/release profile, 39 Python tests, native ROM windows, ten window smoke modes, fmt, clippy, and rustdoc. Public ARM/Thumb/memory/BIOS reports remain unchanged.
This commit is contained in:
parent
663b25e5d1
commit
16286e0cbd
11 changed files with 437 additions and 4 deletions
|
|
@ -81,6 +81,8 @@ pub struct Memory {
|
|||
io: Io,
|
||||
cycles: u64,
|
||||
data_timing: Cell<Option<DataTiming>>,
|
||||
// A completed DMA unit breaks the CPU's next nominal code-access sequence.
|
||||
cpu_resume_nonsequential: bool,
|
||||
cpu_access: Option<CpuAccess>,
|
||||
bios_prefetch: Option<u32>,
|
||||
iwram_bus: IwramBus,
|
||||
|
|
@ -104,6 +106,7 @@ impl Memory {
|
|||
io: Io::default(),
|
||||
cycles: 0,
|
||||
data_timing: Cell::new(None),
|
||||
cpu_resume_nonsequential: false,
|
||||
cpu_access: None,
|
||||
bios_prefetch: None,
|
||||
iwram_bus: IwramBus::default(),
|
||||
|
|
@ -141,6 +144,8 @@ impl Memory {
|
|||
/// Finish the instruction/IRQ entry that started while running. A STOP store
|
||||
/// still pays its full nominal cost before later steps freeze the system.
|
||||
pub(crate) fn complete_cpu_step(&mut self, cycles: u32) {
|
||||
// IRQ entry consumes the resume too, without executing an instruction.
|
||||
self.cpu_resume_nonsequential = false;
|
||||
self.advance_running_cycles(cycles);
|
||||
}
|
||||
|
||||
|
|
@ -341,6 +346,7 @@ impl Memory {
|
|||
// queue a second block while the current block is still transferring.
|
||||
self.advance_cycles(timing.total());
|
||||
self.io.complete_dma_unit(channel, data_latch);
|
||||
self.cpu_resume_nonsequential = true;
|
||||
Ok(Some((channel, timing)))
|
||||
}
|
||||
|
||||
|
|
@ -413,6 +419,10 @@ impl Memory {
|
|||
}
|
||||
|
||||
pub(crate) fn end_cpu_access(&mut self, succeeded: bool, sequential: bool) {
|
||||
if succeeded {
|
||||
// Timed and untimed CPU execution both consume a resume. Errors do not.
|
||||
self.cpu_resume_nonsequential = false;
|
||||
}
|
||||
self.iwram_bus.finish(succeeded, sequential);
|
||||
if let Some(access) = self.cpu_access.take() {
|
||||
if succeeded && access.pc < BIOS_SIZE as u32 {
|
||||
|
|
@ -486,6 +496,16 @@ impl Memory {
|
|||
self.io.waitcnt()
|
||||
}
|
||||
|
||||
/// Resolve the next nominal code access without consuming pending DMA history.
|
||||
/// Successful instruction/IRQ completion consumes it; host reads and idle do not.
|
||||
pub(crate) fn cpu_code_kind(&self, kind: AccessKind) -> AccessKind {
|
||||
if self.cpu_resume_nonsequential {
|
||||
AccessKind::NonSequential
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn begin_data_timing(&self) {
|
||||
debug_assert!(self.data_timing.get().is_none());
|
||||
self.data_timing.set(Some(DataTiming::default()));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue