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,354 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_thunderclock.cpp - Unit tests for ThunderclockCard (Catch2)
|
|
3
|
+
*
|
|
4
|
+
* Tests the Thunderclock Plus real-time clock card implementation including:
|
|
5
|
+
* - Construction and ROM loading
|
|
6
|
+
* - Card metadata (name, preferred slot)
|
|
7
|
+
* - ROM presence and expansion ROM
|
|
8
|
+
* - ProDOS ROM signature bytes
|
|
9
|
+
* - I/O read/write for time data
|
|
10
|
+
* - Reset behavior
|
|
11
|
+
* - Serialization round-trip
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#define CATCH_CONFIG_MAIN
|
|
15
|
+
#include "catch.hpp"
|
|
16
|
+
|
|
17
|
+
#include "thunderclock_card.hpp"
|
|
18
|
+
#include "roms.cpp"
|
|
19
|
+
|
|
20
|
+
#include <cstring>
|
|
21
|
+
#include <ctime>
|
|
22
|
+
#include <vector>
|
|
23
|
+
|
|
24
|
+
using namespace a2e;
|
|
25
|
+
|
|
26
|
+
// Control register flags (matching the existing tests)
|
|
27
|
+
static constexpr uint8_t FLAG_CLOCK = 0x02;
|
|
28
|
+
static constexpr uint8_t FLAG_STROBE = 0x04;
|
|
29
|
+
|
|
30
|
+
// Time read command: bits 3-5 encode the uPD1990C command.
|
|
31
|
+
// CMD_TIMEREAD = 0x03 (binary 011), so shifted into bits 3-5: 0x03 << 3 = 0x18
|
|
32
|
+
static constexpr uint8_t CMD_TIMED = 0x18;
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Construction
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
TEST_CASE("ThunderclockCard constructor creates valid instance", "[thunderclock]") {
|
|
39
|
+
ThunderclockCard card;
|
|
40
|
+
REQUIRE(card.getName() != nullptr);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Card metadata
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
TEST_CASE("ThunderclockCard getName returns Thunderclock", "[thunderclock]") {
|
|
48
|
+
ThunderclockCard card;
|
|
49
|
+
REQUIRE(std::string(card.getName()) == "Thunderclock");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
TEST_CASE("ThunderclockCard getPreferredSlot returns 5", "[thunderclock]") {
|
|
53
|
+
ThunderclockCard card;
|
|
54
|
+
REQUIRE(card.getPreferredSlot() == 5);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// ROM presence
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
TEST_CASE("ThunderclockCard hasROM returns true", "[thunderclock]") {
|
|
62
|
+
ThunderclockCard card;
|
|
63
|
+
REQUIRE(card.hasROM());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
TEST_CASE("ThunderclockCard hasExpansionROM returns true", "[thunderclock]") {
|
|
67
|
+
ThunderclockCard card;
|
|
68
|
+
REQUIRE(card.hasExpansionROM());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// ProDOS ROM signature bytes
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
TEST_CASE("ThunderclockCard ROM signature byte at offset 0x00 is 0x08", "[thunderclock]") {
|
|
76
|
+
ThunderclockCard card;
|
|
77
|
+
REQUIRE(card.readROM(0x00) == 0x08);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
TEST_CASE("ThunderclockCard ROM signature byte at offset 0x02 is 0x28", "[thunderclock]") {
|
|
81
|
+
ThunderclockCard card;
|
|
82
|
+
REQUIRE(card.readROM(0x02) == 0x28);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
TEST_CASE("ThunderclockCard ROM signature byte at offset 0x04 is 0x58", "[thunderclock]") {
|
|
86
|
+
ThunderclockCard card;
|
|
87
|
+
REQUIRE(card.readROM(0x04) == 0x58);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
TEST_CASE("ThunderclockCard ROM signature byte at offset 0x06 is 0x70", "[thunderclock]") {
|
|
91
|
+
ThunderclockCard card;
|
|
92
|
+
REQUIRE(card.readROM(0x06) == 0x70);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
TEST_CASE("ThunderclockCard all four ProDOS signature bytes match", "[thunderclock]") {
|
|
96
|
+
ThunderclockCard card;
|
|
97
|
+
REQUIRE(card.readROM(0x00) == 0x08);
|
|
98
|
+
REQUIRE(card.readROM(0x02) == 0x28);
|
|
99
|
+
REQUIRE(card.readROM(0x04) == 0x58);
|
|
100
|
+
REQUIRE(card.readROM(0x06) == 0x70);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// I/O read initial state
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
TEST_CASE("ThunderclockCard initial I/O read returns 0x00", "[thunderclock]") {
|
|
108
|
+
ThunderclockCard card;
|
|
109
|
+
REQUIRE(card.readIO(0x00) == 0x00);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
TEST_CASE("ThunderclockCard all I/O offsets return same register", "[thunderclock]") {
|
|
113
|
+
ThunderclockCard card;
|
|
114
|
+
|
|
115
|
+
// Issue a command so there is non-trivial state
|
|
116
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
117
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
118
|
+
|
|
119
|
+
uint8_t expected = card.readIO(0x00);
|
|
120
|
+
for (int offset = 1; offset < 16; ++offset) {
|
|
121
|
+
REQUIRE(card.readIO(static_cast<uint8_t>(offset)) == expected);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
TEST_CASE("ThunderclockCard peekIO matches readIO", "[thunderclock]") {
|
|
126
|
+
ThunderclockCard card;
|
|
127
|
+
REQUIRE(card.readIO(0x00) == card.peekIO(0x00));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// I/O read/write for time data
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
TEST_CASE("ThunderclockCard time command loads data via clock bits", "[thunderclock]") {
|
|
135
|
+
ThunderclockCard card;
|
|
136
|
+
|
|
137
|
+
// Issue CMD_TIMED with strobe rising edge
|
|
138
|
+
card.writeIO(0x00, 0x00);
|
|
139
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
140
|
+
|
|
141
|
+
// Clock out the first bit
|
|
142
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
143
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
144
|
+
|
|
145
|
+
// Read should now have data in bit 7
|
|
146
|
+
uint8_t value = card.readIO(0x00);
|
|
147
|
+
// The value in bit 7 is the first time data bit (0 or 1)
|
|
148
|
+
// Just verify no crash and the register is accessible
|
|
149
|
+
REQUIRE((value == (value & 0xFF))); // always true, just ensuring no UB
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
TEST_CASE("ThunderclockCard clock out 40 bits produces valid time", "[thunderclock]") {
|
|
153
|
+
ThunderclockCard card;
|
|
154
|
+
|
|
155
|
+
// Issue time read command with strobe rising edge
|
|
156
|
+
card.writeIO(0x00, 0x00);
|
|
157
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
158
|
+
|
|
159
|
+
// Bit 0 is immediately available after strobe (no clock needed)
|
|
160
|
+
std::vector<int> bits;
|
|
161
|
+
bits.reserve(40);
|
|
162
|
+
|
|
163
|
+
// Read bit 0 (already loaded by strobe)
|
|
164
|
+
uint8_t value = card.readIO(0x00);
|
|
165
|
+
bits.push_back((value & 0x80) ? 1 : 0);
|
|
166
|
+
|
|
167
|
+
// Clock out remaining 39 bits (each clock rising edge advances to next bit)
|
|
168
|
+
for (int i = 1; i < 40; ++i) {
|
|
169
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE); // Clock low
|
|
170
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK); // Clock rising edge
|
|
171
|
+
value = card.readIO(0x00);
|
|
172
|
+
bits.push_back((value & 0x80) ? 1 : 0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
REQUIRE(bits.size() == 40);
|
|
176
|
+
|
|
177
|
+
// Thunderclock bit format: 10 BCD nibbles, LSB-first within each nibble
|
|
178
|
+
// Order: sec_ones(0-3), sec_tens(4-7), min_ones(8-11), min_tens(12-15),
|
|
179
|
+
// hr_ones(16-19), hr_tens(20-23), day_ones(24-27), day_tens(28-31),
|
|
180
|
+
// dow(32-35), month(36-39)
|
|
181
|
+
auto decodeLSBNibble = [&](int startBit) -> int {
|
|
182
|
+
return bits[startBit] | (bits[startBit+1] << 1) | (bits[startBit+2] << 2) | (bits[startBit+3] << 3);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
int secOnes = decodeLSBNibble(0);
|
|
186
|
+
int secTens = decodeLSBNibble(4);
|
|
187
|
+
int minOnes = decodeLSBNibble(8);
|
|
188
|
+
int minTens = decodeLSBNibble(12);
|
|
189
|
+
int hourOnes = decodeLSBNibble(16);
|
|
190
|
+
int hourTens = decodeLSBNibble(20);
|
|
191
|
+
int dayOnes = decodeLSBNibble(24);
|
|
192
|
+
int dayTens = decodeLSBNibble(28);
|
|
193
|
+
int weekday = decodeLSBNibble(32);
|
|
194
|
+
int month = decodeLSBNibble(36);
|
|
195
|
+
|
|
196
|
+
int second = secTens * 10 + secOnes;
|
|
197
|
+
int minute = minTens * 10 + minOnes;
|
|
198
|
+
int hour = hourTens * 10 + hourOnes;
|
|
199
|
+
int day = dayTens * 10 + dayOnes;
|
|
200
|
+
|
|
201
|
+
// Validate ranges
|
|
202
|
+
REQUIRE(month >= 1);
|
|
203
|
+
REQUIRE(month <= 12);
|
|
204
|
+
REQUIRE(weekday >= 0);
|
|
205
|
+
REQUIRE(weekday <= 6);
|
|
206
|
+
REQUIRE(day >= 1);
|
|
207
|
+
REQUIRE(day <= 31);
|
|
208
|
+
REQUIRE(hour >= 0);
|
|
209
|
+
REQUIRE(hour <= 23);
|
|
210
|
+
REQUIRE(minute >= 0);
|
|
211
|
+
REQUIRE(minute <= 59);
|
|
212
|
+
REQUIRE(second >= 0);
|
|
213
|
+
REQUIRE(second <= 59);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
TEST_CASE("ThunderclockCard strobe edge detection - no re-trigger on hold", "[thunderclock]") {
|
|
217
|
+
ThunderclockCard card;
|
|
218
|
+
|
|
219
|
+
card.writeIO(0x00, 0x00); // Low
|
|
220
|
+
card.writeIO(0x00, FLAG_STROBE); // Rising edge
|
|
221
|
+
card.writeIO(0x00, FLAG_STROBE); // Still high - should NOT re-trigger
|
|
222
|
+
card.writeIO(0x00, 0x00); // Falling edge
|
|
223
|
+
card.writeIO(0x00, FLAG_STROBE); // Rising edge again
|
|
224
|
+
|
|
225
|
+
// No crash = pass (edge detection is internal)
|
|
226
|
+
REQUIRE(true);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
TEST_CASE("ThunderclockCard clock edge detection - no shift on hold", "[thunderclock]") {
|
|
230
|
+
ThunderclockCard card;
|
|
231
|
+
|
|
232
|
+
// Issue time command
|
|
233
|
+
card.writeIO(0x00, 0x00);
|
|
234
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
235
|
+
|
|
236
|
+
// Clock rising edge
|
|
237
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
238
|
+
uint8_t val1 = card.readIO(0x00);
|
|
239
|
+
|
|
240
|
+
// Hold clock high - should not shift again
|
|
241
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
242
|
+
uint8_t val2 = card.readIO(0x00);
|
|
243
|
+
|
|
244
|
+
REQUIRE(val1 == val2);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// Reset
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
TEST_CASE("ThunderclockCard reset clears state", "[thunderclock]") {
|
|
252
|
+
ThunderclockCard card;
|
|
253
|
+
|
|
254
|
+
// Set up some state
|
|
255
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
256
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
257
|
+
|
|
258
|
+
card.reset();
|
|
259
|
+
|
|
260
|
+
REQUIRE(card.readIO(0x00) == 0x00);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
// Expansion ROM
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
TEST_CASE("ThunderclockCard expansion ROM is not all zeros", "[thunderclock]") {
|
|
268
|
+
ThunderclockCard card;
|
|
269
|
+
|
|
270
|
+
bool allZero = true;
|
|
271
|
+
for (int i = 0; i < 256; ++i) {
|
|
272
|
+
if (card.readExpansionROM(static_cast<uint16_t>(i)) != 0x00) {
|
|
273
|
+
allZero = false;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
REQUIRE_FALSE(allZero);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
TEST_CASE("ThunderclockCard expansion ROM is not all 0xFF", "[thunderclock]") {
|
|
281
|
+
ThunderclockCard card;
|
|
282
|
+
|
|
283
|
+
bool allFF = true;
|
|
284
|
+
for (int i = 0; i < 256; ++i) {
|
|
285
|
+
if (card.readExpansionROM(static_cast<uint16_t>(i)) != 0xFF) {
|
|
286
|
+
allFF = false;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
REQUIRE_FALSE(allFF);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// Serialization round-trip
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
TEST_CASE("ThunderclockCard getStateSize is correct", "[thunderclock]") {
|
|
298
|
+
ThunderclockCard card;
|
|
299
|
+
REQUIRE(card.getStateSize() == ThunderclockCard::STATE_SIZE);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
TEST_CASE("ThunderclockCard serialize/deserialize round-trip", "[thunderclock]") {
|
|
303
|
+
ThunderclockCard card1;
|
|
304
|
+
|
|
305
|
+
// Set up some state
|
|
306
|
+
card1.writeIO(0x00, 0x00);
|
|
307
|
+
card1.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
308
|
+
card1.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
309
|
+
|
|
310
|
+
uint8_t regBefore = card1.readIO(0x00);
|
|
311
|
+
|
|
312
|
+
// Serialize
|
|
313
|
+
std::vector<uint8_t> buffer(card1.getStateSize());
|
|
314
|
+
size_t written = card1.serialize(buffer.data(), buffer.size());
|
|
315
|
+
REQUIRE(written > 0);
|
|
316
|
+
REQUIRE(written <= buffer.size());
|
|
317
|
+
|
|
318
|
+
// Deserialize into new card
|
|
319
|
+
ThunderclockCard card2;
|
|
320
|
+
size_t consumed = card2.deserialize(buffer.data(), written);
|
|
321
|
+
REQUIRE(consumed > 0);
|
|
322
|
+
|
|
323
|
+
// The register value should be preserved
|
|
324
|
+
uint8_t regAfter = card2.readIO(0x00);
|
|
325
|
+
REQUIRE(regAfter == regBefore);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// Multiple time reads without reset
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
TEST_CASE("ThunderclockCard multiple time reads work correctly", "[thunderclock]") {
|
|
333
|
+
ThunderclockCard card;
|
|
334
|
+
|
|
335
|
+
for (int iter = 0; iter < 3; ++iter) {
|
|
336
|
+
// Issue time read command
|
|
337
|
+
card.writeIO(0x00, 0x00);
|
|
338
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
339
|
+
|
|
340
|
+
// Read 8 bits
|
|
341
|
+
uint8_t byte = 0;
|
|
342
|
+
for (int bit = 0; bit < 8; ++bit) {
|
|
343
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE);
|
|
344
|
+
card.writeIO(0x00, CMD_TIMED | FLAG_STROBE | FLAG_CLOCK);
|
|
345
|
+
uint8_t value = card.readIO(0x00);
|
|
346
|
+
byte = (byte << 1) | ((value & 0x80) ? 1 : 0);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// The byte should be some valid time data
|
|
350
|
+
// (month nibble + weekday nibble for first 8 bits)
|
|
351
|
+
// Just verify no crash
|
|
352
|
+
REQUIRE((byte == (byte & 0xFF))); // always true
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test_via6522.cpp - Unit tests for VIA 6522 timer chip emulation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#define CATCH_CONFIG_MAIN
|
|
6
|
+
#include "catch.hpp"
|
|
7
|
+
|
|
8
|
+
#include "via6522.hpp"
|
|
9
|
+
#include "ay8910.hpp"
|
|
10
|
+
|
|
11
|
+
using namespace a2e;
|
|
12
|
+
|
|
13
|
+
// VIA register addresses (matching the private constants)
|
|
14
|
+
static constexpr uint8_t REG_ORB = 0x00;
|
|
15
|
+
static constexpr uint8_t REG_ORA = 0x01;
|
|
16
|
+
static constexpr uint8_t REG_DDRB = 0x02;
|
|
17
|
+
static constexpr uint8_t REG_DDRA = 0x03;
|
|
18
|
+
static constexpr uint8_t REG_T1CL = 0x04;
|
|
19
|
+
static constexpr uint8_t REG_T1CH = 0x05;
|
|
20
|
+
static constexpr uint8_t REG_T1LL = 0x06;
|
|
21
|
+
static constexpr uint8_t REG_T1LH = 0x07;
|
|
22
|
+
static constexpr uint8_t REG_T2CL = 0x08;
|
|
23
|
+
static constexpr uint8_t REG_T2CH = 0x09;
|
|
24
|
+
static constexpr uint8_t REG_ACR = 0x0B;
|
|
25
|
+
static constexpr uint8_t REG_IFR = 0x0D;
|
|
26
|
+
static constexpr uint8_t REG_IER = 0x0E;
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Register read/write: ORA, ORB, DDRA, DDRB
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
TEST_CASE("VIA6522 ORA register write and read back", "[via][port]") {
|
|
33
|
+
VIA6522 via;
|
|
34
|
+
// Set DDRA to all output
|
|
35
|
+
via.write(REG_DDRA, 0xFF);
|
|
36
|
+
via.write(REG_ORA, 0xA5);
|
|
37
|
+
CHECK(via.getORA() == 0xA5);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
TEST_CASE("VIA6522 ORB register write and read back", "[via][port]") {
|
|
41
|
+
VIA6522 via;
|
|
42
|
+
// Set DDRB to all output
|
|
43
|
+
via.write(REG_DDRB, 0xFF);
|
|
44
|
+
via.write(REG_ORB, 0x5A);
|
|
45
|
+
CHECK(via.getORB() == 0x5A);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
TEST_CASE("VIA6522 DDRA register write and read back", "[via][port]") {
|
|
49
|
+
VIA6522 via;
|
|
50
|
+
via.write(REG_DDRA, 0xF0);
|
|
51
|
+
CHECK(via.getDDRA() == 0xF0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
TEST_CASE("VIA6522 DDRB register write and read back", "[via][port]") {
|
|
55
|
+
VIA6522 via;
|
|
56
|
+
via.write(REG_DDRB, 0x0F);
|
|
57
|
+
CHECK(via.getDDRB() == 0x0F);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Timer 1: write T1CL/T1CH, read back counter
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
TEST_CASE("VIA6522 Timer 1 latch write via T1CL/T1CH", "[via][timer1]") {
|
|
65
|
+
VIA6522 via;
|
|
66
|
+
|
|
67
|
+
// Writing T1CL sets the low latch (doesn't start timer)
|
|
68
|
+
via.write(REG_T1CL, 0x34);
|
|
69
|
+
|
|
70
|
+
// Writing T1CH sets the high latch AND loads counter AND starts timer
|
|
71
|
+
via.write(REG_T1CH, 0x12);
|
|
72
|
+
|
|
73
|
+
// Read back the latch values
|
|
74
|
+
CHECK(via.getT1Latch() == 0x1234);
|
|
75
|
+
CHECK(via.isT1Running() == true);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Timer 1 latch: T1LL/T1LH
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
TEST_CASE("VIA6522 Timer 1 latch only (T1LL/T1LH)", "[via][timer1_latch]") {
|
|
83
|
+
VIA6522 via;
|
|
84
|
+
|
|
85
|
+
// Writing T1LL/T1LH only sets the latch, does not start timer
|
|
86
|
+
via.write(REG_T1LL, 0xCD);
|
|
87
|
+
via.write(REG_T1LH, 0xAB);
|
|
88
|
+
|
|
89
|
+
CHECK(via.getT1Latch() == 0xABCD);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Timer 2: T2CL/T2CH
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
TEST_CASE("VIA6522 Timer 2 write and start", "[via][timer2]") {
|
|
97
|
+
VIA6522 via;
|
|
98
|
+
|
|
99
|
+
// Writing T2CL sets the low latch
|
|
100
|
+
via.write(REG_T2CL, 0x10);
|
|
101
|
+
// Writing T2CH loads counter and starts timer
|
|
102
|
+
via.write(REG_T2CH, 0x00);
|
|
103
|
+
|
|
104
|
+
// Timer 2 should be loaded with 0x0010
|
|
105
|
+
// We can verify it's running by updating and checking the counter
|
|
106
|
+
// decrements
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// IFR (0x0D): read interrupt flags
|
|
111
|
+
// ============================================================================
|
|
112
|
+
|
|
113
|
+
TEST_CASE("VIA6522 IFR initially zero", "[via][ifr]") {
|
|
114
|
+
VIA6522 via;
|
|
115
|
+
CHECK(via.getIFR() == 0x00);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// IER (0x0E): write enable/disable interrupt bits
|
|
120
|
+
// ============================================================================
|
|
121
|
+
|
|
122
|
+
TEST_CASE("VIA6522 IER enable and disable bits", "[via][ier]") {
|
|
123
|
+
VIA6522 via;
|
|
124
|
+
|
|
125
|
+
// Enable Timer 1 interrupt (bit 6): write with bit 7 set = enable
|
|
126
|
+
via.write(REG_IER, 0x80 | 0x40); // Set bit 7 (enable mode) + bit 6 (T1)
|
|
127
|
+
CHECK((via.getIER() & 0x40) != 0);
|
|
128
|
+
|
|
129
|
+
// Disable Timer 1 interrupt: write without bit 7 = disable
|
|
130
|
+
via.write(REG_IER, 0x40); // Clear mode (bit 7=0) + bit 6 (T1)
|
|
131
|
+
CHECK((via.getIER() & 0x40) == 0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
TEST_CASE("VIA6522 IER multiple interrupt sources", "[via][ier]") {
|
|
135
|
+
VIA6522 via;
|
|
136
|
+
|
|
137
|
+
// Enable T1 (bit 6) and T2 (bit 5)
|
|
138
|
+
via.write(REG_IER, 0x80 | 0x60);
|
|
139
|
+
CHECK((via.getIER() & 0x60) == 0x60);
|
|
140
|
+
|
|
141
|
+
// Disable only T2 (bit 5)
|
|
142
|
+
via.write(REG_IER, 0x20); // Clear mode, bit 5
|
|
143
|
+
CHECK((via.getIER() & 0x40) != 0); // T1 still enabled
|
|
144
|
+
CHECK((via.getIER() & 0x20) == 0); // T2 disabled
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// Timer fires IRQ
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
TEST_CASE("VIA6522 Timer 1 fires IRQ after countdown", "[via][timer1_irq]") {
|
|
152
|
+
VIA6522 via;
|
|
153
|
+
|
|
154
|
+
// Enable Timer 1 interrupt
|
|
155
|
+
via.write(REG_IER, 0x80 | 0x40);
|
|
156
|
+
|
|
157
|
+
// Set a short timer value
|
|
158
|
+
via.write(REG_T1CL, 0x05);
|
|
159
|
+
via.write(REG_T1CH, 0x00); // Timer = 5 cycles, starts running
|
|
160
|
+
|
|
161
|
+
CHECK(via.isIRQActive() == false);
|
|
162
|
+
|
|
163
|
+
// Run enough cycles for the timer to fire
|
|
164
|
+
via.update(10);
|
|
165
|
+
|
|
166
|
+
// Timer 1 should have fired, setting IFR bit 6
|
|
167
|
+
CHECK((via.getIFR() & 0x40) != 0);
|
|
168
|
+
CHECK(via.isIRQActive() == true);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
TEST_CASE("VIA6522 Timer 1 IRQ not active when not enabled", "[via][timer1_irq]") {
|
|
172
|
+
VIA6522 via;
|
|
173
|
+
|
|
174
|
+
// Don't enable any interrupts in IER
|
|
175
|
+
|
|
176
|
+
via.write(REG_T1CL, 0x05);
|
|
177
|
+
via.write(REG_T1CH, 0x00);
|
|
178
|
+
|
|
179
|
+
via.update(10);
|
|
180
|
+
|
|
181
|
+
// IFR bit should be set, but IRQ should not be active (IER doesn't enable it)
|
|
182
|
+
CHECK((via.getIFR() & 0x40) != 0); // T1 flag set
|
|
183
|
+
CHECK(via.isIRQActive() == false); // But not enabled
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// IRQ callback invoked on timer fire
|
|
188
|
+
// ============================================================================
|
|
189
|
+
|
|
190
|
+
TEST_CASE("VIA6522 IRQ callback invoked when timer fires", "[via][irq_callback]") {
|
|
191
|
+
VIA6522 via;
|
|
192
|
+
bool callbackFired = false;
|
|
193
|
+
|
|
194
|
+
via.setIRQCallback([&callbackFired]() {
|
|
195
|
+
callbackFired = true;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Enable Timer 1 interrupt
|
|
199
|
+
via.write(REG_IER, 0x80 | 0x40);
|
|
200
|
+
|
|
201
|
+
// Start timer with short value
|
|
202
|
+
via.write(REG_T1CL, 0x02);
|
|
203
|
+
via.write(REG_T1CH, 0x00);
|
|
204
|
+
|
|
205
|
+
CHECK(callbackFired == false);
|
|
206
|
+
|
|
207
|
+
// Run enough cycles
|
|
208
|
+
via.update(10);
|
|
209
|
+
|
|
210
|
+
CHECK(callbackFired == true);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ============================================================================
|
|
214
|
+
// ACR controls timer mode
|
|
215
|
+
// ============================================================================
|
|
216
|
+
|
|
217
|
+
TEST_CASE("VIA6522 ACR register write and read", "[via][acr]") {
|
|
218
|
+
VIA6522 via;
|
|
219
|
+
|
|
220
|
+
via.write(REG_ACR, 0x40); // T1 free-running mode (bit 6)
|
|
221
|
+
CHECK(via.getACR() == 0x40);
|
|
222
|
+
|
|
223
|
+
via.write(REG_ACR, 0x00); // T1 one-shot mode
|
|
224
|
+
CHECK(via.getACR() == 0x00);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
TEST_CASE("VIA6522 Timer 1 free-running mode re-fires", "[via][acr_freerun]") {
|
|
228
|
+
VIA6522 via;
|
|
229
|
+
int callbackCount = 0;
|
|
230
|
+
|
|
231
|
+
via.setIRQCallback([&callbackCount]() {
|
|
232
|
+
callbackCount++;
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Enable T1 interrupt
|
|
236
|
+
via.write(REG_IER, 0x80 | 0x40);
|
|
237
|
+
|
|
238
|
+
// Set ACR for T1 free-running mode (bit 6 set)
|
|
239
|
+
via.write(REG_ACR, 0x40);
|
|
240
|
+
|
|
241
|
+
// Start timer with a short period
|
|
242
|
+
via.write(REG_T1CL, 0x03);
|
|
243
|
+
via.write(REG_T1CH, 0x00);
|
|
244
|
+
|
|
245
|
+
// Run many cycles - should fire multiple times in free-running mode
|
|
246
|
+
// Clear IFR between iterations to allow re-fire
|
|
247
|
+
for (int i = 0; i < 50; i++) {
|
|
248
|
+
via.update(1);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// In free-running mode, the timer should have fired at least once
|
|
252
|
+
CHECK(callbackCount >= 1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ============================================================================
|
|
256
|
+
// reset
|
|
257
|
+
// ============================================================================
|
|
258
|
+
|
|
259
|
+
TEST_CASE("VIA6522 reset clears all state", "[via][reset]") {
|
|
260
|
+
VIA6522 via;
|
|
261
|
+
|
|
262
|
+
// Set up some state
|
|
263
|
+
via.write(REG_DDRA, 0xFF);
|
|
264
|
+
via.write(REG_ORA, 0xAA);
|
|
265
|
+
via.write(REG_IER, 0x80 | 0x40);
|
|
266
|
+
via.write(REG_T1CL, 0x10);
|
|
267
|
+
via.write(REG_T1CH, 0x20);
|
|
268
|
+
|
|
269
|
+
via.reset();
|
|
270
|
+
|
|
271
|
+
CHECK(via.getORA() == 0x00);
|
|
272
|
+
CHECK(via.getORB() == 0x00);
|
|
273
|
+
CHECK(via.getDDRA() == 0x00);
|
|
274
|
+
CHECK(via.getDDRB() == 0x00);
|
|
275
|
+
CHECK(via.getIFR() == 0x00);
|
|
276
|
+
CHECK(via.getACR() == 0x00);
|
|
277
|
+
CHECK(via.isIRQActive() == false);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ============================================================================
|
|
281
|
+
// State serialization
|
|
282
|
+
// ============================================================================
|
|
283
|
+
|
|
284
|
+
TEST_CASE("VIA6522 state serialization round-trip", "[via][state]") {
|
|
285
|
+
VIA6522 via1;
|
|
286
|
+
via1.write(REG_DDRA, 0xFF);
|
|
287
|
+
via1.write(REG_ORA, 0xBB);
|
|
288
|
+
via1.write(REG_DDRB, 0x0F);
|
|
289
|
+
|
|
290
|
+
uint8_t stateBuffer[VIA6522::STATE_SIZE];
|
|
291
|
+
size_t written = via1.exportState(stateBuffer);
|
|
292
|
+
REQUIRE(written == VIA6522::STATE_SIZE);
|
|
293
|
+
|
|
294
|
+
VIA6522 via2;
|
|
295
|
+
via2.importState(stateBuffer);
|
|
296
|
+
|
|
297
|
+
CHECK(via2.getORA() == 0xBB);
|
|
298
|
+
CHECK(via2.getDDRA() == 0xFF);
|
|
299
|
+
CHECK(via2.getDDRB() == 0x0F);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ============================================================================
|
|
303
|
+
// Reading T1CL clears T1 interrupt flag
|
|
304
|
+
// ============================================================================
|
|
305
|
+
|
|
306
|
+
TEST_CASE("VIA6522 reading T1CL clears T1 interrupt flag", "[via][timer1_clear]") {
|
|
307
|
+
VIA6522 via;
|
|
308
|
+
|
|
309
|
+
// Enable T1 interrupt
|
|
310
|
+
via.write(REG_IER, 0x80 | 0x40);
|
|
311
|
+
|
|
312
|
+
// Start short timer
|
|
313
|
+
via.write(REG_T1CL, 0x02);
|
|
314
|
+
via.write(REG_T1CH, 0x00);
|
|
315
|
+
|
|
316
|
+
// Let timer fire
|
|
317
|
+
via.update(10);
|
|
318
|
+
CHECK((via.getIFR() & 0x40) != 0);
|
|
319
|
+
|
|
320
|
+
// Reading T1CL should clear the T1 interrupt flag
|
|
321
|
+
via.read(REG_T1CL);
|
|
322
|
+
CHECK((via.getIFR() & 0x40) == 0);
|
|
323
|
+
}
|