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,285 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_cpu_interrupts.cpp - 65C02 interrupt handling tests
|
|
3
|
+
*
|
|
4
|
+
* Tests BRK, IRQ, NMI behavior, RTI, and level-triggered IRQ
|
|
5
|
+
* via the IRQ status callback mechanism.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#define CATCH_CONFIG_MAIN
|
|
9
|
+
#include "catch.hpp"
|
|
10
|
+
#include "test_helpers.hpp"
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// BRK Instruction
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
TEST_CASE("BRK instruction behavior", "[cpu][interrupt]") {
|
|
17
|
+
|
|
18
|
+
SECTION("BRK pushes PC+2 onto stack") {
|
|
19
|
+
test::CPUTestFixture f;
|
|
20
|
+
f.mem.setIRQVector(0x1000);
|
|
21
|
+
// NOP at handler to prevent undefined behavior
|
|
22
|
+
f.mem.loadProgram(0x1000, {0xEA});
|
|
23
|
+
f.loadAndReset(0x0400, {0x00, 0xEA}); // BRK, padding
|
|
24
|
+
uint8_t spBefore = f.cpu->getSP();
|
|
25
|
+
f.cpu->executeInstruction();
|
|
26
|
+
|
|
27
|
+
// BRK pushes PC+2 (address after BRK + signature byte)
|
|
28
|
+
uint8_t retHi = f.mem[0x0100 + spBefore];
|
|
29
|
+
uint8_t retLo = f.mem[0x0100 + spBefore - 1];
|
|
30
|
+
uint16_t retAddr = (retHi << 8) | retLo;
|
|
31
|
+
REQUIRE(retAddr == 0x0402);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
SECTION("BRK pushes P with B flag set") {
|
|
35
|
+
test::CPUTestFixture f;
|
|
36
|
+
f.mem.setIRQVector(0x1000);
|
|
37
|
+
f.mem.loadProgram(0x1000, {0xEA});
|
|
38
|
+
f.loadAndReset(0x0400, {0x00, 0xEA});
|
|
39
|
+
uint8_t spBefore = f.cpu->getSP();
|
|
40
|
+
f.cpu->executeInstruction();
|
|
41
|
+
|
|
42
|
+
// P is pushed after the two PC bytes, at SP+3 from final SP
|
|
43
|
+
uint8_t pushedP = f.mem[0x0100 + spBefore - 2];
|
|
44
|
+
REQUIRE((pushedP & a2e::FLAG_B) != 0); // B flag set in pushed P
|
|
45
|
+
REQUIRE((pushedP & a2e::FLAG_U) != 0); // U flag always set
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
SECTION("BRK jumps to IRQ vector") {
|
|
49
|
+
test::CPUTestFixture f;
|
|
50
|
+
f.mem.setIRQVector(0x1000);
|
|
51
|
+
f.mem.loadProgram(0x1000, {0xEA});
|
|
52
|
+
f.loadAndReset(0x0400, {0x00, 0xEA});
|
|
53
|
+
f.cpu->executeInstruction();
|
|
54
|
+
REQUIRE(f.cpu->getPC() == 0x1000);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
SECTION("BRK sets I flag") {
|
|
58
|
+
test::CPUTestFixture f;
|
|
59
|
+
f.mem.setIRQVector(0x1000);
|
|
60
|
+
f.mem.loadProgram(0x1000, {0xEA});
|
|
61
|
+
f.loadAndReset(0x0400, {0x00, 0xEA});
|
|
62
|
+
f.cpu->setFlag(a2e::FLAG_I, false);
|
|
63
|
+
f.cpu->executeInstruction();
|
|
64
|
+
REQUIRE(f.cpu->getFlag(a2e::FLAG_I) == true);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
SECTION("BRK clears D flag on 65C02") {
|
|
68
|
+
test::CPUTestFixture f;
|
|
69
|
+
f.mem.setIRQVector(0x1000);
|
|
70
|
+
f.mem.loadProgram(0x1000, {0xEA});
|
|
71
|
+
// SED; BRK
|
|
72
|
+
f.loadAndReset(0x0400, {0xF8, 0x00, 0xEA});
|
|
73
|
+
test::runInstructions(*f.cpu, 2);
|
|
74
|
+
// On 65C02, BRK clears D flag
|
|
75
|
+
REQUIRE(f.cpu->getFlag(a2e::FLAG_D) == false);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// IRQ - Maskable Interrupt
|
|
81
|
+
// ============================================================================
|
|
82
|
+
|
|
83
|
+
TEST_CASE("IRQ behavior", "[cpu][interrupt]") {
|
|
84
|
+
|
|
85
|
+
SECTION("IRQ fires when I flag is clear") {
|
|
86
|
+
test::CPUTestFixture f;
|
|
87
|
+
f.mem.setIRQVector(0x2000);
|
|
88
|
+
f.mem.loadProgram(0x2000, {0xEA}); // NOP at handler
|
|
89
|
+
// CLI; NOP; NOP
|
|
90
|
+
f.loadAndReset(0x0400, {0x58, 0xEA, 0xEA});
|
|
91
|
+
f.cpu->executeInstruction(); // CLI
|
|
92
|
+
|
|
93
|
+
// Trigger IRQ
|
|
94
|
+
f.cpu->irq();
|
|
95
|
+
f.cpu->executeInstruction(); // This NOP should complete, then IRQ fires
|
|
96
|
+
f.cpu->executeInstruction(); // Process the IRQ
|
|
97
|
+
|
|
98
|
+
REQUIRE(f.cpu->getPC() == 0x2001); // One instruction into handler (NOP executed)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
SECTION("IRQ does not fire when I flag is set") {
|
|
102
|
+
test::CPUTestFixture f;
|
|
103
|
+
f.mem.setIRQVector(0x2000);
|
|
104
|
+
f.mem.loadProgram(0x2000, {0xEA});
|
|
105
|
+
// SEI; NOP; NOP
|
|
106
|
+
f.loadAndReset(0x0400, {0x78, 0xEA, 0xEA});
|
|
107
|
+
f.cpu->executeInstruction(); // SEI
|
|
108
|
+
|
|
109
|
+
f.cpu->irq();
|
|
110
|
+
f.cpu->executeInstruction(); // NOP at $0401
|
|
111
|
+
f.cpu->executeInstruction(); // NOP at $0402
|
|
112
|
+
|
|
113
|
+
// Should still be executing main code, not at handler
|
|
114
|
+
REQUIRE(f.cpu->getPC() == 0x0403);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
SECTION("IRQ pushes PC and P with B flag clear") {
|
|
118
|
+
test::CPUTestFixture f;
|
|
119
|
+
f.mem.setIRQVector(0x2000);
|
|
120
|
+
f.mem.loadProgram(0x2000, {0xEA, 0xEA});
|
|
121
|
+
// CLI; NOP (will be interrupted after this)
|
|
122
|
+
f.loadAndReset(0x0400, {0x58, 0xEA, 0xEA});
|
|
123
|
+
f.cpu->executeInstruction(); // CLI
|
|
124
|
+
f.cpu->irq();
|
|
125
|
+
uint8_t spBefore = f.cpu->getSP();
|
|
126
|
+
f.cpu->executeInstruction(); // NOP completes
|
|
127
|
+
f.cpu->executeInstruction(); // IRQ serviced
|
|
128
|
+
|
|
129
|
+
// P pushed during IRQ should have B flag CLEAR (unlike BRK)
|
|
130
|
+
uint8_t pushedP = f.mem[0x0100 + spBefore - 2];
|
|
131
|
+
REQUIRE((pushedP & a2e::FLAG_B) == 0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
SECTION("IRQ sets I flag") {
|
|
135
|
+
test::CPUTestFixture f;
|
|
136
|
+
f.mem.setIRQVector(0x2000);
|
|
137
|
+
f.mem.loadProgram(0x2000, {0xEA});
|
|
138
|
+
f.loadAndReset(0x0400, {0x58, 0xEA}); // CLI; NOP
|
|
139
|
+
f.cpu->executeInstruction(); // CLI
|
|
140
|
+
f.cpu->irq();
|
|
141
|
+
f.cpu->executeInstruction(); // NOP
|
|
142
|
+
f.cpu->executeInstruction(); // IRQ serviced
|
|
143
|
+
|
|
144
|
+
REQUIRE(f.cpu->getFlag(a2e::FLAG_I) == true);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ============================================================================
|
|
149
|
+
// NMI - Non-Maskable Interrupt
|
|
150
|
+
// ============================================================================
|
|
151
|
+
|
|
152
|
+
TEST_CASE("NMI behavior", "[cpu][interrupt]") {
|
|
153
|
+
|
|
154
|
+
SECTION("NMI fires regardless of I flag") {
|
|
155
|
+
test::CPUTestFixture f;
|
|
156
|
+
f.mem.setNMIVector(0x3000);
|
|
157
|
+
f.mem.loadProgram(0x3000, {0xEA, 0xEA});
|
|
158
|
+
// SEI; NOP (NMI should still fire even with I set)
|
|
159
|
+
f.loadAndReset(0x0400, {0x78, 0xEA, 0xEA});
|
|
160
|
+
f.cpu->executeInstruction(); // SEI
|
|
161
|
+
f.cpu->nmi();
|
|
162
|
+
f.cpu->executeInstruction(); // NOP completes
|
|
163
|
+
f.cpu->executeInstruction(); // NMI serviced
|
|
164
|
+
|
|
165
|
+
// Should be at NMI handler
|
|
166
|
+
REQUIRE(f.cpu->getPC() == 0x3001);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
SECTION("NMI pushes PC and P") {
|
|
170
|
+
test::CPUTestFixture f;
|
|
171
|
+
f.mem.setNMIVector(0x3000);
|
|
172
|
+
f.mem.loadProgram(0x3000, {0xEA, 0xEA});
|
|
173
|
+
f.loadAndReset(0x0400, {0xEA, 0xEA}); // NOP; NOP
|
|
174
|
+
uint8_t spBefore = f.cpu->getSP();
|
|
175
|
+
f.cpu->nmi();
|
|
176
|
+
f.cpu->executeInstruction(); // NOP completes
|
|
177
|
+
f.cpu->executeInstruction(); // NMI serviced
|
|
178
|
+
|
|
179
|
+
// SP should have moved down by 3 (PCH, PCL, P)
|
|
180
|
+
REQUIRE(f.cpu->getSP() == static_cast<uint8_t>(spBefore - 3));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
SECTION("NMI jumps to NMI vector") {
|
|
184
|
+
test::CPUTestFixture f;
|
|
185
|
+
f.mem.setNMIVector(0x3000);
|
|
186
|
+
f.mem.loadProgram(0x3000, {0xEA});
|
|
187
|
+
f.loadAndReset(0x0400, {0xEA}); // NOP
|
|
188
|
+
f.cpu->nmi();
|
|
189
|
+
f.cpu->executeInstruction(); // NOP completes
|
|
190
|
+
f.cpu->executeInstruction(); // NMI serviced
|
|
191
|
+
REQUIRE(f.cpu->getPC() == 0x3001); // After executing NOP at $3000
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============================================================================
|
|
196
|
+
// IRQ Status Callback (Level-Triggered IRQs)
|
|
197
|
+
// ============================================================================
|
|
198
|
+
|
|
199
|
+
TEST_CASE("IRQ status callback for level-triggered IRQs", "[cpu][interrupt]") {
|
|
200
|
+
|
|
201
|
+
SECTION("IRQ status callback triggers repeated interrupts while active") {
|
|
202
|
+
test::CPUTestFixture f;
|
|
203
|
+
bool irqActive = true;
|
|
204
|
+
|
|
205
|
+
f.mem.setIRQVector(0x2000);
|
|
206
|
+
// Handler: increment counter memory location, RTI
|
|
207
|
+
// LDA $10; CLC; ADC #$01; STA $10; RTI
|
|
208
|
+
f.mem.loadProgram(0x2000, {0xA5, 0x10, 0x18, 0x69, 0x01, 0x85, 0x10, 0x40});
|
|
209
|
+
f.mem[0x10] = 0x00;
|
|
210
|
+
|
|
211
|
+
// Main: CLI; NOP loop
|
|
212
|
+
f.loadAndReset(0x0400, {0x58, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA,
|
|
213
|
+
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA});
|
|
214
|
+
|
|
215
|
+
f.cpu->setIRQStatusCallback([&irqActive]() { return irqActive; });
|
|
216
|
+
|
|
217
|
+
// Run CLI
|
|
218
|
+
f.cpu->executeInstruction();
|
|
219
|
+
|
|
220
|
+
// Trigger IRQ and run enough instructions for it to be serviced and return
|
|
221
|
+
f.cpu->irq();
|
|
222
|
+
test::runInstructions(*f.cpu, 20);
|
|
223
|
+
|
|
224
|
+
// Counter should have been incremented at least once
|
|
225
|
+
REQUIRE(f.mem[0x10] >= 1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ============================================================================
|
|
230
|
+
// RTI - Return from Interrupt
|
|
231
|
+
// ============================================================================
|
|
232
|
+
|
|
233
|
+
TEST_CASE("RTI instruction", "[cpu][interrupt]") {
|
|
234
|
+
|
|
235
|
+
SECTION("RTI restores P and PC from stack") {
|
|
236
|
+
test::CPUTestFixture f;
|
|
237
|
+
f.mem.setIRQVector(0x2000);
|
|
238
|
+
// Handler: LDA #$42; RTI
|
|
239
|
+
f.mem.loadProgram(0x2000, {0xA9, 0x42, 0x40});
|
|
240
|
+
|
|
241
|
+
// Main: CLC; CLI; NOP; LDX #$99
|
|
242
|
+
f.loadAndReset(0x0400, {0x18, 0x58, 0xEA, 0xA2, 0x99});
|
|
243
|
+
f.cpu->executeInstruction(); // CLC
|
|
244
|
+
f.cpu->executeInstruction(); // CLI
|
|
245
|
+
|
|
246
|
+
uint8_t pBeforeIRQ = f.cpu->getP();
|
|
247
|
+
f.cpu->irq();
|
|
248
|
+
f.cpu->executeInstruction(); // NOP completes
|
|
249
|
+
f.cpu->executeInstruction(); // IRQ serviced, now at handler
|
|
250
|
+
|
|
251
|
+
// Execute handler: LDA #$42
|
|
252
|
+
f.cpu->executeInstruction();
|
|
253
|
+
REQUIRE(f.cpu->getA() == 0x42);
|
|
254
|
+
|
|
255
|
+
// RTI should restore PC and P
|
|
256
|
+
f.cpu->executeInstruction(); // RTI
|
|
257
|
+
// PC should be back to where we were interrupted
|
|
258
|
+
// After RTI, should execute LDX #$99
|
|
259
|
+
f.cpu->executeInstruction(); // LDX #$99
|
|
260
|
+
REQUIRE(f.cpu->getX() == 0x99);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
SECTION("RTI restores flags correctly") {
|
|
264
|
+
test::CPUTestFixture f;
|
|
265
|
+
f.mem.setIRQVector(0x2000);
|
|
266
|
+
// Handler: SEC; RTI (modifies carry, but RTI restores original flags)
|
|
267
|
+
f.mem.loadProgram(0x2000, {0x38, 0x40});
|
|
268
|
+
|
|
269
|
+
// Main: CLC; CLI; NOP
|
|
270
|
+
f.loadAndReset(0x0400, {0x18, 0x58, 0xEA, 0xEA});
|
|
271
|
+
f.cpu->executeInstruction(); // CLC -> C=0
|
|
272
|
+
f.cpu->executeInstruction(); // CLI
|
|
273
|
+
|
|
274
|
+
f.cpu->irq();
|
|
275
|
+
// IRQ is serviced at the start of the next executeInstruction call,
|
|
276
|
+
// before fetching any opcode. So the IRQ fires immediately.
|
|
277
|
+
f.cpu->executeInstruction(); // IRQ serviced (pushes PC and P, jumps to handler)
|
|
278
|
+
|
|
279
|
+
f.cpu->executeInstruction(); // SEC -> C=1 in handler
|
|
280
|
+
REQUIRE(f.cpu->getFlag(a2e::FLAG_C) == true);
|
|
281
|
+
|
|
282
|
+
f.cpu->executeInstruction(); // RTI -> restores original P with C=0
|
|
283
|
+
REQUIRE(f.cpu->getFlag(a2e::FLAG_C) == false);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_disassembler.cpp - Unit tests for 65C02 disassembler
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#define CATCH_CONFIG_MAIN
|
|
6
|
+
#include "catch.hpp"
|
|
7
|
+
|
|
8
|
+
#include "disassembler.hpp"
|
|
9
|
+
|
|
10
|
+
#include <cstring>
|
|
11
|
+
|
|
12
|
+
using namespace a2e;
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// disassembleInstruction tests
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
TEST_CASE("disassembleInstruction NOP (0xEA)", "[disasm][single]") {
|
|
19
|
+
uint8_t data[] = {0xEA};
|
|
20
|
+
auto instr = disassembleInstruction(data, sizeof(data), 0x1000);
|
|
21
|
+
|
|
22
|
+
CHECK(instr.address == 0x1000);
|
|
23
|
+
CHECK(instr.length == 1);
|
|
24
|
+
CHECK(instr.opcode == 0xEA);
|
|
25
|
+
CHECK(std::string(instr.mnemonic) == "NOP");
|
|
26
|
+
CHECK(instr.mode == static_cast<uint8_t>(AddrMode::IMP));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
TEST_CASE("disassembleInstruction LDA immediate (0xA9)", "[disasm][single]") {
|
|
30
|
+
uint8_t data[] = {0xA9, 0x42};
|
|
31
|
+
auto instr = disassembleInstruction(data, sizeof(data), 0x2000);
|
|
32
|
+
|
|
33
|
+
CHECK(instr.address == 0x2000);
|
|
34
|
+
CHECK(instr.length == 2);
|
|
35
|
+
CHECK(instr.opcode == 0xA9);
|
|
36
|
+
CHECK(instr.operand1 == 0x42);
|
|
37
|
+
CHECK(std::string(instr.mnemonic) == "LDA");
|
|
38
|
+
CHECK(instr.mode == static_cast<uint8_t>(AddrMode::IMM));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
TEST_CASE("disassembleInstruction JMP absolute (0x4C)", "[disasm][single]") {
|
|
42
|
+
uint8_t data[] = {0x4C, 0x00, 0xC0};
|
|
43
|
+
auto instr = disassembleInstruction(data, sizeof(data), 0x0300);
|
|
44
|
+
|
|
45
|
+
CHECK(instr.address == 0x0300);
|
|
46
|
+
CHECK(instr.length == 3);
|
|
47
|
+
CHECK(instr.opcode == 0x4C);
|
|
48
|
+
CHECK(instr.operand1 == 0x00);
|
|
49
|
+
CHECK(instr.operand2 == 0xC0);
|
|
50
|
+
CHECK(instr.target == 0xC000);
|
|
51
|
+
CHECK(std::string(instr.mnemonic) == "JMP");
|
|
52
|
+
CHECK(instr.mode == static_cast<uint8_t>(AddrMode::ABS));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
TEST_CASE("disassembleInstruction BRK (0x00)", "[disasm][single]") {
|
|
56
|
+
uint8_t data[] = {0x00};
|
|
57
|
+
auto instr = disassembleInstruction(data, sizeof(data), 0x0400);
|
|
58
|
+
|
|
59
|
+
CHECK(instr.address == 0x0400);
|
|
60
|
+
CHECK(instr.length == 1);
|
|
61
|
+
CHECK(instr.opcode == 0x00);
|
|
62
|
+
CHECK(std::string(instr.mnemonic) == "BRK");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
TEST_CASE("disassembleInstruction branch instruction calculates target", "[disasm][single]") {
|
|
66
|
+
// BNE with forward branch (+5)
|
|
67
|
+
uint8_t data[] = {0xD0, 0x05};
|
|
68
|
+
auto instr = disassembleInstruction(data, sizeof(data), 0x1000);
|
|
69
|
+
|
|
70
|
+
CHECK(instr.length == 2);
|
|
71
|
+
CHECK(std::string(instr.mnemonic) == "BNE");
|
|
72
|
+
// Target = address + 2 + offset = 0x1000 + 2 + 5 = 0x1007
|
|
73
|
+
CHECK(instr.target == 0x1007);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
TEST_CASE("disassembleInstruction branch with negative offset", "[disasm][single]") {
|
|
77
|
+
// BEQ with backward branch (-10 = 0xF6)
|
|
78
|
+
uint8_t data[] = {0xF0, 0xF6};
|
|
79
|
+
auto instr = disassembleInstruction(data, sizeof(data), 0x1000);
|
|
80
|
+
|
|
81
|
+
CHECK(instr.length == 2);
|
|
82
|
+
CHECK(std::string(instr.mnemonic) == "BEQ");
|
|
83
|
+
// Target = 0x1000 + 2 + (-10) = 0x0FF8
|
|
84
|
+
CHECK(instr.target == 0x0FF8);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
TEST_CASE("disassembleInstruction null data returns zero-length", "[disasm][single]") {
|
|
88
|
+
auto instr = disassembleInstruction(nullptr, 0, 0x0000);
|
|
89
|
+
CHECK(instr.length == 0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// disassembleBlock tests
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
TEST_CASE("disassembleBlock multiple instructions", "[disasm][block]") {
|
|
97
|
+
// NOP; LDA #$42; RTS
|
|
98
|
+
uint8_t data[] = {0xEA, 0xA9, 0x42, 0x60};
|
|
99
|
+
auto result = disassembleBlock(data, sizeof(data), 0x0800);
|
|
100
|
+
|
|
101
|
+
REQUIRE(result.instructions.size() == 3);
|
|
102
|
+
|
|
103
|
+
CHECK(std::string(result.instructions[0].mnemonic) == "NOP");
|
|
104
|
+
CHECK(result.instructions[0].address == 0x0800);
|
|
105
|
+
CHECK(result.instructions[0].length == 1);
|
|
106
|
+
|
|
107
|
+
CHECK(std::string(result.instructions[1].mnemonic) == "LDA");
|
|
108
|
+
CHECK(result.instructions[1].address == 0x0801);
|
|
109
|
+
CHECK(result.instructions[1].length == 2);
|
|
110
|
+
|
|
111
|
+
CHECK(std::string(result.instructions[2].mnemonic) == "RTS");
|
|
112
|
+
CHECK(result.instructions[2].address == 0x0803);
|
|
113
|
+
CHECK(result.instructions[2].length == 1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
TEST_CASE("disassembleBlock empty data returns empty result", "[disasm][block]") {
|
|
117
|
+
auto result = disassembleBlock(nullptr, 0, 0x0000);
|
|
118
|
+
CHECK(result.instructions.empty());
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// disassembleWithFlowAnalysis tests
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
TEST_CASE("disassembleWithFlowAnalysis traces code paths", "[disasm][flow]") {
|
|
126
|
+
// Simple subroutine:
|
|
127
|
+
// 0x1000: LDA #$01 (A9 01)
|
|
128
|
+
// 0x1002: BNE $1005 (D0 01)
|
|
129
|
+
// 0x1004: NOP (EA)
|
|
130
|
+
// 0x1005: RTS (60)
|
|
131
|
+
uint8_t data[] = {0xA9, 0x01, 0xD0, 0x01, 0xEA, 0x60};
|
|
132
|
+
std::vector<uint16_t> entries = {0x1000};
|
|
133
|
+
|
|
134
|
+
auto result = disassembleWithFlowAnalysis(data, sizeof(data), 0x1000, entries);
|
|
135
|
+
|
|
136
|
+
// Flow analysis should find all reachable instructions
|
|
137
|
+
REQUIRE(result.instructions.size() >= 3);
|
|
138
|
+
|
|
139
|
+
// Check that we traced both the branch-taken and fall-through paths
|
|
140
|
+
bool foundLDA = false, foundRTS = false, foundBNE = false;
|
|
141
|
+
for (const auto& instr : result.instructions) {
|
|
142
|
+
if (std::string(instr.mnemonic) == "LDA") foundLDA = true;
|
|
143
|
+
if (std::string(instr.mnemonic) == "BNE") foundBNE = true;
|
|
144
|
+
if (std::string(instr.mnemonic) == "RTS") foundRTS = true;
|
|
145
|
+
}
|
|
146
|
+
CHECK(foundLDA);
|
|
147
|
+
CHECK(foundBNE);
|
|
148
|
+
CHECK(foundRTS);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
TEST_CASE("disassembleWithFlowAnalysis convenience overload", "[disasm][flow]") {
|
|
152
|
+
uint8_t data[] = {0xEA, 0x60}; // NOP; RTS
|
|
153
|
+
auto result = disassembleWithFlowAnalysis(data, sizeof(data), 0x0800);
|
|
154
|
+
|
|
155
|
+
REQUIRE(result.instructions.size() == 2);
|
|
156
|
+
CHECK(std::string(result.instructions[0].mnemonic) == "NOP");
|
|
157
|
+
CHECK(std::string(result.instructions[1].mnemonic) == "RTS");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// getInstructionLength tests
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
TEST_CASE("getInstructionLength implied is 1", "[disasm][length]") {
|
|
165
|
+
// NOP (0xEA) is implied
|
|
166
|
+
CHECK(getInstructionLength(0xEA) == 1);
|
|
167
|
+
// RTS (0x60) is implied
|
|
168
|
+
CHECK(getInstructionLength(0x60) == 1);
|
|
169
|
+
// BRK (0x00) is implied
|
|
170
|
+
CHECK(getInstructionLength(0x00) == 1);
|
|
171
|
+
// INX (0xE8) is implied
|
|
172
|
+
CHECK(getInstructionLength(0xE8) == 1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
TEST_CASE("getInstructionLength immediate is 2", "[disasm][length]") {
|
|
176
|
+
// LDA # (0xA9)
|
|
177
|
+
CHECK(getInstructionLength(0xA9) == 2);
|
|
178
|
+
// LDX # (0xA2)
|
|
179
|
+
CHECK(getInstructionLength(0xA2) == 2);
|
|
180
|
+
// CMP # (0xC9)
|
|
181
|
+
CHECK(getInstructionLength(0xC9) == 2);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
TEST_CASE("getInstructionLength absolute is 3", "[disasm][length]") {
|
|
185
|
+
// JMP abs (0x4C)
|
|
186
|
+
CHECK(getInstructionLength(0x4C) == 3);
|
|
187
|
+
// JSR abs (0x20)
|
|
188
|
+
CHECK(getInstructionLength(0x20) == 3);
|
|
189
|
+
// LDA abs (0xAD)
|
|
190
|
+
CHECK(getInstructionLength(0xAD) == 3);
|
|
191
|
+
// STA abs (0x8D)
|
|
192
|
+
CHECK(getInstructionLength(0x8D) == 3);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
TEST_CASE("getInstructionLength relative (branches) is 2", "[disasm][length]") {
|
|
196
|
+
// BNE (0xD0)
|
|
197
|
+
CHECK(getInstructionLength(0xD0) == 2);
|
|
198
|
+
// BEQ (0xF0)
|
|
199
|
+
CHECK(getInstructionLength(0xF0) == 2);
|
|
200
|
+
// BCC (0x90)
|
|
201
|
+
CHECK(getInstructionLength(0x90) == 2);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
TEST_CASE("getInstructionLength zero page is 2", "[disasm][length]") {
|
|
205
|
+
// LDA zp (0xA5)
|
|
206
|
+
CHECK(getInstructionLength(0xA5) == 2);
|
|
207
|
+
// STA zp (0x85)
|
|
208
|
+
CHECK(getInstructionLength(0x85) == 2);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// getMnemonic tests
|
|
213
|
+
// ============================================================================
|
|
214
|
+
|
|
215
|
+
TEST_CASE("getMnemonic returns correct mnemonic strings", "[disasm][mnemonic]") {
|
|
216
|
+
CHECK(std::string(getMnemonic(0xEA)) == "NOP");
|
|
217
|
+
CHECK(std::string(getMnemonic(0xA9)) == "LDA");
|
|
218
|
+
CHECK(std::string(getMnemonic(0x4C)) == "JMP");
|
|
219
|
+
CHECK(std::string(getMnemonic(0x20)) == "JSR");
|
|
220
|
+
CHECK(std::string(getMnemonic(0x60)) == "RTS");
|
|
221
|
+
CHECK(std::string(getMnemonic(0x00)) == "BRK");
|
|
222
|
+
CHECK(std::string(getMnemonic(0xE8)) == "INX");
|
|
223
|
+
CHECK(std::string(getMnemonic(0xC8)) == "INY");
|
|
224
|
+
CHECK(std::string(getMnemonic(0x48)) == "PHA");
|
|
225
|
+
CHECK(std::string(getMnemonic(0x68)) == "PLA");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ============================================================================
|
|
229
|
+
// getAddressingMode tests
|
|
230
|
+
// ============================================================================
|
|
231
|
+
|
|
232
|
+
TEST_CASE("getAddressingMode returns correct modes", "[disasm][addrmode]") {
|
|
233
|
+
CHECK(getAddressingMode(0xEA) == AddrMode::IMP); // NOP - implied
|
|
234
|
+
CHECK(getAddressingMode(0xA9) == AddrMode::IMM); // LDA # - immediate
|
|
235
|
+
CHECK(getAddressingMode(0xA5) == AddrMode::ZP); // LDA zp - zero page
|
|
236
|
+
CHECK(getAddressingMode(0xAD) == AddrMode::ABS); // LDA abs - absolute
|
|
237
|
+
CHECK(getAddressingMode(0xB5) == AddrMode::ZPX); // LDA zp,X
|
|
238
|
+
CHECK(getAddressingMode(0xBD) == AddrMode::ABX); // LDA abs,X
|
|
239
|
+
CHECK(getAddressingMode(0xB9) == AddrMode::ABY); // LDA abs,Y
|
|
240
|
+
CHECK(getAddressingMode(0xA1) == AddrMode::IZX); // LDA (zp,X)
|
|
241
|
+
CHECK(getAddressingMode(0xB1) == AddrMode::IZY); // LDA (zp),Y
|
|
242
|
+
CHECK(getAddressingMode(0x6C) == AddrMode::IND); // JMP (abs)
|
|
243
|
+
CHECK(getAddressingMode(0xD0) == AddrMode::REL); // BNE rel
|
|
244
|
+
CHECK(getAddressingMode(0x0A) == AddrMode::ACC); // ASL A
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// getInstructionCategory tests
|
|
249
|
+
// ============================================================================
|
|
250
|
+
|
|
251
|
+
TEST_CASE("getInstructionCategory returns correct categories", "[disasm][category]") {
|
|
252
|
+
// Branch instructions
|
|
253
|
+
CHECK(getInstructionCategory(0x4C) == InstrCategory::BRANCH); // JMP
|
|
254
|
+
CHECK(getInstructionCategory(0x20) == InstrCategory::BRANCH); // JSR
|
|
255
|
+
CHECK(getInstructionCategory(0xD0) == InstrCategory::BRANCH); // BNE
|
|
256
|
+
CHECK(getInstructionCategory(0x60) == InstrCategory::BRANCH); // RTS
|
|
257
|
+
|
|
258
|
+
// Load/store instructions
|
|
259
|
+
CHECK(getInstructionCategory(0xA9) == InstrCategory::LOAD); // LDA #
|
|
260
|
+
CHECK(getInstructionCategory(0x85) == InstrCategory::LOAD); // STA zp
|
|
261
|
+
CHECK(getInstructionCategory(0xA2) == InstrCategory::LOAD); // LDX #
|
|
262
|
+
|
|
263
|
+
// Math/logic instructions
|
|
264
|
+
CHECK(getInstructionCategory(0x69) == InstrCategory::MATH); // ADC
|
|
265
|
+
CHECK(getInstructionCategory(0x29) == InstrCategory::MATH); // AND #
|
|
266
|
+
CHECK(getInstructionCategory(0xC9) == InstrCategory::MATH); // CMP #
|
|
267
|
+
|
|
268
|
+
// Stack instructions
|
|
269
|
+
CHECK(getInstructionCategory(0x48) == InstrCategory::STACK); // PHA
|
|
270
|
+
CHECK(getInstructionCategory(0x68) == InstrCategory::STACK); // PLA
|
|
271
|
+
CHECK(getInstructionCategory(0x00) == InstrCategory::STACK); // BRK
|
|
272
|
+
|
|
273
|
+
// Flag instructions
|
|
274
|
+
CHECK(getInstructionCategory(0x18) == InstrCategory::FLAG); // CLC
|
|
275
|
+
CHECK(getInstructionCategory(0x38) == InstrCategory::FLAG); // SEC
|
|
276
|
+
CHECK(getInstructionCategory(0x78) == InstrCategory::FLAG); // SEI
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// getFlowType tests
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
TEST_CASE("getFlowType SEQUENTIAL for normal instructions", "[disasm][flow]") {
|
|
284
|
+
CHECK(getFlowType(0xEA) == FlowType::SEQUENTIAL); // NOP
|
|
285
|
+
CHECK(getFlowType(0xA9) == FlowType::SEQUENTIAL); // LDA #
|
|
286
|
+
CHECK(getFlowType(0x85) == FlowType::SEQUENTIAL); // STA zp
|
|
287
|
+
CHECK(getFlowType(0xE8) == FlowType::SEQUENTIAL); // INX
|
|
288
|
+
CHECK(getFlowType(0x48) == FlowType::SEQUENTIAL); // PHA
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
TEST_CASE("getFlowType CONDITIONAL for branches", "[disasm][flow]") {
|
|
292
|
+
CHECK(getFlowType(0xD0) == FlowType::CONDITIONAL); // BNE
|
|
293
|
+
CHECK(getFlowType(0xF0) == FlowType::CONDITIONAL); // BEQ
|
|
294
|
+
CHECK(getFlowType(0x90) == FlowType::CONDITIONAL); // BCC
|
|
295
|
+
CHECK(getFlowType(0xB0) == FlowType::CONDITIONAL); // BCS
|
|
296
|
+
CHECK(getFlowType(0x10) == FlowType::CONDITIONAL); // BPL
|
|
297
|
+
CHECK(getFlowType(0x30) == FlowType::CONDITIONAL); // BMI
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
TEST_CASE("getFlowType UNCONDITIONAL for JMP absolute and BRA", "[disasm][flow]") {
|
|
301
|
+
CHECK(getFlowType(0x4C) == FlowType::UNCONDITIONAL); // JMP abs
|
|
302
|
+
CHECK(getFlowType(0x80) == FlowType::UNCONDITIONAL); // BRA (65C02)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
TEST_CASE("getFlowType CALL for JSR", "[disasm][flow]") {
|
|
306
|
+
CHECK(getFlowType(0x20) == FlowType::CALL); // JSR
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
TEST_CASE("getFlowType RETURN for RTS and RTI", "[disasm][flow]") {
|
|
310
|
+
CHECK(getFlowType(0x60) == FlowType::RETURN); // RTS
|
|
311
|
+
CHECK(getFlowType(0x40) == FlowType::RETURN); // RTI
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
TEST_CASE("getFlowType HALT for BRK, STP, WAI", "[disasm][flow]") {
|
|
315
|
+
CHECK(getFlowType(0x00) == FlowType::HALT); // BRK
|
|
316
|
+
CHECK(getFlowType(0xDB) == FlowType::HALT); // STP
|
|
317
|
+
CHECK(getFlowType(0xCB) == FlowType::HALT); // WAI
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
TEST_CASE("getFlowType INDIRECT for JMP indirect", "[disasm][flow]") {
|
|
321
|
+
CHECK(getFlowType(0x6C) == FlowType::INDIRECT); // JMP (abs)
|
|
322
|
+
CHECK(getFlowType(0x7C) == FlowType::INDIRECT); // JMP (abs,X) - 65C02
|
|
323
|
+
}
|