feat(timing): implement nominal Game Pak opcode prefetch
This commit is contained in:
parent
7f3ebd3a5d
commit
8f22092e06
25 changed files with 1062 additions and 124 deletions
|
|
@ -56,7 +56,7 @@ No extra access is appended. A resumed ROM branch can therefore cost more than i
|
|||
The later [sequencing extension](fetch-access-sequencing.md) applies each instruction's data/internal effects to the following source fetch.
|
||||
CPU history, DMA resume, and ROM page boundaries can all require N without duplicating an access.
|
||||
A completed transfer after DMA can establish a new N requirement for the following fetch.
|
||||
Game Pak prefetch remains unimplemented; WAITCNT bit 14 does not alter this nominal timing path.
|
||||
This baseline preceded active prefetch. The later [nominal queue](gamepak-prefetch.md) can satisfy an N resume request from queued code.
|
||||
|
||||
This change does not select a DMA value for CPU open-bus reads.
|
||||
DMA channel data, persistent local IWRAM lanes, and protected BIOS history remain separate.
|
||||
|
|
|
|||
|
|
@ -123,8 +123,10 @@ The timer therefore runs two additional cycles before the handler stops it.
|
|||
- Cold-start costs and state-changing test/compare quirks are not hardware-validated.
|
||||
- Data timing still uses an instruction-local access trace. This is not a complete bus-event scheduler.
|
||||
- Device clocks still update once per instruction, IRQ entry, or DMA unit.
|
||||
- Game Pak prefetch, configurable EWRAM timing, display contention, and DMA during internal cycles remain unimplemented.
|
||||
- Configurable EWRAM timing, display contention, and DMA during internal cycles remain unimplemented.
|
||||
- IRQ synchronization delays and complete BIOS/general-bus latch history remain incomplete.
|
||||
|
||||
The later [prefetch prerequisite](gamepak-prefetch.md) records source, data, internal, and refill costs in order without changing totals.
|
||||
The later ordered timing transaction preserved these baseline totals.
|
||||
The subsequent [nominal prefetch queue](gamepak-prefetch.md) uses those events and can accelerate code despite a CPU N request.
|
||||
CPU access-kind rules remain separate from the queue's cartridge-bus timing.
|
||||
The next timing work remains an active Game Pak queue, with verified startup and invalidation rules and independent tests.
|
||||
|
|
|
|||
|
|
@ -72,10 +72,11 @@ The later [refill timing extension](refill-fetch-timing.md) also charges source
|
|||
- Thumb BL prefixes use their lookahead address; suffixes add the target pair.
|
||||
- Cold fills have no separate startup charge, even though their samples update supported bus history.
|
||||
- Devices advance once after the whole successful instruction. No per-access timer/IRQ update is introduced.
|
||||
- Game Pak prefetch, display contention, configurable EWRAM timing, and sub-instruction DMA arbitration remain unimplemented.
|
||||
- Display contention, configurable EWRAM timing, and sub-instruction DMA arbitration remain unimplemented.
|
||||
- The later [nominal prefetch queue](gamepak-prefetch.md) changes code costs when bit 14 is enabled.
|
||||
|
||||
This is a fetch-address correction within the existing timing model, not cycle-accurate emulation.
|
||||
The next timing step is Game Pak prefetch with verified startup and invalidation rules.
|
||||
Game Pak prefetch followed this baseline; independent startup and invalidation timing validation remains necessary.
|
||||
|
||||
## Original regression coverage
|
||||
|
||||
|
|
|
|||
|
|
@ -1,115 +1,139 @@
|
|||
# Game Pak prefetch: evidence and timing prerequisites
|
||||
# Game Pak prefetch: nominal queue and evidence
|
||||
|
||||
## Current result
|
||||
|
||||
Game Pak prefetch is **not enabled in the core yet**.
|
||||
WAITCNT bit 14 remains stored without accelerating instruction fetches.
|
||||
This change prepares ordered CPU timing transactions without changing cycle totals or instruction results.
|
||||
WAITCNT bit 14 enables a nominal Game Pak opcode prefetch queue.
|
||||
The queue tracks eight halfwords, partial transfers, full-buffer stops, and cancellation stalls.
|
||||
It follows ordered CPU timing events and the existing instruction-boundary DMA scheduler.
|
||||
This is source-backed timing behavior, not hardware-verified accuracy.
|
||||
|
||||
The previous timing path collected data costs during execution, then calculated source and refill costs afterward.
|
||||
That produced correct existing totals but could not directly advance a prefetch queue in bus-access order.
|
||||
A prefetch implementation needs to distinguish cartridge-bus occupation from time available for background fetching.
|
||||
An aggregate instruction cost cannot identify when to fill or invalidate a queue.
|
||||
The queue stores addresses and timing metadata, not ROM bytes.
|
||||
CPU instruction retention, BIOS protected-read history, and IWRAM local lanes remain separate.
|
||||
Background progress cannot cause an early ROM-file lookup or change instruction values.
|
||||
|
||||
## Sources reviewed
|
||||
|
||||
- [GBATEK GamePak Prefetch](https://www.problemkaputt.de/gbatek-gba-gamepak-prefetch.htm) describes eight 16-bit entries, opcode-only service, and prefetch during internal or other-memory accesses.
|
||||
- [mGBA's cycle-counting article](https://mgba.io/2015/06/27/cycle-counting-prefetch/) explains independent cartridge-bus progress, partially completed fetches, and instruction-pair examples.
|
||||
Its examples and linked timing suite provide leads for independent validation, not a local hardware-test pass.
|
||||
- [NanoBoyAdvance bus timing at 55b5cf0a](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/bus/timing.cc) distinguishes queue hits, in-progress fetches, misses, and cancellation stalls.
|
||||
- [ares prefetch at 6f6786e0](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/prefetch.cpp) provides an independent halfword-queue implementation.
|
||||
Its [bus dispatcher](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/bus.cpp) separates opcode requests, data requests, and other-region progress.
|
||||
- [GBATEK GamePak Prefetch](https://www.problemkaputt.de/gbatek-gba-gamepak-prefetch.htm) describes eight 16-bit entries, opcode-only service, and idle-bus progress.
|
||||
- [mGBA's cycle-counting article](https://mgba.io/2015/06/27/cycle-counting-prefetch/) explains independent cartridge progress and partially completed fetches.
|
||||
Its examples and linked timing suite provide validation leads, not a local hardware-test pass.
|
||||
- [NanoBoyAdvance bus timing at 55b5cf0a](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/bus/timing.cc) distinguishes hits, partial fetches, misses, and cancellation stalls.
|
||||
- [ares prefetch at 6f6786e0](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/prefetch.cpp) uses halfword entries and stops at capacity or page boundaries.
|
||||
Its [bus dispatcher](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/bus.cpp) separates opcode requests, data requests, and other-memory progress.
|
||||
- [Jgenesis prefetch at fab6e2cc](https://github.com/jsgroth/jgenesis/blob/fab6e2ccc60e492dd68b7f1e927b0829a6d80195/backend/gba-core/src/prefetch.rs) independently supports halfword entries, full-buffer stopping, and last-cycle cancellation stalls.
|
||||
Its [bus integration](https://github.com/jsgroth/jgenesis/blob/fab6e2ccc60e492dd68b7f1e927b0829a6d80195/backend/gba-core/src/bus.rs) separates CPU delivery cycles from background transfers and DMA ownership.
|
||||
- [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](https://github.com/zaydlang/PrefetchAbuse) appeared in search results as a cartridge-access cancellation test lead.
|
||||
Only its search excerpt was reviewed. No expectations or implementation code were taken from it.
|
||||
|
||||
No external implementation or ROM was added to the repository.
|
||||
No physical-hardware measurement or external-emulator differential run was performed.
|
||||
|
||||
## Agreement and unresolved details
|
||||
|
||||
The sources agree on the main model:
|
||||
|
||||
- Prefetch is controlled by WAITCNT bit 14.
|
||||
- The buffer serves Game Pak opcodes, not arbitrary ROM data loads.
|
||||
- Internal CPU work and accesses to other memory can give the cartridge bus time to fetch ahead.
|
||||
- A matching ready entry avoids ordinary ROM waits.
|
||||
- A request for an in-progress fetch waits for the remaining work rather than restarting the full access.
|
||||
- Cartridge data traffic and nonmatching code requests interact with the active prefetch sequence.
|
||||
|
||||
However, the implementation details are not interchangeable:
|
||||
|
||||
| Area | Source observation | Required follow-up |
|
||||
| --- | --- | --- |
|
||||
| Entry granularity | NanoBoyAdvance groups ARM words; ares tracks halfwords | Verify partial ARM fetches and ARM/Thumb transitions |
|
||||
| Full buffer | ares stops production and restarts after draining; NanoBoyAdvance uses an instruction-sized countdown/count model | Establish capacity, pause, drain, and restart timing independently |
|
||||
| ROM page boundary | ares prevents background reads across the forced-N boundary; NanoBoyAdvance's queue step uses its retained duty | Verify 128 KiB boundaries without assuming a normal ready-entry hit |
|
||||
| Cancellation | Both contain completion-edge stall handling | Verify the exact halfword phase and which code/data requests trigger a stall |
|
||||
| DMA and idle | Prefetch shares cartridge time with other bus owners | Verify RAM-only DMA, cartridge DMA, HALT/STOP, and resume separately |
|
||||
| WAITCNT changes | Queue state and current transfer timing depend on register changes | Define enable, disable, and changed-wait behavior before integration |
|
||||
|
||||
These differences do not establish that either implementation is wrong.
|
||||
They show why copying one queue policy is not sufficient evidence for the entire subsystem.
|
||||
The first implementation should state its supported cases and retain explicit limits for unresolved behavior.
|
||||
|
||||
## Ordered CPU timing transactions
|
||||
|
||||
`CpuTiming` replaces the data-only accumulator.
|
||||
A timed CPU instruction now records these events in order:
|
||||
|
||||
1. Source instruction fetch, using the incoming access kind and instruction width.
|
||||
2. Actual data reads/writes, using their aligned bus addresses and widths.
|
||||
3. Internal CPU cycles from the incoming instruction classification.
|
||||
4. Refill target N and target+width S accesses, using the resulting instruction state.
|
||||
## Implemented queue rules
|
||||
|
||||
S means sequential access; N means non-sequential access.
|
||||
An instruction without data, internal work, or a refill omits those events.
|
||||
The two refill events occur in `Cpu::refill_pipeline`, alongside its existing mapped target samples.
|
||||
No separate refill-cost formula runs after execution.
|
||||
IRQ entry records its discarded incoming-state source fetch before its ARM vector pair, without a data or internal event.
|
||||
Raw ROM costs include one clock plus the configured wait states for each halfword.
|
||||
The public `bus_cycles` helper remains stateless and does not apply prefetch.
|
||||
|
||||
Code timing retains the entry WAITCNT snapshot.
|
||||
Data timing retains the settings observed at each actual bus access, including pre-write settings for a WAITCNT store.
|
||||
The next instruction uses the new WAITCNT settings.
|
||||
The existing data N/S rule and persistent CPU source-fetch kind are unchanged.
|
||||
1. A ROM code miss pays the requested raw cost, plus any cancellation stall.
|
||||
It starts background fetching at the following halfword address.
|
||||
2. Internal cycles and non-cartridge memory accesses advance an already active stream.
|
||||
They cannot start a stream by themselves.
|
||||
3. A code request must match the queue head. CPU N requests can still match queued code.
|
||||
A queued Thumb halfword or complete ARM word costs one CPU-side cycle.
|
||||
4. That CPU-side cycle also advances background work when production is active.
|
||||
A partial ARM word waits separately for its missing halfwords.
|
||||
5. Background production stops when an advance observes all eight entries occupied.
|
||||
It remains stopped while the queue drains. The next empty demand restarts with N timing.
|
||||
6. ROM data accesses cancel the stream instead of consuming opcode entries.
|
||||
A nonmatching ROM code request also cancels it before starting a new stream.
|
||||
7. Cancellation costs one additional cycle when an active halfword has one cycle remaining.
|
||||
A paused or fully occupied queue does not add that stall.
|
||||
8. Background production pauses before a 128 KiB ROM boundary.
|
||||
Demand at that boundary uses raw forced-N timing and starts the next stream.
|
||||
No background transfer crosses beyond the final ROM window.
|
||||
|
||||
The transaction returns the same `StepTiming` fields as before.
|
||||
A diagnostic discards the entire transaction, including any data events recorded before a late failure.
|
||||
Missing lookahead or target bytes retain their existing deferred diagnostic policy and nominal attempted-fetch cost.
|
||||
Device clocks still advance once after a successful machine step.
|
||||
This is ordered cost accounting, not a cycle-by-cycle bus scheduler.
|
||||
The full-buffer policy follows ares and Jgenesis.
|
||||
The page-boundary policy follows ares conservatively; the reviewed sources differ in this area.
|
||||
The cancellation cycle is included in the requesting code or data field of `StepTiming`.
|
||||
These rules have original regression tests, not a hardware conformance result.
|
||||
|
||||
Untimed execution, host inspection, cold-fill sampling, and DMA do not join a CPU timing transaction.
|
||||
They retain their existing behavior and do not create extra code/data charges.
|
||||
Untimed CPU execution still updates instruction retention and the next fetch kind, but does not compute a timing transaction.
|
||||
A future active prefetch model must define timing-state progression for this API explicitly.
|
||||
## Timing transactions and ownership
|
||||
|
||||
`Memory` owns the committed queue because CPU, DMA, and HALT share cartridge-bus time.
|
||||
Each `CpuTiming` transaction stages a copy and applies events in order:
|
||||
|
||||
1. Source instruction fetch at ARM P+8 or Thumb P+4.
|
||||
2. Actual data accesses, using aligned bus addresses and widths.
|
||||
3. Internal cycles from the incoming instruction classification.
|
||||
4. Refill target N and target+width S accesses, using the resulting instruction state.
|
||||
|
||||
IRQ entry records the discarded incoming-state source, then the ARM vector pair.
|
||||
Non-ROM code fetches advance the existing ROM stream without consuming it.
|
||||
A branch can therefore preserve the stream while executing a BIOS handler.
|
||||
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.
|
||||
|
||||
`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.
|
||||
With prefetch disabled, the CPU-only path retains its previous untimed behavior.
|
||||
Host reads, setup writes, and `Memory::advance_cycles` do not supply queue progress.
|
||||
Host WAITCNT writes do apply queue configuration.
|
||||
|
||||
Cold entry, debugger invalidation, and PC/state mismatches clear staged queue metadata.
|
||||
They preserve the existing nominal S startup policy and add no current/decode fill charge.
|
||||
Accepted IRQ entry uses the interrupted pipeline's cold/retained state before replacing that pipeline.
|
||||
Failed CPU steps preserve the previously committed queue.
|
||||
|
||||
### WAITCNT changes
|
||||
|
||||
A store pays source and data costs using the previous settings.
|
||||
After the write, changing ROM wait fields or bit 14 clears timing metadata immediately.
|
||||
Changing only SRAM or PHI fields preserves the stream.
|
||||
CPU, host byte/halfword/word, and DMA writes use this configuration policy.
|
||||
|
||||
This reset policy is deliberate, not a verified model of live hardware reconfiguration.
|
||||
Jgenesis models retained entries after disable; that behavior is not implemented here.
|
||||
Exact enable/disable transitions and changes during a partial transfer need independent tests.
|
||||
|
||||
### DMA and idle
|
||||
|
||||
A DMA unit stages queue progress independently of the CPU transaction.
|
||||
Its existing two startup cycles precede source and destination accesses in the nominal model.
|
||||
RAM-only DMA supplies idle cartridge time. Cartridge DMA cancels the stream and can incur the completion-edge stall.
|
||||
A failed unit preserves queue state and clocks. A successful WAITCNT destination applies the new configuration.
|
||||
DMA's CPU-resume N request remains separate; a matching queue entry can still satisfy that request.
|
||||
|
||||
`HaltIdle` advances an active queue with its idle cycles. `StopIdle` freezes it.
|
||||
Machine device updates do not advance the queue a second time.
|
||||
Exact DMA startup/completion placement, bus arbitration, and hardware HALT/STOP edges remain unverified.
|
||||
|
||||
## Original regression coverage
|
||||
|
||||
Test builds retain events from the last successful CPU timing transaction.
|
||||
Production builds retain only totals, the code WAITCNT snapshot, and data-sequence state; they allocate no event log.
|
||||
The test log covers the maximum supported block load, including PC, without truncation.
|
||||
Tests inspect event order and metadata independently of total cycle assertions.
|
||||
Queue tests cover all ROM windows, all N/S settings, ARM/Thumb widths, partial words, capacity, and restart.
|
||||
They check every cancellation phase around a fill, exact-head matching, data cancellation, and page/window boundaries.
|
||||
Configuration tests distinguish relevant fields from PHI/SRAM fields.
|
||||
A seeded batched-versus-single-clock comparison checks progression consistency, not independent hardware correctness.
|
||||
|
||||
Coverage includes:
|
||||
Integration tests cover RAM loads, register shifts, ROM data stalls, branch hits/misses, and PC-load refills.
|
||||
They check timed/CPU-only equivalence, debugger invalidation, IRQ entry, WAITCNT writes, DMA, HALT, and STOP.
|
||||
Failure tests cover late block loads, missing ROM bytes, failed DMA, retries, and host isolation.
|
||||
Instruction-pair matrices separately assert CPU N/S kinds and prefetch-adjusted costs.
|
||||
Existing ordered-event tests retain source/data/internal/refill ordering checks, including a full-register LDM with PC.
|
||||
Test builds retain an event log; production builds allocate no event log or queue storage on the heap.
|
||||
|
||||
- Word PC loads and the full register-list LDM sequence.
|
||||
- Thumb POP with word data and halfword target fetches.
|
||||
- ARM-to-Thumb BX, taken branches to fallthrough, and skipped conditions.
|
||||
- Swaps, register-shift internal cycles, and absence of duplicate source/refill events.
|
||||
- WAITCNT stores with old source settings and new settings on the next fetch.
|
||||
- IRQ entry after a Thumb load, including the retained non-sequential source kind.
|
||||
- Failed data accesses, late block-load failure, missing current/lookahead slots, and retry isolation.
|
||||
- Host reads, untimed CPU execution, and DMA isolation from the CPU trace.
|
||||
|
||||
## Validation
|
||||
## Validation and remaining limits
|
||||
|
||||
Workspace and core/demo tests pass in debug and release on Darwin arm64.
|
||||
Formatting, clippy, rustdoc, Python preparation tests, native ROM windows, and all graphics smoke modes pass.
|
||||
The release executable is native Mach-O arm64.
|
||||
Public ARM, Thumb, memory, and BIOS reports match the preceding sequencing change exactly, including cycles and step counts.
|
||||
Public ARM, Thumb, memory, and BIOS reports match the preceding ordered-timing change exactly, including cycles and step counts.
|
||||
Debug and release reports also match. Original CPU and timer demo traces are unchanged.
|
||||
These public checkpoints do not independently validate active prefetch timing.
|
||||
|
||||
## Next implementation work
|
||||
|
||||
Implement and test queue progress against these ordered timing events before enabling WAITCNT acceleration.
|
||||
Keep the cartridge queue separate from CPU instruction retention, BIOS protected-read history, and IWRAM local lanes.
|
||||
Resolve startup, capacity, page boundaries, partial ARM fetches, cancellation, and WAITCNT changes with independent expectations.
|
||||
DMA/idle progression and diagnostic rollback need separate tests before claiming those interactions are supported.
|
||||
Next, validate this queue against independent timing tests, especially full-buffer restart, cancellation, and page boundaries.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ The stopped counter therefore increases by five even though the final machine-cy
|
|||
- Cold current/decode fills have no separate startup timing charge.
|
||||
- BIOS retained history is not a complete refill/data-access bus latch.
|
||||
- Missing instruction bytes remain diagnostics, not hardware prefetch aborts.
|
||||
- Game Pak prefetch, per-access device updates, display contention, configurable EWRAM timing, and sub-instruction DMA arbitration remain unimplemented.
|
||||
- Per-access device updates, display contention, configurable EWRAM timing, and sub-instruction DMA arbitration remain unimplemented.
|
||||
- The later [nominal prefetch queue](gamepak-prefetch.md) can change source and target costs when bit 14 is enabled.
|
||||
|
||||
The next timing work is Game Pak prefetch with verified startup and invalidation rules.
|
||||
Game Pak prefetch followed this baseline; independent startup and invalidation timing validation remains necessary.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue