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,1387 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* mmu.cpp - Memory management unit implementation
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "mmu.hpp"
|
|
9
|
+
#include "../cards/expansion_card.hpp"
|
|
10
|
+
#include <cstring>
|
|
11
|
+
|
|
12
|
+
namespace a2e {
|
|
13
|
+
|
|
14
|
+
MMU::MMU() { reset(); }
|
|
15
|
+
|
|
16
|
+
MMU::~MMU() = default;
|
|
17
|
+
|
|
18
|
+
void MMU::reset() {
|
|
19
|
+
// Clear RAM
|
|
20
|
+
mainRAM_.fill(0);
|
|
21
|
+
auxRAM_.fill(0);
|
|
22
|
+
lcBank1_.fill(0);
|
|
23
|
+
lcBank2_.fill(0);
|
|
24
|
+
lcHighRAM_.fill(0);
|
|
25
|
+
auxLcBank1_.fill(0);
|
|
26
|
+
auxLcBank2_.fill(0);
|
|
27
|
+
auxLcHighRAM_.fill(0);
|
|
28
|
+
|
|
29
|
+
// Reset soft switches to default state
|
|
30
|
+
switches_ = SoftSwitches{};
|
|
31
|
+
|
|
32
|
+
// Keyboard
|
|
33
|
+
keyboardLatch_ = 0;
|
|
34
|
+
|
|
35
|
+
// Reset expansion slot state
|
|
36
|
+
activeExpansionSlot_ = 0;
|
|
37
|
+
|
|
38
|
+
// Reset all installed cards
|
|
39
|
+
for (auto& card : slots_) {
|
|
40
|
+
if (card) {
|
|
41
|
+
card->reset();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Clear tracking (but preserve enabled state)
|
|
46
|
+
clearTracking();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void MMU::warmReset() {
|
|
50
|
+
// Reset soft switches to default state (preserves all RAM)
|
|
51
|
+
// On real Apple IIe hardware, the reset signal resets the IOU/MMU
|
|
52
|
+
// soft switches but does not clear memory
|
|
53
|
+
switches_ = SoftSwitches{};
|
|
54
|
+
|
|
55
|
+
// Keyboard
|
|
56
|
+
keyboardLatch_ = 0;
|
|
57
|
+
|
|
58
|
+
// Reset expansion slot state
|
|
59
|
+
activeExpansionSlot_ = 0;
|
|
60
|
+
|
|
61
|
+
// Reset all installed cards
|
|
62
|
+
for (auto& card : slots_) {
|
|
63
|
+
if (card) {
|
|
64
|
+
card->reset();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ===== Expansion Slot Management =====
|
|
70
|
+
|
|
71
|
+
std::unique_ptr<ExpansionCard> MMU::insertCard(uint8_t slot, std::unique_ptr<ExpansionCard> card) {
|
|
72
|
+
if (slot < 1 || slot > 7) {
|
|
73
|
+
return card; // Invalid slot, return card unchanged
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
std::unique_ptr<ExpansionCard> previous = std::move(slots_[slot - 1]);
|
|
77
|
+
slots_[slot - 1] = std::move(card);
|
|
78
|
+
|
|
79
|
+
// If the removed card owned the expansion ROM, clear it
|
|
80
|
+
if (activeExpansionSlot_ == slot) {
|
|
81
|
+
activeExpansionSlot_ = 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return previous;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
std::unique_ptr<ExpansionCard> MMU::removeCard(uint8_t slot) {
|
|
88
|
+
if (slot < 1 || slot > 7) {
|
|
89
|
+
return nullptr;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
std::unique_ptr<ExpansionCard> card = std::move(slots_[slot - 1]);
|
|
93
|
+
|
|
94
|
+
// If this card owned the expansion ROM, clear it
|
|
95
|
+
if (activeExpansionSlot_ == slot) {
|
|
96
|
+
activeExpansionSlot_ = 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return card;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
ExpansionCard* MMU::getCard(uint8_t slot) const {
|
|
103
|
+
if (slot < 1 || slot > 7) {
|
|
104
|
+
return nullptr;
|
|
105
|
+
}
|
|
106
|
+
return slots_[slot - 1].get();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
bool MMU::isSlotEmpty(uint8_t slot) const {
|
|
110
|
+
if (slot < 1 || slot > 7) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return !slots_[slot - 1];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
void MMU::clearTracking() {
|
|
117
|
+
readCounts_.fill(0);
|
|
118
|
+
writeCounts_.fill(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
void MMU::decayTracking(uint8_t amount) {
|
|
122
|
+
for (size_t i = 0; i < 65536; ++i) {
|
|
123
|
+
if (readCounts_[i] > amount) {
|
|
124
|
+
readCounts_[i] -= amount;
|
|
125
|
+
} else {
|
|
126
|
+
readCounts_[i] = 0;
|
|
127
|
+
}
|
|
128
|
+
if (writeCounts_[i] > amount) {
|
|
129
|
+
writeCounts_[i] -= amount;
|
|
130
|
+
} else {
|
|
131
|
+
writeCounts_[i] = 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
void MMU::loadROM(const uint8_t *systemRom, size_t systemSize,
|
|
137
|
+
const uint8_t *charRom, size_t charSize) {
|
|
138
|
+
if (systemRom && systemSize > 0) {
|
|
139
|
+
std::memcpy(systemROM_.data(), systemRom,
|
|
140
|
+
std::min(systemSize, systemROM_.size()));
|
|
141
|
+
}
|
|
142
|
+
if (charRom && charSize > 0) {
|
|
143
|
+
std::memcpy(charROM_.data(), charRom, std::min(charSize, charROM_.size()));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
uint8_t MMU::readCharROM(uint16_t address) const {
|
|
148
|
+
return charROM_[address & (CHAR_ROM_SIZE - 1)];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
uint8_t MMU::readRAM(uint16_t address, bool aux) const {
|
|
152
|
+
if (aux) {
|
|
153
|
+
return auxRAM_[address];
|
|
154
|
+
}
|
|
155
|
+
return mainRAM_[address];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
void MMU::writeRAM(uint16_t address, uint8_t value, bool aux) {
|
|
159
|
+
if (aux) {
|
|
160
|
+
auxRAM_[address] = value;
|
|
161
|
+
} else {
|
|
162
|
+
mainRAM_[address] = value;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
uint8_t MMU::peek(uint16_t address) const {
|
|
167
|
+
// Non-side-effecting read for debugger/memory viewer
|
|
168
|
+
// Same logic as read() but without any state changes or callbacks
|
|
169
|
+
|
|
170
|
+
// Zero page and stack
|
|
171
|
+
if (address < 0x0200) {
|
|
172
|
+
if (switches_.altzp) {
|
|
173
|
+
return auxRAM_[address];
|
|
174
|
+
}
|
|
175
|
+
return mainRAM_[address];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Main RAM: $0200-$BFFF
|
|
179
|
+
if (address < 0xC000) {
|
|
180
|
+
// Text page 1: $0400-$07FF
|
|
181
|
+
if (address >= 0x0400 && address < 0x0800) {
|
|
182
|
+
if (switches_.store80) {
|
|
183
|
+
return switches_.page2 ? auxRAM_[address] : mainRAM_[address];
|
|
184
|
+
}
|
|
185
|
+
return switches_.ramrd ? auxRAM_[address] : mainRAM_[address];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Text page 2: $0800-$0BFF
|
|
189
|
+
if (address >= 0x0800 && address < 0x0C00) {
|
|
190
|
+
return switches_.ramrd ? auxRAM_[address] : mainRAM_[address];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// HiRes page 1: $2000-$3FFF
|
|
194
|
+
if (address >= 0x2000 && address < 0x4000) {
|
|
195
|
+
if (switches_.store80 && switches_.hires) {
|
|
196
|
+
return switches_.page2 ? auxRAM_[address] : mainRAM_[address];
|
|
197
|
+
}
|
|
198
|
+
return switches_.ramrd ? auxRAM_[address] : mainRAM_[address];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// HiRes page 2: $4000-$5FFF
|
|
202
|
+
if (address >= 0x4000 && address < 0x6000) {
|
|
203
|
+
return switches_.ramrd ? auxRAM_[address] : mainRAM_[address];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// All other main RAM
|
|
207
|
+
return switches_.ramrd ? auxRAM_[address] : mainRAM_[address];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// I/O and soft switches: $C000-$C0FF
|
|
211
|
+
if (address < 0xC100) {
|
|
212
|
+
return peekSoftSwitch(address);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Slot ROM space: $C100-$CFFF
|
|
216
|
+
if (address < 0xD000) {
|
|
217
|
+
if (switches_.intcxrom) {
|
|
218
|
+
return systemROM_[address - 0xC000];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Slot 3 ($C300-$C3FF)
|
|
222
|
+
if (address >= 0xC300 && address < 0xC400) {
|
|
223
|
+
if (!switches_.slotc3rom) {
|
|
224
|
+
return systemROM_[address - 0xC000];
|
|
225
|
+
}
|
|
226
|
+
return 0xFF; // No card, return high byte
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Slot ROM: $C100-$C7FF - check slot system
|
|
230
|
+
if (address < 0xC800) {
|
|
231
|
+
uint8_t slot = (address >> 8) & 0x07;
|
|
232
|
+
uint8_t offset = address & 0xFF;
|
|
233
|
+
if (slot >= 1 && slot <= 7 && slots_[slot - 1]) {
|
|
234
|
+
return slots_[slot - 1]->readROM(offset);
|
|
235
|
+
}
|
|
236
|
+
return 0xFF;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// $C800-$CFFF: Expansion ROM space
|
|
240
|
+
if (address >= 0xC800) {
|
|
241
|
+
if (switches_.intc8rom) {
|
|
242
|
+
return systemROM_[address - 0xC000];
|
|
243
|
+
}
|
|
244
|
+
return 0xFF;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Other slot ROM space - no cards
|
|
248
|
+
return 0xFF;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Language card area: $D000-$FFFF
|
|
252
|
+
if (switches_.lcram) {
|
|
253
|
+
bool useAux = switches_.altzp;
|
|
254
|
+
if (address < 0xE000) {
|
|
255
|
+
uint16_t offset = address - 0xD000;
|
|
256
|
+
if (switches_.lcram2) {
|
|
257
|
+
return useAux ? auxLcBank2_[offset] : lcBank2_[offset];
|
|
258
|
+
} else {
|
|
259
|
+
return useAux ? auxLcBank1_[offset] : lcBank1_[offset];
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
uint16_t offset = address - 0xE000;
|
|
263
|
+
return useAux ? auxLcHighRAM_[offset] : lcHighRAM_[offset];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return systemROM_[address - 0xC000];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
uint8_t MMU::peekAux(uint16_t address) const {
|
|
270
|
+
// Direct read of auxiliary memory for text selection in 80-column mode
|
|
271
|
+
// This always reads from aux RAM regardless of soft switch state
|
|
272
|
+
return auxRAM_[address];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
uint8_t MMU::peekSoftSwitch(uint16_t address) const {
|
|
276
|
+
// Non-side-effecting soft switch read for debugger
|
|
277
|
+
uint8_t reg = address & 0xFF;
|
|
278
|
+
|
|
279
|
+
switch (reg) {
|
|
280
|
+
// Keyboard - return current latch without updating
|
|
281
|
+
// Per AppleWin/hardware behavior, reading any address in $C000-$C00F returns keyboard latch
|
|
282
|
+
case 0x00:
|
|
283
|
+
case 0x01:
|
|
284
|
+
case 0x02:
|
|
285
|
+
case 0x03:
|
|
286
|
+
case 0x04:
|
|
287
|
+
case 0x05:
|
|
288
|
+
case 0x06:
|
|
289
|
+
case 0x07:
|
|
290
|
+
case 0x08:
|
|
291
|
+
case 0x09:
|
|
292
|
+
case 0x0A:
|
|
293
|
+
case 0x0B:
|
|
294
|
+
case 0x0C:
|
|
295
|
+
case 0x0D:
|
|
296
|
+
case 0x0E:
|
|
297
|
+
case 0x0F:
|
|
298
|
+
return keyboardLatch_;
|
|
299
|
+
case 0x10: {
|
|
300
|
+
// Peek returns AKD in bit 7, key code in bits 0-6 (without clearing strobe)
|
|
301
|
+
bool anyKeyDown = anyKeyDownCallback_ ? anyKeyDownCallback_() : false;
|
|
302
|
+
return (anyKeyDown ? 0x80 : 0x00) | (keyboardLatch_ & 0x7F);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Memory switch status reads (these are safe - just report state)
|
|
306
|
+
case 0x11:
|
|
307
|
+
return switches_.lcram2 ? 0x80 : 0x00;
|
|
308
|
+
case 0x12:
|
|
309
|
+
return switches_.lcram ? 0x80 : 0x00;
|
|
310
|
+
case 0x13:
|
|
311
|
+
return switches_.ramrd ? 0x80 : 0x00;
|
|
312
|
+
case 0x14:
|
|
313
|
+
return switches_.ramwrt ? 0x80 : 0x00;
|
|
314
|
+
case 0x15:
|
|
315
|
+
return switches_.intcxrom ? 0x80 : 0x00;
|
|
316
|
+
case 0x16:
|
|
317
|
+
return switches_.altzp ? 0x80 : 0x00;
|
|
318
|
+
case 0x17:
|
|
319
|
+
return switches_.slotc3rom ? 0x80 : 0x00;
|
|
320
|
+
case 0x18:
|
|
321
|
+
return switches_.store80 ? 0x80 : 0x00;
|
|
322
|
+
case 0x19:
|
|
323
|
+
return 0x00; // VBL status - return arbitrary value for peek
|
|
324
|
+
case 0x1A:
|
|
325
|
+
return switches_.text ? 0x80 : 0x00;
|
|
326
|
+
case 0x1B:
|
|
327
|
+
return switches_.mixed ? 0x80 : 0x00;
|
|
328
|
+
case 0x1C:
|
|
329
|
+
return switches_.page2 ? 0x80 : 0x00;
|
|
330
|
+
case 0x1D:
|
|
331
|
+
return switches_.hires ? 0x80 : 0x00;
|
|
332
|
+
case 0x1E:
|
|
333
|
+
return switches_.altCharSet ? 0x80 : 0x00;
|
|
334
|
+
case 0x1F:
|
|
335
|
+
return switches_.col80 ? 0x80 : 0x00;
|
|
336
|
+
|
|
337
|
+
// Buttons - peek returns current state
|
|
338
|
+
case 0x61:
|
|
339
|
+
return buttonCallback_ ? (buttonCallback_(0) & 0x80) : 0x00;
|
|
340
|
+
case 0x62:
|
|
341
|
+
return buttonCallback_ ? (buttonCallback_(1) & 0x80) : 0x00;
|
|
342
|
+
case 0x63:
|
|
343
|
+
return buttonCallback_ ? (buttonCallback_(2) & 0x80) : 0x00;
|
|
344
|
+
|
|
345
|
+
// Paddle inputs
|
|
346
|
+
case 0x64:
|
|
347
|
+
case 0x65:
|
|
348
|
+
case 0x66:
|
|
349
|
+
case 0x67:
|
|
350
|
+
return 0x00;
|
|
351
|
+
|
|
352
|
+
// Slot I/O space: $C090-$C0FF (slots 1-7)
|
|
353
|
+
case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
|
|
354
|
+
case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F:
|
|
355
|
+
case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7:
|
|
356
|
+
case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF:
|
|
357
|
+
case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7:
|
|
358
|
+
case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF:
|
|
359
|
+
case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7:
|
|
360
|
+
case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF:
|
|
361
|
+
case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7:
|
|
362
|
+
case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF:
|
|
363
|
+
case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7:
|
|
364
|
+
case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF:
|
|
365
|
+
case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7:
|
|
366
|
+
case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: {
|
|
367
|
+
// Calculate slot number
|
|
368
|
+
uint8_t slot = ((reg - 0x80) >> 4);
|
|
369
|
+
uint8_t offset = reg & 0x0F;
|
|
370
|
+
|
|
371
|
+
if (slot >= 1 && slot <= 7 && slots_[slot - 1]) {
|
|
372
|
+
return slots_[slot - 1]->peekIO(offset);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return 0x00;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Language card switches - report switch state
|
|
379
|
+
case 0x80:
|
|
380
|
+
case 0x81:
|
|
381
|
+
case 0x82:
|
|
382
|
+
case 0x83:
|
|
383
|
+
case 0x84:
|
|
384
|
+
case 0x85:
|
|
385
|
+
case 0x86:
|
|
386
|
+
case 0x87:
|
|
387
|
+
case 0x88:
|
|
388
|
+
case 0x89:
|
|
389
|
+
case 0x8A:
|
|
390
|
+
case 0x8B:
|
|
391
|
+
case 0x8C:
|
|
392
|
+
case 0x8D:
|
|
393
|
+
case 0x8E:
|
|
394
|
+
case 0x8F:
|
|
395
|
+
return switches_.lcram ? 0x80 : 0x00;
|
|
396
|
+
|
|
397
|
+
default:
|
|
398
|
+
return 0x00;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
uint8_t MMU::read(uint16_t address) {
|
|
403
|
+
// Track read access
|
|
404
|
+
if (trackingEnabled_ && readCounts_[address] < 255) {
|
|
405
|
+
++readCounts_[address];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Watchpoint check on read
|
|
409
|
+
if (watchpointsActive_ && watchpointReadCallback_) {
|
|
410
|
+
// Determine value without side effects for the callback
|
|
411
|
+
uint8_t val = peek(address);
|
|
412
|
+
watchpointReadCallback_(address, val);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Zero page and stack
|
|
416
|
+
if (address < 0x0200) {
|
|
417
|
+
if (switches_.altzp) {
|
|
418
|
+
return auxRAM_[address];
|
|
419
|
+
}
|
|
420
|
+
return mainRAM_[address];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Main RAM: $0200-$BFFF
|
|
424
|
+
if (address < 0xC000) {
|
|
425
|
+
// Text page 1: $0400-$07FF
|
|
426
|
+
if (address >= 0x0400 && address < 0x0800) {
|
|
427
|
+
if (switches_.store80) {
|
|
428
|
+
// 80STORE on: PAGE2 controls main/aux, RAMRD is ignored
|
|
429
|
+
return switches_.page2 ? auxRAM_[address] : mainRAM_[address];
|
|
430
|
+
}
|
|
431
|
+
// 80STORE off: RAMRD controls main/aux
|
|
432
|
+
return switches_.ramrd ? auxRAM_[address] : mainRAM_[address];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Text page 2: $0800-$0BFF
|
|
436
|
+
if (address >= 0x0800 && address < 0x0C00) {
|
|
437
|
+
if (switches_.ramrd) {
|
|
438
|
+
return auxRAM_[address];
|
|
439
|
+
}
|
|
440
|
+
return mainRAM_[address];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// HiRes page 1: $2000-$3FFF
|
|
444
|
+
if (address >= 0x2000 && address < 0x4000) {
|
|
445
|
+
if (switches_.store80 && switches_.hires) {
|
|
446
|
+
// 80STORE+HIRES on: PAGE2 controls main/aux, RAMRD is ignored
|
|
447
|
+
return switches_.page2 ? auxRAM_[address] : mainRAM_[address];
|
|
448
|
+
}
|
|
449
|
+
// 80STORE off or HIRES off: RAMRD controls main/aux
|
|
450
|
+
return switches_.ramrd ? auxRAM_[address] : mainRAM_[address];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// HiRes page 2: $4000-$5FFF
|
|
454
|
+
if (address >= 0x4000 && address < 0x6000) {
|
|
455
|
+
if (switches_.ramrd) {
|
|
456
|
+
return auxRAM_[address];
|
|
457
|
+
}
|
|
458
|
+
return mainRAM_[address];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// All other main RAM
|
|
462
|
+
if (switches_.ramrd) {
|
|
463
|
+
return auxRAM_[address];
|
|
464
|
+
}
|
|
465
|
+
return mainRAM_[address];
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// I/O and soft switches: $C000-$C0FF
|
|
469
|
+
if (address < 0xC100) {
|
|
470
|
+
return readSoftSwitch(address);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Slot ROM space: $C100-$CFFF
|
|
474
|
+
if (address < 0xD000) {
|
|
475
|
+
// When INTCXROM is ON, all of $C100-$CFFF uses internal ROM
|
|
476
|
+
if (switches_.intcxrom) {
|
|
477
|
+
return systemROM_[address - 0xC000];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// INTCXROM is OFF - use slot ROMs
|
|
481
|
+
|
|
482
|
+
// Slot 3 ($C300-$C3FF) has special handling via SLOTC3ROM
|
|
483
|
+
if (address >= 0xC300 && address < 0xC400) {
|
|
484
|
+
if (!switches_.slotc3rom) {
|
|
485
|
+
// SLOTC3ROM off: use internal ROM for slot 3
|
|
486
|
+
// Also activates internal ROM for $C800-$CFFF
|
|
487
|
+
switches_.intc8rom = true;
|
|
488
|
+
return systemROM_[address - 0xC000];
|
|
489
|
+
}
|
|
490
|
+
// SLOTC3ROM on: use slot 3 ROM (no card, return floating bus)
|
|
491
|
+
return getFloatingBusValue();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Slot ROM space: $C100-$C7FF
|
|
495
|
+
// Each slot gets 256 bytes: slot N at $CN00-$CNFF
|
|
496
|
+
if (address < 0xC800) {
|
|
497
|
+
uint8_t slot = (address >> 8) & 0x07;
|
|
498
|
+
uint8_t offset = address & 0xFF;
|
|
499
|
+
|
|
500
|
+
// Access to slot ROM activates that card's expansion ROM
|
|
501
|
+
if (slot >= 1 && slot <= 7 && slots_[slot - 1]) {
|
|
502
|
+
activeExpansionSlot_ = slot;
|
|
503
|
+
return slots_[slot - 1]->readROM(offset);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return getFloatingBusValue();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// $C800-$CFFF: Expansion ROM space
|
|
510
|
+
|
|
511
|
+
// Return internal ROM if slot 3 internal ROM was accessed
|
|
512
|
+
if (switches_.intc8rom) {
|
|
513
|
+
// Access to $CFFF clears the internal ROM select AFTER the read
|
|
514
|
+
if (address == 0xCFFF) {
|
|
515
|
+
uint8_t value = systemROM_[address - 0xC000];
|
|
516
|
+
switches_.intc8rom = false;
|
|
517
|
+
activeExpansionSlot_ = 0;
|
|
518
|
+
return value;
|
|
519
|
+
}
|
|
520
|
+
return systemROM_[address - 0xC000];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Check if a card owns the expansion ROM space
|
|
524
|
+
if (activeExpansionSlot_ >= 1 && activeExpansionSlot_ <= 7) {
|
|
525
|
+
auto& card = slots_[activeExpansionSlot_ - 1];
|
|
526
|
+
if (card && card->hasExpansionROM()) {
|
|
527
|
+
uint8_t value = card->readExpansionROM(address - 0xC800);
|
|
528
|
+
// Access to $CFFF clears the expansion ROM select AFTER the read
|
|
529
|
+
if (address == 0xCFFF) {
|
|
530
|
+
activeExpansionSlot_ = 0;
|
|
531
|
+
}
|
|
532
|
+
return value;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Access to $CFFF with no active expansion ROM still clears the select
|
|
537
|
+
if (address == 0xCFFF) {
|
|
538
|
+
switches_.intc8rom = false;
|
|
539
|
+
activeExpansionSlot_ = 0;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// No expansion ROM active, return floating bus
|
|
543
|
+
return getFloatingBusValue();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Language card area: $D000-$FFFF
|
|
547
|
+
return readLanguageCard(address);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
void MMU::write(uint16_t address, uint8_t value) {
|
|
551
|
+
// Track write access
|
|
552
|
+
if (trackingEnabled_ && writeCounts_[address] < 255) {
|
|
553
|
+
++writeCounts_[address];
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Watchpoint check on write
|
|
557
|
+
if (watchpointsActive_ && watchpointWriteCallback_) {
|
|
558
|
+
watchpointWriteCallback_(address, value);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Zero page and stack
|
|
562
|
+
if (address < 0x0200) {
|
|
563
|
+
if (switches_.altzp) {
|
|
564
|
+
auxRAM_[address] = value;
|
|
565
|
+
} else {
|
|
566
|
+
mainRAM_[address] = value;
|
|
567
|
+
}
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Main RAM: $0200-$BFFF
|
|
572
|
+
if (address < 0xC000) {
|
|
573
|
+
// Text page 1: $0400-$07FF
|
|
574
|
+
if (address >= 0x0400 && address < 0x0800) {
|
|
575
|
+
if (switches_.store80) {
|
|
576
|
+
// 80STORE on: PAGE2 controls main/aux, RAMWRT is ignored
|
|
577
|
+
if (switches_.page2) {
|
|
578
|
+
auxRAM_[address] = value;
|
|
579
|
+
} else {
|
|
580
|
+
mainRAM_[address] = value;
|
|
581
|
+
}
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
// 80STORE off: RAMWRT controls main/aux
|
|
585
|
+
if (switches_.ramwrt) {
|
|
586
|
+
auxRAM_[address] = value;
|
|
587
|
+
} else {
|
|
588
|
+
mainRAM_[address] = value;
|
|
589
|
+
}
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Text page 2: $0800-$0BFF
|
|
594
|
+
if (address >= 0x0800 && address < 0x0C00) {
|
|
595
|
+
if (switches_.ramwrt) {
|
|
596
|
+
auxRAM_[address] = value;
|
|
597
|
+
} else {
|
|
598
|
+
mainRAM_[address] = value;
|
|
599
|
+
}
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// HiRes page 1: $2000-$3FFF
|
|
604
|
+
if (address >= 0x2000 && address < 0x4000) {
|
|
605
|
+
if (switches_.store80 && switches_.hires) {
|
|
606
|
+
// 80STORE+HIRES on: PAGE2 controls main/aux, RAMWRT is ignored
|
|
607
|
+
if (switches_.page2) {
|
|
608
|
+
auxRAM_[address] = value;
|
|
609
|
+
} else {
|
|
610
|
+
mainRAM_[address] = value;
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
// 80STORE off or HIRES off: RAMWRT controls main/aux
|
|
615
|
+
if (switches_.ramwrt) {
|
|
616
|
+
auxRAM_[address] = value;
|
|
617
|
+
} else {
|
|
618
|
+
mainRAM_[address] = value;
|
|
619
|
+
}
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// HiRes page 2: $4000-$5FFF
|
|
624
|
+
if (address >= 0x4000 && address < 0x6000) {
|
|
625
|
+
if (switches_.ramwrt) {
|
|
626
|
+
auxRAM_[address] = value;
|
|
627
|
+
} else {
|
|
628
|
+
mainRAM_[address] = value;
|
|
629
|
+
}
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// All other main RAM
|
|
634
|
+
if (switches_.ramwrt) {
|
|
635
|
+
auxRAM_[address] = value;
|
|
636
|
+
} else {
|
|
637
|
+
mainRAM_[address] = value;
|
|
638
|
+
}
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// I/O and soft switches: $C000-$C0FF
|
|
643
|
+
if (address < 0xC100) {
|
|
644
|
+
writeSoftSwitch(address, value);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Slot ROM space: $C100-$CFFF
|
|
649
|
+
// Most cards don't handle writes, but some (like Mockingboard) use ROM space for I/O
|
|
650
|
+
if (address < 0xD000) {
|
|
651
|
+
// Access to $CFFF clears the expansion ROM select
|
|
652
|
+
if (address == 0xCFFF) {
|
|
653
|
+
switches_.intc8rom = false;
|
|
654
|
+
activeExpansionSlot_ = 0;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Route writes to slot ROM space through cards
|
|
658
|
+
if (address < 0xC800) {
|
|
659
|
+
uint8_t slot = (address >> 8) & 0x07;
|
|
660
|
+
uint8_t offset = address & 0xFF;
|
|
661
|
+
|
|
662
|
+
if (slot >= 1 && slot <= 7 && slots_[slot - 1]) {
|
|
663
|
+
slots_[slot - 1]->writeROM(offset, value);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Language card area: $D000-$FFFF
|
|
670
|
+
writeLanguageCard(address, value);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
uint8_t MMU::getFloatingBusValue() {
|
|
674
|
+
// The floating bus returns whatever byte the video hardware is currently
|
|
675
|
+
// reading. This is determined by the current scanline and horizontal position
|
|
676
|
+
// within the frame.
|
|
677
|
+
//
|
|
678
|
+
// Apple IIe timing:
|
|
679
|
+
// - 65 cycles per scanline (25 hblank + 40 visible)
|
|
680
|
+
// - 262 scanlines per frame (192 visible + 70 vertical blank)
|
|
681
|
+
// - Cycles 0-24: horizontal blanking, cycles 25-64: visible display
|
|
682
|
+
|
|
683
|
+
if (!cycleCallback_) {
|
|
684
|
+
return 0x00;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
uint64_t cycles = cycleCallback_();
|
|
688
|
+
uint32_t frameCycle = cycles % CYCLES_PER_FRAME;
|
|
689
|
+
uint32_t scanline = frameCycle / CYCLES_PER_SCANLINE;
|
|
690
|
+
uint32_t hPos = frameCycle % CYCLES_PER_SCANLINE;
|
|
691
|
+
|
|
692
|
+
// During horizontal blank (cycles 0-24), video reads from
|
|
693
|
+
// unpredictable locations. Return 0 for simplicity during hblank.
|
|
694
|
+
if (hPos < 25) {
|
|
695
|
+
return 0x00;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Convert to visible column (0-39)
|
|
699
|
+
uint32_t col = hPos - 25;
|
|
700
|
+
|
|
701
|
+
// During vertical blank (scanlines 192-261), return data from the
|
|
702
|
+
// last few lines' worth of addresses (video wraps during vblank)
|
|
703
|
+
if (scanline >= 192) {
|
|
704
|
+
scanline = scanline % 192;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Calculate memory address based on current video mode
|
|
708
|
+
uint16_t address;
|
|
709
|
+
|
|
710
|
+
if (switches_.text || !switches_.hires) {
|
|
711
|
+
// Text mode or LoRes mode - reads from text page
|
|
712
|
+
// Each scanline covers 8 text rows due to character height
|
|
713
|
+
int textRow = scanline / 8;
|
|
714
|
+
if (textRow >= 24)
|
|
715
|
+
textRow = 23;
|
|
716
|
+
|
|
717
|
+
// Text/LoRes base address
|
|
718
|
+
uint16_t base = switches_.page2 ? 0x0800 : 0x0400;
|
|
719
|
+
|
|
720
|
+
// Apple II text memory is interleaved in groups of 8 rows
|
|
721
|
+
// Rows 0-7: $000, $080, $100, $180, $200, $280, $300, $380
|
|
722
|
+
// Rows 8-15: $028, $0A8, $128, $1A8, $228, $2A8, $328, $3A8
|
|
723
|
+
// Rows 16-23: $050, $0D0, $150, $1D0, $250, $2D0, $350, $3D0
|
|
724
|
+
static const uint16_t rowOffsets[24] = {
|
|
725
|
+
0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380,
|
|
726
|
+
0x028, 0x0A8, 0x128, 0x1A8, 0x228, 0x2A8, 0x328, 0x3A8,
|
|
727
|
+
0x050, 0x0D0, 0x150, 0x1D0, 0x250, 0x2D0, 0x350, 0x3D0};
|
|
728
|
+
|
|
729
|
+
address = base + rowOffsets[textRow] + col;
|
|
730
|
+
} else {
|
|
731
|
+
// HiRes mode - reads from hi-res page
|
|
732
|
+
uint16_t base = switches_.page2 ? 0x4000 : 0x2000;
|
|
733
|
+
|
|
734
|
+
// Hi-res memory is also interleaved
|
|
735
|
+
// Each group of 64 lines shares a similar pattern
|
|
736
|
+
int group = scanline / 64; // 0, 1, or 2
|
|
737
|
+
int lineInGroup = scanline % 64; // 0-63
|
|
738
|
+
int subGroup = lineInGroup / 8; // 0-7
|
|
739
|
+
int lineInSubGroup = lineInGroup % 8;
|
|
740
|
+
|
|
741
|
+
// Calculate offset within page
|
|
742
|
+
// Lines 0,8,16,24,32,40,48,56 of each group start at group*$28
|
|
743
|
+
// Plus $80 for each subgroup, plus $400 for each line within subgroup
|
|
744
|
+
uint16_t offset = (group * 0x28) + (subGroup * 0x80) + (lineInSubGroup * 0x400);
|
|
745
|
+
address = base + offset + col;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Read from the appropriate memory bank
|
|
749
|
+
if (switches_.store80 && switches_.page2) {
|
|
750
|
+
// When 80STORE is on and PAGE2, read from aux memory for display pages
|
|
751
|
+
if ((address >= 0x0400 && address < 0x0800) ||
|
|
752
|
+
(switches_.hires && address >= 0x2000 && address < 0x4000)) {
|
|
753
|
+
return auxRAM_[address];
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (switches_.ramrd) {
|
|
758
|
+
return auxRAM_[address];
|
|
759
|
+
}
|
|
760
|
+
return mainRAM_[address];
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
uint8_t MMU::readSoftSwitch(uint16_t address) {
|
|
764
|
+
uint8_t reg = address & 0xFF;
|
|
765
|
+
|
|
766
|
+
switch (reg) {
|
|
767
|
+
// Keyboard
|
|
768
|
+
case 0x00: // KEYBOARD
|
|
769
|
+
if (keyboardCallback_) {
|
|
770
|
+
keyboardLatch_ = keyboardCallback_();
|
|
771
|
+
}
|
|
772
|
+
return keyboardLatch_;
|
|
773
|
+
|
|
774
|
+
case 0x10: { // KBDSTRB - clear keyboard strobe, return any-key-down status
|
|
775
|
+
if (keyStrobeCallback_) {
|
|
776
|
+
keyStrobeCallback_();
|
|
777
|
+
}
|
|
778
|
+
keyboardLatch_ &= 0x7F;
|
|
779
|
+
// Bit 7 = any key down status, bits 0-6 = key code
|
|
780
|
+
bool anyKeyDown = anyKeyDownCallback_ ? anyKeyDownCallback_() : false;
|
|
781
|
+
return (anyKeyDown ? 0x80 : 0x00) | (keyboardLatch_ & 0x7F);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Memory switches - reading returns switch state in bit 7, floating bus in bits 0-6
|
|
785
|
+
case 0x11:
|
|
786
|
+
return (switches_.lcram2 ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDLCBNK2
|
|
787
|
+
case 0x12:
|
|
788
|
+
return (switches_.lcram ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDLCRAM
|
|
789
|
+
case 0x13:
|
|
790
|
+
return (switches_.ramrd ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDRAMRD
|
|
791
|
+
case 0x14:
|
|
792
|
+
return (switches_.ramwrt ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDRAMWRT
|
|
793
|
+
case 0x15:
|
|
794
|
+
return (switches_.intcxrom ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDCXROM
|
|
795
|
+
case 0x16:
|
|
796
|
+
return (switches_.altzp ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDALTZP
|
|
797
|
+
case 0x17:
|
|
798
|
+
return (switches_.slotc3rom ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDC3ROM
|
|
799
|
+
case 0x18:
|
|
800
|
+
return (switches_.store80 ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RD80STORE
|
|
801
|
+
case 0x19: { // RDVBLBAR - vertical blank status
|
|
802
|
+
// Bit 7 = 0 during vertical blank (scanlines 192-261), 1 during active display
|
|
803
|
+
uint64_t cycles = cycleCallback_ ? cycleCallback_() : 0;
|
|
804
|
+
uint32_t scanline = (cycles % CYCLES_PER_FRAME) / CYCLES_PER_SCANLINE;
|
|
805
|
+
bool inVBL = (scanline >= 192);
|
|
806
|
+
return (inVBL ? 0x00 : 0x80) | (getFloatingBusValue() & 0x7F);
|
|
807
|
+
}
|
|
808
|
+
case 0x1A:
|
|
809
|
+
return (switches_.text ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDTEXT
|
|
810
|
+
case 0x1B:
|
|
811
|
+
return (switches_.mixed ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDMIXED
|
|
812
|
+
case 0x1C:
|
|
813
|
+
return (switches_.page2 ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDPAGE2
|
|
814
|
+
case 0x1D:
|
|
815
|
+
return (switches_.hires ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDHIRES
|
|
816
|
+
case 0x1E:
|
|
817
|
+
return (switches_.altCharSet ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RDALTCHAR
|
|
818
|
+
case 0x1F:
|
|
819
|
+
return (switches_.col80 ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F); // RD80COL
|
|
820
|
+
|
|
821
|
+
// Cassette output toggle ($C020)
|
|
822
|
+
case 0x20: // CASSETTE OUT - toggle cassette output
|
|
823
|
+
switches_.cassetteOut = !switches_.cassetteOut;
|
|
824
|
+
return getFloatingBusValue();
|
|
825
|
+
|
|
826
|
+
// Unused/reserved ($C021-$C02F)
|
|
827
|
+
case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
|
|
828
|
+
case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F:
|
|
829
|
+
return getFloatingBusValue();
|
|
830
|
+
|
|
831
|
+
// Speaker - returns floating bus
|
|
832
|
+
case 0x30: // SPKR
|
|
833
|
+
if (speakerCallback_) {
|
|
834
|
+
speakerCallback_();
|
|
835
|
+
}
|
|
836
|
+
return getFloatingBusValue();
|
|
837
|
+
|
|
838
|
+
// Unused/reserved ($C031-$C03F)
|
|
839
|
+
case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
|
|
840
|
+
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
|
|
841
|
+
return getFloatingBusValue();
|
|
842
|
+
|
|
843
|
+
// Utility strobe ($C040)
|
|
844
|
+
case 0x40: // STROBE - utility strobe
|
|
845
|
+
return getFloatingBusValue();
|
|
846
|
+
|
|
847
|
+
// Reserved ($C041-$C04F)
|
|
848
|
+
case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
|
|
849
|
+
case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F:
|
|
850
|
+
return getFloatingBusValue();
|
|
851
|
+
|
|
852
|
+
// Annunciators - return floating bus
|
|
853
|
+
case 0x58:
|
|
854
|
+
switches_.an0 = false;
|
|
855
|
+
return getFloatingBusValue();
|
|
856
|
+
case 0x59:
|
|
857
|
+
switches_.an0 = true;
|
|
858
|
+
return getFloatingBusValue();
|
|
859
|
+
case 0x5A:
|
|
860
|
+
switches_.an1 = false;
|
|
861
|
+
return getFloatingBusValue();
|
|
862
|
+
case 0x5B:
|
|
863
|
+
switches_.an1 = true;
|
|
864
|
+
return getFloatingBusValue();
|
|
865
|
+
case 0x5C:
|
|
866
|
+
switches_.an2 = false;
|
|
867
|
+
return getFloatingBusValue();
|
|
868
|
+
case 0x5D:
|
|
869
|
+
switches_.an2 = true;
|
|
870
|
+
return getFloatingBusValue();
|
|
871
|
+
case 0x5E:
|
|
872
|
+
switches_.an3 = false;
|
|
873
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
874
|
+
return getFloatingBusValue(); // AN3 OFF = DHIRES enabled
|
|
875
|
+
case 0x5F:
|
|
876
|
+
switches_.an3 = true;
|
|
877
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
878
|
+
return getFloatingBusValue(); // AN3 ON = DHIRES disabled
|
|
879
|
+
|
|
880
|
+
// Display switches - reading also sets the switch, returns floating bus
|
|
881
|
+
case 0x50:
|
|
882
|
+
switches_.text = false;
|
|
883
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
884
|
+
return getFloatingBusValue(); // TXTCLR (graphics)
|
|
885
|
+
case 0x51:
|
|
886
|
+
switches_.text = true;
|
|
887
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
888
|
+
return getFloatingBusValue(); // TXTSET (text)
|
|
889
|
+
case 0x52:
|
|
890
|
+
switches_.mixed = false;
|
|
891
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
892
|
+
return getFloatingBusValue(); // MIXCLR
|
|
893
|
+
case 0x53:
|
|
894
|
+
switches_.mixed = true;
|
|
895
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
896
|
+
return getFloatingBusValue(); // MIXSET
|
|
897
|
+
case 0x54:
|
|
898
|
+
switches_.page2 = false;
|
|
899
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
900
|
+
return getFloatingBusValue(); // LOWSCR (page 1)
|
|
901
|
+
case 0x55:
|
|
902
|
+
switches_.page2 = true;
|
|
903
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
904
|
+
return getFloatingBusValue(); // HISCR (page 2)
|
|
905
|
+
case 0x56:
|
|
906
|
+
switches_.hires = false;
|
|
907
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
908
|
+
return getFloatingBusValue(); // LORES
|
|
909
|
+
case 0x57:
|
|
910
|
+
switches_.hires = true;
|
|
911
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
912
|
+
return getFloatingBusValue(); // HIRES
|
|
913
|
+
|
|
914
|
+
// 80-column / memory switches (IIe specific)
|
|
915
|
+
// $C000-$C00F are WRITE-ONLY switches - writes modify state, reads return keyboard data
|
|
916
|
+
// Per AppleWin/hardware behavior, reading any address in $C000-$C00F returns keyboard latch
|
|
917
|
+
case 0x01: // 80STORE on (write-only)
|
|
918
|
+
case 0x02: // RAMRD off (write-only)
|
|
919
|
+
case 0x03: // RAMRD on (write-only)
|
|
920
|
+
case 0x04: // RAMWRT off (write-only)
|
|
921
|
+
case 0x05: // RAMWRT on (write-only)
|
|
922
|
+
case 0x06: // INTCXROM off (write-only)
|
|
923
|
+
case 0x07: // INTCXROM on (write-only)
|
|
924
|
+
case 0x08: // ALTZP off (write-only)
|
|
925
|
+
case 0x09: // ALTZP on (write-only)
|
|
926
|
+
case 0x0A: // SLOTC3ROM off (write-only)
|
|
927
|
+
case 0x0B: // SLOTC3ROM on (write-only)
|
|
928
|
+
case 0x0C: // 80COL off (write-only)
|
|
929
|
+
case 0x0D: // 80COL on (write-only)
|
|
930
|
+
case 0x0E: // ALTCHAR off (write-only)
|
|
931
|
+
case 0x0F: // ALTCHAR on (write-only)
|
|
932
|
+
return keyboardLatch_;
|
|
933
|
+
|
|
934
|
+
// Cassette input ($C060)
|
|
935
|
+
case 0x60: // CASSETTE IN - cassette input (active high)
|
|
936
|
+
// Always return low (no cassette) - bit 7 indicates cassette signal
|
|
937
|
+
return getFloatingBusValue() & 0x7F;
|
|
938
|
+
|
|
939
|
+
// Buttons (Open Apple, Closed Apple, Button 2) - bit 7 = state, bits 0-6 = floating bus
|
|
940
|
+
case 0x61: // PB0 / Open Apple
|
|
941
|
+
if (buttonCallback_) {
|
|
942
|
+
return (buttonCallback_(0) & 0x80) | (getFloatingBusValue() & 0x7F);
|
|
943
|
+
}
|
|
944
|
+
return getFloatingBusValue() & 0x7F;
|
|
945
|
+
case 0x62: // PB1 / Closed Apple
|
|
946
|
+
if (buttonCallback_) {
|
|
947
|
+
return (buttonCallback_(1) & 0x80) | (getFloatingBusValue() & 0x7F);
|
|
948
|
+
}
|
|
949
|
+
return getFloatingBusValue() & 0x7F;
|
|
950
|
+
case 0x63: // PB2 / Shift key modifier
|
|
951
|
+
if (buttonCallback_) {
|
|
952
|
+
return (buttonCallback_(2) & 0x80) | (getFloatingBusValue() & 0x7F);
|
|
953
|
+
}
|
|
954
|
+
return getFloatingBusValue() & 0x7F;
|
|
955
|
+
|
|
956
|
+
// Paddle inputs - bit 7 = timer status, bits 0-6 = floating bus
|
|
957
|
+
case 0x64: // PDL0 (joystick X)
|
|
958
|
+
case 0x65: // PDL1 (joystick Y)
|
|
959
|
+
case 0x66: // PDL2
|
|
960
|
+
case 0x67: { // PDL3
|
|
961
|
+
int paddle = reg - 0x64;
|
|
962
|
+
uint64_t currentCycle = cycleCallback_ ? cycleCallback_() : 0;
|
|
963
|
+
uint64_t elapsedCycles = currentCycle - paddleTriggerCycle_;
|
|
964
|
+
uint64_t timerDuration = paddleValues_[paddle] * PADDLE_CYCLES_PER_UNIT;
|
|
965
|
+
// Bit 7 = 1 while timer is running, 0 when expired
|
|
966
|
+
bool timerRunning = (elapsedCycles < timerDuration);
|
|
967
|
+
return (timerRunning ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Paddle trigger reset - starts all paddle timers
|
|
971
|
+
case 0x70: // PTRIG - reset paddle timers
|
|
972
|
+
paddleTriggerCycle_ = cycleCallback_ ? cycleCallback_() : 0;
|
|
973
|
+
return getFloatingBusValue();
|
|
974
|
+
|
|
975
|
+
// Reserved/unused ($C068-$C06F) - State register on IIc
|
|
976
|
+
case 0x68: // STATEREG (IIc only) - on IIe returns floating bus
|
|
977
|
+
case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F:
|
|
978
|
+
return getFloatingBusValue();
|
|
979
|
+
|
|
980
|
+
// Bank switch registers ($C071-$C07E) - mostly IIc/IIgs specific
|
|
981
|
+
case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
|
|
982
|
+
case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E:
|
|
983
|
+
return getFloatingBusValue();
|
|
984
|
+
|
|
985
|
+
// IOUDIS ($C07F) - IOU disable (IIc specific, ignored on IIe)
|
|
986
|
+
case 0x7F:
|
|
987
|
+
// On IIe, reading $C07F returns DHIRES status in bit 7 (same as AN3 inverted)
|
|
988
|
+
return (!switches_.an3 ? 0x80 : 0x00) | (getFloatingBusValue() & 0x7F);
|
|
989
|
+
|
|
990
|
+
// Slot I/O space: $C090-$C0FF (slots 1-7)
|
|
991
|
+
// Each slot gets 16 bytes: slot N at $C080 + (N * 16)
|
|
992
|
+
case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
|
|
993
|
+
case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F:
|
|
994
|
+
case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7:
|
|
995
|
+
case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF:
|
|
996
|
+
case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7:
|
|
997
|
+
case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF:
|
|
998
|
+
case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7:
|
|
999
|
+
case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF:
|
|
1000
|
+
case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7:
|
|
1001
|
+
case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF:
|
|
1002
|
+
case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7:
|
|
1003
|
+
case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF:
|
|
1004
|
+
case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7:
|
|
1005
|
+
case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: {
|
|
1006
|
+
// Calculate slot number: $C090 = slot 1, $C0A0 = slot 2, etc.
|
|
1007
|
+
uint8_t slot = ((reg - 0x80) >> 4);
|
|
1008
|
+
uint8_t offset = reg & 0x0F;
|
|
1009
|
+
|
|
1010
|
+
if (slot >= 1 && slot <= 7 && slots_[slot - 1]) {
|
|
1011
|
+
return slots_[slot - 1]->readIO(offset);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
return getFloatingBusValue();
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Language card
|
|
1018
|
+
case 0x80:
|
|
1019
|
+
case 0x81:
|
|
1020
|
+
case 0x82:
|
|
1021
|
+
case 0x83:
|
|
1022
|
+
case 0x84:
|
|
1023
|
+
case 0x85:
|
|
1024
|
+
case 0x86:
|
|
1025
|
+
case 0x87:
|
|
1026
|
+
case 0x88:
|
|
1027
|
+
case 0x89:
|
|
1028
|
+
case 0x8A:
|
|
1029
|
+
case 0x8B:
|
|
1030
|
+
case 0x8C:
|
|
1031
|
+
case 0x8D:
|
|
1032
|
+
case 0x8E:
|
|
1033
|
+
case 0x8F:
|
|
1034
|
+
return handleLanguageCardSwitch(reg);
|
|
1035
|
+
|
|
1036
|
+
default:
|
|
1037
|
+
// Unimplemented soft switches return floating bus value
|
|
1038
|
+
return getFloatingBusValue();
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
return 0x00;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
uint8_t MMU::handleLanguageCardSwitch(uint8_t reg) {
|
|
1045
|
+
// Language card switches at $C080-$C08F
|
|
1046
|
+
// Bit 3: bank select (0 = bank 2, 1 = bank 1)
|
|
1047
|
+
// Bits 0-1 determine read source and write enable:
|
|
1048
|
+
// 0: Read RAM, write disabled
|
|
1049
|
+
// 1: Read ROM, write enabled (after 2 reads)
|
|
1050
|
+
// 2: Read ROM, write disabled
|
|
1051
|
+
// 3: Read RAM, write enabled (after 2 reads)
|
|
1052
|
+
|
|
1053
|
+
bool bank2 = !(reg & 0x08);
|
|
1054
|
+
uint8_t op = reg & 0x03;
|
|
1055
|
+
|
|
1056
|
+
// RAM vs ROM read selection
|
|
1057
|
+
// Pattern: 0=RAM, 1=ROM, 2=ROM, 3=RAM
|
|
1058
|
+
bool readRAM = (op == 0 || op == 3);
|
|
1059
|
+
|
|
1060
|
+
switch (op) {
|
|
1061
|
+
case 0: // $C080, $C088: Read RAM, write disabled
|
|
1062
|
+
switches_.lcwrite = false;
|
|
1063
|
+
switches_.lcprewrite = false;
|
|
1064
|
+
break;
|
|
1065
|
+
|
|
1066
|
+
case 1: // $C081, $C089: Read ROM, write enable on second read
|
|
1067
|
+
if (switches_.lcprewrite) {
|
|
1068
|
+
switches_.lcwrite = true;
|
|
1069
|
+
}
|
|
1070
|
+
switches_.lcprewrite = true;
|
|
1071
|
+
break;
|
|
1072
|
+
|
|
1073
|
+
case 2: // $C082, $C08A: Read ROM, write disabled
|
|
1074
|
+
switches_.lcwrite = false;
|
|
1075
|
+
switches_.lcprewrite = false;
|
|
1076
|
+
break;
|
|
1077
|
+
|
|
1078
|
+
case 3: // $C083, $C08B: Read RAM, write enable on second read
|
|
1079
|
+
if (switches_.lcprewrite) {
|
|
1080
|
+
switches_.lcwrite = true;
|
|
1081
|
+
}
|
|
1082
|
+
switches_.lcprewrite = true;
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
switches_.lcram = readRAM;
|
|
1087
|
+
switches_.lcram2 = bank2;
|
|
1088
|
+
|
|
1089
|
+
return getFloatingBusValue();
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
void MMU::writeSoftSwitch(uint16_t address, uint8_t value) {
|
|
1093
|
+
uint8_t reg = address & 0xFF;
|
|
1094
|
+
|
|
1095
|
+
switch (reg) {
|
|
1096
|
+
// Keyboard strobe
|
|
1097
|
+
case 0x10:
|
|
1098
|
+
if (keyStrobeCallback_) {
|
|
1099
|
+
keyStrobeCallback_();
|
|
1100
|
+
}
|
|
1101
|
+
keyboardLatch_ &= 0x7F;
|
|
1102
|
+
break;
|
|
1103
|
+
|
|
1104
|
+
// Speaker
|
|
1105
|
+
case 0x30:
|
|
1106
|
+
if (speakerCallback_) {
|
|
1107
|
+
speakerCallback_();
|
|
1108
|
+
}
|
|
1109
|
+
break;
|
|
1110
|
+
|
|
1111
|
+
// 80STORE
|
|
1112
|
+
case 0x00:
|
|
1113
|
+
switches_.store80 = false;
|
|
1114
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1115
|
+
break;
|
|
1116
|
+
case 0x01:
|
|
1117
|
+
switches_.store80 = true;
|
|
1118
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1119
|
+
break;
|
|
1120
|
+
|
|
1121
|
+
// Memory switches
|
|
1122
|
+
case 0x02:
|
|
1123
|
+
switches_.ramrd = false;
|
|
1124
|
+
break;
|
|
1125
|
+
case 0x03:
|
|
1126
|
+
switches_.ramrd = true;
|
|
1127
|
+
break;
|
|
1128
|
+
case 0x04:
|
|
1129
|
+
switches_.ramwrt = false;
|
|
1130
|
+
break;
|
|
1131
|
+
case 0x05:
|
|
1132
|
+
switches_.ramwrt = true;
|
|
1133
|
+
break;
|
|
1134
|
+
case 0x06:
|
|
1135
|
+
switches_.intcxrom = false;
|
|
1136
|
+
break;
|
|
1137
|
+
case 0x07:
|
|
1138
|
+
switches_.intcxrom = true;
|
|
1139
|
+
break;
|
|
1140
|
+
case 0x08:
|
|
1141
|
+
switches_.altzp = false;
|
|
1142
|
+
break;
|
|
1143
|
+
case 0x09:
|
|
1144
|
+
switches_.altzp = true;
|
|
1145
|
+
break;
|
|
1146
|
+
case 0x0A:
|
|
1147
|
+
switches_.slotc3rom = false;
|
|
1148
|
+
break;
|
|
1149
|
+
case 0x0B:
|
|
1150
|
+
switches_.slotc3rom = true;
|
|
1151
|
+
break;
|
|
1152
|
+
case 0x0C:
|
|
1153
|
+
switches_.col80 = false;
|
|
1154
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1155
|
+
break;
|
|
1156
|
+
case 0x0D:
|
|
1157
|
+
switches_.col80 = true;
|
|
1158
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1159
|
+
break;
|
|
1160
|
+
case 0x0E:
|
|
1161
|
+
switches_.altCharSet = false;
|
|
1162
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1163
|
+
break;
|
|
1164
|
+
case 0x0F:
|
|
1165
|
+
switches_.altCharSet = true;
|
|
1166
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1167
|
+
break;
|
|
1168
|
+
|
|
1169
|
+
// Display switches
|
|
1170
|
+
case 0x50:
|
|
1171
|
+
switches_.text = false;
|
|
1172
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1173
|
+
break;
|
|
1174
|
+
case 0x51:
|
|
1175
|
+
switches_.text = true;
|
|
1176
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1177
|
+
break;
|
|
1178
|
+
case 0x52:
|
|
1179
|
+
switches_.mixed = false;
|
|
1180
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1181
|
+
break;
|
|
1182
|
+
case 0x53:
|
|
1183
|
+
switches_.mixed = true;
|
|
1184
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1185
|
+
break;
|
|
1186
|
+
case 0x54:
|
|
1187
|
+
switches_.page2 = false;
|
|
1188
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1189
|
+
break;
|
|
1190
|
+
case 0x55:
|
|
1191
|
+
switches_.page2 = true;
|
|
1192
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1193
|
+
break;
|
|
1194
|
+
case 0x56:
|
|
1195
|
+
switches_.hires = false;
|
|
1196
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1197
|
+
break;
|
|
1198
|
+
case 0x57:
|
|
1199
|
+
switches_.hires = true;
|
|
1200
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1201
|
+
break;
|
|
1202
|
+
|
|
1203
|
+
// Paddle trigger (write also triggers)
|
|
1204
|
+
case 0x70:
|
|
1205
|
+
paddleTriggerCycle_ = cycleCallback_ ? cycleCallback_() : 0;
|
|
1206
|
+
break;
|
|
1207
|
+
|
|
1208
|
+
// Annunciators
|
|
1209
|
+
case 0x58:
|
|
1210
|
+
switches_.an0 = false;
|
|
1211
|
+
break;
|
|
1212
|
+
case 0x59:
|
|
1213
|
+
switches_.an0 = true;
|
|
1214
|
+
break;
|
|
1215
|
+
case 0x5A:
|
|
1216
|
+
switches_.an1 = false;
|
|
1217
|
+
break;
|
|
1218
|
+
case 0x5B:
|
|
1219
|
+
switches_.an1 = true;
|
|
1220
|
+
break;
|
|
1221
|
+
case 0x5C:
|
|
1222
|
+
switches_.an2 = false;
|
|
1223
|
+
break;
|
|
1224
|
+
case 0x5D:
|
|
1225
|
+
switches_.an2 = true;
|
|
1226
|
+
break;
|
|
1227
|
+
case 0x5E:
|
|
1228
|
+
switches_.an3 = false;
|
|
1229
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1230
|
+
break;
|
|
1231
|
+
case 0x5F:
|
|
1232
|
+
switches_.an3 = true;
|
|
1233
|
+
if (videoSwitchCallback_) videoSwitchCallback_();
|
|
1234
|
+
break;
|
|
1235
|
+
|
|
1236
|
+
// Slot I/O space: $C090-$C0FF (slots 1-7)
|
|
1237
|
+
case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
|
|
1238
|
+
case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F:
|
|
1239
|
+
case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7:
|
|
1240
|
+
case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF:
|
|
1241
|
+
case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7:
|
|
1242
|
+
case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF:
|
|
1243
|
+
case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7:
|
|
1244
|
+
case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF:
|
|
1245
|
+
case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7:
|
|
1246
|
+
case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF:
|
|
1247
|
+
case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7:
|
|
1248
|
+
case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF:
|
|
1249
|
+
case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7:
|
|
1250
|
+
case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: {
|
|
1251
|
+
// Calculate slot number: $C090 = slot 1, $C0A0 = slot 2, etc.
|
|
1252
|
+
uint8_t slot = ((reg - 0x80) >> 4);
|
|
1253
|
+
uint8_t offset = reg & 0x0F;
|
|
1254
|
+
|
|
1255
|
+
if (slot >= 1 && slot <= 7 && slots_[slot - 1]) {
|
|
1256
|
+
slots_[slot - 1]->writeIO(offset, value);
|
|
1257
|
+
}
|
|
1258
|
+
break;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// Language card - writes do NOT enable write, and reset prewrite state
|
|
1262
|
+
case 0x80:
|
|
1263
|
+
case 0x81:
|
|
1264
|
+
case 0x82:
|
|
1265
|
+
case 0x83:
|
|
1266
|
+
case 0x84:
|
|
1267
|
+
case 0x85:
|
|
1268
|
+
case 0x86:
|
|
1269
|
+
case 0x87:
|
|
1270
|
+
case 0x88:
|
|
1271
|
+
case 0x89:
|
|
1272
|
+
case 0x8A:
|
|
1273
|
+
case 0x8B:
|
|
1274
|
+
case 0x8C:
|
|
1275
|
+
case 0x8D:
|
|
1276
|
+
case 0x8E:
|
|
1277
|
+
case 0x8F:
|
|
1278
|
+
handleLanguageCardSwitchWrite(reg);
|
|
1279
|
+
break;
|
|
1280
|
+
|
|
1281
|
+
default:
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
void MMU::handleLanguageCardSwitchWrite(uint8_t reg) {
|
|
1287
|
+
// Language card soft switches on WRITE access
|
|
1288
|
+
// On Apple IIe, writes to LC soft switches:
|
|
1289
|
+
// - Do NOT count toward the "double read" requirement for write-enable
|
|
1290
|
+
// - ALL writes reset the prewrite state (clearing any pending write-enable)
|
|
1291
|
+
// - Bank and read source selection still applies
|
|
1292
|
+
//
|
|
1293
|
+
// This means LDA $C083 + STA $C083 + LDA $C083 does NOT enable writes
|
|
1294
|
+
// (the STA resets the counter), but INC $C083 DOES enable writes
|
|
1295
|
+
// (because INC does two reads before the write).
|
|
1296
|
+
|
|
1297
|
+
bool bank2 = !(reg & 0x08);
|
|
1298
|
+
uint8_t op = reg & 0x03;
|
|
1299
|
+
|
|
1300
|
+
// RAM vs ROM read selection (same as reads)
|
|
1301
|
+
bool readRAM = (op == 0 || op == 3);
|
|
1302
|
+
|
|
1303
|
+
switch (op) {
|
|
1304
|
+
case 0: // $C080, $C088: Read RAM, write disabled
|
|
1305
|
+
switches_.lcwrite = false;
|
|
1306
|
+
switches_.lcprewrite = false;
|
|
1307
|
+
break;
|
|
1308
|
+
|
|
1309
|
+
case 1: // $C081, $C089: Read ROM
|
|
1310
|
+
// Write resets prewrite but doesn't disable existing lcwrite
|
|
1311
|
+
switches_.lcprewrite = false;
|
|
1312
|
+
break;
|
|
1313
|
+
|
|
1314
|
+
case 2: // $C082, $C08A: Read ROM, write disabled
|
|
1315
|
+
switches_.lcwrite = false;
|
|
1316
|
+
switches_.lcprewrite = false;
|
|
1317
|
+
break;
|
|
1318
|
+
|
|
1319
|
+
case 3: // $C083, $C08B: Read RAM
|
|
1320
|
+
// Write resets prewrite but doesn't disable existing lcwrite
|
|
1321
|
+
switches_.lcprewrite = false;
|
|
1322
|
+
break;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
switches_.lcram = readRAM;
|
|
1326
|
+
switches_.lcram2 = bank2;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
uint8_t MMU::readLanguageCard(uint16_t address) {
|
|
1330
|
+
if (switches_.lcram) {
|
|
1331
|
+
// Read from RAM
|
|
1332
|
+
bool useAux = switches_.altzp;
|
|
1333
|
+
|
|
1334
|
+
if (address < 0xE000) {
|
|
1335
|
+
// $D000-$DFFF
|
|
1336
|
+
uint16_t offset = address - 0xD000;
|
|
1337
|
+
if (switches_.lcram2) {
|
|
1338
|
+
return useAux ? auxLcBank2_[offset] : lcBank2_[offset];
|
|
1339
|
+
} else {
|
|
1340
|
+
return useAux ? auxLcBank1_[offset] : lcBank1_[offset];
|
|
1341
|
+
}
|
|
1342
|
+
} else {
|
|
1343
|
+
// $E000-$FFFF
|
|
1344
|
+
uint16_t offset = address - 0xE000;
|
|
1345
|
+
return useAux ? auxLcHighRAM_[offset] : lcHighRAM_[offset];
|
|
1346
|
+
}
|
|
1347
|
+
} else {
|
|
1348
|
+
// Read from ROM
|
|
1349
|
+
return systemROM_[address - 0xC000];
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
void MMU::writeLanguageCard(uint16_t address, uint8_t value) {
|
|
1354
|
+
if (!switches_.lcwrite) {
|
|
1355
|
+
return; // Write not enabled
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
bool useAux = switches_.altzp;
|
|
1359
|
+
|
|
1360
|
+
if (address < 0xE000) {
|
|
1361
|
+
// $D000-$DFFF
|
|
1362
|
+
uint16_t offset = address - 0xD000;
|
|
1363
|
+
if (switches_.lcram2) {
|
|
1364
|
+
if (useAux) {
|
|
1365
|
+
auxLcBank2_[offset] = value;
|
|
1366
|
+
} else {
|
|
1367
|
+
lcBank2_[offset] = value;
|
|
1368
|
+
}
|
|
1369
|
+
} else {
|
|
1370
|
+
if (useAux) {
|
|
1371
|
+
auxLcBank1_[offset] = value;
|
|
1372
|
+
} else {
|
|
1373
|
+
lcBank1_[offset] = value;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
} else {
|
|
1377
|
+
// $E000-$FFFF
|
|
1378
|
+
uint16_t offset = address - 0xE000;
|
|
1379
|
+
if (useAux) {
|
|
1380
|
+
auxLcHighRAM_[offset] = value;
|
|
1381
|
+
} else {
|
|
1382
|
+
lcHighRAM_[offset] = value;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
} // namespace a2e
|