mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-03 08:05:40 +00:00
276 lines
8.9 KiB
C++
276 lines
8.9 KiB
C++
//
|
||
// 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
|
||
|
||
namespace Apple {
|
||
namespace 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 <typename Machine> 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 = true;
|
||
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_.set_main_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_.set_card_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_.set_zero_page_paging();
|
||
}
|
||
};
|
||
|
||
}
|
||
}
|
||
|
||
#endif /* AuxiliaryMemorySwitches_h */
|