1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-23 05:29:23 +00:00

Merge pull request #1303 from TomHarte/IIgsCleanliness

Clean up what there currently is of a IIgs.
This commit is contained in:
Thomas Harte 2024-01-04 12:36:40 -05:00 committed by GitHub
commit 0cfd29fafc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 635 additions and 791 deletions

View File

@ -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 &region = 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<unsigned long long>(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<unsigned long long>(total));
// } else {
// printf("Correct at %llu\n", static_cast<unsigned long long>(total));
// }
// }
if(operation == CPU::WDC65816::BusOperation::ReadOpcode) {
// if(total > 482342960 && total < 482352960 && address == 0xe10000) {
// printf("entry: %llu\n", static_cast<unsigned long long>(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<unsigned long long>(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<int>()) % 50;
slow_access_phase_ = (slow_access_phase_ + duration.as<int>()) % 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);

View File

@ -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<uint8_t> &ram, std::vector<uint8_t> &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<uint16_t> addresses, std::vector<uint8_t> 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
// $E000end 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 &region = 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<PagingType::LanguageCard | PagingType::CardArea>();
}
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 0x020x80.
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 <int type> 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 &region = 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 &region = 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 C100C2FF, C300C3FF, C400C7FF and C800CFFF.
//
// 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 = &regions_[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 &region) {
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 = &regions_[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 — 0x08000x0c00.
//
// 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;
}
}

View File

@ -11,6 +11,7 @@
#include <array>
#include <bitset>
#include <cassert>
#include <vector>
#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<uint8_t> &ram, std::vector<uint8_t> &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<uint16_t> addresses, std::vector<uint8_t> 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
// $E000end 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 &region = 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<uint8_t> &ram, std::vector<uint8_t> &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<PagingType::LanguageCard | PagingType::CardArea>();
}
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 0x020x80.
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<MemoryMap>;
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 <int type> 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 &region = 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 &region = 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 C100C2FF, C300C3FF, C400C7FF and C800CFFF.
//
// 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 = &regions[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 &region) {
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 = &regions[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 — 0x08000x0c00.
//
// 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<uint8_t, 65536> 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<Region, 40> 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 &region(uint32_t address) { return regions[region_map[address >> 8]]; }
uint8_t read(const Region &region, uint32_t address) {
const Region &region(uint32_t address) const { return regions_[region_map_[address >> 8]]; }
uint8_t read(const Region &region, uint32_t address) const {
return region.read ? region.read[address] : 0xff;
}
bool is_shadowed(const Region &region, uint32_t address) const {
return shadow_pages[((&region.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 &region, 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][(&region.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 <int type> 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<uint8_t, 65536> region_map_{};
std::array<Region, 40> 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 &region, uint32_t address) const {
return std::size_t(&region.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_{};
};
}

View File

@ -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 = "<group>"; };
42EB81262B23AAC300429AF4 /* IMD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IMD.cpp; sourceTree = "<group>"; };
42EB81272B23AAC300429AF4 /* IMD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IMD.hpp; sourceTree = "<group>"; };
42EB812C2B47008D00429AF4 /* MemoryMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryMap.cpp; sourceTree = "<group>"; };
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 = "<group>"; };
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; };
4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; };
@ -5035,6 +5039,7 @@
4B8DF4D62546561300F3433C /* MemoryMap.hpp */,
4B1EC715255398B000A1F44B /* Sound.hpp */,
4B8DF4F8254E36AD00F3433C /* Video.hpp */,
42EB812C2B47008D00429AF4 /* MemoryMap.cpp */,
);
path = AppleIIgs;
sourceTree = "<group>";
@ -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 */,