Emulate per-model CPU bus access sequences (RMW, dummy reads, interrupt entry) - #289
Merged
Conversation
…ine, and remove design-log references from code comments
…ead-read-write, Sather-correct language card
…ummy-read coverage
…RK padding-byte fetch
…ix a Windows CI race
|
highbyte
added a commit
that referenced
this pull request
Aug 13, 2026
…#290) Hardware IRQ/NMI entry previously consumed 0 cycles in cycle accounting; real 6502-family entry takes 7 (two dummy opcode reads, three stack pushes, two vector reads — the bus sequence itself was implemented in #289). Follow-up scheduled from that work. ## What changes - `CPU.ProcessPendingInterrupts` (and the internal boundary servicing) returns the cycles consumed: new `CPU.InterruptEntryCycles` (7) when an interrupt was serviced, else 0. - The entry cost is folded into the preceding instruction's `InstructionExecResult` (new `WithAdditionalCycles`) before statistics update, so every cycle-paced consumer sees real elapsed time with no further changes: `ExecState` totals (Apple II disk motor/speaker/game-port clocks), `LastInstructionExecResult` (Apple II / VIC-20 frame budgets and VIA ticking), evaluators, and the `InstructionExecuted` event. - The C64's post-device-tick interrupt flush explicitly ticks CIA1/CIA2 and the cartridge slot with the entry cycles and includes them in the raster advance and frame budget. The freeze-button NMI delivery deliberately ignores them (one-shot event outside frame pacing; commented). - Note: on the full `Execute` path, `NmiAcknowledging` now fires before `InstructionExecuted` (servicing moved before stats update); the only NmiAcknowledging consumer (C64) uses the minimal path and is unaffected. ## Impact Frame budgets now fit ~7 fewer CPU cycles per serviced interrupt (~0.04% per raster-IRQ frame on C64) — matching real hardware. All existing C64/VIC-20/Apple II timing tests pass unchanged; benchmarks at parity (hot path gained only a return-value check). ## Tests +4 in `CPUInterruptBoundaryTests`: IRQ entry costs 2+7 on the minimal path (result and ExecState views), NMI costs 2+7 on the full path, `ProcessPendingInterrupts` returns 7/0, and a no-interrupt control keeps plain instruction cycles. Public API note: `ProcessPendingInterrupts` changed `void` → `ulong` (source-compatible for callers ignoring the result; package is alpha).
highbyte
added a commit
that referenced
this pull request
Aug 13, 2026
…ndlers (#291) ## What Completes the CPU instruction-dispatch modernization started with the per-model descriptor tables (#288) and ordered bus sequences (#289): every opcode on both CPU models now executes through small operation cores composed per addressing mode, or bespoke static handlers where composition doesn't fit. The ~70 per-instruction classes and the interface-probing composition path are deleted. ## Changes - **Operation cores** (`InstructionCores`): one static method per operation (loads/stores, transfers, logic, compares, shifts/rotates, INC/DEC, ADC/SBC per model, TSB/TRB, flags, and all NMOS-undocumented operations), bound to addressing modes by `ComposeRead`/`ComposeStore`/`ComposeImplied`/`ComposeRmw`/`ComposeBranch`. - **Bespoke static handlers** for instructions that don't fit composition: shared (`SharedHandlers`: stack, flow, BRK, NOP), NMOS-specific (`NmosHandlers`: JMP (addr) page-wrap bug, JAM), and 65C02-specific (`CmosHandlers`: JMP variants, PHX/PHY/PLX/PLY, defined-NOP family). - **`InstructionBindings`**: the single table both models compose their 256-entry descriptor tables from, with undocumented NMOS opcodes gated by the CPU's compatibility profile at binding time. - **`InstructionList` is now a projection**: its metadata is generated from the NMOS model's descriptor table instead of the descriptor tables being composed from instruction objects — the two views can no longer drift. `InstructionDictionary`/`GetInstruction` and the `Instruction` class hierarchy are removed (pre-1.0 clean break). - **Deleted**: all `Instructions/*` classes, the four `IInstructionUses*` interfaces, `IReadModifyWriteInstruction`, `AddrModeCalcResult`, `InstructionExtraCyclesCalculator`, and the legacy table composition. Net **−4.0k lines**. ## Behavior Zero behavior change by design, verified per migration step against the previous implementation: - Byte-for-byte descriptor/metadata parity across all four compatibility profiles (guarded by test). - All test suites green on macOS and Windows (incl. Klaus Dormann functional/BCD/65C02 tests and the exhaustive RMW bus-sequence matrix). - Hot-path benchmarks at parity with the pre-migration baseline (same-machine-state pairing); allocations unchanged. - Live end-to-end check: C64 boots to BASIC and executes correctly in the Avalonia desktop app.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Makes CPU instructions perform their real per-model bus access sequences — every read and write in hardware order — so memory-mapped I/O with access side effects behaves as it does on real machines. Follow-up to the CPU model architecture PR (#288); milestone M2 of that work.
Behavioral highlights
INC $C083CPU-model observable: it unlocks the card on a 65C02 (read-read-write) but not on an NMOS 6502 (read-write-write) — exactly as on real hardware.STA $DC0D,Xwith X=0 silently acknowledges pending C64 CIA interrupts through its dummy read (covered by an acceptance test).(zp,X),(zp),Y, and 65C02(zp)pointers at$FFtake their high byte from$00(wrapping within zero page) instead of$0100— a result-level fix on both models, same class as theJMP ($xxFF)fix in Add CPU model architecture with NCR 65C02 support #288.Design
PerformsIndexedDummyReadsmodel trait; single source of truth per model); the 65C02 keeps its own read-read-write RMW handlers. No model branches on the hot path, no tracing hooks on the memory path.BusAccessRecorderbuilt purely on the production memory-mapped-I/O mechanism (Memory.MapReader/MapWriter).Deliberately deferred (documented in code)
zp,Xbase-address dummy reads (not page-cross-related) and RTI/pull stack dummies.Tests & performance
$DC0D, interrupt-entry ordering.