// // AuxiliaryMemorySwitches.hpp // Clock Signal // // Created by Thomas Harte on 22/10/2020. // Copyright © 2020 Thomas Harte. All rights reserved. // #ifndef AuxiliaryMemorySwitches_h #define AuxiliaryMemorySwitches_h #include "MemorySwitches.hpp" namespace Apple::II { /*! Models the auxiliary memory soft switches, added as of the Apple IIe, which allow access to the auxiliary 64kb of RAM and to the additional almost-4kb of ROM. Relevant memory accesses should be fed to this class; it'll call: * machine.set_main_paging() if anything in the 'main' state changes, i.e. the lower 48kb excluding the zero and stack pages; * machine.set_card_state() if anything changes with where ROM should appear rather than cards in the $Cxxx range; and * machine.set_zero_page_paging() if the selection of the lowest two pages of RAM changes. Implementation observation: as implemented on the IIe, the zero page setting also affects what happens in the language card area. */ template class AuxiliaryMemorySwitches { public: static constexpr bool Auxiliary = true; static constexpr bool Main = false; static constexpr bool ROM = true; static constexpr bool Card = false; /// Describes banking state between $0200 and $BFFF. struct MainState { struct Region { /// @c true indicates auxiliary memory should be read from; @c false indicates main. bool read = false; /// @c true indicates auxiliary memory should be written to; @c false indicates main. bool write = false; }; /// Describes banking state in the ranges $0200–$03FF, $0800–$1FFF and $4000–$BFFF. Region base; /// Describes banking state in the range $0400–$07FF. Region region_04_08; /// Describes banking state in the range $2000–$3FFF. Region region_20_40; bool operator != (const MainState &rhs) const { return base.read != rhs.base.read || base.write != rhs.base.write || region_04_08.read != rhs.region_04_08.read || region_04_08.write != rhs.region_04_08.write || region_20_40.read != rhs.region_20_40.read || region_20_40.write != rhs.region_20_40.write; } }; /// Describes banking state between $C100 and $Cfff. struct CardState { /// @c true indicates that the built-in ROM should appear from $C100 to $C2FF @c false indicates that cards should service those accesses. bool region_C1_C3 = false; /// @c true indicates that the built-in ROM should appear from $C300 to $C3FF; @c false indicates that cards should service those accesses. bool region_C3 = false; /// @c true indicates that the built-in ROM should appear from $C400 to $C7FF; @c false indicates that cards should service those accesses. bool region_C4_C8 = false; /// @c true indicates that the built-in ROM should appear from $C800 to $CFFF; @c false indicates that cards should service those accesses. bool region_C8_D0 = false; bool operator != (const CardState &rhs) const { return region_C1_C3 != rhs.region_C1_C3 || region_C3 != rhs.region_C3 || region_C4_C8 != rhs.region_C4_C8 || region_C8_D0 != rhs.region_C8_D0; } }; /// Descibes banking state between $0000 and $01ff; @c true indicates that auxiliary memory should be used; @c false indicates main memory. using ZeroState = bool; /// Returns raw switch state for all switches that affect banking, even if they're logically video switches. struct SwitchState { bool read_auxiliary_memory = false; bool write_auxiliary_memory = false; bool internal_CX_rom = false; bool slot_C3_rom = false; bool internal_C8_rom = false; bool store_80 = false; bool alternative_zero_page = false; bool video_page_2 = false; bool high_resolution = false; }; AuxiliaryMemorySwitches(Machine &machine) : machine_(machine) {} /// Used by an owner to forward, at least, any access in the range $C000 to $C00B, /// in $C054 to $C058, or in the range $C300 to $CFFF. Safe to call for any [16-bit] address. void access(uint16_t address, bool is_read) { if(address >= 0xc300 && address < 0xd000) { switches_.internal_C8_rom |= ((address >> 8) == 0xc3) && !switches_.slot_C3_rom; switches_.internal_C8_rom &= (address != 0xcfff); set_card_paging(); return; } if(address < 0xc000 || address >= 0xc058) return; switch(address) { default: break; case 0xc000: case 0xc001: if(!is_read) { switches_.store_80 = address & 1; set_main_paging(); } break; case 0xc002: case 0xc003: if(!is_read) { switches_.read_auxiliary_memory = address & 1; set_main_paging(); } break; case 0xc004: case 0xc005: if(!is_read) { switches_.write_auxiliary_memory = address & 1; set_main_paging(); } break; case 0xc006: case 0xc007: if(!is_read) { switches_.internal_CX_rom = address & 1; set_card_paging(); } break; case 0xc008: case 0xc009: { const bool alternative_zero_page = address & 1; if(!is_read && switches_.alternative_zero_page != alternative_zero_page) { switches_.alternative_zero_page = alternative_zero_page; set_zero_page_paging(); } } break; case 0xc00a: case 0xc00b: if(!is_read) { switches_.slot_C3_rom = address & 1; set_card_paging(); } break; case 0xc054: case 0xc055: switches_.video_page_2 = address & 1; set_main_paging(); break; case 0xc056: case 0xc057: switches_.high_resolution = address & 1; set_main_paging(); break; } } /// Provides part of the IIgs interface. void set_state(uint8_t value) { // b7: 1 => use auxiliary memory for zero page; 0 => use main. [I think the Hardware Reference gets this the wrong way around] // b6: 1 => text page 2 is selected; 0 => text page 1. // b5: 1 => auxiliary RAM bank is selected for reads; 0 => main. // b4: 1 => auxiliary RAM bank is selected for writes; 0 => main. // b0: 1 => the internal ROM is selected for C800+; 0 => card ROM. switches_.alternative_zero_page = value & 0x80; switches_.video_page_2 = value & 0x40; switches_.read_auxiliary_memory = value & 0x20; switches_.write_auxiliary_memory = value & 0x10; switches_.internal_CX_rom = value & 0x01; set_main_paging(); set_zero_page_paging(); set_card_paging(); } uint8_t get_state() const { return (switches_.alternative_zero_page ? 0x80 : 0x00) | (switches_.video_page_2 ? 0x40 : 0x00) | (switches_.read_auxiliary_memory ? 0x20 : 0x00) | (switches_.write_auxiliary_memory ? 0x10 : 0x00) | (switches_.internal_CX_rom ? 0x01 : 0x00); } const MainState &main_state() const { return main_state_; } const CardState &card_state() const { return card_state_; } /// @returns @c true if the alternative zero page should be used; @c false otherwise. ZeroState zero_state() const { return switches_.alternative_zero_page; } const SwitchState switches() const { return switches_; } private: Machine &machine_; SwitchState switches_; MainState main_state_; void set_main_paging() { const auto previous_state = main_state_; // The two appropriately named switches provide the base case. main_state_.base.read = switches_.read_auxiliary_memory; main_state_.base.write = switches_.write_auxiliary_memory; if(switches_.store_80) { // If store 80 is set, use the page 2 flag for the lower carve out; // if both store 80 and high resolution are set, use the page 2 flag for both carve outs. main_state_.region_04_08.read = main_state_.region_04_08.write = switches_.video_page_2; if(switches_.high_resolution) { main_state_.region_20_40.read = main_state_.region_20_40.write = switches_.video_page_2; } else { main_state_.region_20_40 = main_state_.base; } } else { main_state_.region_04_08 = main_state_.region_20_40 = main_state_.base; } if(previous_state != main_state_) { machine_.template set_paging(); } } CardState card_state_; void set_card_paging() { const auto previous_state = card_state_; // By default apply the CX switch through to $C7FF. card_state_.region_C1_C3 = card_state_.region_C4_C8 = switches_.internal_CX_rom; // Allow the C3 region to be switched to internal ROM in isolation even if the rest of the // first half of the CX region is diabled, if its specific switch is also disabled. if(!switches_.internal_CX_rom && !switches_.slot_C3_rom) { card_state_.region_C3 = true; } else { card_state_.region_C3 = card_state_.region_C1_C3; } // Apply the CX switch to $C800+, but also allow the C8 switch to select that region in isolation. card_state_.region_C8_D0 = switches_.internal_CX_rom || switches_.internal_C8_rom; if(previous_state != card_state_) { machine_.template set_paging(); } } void set_zero_page_paging() { // Believe it or not, the zero page is just set or cleared by a single flag. // As though life were rational. machine_.template set_paging(); } }; } #endif /* AuxiliaryMemorySwitches_h */