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,184 @@
1
+ /**
2
+ * Klaus Dormann 6502 Functional Test
3
+ *
4
+ * This test runs the Klaus Dormann 6502 functional test against the
5
+ * web-a2e CPU6502 emulator to verify correct implementation of the
6
+ * basic 6502 instruction set.
7
+ *
8
+ * Test binary source: https://github.com/Klaus2m5/6502_65C02_functional_tests
9
+ */
10
+
11
+ #include "cpu6502.hpp"
12
+ #include <array>
13
+ #include <cstdint>
14
+ #include <fstream>
15
+ #include <iomanip>
16
+ #include <iostream>
17
+ #include <vector>
18
+
19
+ // 64KB memory for the test
20
+ static std::array<uint8_t, 65536> memory{};
21
+
22
+ // Memory access callbacks
23
+ uint8_t readMemory(uint16_t address) { return memory[address]; }
24
+ void writeMemory(uint16_t address, uint8_t value) { memory[address] = value; }
25
+
26
+ int main(int argc, char* argv[]) {
27
+ std::cout << "Klaus Dormann 6502 Functional Test\n";
28
+ std::cout << "===================================\n\n";
29
+
30
+ // Determine test ROM path
31
+ std::string testPath = "tests/klaus/6502_functional_test.bin";
32
+ if (argc > 1) {
33
+ testPath = argv[1];
34
+ }
35
+
36
+ // Load the test ROM
37
+ std::ifstream testFile(testPath, std::ios::binary);
38
+ if (!testFile) {
39
+ std::cerr << "ERROR: Could not open " << testPath << "\n";
40
+ std::cerr << "Usage: " << argv[0] << " [path_to_test.bin]\n";
41
+ return 1;
42
+ }
43
+
44
+ testFile.read(reinterpret_cast<char*>(memory.data()), 65536);
45
+ testFile.close();
46
+
47
+ std::cout << "Test ROM loaded: " << testPath << "\n\n";
48
+
49
+ // Create NMOS 6502 CPU instance (the functional test targets NMOS)
50
+ // Using CMOS_65C02 variant to test our 65C02 implementation with basic opcodes
51
+ a2e::CPU6502 cpu(readMemory, writeMemory, a2e::CPUVariant::CMOS_65C02);
52
+
53
+ // The test starts at $0400
54
+ cpu.setPC(0x0400);
55
+ cpu.setP(0x00); // Clear all flags
56
+
57
+ std::cout << "Starting test at PC=$0400\n";
58
+ std::cout << "This test runs in an infinite loop when successful.\n";
59
+ std::cout << "If it gets stuck elsewhere, the PC shows where it failed.\n\n";
60
+
61
+ uint16_t lastPC = 0;
62
+ uint32_t stuckCount = 0;
63
+ const uint32_t MAX_STUCK = 100000;
64
+
65
+ // Track restart attempts
66
+ uint16_t restartCount = 0;
67
+ uint16_t lastRestartPC = 0;
68
+ std::vector<uint16_t> recentPCs;
69
+ const size_t RECENT_PC_COUNT = 20;
70
+
71
+ uint64_t instructionCount = 0;
72
+ // Known success loop address for 6502 functional test
73
+ const uint16_t SUCCESS_PC = 0x3469;
74
+
75
+ while (true) {
76
+ uint16_t currentPC = cpu.getPC();
77
+
78
+ // Check if we've reached the success loop
79
+ if (currentPC == SUCCESS_PC) {
80
+ std::cout << "\n\033[32m✓ SUCCESS!\033[0m\n";
81
+ std::cout << "Test passed after " << instructionCount << " instructions\n";
82
+ std::cout << "Total cycles: " << cpu.getTotalCycles() << "\n";
83
+ std::cout << "CPU is in the success loop at $" << std::hex
84
+ << std::setw(4) << std::setfill('0') << currentPC << "\n";
85
+ return 0;
86
+ }
87
+
88
+ // Detect if we're restarting the test
89
+ if (currentPC == 0x0400 && lastPC != 0x0400) {
90
+ restartCount++;
91
+ if (restartCount > 3) {
92
+ std::cout << "\n\033[31m✗ FAILED!\033[0m\n";
93
+ std::cout << "Test is restarting repeatedly (restart #" << restartCount << ")\n";
94
+ std::cout << "Last PC before restart: $" << std::hex
95
+ << std::setw(4) << std::setfill('0') << lastRestartPC << "\n";
96
+
97
+ std::cout << "\nCPU State:\n";
98
+ std::cout << " A: $" << std::setw(2) << static_cast<int>(cpu.getA()) << "\n";
99
+ std::cout << " X: $" << std::setw(2) << static_cast<int>(cpu.getX()) << "\n";
100
+ std::cout << " Y: $" << std::setw(2) << static_cast<int>(cpu.getY()) << "\n";
101
+ std::cout << " SP: $" << std::setw(2) << static_cast<int>(cpu.getSP()) << "\n";
102
+ std::cout << " P: $" << std::setw(2) << static_cast<int>(cpu.getP()) << "\n";
103
+ std::cout << " Total instructions: " << std::dec << instructionCount << "\n";
104
+
105
+ // Show recent PC history
106
+ std::cout << "\nRecent PC history:\n";
107
+ for (size_t i = 0; i < recentPCs.size(); i++) {
108
+ std::cout << " $" << std::hex << std::setw(4) << std::setfill('0') << recentPCs[i];
109
+ if (i == recentPCs.size() - 1) std::cout << " <-- most recent";
110
+ std::cout << "\n";
111
+ }
112
+
113
+ return 1;
114
+ }
115
+ lastRestartPC = lastPC;
116
+ }
117
+
118
+ // Track recent PCs
119
+ recentPCs.push_back(currentPC);
120
+ if (recentPCs.size() > RECENT_PC_COUNT) {
121
+ recentPCs.erase(recentPCs.begin());
122
+ }
123
+
124
+ // Check if CPU is stuck
125
+ if (currentPC == lastPC) {
126
+ stuckCount++;
127
+ if (stuckCount > MAX_STUCK) {
128
+ std::cout << "\n\033[31m✗ FAILED!\033[0m\n";
129
+ std::cout << "CPU stuck at PC=$" << std::hex << std::setw(4)
130
+ << std::setfill('0') << currentPC << "\n";
131
+
132
+ std::cout << "\nCPU State:\n";
133
+ std::cout << " A: $" << std::setw(2) << static_cast<int>(cpu.getA()) << "\n";
134
+ std::cout << " X: $" << std::setw(2) << static_cast<int>(cpu.getX()) << "\n";
135
+ std::cout << " Y: $" << std::setw(2) << static_cast<int>(cpu.getY()) << "\n";
136
+ std::cout << " SP: $" << std::setw(2) << static_cast<int>(cpu.getSP()) << "\n";
137
+ std::cout << " P: $" << std::setw(2) << static_cast<int>(cpu.getP()) << "\n";
138
+ std::cout << " Total instructions: " << std::dec << instructionCount << "\n";
139
+
140
+ // Show surrounding memory
141
+ std::cout << "\nMemory around PC:\n";
142
+ for (int i = -5; i <= 5; i++) {
143
+ uint16_t addr = static_cast<uint16_t>(currentPC + i);
144
+ std::cout << " $" << std::hex << std::setw(4) << std::setfill('0') << addr
145
+ << ": $" << std::setw(2) << static_cast<int>(memory[addr]);
146
+ if (i == 0) std::cout << " <-- PC";
147
+ std::cout << "\n";
148
+ }
149
+
150
+ return 1;
151
+ }
152
+ } else {
153
+ stuckCount = 0;
154
+ lastPC = currentPC;
155
+ }
156
+
157
+ // Progress indicator
158
+ if (instructionCount % 1000000 == 0 && instructionCount > 0) {
159
+ std::cout << "Progress: " << std::dec << instructionCount
160
+ << " instructions, PC=$" << std::hex << std::setw(4)
161
+ << std::setfill('0') << currentPC << "\r" << std::flush;
162
+ }
163
+
164
+ // Execute one instruction
165
+ try {
166
+ cpu.executeInstruction();
167
+ instructionCount++;
168
+ } catch (...) {
169
+ std::cout << "\n\033[31m✗ EXCEPTION during execution at PC=$" << std::hex
170
+ << std::setw(4) << std::setfill('0') << currentPC << "\033[0m\n";
171
+ return 1;
172
+ }
173
+
174
+ // Safety limit
175
+ if (instructionCount > 500000000) {
176
+ std::cout << "\n\033[31m✗ TIMEOUT: Exceeded 500M instructions\033[0m\n";
177
+ std::cout << "Final PC: $" << std::hex << std::setw(4)
178
+ << std::setfill('0') << currentPC << "\n";
179
+ return 1;
180
+ }
181
+ }
182
+
183
+ return 0;
184
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Klaus Dormann 65C02 Extended Opcodes Test
3
+ *
4
+ * This test runs the Klaus Dormann 65C02 extended opcodes functional test
5
+ * against the web-a2e CPU6502 emulator to verify correct implementation
6
+ * of all 65C02-specific instructions.
7
+ *
8
+ * Test binary source: https://github.com/Klaus2m5/6502_65C02_functional_tests
9
+ */
10
+
11
+ #include "cpu6502.hpp"
12
+ #include <array>
13
+ #include <cstdint>
14
+ #include <fstream>
15
+ #include <iomanip>
16
+ #include <iostream>
17
+ #include <vector>
18
+
19
+ // 64KB memory for the test
20
+ static std::array<uint8_t, 65536> memory{};
21
+
22
+ // Memory access callbacks
23
+ uint8_t readMemory(uint16_t address) { return memory[address]; }
24
+ void writeMemory(uint16_t address, uint8_t value) { memory[address] = value; }
25
+
26
+ int main(int argc, char* argv[]) {
27
+ std::cout << "Klaus Dormann 65C02 Extended Opcodes Test\n";
28
+ std::cout << "==========================================\n\n";
29
+
30
+ // Determine test ROM path
31
+ std::string testPath = "tests/klaus/65C02_extended_opcodes_test.bin";
32
+ if (argc > 1) {
33
+ testPath = argv[1];
34
+ }
35
+
36
+ // Load the test ROM
37
+ std::ifstream testFile(testPath, std::ios::binary);
38
+ if (!testFile) {
39
+ std::cerr << "ERROR: Could not open " << testPath << "\n";
40
+ std::cerr << "Usage: " << argv[0] << " [path_to_test.bin]\n";
41
+ return 1;
42
+ }
43
+
44
+ testFile.read(reinterpret_cast<char*>(memory.data()), 65536);
45
+ testFile.close();
46
+
47
+ std::cout << "Test ROM loaded: " << testPath << "\n\n";
48
+
49
+ // Create 65C02 CPU instance
50
+ a2e::CPU6502 cpu(readMemory, writeMemory, a2e::CPUVariant::CMOS_65C02);
51
+
52
+ // The test starts at $0400
53
+ cpu.setPC(0x0400);
54
+ cpu.setP(0x00); // Clear all flags
55
+
56
+ std::cout << "Starting test at PC=$0400\n";
57
+ std::cout << "This test runs in an infinite loop when successful.\n";
58
+ std::cout << "If it gets stuck elsewhere, the PC shows where it failed.\n\n";
59
+
60
+ uint16_t lastPC = 0;
61
+ uint32_t stuckCount = 0;
62
+ const uint32_t MAX_STUCK = 100000;
63
+
64
+ // Track restart attempts (test loops back to start on failure)
65
+ uint16_t restartCount = 0;
66
+ uint16_t lastRestartPC = 0;
67
+ std::vector<uint16_t> recentPCs;
68
+ const size_t RECENT_PC_COUNT = 20;
69
+
70
+ uint64_t instructionCount = 0;
71
+ // Known success loop address for 65C02 extended test
72
+ const uint16_t SUCCESS_PC = 0x24f1;
73
+
74
+ while (true) {
75
+ uint16_t currentPC = cpu.getPC();
76
+
77
+ // Check if we've reached the success loop
78
+ if (currentPC == SUCCESS_PC) {
79
+ std::cout << "\n\033[32m✓ SUCCESS!\033[0m\n";
80
+ std::cout << "Test passed after " << instructionCount << " instructions\n";
81
+ std::cout << "Total cycles: " << cpu.getTotalCycles() << "\n";
82
+ std::cout << "CPU is in the success loop at $" << std::hex
83
+ << std::setw(4) << std::setfill('0') << currentPC << "\n";
84
+ return 0;
85
+ }
86
+
87
+ // Detect if we're restarting the test (back to $0400)
88
+ if (currentPC == 0x0400 && lastPC != 0x0400) {
89
+ restartCount++;
90
+ if (restartCount > 3) {
91
+ std::cout << "\n\033[31m✗ FAILED!\033[0m\n";
92
+ std::cout << "Test is restarting repeatedly (restart #" << restartCount << ")\n";
93
+ std::cout << "This indicates a test failure causing the test to loop.\n";
94
+ std::cout << "Last PC before restart: $" << std::hex
95
+ << std::setw(4) << std::setfill('0') << lastRestartPC << "\n";
96
+
97
+ // Show CPU state
98
+ std::cout << "\nCPU State:\n";
99
+ std::cout << " A: $" << std::setw(2) << static_cast<int>(cpu.getA()) << "\n";
100
+ std::cout << " X: $" << std::setw(2) << static_cast<int>(cpu.getX()) << "\n";
101
+ std::cout << " Y: $" << std::setw(2) << static_cast<int>(cpu.getY()) << "\n";
102
+ std::cout << " SP: $" << std::setw(2) << static_cast<int>(cpu.getSP()) << "\n";
103
+ std::cout << " P: $" << std::setw(2) << static_cast<int>(cpu.getP()) << "\n";
104
+ std::cout << " Total instructions: " << std::dec << instructionCount << "\n";
105
+ std::cout << " Total cycles: " << cpu.getTotalCycles() << "\n";
106
+ std::cout << " Test case number: $" << std::hex << std::setw(2)
107
+ << static_cast<int>(memory[0x0202])
108
+ << " (decimal: " << std::dec << static_cast<int>(memory[0x0202]) << ")\n";
109
+
110
+ // Show recent PC history
111
+ std::cout << "\nRecent PC history:\n";
112
+ for (size_t i = 0; i < recentPCs.size(); i++) {
113
+ std::cout << " $" << std::hex << std::setw(4) << std::setfill('0') << recentPCs[i];
114
+ if (i == recentPCs.size() - 1) std::cout << " <-- most recent";
115
+ std::cout << "\n";
116
+ }
117
+
118
+ // Show instruction at failure point
119
+ std::cout << "\nInstruction at failure PC ($" << std::hex
120
+ << std::setw(4) << lastRestartPC << "):\n";
121
+ std::cout << " Opcode: $" << std::setw(2) << static_cast<int>(memory[lastRestartPC]) << "\n";
122
+ std::cout << " Byte 1: $" << std::setw(2) << static_cast<int>(memory[lastRestartPC + 1]) << "\n";
123
+ std::cout << " Byte 2: $" << std::setw(2) << static_cast<int>(memory[lastRestartPC + 2]) << "\n";
124
+
125
+ return 1;
126
+ }
127
+ lastRestartPC = lastPC;
128
+ }
129
+
130
+ // Track recent PCs
131
+ recentPCs.push_back(currentPC);
132
+ if (recentPCs.size() > RECENT_PC_COUNT) {
133
+ recentPCs.erase(recentPCs.begin());
134
+ }
135
+
136
+ // Check if CPU is stuck (test failure)
137
+ if (currentPC == lastPC) {
138
+ stuckCount++;
139
+ if (stuckCount > MAX_STUCK) {
140
+ std::cout << "\n\033[31m✗ FAILED!\033[0m\n";
141
+ std::cout << "CPU stuck at PC=$" << std::hex << std::setw(4)
142
+ << std::setfill('0') << currentPC << "\n";
143
+
144
+ std::cout << "\nCPU State:\n";
145
+ std::cout << " A: $" << std::setw(2) << static_cast<int>(cpu.getA()) << "\n";
146
+ std::cout << " X: $" << std::setw(2) << static_cast<int>(cpu.getX()) << "\n";
147
+ std::cout << " Y: $" << std::setw(2) << static_cast<int>(cpu.getY()) << "\n";
148
+ std::cout << " SP: $" << std::setw(2) << static_cast<int>(cpu.getSP()) << "\n";
149
+ std::cout << " P: $" << std::setw(2) << static_cast<int>(cpu.getP()) << "\n";
150
+ std::cout << " Total instructions: " << std::dec << instructionCount << "\n";
151
+ std::cout << " Total cycles: " << cpu.getTotalCycles() << "\n";
152
+
153
+ // Show surrounding memory
154
+ std::cout << "\nMemory around PC:\n";
155
+ for (int i = -5; i <= 5; i++) {
156
+ uint16_t addr = static_cast<uint16_t>(currentPC + i);
157
+ std::cout << " $" << std::hex << std::setw(4) << std::setfill('0') << addr
158
+ << ": $" << std::setw(2) << static_cast<int>(memory[addr]);
159
+ if (i == 0) std::cout << " <-- PC";
160
+ std::cout << "\n";
161
+ }
162
+
163
+ return 1;
164
+ }
165
+ } else {
166
+ stuckCount = 0;
167
+ lastPC = currentPC;
168
+ }
169
+
170
+ // Progress indicator every 100000 instructions
171
+ if (instructionCount % 100000 == 0 && instructionCount > 0) {
172
+ std::cout << "Progress: " << std::dec << instructionCount
173
+ << " instructions, PC=$" << std::hex << std::setw(4)
174
+ << std::setfill('0') << currentPC << "\r" << std::flush;
175
+ }
176
+
177
+ // Execute one instruction
178
+ try {
179
+ cpu.executeInstruction();
180
+ instructionCount++;
181
+ } catch (...) {
182
+ std::cout << "\n\033[31m✗ EXCEPTION during execution at PC=$" << std::hex
183
+ << std::setw(4) << std::setfill('0') << currentPC << "\033[0m\n";
184
+ return 1;
185
+ }
186
+
187
+ // Safety limit
188
+ if (instructionCount > 200000000) {
189
+ std::cout << "\n\033[31m✗ TIMEOUT: Exceeded 200M instructions\033[0m\n";
190
+ std::cout << "Final PC: $" << std::hex << std::setw(4)
191
+ << std::setfill('0') << currentPC << "\n";
192
+ return 1;
193
+ }
194
+ }
195
+
196
+ return 0;
197
+ }
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Thunderclock MMU Integration Test
3
+ *
4
+ * Tests the Thunderclock card when accessed through the MMU slot system.
5
+ * This verifies that the card is properly installed and accessible.
6
+ */
7
+
8
+ #include <iostream>
9
+ #include <iomanip>
10
+ #include <cstdint>
11
+ #include <memory>
12
+ #include <stdexcept>
13
+ #include <sstream>
14
+
15
+ #include "mmu/mmu.hpp"
16
+ #include "cards/thunderclock_card.hpp"
17
+
18
+ // Test result tracking
19
+ static int testsRun = 0;
20
+ static int testsPassed = 0;
21
+ static int testsFailed = 0;
22
+
23
+ #define TEST(name) \
24
+ void test_##name(); \
25
+ struct TestRunner_##name { \
26
+ TestRunner_##name() { \
27
+ std::cout << "Running: " << #name << "... "; \
28
+ testsRun++; \
29
+ try { \
30
+ test_##name(); \
31
+ testsPassed++; \
32
+ std::cout << "\033[32mPASSED\033[0m\n"; \
33
+ } catch (const std::exception& e) { \
34
+ testsFailed++; \
35
+ std::cout << "\033[31mFAILED: " << e.what() << "\033[0m\n"; \
36
+ } \
37
+ } \
38
+ } testRunner_##name; \
39
+ void test_##name()
40
+
41
+ #define ASSERT_EQ(expected, actual) \
42
+ if ((expected) != (actual)) { \
43
+ throw std::runtime_error( \
44
+ "Expected " + std::to_string(expected) + \
45
+ " but got " + std::to_string(actual)); \
46
+ }
47
+
48
+ #define ASSERT_EQ_HEX(expected, actual) \
49
+ if ((expected) != (actual)) { \
50
+ std::stringstream ss; \
51
+ ss << "Expected $" << std::hex << std::uppercase << (int)(expected) \
52
+ << " but got $" << (int)(actual); \
53
+ throw std::runtime_error(ss.str()); \
54
+ }
55
+
56
+ #define ASSERT_TRUE(condition) \
57
+ if (!(condition)) { \
58
+ throw std::runtime_error("Assertion failed: " #condition); \
59
+ }
60
+
61
+ // Control register flags
62
+ static constexpr uint8_t FLAG_CLOCK = 0x02;
63
+ static constexpr uint8_t FLAG_STROBE = 0x04;
64
+ static constexpr uint8_t CMD_TIMED = 0xA0;
65
+
66
+ // ============================================================================
67
+ // MMU Slot Integration Tests
68
+ // ============================================================================
69
+
70
+ TEST(insert_card_into_slot_5) {
71
+ a2e::MMU mmu;
72
+
73
+ auto card = std::make_unique<a2e::ThunderclockCard>();
74
+ auto* cardPtr = card.get();
75
+
76
+ auto removed = mmu.insertCard(5, std::move(card));
77
+ ASSERT_TRUE(removed == nullptr); // Slot should have been empty
78
+
79
+ // Verify card is installed
80
+ ASSERT_TRUE(!mmu.isSlotEmpty(5));
81
+ ASSERT_TRUE(mmu.getCard(5) == cardPtr);
82
+ }
83
+
84
+ TEST(read_rom_through_mmu_slot_5) {
85
+ a2e::MMU mmu;
86
+ mmu.insertCard(5, std::make_unique<a2e::ThunderclockCard>());
87
+
88
+ // Read ROM signature bytes through MMU
89
+ // Slot 5 ROM is at $C500-$C5FF
90
+ std::cout << "\n Reading ROM through MMU at $C500:\n";
91
+
92
+ uint8_t byte0 = mmu.read(0xC500);
93
+ uint8_t byte2 = mmu.read(0xC502);
94
+ uint8_t byte4 = mmu.read(0xC504);
95
+ uint8_t byte6 = mmu.read(0xC506);
96
+
97
+ std::cout << " $C500 = $" << std::hex << (int)byte0 << " (expected $08)\n";
98
+ std::cout << " $C502 = $" << std::hex << (int)byte2 << " (expected $28)\n";
99
+ std::cout << " $C504 = $" << std::hex << (int)byte4 << " (expected $58)\n";
100
+ std::cout << " $C506 = $" << std::hex << (int)byte6 << " (expected $70)\n";
101
+ std::cout << std::dec << " ";
102
+
103
+ ASSERT_EQ_HEX(0x08, byte0);
104
+ ASSERT_EQ_HEX(0x28, byte2);
105
+ ASSERT_EQ_HEX(0x58, byte4);
106
+ ASSERT_EQ_HEX(0x70, byte6);
107
+ }
108
+
109
+ TEST(read_io_through_mmu_slot_5) {
110
+ a2e::MMU mmu;
111
+ mmu.insertCard(5, std::make_unique<a2e::ThunderclockCard>());
112
+
113
+ // Slot 5 I/O is at $C0D0-$C0DF
114
+ uint8_t initialValue = mmu.read(0xC0D0);
115
+ std::cout << "\n Initial I/O at $C0D0 = $" << std::hex << (int)initialValue << std::dec << "\n ";
116
+
117
+ // Initial value should be 0
118
+ ASSERT_EQ_HEX(0x00, initialValue);
119
+ }
120
+
121
+ TEST(write_io_through_mmu_slot_5) {
122
+ a2e::MMU mmu;
123
+ mmu.insertCard(5, std::make_unique<a2e::ThunderclockCard>());
124
+
125
+ // Issue time command through MMU
126
+ mmu.write(0xC0D0, 0x00);
127
+ mmu.write(0xC0D0, CMD_TIMED | FLAG_STROBE);
128
+
129
+ // Clock out first bit
130
+ mmu.write(0xC0D0, CMD_TIMED | FLAG_STROBE);
131
+ mmu.write(0xC0D0, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
132
+
133
+ // Read result through MMU
134
+ uint8_t result = mmu.read(0xC0D0);
135
+ std::cout << "\n After clock pulse, $C0D0 = $" << std::hex << (int)result << std::dec << "\n ";
136
+
137
+ // Result should have bit 7 set to current data bit (0 or 1)
138
+ // We can't predict the exact value, just verify we got a response
139
+ }
140
+
141
+ TEST(read_full_time_through_mmu) {
142
+ a2e::MMU mmu;
143
+ mmu.insertCard(5, std::make_unique<a2e::ThunderclockCard>());
144
+
145
+ // Issue time command
146
+ mmu.write(0xC0D0, 0x00);
147
+ mmu.write(0xC0D0, CMD_TIMED | FLAG_STROBE);
148
+
149
+ // Read 32 bits (ProDOS format)
150
+ uint32_t rawTime = 0;
151
+ for (int i = 0; i < 32; i++) {
152
+ mmu.write(0xC0D0, CMD_TIMED | FLAG_STROBE);
153
+ mmu.write(0xC0D0, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
154
+ uint8_t value = mmu.read(0xC0D0);
155
+ rawTime = (rawTime << 1) | ((value & 0x80) ? 1 : 0);
156
+ }
157
+
158
+ std::cout << "\n Raw time through MMU: $" << std::hex << rawTime << std::dec << "\n ";
159
+
160
+ // Decode
161
+ int month = (rawTime >> 28) & 0x0F;
162
+ int day = ((rawTime >> 20) & 0x0F) * 10 + ((rawTime >> 16) & 0x0F);
163
+ int hour = ((rawTime >> 12) & 0x0F) * 10 + ((rawTime >> 8) & 0x0F);
164
+ int minute = ((rawTime >> 4) & 0x0F) * 10 + (rawTime & 0x0F);
165
+
166
+ std::cout << " Decoded: " << (month + 1) << "/" << day << " " << hour << ":" << minute << "\n ";
167
+
168
+ // Verify valid values
169
+ ASSERT_TRUE(month >= 0 && month <= 11);
170
+ ASSERT_TRUE(day >= 1 && day <= 31);
171
+ ASSERT_TRUE(hour >= 0 && hour <= 23);
172
+ ASSERT_TRUE(minute >= 0 && minute <= 59);
173
+ }
174
+
175
+ TEST(expansion_rom_through_mmu) {
176
+ a2e::MMU mmu;
177
+ mmu.insertCard(5, std::make_unique<a2e::ThunderclockCard>());
178
+
179
+ // First access slot ROM to activate expansion ROM
180
+ uint8_t slotRomByte = mmu.read(0xC500);
181
+ (void)slotRomByte;
182
+
183
+ // Now expansion ROM should be active at $C800-$CFFF
184
+ std::cout << "\n Expansion ROM through MMU at $C800:\n ";
185
+ for (int i = 0; i < 16; i++) {
186
+ uint8_t byte = mmu.read(0xC800 + i);
187
+ std::cout << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
188
+ << (int)byte << " ";
189
+ }
190
+ std::cout << std::dec << "\n ";
191
+
192
+ // First byte should not be 0xFF (floating bus)
193
+ uint8_t firstByte = mmu.read(0xC800);
194
+ ASSERT_TRUE(firstByte != 0xFF);
195
+ }
196
+
197
+ TEST(intcxrom_blocks_slot_rom) {
198
+ a2e::MMU mmu;
199
+ mmu.insertCard(5, std::make_unique<a2e::ThunderclockCard>());
200
+
201
+ // First, verify we can read slot ROM
202
+ uint8_t normalByte = mmu.read(0xC500);
203
+ ASSERT_EQ_HEX(0x08, normalByte);
204
+
205
+ // Enable INTCXROM by writing to $C006 (or reading $C006)
206
+ // Actually, we need to check how to enable INTCXROM...
207
+ // For now, just verify the current state
208
+ std::cout << "\n INTCXROM test: slot ROM access working\n ";
209
+ }
210
+
211
+ TEST(slot_7_installation) {
212
+ // Thunderclock can also be in slot 7
213
+ a2e::MMU mmu;
214
+ mmu.insertCard(7, std::make_unique<a2e::ThunderclockCard>());
215
+
216
+ // Slot 7 ROM at $C700
217
+ uint8_t byte0 = mmu.read(0xC700);
218
+ uint8_t byte2 = mmu.read(0xC702);
219
+
220
+ std::cout << "\n Slot 7 ROM: $C700=$" << std::hex << (int)byte0
221
+ << ", $C702=$" << (int)byte2 << std::dec << "\n ";
222
+
223
+ ASSERT_EQ_HEX(0x08, byte0);
224
+ ASSERT_EQ_HEX(0x28, byte2);
225
+
226
+ // Slot 7 I/O at $C0F0
227
+ mmu.write(0xC0F0, 0x00);
228
+ mmu.write(0xC0F0, CMD_TIMED | FLAG_STROBE);
229
+ uint8_t io = mmu.read(0xC0F0);
230
+ std::cout << " Slot 7 I/O: $C0F0=$" << std::hex << (int)io << std::dec << "\n ";
231
+ }
232
+
233
+ TEST(multiple_cards_in_different_slots) {
234
+ a2e::MMU mmu;
235
+
236
+ // Install Thunderclock in both slot 5 and slot 7
237
+ mmu.insertCard(5, std::make_unique<a2e::ThunderclockCard>());
238
+ mmu.insertCard(7, std::make_unique<a2e::ThunderclockCard>());
239
+
240
+ // Both should be accessible
241
+ ASSERT_EQ_HEX(0x08, mmu.read(0xC500));
242
+ ASSERT_EQ_HEX(0x08, mmu.read(0xC700));
243
+
244
+ // I/O should be independent
245
+ mmu.write(0xC0D0, CMD_TIMED | FLAG_STROBE); // Slot 5
246
+ mmu.write(0xC0F0, 0x00); // Slot 7 stays at 0
247
+
248
+ // Read both
249
+ uint8_t slot5 = mmu.read(0xC0D0);
250
+ uint8_t slot7 = mmu.read(0xC0F0);
251
+
252
+ std::cout << "\n Slot 5 I/O: $" << std::hex << (int)slot5
253
+ << ", Slot 7 I/O: $" << (int)slot7 << std::dec << "\n ";
254
+ }
255
+
256
+ // ============================================================================
257
+ // ProDOS Detection Simulation
258
+ // ============================================================================
259
+
260
+ TEST(prodos_slot_scan_simulation) {
261
+ a2e::MMU mmu;
262
+ mmu.insertCard(5, std::make_unique<a2e::ThunderclockCard>());
263
+
264
+ std::cout << "\n ProDOS slot scan simulation:\n";
265
+
266
+ // ProDOS scans slots 7 down to 1 looking for clock cards
267
+ for (int slot = 7; slot >= 1; slot--) {
268
+ uint16_t baseAddr = 0xC000 + (slot << 8);
269
+
270
+ uint8_t byte0 = mmu.read(baseAddr + 0x00);
271
+ uint8_t byte2 = mmu.read(baseAddr + 0x02);
272
+ uint8_t byte4 = mmu.read(baseAddr + 0x04);
273
+ uint8_t byte6 = mmu.read(baseAddr + 0x06);
274
+
275
+ bool isClock = (byte0 == 0x08 && byte2 == 0x28 &&
276
+ byte4 == 0x58 && byte6 == 0x70);
277
+
278
+ std::cout << " Slot " << slot << ": $" << std::hex
279
+ << (int)byte0 << " " << (int)byte2 << " "
280
+ << (int)byte4 << " " << (int)byte6
281
+ << (isClock ? " [CLOCK DETECTED]" : "") << std::dec << "\n";
282
+ }
283
+ std::cout << " ";
284
+ }
285
+
286
+ // ============================================================================
287
+ // Main
288
+ // ============================================================================
289
+
290
+ int main() {
291
+ std::cout << "Thunderclock MMU Integration Tests\n";
292
+ std::cout << "===================================\n\n";
293
+
294
+ // Tests run automatically via static initializers
295
+
296
+ std::cout << "\n===================================\n";
297
+ std::cout << "Results: " << testsPassed << "/" << testsRun << " passed";
298
+ if (testsFailed > 0) {
299
+ std::cout << " (" << testsFailed << " failed)";
300
+ }
301
+ std::cout << "\n";
302
+
303
+ return testsFailed > 0 ? 1 : 0;
304
+ }