mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-28 06:49:39 +00:00
164 lines
5.4 KiB
C++
164 lines
5.4 KiB
C++
//
|
|
// MemoryMap.hpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 25/10/2020.
|
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#include <array>
|
|
#include <bitset>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
#include "../AppleII/LanguageCardSwitches.hpp"
|
|
#include "../AppleII/AuxiliaryMemorySwitches.hpp"
|
|
|
|
namespace Apple::IIgs {
|
|
|
|
class MemoryMap {
|
|
public:
|
|
// MARK: - Initial construction and configuration.
|
|
|
|
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);
|
|
|
|
// MARK: - Live bus access notifications and register access.
|
|
|
|
void set_shadow_register(uint8_t value);
|
|
uint8_t get_shadow_register() const;
|
|
|
|
void set_speed_register(uint8_t value);
|
|
|
|
void set_state_register(uint8_t value);
|
|
uint8_t get_state_register() const;
|
|
|
|
void access(uint16_t address, bool is_read);
|
|
|
|
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_;
|
|
}
|
|
|
|
// MARK: - Accessors for reading and writing RAM.
|
|
|
|
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.
|
|
};
|
|
};
|
|
|
|
const Region ®ion(uint32_t address) const { return regions_[region_map_[address >> 8]]; }
|
|
uint8_t read(const Region ®ion, uint32_t address) const {
|
|
return region.read ? region.read[address] : 0xff;
|
|
}
|
|
|
|
bool is_shadowed(const Region ®ion, 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 ®ion, 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;
|
|
}
|
|
|
|
// 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.
|
|
|
|
private:
|
|
AuxiliaryMemorySwitches auxiliary_switches_;
|
|
LanguageCardSwitches language_card_;
|
|
friend AuxiliaryMemorySwitches;
|
|
friend LanguageCardSwitches;
|
|
|
|
uint8_t shadow_register_ = 0x00;
|
|
uint8_t speed_register_ = 0x00;
|
|
|
|
// MARK: - Banking.
|
|
|
|
void assert_is_region(uint8_t start, uint8_t end);
|
|
template <int type> void set_paging();
|
|
|
|
uint8_t *ram_base_ = nullptr;
|
|
|
|
// 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.
|
|
|
|
std::size_t physical_address(const Region ®ion, uint32_t address) const {
|
|
return std::size_t(®ion.write[address] - ram_base_);
|
|
}
|
|
|
|
// MARK: - Shadowing
|
|
|
|
// 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();
|
|
|
|
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_{};
|
|
};
|
|
|
|
}
|