feat(cpu): retain Thumb instruction pipeline halfwords

This commit is contained in:
0xrsydn 2026-10-06 04:10:20 +07:00
commit ea055270f4
21 changed files with 535 additions and 97 deletions

View file

@ -24,7 +24,8 @@ No external source code or test ROM was imported. No physical-hardware or extern
## Implementation
`cpu/pipeline.rs` owns a private two-slot ARM continuation with its expected execution PC.
`cpu/pipeline.rs` owns a private two-slot continuation with its expected execution PC and instruction state.
The later [Thumb extension](thumb-instruction-buffer.md) uses this same structure for halfwords.
Each slot retains either a mapped word or its strict fetch error.
`Cpu` owns and clones this state; full CPU equality includes it.
@ -37,13 +38,13 @@ Successful non-refill ARM execution commits the next pair.
A successful refill into ARM instead samples T and T+4 after data transfers and status restoration.
This includes ARM/Thumb BX, ARM PC writes and loads, taken branches, exception returns, and SWI vectors.
A refill to the sequential address still discards the previous sequential pair.
Untaken branches retain the pair. Leaving ARM clears it; Thumb continues to use direct instruction reads.
Untaken branches retain the pair. A transition to Thumb now replaces it with a Thumb target pair.
Machine IRQ entry captures its vector pair immediately after exception entry.
The standalone exception APIs have no Memory argument, so they invalidate the old pair and leave the vector cold.
This distinction is explicit rather than retaining unrelated words at a coincident PC.
`Memory::fetch_arm_word` reads aligned mapped words without CPU data context, timing traces, or bus-history changes.
`Memory::fetch_instruction` reads aligned mapped instructions without CPU data context, timing traces, or bus-history changes.
Normal memory mirrors apply. Unmapped, truncated, and misaligned fetches retain their existing errors.
No BIOS-protected data value or general open-bus value can become a synthetic instruction through this path.
@ -74,7 +75,7 @@ The current instruction address still selects nominal code cost rather than a se
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 target samples do not establish complete ARM IWRAM or BIOS bus history.
Thumb buffering, instruction-state-changing compare quirks, Game Pak prefetch, and DMA arbitration remain incomplete.
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.
## Original regression coverage

View file

@ -45,7 +45,7 @@ A completed DMA WAITCNT store changes the settings before CPU resume.
## Limits
The timing model has no per-access fetch scheduling or DMA arbitration.
The later [ARM instruction buffer](arm-instruction-buffer.md) retains instruction words but does not change these nominal costs.
The later [ARM](arm-instruction-buffer.md) and [Thumb](thumb-instruction-buffer.md) instruction buffers retain instructions without changing these nominal costs.
It charges the current instruction address, not a separately scheduled PC+4/PC+8 pipeline fetch.
DMA still executes only between instructions, not between data accesses or during CPU internal cycles.

View file

@ -56,7 +56,7 @@ An accepted machine IRQ invalidates history. Failed DMA units preserve it.
The DMA extension below updates existing continuation lanes from successful IWRAM accesses.
This is not a full IWRAM bus model across all CPU states. ARM-target history and DMA-to-CPU ordering remain incomplete.
It is not a persistent instruction pipeline. Self-modifying instruction execution and exact per-access device timing remain unverified.
It is not a persistent instruction pipeline. Exact per-access device timing remains unverified.
## Thumb IWRAM refill extension
@ -77,7 +77,7 @@ 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.
The interpreter still reads Thumb instructions when it executes them; ARM now has separate instruction buffering.
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.
@ -111,8 +111,8 @@ Only complete successful units commit history, preserving the project's atomic d
Cold direct entry still has no expected continuation PC. DMA does not manufacture one, even after a word access.
An existing partially known continuation can gain lanes. IRQ entry and CPU discontinuities retain their invalidation rules.
General DMA open-bus reads, cross-state history, sub-instruction arbitration, and DMA during CPU internal cycles remain outside this subset.
This extension does not fix self-modifying Thumb instruction-buffer behavior.
The later [ARM instruction buffer](arm-instruction-buffer.md) separately retains ARM words across DMA.
This extension models bus lanes, not self-modifying instruction execution.
The later [ARM](arm-instruction-buffer.md) and [Thumb](thumb-instruction-buffer.md) instruction buffers separately retain instructions across DMA.
No physical-hardware measurements or external emulator differential runs were performed.
All regression programs are original, with a separate byte-array lane reference in the DMA matrix tests.

View file

@ -0,0 +1,85 @@
# Persistent Thumb instruction buffering
## Evidence and scope
The [ARM7TDMI pipeline description](https://developer.arm.com/documentation/dvi0027/b/arm7tdmi/instruction-pipeline) identifies fetch, decode, and execute stages.
The [technical reference manual](https://documentation-service.arm.com/static/5e8e1323fd977155116a3129) describes Thumb's two-byte instruction width and visible PC offset of four bytes.
The [Thumb BL cycle sequence](https://support.arm.com/documentation/ddi0210/c/Instruction-Cycle-Timings/Thumb-branch-with-link) distinguishes the sequential prefix from the branching suffix.
[NanoBoyAdvance at 55b5cf0a](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/arm/arm7tdmi.hh) provides an implementation cross-check:
- `Run` takes the retained current instruction and shifts the next instruction forward.
- In Thumb state, it reads the next halfword before executing the current instruction.
- `ReloadPipeline16` reads target T, then T+2. The target instruction later fetches T+4.
- `ReloadPipeline32` uses ARM's four-byte width instead.
Thus, execution at P retains the halfwords at P+2 and P+4 before instruction effects.
A store cannot replace these retained instructions. It can affect P+6, which is fetched later.
A taken PC write discards the sequential pair, even when its destination equals fallthrough.
The BL prefix retains its suffix; only the suffix refills the target pair.
This extends the [ARM instruction buffer](arm-instruction-buffer.md), not the bus scheduler.
No external source code or test ROM was imported. No physical-hardware or external-emulator differential run was performed.
## Implementation
`cpu/pipeline.rs` shares one two-slot buffer between ARM and Thumb.
Each buffer records its expected execution PC and instruction state.
Both must match before retained instructions can be reused.
Thumb halfwords are zero-extended in the slots; upper bits do not hold another instruction.
Cold execution reads the current instruction and samples the next two instructions.
Sequential execution uses retained results and samples only P+4 in Thumb state.
The prospective continuation commits only after successful execution.
Refills sample the target pair after data transfers and status restoration, using the resulting instruction state.
ARM-to-Thumb BX and saved-state exception returns therefore capture T and T+2, not T and T+4.
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.
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.
Failed execution preserves the previous buffer exactly. Newly sampled lookahead does not commit.
CPU stores, DMA transfers, and host writes never replace retained instructions automatically.
`Cpu::invalidate_pipeline()` explicitly discards retained instructions for debugger repair or attachment to different memory.
It does not clear separate BIOS or IWRAM bus history.
CPU clones own independent buffers; full CPU equality includes them.
## Separation from bus history and timing
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.
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.
Instruction-state-changing compare quirks also remain hardware-unverified.
## Original regression coverage
Original programs cover:
- CPU byte/halfword stores to P+2, P+4, and the later P+6 instruction.
- Host writes, RAM/video mirrors, and halfword/word DMA writes.
- Taken and untaken branches, PC writes to fallthrough, and standalone BL suffixes.
- BL prefix retention and suffix refill, with unchanged nominal costs.
- ARM-to-Thumb target pairs at both halfword alignments and SWI/IRQ saved-state returns.
- Failed data loads, discarded speculative advances, deferred short-ROM errors, and strict target diagnostics.
- Debugger invalidation, independent clones, PC/state mismatches, and alignment errors.
- Equal timed/untimed buffers without additional nominal cycles.
The retention regressions failed with the previous direct-fetch Thumb interpreter before implementation.
Existing bus-history tests now invalidate deliberately repaired code or repair a data address without changing the retained instruction.
Successful instruction-semantic tests compare architectural state separately; failed-step tests still compare complete CPU state.
## 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 ARM-buffer reports exactly, including nominal cycle counts.
Debug and release reports also match for the same fixture paths.
These checks do not establish full hardware pipeline or timing conformance.