fix(timers): apply machine accesses at bus completion

This commit is contained in:
0xrsydn 2026-10-06 21:44:24 +07:00
commit bfecc87e84
20 changed files with 765 additions and 137 deletions

View file

@ -48,6 +48,8 @@ The repository is a Cargo workspace with three crates:
| `crates/gba-core/src/cpu/fetch_history_tests.rs` | Shared sample consumption, no execution-entry rereads, and failed-step retry |
| `crates/gba-core/src/cpu/fetch_boundary_tests.rs` | Actual fetch-region snapshots, split refills, IWRAM lane requirements, ROM-window boundaries, and nominal timing |
| `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/timer_step.rs` | Staged timer bank and timer IF bits at ordered CPU/DMA bus phases |
| `crates/gba-core/src/memory/timer_step_tests.rs` | Timer access phases, coherent lanes, cascades, IF ordering, rollback, DMA, idle, and clock ownership |
| `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/machine.rs` | Timed CPU/DMA/device stepping and IRQ delivery |
@ -69,7 +71,7 @@ The repository is a Cargo workspace with three crates:
| `crates/gba-demos/src/timer_demo.rs` | Original timer-configuration program and IRQ handler |
| `crates/gba-demos/src/prefetch_probe.rs` | Original bounded ARM read/control probes and pinned published cancellation observations |
| `crates/gba-demos/examples/prefetch_cancellation.rs` | CSV comparison of published, instruction-boundary, and unadjusted timer intervals; fails on mismatches |
| `crates/gba-core/tests/prefetch_cancellation.rs` | Published read comparisons, cancellation phase, disabled-prefetch controls, and the known timer sampling gap |
| `crates/gba-core/tests/prefetch_cancellation.rs` | Published read comparisons, cancellation phase, disabled-prefetch controls, and unadjusted timer samples |
| `Cargo.toml` | Workspace members and shared package settings |
| `crates/gba-core/src/lib.rs` | Core modules |
| `crates/gba-demos/src/lib.rs` | Demo modules, original instruction/exception bytes, and raw input-test ROM |

View file

@ -10,9 +10,9 @@ direnv exec . cargo run --locked -p gba-demos --example prefetch_cancellation >
```
This headless original ARM probe compares published read-cancellation observations with two separate emulator measurements.
Instruction-boundary totals match, but actual timer samples currently fail. The example therefore exits with status 1.
Do not treat the matching totals as a hardware-timing pass.
See [the protocol, source hashes, and timer sampling gap](research/prefetch-cancellation.md).
Both instruction-boundary totals and actual timer samples now match; the example exits with status 0.
This is a bounded comparison with published observations, not a complete hardware-timing pass.
See [the protocol, source hashes, and timer sampling correction](research/prefetch-cancellation.md).
The probe needs no downloaded ROM, assets, window, or external assembler.
## Open the affine raster demo

View file

@ -56,7 +56,7 @@ The loaded value takes precedence over the updated base. Addresses and register
Pre-indexed loads access the adjusted address; post-indexed loads access the original address.
The discarded post-indexed writeback address is not a second memory access.
Normal alignment, word rotation, odd-halfword behavior, and sign extension still apply.
Loads preserve CPSR and use normal load timing. Device clocks advance after the read, with IRQ sampling on the next step.
Loads preserve CPSR and use normal load timing. Machine timer reads sample at data-access completion; IRQ delivery waits until the next step.
Read diagnostics leave CPU state and device clocks unchanged under the existing atomic-error policy.
Stores with matching base/source still store the original base value before writeback.
PC writeback, byte/halfword PC destinations, and unimplemented user-transfer encodings remain diagnostics.
@ -359,7 +359,8 @@ The new settings apply to subsequent code accesses.
Timed CPU execution uses an ordered `CpuTiming` transaction: source fetch, actual data accesses, internal work, then target-pair fetches.
A failed instruction discards the entire transaction. Target samples do not add duplicate fetch or data costs.
IRQ entry records its source fetch and vector pair in the same path.
Device clocks still advance once after the successful step; this is not per-cycle scheduling.
Machine timer state advances through these events and commits only after success. Other device clocks still advance in bulk afterward.
This is not a complete per-cycle scheduler.
The transaction also stages the nominal Game Pak prefetch queue described below.
### Game Pak opcode prefetch
@ -387,7 +388,7 @@ Host reads and `Memory::advance_cycles` do not advance the queue; machine steps
These are source-backed nominal rules, not a hardware conformance claim.
See [queue evidence, regression coverage, and limits](../research/gamepak-prefetch.md).
The [original cancellation probe](../research/prefetch-cancellation.md) matches published read interval totals at instruction boundaries.
Its unadjusted timer samples still fail because timer reads precede the instruction's device-clock update.
Its unadjusted timer samples now also match after [ordered timer accesses](../research/ordered-timer-access.md).
### Nominal CPU resume after DMA

View file

@ -47,6 +47,9 @@ 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.
Timers stage progress through startup, source, and destination phases. Timer reads sample after source completion; writes apply after destination completion.
Timer IF acknowledgements use that same ordering. Success commits the staged bank; failure discards it.
Other devices then advance in bulk without ticking timers twice. Exact physical DMA startup timing remains unverified.
A successful unit breaks the next nominal CPU code-access sequence, even if DMA accessed only RAM.
The resumed instruction requests N once; enabled opcode prefetch can still satisfy a matching request.
RAM accesses and the nominal startup cycles advance an active prefetch stream; cartridge accesses cancel it.

View file

@ -15,7 +15,7 @@ Entry charges the discarded incoming-state PC+8/PC+4 fetch and the ARM vector pa
DMA resume can make the discarded fetch non-sequential. Missing discarded bytes do not prevent IRQ entry.
See [refill timing](../research/refill-fetch-timing.md) for history effects and remaining limits.
A device event during a step can trigger IRQ entry once no DMA is ready and CPU masks allow delivery.
Bus writes take effect before the step's device-clock update.
Timer accesses use ordered bus-completion phases. Other device writes still take effect before their bulk clock update.
`StepKind::Dma { channel }` identifies a DMA unit. `StepKind::HaltIdle` identifies one bounded HALT clock advance.
`StepKind::StopIdle` identifies a stopped system with no clock progress.
The other step kinds remain `Instruction` and `IrqEntry`.
@ -23,14 +23,24 @@ The other step kinds remain `Instruction` and `IrqEntry`.
Failed steps do not advance the clock or partially change CPU or device state. Earlier successful steps remain committed.
**The clock now uses instruction-specific costs, but it is not cycle-accurate.**
Device clocks update in batches after instructions, exception entries, DMA units, or HALT idle intervals.
Reads observe register state before that batch.
Writes, including timer start/stop and IF acknowledgement, take effect before the entire batch.
This does not reproduce bus-access timing within an instruction or IRQ synchronization delays.
The [prefetch cancellation probe](../research/prefetch-cancellation.md) makes this limitation measurable.
Its timer load omits its own source-fetch time, which differs between the read and control runs.
Nominal instruction-boundary totals match published observations; the actual timer samples do not.
The diagnostic reports both values and returns failure rather than correcting the timer sample.
Timers now use a staged clock during machine instructions, IRQ entry, and DMA units.
Source-fetch cycles advance that clock before data work. Each data access advances it before reading or writing timer registers.
All byte lanes of one access share the same completion time. Internal and target-fetch cycles follow in order.
A timer load therefore includes its source and data cycles, but not its trailing internal cycle.
Timer-start writes do not count preceding cycles. Timer-stop writes count through their own bus completion.
Block transfers preserve distinct word phases; combined reload/control writes merge at one phase, with reload bytes first.
The transaction also owns timer IF bits. Reads see overflows through the current access; acknowledgements clear earlier timer requests.
A later internal cycle can raise an acknowledged timer request again. Non-timer IF bits retain their existing behavior.
IRQ delivery still waits until the next machine step. A committed timer request can wake HALT, but not STOP.
Successful steps commit timers and timer IF bits. Failed steps discard staged counters, prescaler phases, reload/control writes, and requests.
Display and other device clocks then advance once, without advancing timers again.
Those devices still use the bulk scheduler. Mid-instruction display events, general bus arbitration, and IRQ synchronization delays remain unmodeled.
Host clock advances and HALT idle batches retain ordinary bulk timer advancement; STOP idle does not advance timers.
The unchanged [prefetch cancellation probe](../research/prefetch-cancellation.md) now matches both published interval totals and actual timer samples.
No offset is added to its timer values. See [clock ownership and evidence](../research/ordered-timer-access.md) for the bounded scope.
`Cpu::step` remains a CPU-only API. It does not honor HALT/STOP, execute DMA, advance device clocks, or sample device IRQs.
`Cpu::step_timed` executes the same operation and returns `StepTiming`, without advancing devices.
@ -78,9 +88,11 @@ Audio events and interrupt sources other than timers, display events, DMA comple
`--timer-demo` executes original ARM code that configures Timer 0, IE, and IME.
The timer overflows after 16 supplied cycles and enters the original handler through vector `0x18`.
The handler stops Timer 0, acknowledges IF, increments r10, and returns with `SUBS pc, lr, #4`.
The demo ends after 21 steps and 151 nominal cycles with one handler call, using default WAITCNT settings.
The demo completes with one handler call, using default WAITCNT settings.
Its initial ARM ROM fetch uses PC+8 sequential timing; cold pipeline filling has no separate startup charge.
These counts test the timing model; they are not hardware timing measurements.
The stopped counter is now `0xfff4`, rather than the earlier bulk-write result `0xfffb`.
Timer start excludes its preceding nine cycles; timer stop includes its two bus cycles.
These are nominal regression results, not hardware startup-delay measurements.
Its test handler deliberately changes r2 and r10; it does not implement the Nintendo BIOS calling convention.
## Keypad interrupt control

View file

@ -209,12 +209,20 @@ Game Pak prefetch research:
- [Jgenesis prefetch at fab6e2cc](https://github.com/jsgroth/jgenesis/blob/fab6e2ccc60e492dd68b7f1e927b0829a6d80195/backend/gba-core/src/prefetch.rs) and its [bus integration](https://github.com/jsgroth/jgenesis/blob/fab6e2ccc60e492dd68b7f1e927b0829a6d80195/backend/gba-core/src/bus.rs), for independent halfword capacity, full-buffer pause/restart, CPU delivery, and cancellation rules.
- [NanoBoyAdvance boundary test at cc3f4a28](https://codeberg.org/nba-emu/hw-test/src/commit/cc3f4a286cdef823980d9b353bd70befc9927d28/bus/128kb-boundary/source/main.c), reviewed for data LDM/DMA boundaries, not active opcode-queue conformance.
- [PrefetchAbuse source at 9ca57c13](https://github.com/zaydlang/PrefetchAbuse/blob/9ca57c13da7e3c569937f99a42e7c1caca029a2d/src/main.c), its [README](https://github.com/zaydlang/PrefetchAbuse/blob/9ca57c13da7e3c569937f99a42e7c1caca029a2d/README.md), and [Makefile](https://github.com/zaydlang/PrefetchAbuse/blob/9ca57c13da7e3c569937f99a42e7c1caca029a2d/Makefile), for published read observations, actual multiply delays, and build assumptions. No upstream program or ROM was imported.
See [the original cancellation comparison](research/prefetch-cancellation.md) for hashes, correspondence limits, matching nominal intervals, and failing timer samples.
See [the original cancellation comparison](research/prefetch-cancellation.md) for hashes, correspondence limits, and the initial timer sampling failure.
Both nominal intervals and unadjusted timer samples now match after ordered timer accesses.
See [prefetch behavior and evidence](research/gamepak-prefetch.md).
The nominal queue is implemented. Source differences and live WAITCNT transitions remain validation questions.
No hardware conformance result is claimed.
Ordered timer data accesses:
- The pinned [Jgenesis bus at fab6e2cc](https://github.com/jsgroth/jgenesis/blob/fab6e2ccc60e492dd68b7f1e927b0829a6d80195/backend/gba-core/src/bus.rs) charges one I/O cycle before register reads/writes and passes the resulting clock to timers.
- The pinned PrefetchAbuse read observations above isolate the missing source-fetch interval in the previous bulk timer model.
See [staged timer clock ownership](research/ordered-timer-access.md). Relative probe agreement does not establish all absolute timer edges.
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.
The ignore file excludes `roms/`, common game ROM extensions, and save files.

View file

@ -24,7 +24,7 @@ Background progress cannot cause an early ROM-file lookup or change instruction
- [NanoBoyAdvance 128 KiB boundary test at cc3f4a28](https://codeberg.org/nba-emu/hw-test/src/commit/cc3f4a286cdef823980d9b353bd70befc9927d28/bus/128kb-boundary/source/main.c) tests data LDM and DMA boundaries.
Reading this test does not validate active opcode-queue crossings. It was not run here.
- [PrefetchAbuse at 9ca57c13](https://github.com/zaydlang/PrefetchAbuse/blob/9ca57c13da7e3c569937f99a42e7c1caca029a2d/src/main.c) supplies published hardware read-cancellation observations.
A later [original probe](prefetch-cancellation.md) matches the instruction-boundary differences, but exposes incorrect bulk timer sampling.
The [original probe](prefetch-cancellation.md) matches instruction-boundary differences and, after ordered timer accesses, actual timer samples.
The source, README, and build assumptions were reviewed. No upstream implementation or ROM was imported.
No external implementation or ROM was added to the repository.
@ -76,7 +76,7 @@ A later ROM fetch still needs an exact head match to use queued timing.
Success commits queue progress. A diagnostic discards all staged progress, including earlier successful data accesses.
Missing lookahead or target bytes retain deferred diagnostics and nominal attempted-fetch costs.
Device clocks still advance once after a successful machine step, not after each timing event.
Machine timer transactions now follow these events and commit on success. Other devices still advance in bulk after the step.
`Cpu::step_timed` updates the queue and returns costs without advancing devices.
`Cpu::step` uses the same path when prefetch is enabled, but discards returned costs.
@ -135,8 +135,8 @@ Public ARM, Thumb, memory, and BIOS reports match the preceding ordered-timing c
Debug and release reports also match. Original CPU and timer demo traces are unchanged.
These public checkpoints do not independently validate active prefetch timing.
The later [cancellation comparison](prefetch-cancellation.md) matches published read observations only at instruction boundaries.
Actual timer readings still fail because device updates occur after the timer-load instruction.
Next, implement ordered timer observations and validate full-buffer restart and page boundaries independently.
The [cancellation comparison](prefetch-cancellation.md) originally matched published read observations only at instruction boundaries.
[Ordered timer accesses](ordered-timer-access.md) now make the unchanged timer readings match as well.
Next, validate absolute timer edges, full-buffer restart, and page boundaries independently.
Cold-start timing, live WAITCNT reconfiguration, per-access device scheduling, and sub-instruction DMA arbitration remain incomplete.
Exact bus history, display contention, configurable EWRAM timing, and timer/IRQ delays remain separate work.

View file

@ -0,0 +1,100 @@
# Ordered timer data accesses
## Result
Machine timer reads and writes now use bus-access completion times instead of the instruction's initial clock.
The unchanged [prefetch cancellation probe](prefetch-cancellation.md) matches both published interval totals and unadjusted timer readings.
Its program, published observations, and measurement code were not changed to obtain this result.
This is a timer-specific extension to nominal timing, not a complete cycle-accurate device scheduler.
Display events, DMA arbitration, timer startup delays, and IRQ synchronization remain separate limits.
No physical-hardware measurement or external-emulator differential run was performed locally.
## Evidence and phase choice
The preceding probe isolated the error: its missing interval equaled the difference between the final source-fetch costs.
The opcode queue computed those costs correctly, but the timer read occurred before they advanced device time.
Moving only the probe's reported result would conceal that error rather than correct device behavior.
The reviewed [Jgenesis bus at fab6e2cc](https://github.com/jsgroth/jgenesis/blob/fab6e2ccc60e492dd68b7f1e927b0829a6d80195/backend/gba-core/src/bus.rs) charges the I/O cycle before register access.
Its timer reads and writes receive the resulting clock value.
This supports the implemented completion-phase rule for a one-cycle I/O access.
No source code was imported from that implementation.
The published subtraction experiment validates relative read intervals.
It does not independently establish every absolute timer edge, startup delay, or control-write phase.
Those remain explicit validation limits.
## Staged clock ownership
`Memory` owns an optional `TimerStep` during each machine instruction, IRQ entry, or DMA unit.
The transaction copies only the four timers and timer IF bits, not RAM, display state, or the entire I/O object.
Its elapsed timestamp starts at zero and increases monotonically.
It has no heap allocation or event log.
For a CPU instruction:
1. Stage timer progress through the source fetch.
2. For each data access, stage its elapsed cycles before reading or writing timer registers.
3. Stage internal cycles and any target-pair fetches in their existing order.
4. On success, commit timers and timer IF bits at the final timestamp.
5. Advance the other devices and the machine clock once, without ticking timers again.
One word access has one sampling time for all four byte lanes.
Reload bytes merge before control bytes at that same time.
Block transfers have separate completion times for successive words.
A start write cannot count earlier source cycles. A stop write includes clocks through its completion.
Prescaler remainders, cascaded overflow pulses, and reload behavior use the existing timer implementation.
Timer IF reads include requests raised through the access phase.
Acknowledgement clears timer requests already present at that phase; later work can raise them again.
Committing timer flags preserves non-timer IF bits.
Those other sources still follow the existing bulk event model, so IF is not a fully cycle-accurate cross-device snapshot.
IRQ delivery remains at machine-step boundaries, never halfway through an instruction.
A committed enabled timer request wakes HALT. STOP does not use timer requests as a wake source.
### DMA
Each unit uses the existing nominal two-cycle startup on its first transfer.
Timer sampling follows startup plus source cost; destination writes follow the destination cost.
A failed source or destination discards the timer transaction.
A successful unit commits timer state before other device clocks and DMA completion IRQ handling.
This extends the existing whole-unit scheduler; it does not establish physical DMA startup or preemption timing.
### Errors and API isolation
CPU diagnostics discard staged time, counter values, prescaler remainders, and pending timer requests.
This includes failures before instruction fetch and failures after earlier block-load accesses.
Existing store validation still prevents partial writes when a block transfer has an invalid destination.
Previously completed machine steps remain committed.
`Cpu::step` and `Cpu::step_timed` remain CPU-only APIs.
They update CPU state, ordinary register writes, instruction retention, and enabled opcode-prefetch timing as before.
They do not create a timer transaction or advance device clocks.
Host reads and writes likewise remain clock-free.
`Memory::advance_cycles` and HALT idle intervals use the existing bulk timer advancement.
STOP idle freezes all clocks. The instruction entering STOP still pays its complete nominal duration.
Instruction fetches from I/O are not given the data-access timer sampling rule.
Other device reads, writes, rendering, and event scheduling retain their documented limits.
## Validation
Original tests cover ARM/Thumb loads, coherent byte lanes, start/stop stores, and ordered block transfers.
They check old-reload overflow behavior, prescaler retention, cascades, timer IF reads, acknowledgement, and later reassertion.
Other tests cover non-timer IF preservation, deferred IRQ delivery, DMA phases, rollback, and CPU-only isolation.
Capture-enabled and capture-disabled steps check that display clock splits do not tick timers twice.
BIOS HALT/STOP stores check final-cycle ownership and timer wake behavior.
The cancellation example exits successfully in debug and release on Darwin arm64.
Its CSV reports match between build profiles. No correction is applied to loaded timer values.
Workspace/core/demo tests, formatting, clippy, rustdoc, Python tests, native ROM windows, and graphics smoke modes pass.
Pinned public ARM, Thumb, memory, and BIOS reports remain byte-identical to the preceding change.
The original timer demo retains its cycle count and IRQ-entry step.
Its stopped counter changes from `0xfffb` to `0xfff4`: start excludes nine preceding cycles, while stop includes two bus cycles.
The alias-load regression now samples `0x00c0fffb` after source and data cycles, instead of the previous `0x00c0fffa`.
These expectation changes follow the access phases; the independent cancellation program and observations remain unchanged.
Next, validate absolute timer startup/control edges and independently test full-buffer prefetch restart and page boundaries.
Extend other device observations only with explicit clock ownership and rollback coverage.

View file

@ -4,9 +4,9 @@
An original ARM probe reproduces the measured structure of the read cases in `zaydlang/PrefetchAbuse`.
Its instruction-boundary cycle differences match every published read observation.
Its actual emulated timer readings do **not** match those observations.
The difference isolates an existing timer-sampling limitation, not a new queue discrepancy.
No core timing behavior was changed for this comparison.
Its actual emulated timer readings now also match, after [ordered timer accesses](ordered-timer-access.md).
The initial comparison isolated a bulk timer-sampling error, not a queue discrepancy.
The original program, published observations, and measurement code remain unchanged.
This is not a run or a port of the upstream ROM.
The expected values are published measurement facts. The program builder and runner are original.
@ -70,10 +70,10 @@ direnv exec . cargo test --locked -p gba-core --test prefetch_cancellation
direnv exec . cargo run --locked -p gba-demos --example prefetch_cancellation > /tmp/prefetch-cancellation.csv
```
**The example currently exits with status 1 because the timer comparisons fail.**
That status is expected evidence of a known limit, not a passing hardware-timing test.
A matching instruction-boundary total cannot override a failed timer comparison.
Status 0 requires both comparisons to match for every case. Status 2 reports invalid arguments or execution errors.
**The example now exits with status 0 because both comparisons match.**
It previously exited with status 1, exposing the bulk timer-sampling error described below.
A matching instruction-boundary total still cannot override a failed timer comparison.
Status 0 requires both comparisons to match for every case. Status 1 reports mismatches; status 2 reports invalid arguments or execution errors.
The example takes no arguments and needs no downloaded files, window, or external assembler.
Use `--release` to repeat the same checks in a release build.
@ -90,11 +90,11 @@ The CSV columns are:
No timing correction is applied to `timer_delta`.
The runner does not hide, patch, or reinterpret failing timer values.
## Root cause of the timer mismatch
## Historical timer mismatch and correction
The opcode queue sees ordered source, data, and internal costs.
The timer device still updates only after the entire instruction completes.
Consequently, the timer load reads the old counter before its own source-fetch cost advances device time.
Before the correction, the opcode queue saw ordered source, data, and internal costs.
The timer device updated only after the entire instruction completed.
Consequently, the timer load read the old counter before its own source-fetch cost advanced device time.
The read run cancels prefetch with ROM data traffic.
Its subsequent timer load must fetch code from ROM again.
@ -107,22 +107,24 @@ For a one-cycle multiply and WAITCNT `0x4000`:
| --- | ---: |
| Published interval difference | 17 |
| Machine-clock boundary difference | 17 |
| Actual timer-sample difference | 14 |
| Previous timer-sample difference | 14 |
| Current timer-sample difference | 17 |
| Timer-load source fetch in the read run | 8 |
| Timer-load source fetch in the control run | 5 |
The missing three cycles equal `8 - 5`.
For every compared case, the boundary-minus-timer discrepancy equals the final source-fetch cost difference.
Before the correction, each boundary-minus-timer discrepancy equaled the final source-fetch cost difference.
The sample instructions have identical data and internal costs, so those costs cancel in the subtraction.
With prefetch disabled, the two final source-fetch costs match.
The timer and boundary differences then match each other, and the cancellation-specific extra cycle disappears.
This negative control helps separate the sampling problem from timer subtraction or multiply decoding errors.
The regression tests explicitly mark the bulk-sampling expectation as a known gap.
Replace that expectation when timer I/O moves to ordered bus phases.
Do not make the probe pass by adding an offset to its loaded timer value.
Timer startup/stop placement, other device events, and atomic failure behavior also need coverage before changing clock ownership.
Timer data accesses now sample staged device state at bus completion, including the source and data cycles.
Timer writes use the same phase. Successful steps commit that state; failed steps discard it.
The regression now requires actual timer values to match the unchanged published observations.
No offset was added to the loaded value or diagnostic report.
See [ordered timer accesses](ordered-timer-access.md) for start/stop, IF, DMA, API isolation, and rollback coverage.
## Coverage and limits
@ -139,5 +141,6 @@ The largest multiply delay here does not establish eight-halfword capacity behav
Live WAITCNT changes, DMA, HALT/STOP edges, and exact hardware timer startup delays remain outside this probe.
See [the queue model](gamepak-prefetch.md) for those separate limits.
Next, move timer observations toward ordered bus-access phases with explicit rollback and no duplicate device advancement.
Ordered timer observations now retain rollback and avoid duplicate device advancement.
Absolute timer startup and control-edge validation remain necessary.
Independent full-buffer and page-boundary measurements remain necessary before claiming complete prefetch timing accuracy.

View file

@ -51,7 +51,8 @@ Update this file when a feature lands or a limit is removed.
- Persistent fetch access kinds follow CPU data/internal work, compose with DMA resume, and reset after target refills.
- Game Pak wait-state control (`WAITCNT`) for all three ROM windows.
- Bit 14 enables a nominal eight-halfword opcode prefetch queue, with partial fetches, cancellation, and DMA/idle integration.
- An original ARM cancellation probe matches published read interval totals, while separately reporting incorrect bulk timer samples.
- Staged timer clocks follow CPU and DMA bus-completion phases, including timer IF reads/acknowledgements and failed-step rollback.
- The unchanged original ARM cancellation probe matches both published read interval totals and its unadjusted timer samples.
- Sequential/non-sequential data accesses, branch refill costs, and variable multiply timing.
- Optional caller-supplied 16 KiB BIOS mapping, with CPU read protection based on retained ARM PC+8 or aligned Thumb PC+4 words.
- Optional original BIOS replacement: minimal boot, SoftReset, selective RegisterRamReset, IRQ dispatch, interrupt waits, memory copy/fill, integer/fixed-point arithmetic, affine matrices, bit unpacking, LZ77/run-length/Huffman decompression, and differential filters.
@ -121,7 +122,7 @@ CPU/bus costs are nominal estimates, not a cycle-accurate implementation.
The instruction buffer retains the next fetch kind; cold startup costs and per-access device scheduling remain unmodeled.
Game Pak prefetch uses source-backed queue rules, not hardware-verified timing.
Live WAITCNT transitions, page-boundary behavior, and exact DMA/idle ordering still need independent timing validation.
Timer reads occur before the current instruction's device update. Prefetch cancellation probes expose the resulting sample error.
Timer data accesses follow ordered nominal bus phases. Other devices remain instruction-batched; exact timer startup and IRQ delays remain unverified.
Graphics support includes Mode 0–5 debug snapshots and HBlank row capture with sprites, mosaic, windows, and color effects.
Internal affine origins, vertical window flags, and vertical mosaic counters track line boundaries.
Horizontal window comparators retain four-cycle event history.
@ -173,8 +174,8 @@ Writes to cartridge addresses return errors instead of modeling cartridge hardwa
Validate BIOS snapshots with independent hardware tests before claiming exact refill or data-access history.
The desktop runner still uses only the original BIOS. Keep protected reads image-derived for all supplied images.
2. Validate the active nominal Game Pak prefetch queue against independent timing tests.
The [cancellation probe](research/prefetch-cancellation.md) isolates a timer sampling gap despite matching nominal interval totals.
Move timer observations to ordered bus phases without losing rollback or advancing devices twice.
The [cancellation probe](research/prefetch-cancellation.md) now matches after [ordered timer accesses](research/ordered-timer-access.md).
Validate absolute timer startup/control edges and extend ordered observations to other devices without duplicate clocks or lost rollback.
Validate full-buffer restart, page boundaries, live WAITCNT transitions, and cold-start costs.
See [prefetch behavior and evidence](research/gamepak-prefetch.md); exact bus arbitration and timer/IRQ delays remain separate work.
3. Validate keypad retrigger behavior with hardware tests and add remaining device registers; extend BIOS reset coverage as sound/serial support becomes available.

View file

@ -276,13 +276,17 @@ Integration tests check RAM/internal progress, ROM data stalls, branch and IRQ f
WAITCNT CPU/host/DMA writes, RAM/cart DMA, HALT/STOP, failed steps, and missing ROM bytes have separate checks.
See [prefetch evidence and limits](research/gamepak-prefetch.md). These tests validate the nominal model, not physical-hardware accuracy.
An [original cancellation probe](research/prefetch-cancellation.md) compares read/control intervals with pinned, published hardware observations.
Its instruction-boundary totals match; its actual timer samples fail. The example returns nonzero for those failures.
Regressions identify the missing source-fetch interval without applying a correction to the timer values.
Both its instruction-boundary totals and actual timer samples now match. Any mismatch still returns a nonzero status.
Regressions retain the original program and published observations without correcting the loaded timer values.
Disabled-prefetch controls, delay bounds, completion PCs, and fresh-run determinism have separate checks.
Instruction timing tests cover arithmetic, shifts, branches, transfers, block operations, multiply, and exception returns.
Timed and untimed execution are compared across all 65,536 Thumb encodings with fixed initial registers.
Timer/IRQ tests verify that changing WAITCNT changes when an interrupt is sampled.
Timer tests cover all prescalers, reload changes, start/stop behavior, byte writes, large clock advances, and cascades.
Ordered timer tests cover ARM/Thumb sampling, coherent byte lanes, block-transfer phases, reload/control ordering, and prescaler retention.
They verify timer IF reads/acknowledgements, preservation of other IF bits, deferred IRQ delivery, DMA phases, and HALT/STOP ownership.
Failures before fetch and after partial data work must discard staged time and requests. CPU-only APIs must not advance timer devices.
Display-capture splits must not tick committed timers a second time. See [ordered timer accesses](research/ordered-timer-access.md).
Bulk timer advancement is compared against an independent cycle-by-cycle reference for each prescaler and cascade configuration.
Machine tests cover ARM/Thumb IRQ return, pending-request gating, acknowledgement, and the complete timer demo.
Failed I/O block transfers are checked for partial register writes, hidden reload changes, and unintended IF acknowledgement.