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,555 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_mmu.cpp - Unit tests for MMU (Memory Management Unit)
|
|
3
|
+
*
|
|
4
|
+
* Tests the core memory management unit including basic read/write,
|
|
5
|
+
* ROM access, zero page, stack, Language Card switching, auxiliary
|
|
6
|
+
* memory, soft switches, callbacks, paddle input, memory tracking,
|
|
7
|
+
* peek(), and reset().
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#define CATCH_CONFIG_MAIN
|
|
11
|
+
#include "catch.hpp"
|
|
12
|
+
|
|
13
|
+
#include "mmu/mmu.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
|
+
// Basic read/write
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
TEST_CASE("MMU basic write and read back at $0400", "[mmu][basic]") {
|
|
33
|
+
auto mmu = createMMU();
|
|
34
|
+
|
|
35
|
+
mmu->write(0x0400, 0x42);
|
|
36
|
+
REQUIRE(mmu->read(0x0400) == 0x42);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
TEST_CASE("MMU write and read multiple locations", "[mmu][basic]") {
|
|
40
|
+
auto mmu = createMMU();
|
|
41
|
+
|
|
42
|
+
mmu->write(0x0400, 0xAA);
|
|
43
|
+
mmu->write(0x0401, 0xBB);
|
|
44
|
+
mmu->write(0x0402, 0xCC);
|
|
45
|
+
|
|
46
|
+
CHECK(mmu->read(0x0400) == 0xAA);
|
|
47
|
+
CHECK(mmu->read(0x0401) == 0xBB);
|
|
48
|
+
CHECK(mmu->read(0x0402) == 0xCC);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// ROM read
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
TEST_CASE("MMU reads reset vector from ROM at $FFFC/$FFFD", "[mmu][rom]") {
|
|
56
|
+
auto mmu = createMMU();
|
|
57
|
+
|
|
58
|
+
// The reset vector is at the end of the 16KB ROM.
|
|
59
|
+
// ROM offset for $FFFC = $FFFC - $C000 = $3FFC
|
|
60
|
+
uint8_t lo = roms::ROM_SYSTEM[0x3FFC];
|
|
61
|
+
uint8_t hi = roms::ROM_SYSTEM[0x3FFD];
|
|
62
|
+
|
|
63
|
+
// Default is ROM read (lcram is false after reset)
|
|
64
|
+
CHECK(mmu->read(0xFFFC) == lo);
|
|
65
|
+
CHECK(mmu->read(0xFFFD) == hi);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
TEST_CASE("MMU reads ROM correctly at $C100 range with INTCXROM", "[mmu][rom]") {
|
|
69
|
+
auto mmu = createMMU();
|
|
70
|
+
|
|
71
|
+
// Enable INTCXROM so $C100-$CFFF reads internal ROM
|
|
72
|
+
mmu->write(0xC007, 0); // INTCXROM on
|
|
73
|
+
|
|
74
|
+
// Internal ROM byte at $C100 = systemROM_[$0100]
|
|
75
|
+
uint8_t expected = roms::ROM_SYSTEM[0x0100];
|
|
76
|
+
CHECK(mmu->read(0xC100) == expected);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Zero page
|
|
81
|
+
// ============================================================================
|
|
82
|
+
|
|
83
|
+
TEST_CASE("MMU zero page write and read back", "[mmu][zeropage]") {
|
|
84
|
+
auto mmu = createMMU();
|
|
85
|
+
|
|
86
|
+
for (int i = 0; i < 256; ++i) {
|
|
87
|
+
mmu->write(static_cast<uint16_t>(i), static_cast<uint8_t>(i ^ 0xA5));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (int i = 0; i < 256; ++i) {
|
|
91
|
+
INFO("Zero page address $" << std::hex << i);
|
|
92
|
+
REQUIRE(mmu->read(static_cast<uint16_t>(i)) == static_cast<uint8_t>(i ^ 0xA5));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ============================================================================
|
|
97
|
+
// Stack page
|
|
98
|
+
// ============================================================================
|
|
99
|
+
|
|
100
|
+
TEST_CASE("MMU stack page write and read back", "[mmu][stack]") {
|
|
101
|
+
auto mmu = createMMU();
|
|
102
|
+
|
|
103
|
+
for (int i = 0; i < 256; ++i) {
|
|
104
|
+
uint16_t addr = 0x0100 + i;
|
|
105
|
+
mmu->write(addr, static_cast<uint8_t>(i));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (int i = 0; i < 256; ++i) {
|
|
109
|
+
uint16_t addr = 0x0100 + i;
|
|
110
|
+
INFO("Stack page address $" << std::hex << addr);
|
|
111
|
+
REQUIRE(mmu->read(addr) == static_cast<uint8_t>(i));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// Language Card switches
|
|
117
|
+
// ============================================================================
|
|
118
|
+
|
|
119
|
+
TEST_CASE("Language Card: read $C08B twice enables LC write", "[mmu][lc]") {
|
|
120
|
+
auto mmu = createMMU();
|
|
121
|
+
|
|
122
|
+
// $C08B = $C080 | 0x0B => reg = 0x8B, op = 3, bank2 = false (bank 1)
|
|
123
|
+
// op=3 => read RAM, write enable on second read
|
|
124
|
+
mmu->read(0xC08B); // First read: prewrite = true, write still false
|
|
125
|
+
CHECK_FALSE(mmu->getSoftSwitches().lcwrite);
|
|
126
|
+
|
|
127
|
+
mmu->read(0xC08B); // Second read: write enabled
|
|
128
|
+
CHECK(mmu->getSoftSwitches().lcwrite);
|
|
129
|
+
CHECK(mmu->getSoftSwitches().lcram); // Reading RAM enabled
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
TEST_CASE("Language Card: $C080 enables LC read RAM, disables write", "[mmu][lc]") {
|
|
133
|
+
auto mmu = createMMU();
|
|
134
|
+
|
|
135
|
+
// First enable write via double read of $C08B
|
|
136
|
+
mmu->read(0xC08B);
|
|
137
|
+
mmu->read(0xC08B);
|
|
138
|
+
REQUIRE(mmu->getSoftSwitches().lcwrite);
|
|
139
|
+
|
|
140
|
+
// $C080: op=0 => read RAM, write disabled
|
|
141
|
+
mmu->read(0xC080);
|
|
142
|
+
CHECK(mmu->getSoftSwitches().lcram);
|
|
143
|
+
CHECK_FALSE(mmu->getSoftSwitches().lcwrite);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
TEST_CASE("Language Card: write to LC RAM after enabling write", "[mmu][lc]") {
|
|
147
|
+
auto mmu = createMMU();
|
|
148
|
+
|
|
149
|
+
// Enable LC RAM read + write (bank 2): $C083 twice
|
|
150
|
+
mmu->read(0xC083);
|
|
151
|
+
mmu->read(0xC083);
|
|
152
|
+
REQUIRE(mmu->getSoftSwitches().lcram);
|
|
153
|
+
REQUIRE(mmu->getSoftSwitches().lcwrite);
|
|
154
|
+
|
|
155
|
+
// Write to $E000 and read back
|
|
156
|
+
mmu->write(0xE000, 0xDE);
|
|
157
|
+
CHECK(mmu->read(0xE000) == 0xDE);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// LC bank switching
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
TEST_CASE("Language Card: $C088 selects bank 1, $C080 selects bank 2", "[mmu][lc][bank]") {
|
|
165
|
+
auto mmu = createMMU();
|
|
166
|
+
|
|
167
|
+
// $C088: reg=0x88, bank2 = !(0x88 & 0x08) = !(0x08) = false => bank 1
|
|
168
|
+
mmu->read(0xC088);
|
|
169
|
+
CHECK_FALSE(mmu->getSoftSwitches().lcram2); // lcram2=false means bank 1
|
|
170
|
+
|
|
171
|
+
// $C080: reg=0x80, bank2 = !(0x80 & 0x08) = !(0x00) = true => bank 2
|
|
172
|
+
mmu->read(0xC080);
|
|
173
|
+
CHECK(mmu->getSoftSwitches().lcram2); // lcram2=true means bank 2
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
TEST_CASE("Language Card: bank 1 and bank 2 are independent", "[mmu][lc][bank]") {
|
|
177
|
+
auto mmu = createMMU();
|
|
178
|
+
|
|
179
|
+
// Enable LC RAM read + write bank 1: $C08B twice
|
|
180
|
+
mmu->read(0xC08B);
|
|
181
|
+
mmu->read(0xC08B);
|
|
182
|
+
mmu->write(0xD000, 0xAA);
|
|
183
|
+
|
|
184
|
+
// Switch to bank 2: $C083 twice
|
|
185
|
+
mmu->read(0xC083);
|
|
186
|
+
mmu->read(0xC083);
|
|
187
|
+
mmu->write(0xD000, 0xBB);
|
|
188
|
+
|
|
189
|
+
// Read bank 2
|
|
190
|
+
CHECK(mmu->read(0xD000) == 0xBB);
|
|
191
|
+
|
|
192
|
+
// Switch back to bank 1 and verify
|
|
193
|
+
mmu->read(0xC08B);
|
|
194
|
+
mmu->read(0xC08B);
|
|
195
|
+
CHECK(mmu->read(0xD000) == 0xAA);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// Auxiliary memory (RAMWRT / RAMRD)
|
|
200
|
+
// ============================================================================
|
|
201
|
+
|
|
202
|
+
TEST_CASE("Aux memory: RAMWRT routes writes to aux, read stays main", "[mmu][aux]") {
|
|
203
|
+
auto mmu = createMMU();
|
|
204
|
+
|
|
205
|
+
// Write initial value to main memory
|
|
206
|
+
mmu->write(0x0900, 0x11);
|
|
207
|
+
CHECK(mmu->read(0x0900) == 0x11);
|
|
208
|
+
|
|
209
|
+
// Enable RAMWRT (writes go to aux)
|
|
210
|
+
mmu->write(0xC005, 0); // RAMWRT on
|
|
211
|
+
mmu->write(0x0900, 0x22);
|
|
212
|
+
|
|
213
|
+
// Disable RAMWRT
|
|
214
|
+
mmu->write(0xC004, 0); // RAMWRT off
|
|
215
|
+
|
|
216
|
+
// Main memory should still have old value
|
|
217
|
+
CHECK(mmu->read(0x0900) == 0x11);
|
|
218
|
+
|
|
219
|
+
// Aux memory should have new value
|
|
220
|
+
CHECK(mmu->readRAM(0x0900, true) == 0x22);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
TEST_CASE("Aux memory: RAMRD routes reads to aux", "[mmu][aux]") {
|
|
224
|
+
auto mmu = createMMU();
|
|
225
|
+
|
|
226
|
+
// Write different values to main and aux
|
|
227
|
+
mmu->writeRAM(0x0900, 0xAA, false);
|
|
228
|
+
mmu->writeRAM(0x0900, 0xBB, true);
|
|
229
|
+
|
|
230
|
+
// Default: read from main
|
|
231
|
+
CHECK(mmu->read(0x0900) == 0xAA);
|
|
232
|
+
|
|
233
|
+
// Enable RAMRD
|
|
234
|
+
mmu->write(0xC003, 0); // RAMRD on
|
|
235
|
+
CHECK(mmu->read(0x0900) == 0xBB);
|
|
236
|
+
|
|
237
|
+
// Disable RAMRD
|
|
238
|
+
mmu->write(0xC002, 0); // RAMRD off
|
|
239
|
+
CHECK(mmu->read(0x0900) == 0xAA);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ============================================================================
|
|
243
|
+
// ALTZP
|
|
244
|
+
// ============================================================================
|
|
245
|
+
|
|
246
|
+
TEST_CASE("ALTZP: routes zero page to aux memory", "[mmu][altzp]") {
|
|
247
|
+
auto mmu = createMMU();
|
|
248
|
+
|
|
249
|
+
// Write to main zero page
|
|
250
|
+
mmu->write(0x0010, 0xAA);
|
|
251
|
+
|
|
252
|
+
// Write different value to aux zero page
|
|
253
|
+
mmu->writeRAM(0x0010, 0xBB, true);
|
|
254
|
+
|
|
255
|
+
// Default: main zero page
|
|
256
|
+
CHECK(mmu->read(0x0010) == 0xAA);
|
|
257
|
+
|
|
258
|
+
// Enable ALTZP
|
|
259
|
+
mmu->write(0xC009, 0);
|
|
260
|
+
CHECK(mmu->read(0x0010) == 0xBB);
|
|
261
|
+
|
|
262
|
+
// Disable ALTZP
|
|
263
|
+
mmu->write(0xC008, 0);
|
|
264
|
+
CHECK(mmu->read(0x0010) == 0xAA);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// 80STORE + PAGE2
|
|
269
|
+
// ============================================================================
|
|
270
|
+
|
|
271
|
+
TEST_CASE("80STORE+PAGE2: writes to $0400-$07FF go to aux", "[mmu][80store]") {
|
|
272
|
+
auto mmu = createMMU();
|
|
273
|
+
|
|
274
|
+
// Write initial value to main text page
|
|
275
|
+
mmu->write(0x0400, 0x11);
|
|
276
|
+
|
|
277
|
+
// Enable 80STORE
|
|
278
|
+
mmu->write(0xC001, 0); // store80 on
|
|
279
|
+
|
|
280
|
+
// Enable PAGE2
|
|
281
|
+
mmu->write(0xC055, 0); // page2 on (read or write toggles it)
|
|
282
|
+
|
|
283
|
+
// Write to $0400 - should go to aux due to 80STORE+PAGE2
|
|
284
|
+
mmu->write(0x0400, 0x22);
|
|
285
|
+
|
|
286
|
+
// Disable PAGE2 and 80STORE to read main
|
|
287
|
+
mmu->write(0xC054, 0); // page2 off
|
|
288
|
+
mmu->write(0xC000, 0); // store80 off
|
|
289
|
+
|
|
290
|
+
// Main should still have original value
|
|
291
|
+
CHECK(mmu->read(0x0400) == 0x11);
|
|
292
|
+
|
|
293
|
+
// Aux should have the new value
|
|
294
|
+
CHECK(mmu->readRAM(0x0400, true) == 0x22);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ============================================================================
|
|
298
|
+
// Display soft switches
|
|
299
|
+
// ============================================================================
|
|
300
|
+
|
|
301
|
+
TEST_CASE("Display switches: reading $C050-$C057 toggles switches", "[mmu][switches]") {
|
|
302
|
+
auto mmu = createMMU();
|
|
303
|
+
|
|
304
|
+
// Default: text=true
|
|
305
|
+
CHECK(mmu->getSoftSwitches().text);
|
|
306
|
+
|
|
307
|
+
// $C050 = TXTCLR = graphics mode
|
|
308
|
+
mmu->read(0xC050);
|
|
309
|
+
CHECK_FALSE(mmu->getSoftSwitches().text);
|
|
310
|
+
|
|
311
|
+
// $C051 = TXTSET = text mode
|
|
312
|
+
mmu->read(0xC051);
|
|
313
|
+
CHECK(mmu->getSoftSwitches().text);
|
|
314
|
+
|
|
315
|
+
// $C052 = MIXCLR
|
|
316
|
+
mmu->read(0xC053); // MIXSET on
|
|
317
|
+
CHECK(mmu->getSoftSwitches().mixed);
|
|
318
|
+
mmu->read(0xC052); // MIXCLR off
|
|
319
|
+
CHECK_FALSE(mmu->getSoftSwitches().mixed);
|
|
320
|
+
|
|
321
|
+
// $C054/$C055 = PAGE1/PAGE2
|
|
322
|
+
mmu->read(0xC055);
|
|
323
|
+
CHECK(mmu->getSoftSwitches().page2);
|
|
324
|
+
mmu->read(0xC054);
|
|
325
|
+
CHECK_FALSE(mmu->getSoftSwitches().page2);
|
|
326
|
+
|
|
327
|
+
// $C056/$C057 = LORES/HIRES
|
|
328
|
+
mmu->read(0xC057);
|
|
329
|
+
CHECK(mmu->getSoftSwitches().hires);
|
|
330
|
+
mmu->read(0xC056);
|
|
331
|
+
CHECK_FALSE(mmu->getSoftSwitches().hires);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ============================================================================
|
|
335
|
+
// Keyboard callback
|
|
336
|
+
// ============================================================================
|
|
337
|
+
|
|
338
|
+
TEST_CASE("Keyboard callback: read $C000 returns callback value", "[mmu][keyboard]") {
|
|
339
|
+
auto mmu = createMMU();
|
|
340
|
+
|
|
341
|
+
mmu->setKeyboardCallback([]() -> uint8_t { return 0xC1; }); // 'A' with high bit
|
|
342
|
+
|
|
343
|
+
uint8_t val = mmu->read(0xC000);
|
|
344
|
+
CHECK(val == 0xC1);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ============================================================================
|
|
348
|
+
// Speaker callback
|
|
349
|
+
// ============================================================================
|
|
350
|
+
|
|
351
|
+
TEST_CASE("Speaker callback: read $C030 triggers speaker callback", "[mmu][speaker]") {
|
|
352
|
+
auto mmu = createMMU();
|
|
353
|
+
|
|
354
|
+
bool called = false;
|
|
355
|
+
mmu->setSpeakerCallback([&called]() { called = true; });
|
|
356
|
+
|
|
357
|
+
mmu->read(0xC030);
|
|
358
|
+
CHECK(called);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
TEST_CASE("Speaker callback: write $C030 also triggers speaker callback", "[mmu][speaker]") {
|
|
362
|
+
auto mmu = createMMU();
|
|
363
|
+
|
|
364
|
+
bool called = false;
|
|
365
|
+
mmu->setSpeakerCallback([&called]() { called = true; });
|
|
366
|
+
|
|
367
|
+
mmu->write(0xC030, 0x00);
|
|
368
|
+
CHECK(called);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ============================================================================
|
|
372
|
+
// Paddle values
|
|
373
|
+
// ============================================================================
|
|
374
|
+
|
|
375
|
+
TEST_CASE("Paddle: setPaddleValue and getPaddleValue", "[mmu][paddle]") {
|
|
376
|
+
auto mmu = createMMU();
|
|
377
|
+
|
|
378
|
+
mmu->setPaddleValue(0, 200);
|
|
379
|
+
CHECK(mmu->getPaddleValue(0) == 200);
|
|
380
|
+
|
|
381
|
+
mmu->setPaddleValue(1, 50);
|
|
382
|
+
CHECK(mmu->getPaddleValue(1) == 50);
|
|
383
|
+
|
|
384
|
+
// Out of range paddle returns 128 (default)
|
|
385
|
+
CHECK(mmu->getPaddleValue(5) == 128);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
TEST_CASE("Paddle: read $C064 returns timer state based on cycles", "[mmu][paddle]") {
|
|
389
|
+
auto mmu = createMMU();
|
|
390
|
+
|
|
391
|
+
uint64_t currentCycle = 0;
|
|
392
|
+
mmu->setCycleCallback([¤tCycle]() -> uint64_t { return currentCycle; });
|
|
393
|
+
|
|
394
|
+
mmu->setPaddleValue(0, 100);
|
|
395
|
+
|
|
396
|
+
// Trigger paddle timers
|
|
397
|
+
mmu->read(0xC070);
|
|
398
|
+
|
|
399
|
+
// Immediately after trigger: timer should be running (bit 7 = 1)
|
|
400
|
+
uint8_t val = mmu->read(0xC064);
|
|
401
|
+
CHECK((val & 0x80) == 0x80);
|
|
402
|
+
|
|
403
|
+
// Advance cycles past timer duration (100 * 11 = 1100 cycles)
|
|
404
|
+
currentCycle = 1200;
|
|
405
|
+
val = mmu->read(0xC064);
|
|
406
|
+
CHECK((val & 0x80) == 0x00);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ============================================================================
|
|
410
|
+
// Memory tracking
|
|
411
|
+
// ============================================================================
|
|
412
|
+
|
|
413
|
+
TEST_CASE("Memory tracking: enableTracking records read and write counts", "[mmu][tracking]") {
|
|
414
|
+
auto mmu = createMMU();
|
|
415
|
+
|
|
416
|
+
mmu->enableTracking(true);
|
|
417
|
+
mmu->clearTracking();
|
|
418
|
+
|
|
419
|
+
// Perform some reads and writes
|
|
420
|
+
mmu->write(0x0800, 0x42);
|
|
421
|
+
mmu->read(0x0800);
|
|
422
|
+
mmu->read(0x0800);
|
|
423
|
+
|
|
424
|
+
const uint8_t* readCounts = mmu->getReadCounts();
|
|
425
|
+
const uint8_t* writeCounts = mmu->getWriteCounts();
|
|
426
|
+
|
|
427
|
+
CHECK(readCounts[0x0800] == 2);
|
|
428
|
+
CHECK(writeCounts[0x0800] == 1);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
TEST_CASE("Memory tracking: clearTracking resets all counts", "[mmu][tracking]") {
|
|
432
|
+
auto mmu = createMMU();
|
|
433
|
+
|
|
434
|
+
mmu->enableTracking(true);
|
|
435
|
+
mmu->write(0x0800, 0x42);
|
|
436
|
+
mmu->read(0x0800);
|
|
437
|
+
|
|
438
|
+
mmu->clearTracking();
|
|
439
|
+
|
|
440
|
+
CHECK(mmu->getReadCounts()[0x0800] == 0);
|
|
441
|
+
CHECK(mmu->getWriteCounts()[0x0800] == 0);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
TEST_CASE("Memory tracking: decayTracking reduces counts", "[mmu][tracking]") {
|
|
445
|
+
auto mmu = createMMU();
|
|
446
|
+
|
|
447
|
+
mmu->enableTracking(true);
|
|
448
|
+
mmu->clearTracking();
|
|
449
|
+
|
|
450
|
+
// Read 5 times
|
|
451
|
+
for (int i = 0; i < 5; ++i) {
|
|
452
|
+
mmu->read(0x0800);
|
|
453
|
+
}
|
|
454
|
+
REQUIRE(mmu->getReadCounts()[0x0800] == 5);
|
|
455
|
+
|
|
456
|
+
mmu->decayTracking(2);
|
|
457
|
+
CHECK(mmu->getReadCounts()[0x0800] == 3);
|
|
458
|
+
|
|
459
|
+
// Decay past zero should clamp to 0
|
|
460
|
+
mmu->decayTracking(10);
|
|
461
|
+
CHECK(mmu->getReadCounts()[0x0800] == 0);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ============================================================================
|
|
465
|
+
// peek() - non-side-effecting read
|
|
466
|
+
// ============================================================================
|
|
467
|
+
|
|
468
|
+
TEST_CASE("peek does not trigger side effects", "[mmu][peek]") {
|
|
469
|
+
auto mmu = createMMU();
|
|
470
|
+
|
|
471
|
+
// Write a known value to test page
|
|
472
|
+
mmu->write(0x0400, 0x42);
|
|
473
|
+
|
|
474
|
+
// peek should return the value
|
|
475
|
+
CHECK(mmu->peek(0x0400) == 0x42);
|
|
476
|
+
|
|
477
|
+
// peek of speaker address should NOT trigger speaker callback
|
|
478
|
+
bool speakerCalled = false;
|
|
479
|
+
mmu->setSpeakerCallback([&speakerCalled]() { speakerCalled = true; });
|
|
480
|
+
mmu->peek(0xC030);
|
|
481
|
+
CHECK_FALSE(speakerCalled);
|
|
482
|
+
|
|
483
|
+
// peek of keyboard address should NOT trigger keyboard callback
|
|
484
|
+
bool keyboardCalled = false;
|
|
485
|
+
mmu->setKeyboardCallback([&keyboardCalled]() -> uint8_t {
|
|
486
|
+
keyboardCalled = true;
|
|
487
|
+
return 0xC1;
|
|
488
|
+
});
|
|
489
|
+
mmu->peek(0xC000);
|
|
490
|
+
CHECK_FALSE(keyboardCalled);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
TEST_CASE("peek does not update memory tracking", "[mmu][peek]") {
|
|
494
|
+
auto mmu = createMMU();
|
|
495
|
+
|
|
496
|
+
mmu->enableTracking(true);
|
|
497
|
+
mmu->clearTracking();
|
|
498
|
+
|
|
499
|
+
mmu->write(0x0800, 0x42);
|
|
500
|
+
mmu->peek(0x0800);
|
|
501
|
+
|
|
502
|
+
// peek should NOT increment read count
|
|
503
|
+
CHECK(mmu->getReadCounts()[0x0800] == 0);
|
|
504
|
+
// write should still have been tracked
|
|
505
|
+
CHECK(mmu->getWriteCounts()[0x0800] == 1);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// ============================================================================
|
|
509
|
+
// reset
|
|
510
|
+
// ============================================================================
|
|
511
|
+
|
|
512
|
+
TEST_CASE("reset restores defaults", "[mmu][reset]") {
|
|
513
|
+
auto mmu = createMMU();
|
|
514
|
+
|
|
515
|
+
// Change many switches
|
|
516
|
+
mmu->write(0xC005, 0); // RAMWRT on
|
|
517
|
+
mmu->write(0xC003, 0); // RAMRD on
|
|
518
|
+
mmu->write(0xC009, 0); // ALTZP on
|
|
519
|
+
mmu->write(0xC001, 0); // 80STORE on
|
|
520
|
+
mmu->read(0xC057); // HIRES on
|
|
521
|
+
mmu->read(0xC050); // TEXT off
|
|
522
|
+
|
|
523
|
+
// Write some data
|
|
524
|
+
mmu->writeRAM(0x0400, 0x42, false);
|
|
525
|
+
|
|
526
|
+
mmu->reset();
|
|
527
|
+
|
|
528
|
+
const auto& sw = mmu->getSoftSwitches();
|
|
529
|
+
CHECK_FALSE(sw.ramwrt);
|
|
530
|
+
CHECK_FALSE(sw.ramrd);
|
|
531
|
+
CHECK_FALSE(sw.altzp);
|
|
532
|
+
CHECK_FALSE(sw.store80);
|
|
533
|
+
CHECK_FALSE(sw.hires);
|
|
534
|
+
CHECK(sw.text); // text defaults to true
|
|
535
|
+
|
|
536
|
+
// RAM should be cleared
|
|
537
|
+
CHECK(mmu->read(0x0400) == 0x00);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
TEST_CASE("warmReset resets switches but preserves RAM", "[mmu][reset]") {
|
|
541
|
+
auto mmu = createMMU();
|
|
542
|
+
|
|
543
|
+
// Write data and change switches
|
|
544
|
+
mmu->write(0x0400, 0x42);
|
|
545
|
+
mmu->write(0xC005, 0); // RAMWRT on
|
|
546
|
+
|
|
547
|
+
mmu->warmReset();
|
|
548
|
+
|
|
549
|
+
const auto& sw = mmu->getSoftSwitches();
|
|
550
|
+
CHECK_FALSE(sw.ramwrt);
|
|
551
|
+
CHECK(sw.text);
|
|
552
|
+
|
|
553
|
+
// RAM should be preserved
|
|
554
|
+
CHECK(mmu->read(0x0400) == 0x42);
|
|
555
|
+
}
|