Files
CLK/Machines/Thomson/MO/MemoryMap.hpp

416 lines
9.8 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// MemoryMap.hpp
// Clock Signal
//
// Created by Thomas Harte on 08/04/2026.
// Copyright © 2026 Thomas Harte. All rights reserved.
//
#pragma once
#include <algorithm>
#include <array>
#include <cassert>
#include <iterator>
#include <vector>
#include "Machines/Utility/MemoryFuzzer.hpp"
#include "Outputs/Log.hpp"
#include "Processors/6809/6809.hpp"
namespace Thomson {
struct MemoryAccess {
template <typename AddressT>
uint8_t read(const AddressT address) const {
if(read_[address >> 12]) [[likely]] {
return read_[address >> 12][address];
} else {
return 0xff;
}
}
template <typename AddressT>
void write(const AddressT address, const uint8_t value) {
if(write_[address >> 12]) {
write_[address >> 12][address] = value;
} else {
Log::Logger<Log::Source::MO5>::info().append("Unmapped write of %02x to %04x", value, +address);
}
}
// Defined to work correctly only for in-RAM addresses.
uint8_t &operator[] (const size_t index) {
return write_[index >> 12][index];
}
// Works correctly across the board, but is read-only.
struct ConstIterator {
using self_type = ConstIterator;
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = uint8_t;
using pointer = uint8_t *;
using reference = uint8_t &;
uint8_t operator *() {
return underlying_.read(uint16_t(address_));
}
ConstIterator operator++(int) {
ConstIterator prior = *this;
++address_;
return prior;
}
ConstIterator &operator++() {
++address_;
return *this;
}
auto operator <=>(const ConstIterator &rhs) const {
return address_ <=> rhs.address_;
}
private:
ConstIterator(const MemoryAccess &underlying, int address) : underlying_(underlying), address_(address) {}
const MemoryAccess &underlying_;
int address_;
friend MemoryAccess;
};
ConstIterator begin() const {
return ConstIterator(*this, 0);
}
ConstIterator end() const {
return ConstIterator(*this, 0x1'0000);
}
ConstIterator iterator(const uint16_t address) const {
return ConstIterator(*this, address);
}
protected:
void set_read(const size_t slot, const uint8_t *const pointer) {
read_[slot] = pointer ? pointer - (slot << 12) : nullptr;
write_[slot] = nullptr;
}
void set_readwrite(const size_t slot, uint8_t *const pointer) {
read_[slot] = write_[slot] = pointer ? pointer - (slot << 12) : nullptr;
}
uint8_t *write_[0x10]{};
const uint8_t *read_[0x10]{};
};
namespace MO {
enum class AccessMode {
System = 0,
LightPen = 1,
};
enum class GraphicsModeAccess {
Authorised = 0,
Inhibited = 1,
};
template <bool is_mo6>
struct MemoryMap: public MemoryAccess {
public:
MemoryMap() {
// Install RAM.
page_video(false);
// Put fixed RAM into memory.
for(size_t c = 0x0; c < 0x4; c++) {
set_readwrite(c + 0x2, ram_.data() + (c << 12));
}
update_commutable_ram();
// Set RAM to an undefined state.
Memory::Fuzz(ram_);
Memory::Fuzz(video_);
}
void reset() {
page_video(false);
cartridge_page_ = 0;
cartridge_page_ = 0;
rom_page_ = 0;
ram_page_ = 1;
basic_selection_ = false;
b000page_ = B000Page::Empty;
access_mode_ = AccessMode::LightPen;
update_commutable_ram();
update_commutable_rom();
}
// MARK: - ROM installation and connection for video.
void set_rom(const std::vector<uint8_t> &rom) {
//
// MO5 ROM images are 16kb in total, representing what should go into the final 16kb of the memory map.
//
// MO6 ROM images are 64kb in total:
//
// * the first 32kb is BASIC 1: two 16kb pages that might appear from 0xc000, including the monitor;
// * the next 32kb is BASIC 128: two 16kb pages that might appear from 0xb000 or might not appear at all.
//
rom_ = rom;
update_commutable_rom();
}
void set_cartridge(const std::vector<uint8_t> &cartridge) {
cartridge_ = cartridge;
if constexpr (!is_mo6) {
b000page_ = B000Page::Cartridge;
rom_.clear();
}
update_commutable_rom();
}
void set_floppy_rom(const std::vector<uint8_t> &rom) {
std::copy_n(rom.begin(), std::min(rom.size(), floppy_rom_.size()), floppy_rom_.begin());
set_read(0xa, floppy_rom_.data());
}
uint8_t *video(const bool pixels) {
return &video_[pixels ? 0x2000 : 0x0000];
}
// MARK: - Paging.
void page_video(const bool pixels) {
uint8_t *const base = video(pixels);
set_readwrite(0x0, base);
set_readwrite(0x1, base + 0x1000);
}
void page_monitor(const bool page) {
if(!is_mo6) {
return;
}
if(rom_page_ != page) {
rom_page_ = page;
update_commutable_rom();
}
}
bool visible_monitor_page() const {
return rom_page_;
}
// MARK: - Read override.
template <typename AddressT>
uint8_t read(const AddressT address) {
// Some cartridges use reads in the range [BFFC, BFFF] to switch banks.
if(
b000page_ == B000Page::Cartridge &&
address >= 0xbffc &&
address <= 0xbfff &&
cartridge_.size() > 16384
) [[unlikely]] {
cartridge_page_ = address & 3;
update_commutable_rom();
}
if(read_[address >> 12]) [[likely]] {
return read_[address >> 12][address];
} else {
return 0xff;
}
}
using MemoryAccess::write;
// MARK: - Register Access.
template <uint16_t address>
uint8_t read() {
switch(address) {
default:
Log::Logger<Log::Source::MO5>::info().append("Unhandled read from %04x", address);
break;
case 0xa7e4:
if(access_mode_ == AccessMode::System) {
// TODO: b6, b7: displayed RAM page number;
return
((b000page_ == B000Page::Cartridge) ? 0x00 : 0x20) |
(basic_selection_ ? 0x10 : 0x00);
}
break;
case 0xa7e5:
if(access_mode_ == AccessMode::System) {
return uint8_t(ram_page_);
}
break;
case 0xa7e6:
// This is possibly cartridge paging???
if(access_mode_ == AccessMode::System) {
return 0;
}
break;
case 0xa7e7:
return 0xfe | uint8_t(access_mode_); // Other bits come from video.
}
return 0xff;
}
template <uint16_t address>
void write(const uint8_t value) {
switch(address) {
default:
Log::Logger<Log::Source::MO5>::info().append("Unhandled write of %02x to %04x", value, address);
break;
case 0xa7dd:
// "The GATE MODE PAGE (IW18) HANDLES:
// - selection between the BASIC 1 [(IW01) EPROM 27256] or the BASIC 128
// [(IW02) EPROM 27256]. This is set on bit D4 of A7DD.
// - Masking or unmasking of optional ROM cartridges through bit D5 of A7DD.
// (refer to GATE MODE PAGE REGISTERS (page: 22)).
basic_selection_ = value & 0x10;
if(!(value & 0x20)) {
b000page_ = B000Page::Cartridge;
} else if(basic_selection_) {
b000page_ = B000Page::BASIC128;
} else {
b000page_ = B000Page::Empty;
}
update_commutable_rom();
break;
case 0xa7e4:
access_mode_ = AccessMode(value & 1);
break;
case 0xa7e5:
ram_page_ = value & 0b11111;
update_commutable_ram();
break;
case 0xa7e7:
// b7: disable internal floppy
// b6: 1 = enable RAM-in-cartridge space banking via a7e6. 0 = "RAM works like the MO5 64K expansion"?
// b5: 1 = 525 lines/frame; 0 = 625.
// b4: 1 = enable RAM banking via a7e5. 0 = "the braindead system using the two PIAs"?
// b3, b2: computer type (might affect pixel mappings?) [00 = MO6; 01 = TO9+; 11 = TO8]
// b1, b0: RAM type (00 = 64x1; 10 = 128x1; 00 = 256x1)
break;
case 0xa7cb:
// Cartridge paging: used by both 'Jane' and the MO5 RAM expansion.
//
// b0–b1: page number;
// b2: enable or disable RAM visibility; [initially disabled]
// b3: enable or disable writing.
cartridge_page_ = value & 3;
update_commutable_rom();
break;
}
}
AccessMode access_mode() const {
return access_mode_;
}
/// Accesses the memory map if currently in system access mode or if this is a write; otherwise
/// accesses `video`.
template <
uint16_t address,
CPU::M6809::ReadWrite read_write,
typename VideoT
>
void system_access(VideoT &video, CPU::M6809::data_t<read_write> value) {
if constexpr (!CPU::M6809::is_read(read_write)) {
write<address>(value);
} else {
value = access_mode() == AccessMode::System ? read<address>() : video->template read<address>();
}
}
private:
void update_commutable_ram() {
const auto offset = ram_page_ * 16384;
if(offset < ram_.size()) {
uint8_t *const page = &ram_[offset];
for(size_t c = 0x0; c < 0x4; c++) {
set_readwrite(c + 0x6, page + (c << 12));
}
} else {
for(size_t c = 0x0; c < 0x4; c++) {
set_read(c + 0x6, nullptr);
}
}
}
void update_commutable_rom() {
const auto set_16kbrange = [&](const size_t start, const uint8_t *source) {
for(size_t c = 0; c < 4; c++) {
set_read(c + start, source ? source + (c << 12) : nullptr);
}
};
// Establish both correct monitor paging and a default of BASIC 1.
set_16kbrange(0xc, rom_.data() + rom_page_ * 0x4000);
switch(b000page_) {
default:
set_read(0xb, nullptr);
break;
case B000Page::Cartridge:
if(cartridge_page_ * 0x4000 < cartridge_.size()) {
set_16kbrange(0xb, cartridge_.data() + cartridge_page_ * 0x4000);
} else {
set_16kbrange(0xb, nullptr);
}
break;
case B000Page::BASIC128: {
const uint8_t *const basic128 = rom_.data() + 0x8000;
set_16kbrange(0xb, basic128 + rom_page_ * 0x4000);
} break;
}
}
std::array<uint8_t, 0x4000> video_;
std::array<uint8_t, (is_mo6 ? (128 * 1024) : (32 * 1024))> ram_;
std::array<uint8_t, 0x7c0> floppy_rom_;
std::vector<uint8_t> rom_;
std::vector<uint8_t> cartridge_;
size_t cartridge_page_ = 0;
size_t rom_page_ = 0;
size_t ram_page_ = 1;
bool basic_selection_ = false;
enum class B000Page {
Empty,
BASIC128,
Cartridge,
} b000page_ = B000Page::Empty;
AccessMode access_mode_ = AccessMode::LightPen;
friend ConstIterator;
uint8_t passive_read(const uint16_t address) const {
return read_[address >> 12][address];
}
};
}
}