mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Merge pull request #1055 from TomHarte/IIgsMemoryMap
Introduce further IIgs memory map tests.
This commit is contained in:
commit
1e149d0add
@ -183,13 +183,25 @@ 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() {
|
||||
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];
|
||||
}
|
||||
|
||||
if constexpr (bool(type & (PagingType::LanguageCard | PagingType::ZeroPage))) {
|
||||
const auto language_state = language_card_.state();
|
||||
const auto zero_state = auxiliary_switches_.zero_state();
|
||||
|
||||
@ -207,8 +219,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
language_state.write ? nullptr : &ram[0xe000]);
|
||||
}
|
||||
|
||||
// MARK: Auxiliary memory and the other IIe improvements.
|
||||
void set_card_paging() {
|
||||
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);
|
||||
@ -216,20 +227,8 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
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_;
|
||||
}
|
||||
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() {
|
||||
if constexpr (bool(type & PagingType::Main)) {
|
||||
const auto state = auxiliary_switches_.main_state();
|
||||
|
||||
page(0x02, 0x04,
|
||||
@ -250,6 +249,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
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);
|
||||
|
@ -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>();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
|
25
Machines/Apple/AppleII/MemorySwitches.hpp
Normal file
25
Machines/Apple/AppleII/MemorySwitches.hpp
Normal 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 */
|
@ -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) {
|
||||
|
@ -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,13 +253,15 @@ 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
|
||||
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.
|
||||
void set_language_card_paging() {
|
||||
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) {
|
||||
@ -279,14 +283,14 @@ class MemoryMap {
|
||||
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 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 = ram_base;
|
||||
d0_region.write = ram_base;
|
||||
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;
|
||||
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]);
|
||||
@ -294,8 +298,10 @@ class MemoryMap {
|
||||
};
|
||||
|
||||
if(inhibit_banks0001) {
|
||||
set_no_card(0x0000);
|
||||
set_no_card(0x0100);
|
||||
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);
|
||||
@ -308,15 +314,55 @@ class MemoryMap {
|
||||
apply(0xe100, e0_ram);
|
||||
}
|
||||
|
||||
// Cf. AuxiliarySwitches; this should establish whether ROM or card switches
|
||||
// are exposed in the distinct regions C100–C2FF, C300–C3FF, C400–C7FF and
|
||||
// C800–CFFF.
|
||||
// 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 ®ion = 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 ®ion = 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 C100–C2FF, C300–C3FF, C400–C7FF and C800–CFFF.
|
||||
//
|
||||
// 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() {
|
||||
if constexpr (bool(type & (PagingType::CardArea | PagingType::Main))) {
|
||||
const bool inhibit_banks0001 = shadow_register_ & 0x40;
|
||||
const auto state = auxiliary_switches_.card_state();
|
||||
|
||||
@ -333,6 +379,7 @@ class MemoryMap {
|
||||
c0_region.flags |= Region::IsIO;
|
||||
|
||||
#define apply_region(flag, region) \
|
||||
region.write = nullptr; \
|
||||
if(flag) { \
|
||||
region.read = rom; \
|
||||
region.flags &= ~Region::IsIO; \
|
||||
@ -381,20 +428,6 @@ class MemoryMap {
|
||||
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 ®ion = 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 — 0x0800–0x0c00.
|
||||
// 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 ®ion = 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[((®ion.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[((®ion.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][(®ion.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Quick notes on ::IsShadowed contortions:
|
||||
//
|
||||
// The objective is to support shadowing:
|
||||
|
@ -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 */,
|
||||
|
10000
OSBindings/Mac/Clock SignalTests/IIgs Memory Map/mm.json
Normal file
10000
OSBindings/Mac/Clock SignalTests/IIgs Memory Map/mm.json
Normal file
File diff suppressed because it is too large
Load Diff
96
OSBindings/Mac/Clock SignalTests/IIgs Memory Map/readme.md
Normal file
96
OSBindings/Mac/Clock SignalTests/IIgs Memory Map/readme.md
Normal 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 0–3);
|
||||
* it was then marked as [shadowed/IO] until page 12 (i.e. pages 4–11);
|
||||
* it was then un-[shadowed/IO] to page 32;
|
||||
* ...etc.
|
@ -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 ®ion)) {
|
||||
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 ®ion = 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 ®ion = 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 ®ion) {
|
||||
XCTAssert(region.read != nullptr);
|
||||
if(region.read == nullptr) {
|
||||
*stop = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare to correct value.
|
||||
const int foundPhysical = physicalOffset(®ion.read[logical << 8]);
|
||||
if(physical != foundPhysical) {
|
||||
*stop = YES;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Test write pointers.
|
||||
testMemory(@"write", ^(int logical, int physical, const MemoryMap::Region ®ion) {
|
||||
// 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(®ion.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 ®ion =
|
||||
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 ®ion =
|
||||
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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user