refactor(cpu): share instruction fetch samples with bus history

This commit is contained in:
0xrsydn 2026-10-06 14:12:03 +07:00
commit e630d7a0ea
18 changed files with 538 additions and 117 deletions

View file

@ -42,6 +42,9 @@ The repository is a Cargo workspace with three crates:
| `crates/gba-core/src/cpu/tests.rs` | Condition truth tables and immediate arithmetic tests |
| `crates/gba-core/src/cpu/instruction_tests.rs` | Register operations, transfers, branches, and edge cases |
| `crates/gba-core/src/memory.rs` | Memory mapping, I/O routing, write validation, and device clock |
| `crates/gba-core/src/memory/fetch.rs` | Strict instruction samples with captured region-dependent bus observations |
| `crates/gba-core/src/memory/fetch_tests.rs` | Fetch widths, bus lanes, captured refill/BIOS values, strict errors, and side-effect isolation |
| `crates/gba-core/src/cpu/fetch_history_tests.rs` | Shared sample consumption, no execution-entry rereads, and failed-step retry |
| `crates/gba-core/src/io.rs` | I/O registers, timers, HALT/STOP wake-up, next-event bounds, and IRQ latches |
| `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 |

View file

@ -99,7 +99,7 @@ Normal word rotation, odd-halfword behavior, sign extension, and load timing sti
The current interpreter implements bounded ARM and Thumb BIOS prefetch snapshots:
- Before an ARM BIOS instruction executes, sample its mapped PC+8 word without charging another data access.
- Before an ARM BIOS instruction executes, use its pipeline's new mapped PC+8 fetch without charging another data access.
- Before a Thumb BIOS instruction executes, sample the full mapped word at `(PC+4) & ~3`.
BIOS drives a 32-bit word even for a halfword fetch; both distinct lanes are retained.
- After successful execution, retain that snapshot for later protected reads. Skipped conditional instructions count as successful execution.
@ -134,7 +134,8 @@ Missing BIOS images remain unmapped. The unused-memory ARM open-bus snapshot is
The Thumb rule follows documented BIOS bus lanes and a source comparison with NanoBoyAdvance.
See [Thumb BIOS prefetch research](../research/thumb-bios-prefetch.md) for evidence, emulator differences, and test scope.
This BIOS readback model remains separate from the CPU's ARM/Thumb instruction buffer.
The BIOS latch remains separate from the CPU's ARM/Thumb instruction buffer.
Both now consume a shared fetch sample; BIOS history no longer rereads lookahead memory at execution entry.
It does not model exact refill, BIOS data-access, or DMA bus history.
The boundary diagnostic policy and precise bus timing remain hardware-unverified.
Original regressions cover synthetic images, boot, exception returns, access widths, alignment, modes, and diagnostics.

View file

@ -109,8 +109,10 @@ Do not call it for ordinary CPU/DMA stores: that would hide self-modifying-code
This is instruction buffering, not a complete timed fetch pipeline.
Samples add no nominal data accesses or device cycles. Existing refill costs and one-shot DMA resume costs remain unchanged.
Fetch-driven bus history, exact refill costs, per-access device updates, and DMA arbitration remain incomplete.
See the [ARM evidence and diagnostic policy](../research/arm-instruction-buffer.md) and [Thumb extension](../research/thumb-instruction-buffer.md).
Instruction buffering and supported bus history now consume the same mapped fetch samples.
Complete fetch-driven bus history, exact refill costs, per-access device updates, and DMA arbitration remain incomplete.
See the [ARM evidence and diagnostic policy](../research/arm-instruction-buffer.md), [Thumb extension](../research/thumb-instruction-buffer.md),
and [shared fetch samples](../research/shared-fetch-samples.md).
## ARM unused-memory data reads
@ -118,7 +120,8 @@ During ARM execution, data reads from `0x00004000..0x01ffffff` and `0x10000000..
Open bus means the bus retains previously driven data. GBATEK identifies this ARM value as the instruction word at PC+8.
These high addresses do not mirror BIOS, RAM, or cartridge addresses.
The interpreter snapshots mapped bytes at PC+8 before executing each ARM instruction.
The interpreter samples mapped bytes at PC+8 before executing each ARM instruction.
That one fetch supplies both the new instruction-buffer entry and the unused-memory snapshot.
All unused-memory reads within that instruction use the same snapshot.
Byte and halfword reads select their addressed lanes; the CPU applies normal rotation and sign extension.
Base writeback, register aliases, block loads, PC loads, and flags retain their existing instruction semantics.
@ -162,6 +165,8 @@ For BIOS/OAM, aligned instructions expose halfwords at P+4 and P+6.
Instructions at two-byte-only alignment expose halfwords at P+2 and P+4.
The two lanes can differ. A universal repeated-halfword rule would be incorrect.
The pipeline's new fetch supplies the bus snapshot without rereading its instruction bytes.
BIOS/OAM samples include the other halfword of the aligned bus word, but retain only the selected halfword as an instruction.
Only the required mapped bytes are sampled. A 16-bit region needs two lookahead bytes, not four.
Normal memory mirrors apply to these bytes, including physical RAM/video-memory wrap within a mapped region.
If P+4 crosses a 16 MiB address-region boundary, the snapshot remains unknown.
@ -178,8 +183,8 @@ IWRAM needs prior bus lanes, including IWRAM data-read/write changes and possibl
Do not substitute P+2 for that history: it is only the usual case, not a general rule.
The original Nintendo DS also differs from GBA-family IWRAM behavior; this core targets GBA.
This remains a bounded snapshot implementation, not a pipeline or a complete bus-history model.
Refill timing, self-modifying code, DMA-to-CPU transitions, cross-state IWRAM history, unused/write-only I/O, and disabled RAM remain incomplete.
Bus history remains bounded despite sharing samples with the persistent instruction buffer.
Refill timing, general DMA-to-CPU transitions, cross-state IWRAM history, unused/write-only I/O, and disabled RAM remain incomplete.
Original regressions validate the documented formulas. No physical-hardware or independent public Thumb open-bus pass is claimed.
### Sequential IWRAM history
@ -221,8 +226,8 @@ Saved Thumb state, not the target's low bit alone, controls exception-return sam
Stack and block-load returns finish their data accesses before the target samples.
Failed instructions do not sample a target or replace committed history.
Snapshots use strict mapped reads and add no nominal data or device cycles.
They update bus history only; a separate CPU buffer retains the target instructions for execution.
Target samples use strict mapped reads and add no nominal data or device cycles.
The same captured target pair fills the CPU buffer and updates supported IWRAM history; no second target read occurs.
Host inspection does not change captured lanes. A failed target fetch remains a later instruction diagnostic.
Cold direct startup, unsupported target states/regions, and region-crossing lookahead still have no inferred refill history.
DMA between refill and arrival updates this continuation's actual IWRAM access lanes before the target+4 sample.

View file

@ -152,11 +152,12 @@ ARM/Thumb instruction-buffer research:
- [ARM7TDMI instruction pipeline](https://developer.arm.com/documentation/dvi0027/b/arm7tdmi/instruction-pipeline) and [technical reference manual](https://documentation-service.arm.com/static/5e8e1323fd977155116a3129), for fetch/decode/execute stages and the visible ARM/Thumb PC offsets.
- The pinned NanoBoyAdvance ARM7TDMI `Run`, `ReloadPipeline32`, and `ReloadPipeline16` helpers above, for retained instructions, pre-execution fetching, and target-pair refill order.
- The ARM branch cycle sequence above, for target and target+instruction-width fetches.
- [ARM Thumb branch-with-link timing](https://support.arm.com/documentation/ddi0210/c/Instruction-Cycle-Timings/Thumb-branch-with-link), for sequential prefix execution and suffix target refill.
See [persistent ARM instruction buffering](research/arm-instruction-buffer.md) and its [Thumb extension](research/thumb-instruction-buffer.md).
Instruction retention is implemented; unified fetch-driven bus history and per-access timing remain incomplete.
Instruction retention and supported bus snapshots now use [shared fetch samples](research/shared-fetch-samples.md).
That refactor reuses the pinned NanoBoyAdvance loop/refill and ares local-lane evidence above.
Complete bus history and per-access timing remain incomplete; no new hardware conformance result is claimed.
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.

View file

@ -73,7 +73,8 @@ The existing destination-based refill summaries and one-shot DMA resume timing r
The current instruction address still selects nominal code cost rather than a separately scheduled fetch address.
ARM PC+8 open bus and protected BIOS readback remain separate snapshots.
The instruction-buffer sample and those snapshots see mapped memory before instruction effects, but no unified per-access bus model exists yet.
The [shared fetch sample](shared-fetch-samples.md) supplies the instruction-buffer entry and supported bus snapshots without duplicate reads.
No unified per-access bus model exists yet.
The target samples do not establish complete ARM IWRAM or BIOS bus history.
Instruction-state-changing compare quirks, Game Pak prefetch, and DMA arbitration remain incomplete.
DMA still runs between whole instructions, not during their data accesses or internal cycles.

View file

@ -33,7 +33,8 @@ No physical-hardware measurement or external emulator differential run was perfo
## Bounded implementation
`memory/iwram_bus.rs` records lane values and a known-bit mask during consecutive Thumb instructions in IWRAM.
Before execution, the mapped PC+4 halfword updates its addressed lanes.
Before execution, the pipeline's new mapped PC+4 fetch updates its addressed lanes.
The bus-history consumer does not reread memory or reuse the current/decode instruction.
During execution, successful IWRAM bus reads and writes update the staged lanes at their actual aligned addresses and widths.
The latch sees raw bus data before CPU load rotation and sign extension.
Other-region accesses and host inspection do not change it.
@ -65,7 +66,8 @@ For Thumb destinations, those addresses are T and T+2.
[NanoBoyAdvance's refill helpers](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/arm/arm7tdmi.hh) use that order.
Its execution loop then fetches T+4 before executing the first Thumb instruction.
After a successful refill instruction, we sample the two mapped destination halfwords if both remain in Thumb IWRAM.
After a successful refill instruction, the pipeline samples the two mapped destination instructions.
The same captured halfwords update bus history if both remain in Thumb IWRAM.
The samples replace the old lane history and establish the next expected PC as T.
The next instruction's normal PC+4 sample updates one lane while the other retains the captured T+2 halfword.
The samples occur after data accesses and status restoration, so loaded PC values and saved Thumb state select the destination.
@ -76,7 +78,7 @@ Failed instructions retain the previous committed history. An unsupported or inc
Unmapped instruction targets still fail on the following fetch.
Cold direct startup and arbitrary PC changes do not manufacture a refill.
These are bus-history samples, not instruction-buffer contents or extra emulated cycles.
These captured values now supply both bus history and instruction-buffer entries, without extra emulated cycles.
ARM and Thumb execution now use a separate instruction buffer. Cold buffer filling does not seed this bus history.
Changing T+2 after the branch does not replace its captured bus value; changing T+4 before arrival affects the later sample.
Those original tests verify sample ordering, not hardware-accurate self-modifying instruction execution.

View file

@ -0,0 +1,78 @@
# Shared instruction-fetch samples
## Finding and evidence
Persistent ARM/Thumb instruction buffering exposed duplicate memory reads in the interpreter.
The pipeline sampled the new lookahead instruction, then `Memory::begin_cpu_access` reread those bytes for bus history.
Thumb IWRAM refills also read their target pair once for history and again for instruction buffering.
With instruction-boundary scheduling, those reads normally saw identical bytes. They still represented the same fetch twice.
This refactor gives instruction buffering and supported bus-history updates a common captured sample.
It does not introduce new hardware formulas or change nominal cycle costs.
Previously reviewed evidence supports this separation:
- [NanoBoyAdvance at 55b5cf0a](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/arm/arm7tdmi.hh): `Run` fetches the next instruction before execution; refill helpers fetch the target pair.
- [ares at 6f6786e0](https://github.com/ares-emulator/ares/blob/6f6786e04f0822a3475463df284f313ab8518d51/ares/gba/cpu/memory.cpp): IWRAM updates local addressed lanes; EWRAM halfword reads drive duplicated halves.
- [Thumb BIOS research](thumb-bios-prefetch.md) and [Thumb open-bus rules](../hardware/cpu.md#thumb-unused-memory-data-reads): BIOS/OAM bus observations use an aligned word, not a universally duplicated halfword.
These sources were reviewed, not copied into the emulator.
No physical-hardware measurements or external-emulator differential runs were performed.
## Sample and consumers
`memory/fetch.rs` returns an `InstructionFetch` with:
- Fetch address and instruction state.
- Strict instruction result or deferred mapped-read error.
- Supported bus-word observation, when it does not require retained IWRAM lanes.
ARM samples use the fetched word for both instruction bits and bus observation.
Thumb samples retain only their selected halfword as instruction bits.
Supported 16-bit regions duplicate that halfword for bus observation.
BIOS/OAM samples read the adjacent halfword too, without rereading the instruction bytes.
Thumb IWRAM supplies the fetched halfword to the existing lane-retaining transaction instead of creating a full bus word.
The CPU passes only its newly sampled P+8 or P+4 observation to `begin_cpu_access`.
It does not replay the bus observations of retained current/decode instructions.
The consumer performs no mapped memory read.
After successful control flow, the same target samples fill the instruction buffer and supported Thumb IWRAM refill history.
Machine IRQ entry uses this refill path; the ARM target still invalidates Thumb IWRAM continuation history.
Instruction buffers and BIOS/IWRAM latches remain separate retained state.
Sampling alone changes no CPU access context, latch, device clock, DMA resume marker, or data-timing trace.
Instruction errors discard staged history and speculative buffer advances.
A retry takes a new lookahead sample while retaining the previously committed instruction pair.
Unavailable lookahead remains diagnostic only when consumed by an instruction or a data read that needs it.
Strict instruction reads never use protected BIOS or unused-memory data fallback.
## Deliberately unchanged limits
- Cold pipeline filling does not establish previously unknown IWRAM history.
- Thumb 16 MiB region crossings remain unsupported for bus snapshots.
- ARM-wide local IWRAM history and cross-state latch ownership remain incomplete.
- BIOS history commits after successful BIOS instruction execution; target refills do not establish additional BIOS history.
- All unused-memory data reads within an instruction still use its entry snapshot.
- DMA remains scheduled between whole instructions. Sampling does not add an interleaving point.
- Current-instruction and destination-based nominal timing remain unchanged.
- Per-access device updates, Game Pak prefetch, and exact refill/arbitration timing remain incomplete.
## Original tests
CPU tests split the existing internal fetch/execute helpers and mutate lookahead bytes between them.
ARM, narrow Thumb, wide Thumb, and IWRAM tests then require the originally captured values.
Those consumption tests failed with the previous duplicate-read path.
The split is a test-only ownership check, not a hardware DMA experiment.
Memory tests use explicit expected lane values across mapped regions and both Thumb alignments.
They also check target-pair consumption after mutation, BIOS snapshot retention, strict errors, short ROMs, and data-context isolation.
Tests verify that sampling adds no timing or history effects and that failed execution preserves committed history.
Existing instruction-buffer, bus-history, exception, DMA, and nominal timing tests remain regression coverage.
## Validation result
Workspace and core/demo tests pass in debug and release on Darwin arm64.
Formatting, lint checks, rustdoc, preparation tests, native ROM windows, and graphics smoke modes pass.
Public ARM, Thumb, memory, and BIOS reports match the previous Thumb-buffer reports exactly, including nominal cycle counts.
Debug and release reports also match for the same fixture paths.
These checks validate the refactor against existing behavior, not complete hardware bus or timing conformance.

View file

@ -40,7 +40,8 @@ Do not treat agreement between all emulators, or exact hardware conformance, as
## Implementation scope
`Memory::begin_cpu_access` samples before execution, using the incoming instruction state and strict mapped bytes.
`Memory::begin_cpu_access` now consumes the pipeline's new fetch sample, using the incoming instruction state.
The [shared sampler](shared-fetch-samples.md) captures strict mapped bytes before execution.
`end_cpu_access` commits BIOS history only after success. Failed instructions preserve previous history.
Thumb execution outside BIOS neither initializes nor replaces BIOS history.
Host reads remain raw inspection. CPU data loads outside BIOS select lanes from retained history as before.

View file

@ -36,7 +36,8 @@ ARM-to-Thumb BX and saved-state exception returns therefore capture T and T+2, n
Thumb-to-ARM transitions and SWI/IRQ vectors capture ARM pairs.
`Memory::fetch_instruction` uses strict mapped reads with the selected instruction width.
A Thumb fetch reads exactly two bytes, even in a region whose separate bus snapshot uses a full word.
Its instruction result needs only two bytes in Thumb state.
The later [shared fetch sampler](shared-fetch-samples.md) also captures the adjacent halfword for BIOS/OAM bus observations.
Memory mirrors apply. Unmapped and misaligned instruction reads retain diagnostics rather than using data open bus.
Lookahead errors remain deferred until their slot executes; a branch can discard them.
@ -51,12 +52,12 @@ CPU clones own independent buffers; full CPU equality includes them.
Instruction retention and data-bus snapshots remain separate models.
Cold instruction-buffer filling does not make unknown IWRAM bus lanes known.
Successful DMA can update existing IWRAM bus lanes without changing retained CPU instructions.
Refill bus-history sampling and instruction sampling currently perform separate strict mapped reads.
Refill bus history and instruction buffering now consume the same captured target pair without a second mapped read.
Buffer samples add no nominal cycles or data accesses.
Timed and untimed execution use the same buffer logic.
Destination-based refill summaries and one-shot non-sequential CPU resume costs remain unchanged.
Unified fetch-driven bus history, exact region crossings, per-access timing, Game Pak prefetch, and sub-instruction DMA arbitration remain incomplete.
Complete fetch-driven bus history, exact region crossings, per-access timing, Game Pak prefetch, and sub-instruction DMA arbitration remain incomplete.
Instruction-state-changing compare quirks also remain hardware-unverified.
## Original regression coverage

View file

@ -28,6 +28,7 @@ Update this file when a feature lands or a limit is removed.
- Original ARM graphics demo that uses DMA, BIOS VBlank waiting, and a ROM-side IRQ callback.
- Sixteen active 32-bit CPU registers, with ARM7 mode-specific register banks.
- Persistent ARM/Thumb instruction buffering, sequential fetch retention, branch/vector target pairs, and deferred fetch diagnostics.
- Shared mapped fetch samples for instruction retention, supported open-bus values, BIOS snapshots, and Thumb IWRAM refill history.
- User, System, Supervisor, IRQ, FIQ, Abort, and Undefined modes.
- Current and saved program status registers (`CPSR`/`SPSR`), with `MRS`/`MSR` transfers.
- ARM/Thumb software interrupts (`SWI`), exception vectors, and status-restoring returns.
@ -141,7 +142,8 @@ Cold or invalidated IWRAM history remains diagnostic until all lanes are known.
Refills into Thumb IWRAM establish new target-pair history after the instruction succeeds.
Accepted machine IRQs, unsupported refills, and other execution states/regions invalidate continuation history.
Successful DMA updates existing Thumb IWRAM continuation lanes for actual IWRAM accesses, before the resumed PC+4 sample.
ARM/Thumb instruction buffering retains sequential instructions and refill target pairs, independently of these bus snapshots.
ARM/Thumb instruction buffering retains sequential instructions and refill target pairs.
Instruction buffering and supported bus history now consume the same mapped fetch samples, without sharing retained storage.
Complete ARM bus history, exact region-crossing bus behavior, general DMA handoff, and unused/write-only I/O open bus remain incomplete.
[DMA channel data latches](hardware/dma.md#retained-channel-data) support blocked-source reuse, but never replace CPU bus history.
BIOS protection uses separate retained BIOS fetch snapshots as described in [BIOS behavior](hardware/bios.md#cpu-bios-read-protection).
@ -153,7 +155,7 @@ Writes to cartridge addresses return errors instead of modeling cartridge hardwa
## Next steps
1. Connect ARM/Thumb instruction fetches to bus history with independent per-access tests.
1. Extend the shared fetch-sample model to verified cross-state bus history and region crossings.
General DMA open-bus reads and sub-instruction arbitration remain separate from retained channel data and local IWRAM lanes.
Keep unknown history diagnostic until fetch ordering and latch ownership have independent coverage.
Validate BIOS snapshots with independent hardware tests before claiming exact refill or data-access history.

View file

@ -88,6 +88,10 @@ The display-runner error test allows an already buffered branch to complete befo
Thumb instruction-buffer tests verify retained P+2/P+4 halfwords, BL prefix/suffix behavior, and state-tagged target pairs.
They also cover stores, host/DMA writes, saved-state returns, deferred errors, mirrors, debugger repair, and nominal timing.
Bus-history repair tests explicitly invalidate buffered code or repair a data address; failure checks retain full CPU equality.
Shared fetch-sample tests mutate memory between internal sampling and consumption to reject duplicate reads.
They cover ARM words, Thumb narrow/wide buses, IWRAM lanes, target pairs, BIOS retention, and retry after failure.
This test-only split is not a DMA scheduling feature. Separate tests verify strict fetch errors and no timing/history side effects.
See [shared fetch samples](research/shared-fetch-samples.md) for the unchanged hardware limits.
See [ARM instruction buffering](research/arm-instruction-buffer.md) and its [Thumb extension](research/thumb-instruction-buffer.md) for remaining limits.
The pinned public ARM ROM now reaches its completion checkpoint with r12 = 0 in debug and release builds.
The pinned public Thumb ROM also reaches its ARM-state checkpoint with r7 = 0 in both builds.