web-a2e 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clangd +5 -0
- package/.mcp.json +12 -0
- package/CLAUDE.md +362 -0
- package/CMakeLists.txt +774 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/build-wasm/generated/roms.cpp +2447 -0
- package/docker-compose.staging.yml +9 -0
- package/docs/basic-rom-disassembly.md +6663 -0
- package/docs/softswitch-comparison.md +273 -0
- package/docs/thunderclock-debug.md +89 -0
- package/examples/cube.bas +72 -0
- package/examples/hello.s +55 -0
- package/examples/scroll.s +140 -0
- package/package.json +18 -0
- package/public/assets/apple-logo-old.png +0 -0
- package/public/assets/apple-logo.png +0 -0
- package/public/assets/drive-closed-light-on.png +0 -0
- package/public/assets/drive-closed.png +0 -0
- package/public/assets/drive-open-light-on.png +0 -0
- package/public/assets/drive-open.png +0 -0
- package/public/audio-worklet.js +82 -0
- package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
- package/public/disks/ProDOS 2.4.3.po +0 -0
- package/public/disks/h32mb.2mg +0 -0
- package/public/disks/library.json +26 -0
- package/public/docs/llms/llm-assembler.txt +90 -0
- package/public/docs/llms/llm-basic-program.txt +256 -0
- package/public/docs/llms/llm-disk-drives.txt +72 -0
- package/public/docs/llms/llm-file-explorer.txt +50 -0
- package/public/docs/llms/llm-hard-drives.txt +80 -0
- package/public/docs/llms/llm-main.txt +51 -0
- package/public/docs/llms/llm-slot-configuration.txt +66 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/index.html +661 -0
- package/public/llms.txt +49 -0
- package/public/manifest.json +29 -0
- package/public/shaders/burnin.glsl +22 -0
- package/public/shaders/crt.glsl +706 -0
- package/public/shaders/edge.glsl +109 -0
- package/public/shaders/vertex.glsl +8 -0
- package/public/sw.js +186 -0
- package/roms/341-0027.bin +0 -0
- package/roms/341-0160-A-US-UK.bin +0 -0
- package/roms/341-0160-A.bin +0 -0
- package/roms/342-0273-A-US-UK.bin +0 -0
- package/roms/342-0349-B-C0-FF.bin +0 -0
- package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
- package/roms/Thunderclock Plus ROM.bin +0 -0
- package/scripts/generate_roms.sh +69 -0
- package/src/bindings/wasm_interface.cpp +1940 -0
- package/src/core/assembler/assembler.cpp +1239 -0
- package/src/core/assembler/assembler.hpp +115 -0
- package/src/core/audio/audio.cpp +160 -0
- package/src/core/audio/audio.hpp +81 -0
- package/src/core/basic/basic_detokenizer.cpp +436 -0
- package/src/core/basic/basic_detokenizer.hpp +41 -0
- package/src/core/basic/basic_tokenizer.cpp +286 -0
- package/src/core/basic/basic_tokenizer.hpp +26 -0
- package/src/core/basic/basic_tokens.hpp +295 -0
- package/src/core/cards/disk2_card.cpp +568 -0
- package/src/core/cards/disk2_card.hpp +316 -0
- package/src/core/cards/expansion_card.hpp +185 -0
- package/src/core/cards/mockingboard/ay8910.cpp +616 -0
- package/src/core/cards/mockingboard/ay8910.hpp +159 -0
- package/src/core/cards/mockingboard/via6522.cpp +530 -0
- package/src/core/cards/mockingboard/via6522.hpp +163 -0
- package/src/core/cards/mockingboard_card.cpp +312 -0
- package/src/core/cards/mockingboard_card.hpp +159 -0
- package/src/core/cards/mouse_card.cpp +654 -0
- package/src/core/cards/mouse_card.hpp +190 -0
- package/src/core/cards/smartport/block_device.cpp +202 -0
- package/src/core/cards/smartport/block_device.hpp +60 -0
- package/src/core/cards/smartport/smartport_card.cpp +603 -0
- package/src/core/cards/smartport/smartport_card.hpp +120 -0
- package/src/core/cards/thunderclock_card.cpp +237 -0
- package/src/core/cards/thunderclock_card.hpp +122 -0
- package/src/core/cpu/cpu6502.cpp +1609 -0
- package/src/core/cpu/cpu6502.hpp +203 -0
- package/src/core/debug/condition_evaluator.cpp +470 -0
- package/src/core/debug/condition_evaluator.hpp +87 -0
- package/src/core/disassembler/disassembler.cpp +552 -0
- package/src/core/disassembler/disassembler.hpp +171 -0
- package/src/core/disk-image/disk_image.hpp +267 -0
- package/src/core/disk-image/dsk_disk_image.cpp +827 -0
- package/src/core/disk-image/dsk_disk_image.hpp +204 -0
- package/src/core/disk-image/gcr_encoding.cpp +147 -0
- package/src/core/disk-image/gcr_encoding.hpp +78 -0
- package/src/core/disk-image/woz_disk_image.cpp +1049 -0
- package/src/core/disk-image/woz_disk_image.hpp +343 -0
- package/src/core/emulator.cpp +2126 -0
- package/src/core/emulator.hpp +434 -0
- package/src/core/filesystem/dos33.cpp +178 -0
- package/src/core/filesystem/dos33.hpp +66 -0
- package/src/core/filesystem/pascal.cpp +262 -0
- package/src/core/filesystem/pascal.hpp +87 -0
- package/src/core/filesystem/prodos.cpp +369 -0
- package/src/core/filesystem/prodos.hpp +119 -0
- package/src/core/input/keyboard.cpp +227 -0
- package/src/core/input/keyboard.hpp +111 -0
- package/src/core/mmu/mmu.cpp +1387 -0
- package/src/core/mmu/mmu.hpp +236 -0
- package/src/core/types.hpp +196 -0
- package/src/core/video/video.cpp +680 -0
- package/src/core/video/video.hpp +156 -0
- package/src/css/assembler-editor.css +1617 -0
- package/src/css/base.css +470 -0
- package/src/css/basic-debugger.css +791 -0
- package/src/css/basic-editor.css +792 -0
- package/src/css/controls.css +783 -0
- package/src/css/cpu-debugger.css +1413 -0
- package/src/css/debug-base.css +160 -0
- package/src/css/debug-windows.css +6455 -0
- package/src/css/disk-drives.css +406 -0
- package/src/css/documentation.css +392 -0
- package/src/css/file-explorer.css +867 -0
- package/src/css/hard-drive.css +180 -0
- package/src/css/layout.css +217 -0
- package/src/css/memory-windows.css +798 -0
- package/src/css/modals.css +510 -0
- package/src/css/monitor.css +425 -0
- package/src/css/release-notes.css +101 -0
- package/src/css/responsive.css +400 -0
- package/src/css/rule-builder.css +340 -0
- package/src/css/save-states.css +201 -0
- package/src/css/settings-windows.css +1231 -0
- package/src/css/window-switcher.css +150 -0
- package/src/js/agent/agent-manager.js +643 -0
- package/src/js/agent/agent-tools.js +293 -0
- package/src/js/agent/agent-version-tools.js +131 -0
- package/src/js/agent/assembler-tools.js +357 -0
- package/src/js/agent/basic-program-tools.js +894 -0
- package/src/js/agent/disk-tools.js +417 -0
- package/src/js/agent/file-explorer-tools.js +269 -0
- package/src/js/agent/index.js +13 -0
- package/src/js/agent/main-tools.js +222 -0
- package/src/js/agent/slot-tools.js +303 -0
- package/src/js/agent/smartport-tools.js +257 -0
- package/src/js/agent/window-tools.js +80 -0
- package/src/js/audio/audio-driver.js +417 -0
- package/src/js/audio/audio-worklet.js +85 -0
- package/src/js/audio/index.js +8 -0
- package/src/js/config/default-layout.js +34 -0
- package/src/js/config/version.js +8 -0
- package/src/js/data/apple2-rom-routines.js +577 -0
- package/src/js/debug/assembler-editor-window.js +2993 -0
- package/src/js/debug/basic-breakpoint-manager.js +529 -0
- package/src/js/debug/basic-program-parser.js +436 -0
- package/src/js/debug/basic-program-window.js +2594 -0
- package/src/js/debug/basic-variable-inspector.js +447 -0
- package/src/js/debug/breakpoint-manager.js +472 -0
- package/src/js/debug/cpu-debugger-window.js +2396 -0
- package/src/js/debug/index.js +22 -0
- package/src/js/debug/label-manager.js +238 -0
- package/src/js/debug/memory-browser-window.js +416 -0
- package/src/js/debug/memory-heat-map-window.js +481 -0
- package/src/js/debug/memory-map-window.js +206 -0
- package/src/js/debug/mockingboard-window.js +882 -0
- package/src/js/debug/mouse-card-window.js +355 -0
- package/src/js/debug/rule-builder-window.js +648 -0
- package/src/js/debug/soft-switch-window.js +458 -0
- package/src/js/debug/stack-viewer-window.js +221 -0
- package/src/js/debug/symbols.js +416 -0
- package/src/js/debug/trace-panel.js +291 -0
- package/src/js/debug/zero-page-watch-window.js +297 -0
- package/src/js/disk-manager/disk-drives-window.js +212 -0
- package/src/js/disk-manager/disk-operations.js +284 -0
- package/src/js/disk-manager/disk-persistence.js +301 -0
- package/src/js/disk-manager/disk-surface-renderer.js +388 -0
- package/src/js/disk-manager/drive-sounds.js +139 -0
- package/src/js/disk-manager/hard-drive-manager.js +481 -0
- package/src/js/disk-manager/hard-drive-persistence.js +187 -0
- package/src/js/disk-manager/hard-drive-window.js +57 -0
- package/src/js/disk-manager/index.js +890 -0
- package/src/js/display/display-settings-window.js +383 -0
- package/src/js/display/index.js +10 -0
- package/src/js/display/screen-window.js +342 -0
- package/src/js/display/webgl-renderer.js +705 -0
- package/src/js/file-explorer/disassembler.js +574 -0
- package/src/js/file-explorer/dos33.js +266 -0
- package/src/js/file-explorer/file-viewer.js +359 -0
- package/src/js/file-explorer/index.js +1261 -0
- package/src/js/file-explorer/prodos.js +549 -0
- package/src/js/file-explorer/utils.js +67 -0
- package/src/js/help/documentation-window.js +1096 -0
- package/src/js/help/index.js +10 -0
- package/src/js/help/release-notes-window.js +85 -0
- package/src/js/help/release-notes.js +612 -0
- package/src/js/input/gamepad-handler.js +176 -0
- package/src/js/input/index.js +12 -0
- package/src/js/input/input-handler.js +396 -0
- package/src/js/input/joystick-window.js +404 -0
- package/src/js/input/mouse-handler.js +99 -0
- package/src/js/input/text-selection.js +462 -0
- package/src/js/main.js +653 -0
- package/src/js/state/index.js +15 -0
- package/src/js/state/save-states-window.js +393 -0
- package/src/js/state/state-manager.js +409 -0
- package/src/js/state/state-persistence.js +218 -0
- package/src/js/ui/confirm.js +43 -0
- package/src/js/ui/disk-drive-positioner.js +347 -0
- package/src/js/ui/reminder-controller.js +129 -0
- package/src/js/ui/slot-configuration-window.js +560 -0
- package/src/js/ui/theme-manager.js +61 -0
- package/src/js/ui/toast.js +44 -0
- package/src/js/ui/ui-controller.js +897 -0
- package/src/js/ui/window-switcher.js +275 -0
- package/src/js/utils/basic-autocomplete.js +832 -0
- package/src/js/utils/basic-highlighting.js +473 -0
- package/src/js/utils/basic-tokenizer.js +153 -0
- package/src/js/utils/basic-tokens.js +117 -0
- package/src/js/utils/constants.js +28 -0
- package/src/js/utils/indexeddb-helper.js +225 -0
- package/src/js/utils/merlin-editor-support.js +905 -0
- package/src/js/utils/merlin-highlighting.js +551 -0
- package/src/js/utils/storage.js +125 -0
- package/src/js/utils/string-utils.js +19 -0
- package/src/js/utils/wasm-memory.js +54 -0
- package/src/js/windows/base-window.js +690 -0
- package/src/js/windows/index.js +9 -0
- package/src/js/windows/window-manager.js +375 -0
- package/tests/catch2/catch.hpp +17976 -0
- package/tests/common/basic_program_builder.cpp +119 -0
- package/tests/common/basic_program_builder.hpp +209 -0
- package/tests/common/disk_image_builder.cpp +444 -0
- package/tests/common/disk_image_builder.hpp +141 -0
- package/tests/common/test_helpers.hpp +118 -0
- package/tests/gcr/gcr-test.cpp +142 -0
- package/tests/integration/check-rom.js +70 -0
- package/tests/integration/compare-boot.js +239 -0
- package/tests/integration/crash-trace.js +102 -0
- package/tests/integration/disk-boot-test.js +264 -0
- package/tests/integration/memory-crash.js +108 -0
- package/tests/integration/nibble-read-test.js +249 -0
- package/tests/integration/phase-test.js +159 -0
- package/tests/integration/test_emulator.cpp +291 -0
- package/tests/integration/test_emulator_basic.cpp +91 -0
- package/tests/integration/test_emulator_debug.cpp +344 -0
- package/tests/integration/test_emulator_disk.cpp +153 -0
- package/tests/integration/test_emulator_state.cpp +163 -0
- package/tests/klaus/6502_functional_test.bin +0 -0
- package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
- package/tests/klaus/klaus_6502_test.cpp +184 -0
- package/tests/klaus/klaus_65c02_test.cpp +197 -0
- package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
- package/tests/thunderclock/thunderclock_test.cpp +550 -0
- package/tests/unit/test_assembler.cpp +521 -0
- package/tests/unit/test_audio.cpp +196 -0
- package/tests/unit/test_ay8910.cpp +311 -0
- package/tests/unit/test_basic_detokenizer.cpp +265 -0
- package/tests/unit/test_basic_tokenizer.cpp +382 -0
- package/tests/unit/test_block_device.cpp +259 -0
- package/tests/unit/test_condition_evaluator.cpp +219 -0
- package/tests/unit/test_cpu6502.cpp +1301 -0
- package/tests/unit/test_cpu_addressing.cpp +361 -0
- package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
- package/tests/unit/test_cpu_decimal.cpp +166 -0
- package/tests/unit/test_cpu_interrupts.cpp +285 -0
- package/tests/unit/test_disassembler.cpp +323 -0
- package/tests/unit/test_disk2_card.cpp +330 -0
- package/tests/unit/test_dos33.cpp +273 -0
- package/tests/unit/test_dsk_disk_image.cpp +315 -0
- package/tests/unit/test_expansion_card.cpp +178 -0
- package/tests/unit/test_gcr_encoding.cpp +232 -0
- package/tests/unit/test_keyboard.cpp +262 -0
- package/tests/unit/test_mmu.cpp +555 -0
- package/tests/unit/test_mmu_slots.cpp +323 -0
- package/tests/unit/test_mockingboard.cpp +352 -0
- package/tests/unit/test_mouse_card.cpp +386 -0
- package/tests/unit/test_pascal.cpp +248 -0
- package/tests/unit/test_prodos.cpp +259 -0
- package/tests/unit/test_smartport_card.cpp +321 -0
- package/tests/unit/test_thunderclock.cpp +354 -0
- package/tests/unit/test_via6522.cpp +323 -0
- package/tests/unit/test_video.cpp +319 -0
- package/tests/unit/test_woz_disk_image.cpp +257 -0
- package/vite.config.js +96 -0
- package/wiki/AI-Agent.md +372 -0
- package/wiki/Architecture-Overview.md +303 -0
- package/wiki/Audio-System.md +449 -0
- package/wiki/CPU-Emulation.md +477 -0
- package/wiki/Debugger.md +516 -0
- package/wiki/Disk-Drives.md +161 -0
- package/wiki/Disk-System-Internals.md +547 -0
- package/wiki/Display-Settings.md +88 -0
- package/wiki/Expansion-Slots.md +187 -0
- package/wiki/File-Explorer.md +259 -0
- package/wiki/Getting-Started.md +156 -0
- package/wiki/Home.md +69 -0
- package/wiki/Input-Devices.md +183 -0
- package/wiki/Keyboard-Shortcuts.md +158 -0
- package/wiki/Memory-System.md +364 -0
- package/wiki/Save-States.md +172 -0
- package/wiki/Video-Rendering.md +658 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
# CPU Emulation
|
|
2
|
+
|
|
3
|
+
This page describes the 65C02 CPU emulation implementation, covering the instruction set, addressing modes, cycle timing, decimal mode, interrupts, and NMOS/CMOS behavioral differences.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Overview](#overview)
|
|
10
|
+
- [CPU Variant Selection](#cpu-variant-selection)
|
|
11
|
+
- [Registers](#registers)
|
|
12
|
+
- [Status Flags](#status-flags)
|
|
13
|
+
- [Addressing Modes](#addressing-modes)
|
|
14
|
+
- [Instruction Set](#instruction-set)
|
|
15
|
+
- [65C02 Extensions](#65c02-extensions)
|
|
16
|
+
- [Cycle Counting](#cycle-counting)
|
|
17
|
+
- [Page Crossing Penalties](#page-crossing-penalties)
|
|
18
|
+
- [Branch Timing](#branch-timing)
|
|
19
|
+
- [Read-Modify-Write Behavior](#read-modify-write-behavior)
|
|
20
|
+
- [Decimal Mode](#decimal-mode)
|
|
21
|
+
- [Interrupts](#interrupts)
|
|
22
|
+
- [JMP Indirect Bug](#jmp-indirect-bug)
|
|
23
|
+
- [Memory Access Pattern](#memory-access-pattern)
|
|
24
|
+
- [Reset Sequence](#reset-sequence)
|
|
25
|
+
- [Testing](#testing)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Overview
|
|
30
|
+
|
|
31
|
+
The CPU is implemented in `src/core/cpu/cpu6502.cpp` and `cpu6502.hpp`. It emulates the WDC 65C02 processor used in the Apple IIe Enhanced, running at 1.023 MHz. The implementation is instruction-level cycle-accurate: each instruction consumes the correct number of cycles as looked up from a 256-entry cycle table, with additional cycles added dynamically for page crossings, taken branches, and decimal mode operations.
|
|
32
|
+
|
|
33
|
+
The CPU does not access memory directly. Instead, it receives read and write callback functions at construction time that route all bus access through the MMU. This allows the MMU to handle soft switches, bank switching, and expansion card I/O transparently.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## CPU Variant Selection
|
|
38
|
+
|
|
39
|
+
The emulator supports two CPU variants:
|
|
40
|
+
|
|
41
|
+
| Variant | Enum | Description |
|
|
42
|
+
|---------|------|-------------|
|
|
43
|
+
| NMOS 6502 | `CPUVariant::NMOS_6502` | Original MOS 6502 with JMP indirect bug, invalid decimal mode flags |
|
|
44
|
+
| CMOS 65C02 | `CPUVariant::CMOS_65C02` | WDC 65C02 with fixed JMP, valid decimal flags, new instructions |
|
|
45
|
+
|
|
46
|
+
The Apple IIe Enhanced uses `CMOS_65C02`. The NMOS variant is available for testing with the Klaus Dormann 6502 test suite.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Registers
|
|
51
|
+
|
|
52
|
+
| Register | Size | Initial Value | Description |
|
|
53
|
+
|----------|------|---------------|-------------|
|
|
54
|
+
| A | 8-bit | `$00` | Accumulator |
|
|
55
|
+
| X | 8-bit | `$00` | X index register |
|
|
56
|
+
| Y | 8-bit | `$00` | Y index register |
|
|
57
|
+
| SP | 8-bit | `$FD` | Stack pointer (stack at `$0100`-`$01FF`) |
|
|
58
|
+
| PC | 16-bit | from `$FFFC` | Program counter (loaded from reset vector) |
|
|
59
|
+
| P | 8-bit | `$24` | Processor status (I and U flags set) |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Status Flags
|
|
64
|
+
|
|
65
|
+
The processor status register (P) contains 8 flags packed into a single byte:
|
|
66
|
+
|
|
67
|
+
| Bit | Flag | Hex | Description |
|
|
68
|
+
|-----|------|-----|-------------|
|
|
69
|
+
| 0 | C | `$01` | Carry -- set on unsigned overflow/borrow |
|
|
70
|
+
| 1 | Z | `$02` | Zero -- set when result is zero |
|
|
71
|
+
| 2 | I | `$04` | Interrupt disable -- masks IRQ when set |
|
|
72
|
+
| 3 | D | `$08` | Decimal -- enables BCD arithmetic in ADC/SBC |
|
|
73
|
+
| 4 | B | `$10` | Break -- distinguishes BRK from IRQ on stack |
|
|
74
|
+
| 5 | U | `$20` | Unused -- always reads as 1 |
|
|
75
|
+
| 6 | V | `$40` | Overflow -- set on signed overflow |
|
|
76
|
+
| 7 | N | `$80` | Negative -- set when bit 7 of result is 1 |
|
|
77
|
+
|
|
78
|
+
The B flag is not a physical register bit. It is pushed as 1 by BRK/PHP and as 0 by IRQ/NMI, allowing interrupt handlers to distinguish the source. The U bit is always set when the status register is pushed to the stack.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Addressing Modes
|
|
83
|
+
|
|
84
|
+
The emulator implements all standard 6502 addressing modes plus 65C02 extensions:
|
|
85
|
+
|
|
86
|
+
| Mode | Syntax | Bytes | Example | Implementation |
|
|
87
|
+
|------|--------|-------|---------|----------------|
|
|
88
|
+
| Implied | -- | 1 | `CLC` | No operand |
|
|
89
|
+
| Accumulator | A | 1 | `ASL A` | Operates on A register |
|
|
90
|
+
| Immediate | #$nn | 2 | `LDA #$42` | `addrImmediate()` -- returns PC, increments PC |
|
|
91
|
+
| Zero Page | $nn | 2 | `LDA $42` | `addrZeroPage()` -- fetches byte, address is `$00nn` |
|
|
92
|
+
| Zero Page,X | $nn,X | 2 | `LDA $42,X` | `addrZeroPageX()` -- `(fetch() + X) & $FF` (wraps) |
|
|
93
|
+
| Zero Page,Y | $nn,Y | 2 | `LDX $42,Y` | `addrZeroPageY()` -- `(fetch() + Y) & $FF` (wraps) |
|
|
94
|
+
| Absolute | $nnnn | 3 | `LDA $1234` | `addrAbsolute()` -- fetches 16-bit word |
|
|
95
|
+
| Absolute,X | $nnnn,X | 3 | `LDA $1234,X` | `addrAbsoluteX()` -- base + X, +1 cycle if page crossed |
|
|
96
|
+
| Absolute,Y | $nnnn,Y | 3 | `LDA $1234,Y` | `addrAbsoluteY()` -- base + Y, +1 cycle if page crossed |
|
|
97
|
+
| Indirect | ($nnnn) | 3 | `JMP ($1234)` | `addrIndirect()` -- reads pointer, then reads target |
|
|
98
|
+
| (Indirect,X) | ($nn,X) | 2 | `LDA ($42,X)` | `addrIndexedIndirect()` -- `(zp + X) & $FF`, reads pointer |
|
|
99
|
+
| (Indirect),Y | ($nn),Y | 2 | `LDA ($42),Y` | `addrIndirectIndexed()` -- reads pointer from zp, adds Y |
|
|
100
|
+
| (Indirect ZP) | ($nn) | 2 | `LDA ($42)` | `addrIndirectZP()` -- 65C02 only, reads pointer from zp |
|
|
101
|
+
| (Absolute,X) | ($nnnn,X) | 3 | `JMP ($1234,X)` | 65C02 only, used by JMP |
|
|
102
|
+
| ZP Relative | $nn,$rr | 3 | `BBR0 $42,$rr` | 65C02 only, used by BBR/BBS |
|
|
103
|
+
| Relative | $rr | 2 | `BEQ $rr` | Signed offset from PC+2 |
|
|
104
|
+
|
|
105
|
+
Zero page addressing always wraps within the `$00`-`$FF` range. For `(Indirect,X)`, both the base and pointer read wrap within zero page.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Instruction Set
|
|
110
|
+
|
|
111
|
+
### Load/Store
|
|
112
|
+
|
|
113
|
+
| Opcode | Instruction | Modes |
|
|
114
|
+
|--------|-------------|-------|
|
|
115
|
+
| LDA | Load A | imm, zp, zp,X, abs, abs,X, abs,Y, (zp,X), (zp),Y, (zp) |
|
|
116
|
+
| LDX | Load X | imm, zp, zp,Y, abs, abs,Y |
|
|
117
|
+
| LDY | Load Y | imm, zp, zp,X, abs, abs,X |
|
|
118
|
+
| STA | Store A | zp, zp,X, abs, abs,X, abs,Y, (zp,X), (zp),Y, (zp) |
|
|
119
|
+
| STX | Store X | zp, zp,Y, abs |
|
|
120
|
+
| STY | Store Y | zp, zp,X, abs |
|
|
121
|
+
| STZ | Store Zero | zp, zp,X, abs, abs,X (65C02) |
|
|
122
|
+
|
|
123
|
+
### Arithmetic
|
|
124
|
+
|
|
125
|
+
| Opcode | Instruction | Description |
|
|
126
|
+
|--------|-------------|-------------|
|
|
127
|
+
| ADC | Add with Carry | A = A + M + C (decimal mode aware) |
|
|
128
|
+
| SBC | Subtract with Carry | A = A - M - !C (decimal mode aware) |
|
|
129
|
+
| INC | Increment Memory | M = M + 1 |
|
|
130
|
+
| DEC | Decrement Memory | M = M - 1 |
|
|
131
|
+
| INC A | Increment A | A = A + 1 (65C02) |
|
|
132
|
+
| DEC A | Decrement A | A = A - 1 (65C02) |
|
|
133
|
+
| INX/INY | Increment X/Y | X/Y = X/Y + 1 |
|
|
134
|
+
| DEX/DEY | Decrement X/Y | X/Y = X/Y - 1 |
|
|
135
|
+
|
|
136
|
+
### Logic
|
|
137
|
+
|
|
138
|
+
| Opcode | Instruction | Description |
|
|
139
|
+
|--------|-------------|-------------|
|
|
140
|
+
| AND | Bitwise AND | A = A & M |
|
|
141
|
+
| ORA | Bitwise OR | A = A \| M |
|
|
142
|
+
| EOR | Bitwise XOR | A = A ^ M |
|
|
143
|
+
| BIT | Bit Test | Z = !(A & M), N = M.7, V = M.6 |
|
|
144
|
+
|
|
145
|
+
### Shift/Rotate
|
|
146
|
+
|
|
147
|
+
| Opcode | Instruction | Description |
|
|
148
|
+
|--------|-------------|-------------|
|
|
149
|
+
| ASL | Arithmetic Shift Left | C <- [7..0] <- 0 |
|
|
150
|
+
| LSR | Logical Shift Right | 0 -> [7..0] -> C |
|
|
151
|
+
| ROL | Rotate Left | C <- [7..0] <- C |
|
|
152
|
+
| ROR | Rotate Right | C -> [7..0] -> C |
|
|
153
|
+
|
|
154
|
+
### Compare
|
|
155
|
+
|
|
156
|
+
| Opcode | Instruction | Description |
|
|
157
|
+
|--------|-------------|-------------|
|
|
158
|
+
| CMP | Compare A | Sets C, Z, N from A - M |
|
|
159
|
+
| CPX | Compare X | Sets C, Z, N from X - M |
|
|
160
|
+
| CPY | Compare Y | Sets C, Z, N from Y - M |
|
|
161
|
+
|
|
162
|
+
### Branch
|
|
163
|
+
|
|
164
|
+
All branches are relative with a signed 8-bit offset. Base timing is 2 cycles; +1 if taken, +1 more if page crossed.
|
|
165
|
+
|
|
166
|
+
| Opcode | Instruction | Condition |
|
|
167
|
+
|--------|-------------|-----------|
|
|
168
|
+
| BPL | Branch if Plus | N = 0 |
|
|
169
|
+
| BMI | Branch if Minus | N = 1 |
|
|
170
|
+
| BVC | Branch if Overflow Clear | V = 0 |
|
|
171
|
+
| BVS | Branch if Overflow Set | V = 1 |
|
|
172
|
+
| BCC | Branch if Carry Clear | C = 0 |
|
|
173
|
+
| BCS | Branch if Carry Set | C = 1 |
|
|
174
|
+
| BNE | Branch if Not Equal | Z = 0 |
|
|
175
|
+
| BEQ | Branch if Equal | Z = 1 |
|
|
176
|
+
| BRA | Branch Always | always (65C02) |
|
|
177
|
+
|
|
178
|
+
### Jump/Subroutine
|
|
179
|
+
|
|
180
|
+
| Opcode | Hex | Description |
|
|
181
|
+
|--------|-----|-------------|
|
|
182
|
+
| JMP abs | `$4C` | Jump to absolute address |
|
|
183
|
+
| JMP (ind) | `$6C` | Jump indirect |
|
|
184
|
+
| JMP (abs,X) | `$7C` | Jump absolute indexed indirect (65C02) |
|
|
185
|
+
| JSR | `$20` | Push PC-1, jump to subroutine |
|
|
186
|
+
| RTS | `$60` | Pull PC, increment, return from subroutine |
|
|
187
|
+
| RTI | `$40` | Pull P and PC, return from interrupt |
|
|
188
|
+
|
|
189
|
+
### Stack
|
|
190
|
+
|
|
191
|
+
| Opcode | Hex | Description |
|
|
192
|
+
|--------|-----|-------------|
|
|
193
|
+
| PHA | `$48` | Push A |
|
|
194
|
+
| PLA | `$68` | Pull A (updates N, Z) |
|
|
195
|
+
| PHP | `$08` | Push P (with B and U set) |
|
|
196
|
+
| PLP | `$28` | Pull P (B cleared, U set) |
|
|
197
|
+
| PHX | `$DA` | Push X (65C02) |
|
|
198
|
+
| PLX | `$FA` | Pull X (65C02) |
|
|
199
|
+
| PHY | `$5A` | Push Y (65C02) |
|
|
200
|
+
| PLY | `$7A` | Pull Y (65C02) |
|
|
201
|
+
|
|
202
|
+
### Transfer
|
|
203
|
+
|
|
204
|
+
| Opcode | Instruction | Description |
|
|
205
|
+
|--------|-------------|-------------|
|
|
206
|
+
| TAX | A -> X | Updates N, Z |
|
|
207
|
+
| TAY | A -> Y | Updates N, Z |
|
|
208
|
+
| TXA | X -> A | Updates N, Z |
|
|
209
|
+
| TYA | Y -> A | Updates N, Z |
|
|
210
|
+
| TSX | SP -> X | Updates N, Z |
|
|
211
|
+
| TXS | X -> SP | No flag changes |
|
|
212
|
+
|
|
213
|
+
### Flag Operations
|
|
214
|
+
|
|
215
|
+
| Opcode | Instruction | Description |
|
|
216
|
+
|--------|-------------|-------------|
|
|
217
|
+
| CLC | Clear Carry | C = 0 |
|
|
218
|
+
| SEC | Set Carry | C = 1 |
|
|
219
|
+
| CLI | Clear Interrupt | I = 0 |
|
|
220
|
+
| SEI | Set Interrupt | I = 1 |
|
|
221
|
+
| CLV | Clear Overflow | V = 0 |
|
|
222
|
+
| CLD | Clear Decimal | D = 0 |
|
|
223
|
+
| SED | Set Decimal | D = 1 |
|
|
224
|
+
|
|
225
|
+
### BRK
|
|
226
|
+
|
|
227
|
+
`BRK` ($00) pushes PC+2 and P (with B set) to the stack, sets I, and jumps to the IRQ vector at `$FFFE`/`$FFFF`. On the 65C02, the D flag is also cleared.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 65C02 Extensions
|
|
232
|
+
|
|
233
|
+
The emulator implements the full WDC 65C02 instruction set, including Rockwell/WDC extensions:
|
|
234
|
+
|
|
235
|
+
### New Instructions
|
|
236
|
+
|
|
237
|
+
| Instruction | Opcodes | Description |
|
|
238
|
+
|-------------|---------|-------------|
|
|
239
|
+
| STZ | `$64`, `$74`, `$9C`, `$9E` | Store zero to memory |
|
|
240
|
+
| BRA | `$80` | Unconditional branch |
|
|
241
|
+
| PHX/PLX | `$DA`/`$FA` | Push/pull X register |
|
|
242
|
+
| PHY/PLY | `$5A`/`$7A` | Push/pull Y register |
|
|
243
|
+
| INC A | `$1A` | Increment accumulator |
|
|
244
|
+
| DEC A | `$3A` | Decrement accumulator |
|
|
245
|
+
| TRB | `$14`, `$1C` | Test and Reset Bits (M = M & ~A, Z from A & M) |
|
|
246
|
+
| TSB | `$04`, `$0C` | Test and Set Bits (M = M \| A, Z from A & M) |
|
|
247
|
+
|
|
248
|
+
### New Addressing Modes
|
|
249
|
+
|
|
250
|
+
| Instruction | Mode | Description |
|
|
251
|
+
|-------------|------|-------------|
|
|
252
|
+
| LDA/STA/etc. (zp) | Zero Page Indirect | `$B2`, `$92`, etc. |
|
|
253
|
+
| JMP (abs,X) | Absolute Indexed Indirect | `$7C` |
|
|
254
|
+
| BIT #imm | Immediate BIT | `$89` -- only sets Z, not N or V |
|
|
255
|
+
| BIT zp,X / abs,X | Extended BIT | `$34`, `$3C` |
|
|
256
|
+
|
|
257
|
+
### Rockwell/WDC Bit Instructions
|
|
258
|
+
|
|
259
|
+
| Instruction | Opcodes | Description |
|
|
260
|
+
|-------------|---------|-------------|
|
|
261
|
+
| RMB0-RMB7 | `$07`-`$77` (step `$10`) | Reset (clear) bit n in zero page |
|
|
262
|
+
| SMB0-SMB7 | `$87`-`$F7` (step `$10`) | Set bit n in zero page |
|
|
263
|
+
| BBR0-BBR7 | `$0F`-`$7F` (step `$10`) | Branch if bit n in zero page is clear |
|
|
264
|
+
| BBS0-BBS7 | `$8F`-`$FF` (step `$10`) | Branch if bit n in zero page is set |
|
|
265
|
+
|
|
266
|
+
BBR/BBS use a 3-byte encoding: opcode, zero page address, relative offset. The bit number is encoded in bits 4-6 of the opcode.
|
|
267
|
+
|
|
268
|
+
### WAI and STP
|
|
269
|
+
|
|
270
|
+
`WAI` (`$CB`) and `STP` (`$DB`) are recognized but treated as no-ops in the current implementation.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Cycle Counting
|
|
275
|
+
|
|
276
|
+
Each instruction's base cycle count is stored in a 256-entry lookup table (`CYCLE_TABLE`). The `executeInstruction()` method loads the base count, executes the opcode, adds any dynamic penalties, then commits the total to `totalCycles_`.
|
|
277
|
+
|
|
278
|
+
The `getTotalCycles()` method returns cycle counts that account for mid-instruction timing. During instruction execution (when `cycleCount_ > 0`), it returns the cycle of the last bus access rather than the final cycle. This matters for soft switch callbacks that need to know when the effective memory operation occurred.
|
|
279
|
+
|
|
280
|
+
```cpp
|
|
281
|
+
uint64_t getTotalCycles() const {
|
|
282
|
+
return cycleCount_ > 0 ? totalCycles_ + cycleCount_ - 1 : totalCycles_;
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Page Crossing Penalties
|
|
289
|
+
|
|
290
|
+
Indexed addressing modes (Absolute,X / Absolute,Y / (Indirect),Y) add one extra cycle when the indexing causes a page boundary crossing (i.e., the high byte of the effective address differs from the high byte of the base address).
|
|
291
|
+
|
|
292
|
+
For **read** operations (LDA, CMP, ADC, etc.), the page crossing check is enabled (`checkPage = true`), adding +1 cycle.
|
|
293
|
+
|
|
294
|
+
For **write** and **read-modify-write** operations (STA, ASL abs,X, etc.), the page crossing check is disabled (`checkPage = false`) because the 6502 always takes the extra cycle on these operations regardless of whether a page is actually crossed.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Branch Timing
|
|
299
|
+
|
|
300
|
+
Branch instructions have variable timing:
|
|
301
|
+
|
|
302
|
+
- **Not taken**: 2 cycles (base)
|
|
303
|
+
- **Taken, same page**: 3 cycles (+1)
|
|
304
|
+
- **Taken, page crossed**: 4 cycles (+2)
|
|
305
|
+
|
|
306
|
+
```cpp
|
|
307
|
+
void CPU6502::branch(bool condition) {
|
|
308
|
+
int8_t offset = static_cast<int8_t>(fetch());
|
|
309
|
+
if (condition) {
|
|
310
|
+
uint16_t oldPC = pc_;
|
|
311
|
+
pc_ += offset;
|
|
312
|
+
cycleCount_++;
|
|
313
|
+
if ((oldPC & 0xFF00) != (pc_ & 0xFF00)) {
|
|
314
|
+
cycleCount_++; // Page crossing penalty
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Read-Modify-Write Behavior
|
|
323
|
+
|
|
324
|
+
Read-modify-write instructions (ASL, LSR, ROL, ROR, INC, DEC on memory) have different bus behavior between the NMOS and CMOS variants:
|
|
325
|
+
|
|
326
|
+
- **NMOS 6502**: Read value, write original value back (dummy write), write new value. This "double write" can trigger side effects in I/O registers.
|
|
327
|
+
- **CMOS 65C02**: Read value, dummy read (re-read), write new value. The dummy read avoids the double-write problem.
|
|
328
|
+
|
|
329
|
+
```cpp
|
|
330
|
+
// Example: ASL zero page
|
|
331
|
+
addr = addrZeroPage();
|
|
332
|
+
value = read(addr);
|
|
333
|
+
if (variant_ == CPUVariant::CMOS_65C02)
|
|
334
|
+
read(addr); // 65C02: dummy read
|
|
335
|
+
else
|
|
336
|
+
write(addr, value); // NMOS: dummy write of old value
|
|
337
|
+
write(addr, opASL(value));
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Decimal Mode
|
|
343
|
+
|
|
344
|
+
When the D flag is set, ADC and SBC perform BCD (Binary-Coded Decimal) arithmetic. The emulator implements full BCD correction for both operations.
|
|
345
|
+
|
|
346
|
+
### ADC in Decimal Mode
|
|
347
|
+
|
|
348
|
+
1. Compute the binary overflow flag (V) from the uncorrected binary result
|
|
349
|
+
2. Add low nibbles with carry; if > 9, add 6 (BCD correction) and carry into high nibble
|
|
350
|
+
3. Add high nibbles; if > 9, add 6 and set carry
|
|
351
|
+
4. On 65C02: N and Z flags are set from the final BCD result (valid flags)
|
|
352
|
+
5. On 65C02: takes one extra cycle compared to binary mode
|
|
353
|
+
|
|
354
|
+
### SBC in Decimal Mode
|
|
355
|
+
|
|
356
|
+
1. Compute binary carry and overflow flags from the uncorrected binary result
|
|
357
|
+
2. Subtract low nibbles with borrow; if < 0, subtract 6 and borrow from high nibble
|
|
358
|
+
3. Subtract high nibbles; if < 0, subtract 6
|
|
359
|
+
4. N and Z from the final BCD result
|
|
360
|
+
5. On 65C02: takes one extra cycle
|
|
361
|
+
|
|
362
|
+
### NMOS vs CMOS Decimal Behavior
|
|
363
|
+
|
|
364
|
+
On the NMOS 6502, the N and Z flags after decimal ADC/SBC are derived from the intermediate binary result and may not be meaningful. The 65C02 corrects this by computing N and Z from the final BCD-adjusted result. The implementation uses the same code path for both variants, with the 65C02 producing correct flags by virtue of updating N/Z after BCD correction.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Interrupts
|
|
369
|
+
|
|
370
|
+
### IRQ (Maskable Interrupt)
|
|
371
|
+
|
|
372
|
+
- Checked at the start of each `executeInstruction()` call
|
|
373
|
+
- Only fires if `irqPending_` is true and the I flag is clear
|
|
374
|
+
- Pushes PC and P (with B clear, U set) to the stack
|
|
375
|
+
- Sets the I flag (and clears D on 65C02)
|
|
376
|
+
- Loads PC from the IRQ vector at `$FFFE`/`$FFFF`
|
|
377
|
+
- Takes 7 cycles
|
|
378
|
+
- The `irq()` method sets `irqPending_ = true` (used by VIA timer callbacks)
|
|
379
|
+
- A level-triggered IRQ status callback can be registered for peripherals like the Mockingboard VIA
|
|
380
|
+
|
|
381
|
+
### NMI (Non-Maskable Interrupt)
|
|
382
|
+
|
|
383
|
+
- Edge-triggered: only fires once per assertion
|
|
384
|
+
- `nmi()` sets `nmiPending_` and `nmiEdge_` (only if `nmiEdge_` is not already set)
|
|
385
|
+
- Same push/vector sequence as IRQ but uses vector at `$FFFA`/`$FFFB`
|
|
386
|
+
- Cannot be masked by the I flag
|
|
387
|
+
- Takes 7 cycles, clears D on 65C02
|
|
388
|
+
|
|
389
|
+
### BRK (Software Interrupt)
|
|
390
|
+
|
|
391
|
+
- Pushes PC+2 (skip past the BRK signature byte)
|
|
392
|
+
- Pushes P with B flag set (distinguishes from hardware IRQ)
|
|
393
|
+
- Jumps to the IRQ vector at `$FFFE`/`$FFFF`
|
|
394
|
+
- 65C02 clears D flag
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## JMP Indirect Bug
|
|
399
|
+
|
|
400
|
+
The NMOS 6502 has a well-known bug with `JMP ($xxFF)`: when the low byte of the pointer address is `$FF`, the high byte is fetched from `$xx00` instead of `$xx00+$0100`. The emulator faithfully reproduces this behavior for the NMOS variant:
|
|
401
|
+
|
|
402
|
+
```cpp
|
|
403
|
+
uint16_t CPU6502::addrIndirect() {
|
|
404
|
+
uint16_t ptr = fetchWord();
|
|
405
|
+
if (variant_ == CPUVariant::NMOS_6502 && (ptr & 0xFF) == 0xFF) {
|
|
406
|
+
return read(ptr) | (read(ptr & 0xFF00) << 8); // Bug: wraps within page
|
|
407
|
+
}
|
|
408
|
+
return read(ptr) | (read(ptr + 1) << 8); // 65C02: correct behavior
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
The 65C02 fixes this bug -- `JMP ($xxFF)` correctly reads the high byte from `$xx00+$0100`.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Memory Access Pattern
|
|
417
|
+
|
|
418
|
+
The CPU never accesses memory directly. All reads and writes go through callback functions provided at construction:
|
|
419
|
+
|
|
420
|
+
```cpp
|
|
421
|
+
CPU6502(ReadCallback read, WriteCallback write, CPUVariant variant);
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Where:
|
|
425
|
+
- `ReadCallback = std::function<uint8_t(uint16_t)>`
|
|
426
|
+
- `WriteCallback = std::function<void(uint16_t, uint8_t)>`
|
|
427
|
+
|
|
428
|
+
In the emulator, these callbacks route through the MMU:
|
|
429
|
+
|
|
430
|
+
```cpp
|
|
431
|
+
cpu_ = std::make_unique<CPU6502>(
|
|
432
|
+
[this](uint16_t addr) { return cpuRead(addr); },
|
|
433
|
+
[this](uint16_t addr, uint8_t val) { cpuWrite(addr, val); },
|
|
434
|
+
CPUVariant::CMOS_65C02);
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
This design means the CPU has no knowledge of bank switching, soft switches, or expansion card I/O. The MMU handles all address decoding transparently.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Reset Sequence
|
|
442
|
+
|
|
443
|
+
On reset (`reset()` method):
|
|
444
|
+
|
|
445
|
+
1. A, X, Y are set to `$00`
|
|
446
|
+
2. SP is set to `$FD` (the 6502 decrements SP by 3 during reset but does not write)
|
|
447
|
+
3. P is set to `$24` (I flag set, U always set)
|
|
448
|
+
4. PC is loaded from the reset vector at `$FFFC`/`$FFFD`
|
|
449
|
+
5. 7 cycles are added to the total cycle count
|
|
450
|
+
6. All interrupt pending flags are cleared
|
|
451
|
+
|
|
452
|
+
A warm reset (`warmReset()` in the `Emulator` class) only resets the CPU -- memory, disk state, and expansion cards are preserved, matching the behavior of pressing Ctrl+Reset on real hardware.
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## Testing
|
|
457
|
+
|
|
458
|
+
The CPU implementation is validated against the Klaus Dormann 6502/65C02 functional test suites:
|
|
459
|
+
|
|
460
|
+
- **`klaus_6502_test`** -- Tests all NMOS 6502 instructions, addressing modes, and flags, including decimal mode
|
|
461
|
+
- **`klaus_65c02_test`** -- Tests 65C02 extended opcodes (STZ, BRA, PHX/PLX, PHY/PLY, TRB/TSB, etc.)
|
|
462
|
+
|
|
463
|
+
These tests run as native C++ executables and are executed via CTest:
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
cd build-native && ctest --verbose
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
The test runner executes the test ROM until PC reaches a known completion address or detects a stuck loop (indicating a test failure). Both tests must pass to confirm correct CPU emulation.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## See Also
|
|
474
|
+
|
|
475
|
+
- [[Architecture-Overview]] -- How the CPU fits into the emulator
|
|
476
|
+
- [[Memory-System]] -- MMU that handles CPU read/write callbacks
|
|
477
|
+
- [[Video-Rendering]] -- Scanline rendering driven by CPU cycle count
|