diff --git a/Machines/Apple/AppleIIgs/AppleIIgs.cpp b/Machines/Apple/AppleIIgs/AppleIIgs.cpp index ce3e2236a..53de14c53 100644 --- a/Machines/Apple/AppleIIgs/AppleIIgs.cpp +++ b/Machines/Apple/AppleIIgs/AppleIIgs.cpp @@ -71,115 +71,6 @@ constexpr uint8_t default_bram[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x96, 0x57, 0x3c, }; -/*class MemManagerChecker { - int handle_total_ = 0; - bool dump_bank(const Apple::IIgs::MemoryMap &memory, const char *name, uint32_t address, bool print, uint32_t must_contain = 0xffffffff) { - const auto handles = memory.regions[memory.region_map[0xe117]].read; - bool did_find = false; - - if(print) printf("%s: ", name); - int max = 52; - uint32_t last_visited = 0; - - // Seed address. - address = uint32_t(handles[address] | (handles[address+1] << 8) | (handles[address+2] << 16) | (handles[address+3] << 24)); - - while(true) { - did_find |= address == must_contain; - - if(!address) { - if(print) printf("nil\n"); - break; - } - ++handle_total_; - if(address < 0xe11700 || address > 0xe11aff) { - if(print) printf("Out of bounds error with address = %06x!\n", address); - return false; - } - if((address - 0xe11700)%20) { - if(print) printf("Address alignment error!\n"); - return false; - } - - const uint32_t previous = uint32_t(handles[address+12] | (handles[address+13] << 8) | (handles[address+14] << 16) | (handles[address+15] << 24)); - const uint32_t next = uint32_t(handles[address+16] | (handles[address+17] << 8) | (handles[address+18] << 16) | (handles[address+19] << 24)); - const uint32_t pointer = uint32_t(handles[address] | (handles[address+1] << 8) | (handles[address+2] << 16) | (handles[address+3] << 24)); - const uint32_t size = uint32_t(handles[address+8] | (handles[address+9] << 8) | (handles[address+10] << 16) | (handles[address+11] << 24)); - if(print) printf("%06x (<- %06x | %06x ->) [%06x:%06x] -> \n", address, previous, next, pointer, size); - - if(previous && ((previous < 0xe0'0000) || (previous > 0xe2'0000))) { - if(print) printf("Out of bounds error with previous = %06x! [%d && (%d || %d)]\n", previous, bool(previous), previous < 0xe0'0000, previous > 0xe2'0000); - return false; - } - if((previous || last_visited) && (previous != last_visited)) { - if(print) printf("Back link error!\n"); - return false; - } - - last_visited = address; - address = next; - - --max; - if(!max) { - if(print) printf("Endless loop error!\n"); - return false; - } - } - - if(must_contain != 0xffffffff) { - if(!did_find) { - if(print) printf("%08x not found\n", must_contain); - return false; - } - } - - return true; - } - -// bool has_seen_valid_memory_ = false; -// bool should_validate_ = false; - - public: - bool validate_memory_manager(const Apple::IIgs::MemoryMap &memory, bool print) { - const auto pointers = memory.regions[memory.region_map[0xe116]].read; - - constexpr uint32_t address = 0xe1162c; - const uint32_t last_high_handle = uint32_t(pointers[address] | (pointers[address+1] << 8) | (pointers[address+2] << 16) | (pointers[address+3] << 24)); - - // Check for initial state having been reached. -// if(!has_seen_valid_memory_) { -// if(pointers[0xe11624]) return true; -// for(int c = 0xe1160c; c < 0xe1161c; c++) { -// if(pointers[c]) return true; -// } -// has_seen_valid_memory_ = true; -// } - - // Output. - if(print) printf("\nNumber of banks: %d\n", pointers[0xe11624]); - if(print) printf("Last high handle: %04x\n", last_high_handle); - bool result = true; - - handle_total_ = 0; - result &= dump_bank(memory, "Mem", 0xe11600, print, last_high_handle); - result &= dump_bank(memory, "Purge", 0xe11604, print); - result &= dump_bank(memory, "Free", 0xe11608, print); - - // TODO: and other checs? - -// result &= dump_bank("Bank 0", 0xe1160c); -// result &= dump_bank("Bank 1", 0xe11610); -// result &= dump_bank("Bank E0", 0xe11614); -// result &= dump_bank("Bank E1", 0xe11618); -// result &= dump_bank("Bank FF", 0xe1161c); - - if(print) printf("Total: %d\n", handle_total_); - if(handle_total_ != 51) result &= false; - - return result; - } -};*/ - } namespace Apple { @@ -305,7 +196,6 @@ class ConcreteMachine: iwm_->set_drive(1, &drives35_[1]); // Randomise RAM contents. -// std::srand(23); Memory::Fuzz(ram_); // Prior to ROM03 there's no power-on bit. @@ -382,29 +272,10 @@ class ConcreteMachine: } // MARK: BusHandler. - uint64_t total = 0; forceinline Cycles perform_bus_operation(const CPU::WDC65816::BusOperation operation, const uint32_t address, uint8_t *const value) { const auto ®ion = memory_.region(address); - static bool log = false; bool is_1Mhz = false; -// if(operation == CPU::WDC65816::BusOperation::ReadOpcode) { -// if(address == 0xfe00d5) { -// printf(""); -// } -// -// printf("%06x a:%04x x:%04x y:%04x s:%04x d:%04x b:%04x\n", -// address, -// m65816_.value_of(CPU::WDC65816::Register::A), -// m65816_.value_of(CPU::WDC65816::Register::X), -// m65816_.value_of(CPU::WDC65816::Register::Y), -//// m65816_.value_of(CPU::WDC65816::Register::Flags), -// m65816_.value_of(CPU::WDC65816::Register::StackPointer), -// m65816_.value_of(CPU::WDC65816::Register::Direct), -// m65816_.value_of(CPU::WDC65816::Register::DataBank) -// ); -// } - if(operation == CPU::WDC65816::BusOperation::ReadVector && !(memory_.get_shadow_register()&0x40)) { // I think vector pulls always go to ROM? // That's slightly implied in the documentation, and doing so makes GS/OS boot, so... @@ -514,6 +385,11 @@ class ConcreteMachine: video_->set_page2(*value & 0x40); break; + case Read(0xc069): + case Write(0xc069): + // Swallow silently; often hit as a side effect of a 16-bit write to 0xc068. + break; + // Various independent memory switch reads [TODO: does the IIe-style keyboard provide the low seven?]. #define SwitchRead(s) *value = memory_.s ? 0x80 : 0x00; is_1Mhz = true; #define LanguageRead(s) SwitchRead(language_card_switches().state().s) @@ -899,7 +775,6 @@ class ConcreteMachine: // Temporary: log _potential_ mistakes. if((address_suffix < 0xc100 && address_suffix >= 0xc090) || (address_suffix < 0xc080)) { printf("Internal card-area access: %04x\n", address_suffix); -// log |= operation == CPU::WDC65816::BusOperation::ReadOpcode; } if(is_read) { *value = rom_[rom_.size() - 65536 + address_suffix]; @@ -934,7 +809,6 @@ class ConcreteMachine: if(address_suffix < 0xc080) { // TODO: all other IO accesses. printf("Unhandled IO %s: %04x\n", is_read ? "read" : "write", address_suffix); -// assert(false); } } } @@ -967,63 +841,6 @@ class ConcreteMachine: } } - - if(operation == CPU::WDC65816::BusOperation::ReadOpcode) { -// if(total >= 92168628 && !validate_memory_manager(false) && address < 0xe10000) { -// printf("@%llu\n", static_cast(total)); -// validate_memory_manager(true); -// } -// assert(address); - } - -// if(total == 132222166 || total == 467891275 || total == 491026055) { -// validate_memory_manager(true); -// } - -// if(operation == CPU::WDC65816::BusOperation::Write && ( -// (address >= 0xe11700 && address <= 0xe11aff) || -// address == 0xe11624 || (address >= 0xe1160c && address < 0xe1161c)) -// ) { -// // Test for breakages in the chain. -// if(!dump_memory_manager()) { -// printf("Broken at %llu\n", static_cast(total)); -// } else { -// printf("Correct at %llu\n", static_cast(total)); -// } -// } - - if(operation == CPU::WDC65816::BusOperation::ReadOpcode) { -// if(total > 482342960 && total < 482352960 && address == 0xe10000) { -// printf("entry: %llu\n", static_cast(total)); -// } - -// log |= address == 0xfc144f; -// log &= !((address < 0xfc144f) || (address >= 0xfc1490)); - -// if(address == 0xfc02b1) { -// dump_memory_manager(); -// } - } - - if(log) { - printf("%06x %s %02x [%s]", address, isReadOperation(operation) ? "->" : "<-", *value, (is_1Mhz || (speed_register_ & motor_flags_)) ? "1.0" : "2.8"); - if(operation == CPU::WDC65816::BusOperation::ReadOpcode) { - printf(" a:%04x x:%04x y:%04x s:%04x e:%d p:%02x db:%02x pb:%02x d:%04x [tot:%llu]\n", - m65816_.value_of(CPU::WDC65816::Register::A), - m65816_.value_of(CPU::WDC65816::Register::X), - m65816_.value_of(CPU::WDC65816::Register::Y), - m65816_.value_of(CPU::WDC65816::Register::StackPointer), - m65816_.value_of(CPU::WDC65816::Register::EmulationFlag), - m65816_.value_of(CPU::WDC65816::Register::Flags), - m65816_.value_of(CPU::WDC65816::Register::DataBank), - m65816_.value_of(CPU::WDC65816::Register::ProgramBank), - m65816_.value_of(CPU::WDC65816::Register::Direct), - static_cast(total) - ); - } else printf("\n"); - } - - Cycles duration; // In preparation for this test: the top bit of speed_register_ has been inverted, @@ -1054,7 +871,6 @@ class ConcreteMachine: fast_access_phase_ = (fast_access_phase_ + duration.as()) % 50; slow_access_phase_ = (slow_access_phase_ + duration.as()) % 912; - // Propagate time far and wide. cycles_since_clock_tick_ += duration; auto ticks = cycles_since_clock_tick_.divide(Cycles(CLOCK_RATE)).as_integral(); @@ -1066,15 +882,10 @@ class ConcreteMachine: update_interrupts(); } -// if(operation == CPU::WDC65816::BusOperation::ReadOpcode && *value == 0x00) { -// printf("%06x: %02x\n", address, *value); -// } - video_ += duration; iwm_ += duration; cycles_since_audio_update_ += duration; adb_glu_ += duration; - total += decltype(total)(duration.as_integral()); if(cycles_since_audio_update_ >= cycles_until_audio_event_) { AudioUpdater updater(this); diff --git a/Machines/Apple/AppleIIgs/MemoryMap.cpp b/Machines/Apple/AppleIIgs/MemoryMap.cpp new file mode 100644 index 000000000..04a33caaa --- /dev/null +++ b/Machines/Apple/AppleIIgs/MemoryMap.cpp @@ -0,0 +1,533 @@ +// +// MemoryMap.cpp +// Clock Signal +// +// Created by Thomas Harte on 04/01/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#include "MemoryMap.hpp" + +using namespace Apple::IIgs; +using PagingType = Apple::II::PagingType; + +void MemoryMap::set_storage(std::vector &ram, std::vector &rom) { + // Keep a pointer for later; also note the proper RAM offset. + ram_base_ = ram.data(); + shadow_base_[0] = ram_base_; // i.e. all unshadowed writes go to where they've already gone (to make a no-op). + shadow_base_[1] = &ram[ram.size() - 0x02'0000]; // i.e. all shadowed writes go somewhere in the last + // 128bk of RAM. + + // Establish bank mapping. + uint8_t next_region = 0; + auto region = [&]() -> uint8_t { + assert(next_region != this->regions_.size()); + return next_region++; + }; + auto set_region = [this](uint8_t bank, uint16_t start, uint16_t end, uint8_t region) { + assert((end == 0xffff) || !(end&0xff)); + assert(!(start&0xff)); + + // Fill in memory map. + size_t target = size_t((bank << 8) | (start >> 8)); + for(int c = start; c < end; c += 0x100) { + region_map_[target] = region; + ++target; + } + }; + auto set_regions = [set_region, region](uint8_t bank, std::initializer_list addresses, std::vector allocated_regions = {}) { + uint16_t previous = 0x0000; + auto next_region = allocated_regions.begin(); + for(uint16_t address: addresses) { + set_region(bank, previous, address, next_region != allocated_regions.end() ? *next_region : region()); + previous = address; + assert(next_region != allocated_regions.end() || allocated_regions.empty()); + if(next_region != allocated_regions.end()) ++next_region; + } + assert(next_region == allocated_regions.end()); + }; + + // Current beliefs about the IIgs memory map: + // + // * language card banking applies to banks $00, $01, $e0 and $e1; + // * auxiliary memory switches apply to bank $00 only; + // * shadowing may be enabled only on banks $00 and $01, or on all RAM pages; and + // * whether bit 16 of the address is passed to the Mega II is selectable — this affects both the destination + // of odd-bank shadows, and whether bank $e1 is actually distinct from $e0. + // + // So: + // + // * bank $00 needs to be divided by auxiliary and language card zones; + // * banks $01, $e0 and $e1 need to be divided by language card zones only; and + // * ROM banks and all other fast RAM banks don't need subdivision. + + // Language card zones: + // + // $D000–$E000 4kb window, into either bank 1 or bank 2 + // $E000–end 12kb window, always the same RAM. + + // Auxiliary zones: + // + // $0000–$0200 Zero page (and stack) + // $0200–$0400 [space in between] + // $0400–$0800 Text Page 1 + // $0800–$2000 [space in between] + // $2000–$4000 High-res Page 1 + // $4000–$C000 [space in between] + + // Card zones: + // + // $C100–$C2FF either cards or IIe-style ROM + // $C300–$C3FF IIe-supplied 80-column card replacement ROM + // $C400–$C7FF either cards or IIe-style ROM + // $C800–$CFFF Standard extended card area + + // Reserve region 0 as that for unmapped memory. + region(); + + // Bank $00: all locations potentially affected by the auxiliary switches or the + // language switches. + set_regions(0x00, { + 0x0200, 0x0400, 0x0800, + 0x2000, 0x4000, + 0xc000, 0xc100, 0xc300, 0xc400, 0xc800, + 0xd000, 0xe000, + 0xffff + }); + + // Bank $01: all locations potentially affected by the language switches and card switches. + set_regions(0x01, { + 0xc000, 0xc100, 0xc300, 0xc400, 0xc800, + 0xd000, 0xe000, + 0xffff + }); + + // Banks $02–[end of RAM]: a single region. + const auto fast_region = region(); + const uint8_t fast_ram_bank_limit = uint8_t(ram.size() / 0x01'0000); + for(uint8_t bank = 0x02; bank < fast_ram_bank_limit; bank++) { + set_region(bank, 0x0000, 0xffff, fast_region); + } + + // [Banks $80–$e0: empty]. + + // Banks $e0, $e1: all locations potentially affected by the language switches or marked for IO. + // Alas, separate regions are needed due to the same ROM appearing on both pages. + for(uint8_t c = 0; c < 2; c++) { + set_regions(0xe0 + c, {0xc000, 0xc100, 0xc300, 0xc400, 0xc800, 0xd000, 0xe000, 0xffff}); + } + + // [Banks $e2–[ROM start]: empty]. + + // ROM banks: directly mapped to ROM. + const uint8_t rom_bank_count = uint8_t(rom.size() >> 16); + const uint8_t first_rom_bank = uint8_t(0x100 - rom_bank_count); + const uint8_t rom_region = region(); + for(uint8_t c = 0; c < rom_bank_count; ++c) { + set_region(first_rom_bank + c, 0x0000, 0xffff, rom_region); + } + + // Apply proper storage to those banks. + auto set_storage = [this](uint32_t address, const uint8_t *read, uint8_t *write) { + // Don't allow the reserved null region to be modified. + assert(region_map_[address >> 8]); + + // Either set or apply a quick bit of testing as to the logic at play. + auto ®ion = regions_[region_map_[address >> 8]]; + if(read) read -= address; + if(write) write -= address; + if(!region.read) { + region.read = read; + region.write = write; + } else { + assert(region.read == read); + assert(region.write == write); + } + }; + + // This is highly redundant, but decouples this step from the above. + for(size_t c = 0; c < 0x80'0000; c += 0x100) { + if(c < ram.size() - 0x02'0000) { + set_storage(uint32_t(c), &ram[c], &ram[c]); + } + } + uint8_t *const slow_ram = &ram[ram.size() - 0x02'0000] - 0xe0'0000; + for(size_t c = 0xe0'0000; c < 0xe2'0000; c += 0x100) { + set_storage(uint32_t(c), &slow_ram[c], &slow_ram[c]); + } + for(uint32_t c = 0; c < uint32_t(rom_bank_count); c++) { + set_storage((first_rom_bank + c) << 16, &rom[c << 16], nullptr); + } + + // Set shadowing as working from banks 0 and 1 (forever). + shadow_banks_[0] = true; + + // TODO: set 1Mhz flags. + + // Apply initial language/auxiliary state. + set_paging<~0>(); +} + +void MemoryMap::set_shadow_register(uint8_t value) { + const uint8_t diff = value ^ shadow_register_; + shadow_register_ = value; + + if(diff & 0x40) { // IO/language-card inhibit. + set_paging(); + } + + if(diff & 0x3f) { + set_shadowing(); + } +} + +uint8_t MemoryMap::get_shadow_register() const { + return shadow_register_; +} + +void MemoryMap::set_speed_register(uint8_t value) { + speed_register_ = value; + + // Enable or disable shadowing from banks 0x02–0x80. + for(size_t c = 0x01; c < 0x40; c++) { + shadow_banks_[c] = speed_register_ & 0x10; + } +} + +void MemoryMap::set_state_register(uint8_t value) { + auxiliary_switches_.set_state(value); + language_card_.set_state(value); +} + +uint8_t MemoryMap::get_state_register() const { + return language_card_.get_state() | auxiliary_switches_.get_state(); +} + +void MemoryMap::access(uint16_t address, bool is_read) { + auxiliary_switches_.access(address, is_read); + if((address & 0xfff0) == 0xc080) language_card_.access(address, is_read); +} + +void MemoryMap::assert_is_region(uint8_t start, uint8_t end) { + assert(region_map_[start] == region_map_[start-1]+1); + assert(region_map_[end-1] == region_map_[start]); + assert(region_map_[end] == region_map_[end-1]+1); +} + +template void MemoryMap::set_paging() { + // Establish whether main or auxiliary RAM + // is exposed in bank $00 for a bunch of regions. + if constexpr (type & PagingType::Main) { + const auto set = [&](std::size_t page, const auto &flags) { + auto ®ion = regions_[region_map_[page]]; + region.read = flags.read ? &ram_base_[0x01'0000] : ram_base_; + region.write = flags.write ? &ram_base_[0x01'0000] : ram_base_; + }; + const auto state = auxiliary_switches_.main_state(); + + // Base: $0200–$03FF. + set(0x02, state.base); + assert_is_region(0x02, 0x04); + + // Region $0400–$07ff. + set(0x04, state.region_04_08); + assert_is_region(0x04, 0x08); + + // Base: $0800–$1FFF. + set(0x08, state.base); + assert_is_region(0x08, 0x20); + + // Region $2000–$3FFF. + set(0x20, state.region_20_40); + assert_is_region(0x20, 0x40); + + // Base: $4000–$BFFF. + set(0x40, state.base); + assert_is_region(0x40, 0xc0); + } + + // Update whether base or auxiliary RAM is visible in: (i) the zero + // and stack pages; and (ii) anywhere that the language card is exposing RAM instead of ROM. + if constexpr (bool(type & PagingType::ZeroPage)) { + // Affects bank $00 only, and should be a single region. + auto ®ion = regions_[region_map_[0]]; + region.read = region.write = auxiliary_switches_.zero_state() ? &ram_base_[0x01'0000] : ram_base_; + assert(region_map_[0x0000] == region_map_[0x0001]); + assert(region_map_[0x0001]+1 == region_map_[0x0002]); + } + + // Establish whether ROM or card switches are exposed in the distinct + // regions C100–C2FF, C300–C3FF, C400–C7FF and C800–CFFF. + // + // On the IIgs it intersects with the current shadow register. + if constexpr (bool(type & (PagingType::CardArea | PagingType::Main))) { + const bool inhibit_banks0001 = shadow_register_ & 0x40; + const auto state = auxiliary_switches_.card_state(); + + auto apply = [&state, this](uint32_t bank_base) { + auto &c0_region = regions_[region_map_[bank_base | 0xc0]]; + auto &c1_region = regions_[region_map_[bank_base | 0xc1]]; + auto &c3_region = regions_[region_map_[bank_base | 0xc3]]; + auto &c4_region = regions_[region_map_[bank_base | 0xc4]]; + auto &c8_region = regions_[region_map_[bank_base | 0xc8]]; + + const uint8_t *const rom = ®ions_[region_map_[0xffd0]].read[0xffc100] - ((bank_base << 8) + 0xc100); + + // This is applied dynamically as it may be added or lost in banks $00 and $01. + c0_region.flags |= Region::IsIO; + + const auto apply_region = [&](bool flag, auto ®ion) { + region.write = nullptr; + if(flag) { + region.read = rom; + region.flags &= ~Region::IsIO; + } else { + region.flags |= Region::IsIO; + } + }; + + apply_region(state.region_C1_C3, c1_region); + apply_region(state.region_C3, c3_region); + apply_region(state.region_C4_C8, c4_region); + apply_region(state.region_C8_D0, c8_region); + + // Sanity checks. + assert(region_map_[bank_base | 0xc1] == region_map_[bank_base | 0xc0]+1); + assert(region_map_[bank_base | 0xc2] == region_map_[bank_base | 0xc1]); + assert(region_map_[bank_base | 0xc3] == region_map_[bank_base | 0xc2]+1); + assert(region_map_[bank_base | 0xc4] == region_map_[bank_base | 0xc3]+1); + assert(region_map_[bank_base | 0xc7] == region_map_[bank_base | 0xc4]); + assert(region_map_[bank_base | 0xc8] == region_map_[bank_base | 0xc7]+1); + assert(region_map_[bank_base | 0xcf] == region_map_[bank_base | 0xc8]); + assert(region_map_[bank_base | 0xd0] == region_map_[bank_base | 0xcf]+1); + }; + + if(inhibit_banks0001) { + // Set no IO in the Cx00 range for banks $00 and $01, just + // regular RAM (or possibly auxiliary). + const auto auxiliary_state = auxiliary_switches_.main_state(); + for(uint8_t region = region_map_[0x00c0]; region < region_map_[0x00d0]; region++) { + regions_[region].read = auxiliary_state.base.read ? &ram_base_[0x01'0000] : ram_base_; + regions_[region].write = auxiliary_state.base.write ? &ram_base_[0x01'0000] : ram_base_; + regions_[region].flags &= ~Region::IsIO; + } + for(uint8_t region = region_map_[0x01c0]; region < region_map_[0x01d0]; region++) { + regions_[region].read = regions_[region].write = ram_base_; + regions_[region].flags &= ~Region::IsIO; + } + } else { + // Obey the card state for banks $00 and $01. + apply(0x0000); + apply(0x0100); + } + + // Obey the card state for banks $e0 and $e1. + apply(0xe000); + apply(0xe100); + } + + // Update the region from + // $D000 onwards as per the state of the language card flags — there may + // end up being ROM or RAM (or auxiliary RAM), and the first 4kb of it + // may be drawn from either of two pools. + if constexpr (bool(type & (PagingType::LanguageCard | PagingType::ZeroPage | PagingType::Main))) { + const auto language_state = language_card_.state(); + const auto zero_state = auxiliary_switches_.zero_state(); + const auto main = auxiliary_switches_.main_state(); + const bool inhibit_banks0001 = shadow_register_ & 0x40; + + auto apply = [&language_state, this](uint32_t bank_base, uint8_t *ram) { + // This assumes bank 1 is the one before bank 2 when RAM is linear. + uint8_t *const d0_ram_bank = ram - (language_state.bank2 ? 0x0000 : 0x1000); + + // Crib the ROM pointer from a page it's always visible on. + const uint8_t *const rom = ®ions_[region_map_[0xffd0]].read[0xff'd000] - ((bank_base << 8) + 0xd000); + + auto &d0_region = regions_[region_map_[bank_base | 0xd0]]; + d0_region.read = language_state.read ? d0_ram_bank : rom; + d0_region.write = language_state.write ? nullptr : d0_ram_bank; + + auto &e0_region = regions_[region_map_[bank_base | 0xe0]]; + e0_region.read = language_state.read ? ram : rom; + e0_region.write = language_state.write ? nullptr : ram; + + // Assert assumptions made above re: memory layout. + assert(region_map_[bank_base | 0xd0] + 1 == region_map_[bank_base | 0xe0]); + assert(region_map_[bank_base | 0xe0] == region_map_[bank_base | 0xff]); + }; + auto set_no_card = [this](uint32_t bank_base, uint8_t *read, uint8_t *write) { + auto &d0_region = regions_[region_map_[bank_base | 0xd0]]; + d0_region.read = read; + d0_region.write = write; + + auto &e0_region = regions_[region_map_[bank_base | 0xe0]]; + e0_region.read = read; + e0_region.write = write; + + // Assert assumptions made above re: memory layout. + assert(region_map_[bank_base | 0xd0] + 1 == region_map_[bank_base | 0xe0]); + assert(region_map_[bank_base | 0xe0] == region_map_[bank_base | 0xff]); + }; + + if(inhibit_banks0001) { + set_no_card(0x0000, + main.base.read ? &ram_base_[0x01'0000] : ram_base_, + main.base.write ? &ram_base_[0x01'0000] : ram_base_); + set_no_card(0x0100, ram_base_, ram_base_); + } else { + apply(0x0000, zero_state ? &ram_base_[0x01'0000] : ram_base_); + apply(0x0100, ram_base_); + } + + // The pointer stored in region_map_[0xe000] has already been adjusted for + // the 0xe0'0000 addressing offset. + uint8_t *const e0_ram = regions_[region_map_[0xe000]].write; + apply(0xe000, e0_ram); + apply(0xe100, e0_ram); + } +} + +// IIgs specific: sets or resets the ::IsShadowed flag across affected banks as +// per the current state of the shadow register. +// +// Completely distinct from the auxiliary and language card switches. +void MemoryMap::set_shadowing() { + // Relevant bits: + // + // b5: inhibit shadowing, text page 2 [if ROM 03; as if always set otherwise] + // b4: inhibit shadowing, auxiliary high-res graphics + // b3: inhibit shadowing, super high-res graphics + // b2: inhibit shadowing, high-res graphics page 2 + // b1: inhibit shadowing, high-res graphics page 1 + // b0: inhibit shadowing, text page 1 + // + // The interpretations of how the overlapping high-res and super high-res inhibit + // bits apply used below is taken from The Apple IIgs Technical Reference, P. 178. + + // Of course, zones are: + // + // $0400–$0800 Text Page 1 + // $0800–$0C00 Text Page 2 [ROM 03 machines] + // $2000–$4000 High-res Page 1, and Super High-res in odd banks + // $4000–$6000 High-res Page 2, and Huper High-res in odd banks + // $6000–$a000 Odd banks only, rest of Super High-res + // [plus IO and language card space, subject to your definition of shadowing] + + enum Inhibit { + TextPage1 = 0x01, + HighRes1 = 0x02, + HighRes2 = 0x04, + SuperHighRes = 0x08, + AuxiliaryHighRes = 0x10, + TextPage2 = 0x20, + }; + + // Clear all shadowing. + shadow_pages_.reset(); + + // Text Page 1, main and auxiliary — $0400–$0800. + { + const bool should_shadow_text1 = !(shadow_register_ & Inhibit::TextPage1); + if(should_shadow_text1) { + shadow_pages_ |= shadow_text1_; + } + } + + // Text Page 2, main and auxiliary — 0x0800–0x0c00. + // + // The mask applied will be all 0 for a pre-ROM03 machine. + { + const bool should_shadow_text2 = !(shadow_register_ & Inhibit::TextPage2); + if(should_shadow_text2) { + shadow_pages_ |= shadow_text2_; + } + } + + // Hi-res graphics Page 1, main and auxiliary — $2000–$4000; + // also part of the super high-res graphics page on odd pages. + // + // Even test applied: + // high-res graphics page 1 inhibit bit alone is definitive. + // + // Odd test: + // (high-res graphics inhibit or auxiliary high res graphics inhibit) _and_ + // (super high-res inhibit). + // + { + const bool should_shadow_highres1 = !(shadow_register_ & Inhibit::HighRes1); + if(should_shadow_highres1) { + shadow_pages_ |= shadow_highres1_; + } + + const bool should_shadow_aux_highres1 = !( + shadow_register_ & (Inhibit::HighRes1 | Inhibit::AuxiliaryHighRes) && + shadow_register_ & Inhibit::SuperHighRes + ); + if(should_shadow_aux_highres1) { + shadow_pages_ |= shadow_highres1_aux_; + } + } + + // Hi-res graphics Page 2, main and auxiliary — $4000–$6000; + // also part of the super high-res graphics page. + // + // Test applied: much like that for page 1. + { + const bool should_shadow_highres2 = !(shadow_register_ & Inhibit::HighRes2); + if(should_shadow_highres2) { + shadow_pages_ |= shadow_highres2_; + } + + const bool should_shadow_aux_highres2 = !( + shadow_register_ & (Inhibit::HighRes2 | Inhibit::AuxiliaryHighRes) && + shadow_register_ & Inhibit::SuperHighRes + ); + if(should_shadow_aux_highres2) { + shadow_pages_ |= shadow_highres2_aux_; + } + } + + // Residue of Super Hi-Res — $6000–$a000 (odd pages only). + // + // Test applied: + // auxiliary high res graphics inhibit and super high-res inhibit + { + const bool should_shadow_superhighres = !( + shadow_register_ & Inhibit::SuperHighRes && + shadow_register_ & Inhibit::AuxiliaryHighRes + ); + if(should_shadow_superhighres) { + shadow_pages_ |= shadow_superhighres_; + } + } +} + +void MemoryMap::setup_shadow_maps(bool is_rom03) { + static constexpr int shadow_shift = 10; + static constexpr int auxiliary_offset = 0x1'0000 >> shadow_shift; + + for(size_t c = 0x0400 >> shadow_shift; c < 0x0800 >> shadow_shift; c++) { + shadow_text1_[c] = shadow_text1_[c+auxiliary_offset] = true; + } + + // Shadowing of text page 2 was added only with the ROM03 machine. + if(is_rom03) { + for(size_t c = 0x0800 >> shadow_shift; c < 0x0c00 >> shadow_shift; c++) { + shadow_text2_[c] = shadow_text2_[c+auxiliary_offset] = true; + } + } + + for(size_t c = 0x2000 >> shadow_shift; c < 0x4000 >> shadow_shift; c++) { + shadow_highres1_[c] = true; + shadow_highres1_aux_[c+auxiliary_offset] = true; + } + + for(size_t c = 0x4000 >> shadow_shift; c < 0x6000 >> shadow_shift; c++) { + shadow_highres2_[c] = true; + shadow_highres2_aux_[c+auxiliary_offset] = true; + } + + for(size_t c = 0x6000 >> shadow_shift; c < 0xa000 >> shadow_shift; c++) { + shadow_superhighres_[c+auxiliary_offset] = true; + } +} diff --git a/Machines/Apple/AppleIIgs/MemoryMap.hpp b/Machines/Apple/AppleIIgs/MemoryMap.hpp index 3e38e38f1..00c4a4355 100644 --- a/Machines/Apple/AppleIIgs/MemoryMap.hpp +++ b/Machines/Apple/AppleIIgs/MemoryMap.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include "../AppleII/LanguageCardSwitches.hpp" @@ -19,9 +20,6 @@ namespace Apple::IIgs { class MemoryMap { - private: - using PagingType = Apple::II::PagingType; - public: // MARK: - Initial construction and configuration. @@ -29,204 +27,20 @@ class MemoryMap { setup_shadow_maps(is_rom03); } - void set_storage(std::vector &ram, std::vector &rom) { - // Keep a pointer for later; also note the proper RAM offset. - ram_base = ram.data(); - shadow_base[0] = ram_base; // i.e. all unshadowed writes go to where they've already gone (to make a no-op). - shadow_base[1] = &ram[ram.size() - 0x02'0000]; // i.e. all shadowed writes go somewhere in the last - // 128bk of RAM. - - // Establish bank mapping. - uint8_t next_region = 0; - auto region = [&]() -> uint8_t { - assert(next_region != this->regions.size()); - return next_region++; - }; - auto set_region = [this](uint8_t bank, uint16_t start, uint16_t end, uint8_t region) { - assert((end == 0xffff) || !(end&0xff)); - assert(!(start&0xff)); - - // Fill in memory map. - size_t target = size_t((bank << 8) | (start >> 8)); - for(int c = start; c < end; c += 0x100) { - region_map[target] = region; - ++target; - } - }; - auto set_regions = [set_region, region](uint8_t bank, std::initializer_list addresses, std::vector allocated_regions = {}) { - uint16_t previous = 0x0000; - auto next_region = allocated_regions.begin(); - for(uint16_t address: addresses) { - set_region(bank, previous, address, next_region != allocated_regions.end() ? *next_region : region()); - previous = address; - assert(next_region != allocated_regions.end() || allocated_regions.empty()); - if(next_region != allocated_regions.end()) ++next_region; - } - assert(next_region == allocated_regions.end()); - }; - - // Current beliefs about the IIgs memory map: - // - // * language card banking applies to banks $00, $01, $e0 and $e1; - // * auxiliary memory switches apply to bank $00 only; - // * shadowing may be enabled only on banks $00 and $01, or on all RAM pages; and - // * whether bit 16 of the address is passed to the Mega II is selectable — this affects both the destination - // of odd-bank shadows, and whether bank $e1 is actually distinct from $e0. - // - // So: - // - // * bank $00 needs to be divided by auxiliary and language card zones; - // * banks $01, $e0 and $e1 need to be divided by language card zones only; and - // * ROM banks and all other fast RAM banks don't need subdivision. - - // Language card zones: - // - // $D000–$E000 4kb window, into either bank 1 or bank 2 - // $E000–end 12kb window, always the same RAM. - - // Auxiliary zones: - // - // $0000–$0200 Zero page (and stack) - // $0200–$0400 [space in between] - // $0400–$0800 Text Page 1 - // $0800–$2000 [space in between] - // $2000–$4000 High-res Page 1 - // $4000–$C000 [space in between] - - // Card zones: - // - // $C100–$C2FF either cards or IIe-style ROM - // $C300–$C3FF IIe-supplied 80-column card replacement ROM - // $C400–$C7FF either cards or IIe-style ROM - // $C800–$CFFF Standard extended card area - - // Reserve region 0 as that for unmapped memory. - region(); - - // Bank $00: all locations potentially affected by the auxiliary switches or the - // language switches. - set_regions(0x00, { - 0x0200, 0x0400, 0x0800, - 0x2000, 0x4000, - 0xc000, 0xc100, 0xc300, 0xc400, 0xc800, - 0xd000, 0xe000, - 0xffff - }); - - // Bank $01: all locations potentially affected by the language switches and card switches. - set_regions(0x01, { - 0xc000, 0xc100, 0xc300, 0xc400, 0xc800, - 0xd000, 0xe000, - 0xffff - }); - - // Banks $02–[end of RAM]: a single region. - const auto fast_region = region(); - const uint8_t fast_ram_bank_limit = uint8_t(ram.size() / 0x01'0000); - for(uint8_t bank = 0x02; bank < fast_ram_bank_limit; bank++) { - set_region(bank, 0x0000, 0xffff, fast_region); - } - - // [Banks $80–$e0: empty]. - - // Banks $e0, $e1: all locations potentially affected by the language switches or marked for IO. - // Alas, separate regions are needed due to the same ROM appearing on both pages. - for(uint8_t c = 0; c < 2; c++) { - set_regions(0xe0 + c, {0xc000, 0xc100, 0xc300, 0xc400, 0xc800, 0xd000, 0xe000, 0xffff}); - } - - // [Banks $e2–[ROM start]: empty]. - - // ROM banks: directly mapped to ROM. - const uint8_t rom_bank_count = uint8_t(rom.size() >> 16); - const uint8_t first_rom_bank = uint8_t(0x100 - rom_bank_count); - const uint8_t rom_region = region(); - for(uint8_t c = 0; c < rom_bank_count; ++c) { - set_region(first_rom_bank + c, 0x0000, 0xffff, rom_region); - } - - // Apply proper storage to those banks. - auto set_storage = [this](uint32_t address, const uint8_t *read, uint8_t *write) { - // Don't allow the reserved null region to be modified. - assert(region_map[address >> 8]); - - // Either set or apply a quick bit of testing as to the logic at play. - auto ®ion = regions[region_map[address >> 8]]; - if(read) read -= address; - if(write) write -= address; - if(!region.read) { - region.read = read; - region.write = write; - } else { - assert(region.read == read); - assert(region.write == write); - } - }; - - // This is highly redundant, but decouples this step from the above. - for(size_t c = 0; c < 0x80'0000; c += 0x100) { - if(c < ram.size() - 0x02'0000) { - set_storage(uint32_t(c), &ram[c], &ram[c]); - } - } - uint8_t *const slow_ram = &ram[ram.size() - 0x02'0000] - 0xe0'0000; - for(size_t c = 0xe0'0000; c < 0xe2'0000; c += 0x100) { - set_storage(uint32_t(c), &slow_ram[c], &slow_ram[c]); - } - for(uint32_t c = 0; c < uint32_t(rom_bank_count); c++) { - set_storage((first_rom_bank + c) << 16, &rom[c << 16], nullptr); - } - - // Set shadowing as working from banks 0 and 1 (forever). - shadow_banks[0] = true; - - // TODO: set 1Mhz flags. - - // Apply initial language/auxiliary state. - set_paging<~0>(); - } + /// Sets the ROM and RAM storage underlying this MemoryMap. + void set_storage(std::vector &ram, std::vector &rom); // MARK: - Live bus access notifications and register access. - void set_shadow_register(uint8_t value) { - const uint8_t diff = value ^ shadow_register_; - shadow_register_ = value; + void set_shadow_register(uint8_t value); + uint8_t get_shadow_register() const; - if(diff & 0x40) { // IO/language-card inhibit. - set_paging(); - } + void set_speed_register(uint8_t value); - if(diff & 0x3f) { - set_shadowing(); - } - } + void set_state_register(uint8_t value); + uint8_t get_state_register() const; - uint8_t get_shadow_register() const { - return shadow_register_; - } - - void set_speed_register(uint8_t value) { - speed_register_ = value; - - // Enable or disable shadowing from banks 0x02–0x80. - for(size_t c = 0x01; c < 0x40; c++) { - shadow_banks[c] = speed_register_ & 0x10; - } - } - - void set_state_register(uint8_t value) { - auxiliary_switches_.set_state(value); - language_card_.set_state(value); - } - - uint8_t get_state_register() const { - return language_card_.get_state() | auxiliary_switches_.get_state(); - } - - void access(uint16_t address, bool is_read) { - auxiliary_switches_.access(address, is_read); - if((address & 0xfff0) == 0xc080) language_card_.access(address, is_read); - } + void access(uint16_t address, bool is_read); using AuxiliaryMemorySwitches = Apple::II::AuxiliaryMemorySwitches; const AuxiliaryMemorySwitches &auxiliary_switches() const { @@ -238,377 +52,7 @@ class MemoryMap { return language_card_; } - private: - AuxiliaryMemorySwitches auxiliary_switches_; - LanguageCardSwitches language_card_; - friend AuxiliaryMemorySwitches; - friend LanguageCardSwitches; - - uint8_t shadow_register_ = 0x00; - uint8_t speed_register_ = 0x00; - - // MARK: - Memory banking. - void assert_is_region(uint8_t start, uint8_t end) { - assert(region_map[start] == region_map[start-1]+1); - assert(region_map[end-1] == region_map[start]); - assert(region_map[end] == region_map[end-1]+1); - } - - template void set_paging() { - // Establish whether main or auxiliary RAM - // is exposed in bank $00 for a bunch of regions. - if constexpr (type & PagingType::Main) { - const auto set = [&](std::size_t page, const auto &flags) { - auto ®ion = regions[region_map[page]]; - region.read = flags.read ? &ram_base[0x01'0000] : ram_base; - region.write = flags.write ? &ram_base[0x01'0000] : ram_base; - }; - const auto state = auxiliary_switches_.main_state(); - - // Base: $0200–$03FF. - set(0x02, state.base); - assert_is_region(0x02, 0x04); - - // Region $0400–$07ff. - set(0x04, state.region_04_08); - assert_is_region(0x04, 0x08); - - // Base: $0800–$1FFF. - set(0x08, state.base); - assert_is_region(0x08, 0x20); - - // Region $2000–$3FFF. - set(0x20, state.region_20_40); - assert_is_region(0x20, 0x40); - - // Base: $4000–$BFFF. - set(0x40, state.base); - assert_is_region(0x40, 0xc0); - } - - // Update whether base or auxiliary RAM is visible in: (i) the zero - // and stack pages; and (ii) anywhere that the language card is exposing RAM instead of ROM. - if constexpr (bool(type & PagingType::ZeroPage)) { - // Affects bank $00 only, and should be a single region. - auto ®ion = regions[region_map[0]]; - region.read = region.write = auxiliary_switches_.zero_state() ? &ram_base[0x01'0000] : ram_base; - assert(region_map[0x0000] == region_map[0x0001]); - assert(region_map[0x0001]+1 == region_map[0x0002]); - } - - // Establish whether ROM or card switches are exposed in the distinct - // regions C100–C2FF, C300–C3FF, C400–C7FF and C800–CFFF. - // - // On the IIgs it intersects with the current shadow register. - if constexpr (bool(type & (PagingType::CardArea | PagingType::Main))) { - const bool inhibit_banks0001 = shadow_register_ & 0x40; - const auto state = auxiliary_switches_.card_state(); - - auto apply = [&state, this](uint32_t bank_base) { - auto &c0_region = regions[region_map[bank_base | 0xc0]]; - auto &c1_region = regions[region_map[bank_base | 0xc1]]; - auto &c3_region = regions[region_map[bank_base | 0xc3]]; - auto &c4_region = regions[region_map[bank_base | 0xc4]]; - auto &c8_region = regions[region_map[bank_base | 0xc8]]; - - const uint8_t *const rom = ®ions[region_map[0xffd0]].read[0xffc100] - ((bank_base << 8) + 0xc100); - - // This is applied dynamically as it may be added or lost in banks $00 and $01. - c0_region.flags |= Region::IsIO; - - const auto apply_region = [&](bool flag, auto ®ion) { - region.write = nullptr; - if(flag) { - region.read = rom; - region.flags &= ~Region::IsIO; - } else { - region.flags |= Region::IsIO; - } - }; - - apply_region(state.region_C1_C3, c1_region); - apply_region(state.region_C3, c3_region); - apply_region(state.region_C4_C8, c4_region); - apply_region(state.region_C8_D0, c8_region); - - // Sanity checks. - assert(region_map[bank_base | 0xc1] == region_map[bank_base | 0xc0]+1); - assert(region_map[bank_base | 0xc2] == region_map[bank_base | 0xc1]); - assert(region_map[bank_base | 0xc3] == region_map[bank_base | 0xc2]+1); - assert(region_map[bank_base | 0xc4] == region_map[bank_base | 0xc3]+1); - assert(region_map[bank_base | 0xc7] == region_map[bank_base | 0xc4]); - assert(region_map[bank_base | 0xc8] == region_map[bank_base | 0xc7]+1); - assert(region_map[bank_base | 0xcf] == region_map[bank_base | 0xc8]); - assert(region_map[bank_base | 0xd0] == region_map[bank_base | 0xcf]+1); - }; - - if(inhibit_banks0001) { - // Set no IO in the Cx00 range for banks $00 and $01, just - // regular RAM (or possibly auxiliary). - const auto auxiliary_state = auxiliary_switches_.main_state(); - for(uint8_t region = region_map[0x00c0]; region < region_map[0x00d0]; region++) { - regions[region].read = auxiliary_state.base.read ? &ram_base[0x01'0000] : ram_base; - regions[region].write = auxiliary_state.base.write ? &ram_base[0x01'0000] : ram_base; - regions[region].flags &= ~Region::IsIO; - } - for(uint8_t region = region_map[0x01c0]; region < region_map[0x01d0]; region++) { - regions[region].read = regions[region].write = ram_base; - regions[region].flags &= ~Region::IsIO; - } - } else { - // Obey the card state for banks $00 and $01. - apply(0x0000); - apply(0x0100); - } - - // Obey the card state for banks $e0 and $e1. - apply(0xe000); - apply(0xe100); - } - - // Update the region from - // $D000 onwards as per the state of the language card flags — there may - // end up being ROM or RAM (or auxiliary RAM), and the first 4kb of it - // may be drawn from either of two pools. - if constexpr (bool(type & (PagingType::LanguageCard | PagingType::ZeroPage | PagingType::Main))) { - const auto language_state = language_card_.state(); - const auto zero_state = auxiliary_switches_.zero_state(); - const auto main = auxiliary_switches_.main_state(); - const bool inhibit_banks0001 = shadow_register_ & 0x40; - - auto apply = [&language_state, this](uint32_t bank_base, uint8_t *ram) { - // This assumes bank 1 is the one before bank 2 when RAM is linear. - uint8_t *const d0_ram_bank = ram - (language_state.bank2 ? 0x0000 : 0x1000); - - // Crib the ROM pointer from a page it's always visible on. - const uint8_t *const rom = ®ions[region_map[0xffd0]].read[0xff'd000] - ((bank_base << 8) + 0xd000); - - auto &d0_region = regions[region_map[bank_base | 0xd0]]; - d0_region.read = language_state.read ? d0_ram_bank : rom; - d0_region.write = language_state.write ? nullptr : d0_ram_bank; - - auto &e0_region = regions[region_map[bank_base | 0xe0]]; - e0_region.read = language_state.read ? ram : rom; - e0_region.write = language_state.write ? nullptr : ram; - - // Assert assumptions made above re: memory layout. - assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]); - assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]); - }; - auto set_no_card = [this](uint32_t bank_base, uint8_t *read, uint8_t *write) { - auto &d0_region = regions[region_map[bank_base | 0xd0]]; - d0_region.read = read; - d0_region.write = write; - - auto &e0_region = regions[region_map[bank_base | 0xe0]]; - e0_region.read = read; - e0_region.write = write; - - // Assert assumptions made above re: memory layout. - assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]); - assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]); - }; - - if(inhibit_banks0001) { - set_no_card(0x0000, - main.base.read ? &ram_base[0x01'0000] : ram_base, - main.base.write ? &ram_base[0x01'0000] : ram_base); - set_no_card(0x0100, ram_base, ram_base); - } else { - apply(0x0000, zero_state ? &ram_base[0x01'0000] : ram_base); - apply(0x0100, ram_base); - } - - // The pointer stored in region_map[0xe000] has already been adjusted for - // the 0xe0'0000 addressing offset. - uint8_t *const e0_ram = regions[region_map[0xe000]].write; - apply(0xe000, e0_ram); - apply(0xe100, e0_ram); - } - } - - // IIgs specific: sets or resets the ::IsShadowed flag across affected banks as - // per the current state of the shadow register. - // - // Completely distinct from the auxiliary and language card switches. - void set_shadowing() { - // Relevant bits: - // - // b5: inhibit shadowing, text page 2 [if ROM 03; as if always set otherwise] - // b4: inhibit shadowing, auxiliary high-res graphics - // b3: inhibit shadowing, super high-res graphics - // b2: inhibit shadowing, high-res graphics page 2 - // b1: inhibit shadowing, high-res graphics page 1 - // b0: inhibit shadowing, text page 1 - // - // The interpretations of how the overlapping high-res and super high-res inhibit - // bits apply used below is taken from The Apple IIgs Technical Reference, P. 178. - - // Of course, zones are: - // - // $0400–$0800 Text Page 1 - // $0800–$0C00 Text Page 2 [ROM 03 machines] - // $2000–$4000 High-res Page 1, and Super High-res in odd banks - // $4000–$6000 High-res Page 2, and Huper High-res in odd banks - // $6000–$a000 Odd banks only, rest of Super High-res - // [plus IO and language card space, subject to your definition of shadowing] - - enum Inhibit { - TextPage1 = 0x01, - HighRes1 = 0x02, - HighRes2 = 0x04, - SuperHighRes = 0x08, - AuxiliaryHighRes = 0x10, - TextPage2 = 0x20, - }; - - // Clear all shadowing. - shadow_pages.reset(); - - // Text Page 1, main and auxiliary — $0400–$0800. - { - const bool should_shadow_text1 = !(shadow_register_ & Inhibit::TextPage1); - if(should_shadow_text1) { - shadow_pages |= shadow_text1; - } - } - - // Text Page 2, main and auxiliary — 0x0800–0x0c00. - // - // The mask applied will be all 0 for a pre-ROM03 machine. - { - const bool should_shadow_text2 = !(shadow_register_ & Inhibit::TextPage2); - if(should_shadow_text2) { - shadow_pages |= shadow_text2; - } - } - - // Hi-res graphics Page 1, main and auxiliary — $2000–$4000; - // also part of the super high-res graphics page on odd pages. - // - // Even test applied: - // high-res graphics page 1 inhibit bit alone is definitive. - // - // Odd test: - // (high-res graphics inhibit or auxiliary high res graphics inhibit) _and_ - // (super high-res inhibit). - // - { - const bool should_shadow_highres1 = !(shadow_register_ & Inhibit::HighRes1); - if(should_shadow_highres1) { - shadow_pages |= shadow_highres1; - } - - const bool should_shadow_aux_highres1 = !( - shadow_register_ & (Inhibit::HighRes1 | Inhibit::AuxiliaryHighRes) && - shadow_register_ & Inhibit::SuperHighRes - ); - if(should_shadow_aux_highres1) { - shadow_pages |= shadow_highres1_aux; - } - } - - // Hi-res graphics Page 2, main and auxiliary — $4000–$6000; - // also part of the super high-res graphics page. - // - // Test applied: much like that for page 1. - { - const bool should_shadow_highres2 = !(shadow_register_ & Inhibit::HighRes2); - if(should_shadow_highres2) { - shadow_pages |= shadow_highres2; - } - - const bool should_shadow_aux_highres2 = !( - shadow_register_ & (Inhibit::HighRes2 | Inhibit::AuxiliaryHighRes) && - shadow_register_ & Inhibit::SuperHighRes - ); - if(should_shadow_aux_highres2) { - shadow_pages |= shadow_highres2_aux; - } - } - - // Residue of Super Hi-Res — $6000–$a000 (odd pages only). - // - // Test applied: - // auxiliary high res graphics inhibit and super high-res inhibit - { - const bool should_shadow_superhighres = !( - shadow_register_ & Inhibit::SuperHighRes && - shadow_register_ & Inhibit::AuxiliaryHighRes - ); - if(should_shadow_superhighres) { - shadow_pages |= shadow_superhighres; - } - } - } - - void print_state() { - uint8_t region = region_map[0]; - uint32_t start = 0; - for(uint32_t top = 0; top < 65536; top++) { - if(region_map[top] == region) continue; - - printf("%06x -> %06x\t", start, top << 8); - printf("%c%c\n", - (regions[region_map[top] - 1].flags & Region::Is1Mhz) ? '1' : '-', - (regions[region_map[top] - 1].flags & Region::IsIO) ? 'x' : '-' - ); - - start = top << 8; - region = region_map[top]; - } - } - - private: - // Various precomputed bitsets describing key regions; std::bitset doesn't support constexpr instantiation - // beyond the first 64 bits at the time of writing, alas, so these are generated at runtime. - std::bitset<128> shadow_text1; - std::bitset<128> shadow_text2; - std::bitset<128> shadow_highres1, shadow_highres1_aux; - std::bitset<128> shadow_highres2, shadow_highres2_aux; - std::bitset<128> shadow_superhighres; - - void setup_shadow_maps(bool is_rom03) { - static constexpr int shadow_shift = 10; - static constexpr int auxiliary_offset = 0x1'0000 >> shadow_shift; - - for(size_t c = 0x0400 >> shadow_shift; c < 0x0800 >> shadow_shift; c++) { - shadow_text1[c] = shadow_text1[c+auxiliary_offset] = true; - } - - // Shadowing of text page 2 was added only with the ROM03 machine. - if(is_rom03) { - for(size_t c = 0x0800 >> shadow_shift; c < 0x0c00 >> shadow_shift; c++) { - shadow_text2[c] = shadow_text2[c+auxiliary_offset] = true; - } - } - - for(size_t c = 0x2000 >> shadow_shift; c < 0x4000 >> shadow_shift; c++) { - shadow_highres1[c] = true; - shadow_highres1_aux[c+auxiliary_offset] = true; - } - - for(size_t c = 0x4000 >> shadow_shift; c < 0x6000 >> shadow_shift; c++) { - shadow_highres2[c] = true; - shadow_highres2_aux[c+auxiliary_offset] = true; - } - - for(size_t c = 0x6000 >> shadow_shift; c < 0xa000 >> shadow_shift; c++) { - shadow_superhighres[c+auxiliary_offset] = true; - } - } - - public: - // Memory layout here is done via double indirection; the main loop should: - // (i) use the top two bytes of the address to get an index from memory_map_; and - // (ii) use that to index the memory_regions table. - // - // Pointers are eight bytes at the time of writing, so the extra level of indirection - // reduces what would otherwise be a 1.25mb table down to not a great deal more than 64kb. - std::array region_map{}; - uint8_t *ram_base = nullptr; - uint8_t *shadow_base[2] = {nullptr, nullptr}; - static constexpr int shadow_mask[2] = {0xff'ffff, 0x01'ffff}; + // MARK: - Accessors for reading and writing RAM. struct Region { uint8_t *write = nullptr; @@ -621,51 +65,99 @@ class MemoryMap { }; }; - // Shadow_pages: divides the final 128kb of memory into 1kb chunks and includes a flag to indicate whether - // each is a potential destination for shadowing. - // - // Shadow_banks: divides the whole 16mb of memory into 128kb chunks and includes a flag to indicate whether - // each is a potential source of shadowing. - std::bitset<128> shadow_pages{}, shadow_banks{}; - - std::array regions; // An assert above ensures that this is large enough; there's no - // doctrinal reason for it to be whatever size it is now, just - // adjust as required. - - // The below encapsulates an assumption that Apple intends to shadow physical addresses (i.e. after mapping). - // I couldn't really find clear documentation on this. - - const Region ®ion(uint32_t address) { return regions[region_map[address >> 8]]; } - uint8_t read(const Region ®ion, uint32_t address) { + const Region ®ion(uint32_t address) const { return regions_[region_map_[address >> 8]]; } + uint8_t read(const Region ®ion, uint32_t address) const { return region.read ? region.read[address] : 0xff; } bool is_shadowed(const Region ®ion, uint32_t address) const { - return shadow_pages[((®ion.write[address] - ram_base) >> 10) & 127] & shadow_banks[address >> 17]; + // ROM is never shadowed. + if(!region.write) { + return false; + } - // Quick notes on contortions above: - // - // The objective is to support shadowing: - // 1. without storing a whole extra pointer, and such that the shadowing flags - // are orthogonal to the current auxiliary memory settings; - // 2. in such a way as to support shadowing both in banks $00/$01 and elsewhere; and - // 3. to do so without introducing too much in the way of branching. - // - // Hence the implemented solution: if shadowing is enabled then use the distance from the start of - // physical RAM modulo 128k indexed into the bank $e0/$e1 RAM. - // - // With a further twist: the modulo and pointer are indexed on ::IsShadowed to eliminate a branch - // even on that. + const auto physical = physical_address(region, address); + assert(physical >= 0 && physical <= 0xff'ffff); + return shadow_pages_[(physical >> 10) & 127] & shadow_banks_[physical >> 17]; } void write(const Region ®ion, uint32_t address, uint8_t value) { if(!region.write) { return; } + // Write once. region.write[address] = value; + + // Write again, either to the same place (if unshadowed) or to the shadow destination. + static constexpr std::size_t shadow_mask[2] = {0xff'ffff, 0x01'ffff}; const bool shadowed = is_shadowed(region, address); - shadow_base[shadowed][(®ion.write[address] - ram_base) & shadow_mask[shadowed]] = value; + shadow_base_[shadowed][physical_address(region, address) & shadow_mask[shadowed]] = value; } + + // The objective is to support shadowing: + // 1. without storing a whole extra pointer, and such that the shadowing flags + // are orthogonal to the current auxiliary memory settings; + // 2. in such a way as to support shadowing both in banks $00/$01 and elsewhere; and + // 3. to do so without introducing too much in the way of branching. + // + // Hence the implemented solution: if shadowing is enabled then use the distance from the start of + // physical RAM modulo 128k indexed into the bank $e0/$e1 RAM. + // + // With a further twist: the modulo and pointer are indexed on ::IsShadowed to eliminate a branch + // even on that. + + private: + AuxiliaryMemorySwitches auxiliary_switches_; + LanguageCardSwitches language_card_; + friend AuxiliaryMemorySwitches; + friend LanguageCardSwitches; + + uint8_t shadow_register_ = 0x00; + uint8_t speed_register_ = 0x00; + + // MARK: - Banking. + + void assert_is_region(uint8_t start, uint8_t end); + template void set_paging(); + + uint8_t *ram_base_ = nullptr; + + // Memory layout here is done via double indirection; the main loop should: + // (i) use the top two bytes of the address to get an index from region_map; and + // (ii) use that to index the memory_regions table. + // + // Pointers are eight bytes at the time of writing, so the extra level of indirection + // reduces what would otherwise be a 1.25mb table down to not a great deal more than 64kb. + std::array region_map_{}; + std::array regions_; // An assert above ensures that this is large enough; there's no + // doctrinal reason for it to be whatever size it is now, just + // adjust as required. + + std::size_t physical_address(const Region ®ion, uint32_t address) const { + return std::size_t(®ion.write[address] - ram_base_); + } + + // MARK: - Shadowing + + // Various precomputed bitsets describing key regions; std::bitset doesn't support constexpr instantiation + // beyond the first 64 bits at the time of writing, alas, so these are generated at runtime. + std::bitset<128> shadow_text1_; + std::bitset<128> shadow_text2_; + std::bitset<128> shadow_highres1_, shadow_highres1_aux_; + std::bitset<128> shadow_highres2_, shadow_highres2_aux_; + std::bitset<128> shadow_superhighres_; + void setup_shadow_maps(bool is_rom03); + void set_shadowing(); + + uint8_t *shadow_base_[2] = {nullptr, nullptr}; + + // Divide the final 128kb of memory into 1kb chunks and flag to indicate whether + // each is a potential destination for shadowing. + std::bitset<128> shadow_pages_{}; + + // Divide the whole 16mb of memory into 128kb chunks and flag to indicate whether + // each is a potential source of shadowing. + std::bitset<128> shadow_banks_{}; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 0825ca784..a3042258f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -39,6 +39,9 @@ 42E5C3932AC46A7700DA093D /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 42E5C3922AC46A7700DA093D /* Carbon.framework */; }; 42EB81282B23AAC300429AF4 /* IMD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42EB81262B23AAC300429AF4 /* IMD.cpp */; }; 42EB81292B23AAC300429AF4 /* IMD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42EB81262B23AAC300429AF4 /* IMD.cpp */; }; + 42EB812D2B4700B700429AF4 /* MemoryMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42EB812C2B47008D00429AF4 /* MemoryMap.cpp */; }; + 42EB812E2B4700B700429AF4 /* MemoryMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42EB812C2B47008D00429AF4 /* MemoryMap.cpp */; }; + 42EB812F2B4700B800429AF4 /* MemoryMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42EB812C2B47008D00429AF4 /* MemoryMap.cpp */; }; 4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; }; 4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; }; 4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; @@ -1198,6 +1201,7 @@ 42EB81252B21788200429AF4 /* RTC.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RTC.hpp; sourceTree = ""; }; 42EB81262B23AAC300429AF4 /* IMD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IMD.cpp; sourceTree = ""; }; 42EB81272B23AAC300429AF4 /* IMD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IMD.hpp; sourceTree = ""; }; + 42EB812C2B47008D00429AF4 /* MemoryMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryMap.cpp; sourceTree = ""; }; 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = ""; }; 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = ""; }; 4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = ""; }; @@ -5035,6 +5039,7 @@ 4B8DF4D62546561300F3433C /* MemoryMap.hpp */, 4B1EC715255398B000A1F44B /* Sound.hpp */, 4B8DF4F8254E36AD00F3433C /* Video.hpp */, + 42EB812C2B47008D00429AF4 /* MemoryMap.cpp */, ); path = AppleIIgs; sourceTree = ""; @@ -5853,6 +5858,7 @@ 423820452B1A90BE00964EFE /* PCBooter.cpp in Sources */, 4B8318B922D3E56D006DB630 /* MemoryPacker.cpp in Sources */, 4B055ABA1FAE86170060FFFF /* Commodore.cpp in Sources */, + 42EB812E2B4700B700429AF4 /* MemoryMap.cpp in Sources */, 4B0ACC2F23775819008902D0 /* TIA.cpp in Sources */, 4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */, 4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */, @@ -6087,6 +6093,7 @@ 4B7C681A275196E8001671EC /* MouseJoystick.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */, + 42EB812D2B4700B700429AF4 /* MemoryMap.cpp in Sources */, 4B9EC0EA26B384080060A31F /* Keyboard.cpp in Sources */, 4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */, 4B69DEB62AB79E4F0055B217 /* Instruction.cpp in Sources */, @@ -6242,6 +6249,7 @@ buildActionMask = 2147483647; files = ( 4B778EF623A5EB600000D260 /* WOZ.cpp in Sources */, + 42EB812F2B4700B800429AF4 /* MemoryMap.cpp in Sources */, 4B778F1423A5EC960000D260 /* Z80Storage.cpp in Sources */, 4B778F1F23A5EDC70000D260 /* Audio.cpp in Sources */, 4B778F1523A5EC980000D260 /* PartialMachineCycle.cpp in Sources */,