1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-18 16:30:29 +00:00

Adjust more dangling indentation changes.

This commit is contained in:
Thomas Harte 2024-12-04 22:29:08 -05:00
parent 3d2eefc7e7
commit ce5aae3f7d
45 changed files with 5530 additions and 5528 deletions

View File

@ -70,7 +70,7 @@ private:
uint8_t track_;
std::shared_ptr<Sector> sector_cache_[65536];
void process_input_bit(const int value) {
void process_input_bit(const int value) override {
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
bit_count_++;
}
@ -107,7 +107,7 @@ private:
}
}
void process_index_hole() {
void process_index_hole() override {
index_count_++;
}

View File

@ -24,261 +24,261 @@ namespace Apple::II {
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;
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 $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 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;
/// 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 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;
void reset() {
*this = SwitchState();
}
};
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 {
bool operator != (const MainState &rhs) 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);
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;
}
};
const MainState &main_state() const {
return main_state_;
}
/// 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;
const CardState &card_state() const {
return card_state_;
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;
}
};
/// @returns @c true if the alternative zero page should be used; @c false otherwise.
ZeroState zero_state() const {
return switches_.alternative_zero_page;
}
/// Descibes banking state between $0000 and $01ff; @c true indicates that auxiliary memory should be used; @c false indicates main memory.
using ZeroState = bool;
const SwitchState switches() const {
return switches_;
}
/// 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;
void reset() {
switches_.reset();
*this = SwitchState();
}
};
set_main_paging();
set_zero_page_paging();
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;
}
private:
Machine &machine_;
SwitchState switches_;
if(address < 0xc000 || address >= 0xc058) return;
MainState main_state_;
void set_main_paging() {
const auto previous_state = main_state_;
switch(address) {
default: break;
// 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;
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_;
}
void reset() {
switches_.reset();
set_main_paging();
set_zero_page_paging();
set_card_paging();
}
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_04_08 = main_state_.region_20_40 = main_state_.base;
}
if(previous_state != main_state_) {
machine_.template set_paging<PagingType::Main>();
main_state_.region_20_40 = main_state_.base;
}
} else {
main_state_.region_04_08 = main_state_.region_20_40 = main_state_.base;
}
CardState card_state_;
void set_card_paging() {
const auto previous_state = card_state_;
if(previous_state != main_state_) {
machine_.template set_paging<PagingType::Main>();
}
}
// By default apply the CX switch through to $C7FF.
card_state_.region_C1_C3 = card_state_.region_C4_C8 = switches_.internal_CX_rom;
CardState card_state_;
void set_card_paging() {
const auto previous_state = card_state_;
// 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;
}
// By default apply the CX switch through to $C7FF.
card_state_.region_C1_C3 = card_state_.region_C4_C8 = switches_.internal_CX_rom;
// 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<PagingType::CardArea>();
}
// 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;
}
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<PagingType::ZeroPage>();
// 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<PagingType::CardArea>();
}
}
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<PagingType::ZeroPage>();
}
};
}

View File

@ -19,67 +19,67 @@
namespace Apple::IIgs::ADB {
class GLU: public InstructionSet::M50740::PortHandler {
public:
GLU();
public:
GLU();
uint8_t get_keyboard_data();
uint8_t get_mouse_data();
uint8_t get_modifier_status();
uint8_t get_any_key_down();
uint8_t get_keyboard_data();
uint8_t get_mouse_data();
uint8_t get_modifier_status();
uint8_t get_any_key_down();
uint8_t get_data();
uint8_t get_status();
uint8_t get_data();
uint8_t get_status();
void set_command(uint8_t);
void set_status(uint8_t);
void clear_key_strobe();
void set_command(uint8_t);
void set_status(uint8_t);
void clear_key_strobe();
void set_microcontroller_rom(const std::vector<uint8_t> &rom);
void set_microcontroller_rom(const std::vector<uint8_t> &rom);
void run_for(Cycles cycles);
void run_for(Cycles cycles);
bool get_command_button() const;
bool get_option_button() const;
bool get_command_button() const;
bool get_option_button() const;
void set_vertical_blank(bool);
bool get_vertical_blank() {
return vertical_blank_;
}
void set_vertical_blank(bool);
bool get_vertical_blank() {
return vertical_blank_;
}
Apple::ADB::Keyboard &keyboard() {
return keyboard_;
}
Inputs::Mouse &get_mouse() {
return mouse_;
}
Apple::ADB::Keyboard &keyboard() {
return keyboard_;
}
Inputs::Mouse &get_mouse() {
return mouse_;
}
private:
InstructionSet::M50740::Executor executor_;
private:
InstructionSet::M50740::Executor executor_;
void run_ports_for(Cycles) override;
void set_port_output(int port, uint8_t value) override;
uint8_t get_port_input(int port) override;
void run_ports_for(Cycles) override;
void set_port_output(int port, uint8_t value) override;
uint8_t get_port_input(int port) override;
uint8_t registers_[16]{};
uint8_t registers_[16]{};
uint8_t register_address_;
uint8_t register_latch_ = 0xff;
bool register_strobe_ = false;
uint8_t register_address_;
uint8_t register_latch_ = 0xff;
bool register_strobe_ = false;
uint8_t status_ = 0x00;
uint8_t status_ = 0x00;
Apple::ADB::Bus bus_;
size_t controller_id_;
Apple::ADB::Bus bus_;
size_t controller_id_;
uint8_t modifier_state_ = 0;
uint8_t modifier_state_ = 0;
bool vertical_blank_ = false;
bool vertical_blank_ = false;
int visible_mouse_register_ = 2;
int visible_mouse_register_ = 2;
// For now, attach only a keyboard and mouse.
Apple::ADB::Mouse mouse_;
Apple::ADB::Keyboard keyboard_;
// For now, attach only a keyboard and mouse.
Apple::ADB::Mouse mouse_;
Apple::ADB::Keyboard keyboard_;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -20,144 +20,144 @@
namespace Apple::IIgs {
class MemoryMap {
public:
// MARK: - Initial construction and configuration.
public:
// MARK: - Initial construction and configuration.
MemoryMap(bool is_rom03) : auxiliary_switches_(*this), language_card_(*this) {
setup_shadow_maps(is_rom03);
}
MemoryMap(bool is_rom03) : auxiliary_switches_(*this), language_card_(*this) {
setup_shadow_maps(is_rom03);
}
/// Sets the ROM and RAM storage underlying this MemoryMap.
void set_storage(std::vector<uint8_t> &ram, std::vector<uint8_t> &rom);
/// Sets the ROM and RAM storage underlying this MemoryMap.
void set_storage(std::vector<uint8_t> &ram, std::vector<uint8_t> &rom);
// MARK: - Live bus access notifications and register access.
// MARK: - Live bus access notifications and register access.
void set_shadow_register(uint8_t value);
uint8_t get_shadow_register() const;
void set_shadow_register(uint8_t value);
uint8_t get_shadow_register() const;
void set_speed_register(uint8_t value);
void set_speed_register(uint8_t value);
void set_state_register(uint8_t value);
uint8_t get_state_register() const;
void set_state_register(uint8_t value);
uint8_t get_state_register() const;
void access(uint16_t address, bool is_read);
void access(uint16_t address, bool is_read);
using AuxiliaryMemorySwitches = Apple::II::AuxiliaryMemorySwitches<MemoryMap>;
const AuxiliaryMemorySwitches &auxiliary_switches() const {
return auxiliary_switches_;
}
using AuxiliaryMemorySwitches = Apple::II::AuxiliaryMemorySwitches<MemoryMap>;
const AuxiliaryMemorySwitches &auxiliary_switches() const {
return auxiliary_switches_;
}
using LanguageCardSwitches = Apple::II::LanguageCardSwitches<MemoryMap>;
const LanguageCardSwitches &language_card_switches() const {
return language_card_;
}
using LanguageCardSwitches = Apple::II::LanguageCardSwitches<MemoryMap>;
const LanguageCardSwitches &language_card_switches() const {
return language_card_;
}
// MARK: - Accessors for reading and writing RAM.
// MARK: - Accessors for reading and writing RAM.
struct Region {
uint8_t *write = nullptr;
const uint8_t *read = nullptr;
uint8_t flags = 0;
struct Region {
uint8_t *write = nullptr;
const uint8_t *read = nullptr;
uint8_t flags = 0;
enum Flag: uint8_t {
Is1Mhz = 1 << 0, // Both reads and writes should be synchronised with the 1Mhz clock.
IsIO = 1 << 1, // Indicates that this region should be checked for soft switches, registers, etc.
};
enum Flag: uint8_t {
Is1Mhz = 1 << 0, // Both reads and writes should be synchronised with the 1Mhz clock.
IsIO = 1 << 1, // Indicates that this region should be checked for soft switches, registers, etc.
};
};
const Region &region(uint32_t address) const { return regions_[region_map_[address >> 8]]; }
uint8_t read(const Region &region, uint32_t address) const {
return region.read ? region.read[address] : 0xff;
const Region &region(uint32_t address) const { return regions_[region_map_[address >> 8]]; }
uint8_t read(const Region &region, uint32_t address) const {
return region.read ? region.read[address] : 0xff;
}
bool is_shadowed(const Region &region, uint32_t address) const {
// ROM is never shadowed.
if(!region.write) {
return false;
}
bool is_shadowed(const Region &region, uint32_t address) const {
// ROM is never shadowed.
if(!region.write) {
return false;
}
const auto physical = physical_address(region, address);
assert(physical <= 0xff'ffff);
return shadow_pages_[(physical >> 10) & 127] && shadow_banks_[physical >> 17];
}
void write(const Region &region, uint32_t address, uint8_t value) {
if(!region.write) {
return;
}
// Write once.
region.write[address] = value;
// Write again, either to the same place (if unshadowed) or to the shadow destination.
static constexpr std::size_t shadow_mask[2] = {0xff'ffff, 0x01'ffff};
const bool shadowed = is_shadowed(region, address);
shadow_base_[shadowed][physical_address(region, address) & shadow_mask[shadowed]] = value;
const auto physical = physical_address(region, address);
assert(physical <= 0xff'ffff);
return shadow_pages_[(physical >> 10) & 127] && shadow_banks_[physical >> 17];
}
void write(const Region &region, uint32_t address, uint8_t value) {
if(!region.write) {
return;
}
// The objective is to support shadowing:
// 1. without storing a whole extra pointer, and such that the shadowing flags
// are orthogonal to the current auxiliary memory settings;
// 2. in such a way as to support shadowing both in banks $00/$01 and elsewhere; and
// 3. to do so without introducing too much in the way of branching.
//
// Hence the implemented solution: if shadowing is enabled then use the distance from the start of
// physical RAM modulo 128k indexed into the bank $e0/$e1 RAM.
//
// With a further twist: the modulo and pointer are indexed on ::IsShadowed to eliminate a branch
// even on that.
// Write once.
region.write[address] = value;
private:
AuxiliaryMemorySwitches auxiliary_switches_;
LanguageCardSwitches language_card_;
friend AuxiliaryMemorySwitches;
friend LanguageCardSwitches;
// Write again, either to the same place (if unshadowed) or to the shadow destination.
static constexpr std::size_t shadow_mask[2] = {0xff'ffff, 0x01'ffff};
const bool shadowed = is_shadowed(region, address);
shadow_base_[shadowed][physical_address(region, address) & shadow_mask[shadowed]] = value;
}
uint8_t shadow_register_ = 0x00;
uint8_t speed_register_ = 0x00;
// The objective is to support shadowing:
// 1. without storing a whole extra pointer, and such that the shadowing flags
// are orthogonal to the current auxiliary memory settings;
// 2. in such a way as to support shadowing both in banks $00/$01 and elsewhere; and
// 3. to do so without introducing too much in the way of branching.
//
// Hence the implemented solution: if shadowing is enabled then use the distance from the start of
// physical RAM modulo 128k indexed into the bank $e0/$e1 RAM.
//
// With a further twist: the modulo and pointer are indexed on ::IsShadowed to eliminate a branch
// even on that.
// MARK: - Banking.
private:
AuxiliaryMemorySwitches auxiliary_switches_;
LanguageCardSwitches language_card_;
friend AuxiliaryMemorySwitches;
friend LanguageCardSwitches;
void assert_is_region(uint8_t start, uint8_t end);
template <int type> void set_paging();
uint8_t shadow_register_ = 0x00;
uint8_t speed_register_ = 0x00;
uint8_t *ram_base_ = nullptr;
// MARK: - Banking.
// 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 region_map; and
// (ii) use that to index the memory_regions table.
//
// 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<uint8_t, 65536> region_map_{};
std::array<Region, 40> regions_; // An 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.
void assert_is_region(uint8_t start, uint8_t end);
template <int type> void set_paging();
std::size_t physical_address(const Region &region, uint32_t address) const {
return std::size_t(&region.write[address] - ram_base_);
}
uint8_t *ram_base_ = nullptr;
// MARK: - Shadowing
// 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 region_map; and
// (ii) use that to index the memory_regions table.
//
// 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<uint8_t, 65536> region_map_{};
std::array<Region, 40> regions_; // An 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.
// Various precomputed bitsets describing key regions; std::bitset doesn't support constexpr instantiation
// beyond the first 64 bits at the time of writing, alas, so these are generated at runtime.
std::bitset<128> shadow_text1_;
std::bitset<128> shadow_text2_;
std::bitset<128> shadow_highres1_, shadow_highres1_aux_;
std::bitset<128> shadow_highres2_, shadow_highres2_aux_;
std::bitset<128> shadow_superhighres_;
void setup_shadow_maps(bool is_rom03);
void set_shadowing();
std::size_t physical_address(const Region &region, uint32_t address) const {
return std::size_t(&region.write[address] - ram_base_);
}
uint8_t *shadow_base_[2] = {nullptr, nullptr};
// MARK: - Shadowing
// Divide the final 128kb of memory into 1kb chunks and flag to indicate whether
// each is a potential destination for shadowing.
std::bitset<128> shadow_pages_{};
// Various precomputed bitsets describing key regions; std::bitset doesn't support constexpr instantiation
// beyond the first 64 bits at the time of writing, alas, so these are generated at runtime.
std::bitset<128> shadow_text1_;
std::bitset<128> shadow_text2_;
std::bitset<128> shadow_highres1_, shadow_highres1_aux_;
std::bitset<128> shadow_highres2_, shadow_highres2_aux_;
std::bitset<128> shadow_superhighres_;
void setup_shadow_maps(bool is_rom03);
void set_shadowing();
// Divide the whole 16mb of memory into 128kb chunks and flag to indicate whether
// each is a potential source of shadowing.
std::bitset<128> shadow_banks_{};
uint8_t *shadow_base_[2] = {nullptr, nullptr};
// Divide the final 128kb of memory into 1kb chunks and flag to indicate whether
// each is a potential destination for shadowing.
std::bitset<128> shadow_pages_{};
// Divide the whole 16mb of memory into 128kb chunks and flag to indicate whether
// each is a potential source of shadowing.
std::bitset<128> shadow_banks_{};
};
}

View File

@ -17,90 +17,90 @@
namespace Apple::IIgs::Sound {
class GLU: public Outputs::Speaker::BufferSource<GLU, false> { // TODO: isn't this stereo?
public:
GLU(Concurrency::AsyncTaskQueue<false> &audio_queue);
public:
GLU(Concurrency::AsyncTaskQueue<false> &audio_queue);
void set_control(uint8_t);
uint8_t get_control();
void set_data(uint8_t);
uint8_t get_data();
void set_address_low(uint8_t);
uint8_t get_address_low();
void set_address_high(uint8_t);
uint8_t get_address_high();
void set_control(uint8_t);
uint8_t get_control();
void set_data(uint8_t);
uint8_t get_data();
void set_address_low(uint8_t);
uint8_t get_address_low();
void set_address_high(uint8_t);
uint8_t get_address_high();
void run_for(Cycles);
Cycles next_sequence_point() const;
bool get_interrupt_line();
void run_for(Cycles);
Cycles next_sequence_point() const;
bool get_interrupt_line();
// SampleSource.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range);
bool is_zero_level() const { return false; } // TODO.
// SampleSource.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void set_sample_volume_range(std::int16_t range);
bool is_zero_level() const { return false; } // TODO.
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
uint16_t address_ = 0;
uint16_t address_ = 0;
// Use a circular buffer for piping memory alterations onto the audio
// thread; it would be prohibitive to defer every write individually.
//
// Assumed: on most modern architectures, an atomic 64-bit read or
// write can be achieved locklessly.
struct MemoryWrite {
uint32_t time;
uint16_t address;
uint8_t value;
bool enabled;
};
static_assert(sizeof(MemoryWrite) == 8);
constexpr static int StoreBufferSize = 16384;
// Use a circular buffer for piping memory alterations onto the audio
// thread; it would be prohibitive to defer every write individually.
//
// Assumed: on most modern architectures, an atomic 64-bit read or
// write can be achieved locklessly.
struct MemoryWrite {
uint32_t time;
uint16_t address;
uint8_t value;
bool enabled;
};
static_assert(sizeof(MemoryWrite) == 8);
constexpr static int StoreBufferSize = 16384;
std::atomic<MemoryWrite> pending_stores_[StoreBufferSize];
uint32_t pending_store_read_ = 0, pending_store_read_time_ = 0;
uint32_t pending_store_write_ = 0, pending_store_write_time_ = 0;
std::atomic<MemoryWrite> pending_stores_[StoreBufferSize];
uint32_t pending_store_read_ = 0, pending_store_read_time_ = 0;
uint32_t pending_store_write_ = 0, pending_store_write_time_ = 0;
// Maintain state both 'locally' (i.e. on the emulation thread) and
// 'remotely' (i.e. on the audio thread).
struct EnsoniqState {
uint8_t ram_[65536];
struct Oscillator {
uint32_t position;
// Maintain state both 'locally' (i.e. on the emulation thread) and
// 'remotely' (i.e. on the audio thread).
struct EnsoniqState {
uint8_t ram_[65536];
struct Oscillator {
uint32_t position;
// Programmer-set values.
uint16_t velocity;
uint8_t volume;
uint8_t address;
uint8_t control;
uint8_t table_size;
// Programmer-set values.
uint16_t velocity;
uint8_t volume;
uint8_t address;
uint8_t control;
uint8_t table_size;
// Derived state.
uint32_t overflow_mask; // If a non-zero bit gets anywhere into the overflow mask, this channel
// has wrapped around. It's a function of table_size.
bool interrupt_request = false; // Will be non-zero if this channel would request an interrupt, were
// it currently enabled to do so.
// Derived state.
uint32_t overflow_mask; // If a non-zero bit gets anywhere into the overflow mask, this channel
// has wrapped around. It's a function of table_size.
bool interrupt_request = false; // Will be non-zero if this channel would request an interrupt, were
// it currently enabled to do so.
uint8_t sample(uint8_t *ram);
int16_t output(uint8_t *ram);
} oscillators[32];
uint8_t sample(uint8_t *ram);
int16_t output(uint8_t *ram);
} oscillators[32];
// Some of these aren't actually needed on both threads.
uint8_t control = 0;
int oscillator_count = 1;
// Some of these aren't actually needed on both threads.
uint8_t control = 0;
int oscillator_count = 1;
void set_register(uint16_t address, uint8_t value);
} local_, remote_;
void set_register(uint16_t address, uint8_t value);
} local_, remote_;
// Functions to update an EnsoniqState; these don't belong to the state itself
// because they also access the pending stores (inter alia).
template <Outputs::Speaker::Action action>
void generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void skip_audio(EnsoniqState &state, size_t number_of_samples);
// Functions to update an EnsoniqState; these don't belong to the state itself
// because they also access the pending stores (inter alia).
template <Outputs::Speaker::Action action>
void generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target);
void skip_audio(EnsoniqState &state, size_t number_of_samples);
// Audio-thread state.
int16_t output_range_ = 0;
// Audio-thread state.
int16_t output_range_ = 0;
};
}

View File

@ -20,236 +20,236 @@ namespace Apple::IIgs::Video {
stretched cycle.
*/
class Video: public Apple::II::VideoSwitches<Cycles> {
public:
Video();
void set_internal_ram(const uint8_t *);
bool get_is_vertical_blank(Cycles offset);
uint8_t get_horizontal_counter(Cycles offset);
uint8_t get_vertical_counter(Cycles offset);
void set_new_video(uint8_t);
uint8_t get_new_video();
void clear_interrupts(uint8_t);
uint8_t get_interrupt_register();
void set_interrupt_register(uint8_t);
bool get_interrupt_line();
void notify_clock_tick();
void set_border_colour(uint8_t);
void set_text_colour(uint8_t);
uint8_t get_text_colour();
uint8_t get_border_colour();
void set_composite_is_colour(bool);
bool get_composite_is_colour();
/// Sets the scan target.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
/// Gets the type of output.
Outputs::Display::DisplayType get_display_type() const;
/// Determines the period until video might autonomously update its interrupt lines.
Cycles next_sequence_point() const;
/// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are
/// generated here.
void set_megaii_interrupts_enabled(uint8_t);
uint8_t get_megaii_interrupt_status();
void clear_megaii_interrupts();
private:
Outputs::CRT::CRT crt_;
// This is coupled to Apple::II::GraphicsMode, but adds detail for the IIgs.
enum class GraphicsMode {
Text = 0,
DoubleText,
HighRes,
DoubleHighRes,
LowRes,
DoubleLowRes,
FatLowRes,
// Additions:
DoubleHighResMono,
SuperHighRes
};
constexpr bool is_colour_ntsc(const GraphicsMode m) {
return m >= GraphicsMode::HighRes && m <= GraphicsMode::FatLowRes;
}
GraphicsMode graphics_mode(int row) const {
if(new_video_ & 0x80) {
return GraphicsMode::SuperHighRes;
}
const auto ii_mode = Apple::II::VideoSwitches<Cycles>::graphics_mode(row);
switch(ii_mode) {
// Coupling very much assumed here.
case Apple::II::GraphicsMode::DoubleHighRes:
if(new_video_ & 0x20) {
return GraphicsMode::DoubleHighResMono;
}
[[fallthrough]];
default: return GraphicsMode(int(ii_mode)); break;
}
}
enum class PixelBufferFormat {
Text, DoubleText, NTSC, NTSCMono, SuperHighRes
};
constexpr PixelBufferFormat format_for_mode(GraphicsMode m) {
switch(m) {
case GraphicsMode::Text: return PixelBufferFormat::Text;
case GraphicsMode::DoubleText: return PixelBufferFormat::DoubleText;
default: return PixelBufferFormat::NTSC;
case GraphicsMode::DoubleHighResMono: return PixelBufferFormat::NTSCMono;
case GraphicsMode::SuperHighRes: return PixelBufferFormat::SuperHighRes;
}
}
void advance(Cycles);
uint8_t new_video_ = 0x01;
class Interrupts {
public:
Video();
void set_internal_ram(const uint8_t *);
void add(uint8_t value) {
// Taken literally, status accumulates regardless of being enabled,
// potentially to be polled, it simply doesn't trigger an interrupt.
value_ |= value;
test();
}
bool get_is_vertical_blank(Cycles offset);
uint8_t get_horizontal_counter(Cycles offset);
uint8_t get_vertical_counter(Cycles offset);
void clear(uint8_t value) {
// Zeroes in bits 5 or 6 clear the respective interrupts.
value_ &= value | ~0x60;
test();
}
void set_new_video(uint8_t);
uint8_t get_new_video();
void set_control(uint8_t value) {
// Ones in bits 1 or 2 enable the respective interrupts.
value_ = (value_ & ~0x6) | (value & 0x6);
test();
}
void clear_interrupts(uint8_t);
uint8_t get_interrupt_register();
void set_interrupt_register(uint8_t);
bool get_interrupt_line();
uint8_t status() const {
return value_;
}
void notify_clock_tick();
void set_border_colour(uint8_t);
void set_text_colour(uint8_t);
uint8_t get_text_colour();
uint8_t get_border_colour();
void set_composite_is_colour(bool);
bool get_composite_is_colour();
/// Sets the scan target.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
/// Gets the type of output.
Outputs::Display::DisplayType get_display_type() const;
/// Determines the period until video might autonomously update its interrupt lines.
Cycles next_sequence_point() const;
/// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are
/// generated here.
void set_megaii_interrupts_enabled(uint8_t);
uint8_t get_megaii_interrupt_status();
void clear_megaii_interrupts();
bool active() const {
return value_ & 0x80;
}
private:
Outputs::CRT::CRT crt_;
// This is coupled to Apple::II::GraphicsMode, but adds detail for the IIgs.
enum class GraphicsMode {
Text = 0,
DoubleText,
HighRes,
DoubleHighRes,
LowRes,
DoubleLowRes,
FatLowRes,
// Additions:
DoubleHighResMono,
SuperHighRes
};
constexpr bool is_colour_ntsc(const GraphicsMode m) {
return m >= GraphicsMode::HighRes && m <= GraphicsMode::FatLowRes;
}
GraphicsMode graphics_mode(int row) const {
if(new_video_ & 0x80) {
return GraphicsMode::SuperHighRes;
}
const auto ii_mode = Apple::II::VideoSwitches<Cycles>::graphics_mode(row);
switch(ii_mode) {
// Coupling very much assumed here.
case Apple::II::GraphicsMode::DoubleHighRes:
if(new_video_ & 0x20) {
return GraphicsMode::DoubleHighResMono;
}
[[fallthrough]];
default: return GraphicsMode(int(ii_mode)); break;
void test() {
value_ &= 0x7f;
if((value_ >> 4) & value_ & 0x6) {
value_ |= 0x80;
}
}
enum class PixelBufferFormat {
Text, DoubleText, NTSC, NTSCMono, SuperHighRes
};
constexpr PixelBufferFormat format_for_mode(GraphicsMode m) {
switch(m) {
case GraphicsMode::Text: return PixelBufferFormat::Text;
case GraphicsMode::DoubleText: return PixelBufferFormat::DoubleText;
default: return PixelBufferFormat::NTSC;
case GraphicsMode::DoubleHighResMono: return PixelBufferFormat::NTSCMono;
case GraphicsMode::SuperHighRes: return PixelBufferFormat::SuperHighRes;
}
}
void advance(Cycles);
uint8_t new_video_ = 0x01;
class Interrupts {
public:
void add(uint8_t value) {
// Taken literally, status accumulates regardless of being enabled,
// potentially to be polled, it simply doesn't trigger an interrupt.
value_ |= value;
test();
}
void clear(uint8_t value) {
// Zeroes in bits 5 or 6 clear the respective interrupts.
value_ &= value | ~0x60;
test();
}
void set_control(uint8_t value) {
// Ones in bits 1 or 2 enable the respective interrupts.
value_ = (value_ & ~0x6) | (value & 0x6);
test();
}
uint8_t status() const {
return value_;
}
bool active() const {
return value_ & 0x80;
}
private:
void test() {
value_ &= 0x7f;
if((value_ >> 4) & value_ & 0x6) {
value_ |= 0x80;
}
}
// Overall meaning of value is as per the VGC interrupt register, i.e.
//
// b7: interrupt status;
// b6: 1-second interrupt status;
// b5: scan-line interrupt status;
// b4: reserved;
// b3: reserved;
// b2: 1-second interrupt enable;
// b1: scan-line interrupt enable;
// b0: reserved.
uint8_t value_ = 0x00;
} interrupts_;
int cycles_into_frame_ = 0;
const uint8_t *ram_ = nullptr;
// The modal colours.
uint16_t border_colour_ = 0;
uint8_t border_colour_entry_ = 0;
uint8_t text_colour_entry_ = 0xf0;
uint16_t text_colour_ = 0xffff;
uint16_t background_colour_ = 0;
// Current pixel output buffer and conceptual format.
PixelBufferFormat pixels_format_;
uint16_t *pixels_ = nullptr, *next_pixel_ = nullptr;
int pixels_start_column_;
void output_row(int row, int start, int end);
uint16_t *output_super_high_res(uint16_t *target, int start, int end, int row) const;
uint16_t *output_text(uint16_t *target, int start, int end, int row) const;
uint16_t *output_double_text(uint16_t *target, int start, int end, int row) const;
uint16_t *output_char(uint16_t *target, uint8_t source, int row) const;
uint16_t *output_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_fat_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_high_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_high_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_high_resolution_mono(uint16_t *target, int start, int end, int row);
// Super high-res per-line state.
uint8_t line_control_;
uint16_t palette_[16];
// Storage used for fill mode.
uint16_t *palette_zero_[4] = {nullptr, nullptr, nullptr, nullptr}, palette_throwaway_;
// Lookup tables and state to assist in the IIgs' mapping from NTSC to RGB.
// Overall meaning of value is as per the VGC interrupt register, i.e.
//
// My understanding of the real-life algorithm is: maintain a four-bit buffer.
// Fill it in a circular fashion. Ordinarily, output the result of looking
// up the RGB mapping of those four bits of Apple II output (which outputs four
// bits per NTSC colour cycle), commuted as per current phase. But if the bit
// being inserted differs from that currently in its position in the shift
// register, hold the existing output for three shifts.
//
// From there I am using the following:
// b7: interrupt status;
// b6: 1-second interrupt status;
// b5: scan-line interrupt status;
// b4: reserved;
// b3: reserved;
// b2: 1-second interrupt enable;
// b1: scan-line interrupt enable;
// b0: reserved.
uint8_t value_ = 0x00;
} interrupts_;
// Maps from:
//
// b0 = b0 of the shift register
// b1 = b4 of the shift register
// b2 = current delay count
//
// to a new delay count.
uint8_t ntsc_delay_lookup_[20];
uint32_t ntsc_shift_ = 0; // Assumption here: logical shifts will ensue, rather than arithmetic.
int ntsc_delay_ = 0;
int cycles_into_frame_ = 0;
const uint8_t *ram_ = nullptr;
/// Outputs the lowest 14 bits from @c ntsc_shift_, mapping to RGB.
/// Phase is derived from @c column.
uint16_t *output_shift(uint16_t *target, int column);
// The modal colours.
uint16_t border_colour_ = 0;
uint8_t border_colour_entry_ = 0;
uint8_t text_colour_entry_ = 0xf0;
uint16_t text_colour_ = 0xffff;
uint16_t background_colour_ = 0;
// Common getter for the two counters.
struct Counters {
Counters(int v, int h) : vertical(v), horizontal(h) {}
const int vertical, horizontal;
};
Counters get_counters(Cycles offset);
// Current pixel output buffer and conceptual format.
PixelBufferFormat pixels_format_;
uint16_t *pixels_ = nullptr, *next_pixel_ = nullptr;
int pixels_start_column_;
// Marshalls the Mega II-style interrupt state.
uint8_t megaii_interrupt_mask_ = 0;
uint8_t megaii_interrupt_state_ = 0;
int megaii_frame_counter_ = 0; // To count up to quarter-second interrupts.
void output_row(int row, int start, int end);
uint16_t *output_super_high_res(uint16_t *target, int start, int end, int row) const;
uint16_t *output_text(uint16_t *target, int start, int end, int row) const;
uint16_t *output_double_text(uint16_t *target, int start, int end, int row) const;
uint16_t *output_char(uint16_t *target, uint8_t source, int row) const;
uint16_t *output_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_fat_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_high_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_high_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_high_resolution_mono(uint16_t *target, int start, int end, int row);
// Super high-res per-line state.
uint8_t line_control_;
uint16_t palette_[16];
// Storage used for fill mode.
uint16_t *palette_zero_[4] = {nullptr, nullptr, nullptr, nullptr}, palette_throwaway_;
// Lookup tables and state to assist in the IIgs' mapping from NTSC to RGB.
//
// My understanding of the real-life algorithm is: maintain a four-bit buffer.
// Fill it in a circular fashion. Ordinarily, output the result of looking
// up the RGB mapping of those four bits of Apple II output (which outputs four
// bits per NTSC colour cycle), commuted as per current phase. But if the bit
// being inserted differs from that currently in its position in the shift
// register, hold the existing output for three shifts.
//
// From there I am using the following:
// Maps from:
//
// b0 = b0 of the shift register
// b1 = b4 of the shift register
// b2 = current delay count
//
// to a new delay count.
uint8_t ntsc_delay_lookup_[20];
uint32_t ntsc_shift_ = 0; // Assumption here: logical shifts will ensue, rather than arithmetic.
int ntsc_delay_ = 0;
/// Outputs the lowest 14 bits from @c ntsc_shift_, mapping to RGB.
/// Phase is derived from @c column.
uint16_t *output_shift(uint16_t *target, int column);
// Common getter for the two counters.
struct Counters {
Counters(int v, int h) : vertical(v), horizontal(h) {}
const int vertical, horizontal;
};
Counters get_counters(Cycles offset);
// Marshalls the Mega II-style interrupt state.
uint8_t megaii_interrupt_mask_ = 0;
uint8_t megaii_interrupt_state_ = 0;