Add CPU model architecture with NCR 65C02 support - #288
Merged
Conversation
…posed at CPU construction
… INC/DEC A, new BIT modes, (zp) addressing, JMP (abs,X)
…t abs,X cycle change, and NMOS-facade doc comments
…ed pinned-revision test compiler; allow CI test workflow to run on demand
…nger timeout, tolerant listing-label parser, symbol addresses from listing
…yle listing lines
…model mismatch on restore
|
highbyte
added a commit
that referenced
this pull request
Aug 13, 2026
…pt entry) (#289) 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 - **Read-modify-write sequences per model**: NMOS RMW instructions (official and undocumented) perform read-write-write — the unmodified value is written back before the result. The 65C02 performs read-read-write. Exhaustively guarded by a sequence matrix test over all 66 NMOS and 28 65C02 memory-RMW opcode bytes. - **Apple II language card is now Sather-correct** (write-enable requires two consecutive odd READS; a write never completes the sequence — previously a read+write could unlock). Combined with the RMW sequences this makes `INC $C083` CPU-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. - **NMOS indexed dummy reads**: reads that cross a page touch the un-carried address first; indexed stores and RMW always do. Classic consequence now emulated: `STA $DC0D,X` with X=0 silently acknowledges pending C64 CIA interrupts through its dummy read (covered by an acceptance test). - **Zero-page pointer wrap**: `(zp,X)`, `(zp),Y`, and 65C02 `(zp)` pointers at `$FF` take their high byte from `$00` (wrapping within zero page) instead of `$0100` — a result-level fix on both models, same class as the `JMP ($xxFF)` fix in #288. - **Interrupt-entry sequences**: IRQ/NMI entry performs the two hardware dummy reads of the next opcode byte before the stack pushes and vector reads; BRK fetches its padding byte as a real bus access (pushed return address unchanged). ## Design - Sequences are composed at CPU-construction time per model (a `PerformsIndexedDummyReads` model 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. - Tests observe bus traffic via a `BusAccessRecorder` built purely on the production memory-mapped-I/O mechanism (`Memory.MapReader`/`MapWriter`). - Language-card rules verified against Sather (Understanding the Apple IIe, 5-23) via two independent emulator implementations before coding. ## Deliberately deferred (documented in code) - 65C02-specific dummy-read addresses (the CMOS part re-targeted them; no observable case yet). - `zp,X` base-address dummy reads (not page-cross-related) and RTI/pull stack dummies. - Interrupt-entry cycle accounting (entry currently consumes 0 cycles, real hardware 7) — pre-existing gap, discovered here; changing it shifts C64/VIC-20 frame timing so it needs its own decision. ## Tests & performance - ~40 new/updated tests: per-model sequence characterization, exhaustive RMW matrix, pointer-wrap, language-card model divergence, C64 `$DC0D`, interrupt-entry ordering. - All suites green incl. Klaus NMOS functional/BCD locally and the Klaus 65C02 extended-opcodes test on Windows CI. - Benchmarks: representative C64 workload and hot-path benchmarks at or below the milestone baseline after all changes (per-part measurements recorded during development; no benchmark moved ≥5%).
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.



Introduces an internal CPU model architecture and the NCR 65C02 as the first alternative CPU model, enabling 65C02 software (enhanced-Apple-IIe-era ProDOS titles) to run. Milestone M1 of the CPU model architecture feature.
Highlights
CpuModelDefinition(identity, supported profiles, traits, table factories) selected once at CPU construction. Public API unchanged;OpCodeId/InstructionListremain the NMOS facade (documented as such).CPU_Run_1000Instructions−21%, representative C64 frame workload −14% (seebenchmarks/.../RESULTS.md).JMP ($xxFF)page-wrap bug implemented (the emulator previously had CMOS behavior while presenting as NMOS) via per-byte model handler overrides.ncr65c02, the enhanced Apple IIe CPU): all 256 bytes defined (78 defined NOPs with documented sizes/cycles — no byte can jam a 65C02), 27 new opcodes (STZ,BRA,PHX/PHY/PLX/PLY,TRB/TSB,INC/DEC A, newBITmodes,(zp)addressing,JMP (abs,X)), redefined bytes ($9C= STZ abs), CMOS decimal ADC/SBC with valid N/Z flags (+1 cycle), D cleared on interrupt/BRK/reset entry, fixed linearJMP (abs)at 6 cycles, shift/rotateabs,Xat 6+p cycles.cpu-6502module v2 records the CPU model id and hard-rejects restoring onto a different model (before applying any state); v1 payloads restore asnmos6502, proven against a checked-in fixture written by the actual v1 writer. Apple II portable snapshot settings carry the CPU model (machine shape).workflow_dispatch).Test coverage
Performance
Benchmarked per milestone against a recorded baseline (Apple M1, see RESULTS.md): descriptor dispatch improved the hot path ~20%; adding the 65C02 model cost the NMOS path nothing; no new allocations (per-instruction paths remain allocation-free).
Design/history: design log feature document
cpu-models-65c02(M1 scope; M2 ordered bus accesses, M3 6510 port model, M4 port-state migration are follow-ups).