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.
Files changed (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. 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