// // AppleIIgs.cpp // Clock Signal // // Created by Thomas Harte on 20/10/2020. // Copyright 2020 Thomas Harte. All rights reserved. // #include "AppleIIgs.hpp" #include "../../MachineTypes.hpp" #include "../../../Processors/65816/65816.hpp" #include "../../../Analyser/Static/AppleIIgs/Target.hpp" #include "../AppleII/LanguageCardSwitches.hpp" #include "../AppleII/AuxiliaryMemorySwitches.hpp" namespace Apple { namespace IIgs { class ConcreteMachine: public Apple::IIgs::Machine, public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, public CPU::MOS6502Esque::BusHandler { public: ConcreteMachine(const Analyser::Static::AppleIIgs::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : m65816_(*this), auxiliary_switches_(*this), language_card_(*this) { set_clock_rate(14318180.0); using Target = Analyser::Static::AppleIIgs::Target; std::vector rom_descriptions; const std::string machine_name = "AppleIIgs"; switch(target.model) { case Target::Model::ROM00: /* TODO */ case Target::Model::ROM01: rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM01", "apple2gs.rom", 128*1024, 0x42f124b0); break; case Target::Model::ROM03: rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM03", "apple2gs.rom2", 256*1024, 0xde7ddf29); break; } const auto roms = rom_fetcher(rom_descriptions); if(!roms[0]) { throw ROMMachine::Error::MissingROMs; } rom_ = *roms[0]; size_t ram_size = 0; switch(target.memory_model) { case Target::MemoryModel::TwoHundredAndFiftySixKB: ram_size = 256; break; case Target::MemoryModel::OneMB: ram_size = 128 + 1024; break; case Target::MemoryModel::EightMB: ram_size = 128 + 8 * 1024; break; } ram_.resize(ram_size * 1024); // Establish bank storage. // 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]; } } // 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]; // 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]; } // Establish initial bank mapping. for(size_t c = 0; c < 65536; ++c) { bank_mapping_[c].destination = uint8_t(c >> 8); } for(size_t c = 0; c < 256; ++c) { bank_mapping_[0xe000 + c].flags = BankMapping::Is1Mhz; bank_mapping_[0xe100 + c].flags = BankMapping::Is1Mhz; } // Apply initial language/auxiliary state. [TODO: including shadowing register]. set_card_paging(); set_zero_page_paging(); set_main_paging(); } void run_for(const Cycles cycles) override { m65816_.run_for(cycles); } void set_scan_target(Outputs::Display::ScanTarget *) override { } Outputs::Display::ScanStatus get_scaled_scan_status() const override { return Outputs::Display::ScanStatus(); } 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]; if(mapping.flags & BankMapping::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; } else { if(storage.write) { storage.write[address & 0xffff] = *value; if(mapping.flags & BankMapping::IsShadowed) { bank_storage_[mapping.destination + 0xe0].write[address & 0xffff] = *value; } } } } Cycles duration; // 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; } // MARK: - Memory banking. void set_language_card_paging() { } void set_card_paging() { } void set_zero_page_paging() { set_language_card_paging(); } void set_main_paging() { } private: CPU::WDC65816::Processor m65816_; Apple::II::AuxiliaryMemorySwitches auxiliary_switches_; Apple::II::LanguageCardSwitches language_card_; int fast_access_phase_ = 0; int slow_access_phase_ = 0; // 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. // // 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; uint8_t flags = 0; enum Flag: uint8_t { IsShadowed = 1 << 0, Is1Mhz = 1 << 1, IsIO = 1 << 2, }; }; static_assert(sizeof(BankMapping) == 2); BankMapping bank_mapping_[65536]; // 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]; // Actual memory storage. std::vector ram_; std::vector rom_; }; } } using namespace Apple::IIgs; Machine *Machine::AppleIIgs(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { return new ConcreteMachine(*dynamic_cast(target), rom_fetcher); } Machine::~Machine() {}