fix(timers): apply machine accesses at bus completion
This commit is contained in:
parent
8930ea93b4
commit
bfecc87e84
20 changed files with 765 additions and 137 deletions
|
|
@ -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.
|
||||
|
|
|
|||
100
docs/research/ordered-timer-access.md
Normal file
100
docs/research/ordered-timer-access.md
Normal 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.
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue