1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-26 10:29:31 +00:00

Merge pull request #1055 from TomHarte/IIgsMemoryMap

Introduce further IIgs memory map tests.
This commit is contained in:
Thomas Harte 2022-06-29 15:19:31 -04:00 committed by GitHub
commit 1e149d0add
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 10595 additions and 256 deletions

View File

@ -183,72 +183,72 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
}
// MARK: The language card.
// MARK: - The language card, auxiliary memory, and IIe-specific improvements.
LanguageCardSwitches<ConcreteMachine> language_card_;
AuxiliaryMemorySwitches<ConcreteMachine> auxiliary_switches_;
friend LanguageCardSwitches<ConcreteMachine>;
friend AuxiliaryMemorySwitches<ConcreteMachine>;
void set_language_card_paging() {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
uint8_t *const ram = zero_state ? aux_ram_ : ram_;
uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data();
// Which way the region here is mapped to be banks 1 and 2 is
// arbitrary.
page(0xd0, 0xe0,
language_state.read ? &ram[language_state.bank2 ? 0xd000 : 0xc000] : rom,
language_state.write ? nullptr : &ram[language_state.bank2 ? 0xd000 : 0xc000]);
page(0xe0, 0x100,
language_state.read ? &ram[0xe000] : &rom[0x1000],
language_state.write ? nullptr : &ram[0xe000]);
}
// MARK: Auxiliary memory and the other IIe improvements.
void set_card_paging() {
const auto state = auxiliary_switches_.card_state();
page(0xc1, 0xc4, state.region_C1_C3 ? &rom_[0xc100 - 0xc100] : nullptr, nullptr);
read_pages_[0xc3] = state.region_C3 ? &rom_[0xc300 - 0xc100] : nullptr;
page(0xc4, 0xc8, state.region_C4_C8 ? &rom_[0xc400 - 0xc100] : nullptr, nullptr);
page(0xc8, 0xd0, state.region_C8_D0 ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
}
void set_zero_page_paging() {
if(auxiliary_switches_.zero_state()) {
write_pages_[0] = aux_ram_;
} else {
write_pages_[0] = ram_;
template <int type> void set_paging() {
if constexpr (bool(type & PagingType::ZeroPage)) {
if(auxiliary_switches_.zero_state()) {
write_pages_[0] = aux_ram_;
} else {
write_pages_[0] = ram_;
}
write_pages_[1] = write_pages_[0] + 256;
read_pages_[0] = write_pages_[0];
read_pages_[1] = write_pages_[1];
}
write_pages_[1] = write_pages_[0] + 256;
read_pages_[0] = write_pages_[0];
read_pages_[1] = write_pages_[1];
// Zero page banking also affects interpretation of the language card's switches.
set_language_card_paging();
}
void set_main_paging() {
const auto state = auxiliary_switches_.main_state();
if constexpr (bool(type & (PagingType::LanguageCard | PagingType::ZeroPage))) {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
page(0x02, 0x04,
state.base.read ? &aux_ram_[0x0200] : &ram_[0x0200],
state.base.write ? &aux_ram_[0x0200] : &ram_[0x0200]);
page(0x08, 0x20,
state.base.read ? &aux_ram_[0x0800] : &ram_[0x0800],
state.base.write ? &aux_ram_[0x0800] : &ram_[0x0800]);
page(0x40, 0xc0,
state.base.read ? &aux_ram_[0x4000] : &ram_[0x4000],
state.base.write ? &aux_ram_[0x4000] : &ram_[0x4000]);
uint8_t *const ram = zero_state ? aux_ram_ : ram_;
uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data();
page(0x04, 0x08,
state.region_04_08.read ? &aux_ram_[0x0400] : &ram_[0x0400],
state.region_04_08.write ? &aux_ram_[0x0400] : &ram_[0x0400]);
// Which way the region here is mapped to be banks 1 and 2 is
// arbitrary.
page(0xd0, 0xe0,
language_state.read ? &ram[language_state.bank2 ? 0xd000 : 0xc000] : rom,
language_state.write ? nullptr : &ram[language_state.bank2 ? 0xd000 : 0xc000]);
page(0x20, 0x40,
state.region_20_40.read ? &aux_ram_[0x2000] : &ram_[0x2000],
state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]);
page(0xe0, 0x100,
language_state.read ? &ram[0xe000] : &rom[0x1000],
language_state.write ? nullptr : &ram[0xe000]);
}
if constexpr (bool(type & PagingType::CardArea)) {
const auto state = auxiliary_switches_.card_state();
page(0xc1, 0xc4, state.region_C1_C3 ? &rom_[0xc100 - 0xc100] : nullptr, nullptr);
read_pages_[0xc3] = state.region_C3 ? &rom_[0xc300 - 0xc100] : nullptr;
page(0xc4, 0xc8, state.region_C4_C8 ? &rom_[0xc400 - 0xc100] : nullptr, nullptr);
page(0xc8, 0xd0, state.region_C8_D0 ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
}
if constexpr (bool(type & PagingType::Main)) {
const auto state = auxiliary_switches_.main_state();
page(0x02, 0x04,
state.base.read ? &aux_ram_[0x0200] : &ram_[0x0200],
state.base.write ? &aux_ram_[0x0200] : &ram_[0x0200]);
page(0x08, 0x20,
state.base.read ? &aux_ram_[0x0800] : &ram_[0x0800],
state.base.write ? &aux_ram_[0x0800] : &ram_[0x0800]);
page(0x40, 0xc0,
state.base.read ? &aux_ram_[0x4000] : &ram_[0x4000],
state.base.write ? &aux_ram_[0x4000] : &ram_[0x4000]);
page(0x04, 0x08,
state.region_04_08.read ? &aux_ram_[0x0400] : &ram_[0x0400],
state.region_04_08.write ? &aux_ram_[0x0400] : &ram_[0x0400]);
page(0x20, 0x40,
state.region_20_40.read ? &aux_ram_[0x2000] : &ram_[0x2000],
state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]);
}
}
// MARK: - Keyboard and typing.
@ -485,8 +485,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
// Set up the default memory blocks. On a II or II+ these values will never change.
// On a IIe they'll be affected by selection of auxiliary RAM.
set_main_paging();
set_zero_page_paging();
set_paging<PagingType::Main | PagingType::ZeroPage>();
// Set the whole card area to initially backed by nothing.
page(0xc0, 0xd0, nullptr, nullptr);

View File

@ -9,6 +9,8 @@
#ifndef AuxiliaryMemorySwitches_h
#define AuxiliaryMemorySwitches_h
#include "MemorySwitches.hpp"
namespace Apple {
namespace II {
@ -235,7 +237,7 @@ template <typename Machine> class AuxiliaryMemorySwitches {
}
if(previous_state != main_state_) {
machine_.set_main_paging();
machine_.template set_paging<PagingType::Main>();
}
}
@ -258,14 +260,14 @@ template <typename Machine> class AuxiliaryMemorySwitches {
card_state_.region_C8_D0 = switches_.internal_CX_rom || switches_.internal_C8_rom;
if(previous_state != card_state_) {
machine_.set_card_paging();
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_.set_zero_page_paging();
machine_.template set_paging<PagingType::ZeroPage>();
}
};

View File

@ -9,6 +9,8 @@
#ifndef LanguageCardSwitches_h
#define LanguageCardSwitches_h
#include "MemorySwitches.hpp"
namespace Apple {
namespace II {
@ -70,7 +72,7 @@ template <typename Machine> class LanguageCardSwitches {
// Apply whatever the net effect of all that is to the memory map.
if(previous_state != state_) {
machine_.set_language_card_paging();
machine_.template set_paging<PagingType::LanguageCard>();
}
}
@ -90,7 +92,7 @@ template <typename Machine> class LanguageCardSwitches {
state_.bank2 = value & 0x04;
if(previous_state != state_) {
machine_.set_language_card_paging();
machine_.template set_paging<PagingType::LanguageCard>();
}
}

View File

@ -0,0 +1,25 @@
//
// MemorySwitches.hpp
// Clock Signal
//
// Created by Thomas Harte on 27/06/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef MemorySwitches_h
#define MemorySwitches_h
namespace Apple {
namespace II {
enum PagingType: int {
Main = 1 << 0,
ZeroPage = 1 << 1,
CardArea = 1 << 2,
LanguageCard = 1 << 3,
};
}
}
#endif /* MemorySwitches_h */

View File

@ -278,7 +278,7 @@ class ConcreteMachine:
// rom_[0x36403] = 0xab; // ECT_SEQ
// rom_[0x36404] = 0x64;
rom_[0xfc146f] = rom_[0xfc1470] = 0xea;
// rom_[0xfc146f] = rom_[0xfc1470] = 0xea;
size_t ram_size = 0;
switch(target.memory_model) {

View File

@ -20,6 +20,9 @@ namespace Apple {
namespace IIgs {
class MemoryMap {
private:
using PagingType = Apple::II::PagingType;
public:
// MARK: - Initial construction and configuration.
@ -179,7 +182,7 @@ class MemoryMap {
// TODO: set 1Mhz flags.
// Apply initial language/auxiliary state.
set_all_paging();
set_paging<~0>();
}
// MARK: - Live bus access notifications and register access.
@ -189,8 +192,7 @@ class MemoryMap {
shadow_register_ = value;
if(diff & 0x40) { // IO/language-card inhibit.
set_language_card_paging();
set_card_paging();
set_paging<PagingType::LanguageCard | PagingType::CardArea>();
}
if(diff & 0x3f) {
@ -251,150 +253,181 @@ class MemoryMap {
assert(region_map[end-1] == region_map[start]); \
assert(region_map[end] == region_map[end-1]+1);
// Cf. LanguageCardSwitches; this function should update the region from
// $D000 onwards as per the state of the language card flags — there may
// end up being ROM or RAM (or auxiliary RAM), and the first 4kb of it
// may be drawn from either of two pools.
void set_language_card_paging() {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
const bool inhibit_banks0001 = shadow_register_ & 0x40;
template <int type> void set_paging() {
// Update the region from
// $D000 onwards as per the state of the language card flags — there may
// end up being ROM or RAM (or auxiliary RAM), and the first 4kb of it
// may be drawn from either of two pools.
if constexpr (bool(type & (PagingType::LanguageCard | PagingType::ZeroPage | PagingType::Main))) {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
const auto main = auxiliary_switches_.main_state();
const bool inhibit_banks0001 = shadow_register_ & 0x40;
auto apply = [&language_state, this](uint32_t bank_base, uint8_t *ram) {
// This assumes bank 1 is the one before bank 2 when RAM is linear.
uint8_t *const d0_ram_bank = ram - (language_state.bank2 ? 0x0000 : 0x1000);
auto apply = [&language_state, this](uint32_t bank_base, uint8_t *ram) {
// This assumes bank 1 is the one before bank 2 when RAM is linear.
uint8_t *const d0_ram_bank = ram - (language_state.bank2 ? 0x0000 : 0x1000);
// Crib the ROM pointer from a page it's always visible on.
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xff'd000] - ((bank_base << 8) + 0xd000);
// Crib the ROM pointer from a page it's always visible on.
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xff'd000] - ((bank_base << 8) + 0xd000);
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = language_state.read ? d0_ram_bank : rom;
d0_region.write = language_state.write ? nullptr : d0_ram_bank;
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = language_state.read ? d0_ram_bank : rom;
d0_region.write = language_state.write ? nullptr : d0_ram_bank;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = language_state.read ? ram : rom;
e0_region.write = language_state.write ? nullptr : ram;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = language_state.read ? ram : rom;
e0_region.write = language_state.write ? nullptr : ram;
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
auto set_no_card = [this](uint32_t bank_base) {
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = ram_base;
d0_region.write = ram_base;
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
auto set_no_card = [this](uint32_t bank_base, uint8_t *read, uint8_t *write) {
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = read;
d0_region.write = write;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = ram_base;
e0_region.write = ram_base;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = read;
e0_region.write = write;
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
if(inhibit_banks0001) {
set_no_card(0x0000);
set_no_card(0x0100);
} else {
apply(0x0000, zero_state ? &ram_base[0x01'0000] : ram_base);
apply(0x0100, ram_base);
}
// The pointer stored in region_map[0xe000] has already been adjusted for
// the 0xe0'0000 addressing offset.
uint8_t *const e0_ram = regions[region_map[0xe000]].write;
apply(0xe000, e0_ram);
apply(0xe100, e0_ram);
}
// Cf. AuxiliarySwitches; this should establish whether ROM or card switches
// are exposed in the distinct regions C100C2FF, C300C3FF, C400C7FF and
// C800CFFF.
//
// On the IIgs it intersects with the current shadow register.
//
// TODO: so... shouldn't the card mask be incorporated here? I've got it implemented
// distinctly at present, but does that create any invalid state interactions?
void set_card_paging() {
const bool inhibit_banks0001 = shadow_register_ & 0x40;
const auto state = auxiliary_switches_.card_state();
auto apply = [&state, this](uint32_t bank_base) {
auto &c0_region = regions[region_map[bank_base | 0xc0]];
auto &c1_region = regions[region_map[bank_base | 0xc1]];
auto &c3_region = regions[region_map[bank_base | 0xc3]];
auto &c4_region = regions[region_map[bank_base | 0xc4]];
auto &c8_region = regions[region_map[bank_base | 0xc8]];
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xffc100] - ((bank_base << 8) + 0xc100);
// This is applied dynamically as it may be added or lost in banks $00 and $01.
c0_region.flags |= Region::IsIO;
#define apply_region(flag, region) \
if(flag) { \
region.read = rom; \
region.flags &= ~Region::IsIO; \
} else { \
region.flags |= Region::IsIO; \
if(inhibit_banks0001) {
set_no_card(0x0000,
main.base.read ? &ram_base[0x01'0000] : ram_base,
main.base.write ? &ram_base[0x01'0000] : ram_base);
set_no_card(0x0100, ram_base, ram_base);
} else {
apply(0x0000, zero_state ? &ram_base[0x01'0000] : ram_base);
apply(0x0100, ram_base);
}
apply_region(state.region_C1_C3, c1_region);
apply_region(state.region_C3, c3_region);
apply_region(state.region_C4_C8, c4_region);
apply_region(state.region_C8_D0, c8_region);
// The pointer stored in region_map[0xe000] has already been adjusted for
// the 0xe0'0000 addressing offset.
uint8_t *const e0_ram = regions[region_map[0xe000]].write;
apply(0xe000, e0_ram);
apply(0xe100, e0_ram);
}
// Establish whether main or auxiliary RAM
// is exposed in bank $00 for a bunch of regions.
if constexpr (type & PagingType::Main) {
const auto state = auxiliary_switches_.main_state();
#define set(page, flags) {\
auto &region = regions[region_map[page]]; \
region.read = flags.read ? &ram_base[0x01'0000] : ram_base; \
region.write = flags.write ? &ram_base[0x01'0000] : ram_base; \
}
// Base: $0200$03FF.
set(0x02, state.base);
assert_is_region(0x02, 0x04);
// Region $0400$07ff.
set(0x04, state.region_04_08);
assert_is_region(0x04, 0x08);
// Base: $0800$1FFF.
set(0x08, state.base);
assert_is_region(0x08, 0x20);
// Region $2000$3FFF.
set(0x20, state.region_20_40);
assert_is_region(0x20, 0x40);
// Base: $4000$BFFF.
set(0x40, state.base);
assert_is_region(0x40, 0xc0);
#undef set
}
// Update whether base or auxiliary RAM is visible in: (i) the zero
// and stack pages; and (ii) anywhere that the language card is exposing RAM instead of ROM.
if constexpr (bool(type & PagingType::ZeroPage)) {
// Affects bank $00 only, and should be a single region.
auto &region = regions[region_map[0]];
region.read = region.write = auxiliary_switches_.zero_state() ? &ram_base[0x01'0000] : ram_base;
assert(region_map[0x0000] == region_map[0x0001]);
assert(region_map[0x0001]+1 == region_map[0x0002]);
}
// Establish whether ROM or card switches are exposed in the distinct
// regions C100C2FF, C300C3FF, C400C7FF and C800CFFF.
//
// On the IIgs it intersects with the current shadow register.
if constexpr (bool(type & (PagingType::CardArea | PagingType::Main))) {
const bool inhibit_banks0001 = shadow_register_ & 0x40;
const auto state = auxiliary_switches_.card_state();
auto apply = [&state, this](uint32_t bank_base) {
auto &c0_region = regions[region_map[bank_base | 0xc0]];
auto &c1_region = regions[region_map[bank_base | 0xc1]];
auto &c3_region = regions[region_map[bank_base | 0xc3]];
auto &c4_region = regions[region_map[bank_base | 0xc4]];
auto &c8_region = regions[region_map[bank_base | 0xc8]];
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xffc100] - ((bank_base << 8) + 0xc100);
// This is applied dynamically as it may be added or lost in banks $00 and $01.
c0_region.flags |= Region::IsIO;
#define apply_region(flag, region) \
region.write = nullptr; \
if(flag) { \
region.read = rom; \
region.flags &= ~Region::IsIO; \
} else { \
region.flags |= Region::IsIO; \
}
apply_region(state.region_C1_C3, c1_region);
apply_region(state.region_C3, c3_region);
apply_region(state.region_C4_C8, c4_region);
apply_region(state.region_C8_D0, c8_region);
#undef apply_region
// Sanity checks.
assert(region_map[bank_base | 0xc1] == region_map[bank_base | 0xc0]+1);
assert(region_map[bank_base | 0xc2] == region_map[bank_base | 0xc1]);
assert(region_map[bank_base | 0xc3] == region_map[bank_base | 0xc2]+1);
assert(region_map[bank_base | 0xc4] == region_map[bank_base | 0xc3]+1);
assert(region_map[bank_base | 0xc7] == region_map[bank_base | 0xc4]);
assert(region_map[bank_base | 0xc8] == region_map[bank_base | 0xc7]+1);
assert(region_map[bank_base | 0xcf] == region_map[bank_base | 0xc8]);
assert(region_map[bank_base | 0xd0] == region_map[bank_base | 0xcf]+1);
};
// Sanity checks.
assert(region_map[bank_base | 0xc1] == region_map[bank_base | 0xc0]+1);
assert(region_map[bank_base | 0xc2] == region_map[bank_base | 0xc1]);
assert(region_map[bank_base | 0xc3] == region_map[bank_base | 0xc2]+1);
assert(region_map[bank_base | 0xc4] == region_map[bank_base | 0xc3]+1);
assert(region_map[bank_base | 0xc7] == region_map[bank_base | 0xc4]);
assert(region_map[bank_base | 0xc8] == region_map[bank_base | 0xc7]+1);
assert(region_map[bank_base | 0xcf] == region_map[bank_base | 0xc8]);
assert(region_map[bank_base | 0xd0] == region_map[bank_base | 0xcf]+1);
};
if(inhibit_banks0001) {
// Set no IO in the Cx00 range for banks $00 and $01, just
// regular RAM (or possibly auxiliary).
const auto auxiliary_state = auxiliary_switches_.main_state();
for(uint8_t region = region_map[0x00c0]; region < region_map[0x00d0]; region++) {
regions[region].read = auxiliary_state.base.read ? &ram_base[0x01'0000] : ram_base;
regions[region].write = auxiliary_state.base.write ? &ram_base[0x01'0000] : ram_base;
regions[region].flags &= ~Region::IsIO;
if(inhibit_banks0001) {
// Set no IO in the Cx00 range for banks $00 and $01, just
// regular RAM (or possibly auxiliary).
const auto auxiliary_state = auxiliary_switches_.main_state();
for(uint8_t region = region_map[0x00c0]; region < region_map[0x00d0]; region++) {
regions[region].read = auxiliary_state.base.read ? &ram_base[0x01'0000] : ram_base;
regions[region].write = auxiliary_state.base.write ? &ram_base[0x01'0000] : ram_base;
regions[region].flags &= ~Region::IsIO;
}
for(uint8_t region = region_map[0x01c0]; region < region_map[0x01d0]; region++) {
regions[region].read = regions[region].write = ram_base;
regions[region].flags &= ~Region::IsIO;
}
} else {
// Obey the card state for banks $00 and $01.
apply(0x0000);
apply(0x0100);
}
for(uint8_t region = region_map[0x01c0]; region < region_map[0x01d0]; region++) {
regions[region].read = regions[region].write = ram_base;
regions[region].flags &= ~Region::IsIO;
}
} else {
// Obey the card state for banks $00 and $01.
apply(0x0000);
apply(0x0100);
// Obey the card state for banks $e0 and $e1.
apply(0xe000);
apply(0xe100);
}
// Obey the card state for banks $e0 and $e1.
apply(0xe000);
apply(0xe100);
}
// Cf. LanguageCardSwitches; this should update whether base or auxiliary RAM is
// visible in: (i) the zero and stack pages; and (ii) anywhere that the language
// card is exposing RAM instead of ROM.
void set_zero_page_paging() {
// Affects bank $00 only, and should be a single region.
auto &region = regions[region_map[0]];
region.read = region.write = auxiliary_switches_.zero_state() ? &ram_base[0x01'0000] : ram_base;
assert(region_map[0x0000] == region_map[0x0001]);
assert(region_map[0x0001]+1 == region_map[0x0002]);
// Switching to or from auxiliary RAM potentially affects the
// language card area.
set_language_card_paging();
}
// IIgs specific: sets or resets the ::IsShadowed flag across affected banks as
@ -423,18 +456,27 @@ class MemoryMap {
// $6000$a000 Odd banks only, rest of Super High-res
// [plus IO and language card space, subject to your definition of shadowing]
constexpr int shadow_shift = 10;
constexpr int auxiliary_offset = 0x10000 >> shadow_shift;
static constexpr int shadow_shift = 10;
static constexpr int auxiliary_offset = 0x1'0000 >> shadow_shift;
enum Inhibit {
TextPage1 = 0x01,
HighRes1 = 0x02,
HighRes2 = 0x04,
SuperHighRes = 0x08,
AuxiliaryHighRes = 0x10,
TextPage2 = 0x20,
};
// Text Page 1, main and auxiliary — $0400$0800.
for(size_t c = 0x0400 >> shadow_shift; c < 0x0800 >> shadow_shift; c++) {
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x01);
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & Inhibit::TextPage1);
}
// Text Page 2, main and auxiliary — 0x08000x0c00.
// TODO: on a ROM03 machine only.
for(size_t c = 0x0800 >> shadow_shift; c < 0x0c00 >> shadow_shift; c++) {
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x20);
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & Inhibit::TextPage2);
}
// Hi-res graphics Page 1, main and auxiliary — $2000$4000;
@ -447,8 +489,11 @@ class MemoryMap {
// (high-res graphics inhibit or auxiliary high res graphics inhibit) _and_ (super high-res inhibit).
//
for(size_t c = 0x2000 >> shadow_shift; c < 0x4000 >> shadow_shift; c++) {
shadow_pages[c] = !(shadow_register_ & 0x02);
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x12);
shadow_pages[c] = !(shadow_register_ & Inhibit::HighRes1);
shadow_pages[c+auxiliary_offset] = !(
shadow_register_ & (Inhibit::HighRes1 | Inhibit::AuxiliaryHighRes) &&
shadow_register_ & Inhibit::SuperHighRes
);
}
// Hi-res graphics Page 2, main and auxiliary — $4000$6000;
@ -456,62 +501,23 @@ class MemoryMap {
//
// Test applied: much like that for page 1.
for(size_t c = 0x4000 >> shadow_shift; c < 0x6000 >> shadow_shift; c++) {
shadow_pages[c] = !(shadow_register_ & 0x04);
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x14);
shadow_pages[c] = !(shadow_register_ & Inhibit::HighRes2);
shadow_pages[c+auxiliary_offset] = !(
shadow_register_ & (Inhibit::HighRes2 | Inhibit::AuxiliaryHighRes) &&
shadow_register_ & Inhibit::SuperHighRes
);
}
// Residue of Super Hi-Res — $6000$a000 (odd pages only).
//
// Test applied:
// auxiliary high res graphics inhibit and super high-res inhibit
for(size_t c = 0x6000 >> shadow_shift; c < 0xa000 >> shadow_shift; c++) {
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x08);
shadow_pages[c+auxiliary_offset] =
!(shadow_register_ & Inhibit::SuperHighRes && shadow_register_ & Inhibit::AuxiliaryHighRes);
}
}
// Cf. the AuxiliarySwitches; establishes whether main or auxiliary RAM
// is exposed in bank $00 for a bunch of regions.
void set_main_paging() {
const auto state = auxiliary_switches_.main_state();
#define set(page, flags) {\
auto &region = regions[region_map[page]]; \
region.read = flags.read ? &ram_base[0x01'0000] : ram_base; \
region.write = flags.write ? &ram_base[0x01'0000] : ram_base; \
}
// Base: $0200$03FF.
set(0x02, state.base);
assert_is_region(0x02, 0x04);
// Region $0400$07ff.
set(0x04, state.region_04_08);
assert_is_region(0x04, 0x08);
// Base: $0800$1FFF.
set(0x08, state.base);
assert_is_region(0x08, 0x20);
// Region $2000$3FFF.
set(0x20, state.region_20_40);
assert_is_region(0x20, 0x40);
// Base: $4000$BFFF.
set(0x40, state.base);
assert_is_region(0x40, 0xc0);
#undef set
// This also affects shadowing flags, if shadowing is enabled at all,
// and might affect RAM in the IO area of bank $00 because the language
// card can be inhibited on a IIgs.
set_card_paging();
}
void set_all_paging() {
set_card_paging();
set_zero_page_paging(); // ... which calls set_language_card_paging().
set_main_paging();
set_shadowing();
}
void print_state() {
uint8_t region = region_map[0];
uint32_t start = 0;
@ -559,7 +565,7 @@ class MemoryMap {
//
// Shadow_banks: divides the whole 16mb of memory into 128kb chunks and includes a flag to indicate whether
// each is a potential source of shadowing.
std::bitset<128> shadow_pages, shadow_banks;
std::bitset<128> shadow_pages{}, shadow_banks{};
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
@ -570,8 +576,28 @@ class MemoryMap {
// would be less efficient. Verify that?
#define MemoryMapRegion(map, address) map.regions[map.region_map[address >> 8]]
#define IsShadowed(map, region, address) (map.shadow_pages[((&region.write[address] - map.ram_base) >> 10) & 127] & map.shadow_banks[address >> 17])
#define MemoryMapRead(region, address, value) *value = region.read ? region.read[address] : 0xff
// The below encapsulates the fact that I've yet to determine whether Apple intends to
// indicate that logical addresses (i.e. those prior to being mapped per the current paging)
// or physical addresses (i.e. after mapping) are subject to shadowing.
#ifdef SHADOW_LOGICAL
#define IsShadowed(map, region, address) \
(map.shadow_pages[((&region.write[address] - map.ram_base) >> 10) & 127] & map.shadow_banks[address >> 17])
#define MemoryMapWrite(map, region, address, value) \
if(region.write) { \
region.write[address] = *value; \
const bool _mm_is_shadowed = IsShadowed(map, region, address); \
map.shadow_base[_mm_is_shadowed][address & map.shadow_mask[_mm_is_shadowed]] = *value; \
}
#else
#define IsShadowed(map, region, address) \
(map.shadow_pages[(address >> 10) & 127] & map.shadow_banks[address >> 17])
#define MemoryMapWrite(map, region, address, value) \
if(region.write) { \
region.write[address] = *value; \
@ -579,6 +605,8 @@ class MemoryMap {
map.shadow_base[_mm_is_shadowed][(&region.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \
}
#endif
// Quick notes on ::IsShadowed contortions:
//
// The objective is to support shadowing:

View File

@ -946,6 +946,7 @@
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
4BBB70A8202014E2002FE009 /* MultiProducer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiProducer.cpp */; };
4BBB70A9202014E2002FE009 /* MultiProducer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiProducer.cpp */; };
4BBB77DD2867EBB300D335A1 /* IIgs Memory Map in Resources */ = {isa = PBXBuildFile; fileRef = 4BBB77DC2867EBB300D335A1 /* IIgs Memory Map */; };
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; };
4BBF49AF1ED2880200AB3669 /* FUSETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF49AE1ED2880200AB3669 /* FUSETests.swift */; };
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
@ -2009,6 +2010,7 @@
4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; };
4BBB70A6202014E2002FE009 /* MultiProducer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiProducer.cpp; sourceTree = "<group>"; };
4BBB70A7202014E2002FE009 /* MultiProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiProducer.hpp; sourceTree = "<group>"; };
4BBB77DC2867EBB300D335A1 /* IIgs Memory Map */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "IIgs Memory Map"; sourceTree = "<group>"; };
4BBC951C1F368D83008F4C34 /* i8272.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = i8272.cpp; sourceTree = "<group>"; };
4BBC951D1F368D83008F4C34 /* i8272.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = i8272.hpp; sourceTree = "<group>"; };
4BBF49AE1ED2880200AB3669 /* FUSETests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FUSETests.swift; sourceTree = "<group>"; };
@ -2135,6 +2137,7 @@
4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; };
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
4BE0151C286A8C8E00EA42E9 /* MemorySwitches.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemorySwitches.hpp; sourceTree = "<group>"; };
4BE0A3EC237BB170002AB46F /* ST.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ST.cpp; sourceTree = "<group>"; };
4BE0A3ED237BB170002AB46F /* ST.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ST.hpp; sourceTree = "<group>"; };
4BE211DD253E4E4800435408 /* 65C02_no_Rockwell_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_no_Rockwell_test.bin; path = "Klaus Dormann/65C02_no_Rockwell_test.bin"; sourceTree = "<group>"; };
@ -2462,6 +2465,7 @@
4B1414631B588A1100E04248 /* Test Binaries */ = {
isa = PBXGroup;
children = (
4BBB77DC2867EBB300D335A1 /* IIgs Memory Map */,
4B7C7A06282C3DED002D6C0B /* flamewing 68000 BCD tests */,
4B680CE323A555CA00451D43 /* 68000 Comparative Tests */,
4B75F97A280D7C7700121055 /* 68000 Decoding */,
@ -4625,6 +4629,7 @@
4BCE004C227CE8CA000CA200 /* DiskIICard.hpp */,
4B2E86E125DC95150024F1E9 /* Joystick.hpp */,
4BF40A5525424C770033EA39 /* LanguageCardSwitches.hpp */,
4BE0151C286A8C8E00EA42E9 /* MemorySwitches.hpp */,
4BCE004F227CE8CA000CA200 /* Video.hpp */,
4B8DF4F2254E141700F3433C /* VideoSwitches.hpp */,
);
@ -5401,6 +5406,7 @@
4B8DF62F2550D91600F3433C /* CPUCMP-trace_compare.log in Resources */,
4B8DF6412550D91600F3433C /* CPUROR.sfc in Resources */,
4BB299691B587D8400A49093 /* irq in Resources */,
4BBB77DD2867EBB300D335A1 /* IIgs Memory Map in Resources */,
4B8DF6962550D91700F3433C /* CPUJMP.sfc in Resources */,
4BB299851B587D8400A49093 /* ldyzx in Resources */,
4BB299F31B587D8400A49093 /* trap7 in Resources */,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,96 @@
# Apple IIgs Memory Map Tests
## Sample test:
{
"hires": false,
"lcw": false,
"80store": true,
"shadow": 12,
"state": 183,
"read": [
[0, 192, 256, 448],
[193, 208, 65473, 65488],
[209, 256, 465, 512],
[257, 448, 0, 0],
[449, 464, 65473, 65488],
[465, 512, 465, 512],
[513, 57537, 0, 0],
[57538, 57552, 65474, 65488],
[57553, 57600, 57553, 57600],
[57601, 57793, 0, 0],
[57794, 57808, 65474, 65488],
[57809, 57856, 57809, 57856],
[57857, 65536, 0, 0]
],
"write": [
[0, 192, 256, 448],
[193, 256, 65473, 65536],
[257, 448, 0, 0],
[449, 512, 65473, 65536],
[513, 57537, 0, 0],
[57538, 57600, 65474, 65536],
[57601, 57793, 0, 0],
[57794, 57856, 65474, 65536],
[57857, 65536, 0, 0]
],
"shadowed": [4, 12, 32, 64, 260, 268, 288, 320, 352, 416, 65535],
"io": [192, 193, 448, 449, 57536, 57537, 57792, 57793, 65535]
}
## Application
Perform, in the order listed:
"hires": false,
If `hires` is true, access IO address `0x57`; otherwise access IO address `0x56`.
"lcw": false,
If `lcw` is true, write any value to IO address `0x81` twice; otherwise write to IO address `0x80` at least once.
"80store": true,
If `80store` is true, access IO address `0x01`; otherwise access IO address `0x00`.
"shadow": 12,
Store the value of `shadow` to IO address `0x35`.
"state": 183,
Store the value of `state` to IO address `0x68`.
## Test
**Only memory areas which are subject to paging are recorded with valid physical addresses.**
### `read` and `write`
Each entry looks like:
[0, 192, 256, 448]
Which is of the form:
[logical start, logical end, physical start, physical end]
Where all numbers are page numbers, i.e. address / 256.
So e.g. the entry above means that between logical addresses `$00:0000` and `$00:C000` you should find the physical RAM located between addresses `$01:0000` and `$01:C0000`.
If physical end == physical start then the same destination is used for all logical addresses; if that destination is `0` then the area is unmapped.
### `shadowed ` and `io`
An example chain looks like:
[4, 12, 32, 64, 260, 268, 288, 320, 352, 416, 65535]
Starting from a default value of `false`, that means:
* memory remained un-[shadowed/IO] for until page 4 (i.e. pages 03);
* it was then marked as [shadowed/IO] until page 12 (i.e. pages 411);
* it was then un-[shadowed/IO] to page 32;
* ...etc.

View File

@ -89,8 +89,11 @@ namespace {
test_bank(0xe1'0000);
}
// TODO: edit and reenable shadowing tests below, once I clear up whether shadowing is based on
// logical or physical address.
/// Tests that writes to $00:$0400 and to $01:$0400 are subsequently visible at $e0:$0400 and $e1:$0400.
- (void)testShadowing {
/*- (void)testShadowing {
[self write:0xab address:0x00'0400];
[self write:0xcd address:0x01'0400];
XCTAssertEqual([self readAddress:0xe0'0400], 0xab);
@ -123,7 +126,7 @@ namespace {
XCTAssertEqual([self readAddress:0xe1'0400], 0xcb);
XCTAssertEqual([self readAddress:0x00'0400], 0xde);
XCTAssertEqual([self readAddress:0x01'0400], 0xde);
}
}*/
- (void)testE0E1RAMConsistent {
// Do some random language card paging, to hit set_language_card.
@ -228,4 +231,180 @@ namespace {
}
}
- (void)testJSONExamples {
NSArray<NSDictionary *> *const tests =
[NSJSONSerialization JSONObjectWithData:
[NSData dataWithContentsOfURL:
[[NSBundle bundleForClass:[self class]]
URLForResource:@"mm"
withExtension:@"json"
subdirectory:@"IIgs Memory Map"]]
options:0
error:nil];
[tests enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull test, NSUInteger index, BOOL * _Nonnull stop) {
NSLog(@"Test index %lu", static_cast<unsigned long>(index));
// Apply state.
const bool highRes = [test[@"hires"] boolValue];
const bool lcw = [test[@"lcw"] boolValue];
const bool store80 = [test[@"80store"] boolValue];
const uint8_t shadow = [test[@"shadow"] integerValue];
const uint8_t state = [test[@"state"] integerValue];
_memoryMap.access(0xc056 + highRes, false);
_memoryMap.access(0xc080 + lcw, true);
_memoryMap.access(0xc080 + lcw, true);
_memoryMap.access(0xc000 + store80, false);
_memoryMap.set_shadow_register(shadow);
_memoryMap.set_state_register(state);
// Test results.
auto testMemory =
^(NSString *type, void (^ applyTest)(int logical, int physical, const MemoryMap::Region &region)) {
for(NSArray<NSNumber *> *region in test[type]) {
const auto logicalStart = [region[0] intValue];
const auto logicalEnd = [region[1] intValue];
const auto physicalStart = [region[2] intValue];
const auto physicalEnd = [region[3] intValue];
if(physicalEnd == physicalStart && physicalStart == 0) {
continue;
}
int physical = physicalStart;
for(int logical = logicalStart; logical < logicalEnd; logical++) {
const auto &region = self->_memoryMap.regions[self->_memoryMap.region_map[logical]];
// Don't worry about IO pages here; they'll be compared shortly.
if(!(region.flags & MemoryMap::Region::IsIO)) {
const auto &region = self->_memoryMap.regions[self->_memoryMap.region_map[logical]];
applyTest(logical, physical, region);
if(*stop) {
NSLog(@"Logical page %04x should be mapped to %@ physical %04x",
logical,
type,
physical);
NSLog(@"Stopping after first failure");
return;
}
}
if(physical != physicalEnd) ++physical;
}
}
};
auto physicalOffset =
^(const uint8_t *pointer) {
// Check for a mapping to RAM.
if(pointer >= self->_ram.data() && pointer < &(*self->_ram.end())) {
int foundPhysical = int(pointer - self->_ram.data()) >> 8;
// This emulator maps a contiguous 8mb + 128kb of RAM such that the
// first 8mb resides up to physical location 0x8000, and the final
// 128kb sits from locatio 0xe000. So adjust for that here.
if(foundPhysical >= 0x8000) {
foundPhysical += 0xe000 - 0x8000;
}
return foundPhysical;
}
// Check for a mapping to ROM.
if(pointer >= self->_rom.data() && pointer < &(*self->_rom.end())) {
// This emulator uses a separate store for ROM, which sholud appear in
// the memory map from locatio 0xfc00.
return 0xfc00 + (int(pointer - self->_rom.data()) >> 8);
}
return -1;
};
// Test read pointers.
testMemory(@"read", ^(int logical, int physical, const MemoryMap::Region &region) {
XCTAssert(region.read != nullptr);
if(region.read == nullptr) {
*stop = YES;
return;
}
// Compare to correct value.
const int foundPhysical = physicalOffset(&region.read[logical << 8]);
if(physical != foundPhysical) {
*stop = YES;
return;
}
});
// Test write pointers.
testMemory(@"write", ^(int logical, int physical, const MemoryMap::Region &region) {
// This emulator guards writes to ROM by setting those pointers to nullptr;
// so allow a nullptr write target if ROM is mapped here.
if(region.write == nullptr && physical >= 0xfc00) {
return;
}
XCTAssert(region.write != nullptr);
if(region.write == nullptr) {
*stop = YES;
return;
}
// Compare to correct value.
const int foundPhysical = physicalOffset(&region.write[logical << 8]);
if(physical != foundPhysical) {
*stop = YES;
return;
}
});
// Test shadowed regions.
bool shouldBeShadowed = false;
int logical = 0;
for(NSNumber *next in test[@"shadowed"]) {
while(logical < [next intValue]) {
[[maybe_unused]] const auto &region =
self->_memoryMap.regions[self->_memoryMap.region_map[logical]];
const bool isShadowed =
IsShadowed(_memoryMap, region, (logical << 8));
XCTAssertEqual(
isShadowed,
shouldBeShadowed,
@"Logical page %04x %@ subject to shadowing", logical, shouldBeShadowed ? @"should be" : @"should not be");
++logical;
}
shouldBeShadowed ^= true;
}
// Test IO regions.
bool shouldBeIO = false;
logical = 0;
for(NSNumber *next in test[@"io"]) {
while(logical < [next intValue]) {
const auto &region =
self->_memoryMap.regions[self->_memoryMap.region_map[logical]];
// This emulator marks card pages as IO because it uses IO to mean
// "anything that isn't the built-in RAM". Just don't test card pages.
const bool isIO =
region.flags & MemoryMap::Region::IsIO &&
(((logical & 0xff) < 0xc1) || ((logical & 0xff) > 0xcf));
XCTAssertEqual(
isIO,
shouldBeIO,
@"Logical page %04x %@ marked as IO", logical, shouldBeIO ? @"should be" : @"should not be");
++logical;
}
shouldBeIO ^= true;
}
}];
}
@end

View File

@ -29,6 +29,8 @@ template <Type type, bool has_cias> class ConcreteAllRAMProcessor:
public AllRAMProcessor, public CPU::MOS6502Esque::BusHandlerT<type>
{
public:
using typename CPU::MOS6502Esque::BusHandlerT<type>::AddressType;
ConcreteAllRAMProcessor(size_t memory_size) :
AllRAMProcessor(memory_size),
mos6502_(*this),
@ -37,7 +39,7 @@ template <Type type, bool has_cias> class ConcreteAllRAMProcessor:
mos6502_.set_power_on(false);
}
inline Cycles perform_bus_operation(BusOperation operation, uint32_t address, uint8_t *value) {
Cycles perform_bus_operation(BusOperation operation, AddressType address, uint8_t *value) {
timestamp_ += Cycles(1);
if constexpr (has_cias) {

View File

@ -9,7 +9,7 @@
template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler, uses_ready_line>::run_for(const Cycles cycles) {
#define perform_bus(address, value, operation) \
bus_address_ = address & 0xff'ffff; \
bus_address_ = (address) & 0xff'ffff; \
bus_value_ = value; \
bus_operation_ = operation