2020-10-23 02:33:31 +00:00
|
|
|
|
//
|
|
|
|
|
// 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 {
|
|
|
|
|
|
2020-10-23 22:44:47 +00:00
|
|
|
|
/*!
|
|
|
|
|
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.
|
|
|
|
|
*/
|
2020-10-23 02:33:31 +00:00
|
|
|
|
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 {
|
2020-12-11 03:47:11 +00:00
|
|
|
|
/// @c true indicates auxiliary memory should be read from; @c false indicates main.
|
2020-10-23 02:33:31 +00:00
|
|
|
|
bool read = false;
|
2020-12-11 03:47:11 +00:00
|
|
|
|
/// @c true indicates auxiliary memory should be written to; @c false indicates main.
|
2020-10-23 02:33:31 +00:00
|
|
|
|
bool write = false;
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-06 00:07:38 +00:00
|
|
|
|
/// Describes banking state in the ranges $0200–$03FF, $0800–$1FFF and $4000–$BFFF.
|
2020-10-23 02:33:31 +00:00
|
|
|
|
Region base;
|
2020-12-06 00:07:38 +00:00
|
|
|
|
/// Describes banking state in the range $0400–$07FF.
|
2020-10-23 02:33:31 +00:00
|
|
|
|
Region region_04_08;
|
|
|
|
|
/// Describes banking state in the range $2000–$3FFF.
|
|
|
|
|
Region region_20_40;
|
|
|
|
|
|
2020-10-23 22:44:47 +00:00
|
|
|
|
bool operator != (const MainState &rhs) const {
|
2020-10-23 02:33:31 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2020-10-23 22:44:47 +00:00
|
|
|
|
bool operator != (const CardState &rhs) const {
|
2020-10-23 02:33:31 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2021-09-10 02:06:36 +00:00
|
|
|
|
bool internal_CX_rom = true;
|
2020-10-23 02:33:31 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-24 23:00:03 +00:00
|
|
|
|
if(address < 0xc000 || address >= 0xc058) return;
|
2020-10-23 02:33:31 +00:00
|
|
|
|
|
|
|
|
|
switch(address) {
|
|
|
|
|
default: break;
|
|
|
|
|
|
|
|
|
|
case 0xc000: case 0xc001:
|
2020-10-24 23:00:03 +00:00
|
|
|
|
if(!is_read) {
|
|
|
|
|
switches_.store_80 = address & 1;
|
|
|
|
|
set_main_paging();
|
|
|
|
|
}
|
2020-10-23 02:33:31 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0xc002: case 0xc003:
|
2020-10-24 23:00:03 +00:00
|
|
|
|
if(!is_read) {
|
|
|
|
|
switches_.read_auxiliary_memory = address & 1;
|
|
|
|
|
set_main_paging();
|
|
|
|
|
}
|
2020-10-23 02:33:31 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0xc004: case 0xc005:
|
2020-10-24 23:00:03 +00:00
|
|
|
|
if(!is_read) {
|
|
|
|
|
switches_.write_auxiliary_memory = address & 1;
|
|
|
|
|
set_main_paging();
|
|
|
|
|
}
|
2020-10-23 02:33:31 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0xc006: case 0xc007:
|
2020-10-24 23:00:03 +00:00
|
|
|
|
if(!is_read) {
|
|
|
|
|
switches_.internal_CX_rom = address & 1;
|
|
|
|
|
set_card_paging();
|
|
|
|
|
}
|
2020-10-23 02:33:31 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2020-12-12 02:43:13 +00:00
|
|
|
|
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;
|
2020-10-23 02:33:31 +00:00
|
|
|
|
set_zero_page_paging();
|
|
|
|
|
}
|
2020-12-12 02:43:13 +00:00
|
|
|
|
} break;
|
2020-10-23 02:33:31 +00:00
|
|
|
|
|
|
|
|
|
case 0xc00a: case 0xc00b:
|
2020-10-24 23:00:03 +00:00
|
|
|
|
if(!is_read) {
|
|
|
|
|
switches_.slot_C3_rom = address & 1;
|
|
|
|
|
set_card_paging();
|
|
|
|
|
}
|
2020-10-23 02:33:31 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-29 01:58:20 +00:00
|
|
|
|
/// Provides part of the IIgs interface.
|
|
|
|
|
void set_state(uint8_t value) {
|
2020-12-11 03:11:53 +00:00
|
|
|
|
// 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.
|
2020-10-29 01:58:20 +00:00
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-10 03:11:20 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-29 01:58:20 +00:00
|
|
|
|
const MainState &main_state() const {
|
2020-10-23 02:33:31 +00:00
|
|
|
|
return main_state_;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-29 01:58:20 +00:00
|
|
|
|
const CardState &card_state() const {
|
2020-10-23 02:33:31 +00:00
|
|
|
|
return card_state_;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-11 03:11:53 +00:00
|
|
|
|
/// @returns @c true if the alternative zero page should be used; @c false otherwise.
|
2021-03-22 02:25:14 +00:00
|
|
|
|
ZeroState zero_state() const {
|
2020-10-23 02:33:31 +00:00
|
|
|
|
return switches_.alternative_zero_page;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-29 01:58:20 +00:00
|
|
|
|
const SwitchState switches() const {
|
2020-10-23 02:33:31 +00:00
|
|
|
|
return switches_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
Machine &machine_;
|
|
|
|
|
SwitchState switches_;
|
|
|
|
|
|
|
|
|
|
MainState main_state_;
|
|
|
|
|
void set_main_paging() {
|
2020-10-23 22:44:47 +00:00
|
|
|
|
const auto previous_state = main_state_;
|
2020-10-23 02:33:31 +00:00
|
|
|
|
|
|
|
|
|
// 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() {
|
2020-10-23 22:44:47 +00:00
|
|
|
|
const auto previous_state = card_state_;
|
2020-10-23 02:33:31 +00:00
|
|
|
|
|
|
|
|
|
// 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 */
|