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,323 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_mmu_slots.cpp - Unit tests for MMU expansion slot system
|
|
3
|
+
*
|
|
4
|
+
* Tests the expansion card slot management including card insertion,
|
|
5
|
+
* removal, ROM routing, I/O routing, INTCXROM/SLOTC3ROM switches,
|
|
6
|
+
* expansion ROM activation, and multiple cards.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#define CATCH_CONFIG_MAIN
|
|
10
|
+
#include "catch.hpp"
|
|
11
|
+
|
|
12
|
+
#include "mmu/mmu.hpp"
|
|
13
|
+
#include "cards/thunderclock_card.hpp"
|
|
14
|
+
#include "roms.cpp"
|
|
15
|
+
|
|
16
|
+
#include <memory>
|
|
17
|
+
|
|
18
|
+
using namespace a2e;
|
|
19
|
+
|
|
20
|
+
// Helper: create an MMU with ROMs loaded
|
|
21
|
+
static std::unique_ptr<MMU> createMMU() {
|
|
22
|
+
auto mmu = std::make_unique<MMU>();
|
|
23
|
+
mmu->loadROM(roms::ROM_SYSTEM, roms::ROM_SYSTEM_SIZE,
|
|
24
|
+
roms::ROM_CHAR, roms::ROM_CHAR_SIZE);
|
|
25
|
+
return mmu;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Slot empty/occupied state
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
TEST_CASE("Slots are initially empty", "[mmu][slots]") {
|
|
33
|
+
auto mmu = createMMU();
|
|
34
|
+
|
|
35
|
+
for (int slot = 1; slot <= 7; ++slot) {
|
|
36
|
+
INFO("Slot " << slot);
|
|
37
|
+
CHECK(mmu->isSlotEmpty(slot));
|
|
38
|
+
CHECK(mmu->getCard(slot) == nullptr);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
TEST_CASE("isSlotEmpty returns true for out-of-range slots", "[mmu][slots]") {
|
|
43
|
+
auto mmu = createMMU();
|
|
44
|
+
|
|
45
|
+
CHECK(mmu->isSlotEmpty(0));
|
|
46
|
+
CHECK(mmu->isSlotEmpty(8));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Card insertion
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
TEST_CASE("Insert card into slot: slot becomes occupied", "[mmu][slots]") {
|
|
54
|
+
auto mmu = createMMU();
|
|
55
|
+
|
|
56
|
+
auto card = std::make_unique<ThunderclockCard>();
|
|
57
|
+
auto* cardPtr = card.get();
|
|
58
|
+
|
|
59
|
+
auto removed = mmu->insertCard(5, std::move(card));
|
|
60
|
+
|
|
61
|
+
CHECK(removed == nullptr); // Slot was empty
|
|
62
|
+
CHECK_FALSE(mmu->isSlotEmpty(5));
|
|
63
|
+
CHECK(mmu->getCard(5) == cardPtr);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
TEST_CASE("insertCard returns previously installed card", "[mmu][slots]") {
|
|
67
|
+
auto mmu = createMMU();
|
|
68
|
+
|
|
69
|
+
auto card1 = std::make_unique<ThunderclockCard>();
|
|
70
|
+
auto* card1Ptr = card1.get();
|
|
71
|
+
mmu->insertCard(5, std::move(card1));
|
|
72
|
+
|
|
73
|
+
auto card2 = std::make_unique<ThunderclockCard>();
|
|
74
|
+
auto removed = mmu->insertCard(5, std::move(card2));
|
|
75
|
+
|
|
76
|
+
// Should get back card1
|
|
77
|
+
REQUIRE(removed != nullptr);
|
|
78
|
+
CHECK(removed.get() == card1Ptr);
|
|
79
|
+
CHECK_FALSE(mmu->isSlotEmpty(5));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Card removal
|
|
84
|
+
// ============================================================================
|
|
85
|
+
|
|
86
|
+
TEST_CASE("removeCard returns the card and slot becomes empty", "[mmu][slots]") {
|
|
87
|
+
auto mmu = createMMU();
|
|
88
|
+
|
|
89
|
+
auto card = std::make_unique<ThunderclockCard>();
|
|
90
|
+
auto* cardPtr = card.get();
|
|
91
|
+
mmu->insertCard(5, std::move(card));
|
|
92
|
+
|
|
93
|
+
auto removed = mmu->removeCard(5);
|
|
94
|
+
REQUIRE(removed != nullptr);
|
|
95
|
+
CHECK(removed.get() == cardPtr);
|
|
96
|
+
CHECK(mmu->isSlotEmpty(5));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
TEST_CASE("removeCard from empty slot returns nullptr", "[mmu][slots]") {
|
|
100
|
+
auto mmu = createMMU();
|
|
101
|
+
|
|
102
|
+
auto removed = mmu->removeCard(5);
|
|
103
|
+
CHECK(removed == nullptr);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// Slot ROM routing
|
|
108
|
+
// ============================================================================
|
|
109
|
+
|
|
110
|
+
TEST_CASE("Slot ROM: read $C500-$C5FF accesses Thunderclock card ROM", "[mmu][slots][rom]") {
|
|
111
|
+
auto mmu = createMMU();
|
|
112
|
+
mmu->insertCard(5, std::make_unique<ThunderclockCard>());
|
|
113
|
+
|
|
114
|
+
// Thunderclock ROM signature bytes (ProDOS clock detection)
|
|
115
|
+
CHECK(mmu->read(0xC500) == 0x08); // PHP
|
|
116
|
+
CHECK(mmu->read(0xC502) == 0x28); // PLP
|
|
117
|
+
CHECK(mmu->read(0xC504) == 0x58); // CLI
|
|
118
|
+
CHECK(mmu->read(0xC506) == 0x70); // BVS
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
TEST_CASE("Slot ROM: empty slot returns floating bus value", "[mmu][slots][rom]") {
|
|
122
|
+
auto mmu = createMMU();
|
|
123
|
+
|
|
124
|
+
// No cycle callback so floating bus returns 0x00
|
|
125
|
+
uint8_t val = mmu->read(0xC500);
|
|
126
|
+
CHECK(val == 0x00);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// Slot I/O routing
|
|
131
|
+
// ============================================================================
|
|
132
|
+
|
|
133
|
+
TEST_CASE("Slot I/O: read/write $C0D0-$C0DF accesses slot 5 card", "[mmu][slots][io]") {
|
|
134
|
+
auto mmu = createMMU();
|
|
135
|
+
mmu->insertCard(5, std::make_unique<ThunderclockCard>());
|
|
136
|
+
|
|
137
|
+
// Initial I/O read from Thunderclock
|
|
138
|
+
uint8_t initialValue = mmu->read(0xC0D0);
|
|
139
|
+
CHECK(initialValue == 0x00);
|
|
140
|
+
|
|
141
|
+
// Write a command and verify I/O is being routed
|
|
142
|
+
mmu->write(0xC0D0, 0x00);
|
|
143
|
+
mmu->write(0xC0D0, 0xA4); // CMD_TIMED | FLAG_STROBE
|
|
144
|
+
|
|
145
|
+
// The fact that we can write and read without crashing confirms routing
|
|
146
|
+
uint8_t result = mmu->read(0xC0D0);
|
|
147
|
+
// Result will have bit 7 set to data bit from Thunderclock
|
|
148
|
+
(void)result; // Value depends on time, just verify no crash
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
TEST_CASE("Slot I/O: slot 7 I/O at $C0F0-$C0FF", "[mmu][slots][io]") {
|
|
152
|
+
auto mmu = createMMU();
|
|
153
|
+
mmu->insertCard(7, std::make_unique<ThunderclockCard>());
|
|
154
|
+
|
|
155
|
+
uint8_t val = mmu->read(0xC0F0);
|
|
156
|
+
CHECK(val == 0x00);
|
|
157
|
+
|
|
158
|
+
// Slot 7 ROM at $C700
|
|
159
|
+
CHECK(mmu->read(0xC700) == 0x08);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// INTCXROM switch
|
|
164
|
+
// ============================================================================
|
|
165
|
+
|
|
166
|
+
TEST_CASE("INTCXROM: when internal, slot ROM reads from internal ROM", "[mmu][slots][intcxrom]") {
|
|
167
|
+
auto mmu = createMMU();
|
|
168
|
+
mmu->insertCard(5, std::make_unique<ThunderclockCard>());
|
|
169
|
+
|
|
170
|
+
// Verify card ROM is accessible first
|
|
171
|
+
CHECK(mmu->read(0xC500) == 0x08);
|
|
172
|
+
|
|
173
|
+
// Enable INTCXROM (write to $C007)
|
|
174
|
+
mmu->write(0xC007, 0);
|
|
175
|
+
CHECK(mmu->getSoftSwitches().intcxrom);
|
|
176
|
+
|
|
177
|
+
// Now $C500 should read from internal ROM instead of card
|
|
178
|
+
uint8_t internalByte = roms::ROM_SYSTEM[0x0500]; // offset $C500 - $C000 = $0500
|
|
179
|
+
CHECK(mmu->read(0xC500) == internalByte);
|
|
180
|
+
|
|
181
|
+
// Disable INTCXROM (write to $C006)
|
|
182
|
+
mmu->write(0xC006, 0);
|
|
183
|
+
CHECK_FALSE(mmu->getSoftSwitches().intcxrom);
|
|
184
|
+
|
|
185
|
+
// Card ROM should be accessible again
|
|
186
|
+
CHECK(mmu->read(0xC500) == 0x08);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// SLOTC3ROM switch
|
|
191
|
+
// ============================================================================
|
|
192
|
+
|
|
193
|
+
TEST_CASE("SLOTC3ROM: controls slot 3 behavior", "[mmu][slots][slotc3rom]") {
|
|
194
|
+
auto mmu = createMMU();
|
|
195
|
+
|
|
196
|
+
// Default: SLOTC3ROM is off, slot 3 uses internal ROM
|
|
197
|
+
CHECK_FALSE(mmu->getSoftSwitches().slotc3rom);
|
|
198
|
+
|
|
199
|
+
// Read from $C300 should return internal ROM
|
|
200
|
+
uint8_t internalByte = roms::ROM_SYSTEM[0x0300]; // $C300 - $C000
|
|
201
|
+
CHECK(mmu->read(0xC300) == internalByte);
|
|
202
|
+
|
|
203
|
+
// Enable SLOTC3ROM (write to $C00B)
|
|
204
|
+
mmu->write(0xC00B, 0);
|
|
205
|
+
CHECK(mmu->getSoftSwitches().slotc3rom);
|
|
206
|
+
|
|
207
|
+
// Disable SLOTC3ROM (write to $C00A)
|
|
208
|
+
mmu->write(0xC00A, 0);
|
|
209
|
+
CHECK_FALSE(mmu->getSoftSwitches().slotc3rom);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ============================================================================
|
|
213
|
+
// Expansion ROM
|
|
214
|
+
// ============================================================================
|
|
215
|
+
|
|
216
|
+
TEST_CASE("Expansion ROM: reading $Cn00 activates expansion ROM at $C800", "[mmu][slots][exprom]") {
|
|
217
|
+
auto mmu = createMMU();
|
|
218
|
+
mmu->insertCard(5, std::make_unique<ThunderclockCard>());
|
|
219
|
+
|
|
220
|
+
// Access slot 5 ROM to activate expansion ROM
|
|
221
|
+
mmu->read(0xC500);
|
|
222
|
+
|
|
223
|
+
// Thunderclock has expansion ROM, should be active now
|
|
224
|
+
CHECK(mmu->getActiveExpansionSlot() == 5);
|
|
225
|
+
|
|
226
|
+
// Read from expansion ROM area - should not be floating bus
|
|
227
|
+
uint8_t byte = mmu->read(0xC800);
|
|
228
|
+
// Thunderclock expansion ROM should have valid data (not 0xFF floating bus)
|
|
229
|
+
// We just verify it was activated and returns card data
|
|
230
|
+
(void)byte;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
TEST_CASE("Expansion ROM: reading $CFFF deactivates expansion ROM", "[mmu][slots][exprom]") {
|
|
234
|
+
auto mmu = createMMU();
|
|
235
|
+
mmu->insertCard(5, std::make_unique<ThunderclockCard>());
|
|
236
|
+
|
|
237
|
+
// Activate expansion ROM
|
|
238
|
+
mmu->read(0xC500);
|
|
239
|
+
REQUIRE(mmu->getActiveExpansionSlot() == 5);
|
|
240
|
+
|
|
241
|
+
// Read $CFFF to deactivate
|
|
242
|
+
mmu->read(0xCFFF);
|
|
243
|
+
CHECK(mmu->getActiveExpansionSlot() == 0);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
TEST_CASE("Expansion ROM: switching between cards updates active slot", "[mmu][slots][exprom]") {
|
|
247
|
+
auto mmu = createMMU();
|
|
248
|
+
mmu->insertCard(5, std::make_unique<ThunderclockCard>());
|
|
249
|
+
mmu->insertCard(7, std::make_unique<ThunderclockCard>());
|
|
250
|
+
|
|
251
|
+
// Activate slot 5 expansion ROM
|
|
252
|
+
mmu->read(0xC500);
|
|
253
|
+
CHECK(mmu->getActiveExpansionSlot() == 5);
|
|
254
|
+
|
|
255
|
+
// Activate slot 7 expansion ROM
|
|
256
|
+
mmu->read(0xC700);
|
|
257
|
+
CHECK(mmu->getActiveExpansionSlot() == 7);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ============================================================================
|
|
261
|
+
// Multiple cards in different slots
|
|
262
|
+
// ============================================================================
|
|
263
|
+
|
|
264
|
+
TEST_CASE("Multiple cards: independent access in different slots", "[mmu][slots][multi]") {
|
|
265
|
+
auto mmu = createMMU();
|
|
266
|
+
mmu->insertCard(5, std::make_unique<ThunderclockCard>());
|
|
267
|
+
mmu->insertCard(7, std::make_unique<ThunderclockCard>());
|
|
268
|
+
|
|
269
|
+
// Both should be accessible
|
|
270
|
+
CHECK(mmu->read(0xC500) == 0x08);
|
|
271
|
+
CHECK(mmu->read(0xC700) == 0x08);
|
|
272
|
+
|
|
273
|
+
// Both slot I/O spaces should work independently
|
|
274
|
+
CHECK(mmu->read(0xC0D0) == 0x00); // Slot 5
|
|
275
|
+
CHECK(mmu->read(0xC0F0) == 0x00); // Slot 7
|
|
276
|
+
|
|
277
|
+
// Slots that don't have cards are still empty
|
|
278
|
+
CHECK(mmu->isSlotEmpty(1));
|
|
279
|
+
CHECK(mmu->isSlotEmpty(2));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
TEST_CASE("Card in slot 1: ROM at $C100, I/O at $C090", "[mmu][slots][multi]") {
|
|
283
|
+
auto mmu = createMMU();
|
|
284
|
+
mmu->insertCard(1, std::make_unique<ThunderclockCard>());
|
|
285
|
+
|
|
286
|
+
// Slot 1 ROM at $C100
|
|
287
|
+
CHECK(mmu->read(0xC100) == 0x08);
|
|
288
|
+
|
|
289
|
+
// Slot 1 I/O at $C090
|
|
290
|
+
CHECK(mmu->read(0xC090) == 0x00);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ============================================================================
|
|
294
|
+
// INTCXROM affects all slot ROMs
|
|
295
|
+
// ============================================================================
|
|
296
|
+
|
|
297
|
+
TEST_CASE("INTCXROM blocks ROM for all slots simultaneously", "[mmu][slots][intcxrom]") {
|
|
298
|
+
auto mmu = createMMU();
|
|
299
|
+
mmu->insertCard(1, std::make_unique<ThunderclockCard>());
|
|
300
|
+
mmu->insertCard(5, std::make_unique<ThunderclockCard>());
|
|
301
|
+
mmu->insertCard(7, std::make_unique<ThunderclockCard>());
|
|
302
|
+
|
|
303
|
+
// All cards read their own ROM initially
|
|
304
|
+
CHECK(mmu->read(0xC100) == 0x08);
|
|
305
|
+
CHECK(mmu->read(0xC500) == 0x08);
|
|
306
|
+
CHECK(mmu->read(0xC700) == 0x08);
|
|
307
|
+
|
|
308
|
+
// Enable INTCXROM
|
|
309
|
+
mmu->write(0xC007, 0);
|
|
310
|
+
|
|
311
|
+
// All slots now read internal ROM
|
|
312
|
+
CHECK(mmu->read(0xC100) == roms::ROM_SYSTEM[0x0100]);
|
|
313
|
+
CHECK(mmu->read(0xC500) == roms::ROM_SYSTEM[0x0500]);
|
|
314
|
+
CHECK(mmu->read(0xC700) == roms::ROM_SYSTEM[0x0700]);
|
|
315
|
+
|
|
316
|
+
// Disable INTCXROM
|
|
317
|
+
mmu->write(0xC006, 0);
|
|
318
|
+
|
|
319
|
+
// Cards are accessible again
|
|
320
|
+
CHECK(mmu->read(0xC100) == 0x08);
|
|
321
|
+
CHECK(mmu->read(0xC500) == 0x08);
|
|
322
|
+
CHECK(mmu->read(0xC700) == 0x08);
|
|
323
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_mockingboard.cpp - Unit tests for MockingboardCard
|
|
3
|
+
*
|
|
4
|
+
* Tests the Mockingboard sound card implementation including:
|
|
5
|
+
* - Construction
|
|
6
|
+
* - Card metadata (name, preferred slot)
|
|
7
|
+
* - VIA register access via ROM space
|
|
8
|
+
* - PSG register write sequence via VIA
|
|
9
|
+
* - Timer updates
|
|
10
|
+
* - Reset behavior
|
|
11
|
+
* - Enable/disable state
|
|
12
|
+
* - Audio sample generation
|
|
13
|
+
* - Serialization round-trip
|
|
14
|
+
* - IRQ generation from VIA timer
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
#define CATCH_CONFIG_MAIN
|
|
18
|
+
#include "catch.hpp"
|
|
19
|
+
|
|
20
|
+
#include "mockingboard_card.hpp"
|
|
21
|
+
|
|
22
|
+
#include <cstring>
|
|
23
|
+
#include <vector>
|
|
24
|
+
|
|
25
|
+
using namespace a2e;
|
|
26
|
+
|
|
27
|
+
// VIA register offsets (within the VIA's 16-register space)
|
|
28
|
+
static constexpr uint8_t VIA_ORB = 0x00;
|
|
29
|
+
static constexpr uint8_t VIA_ORA = 0x01;
|
|
30
|
+
static constexpr uint8_t VIA_DDRB = 0x02;
|
|
31
|
+
static constexpr uint8_t VIA_DDRA = 0x03;
|
|
32
|
+
static constexpr uint8_t VIA_T1CL = 0x04;
|
|
33
|
+
static constexpr uint8_t VIA_T1CH = 0x05;
|
|
34
|
+
static constexpr uint8_t VIA_T1LL = 0x06;
|
|
35
|
+
static constexpr uint8_t VIA_T1LH = 0x07;
|
|
36
|
+
static constexpr uint8_t VIA_ACR = 0x0B;
|
|
37
|
+
static constexpr uint8_t VIA_IFR = 0x0D;
|
|
38
|
+
static constexpr uint8_t VIA_IER = 0x0E;
|
|
39
|
+
|
|
40
|
+
// VIA1 base offset in ROM space: bit 7 = 0, so offsets 0x00-0x0F
|
|
41
|
+
// VIA2 base offset in ROM space: bit 7 = 1, so offsets 0x80-0x8F
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Construction
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
TEST_CASE("MockingboardCard constructor creates a valid instance", "[mockingboard]") {
|
|
48
|
+
MockingboardCard card;
|
|
49
|
+
REQUIRE(card.getName() != nullptr);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Card metadata
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
TEST_CASE("MockingboardCard getName returns Mockingboard", "[mockingboard]") {
|
|
57
|
+
MockingboardCard card;
|
|
58
|
+
REQUIRE(std::string(card.getName()) == "Mockingboard");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
TEST_CASE("MockingboardCard getPreferredSlot returns 4", "[mockingboard]") {
|
|
62
|
+
MockingboardCard card;
|
|
63
|
+
REQUIRE(card.getPreferredSlot() == 4);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
TEST_CASE("MockingboardCard hasROM returns true", "[mockingboard]") {
|
|
67
|
+
MockingboardCard card;
|
|
68
|
+
REQUIRE(card.hasROM());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
TEST_CASE("MockingboardCard hasExpansionROM returns false", "[mockingboard]") {
|
|
72
|
+
MockingboardCard card;
|
|
73
|
+
REQUIRE_FALSE(card.hasExpansionROM());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// VIA register access via ROM space
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
TEST_CASE("MockingboardCard write/read VIA1 DDRA register", "[mockingboard]") {
|
|
81
|
+
MockingboardCard card;
|
|
82
|
+
|
|
83
|
+
// Write 0xFF to VIA1 DDRA (offset 0x03 in VIA1 space = ROM offset 0x03)
|
|
84
|
+
card.writeROM(VIA_DDRA, 0xFF);
|
|
85
|
+
uint8_t val = card.readROM(VIA_DDRA);
|
|
86
|
+
REQUIRE(val == 0xFF);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
TEST_CASE("MockingboardCard write/read VIA1 DDRB register", "[mockingboard]") {
|
|
90
|
+
MockingboardCard card;
|
|
91
|
+
|
|
92
|
+
card.writeROM(VIA_DDRB, 0xFF);
|
|
93
|
+
uint8_t val = card.readROM(VIA_DDRB);
|
|
94
|
+
REQUIRE(val == 0xFF);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
TEST_CASE("MockingboardCard write/read VIA2 DDRA register", "[mockingboard]") {
|
|
98
|
+
MockingboardCard card;
|
|
99
|
+
|
|
100
|
+
// VIA2 registers are at offset 0x80+
|
|
101
|
+
card.writeROM(0x80 | VIA_DDRA, 0xFF);
|
|
102
|
+
uint8_t val = card.readROM(0x80 | VIA_DDRA);
|
|
103
|
+
REQUIRE(val == 0xFF);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
TEST_CASE("MockingboardCard write/read VIA2 DDRB register", "[mockingboard]") {
|
|
107
|
+
MockingboardCard card;
|
|
108
|
+
|
|
109
|
+
card.writeROM(0x80 | VIA_DDRB, 0xFF);
|
|
110
|
+
uint8_t val = card.readROM(0x80 | VIA_DDRB);
|
|
111
|
+
REQUIRE(val == 0xFF);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
TEST_CASE("MockingboardCard VIA1 and VIA2 are independent", "[mockingboard]") {
|
|
115
|
+
MockingboardCard card;
|
|
116
|
+
|
|
117
|
+
card.writeROM(VIA_DDRA, 0xAA);
|
|
118
|
+
card.writeROM(0x80 | VIA_DDRA, 0x55);
|
|
119
|
+
|
|
120
|
+
REQUIRE(card.readROM(VIA_DDRA) == 0xAA);
|
|
121
|
+
REQUIRE(card.readROM(0x80 | VIA_DDRA) == 0x55);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// PSG register write sequence via VIA
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
TEST_CASE("MockingboardCard PSG register write via VIA1 protocol", "[mockingboard]") {
|
|
129
|
+
MockingboardCard card;
|
|
130
|
+
|
|
131
|
+
// The PSG write protocol via VIA:
|
|
132
|
+
// 1. Set DDRA to 0xFF (all outputs) and DDRB to 0xFF (all outputs)
|
|
133
|
+
// 2. Write register address to ORA
|
|
134
|
+
// 3. Set ORB to 0x07 (LATCH command: BC1=1, BDIR=1, /RESET=1)
|
|
135
|
+
// 4. Set ORB to 0x04 (INACTIVE: BC1=0, BDIR=0, /RESET=1)
|
|
136
|
+
// 5. Write register value to ORA
|
|
137
|
+
// 6. Set ORB to 0x06 (WRITE command: BC1=0, BDIR=1, /RESET=1)
|
|
138
|
+
// 7. Set ORB to 0x04 (INACTIVE)
|
|
139
|
+
|
|
140
|
+
// Step 1: Set data directions
|
|
141
|
+
card.writeROM(VIA_DDRA, 0xFF);
|
|
142
|
+
card.writeROM(VIA_DDRB, 0xFF);
|
|
143
|
+
|
|
144
|
+
// Step 2: Latch register address (register 7 = mixer)
|
|
145
|
+
card.writeROM(VIA_ORA, 0x07);
|
|
146
|
+
card.writeROM(VIA_ORB, 0x07); // LATCH
|
|
147
|
+
card.writeROM(VIA_ORB, 0x04); // INACTIVE
|
|
148
|
+
|
|
149
|
+
// Step 3: Write value to register
|
|
150
|
+
card.writeROM(VIA_ORA, 0x38); // All channels: tone on, noise off
|
|
151
|
+
card.writeROM(VIA_ORB, 0x06); // WRITE
|
|
152
|
+
card.writeROM(VIA_ORB, 0x04); // INACTIVE
|
|
153
|
+
|
|
154
|
+
// Verify via debug accessor
|
|
155
|
+
const AY8910& psg1 = card.getPSG1();
|
|
156
|
+
REQUIRE(psg1.getRegister(7) == 0x38);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// update() advances timers
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
TEST_CASE("MockingboardCard update does not crash", "[mockingboard]") {
|
|
164
|
+
MockingboardCard card;
|
|
165
|
+
|
|
166
|
+
// Simply verify update() runs without crashing
|
|
167
|
+
card.update(100);
|
|
168
|
+
card.update(1000);
|
|
169
|
+
card.update(10000);
|
|
170
|
+
REQUIRE(true); // If we get here, no crash occurred
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// reset() clears state
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
TEST_CASE("MockingboardCard reset clears VIA state", "[mockingboard]") {
|
|
178
|
+
MockingboardCard card;
|
|
179
|
+
|
|
180
|
+
// Write something to VIA1
|
|
181
|
+
card.writeROM(VIA_DDRA, 0xFF);
|
|
182
|
+
card.writeROM(VIA_ORA, 0xAA);
|
|
183
|
+
|
|
184
|
+
card.reset();
|
|
185
|
+
|
|
186
|
+
// After reset, DDRA should be 0
|
|
187
|
+
const VIA6522& via1 = card.getVIA1();
|
|
188
|
+
REQUIRE(via1.getDDRA() == 0x00);
|
|
189
|
+
REQUIRE(via1.getORA() == 0x00);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Enable/disable
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
TEST_CASE("MockingboardCard isEnabled is true by default", "[mockingboard]") {
|
|
197
|
+
MockingboardCard card;
|
|
198
|
+
REQUIRE(card.isEnabled());
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
TEST_CASE("MockingboardCard setEnabled toggles state", "[mockingboard]") {
|
|
202
|
+
MockingboardCard card;
|
|
203
|
+
|
|
204
|
+
card.setEnabled(false);
|
|
205
|
+
REQUIRE_FALSE(card.isEnabled());
|
|
206
|
+
|
|
207
|
+
card.setEnabled(true);
|
|
208
|
+
REQUIRE(card.isEnabled());
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Audio generation
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
TEST_CASE("MockingboardCard generateStereoSamples produces output", "[mockingboard]") {
|
|
216
|
+
MockingboardCard card;
|
|
217
|
+
|
|
218
|
+
// Generate a small buffer of stereo samples
|
|
219
|
+
const int frameCount = 128;
|
|
220
|
+
std::vector<float> buffer(frameCount * 2, -999.0f); // interleaved L/R
|
|
221
|
+
|
|
222
|
+
card.generateStereoSamples(buffer.data(), frameCount, 48000);
|
|
223
|
+
|
|
224
|
+
// After generation, buffer should have been written (values should not be -999)
|
|
225
|
+
bool anyWritten = false;
|
|
226
|
+
for (int i = 0; i < frameCount * 2; ++i) {
|
|
227
|
+
if (buffer[i] != -999.0f) {
|
|
228
|
+
anyWritten = true;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
REQUIRE(anyWritten);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
TEST_CASE("MockingboardCard generateStereoSamples with timing does not crash", "[mockingboard]") {
|
|
236
|
+
MockingboardCard card;
|
|
237
|
+
|
|
238
|
+
const int frameCount = 128;
|
|
239
|
+
std::vector<float> buffer(frameCount * 2, 0.0f);
|
|
240
|
+
|
|
241
|
+
card.generateStereoSamples(buffer.data(), frameCount, 48000, 0, 2730);
|
|
242
|
+
REQUIRE(true); // No crash
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// I/O space (unused by Mockingboard)
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
TEST_CASE("MockingboardCard readIO returns a value without crash", "[mockingboard]") {
|
|
250
|
+
MockingboardCard card;
|
|
251
|
+
// Mockingboard does not use I/O space, but calling it should not crash
|
|
252
|
+
uint8_t val = card.readIO(0x00);
|
|
253
|
+
(void)val;
|
|
254
|
+
REQUIRE(true);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
TEST_CASE("MockingboardCard writeIO does not crash", "[mockingboard]") {
|
|
258
|
+
MockingboardCard card;
|
|
259
|
+
card.writeIO(0x00, 0x55);
|
|
260
|
+
REQUIRE(true);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
// Serialization round-trip
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
TEST_CASE("MockingboardCard getStateSize returns expected size", "[mockingboard]") {
|
|
268
|
+
MockingboardCard card;
|
|
269
|
+
REQUIRE(card.getStateSize() == MockingboardCard::STATE_SIZE);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
TEST_CASE("MockingboardCard serialize/deserialize round-trip", "[mockingboard]") {
|
|
273
|
+
MockingboardCard card1;
|
|
274
|
+
|
|
275
|
+
// Set up some state: write to PSG via VIA1
|
|
276
|
+
card1.writeROM(VIA_DDRA, 0xFF);
|
|
277
|
+
card1.writeROM(VIA_DDRB, 0xFF);
|
|
278
|
+
card1.writeROM(VIA_ORA, 0x07); // register address = 7
|
|
279
|
+
card1.writeROM(VIA_ORB, 0x07); // LATCH
|
|
280
|
+
card1.writeROM(VIA_ORB, 0x04); // INACTIVE
|
|
281
|
+
card1.writeROM(VIA_ORA, 0x38); // value
|
|
282
|
+
card1.writeROM(VIA_ORB, 0x06); // WRITE
|
|
283
|
+
card1.writeROM(VIA_ORB, 0x04); // INACTIVE
|
|
284
|
+
|
|
285
|
+
// Serialize
|
|
286
|
+
std::vector<uint8_t> buffer(card1.getStateSize());
|
|
287
|
+
size_t written = card1.serialize(buffer.data(), buffer.size());
|
|
288
|
+
REQUIRE(written > 0);
|
|
289
|
+
REQUIRE(written <= buffer.size());
|
|
290
|
+
|
|
291
|
+
// Deserialize into a new card
|
|
292
|
+
MockingboardCard card2;
|
|
293
|
+
size_t consumed = card2.deserialize(buffer.data(), written);
|
|
294
|
+
REQUIRE(consumed > 0);
|
|
295
|
+
|
|
296
|
+
// Verify PSG state was preserved
|
|
297
|
+
REQUIRE(card2.getPSG1().getRegister(7) == card1.getPSG1().getRegister(7));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
// IRQ: set VIA timer, update until fires, check isIRQActive
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
TEST_CASE("MockingboardCard IRQ fires from VIA1 Timer 1", "[mockingboard]") {
|
|
305
|
+
MockingboardCard card;
|
|
306
|
+
|
|
307
|
+
// Track IRQ firing via callback
|
|
308
|
+
bool irqFired = false;
|
|
309
|
+
card.setIRQCallback([&irqFired]() {
|
|
310
|
+
irqFired = true;
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Enable Timer 1 interrupt in VIA1:
|
|
314
|
+
// Write to IER: set bit 7 (enable) and bit 6 (T1)
|
|
315
|
+
card.writeROM(VIA_IER, 0xC0);
|
|
316
|
+
|
|
317
|
+
// Set ACR for one-shot mode (bit 6 = 0)
|
|
318
|
+
card.writeROM(VIA_ACR, 0x00);
|
|
319
|
+
|
|
320
|
+
// Load a short timer value: latch low then high starts the timer
|
|
321
|
+
card.writeROM(VIA_T1CL, 0x05); // low byte = 5
|
|
322
|
+
card.writeROM(VIA_T1CH, 0x00); // high byte = 0 (starts timer)
|
|
323
|
+
|
|
324
|
+
// Update enough cycles for the timer to fire (timer counts down from 5)
|
|
325
|
+
for (int i = 0; i < 20; ++i) {
|
|
326
|
+
card.update(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Either the IRQ callback was called or the IRQ flag is set
|
|
330
|
+
bool irqActive = card.isIRQActive() || irqFired;
|
|
331
|
+
REQUIRE(irqActive);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
// Debug accessors
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
|
|
338
|
+
TEST_CASE("MockingboardCard debug accessors return VIA/PSG references", "[mockingboard]") {
|
|
339
|
+
MockingboardCard card;
|
|
340
|
+
|
|
341
|
+
const VIA6522& via1 = card.getVIA1();
|
|
342
|
+
const VIA6522& via2 = card.getVIA2();
|
|
343
|
+
const AY8910& psg1 = card.getPSG1();
|
|
344
|
+
const AY8910& psg2 = card.getPSG2();
|
|
345
|
+
|
|
346
|
+
// Just verify the accessors work and return references
|
|
347
|
+
(void)via1.getDDRA();
|
|
348
|
+
(void)via2.getDDRA();
|
|
349
|
+
(void)psg1.getRegister(0);
|
|
350
|
+
(void)psg2.getRegister(0);
|
|
351
|
+
REQUIRE(true);
|
|
352
|
+
}
|