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:
0xrsydn 2026-10-06 03:12:54 +07:00
commit 16286e0cbd
11 changed files with 437 additions and 4 deletions

View file

@ -288,6 +288,24 @@ The writable mask is `0x5fff`; the Game Pak type flag reads as GBA, and the uppe
A CPU write to WAITCNT does not retroactively change that instruction's code-access cost.
The new settings apply to subsequent code accesses.
### Nominal CPU resume after DMA
A successful DMA unit marks the next CPU instruction's nominal code access non-sequential.
This applies even when DMA only accesses RAM. Consecutive units retain one pending resume, not multiple penalties.
A non-refill instruction uses its normal width and current WAITCNT with N instead of S.
ARM word accesses retain an S cost for their second halfword.
Stores and ROM boundaries that already use N do not receive an additional cost.
Successful timed or untimed CPU execution consumes pending resume, including skipped conditions and internal-memory instructions.
Successful machine IRQ entry also consumes it. Failed CPU/DMA steps preserve it.
Host access, clock-only advancement, and HALT/STOP idle do not consume it.
A failed DMA unit cannot create pending resume.
DMA WAITCNT writes apply before resume; CPU WAITCNT writes still use the previous code-access settings.
Branch and exception refill summaries retain their existing destination-based `1N+2S` costs, without an added resume access.
The old-PC fetch is not modeled separately. This remains nominal timing, not a per-access pipeline.
See [the evidence and limitations](../research/dma-resume-timing.md).
Important timing limits:
- Code S/N counts follow instruction summaries, not a simulated fetch pipeline.

View file

@ -47,7 +47,10 @@ CPU delivery waits until no DMA is ready. IRQ entry does not acknowledge IF.
Timing uses independent source and destination bus costs, including WAITCNT and 128 KiB ROM boundaries.
The first unit uses non-sequential accesses plus two internal cycles. Later units use sequential accesses.
Each step reports zero code cycles. Overlapping transfers read each unit after the preceding write; they are not bulk host copies.
Startup scheduling delays, channel-resumption costs, CPU fetch-state effects, and display-bus contention remain unmodeled.
A successful unit breaks the next nominal CPU code-access sequence, even if DMA accessed only RAM.
The resumed non-refill instruction uses N code timing once; later instructions return to their normal summaries.
See [CPU resume timing](cpu.md#nominal-cpu-resume-after-dma) for failure, idle, and refill rules.
Startup scheduling delays, channel-resumption costs, a full CPU fetch pipeline, and display-bus contention remain unmodeled.
Special timing modes (sound FIFO/video capture), Game Pak DRQ, and prohibited source mode 3 return `DmaError::UnsupportedControl`.
DMA reads below work RAM reuse known channel data as described below. They never expose BIOS bytes.