// // LinearMemory.hpp // Clock Signal // // Created by Thomas Harte on 24/03/2025. // Copyright © 2025 Thomas Harte. All rights reserved. // #pragma once #include "InstructionSets/x86/AccessType.hpp" #include "InstructionSets/x86/Model.hpp" #include "Machines/Utility/MemoryFuzzer.hpp" #include #include #include #include #include #include namespace PCCompatible { // TODO: send writes to the ROM area off to nowhere. // TODO: support banked sections for EGA/VGA and possibly EMS purposes. struct DummyValue { public: template IntT &value() { if constexpr (std::is_same_v) { return dummies_.dummy32; } else if constexpr (std::is_same_v) { return dummies_.dummy16; } else if constexpr (std::is_same_v) { return dummies_.dummy8; } } private: union { uint32_t dummy32; uint16_t dummy16; uint8_t dummy8; } dummies_; }; template struct LinearPool { static constexpr size_t MaxAddress = MaxAddressV; LinearPool() { Memory::Fuzz(memory); } // // External access. // // Provided for setup. void install(const uint32_t address, const uint8_t *const data, const uint32_t length) { std::copy(data, data + length, memory.begin() + std::vector::difference_type(address)); } // Used by both DMA devices and by the CGA and MDA cards to set up their base pointers. // @c address is always physical. uint8_t *at(const uint32_t address) { return &memory[address]; } template IntT read(const uint32_t address) { if(address >= MaxAddressV) return IntT(~0); return *reinterpret_cast(&memory[address]); } protected: std::array memory; }; struct SplitHolder { using AccessType = InstructionSet::x86::AccessType; template typename InstructionSet::x86::Accessor::type access( const uint32_t address, const uint32_t base, const uint32_t bytes_available, uint8_t *const memory ) { if(bytes_available >= sizeof(IntT)) { return *reinterpret_cast(&memory[address]); } // This is a large quantity that straddles the limit, // but if it's being read only then just assemble it and // forget about things... if constexpr (!is_writeable(type)) { if constexpr (std::is_same_v) { return uint16_t(memory[address] | (memory[base] << 8)); } IntT result; auto buffer = reinterpret_cast(&result); std::memcpy(buffer, &memory[address], bytes_available); std::memcpy(buffer + bytes_available, &memory[base], sizeof(IntT) - bytes_available); return result; } // The caller needs an atomic unit that looks like an IntT and will // need to be written out eventually, so set up for that. write_back_address_[0] = address; write_back_address_[1] = base; write_back_lead_size_ = bytes_available; // Seed value only if this is a modify if constexpr (type == AccessType::ReadModifyWrite) { auto buffer = reinterpret_cast(write_back_value()); if constexpr (std::is_same_v) { buffer[0] = memory[address]; buffer[1] = memory[base]; } else { std::memcpy(buffer, &memory[address], bytes_available); std::memcpy(buffer + bytes_available, &memory[base], sizeof(IntT) - bytes_available); } } return *write_back_value(); } template void write_back(uint8_t *const memory) { if constexpr (std::is_same_v) { return; } if(write_back_address_[0] == NoWriteBack) { return; } auto buffer = reinterpret_cast(write_back_value()); if constexpr (std::is_same_v) { memory[write_back_address_[0]] = buffer[0]; memory[write_back_address_[1]] = buffer[1]; } else { std::memcpy(&memory[write_back_address_[0]], buffer, write_back_lead_size_); std::memcpy(&memory[write_back_address_[1]], buffer + write_back_lead_size_, sizeof(IntT) - write_back_lead_size_); } write_back_address_[0] = NoWriteBack; } private: static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back. uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack}; uint32_t write_back_lead_size_; union { uint16_t word; uint32_t dword; } write_back_value_; template IntT *write_back_value() { static_assert(std::is_same_v || std::is_same_v); if constexpr (std::is_same_v) { return &write_back_value_.word; } else { return &write_back_value_.dword; } } }; template struct LinearMemory; template struct LinearMemory>: public SplitHolder, public LinearPool<1 << 20> { static constexpr bool RequiresPreauthorisation = false; template typename InstructionSet::x86::Accessor::type access( uint32_t address, const uint32_t base ) { address &= MaxAddress - 1; // Bytes: always safe. if constexpr (std::is_same_v) { return memory[address]; } else { // Split on end of address space. if(address == MaxAddress - 1) { return SplitHolder::access(address, base, 1, memory.data()); } // Split on end of segment if this is an 8086. if constexpr (model == InstructionSet::x86::Model::i8086) { const uint32_t offset = address - base; if(offset == 0xffff) { return SplitHolder::access(address, base, 1, memory.data()); } } } // Don't split. return *reinterpret_cast(&memory[address]); } template void write_back() { if constexpr (!std::is_same_v) { SplitHolder::write_back(memory.data()); } } template void preauthorised_write( uint32_t address, const uint32_t base, IntT value ) { address &= MaxAddress - 1; // Bytes can be written without further ado. if constexpr (std::is_same_v) { memory[address] = value; return; } // Words that straddle the segment end must be split in two. if constexpr (model == InstructionSet::x86::Model::i8086) { const uint32_t offset = address - base; if(offset == 0xffff) { memory[address] = uint8_t(value & 0xff); memory[base] = uint8_t(value >> 8); return; } } // Words that straddle the end of physical RAM must also be split in two. if(address == MaxAddress - 1) { memory[address] = uint8_t(value & 0xff); memory[0] = uint8_t(value >> 8); return; } // It's safe just to write then. *reinterpret_cast(&memory[address]) = value; } void preauthorise_read(uint32_t, uint32_t) {} void preauthorise_write(uint32_t, uint32_t) {} }; // TODO: increase template parameter to LinearPool, which is the base RAM size. template <> struct LinearMemory: public LinearPool<1 << 20> { static constexpr bool RequiresPreauthorisation = true; LinearMemory() { set_a20_enabled(true); } // A20 is the only thing that can cause split accesses on an 80286. void set_a20_enabled(const bool enabled) { address_mask_ = uint32_t(~0); if(!enabled) { address_mask_ &= uint32_t(~(1 << 20)); } } using AccessType = InstructionSet::x86::AccessType; template typename InstructionSet::x86::Accessor::type access( uint32_t address, uint32_t ) { if(MaxAddress != (1 << 24) && (address & address_mask_) >= MaxAddress) { return dummy_.value(); } return *reinterpret_cast(&memory[address & address_mask_]); } template typename InstructionSet::x86::Accessor::type access( uint32_t address, uint32_t ) const { static_assert(!is_writeable(type)); if(MaxAddress != (1 << 24) && (address & address_mask_) >= MaxAddress) { return dummy_.value(); } return *reinterpret_cast(&memory[address & address_mask_]); } template void write_back() { dummy_.value() = IntT(~0); } template void preauthorised_write( uint32_t address, const uint32_t, IntT value ) { if(MaxAddress != (1 << 24) && (address & address_mask_) >= MaxAddress) return; *reinterpret_cast(&memory[address & address_mask_]) = value; } void preauthorise_read(uint32_t, uint32_t) {} void preauthorise_write(uint32_t, uint32_t) {} private: uint32_t address_mask_; inline static DummyValue dummy_; }; }