diff --git a/Machines/Apple/AppleIIgs/AppleIIgs.cpp b/Machines/Apple/AppleIIgs/AppleIIgs.cpp index eb0d683ae..81022f893 100644 --- a/Machines/Apple/AppleIIgs/AppleIIgs.cpp +++ b/Machines/Apple/AppleIIgs/AppleIIgs.cpp @@ -16,6 +16,9 @@ #include "../AppleII/LanguageCardSwitches.hpp" #include "../AppleII/AuxiliaryMemorySwitches.hpp" +#include +#include + namespace Apple { namespace IIgs { @@ -69,33 +72,159 @@ class ConcreteMachine: } ram_.resize(ram_size * 1024); - // Establish bank storage. + // Establish bank mapping. + uint8_t next_region = 0; + auto region = [&next_region, this]() -> uint8_t { + assert(next_region != memory_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)); - // Fast RAM storage. - for(size_t c = 0; c < 0x80; ++c) { - if(c * 64 < (ram_size - 128)) { - bank_storage_[c].read = bank_storage_[c].write = &ram_[c * 0x10000]; + // Fill in memory map. + size_t target = size_t((bank << 8) | (start >> 8)); + for(int c = start; c < end; c += 0x100) { + memory_map_[target] = region; + ++target; + } + }; + + // Current beliefs about the IIgs memory map: + // + // * language card banking applies to banks $00, $01, $e0 and $e1; + // * auxiliary memory switches apply to banks $00 only; + // * shadowing may be enabled only on banks $00 and $01, or on all RAM pages. + // + // So banks $00 and $01 need their own divided spaces at the shadowing resolution, + // all the other fast RAM banks can share a set of divided spaces, $e0 and $e1 need + // to be able to deal with language card-level division but no further, and the pure + // ROM pages don't need to be subdivided at all. + + // Reserve region 0 as that for unmapped memory. + region(); + + // Bank $00: all locations potentially affected by the auxiliary switches or the + // language switches. Which will naturally align with shadowable zones. + set_region(0x00, 0x0000, 0x0200, region()); + set_region(0x00, 0x0200, 0x0400, region()); + set_region(0x00, 0x0400, 0x0800, region()); + set_region(0x00, 0x0800, 0x2000, region()); + set_region(0x00, 0x2000, 0x4000, region()); + set_region(0x00, 0x4000, 0xc000, region()); + set_region(0x00, 0xc000, 0xc100, region()); + set_region(0x00, 0xc100, 0xc300, region()); + set_region(0x00, 0xc300, 0xc400, region()); + set_region(0x00, 0xc400, 0xc800, region()); + set_region(0x00, 0xc800, 0xd000, region()); + set_region(0x00, 0xd000, 0xe000, region()); + set_region(0x00, 0xe000, 0xffff, region()); + + // Bank $01: all locations potentially affected by the language switches, by shadowing, + // or marked for IO. + set_region(0x01, 0x0000, 0x0400, region()); + set_region(0x01, 0x0400, 0x0800, region()); + set_region(0x01, 0x0800, 0x0c00, region()); + set_region(0x01, 0x0c00, 0x2000, region()); + set_region(0x01, 0x2000, 0x4000, region()); + set_region(0x01, 0x4000, 0x6000, region()); + set_region(0x01, 0x6000, 0xa000, region()); + set_region(0x01, 0xa000, 0xc000, region()); + set_region(0x01, 0xc000, 0xd000, region()); + set_region(0x01, 0xd000, 0xe000, region()); + set_region(0x01, 0xe000, 0xffff, region()); + + // Banks $02–[end of RAM]: all locations potentially affected by shadowing. + const uint8_t fast_ram_bank_count = uint8_t((ram_size - 128)/64); + if(fast_ram_bank_count > 2) { + const uint8_t evens[] = { + region(), // 0x0000 – 0x0400. + region(), // 0x0400 – 0x0800. + region(), // 0x0800 – 0x0c00. + region(), // 0x0c00 – 0x2000. + region(), // 0x2000 – 0x4000. + region(), // 0x4000 – 0x6000. + region(), // 0x6000 – [end]. + }; + const uint8_t odds[] = { + region(), // 0x0000 – 0x0400. + region(), // 0x0400 – 0x0800. + region(), // 0x0800 – 0x0c00. + region(), // 0x0c00 – 0x2000. + region(), // 0x2000 – 0x4000. + region(), // 0x4000 – 0x6000. + region(), // 0x6000 – 0xa000. + region(), // 0xa000 – [end]. + }; + for(uint8_t bank = 0x02; bank < fast_ram_bank_count; bank += 2) { + set_region(bank, 0x0000, 0x0400, evens[0]); + set_region(bank, 0x0400, 0x0800, evens[1]); + set_region(bank, 0x0800, 0x0c00, evens[2]); + set_region(bank, 0x0c00, 0x2000, evens[3]); + set_region(bank, 0x2000, 0x4000, evens[4]); + set_region(bank, 0x4000, 0x6000, evens[5]); + set_region(bank, 0x6000, 0xffff, evens[6]); + + set_region(bank+1, 0x0000, 0x0400, odds[0]); + set_region(bank+1, 0x0400, 0x0800, odds[1]); + set_region(bank+1, 0x0800, 0x0c00, odds[2]); + set_region(bank+1, 0x0c00, 0x2000, odds[3]); + set_region(bank+1, 0x2000, 0x4000, odds[4]); + set_region(bank+1, 0x4000, 0x6000, odds[5]); + set_region(bank+1, 0x6000, 0xa000, odds[6]); + set_region(bank+1, 0xa000, 0xffff, odds[7]); } } - // Mega II RAM storage. - bank_storage_[0xe0].read = bank_storage_[0xe0].write = &ram_[ram_.size() - 0x20000]; - bank_storage_[0xe1].read = bank_storage_[0xe1].write = &ram_[ram_.size() - 0x10000]; + // [Banks $80–$e0: empty]. - // ROM storage. - const size_t rom_page_count = rom_.size() >> 16; - const size_t first_rom_page = 0x100 - rom_page_count; - for(size_t c = 0; c < rom_page_count; ++c) { - bank_storage_[first_rom_page + c].read = &rom_[c * 0x10000]; + // Banks $e0, $e1: all locations potentially affected by the language switches or marked for IO. + for(uint8_t c = 0; c < 2; c++) { + set_region(0xe0 + c, 0x0000, 0xc000, region()); + set_region(0xe0 + c, 0xc000, 0xd000, region()); + set_region(0xe0 + c, 0xd000, 0xffff, region()); } - // Establish initial bank mapping. - for(size_t c = 0; c < 65536; ++c) { - bank_mapping_[c].destination = uint8_t(c >> 8); + // [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, 0xff00, rom_region); } - for(size_t c = 0; c < 256; ++c) { - bank_mapping_[0xe000 + c].flags = BankMapping::Is1Mhz; - bank_mapping_[0xe100 + c].flags = BankMapping::Is1Mhz; + + // 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(memory_map_[address >> 8]); + + // Either set or apply a quick bit of testing as to the logic at play. + auto ®ion = memory_regions_[memory_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 < 0x800000; c += 0x100) { + if(c < (ram_size - 128)*1024) { + set_storage(uint32_t(c), &ram_[c], &ram_[c]); + } + } + uint8_t *const slow_ram = &ram_[ram_.size() - 0x20000]; + for(size_t c = 0xe00000; c < 0xe20000; c += 0x100) { + set_storage(uint32_t(c), &slow_ram[c - 0xe00000], &slow_ram[c - 0xe00000]); + } + for(uint32_t c = 0; c < uint32_t(rom_bank_count); ++c) { + set_storage((first_rom_bank + c) << 16, &rom_[c << 16], &rom_[c << 16]); } // Apply initial language/auxiliary state. [TODO: including shadowing register]. @@ -116,37 +245,38 @@ class ConcreteMachine: } forceinline Cycles perform_bus_operation(const CPU::WDC65816::BusOperation operation, const uint32_t address, uint8_t *const value) { - const BankMapping &mapping = bank_mapping_[address >> 8]; + const MemoryRegion ®ion = memory_regions_[memory_map_[address >> 8]]; - if(mapping.flags & BankMapping::IsIO) { + if(region.flags & MemoryRegion::IsIO) { // TODO: all IO accesses. } else { - const BankStorage &storage = bank_storage_[mapping.destination]; - // TODO: branching below is predicated on the idea that an extra 64kb of scratch write area // and 64kb of 0xffs would be worse than branching due to the data set increase. Verify that? if(isReadOperation(operation)) { - *value = storage.read ? storage.read[address & 0xffff] : 0xff; + *value = region.read ? region.read[address] : 0xff; } else { - if(storage.write) { - storage.write[address & 0xffff] = *value; - if(mapping.flags & BankMapping::IsShadowed) { - bank_storage_[mapping.destination + 0xe0].write[address & 0xffff] = *value; + if(region.write) { + region.write[address] = *value; + + // Apply shadowing. + if(region.flags & (MemoryRegion::IsShadowedE0|MemoryRegion::IsShadowedE1)) { + const uint32_t shadowed_address = (address & 0xffff) + (uint32_t(0xe1 - (region.flags&MemoryRegion::IsShadowedE0)) << 16); + memory_regions_[memory_map_[shadowed_address >> 8]].write[shadowed_address] = *value; } } } } - Cycles duration; + Cycles duration = Cycles(5); - // Determine the cost of this access. - if((mapping.flags & BankMapping::Is1Mhz) || ((mapping.flags & BankMapping::IsShadowed) && !isReadOperation(operation))) { - // TODO: (i) get into phase; (ii) allow for the 1Mhz bus length being sporadically 16 rather than 14. - duration = Cycles(14); - } else { - // TODO: (i) get into phase; (ii) allow for collisions with the refresh cycle. - duration = Cycles(5); - } + // TODO: determine the cost of this access. +// if((mapping.flags & BankMapping::Is1Mhz) || ((mapping.flags & BankMapping::IsShadowed) && !isReadOperation(operation))) { +// // TODO: (i) get into phase; (ii) allow for the 1Mhz bus length being sporadically 16 rather than 14. +// duration = Cycles(14); +// } else { +// // TODO: (i) get into phase; (ii) allow for collisions with the refresh cycle. +// duration = Cycles(5); +// } fast_access_phase_ = (fast_access_phase_ + duration.as()) % 5; // TODO: modulo something else, to allow for refresh. slow_access_phase_ = (slow_access_phase_ + duration.as()) % 14; // TODO: modulo something else, to allow for stretched cycles. return duration; @@ -173,34 +303,31 @@ class ConcreteMachine: // MARK: - Memory layout and storage. - // Memory layout part 1: the bank mapping. Indexed by the top 16 bits of the address, - // each entry provides the actual bank that should be used plus some flags affecting the - // access: whether this section of memory is currently enabled for shadowing, whether - // accesses should cost 1 Mhz, and whether this is actually an IO area. + // 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. // - // Implementation note: the shadow and IO flags are more sensibly part of this table; - // logically the 1Mhz flag would ideally go with BankStorage but since there's no space - // in there currently set aside for flags, keeping it in the mapping will do. - struct BankMapping { - uint8_t destination = 0; + // 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 memory_map_; + + struct MemoryRegion { + uint8_t *write = nullptr; + const uint8_t *read = nullptr; uint8_t flags = 0; enum Flag: uint8_t { - IsShadowed = 1 << 0, - Is1Mhz = 1 << 1, - IsIO = 1 << 2, + IsShadowedE0 = 1 << 0, // i.e. writes should also be written to bank $e0, and costed appropriately. + IsShadowedE1 = 1 << 1, // i.e. writes should also be written to bank $e1, and costed appropriately. + Is1Mhz = 1 << 2, // Both reads and writes should be synchronised with the 1Mhz clock. + IsIO = 1 << 3, // Indicates that this region should be checked for soft switches, registers, etc. }; }; - static_assert(sizeof(BankMapping) == 2); - BankMapping bank_mapping_[65536]; + std::array memory_regions_; // The 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. - // Memory layout part 2: the bank storage. For each bank both a read and a write pointer - // are offered, indicating where the contents of this bank actually reside. - struct BankStorage { - uint8_t *write = nullptr; - const uint8_t *read = nullptr; - }; - BankStorage bank_storage_[256]; + // MARK: - Memory storage. // Actual memory storage. std::vector ram_;