143 lines
8.3 KiB
Markdown
143 lines
8.3 KiB
Markdown
# Prefetch cancellation: independent read observations
|
|
|
|
## Result
|
|
|
|
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.
|
|
|
|
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.
|
|
No external implementation, ROM, saved data, Nintendo code, or assets were added.
|
|
No physical-hardware measurement or external-emulator differential run was performed locally.
|
|
|
|
## Pinned evidence
|
|
|
|
Repository: [zaydlang/PrefetchAbuse](https://github.com/zaydlang/PrefetchAbuse)
|
|
|
|
Revision: `9ca57c13da7e3c569937f99a42e7c1caca029a2d`
|
|
|
|
| Reviewed file | Bytes | SHA-256 |
|
|
| --- | ---: | --- |
|
|
| [README.md](https://github.com/zaydlang/PrefetchAbuse/blob/9ca57c13da7e3c569937f99a42e7c1caca029a2d/README.md) | 1662 | `4ff13a5ec496bcadc65aaa5264703ebafdf0709a7624c1bb1c6175461d213b9c` |
|
|
| [src/main.c](https://github.com/zaydlang/PrefetchAbuse/blob/9ca57c13da7e3c569937f99a42e7c1caca029a2d/src/main.c) | 6753 | `0b4286917ae6f7baf9c44012bd521816bcfe7a068df59f12da14fbb8f4e32b04` |
|
|
|
|
The [Makefile](https://github.com/zaydlang/PrefetchAbuse/blob/9ca57c13da7e3c569937f99a42e7c1caca029a2d/Makefile) was also reviewed for build assumptions.
|
|
It expects devkitARM and libgba. This probe does not need either dependency.
|
|
The pinned tree contains no license file or built ROM. Its saved-data file was not used.
|
|
Only source links, hashes, and factual observations are recorded here; upstream code is not redistributed.
|
|
|
|
The author describes the expected arrays as hardware results.
|
|
The README calls the delays NOPs, but `src/main.c` uses ARM multiply instructions.
|
|
The source, not that wording, defines the compared protocol.
|
|
The display labels also start at zero, while the first multiply supplies one internal cycle.
|
|
The original probe labels cases by actual internal-cycle count, from one through eight.
|
|
|
|
## Compared protocol
|
|
|
|
Each case runs twice, once with an additional ROM data read and once without it.
|
|
Both runs use fresh machines, the same WAITCNT setting, and the same multiply delay.
|
|
|
|
1. Set up the timer address, enable value, ROM data address, and multiplier values before measurement.
|
|
2. Start Timer 0 with zero reload, prescaler one, and no interrupt request.
|
|
3. Execute a multiply with one through four internal cycles.
|
|
For larger delays, execute a four-cycle multiply followed by another multiply supplying the remainder.
|
|
4. In the read run only, execute a word load from `0x08000000`.
|
|
5. Read Timer 0 with a halfword load.
|
|
|
|
Code executes in the first ROM wait-state window, away from a 128 KiB boundary.
|
|
The final setup literal load cancels earlier queue activity before the timer-start store establishes the measured stream.
|
|
All measured instruction bytes and lookahead are mapped.
|
|
The probe stops after the timer sample; it does not execute padding, literals, or a return handler.
|
|
|
|
Our setup register allocation and code layout differ from upstream.
|
|
There is no C runtime, console, input loop, BIOS boot, or upstream function prologue in the probe.
|
|
The measured store/multiply/read/sample structure and multiplier values are preserved.
|
|
This correspondence supports a bounded comparison, not a claim of binary equivalence.
|
|
|
|
The source compares `read_test - calibration` separately for each delay and WAITCNT setting.
|
|
The observations measure that interval difference, not the isolated data-access cost of one LDR.
|
|
The four WAITCNT values are `0x4000`, `0x4004`, `0x4010`, and `0x4014`.
|
|
The factual read table is recorded in `gba_demos::prefetch_probe::PUBLISHED_READ_DELTAS`.
|
|
The queue implementation does not supply or calculate that table.
|
|
|
|
## Reproduce on Darwin
|
|
|
|
```sh
|
|
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 takes no arguments and needs no downloaded files, window, or external assembler.
|
|
|
|
Use `--release` to repeat the same checks in a release build.
|
|
Debug and release CSV reports match on Darwin arm64.
|
|
The CSV columns are:
|
|
|
|
- `idle_cycles`: sum of multiply internal cycles, not total elapsed time.
|
|
- `waitcnt`: configured wait-state control.
|
|
- `published_delta`: upstream read-minus-control observation, in decimal cycles.
|
|
- `boundary_delta`: read-minus-control machine-clock interval between completed timer-start and timer-sample instructions.
|
|
- `timer_delta`: difference between the actual timer values loaded into the CPU register.
|
|
- `boundary_matches`, `timer_matches`: separate comparisons with the published observation.
|
|
|
|
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
|
|
|
|
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.
|
|
|
|
The read run cancels prefetch with ROM data traffic.
|
|
Its subsequent timer load must fetch code from ROM again.
|
|
The control run retains queue progress, so its timer load has a shorter code-fetch cost.
|
|
Sampling both timers too early therefore loses different numbers of cycles; calibration cannot cancel that difference.
|
|
|
|
For a one-cycle multiply and WAITCNT `0x4000`:
|
|
|
|
| Quantity | Cycles |
|
|
| --- | ---: |
|
|
| Published interval difference | 17 |
|
|
| Machine-clock boundary difference | 17 |
|
|
| Actual timer-sample difference | 14 |
|
|
| 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.
|
|
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.
|
|
|
|
## Coverage and limits
|
|
|
|
The extra data cycle appears in the fast sequential settings after four or seven multiply internal cycles.
|
|
Those cases match the published completion-edge cancellation pattern.
|
|
Original regressions inspect data timing separately from multiply internal work and final timer-fetch timing.
|
|
Other checks cover deterministic fresh runs, bounded delay arguments, completion PCs, and disabled-prefetch controls.
|
|
Workspace/core/demo tests, clippy, formatting, and rustdoc pass on Darwin arm64 in debug and release.
|
|
The existing pinned ARM, Thumb, memory, and BIOS reports remain unchanged.
|
|
|
|
This comparison does not cover Thumb code, cartridge writes, full-buffer restart, or page crossings.
|
|
The upstream write cases remain unsupported because the core reports cartridge writes as diagnostics.
|
|
The largest multiply delay here does not establish eight-halfword capacity behavior.
|
|
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.
|
|
Independent full-buffer and page-boundary measurements remain necessary before claiming complete prefetch timing accuracy.
|