// // Memory.hpp // Clock Signal // // Created by Thomas Harte on 01/12/2023. // Copyright © 2023 Thomas Harte. All rights reserved. // #ifndef Memory_hpp #define Memory_hpp #include "Registers.hpp" #include "Segments.hpp" #include "../../InstructionSets/x86/AccessType.hpp" #include namespace PCCompatible { // TODO: send writes to the ROM area off to nowhere. struct Memory { public: using AccessType = InstructionSet::x86::AccessType; // Constructor. Memory(Registers ®isters, const Segments &segments) : registers_(registers), segments_(segments) {} // // Preauthorisation call-ins. Since only an 8088 is currently modelled, all accesses are implicitly authorised. // void preauthorise_stack_write([[maybe_unused]] uint32_t length) {} void preauthorise_stack_read([[maybe_unused]] uint32_t length) {} void preauthorise_read([[maybe_unused]] InstructionSet::x86::Source segment, [[maybe_unused]] uint16_t start, [[maybe_unused]] uint32_t length) {} void preauthorise_read([[maybe_unused]] uint32_t start, [[maybe_unused]] uint32_t length) {} // // Access call-ins. // // Accesses an address based on segment:offset. template typename InstructionSet::x86::Accessor::type access(InstructionSet::x86::Source segment, uint16_t offset) { const uint32_t physical_address = address(segment, offset); if constexpr (std::is_same_v) { // If this is a 16-bit access that runs past the end of the segment, it'll wrap back // to the start. So the 16-bit value will need to be a local cache. if(offset == 0xffff) { return split_word(physical_address, address(segment, 0)); } } return access(physical_address); } // Accesses an address based on physical location. template typename InstructionSet::x86::Accessor::type access(uint32_t address) { // Dispense with the single-byte case trivially. if constexpr (std::is_same_v) { return memory[address]; } else if(address != 0xf'ffff) { return *reinterpret_cast(&memory[address]); } else { return split_word(address, 0); } } template void write_back() { if constexpr (std::is_same_v) { if(write_back_address_[0] != NoWriteBack) { memory[write_back_address_[0]] = write_back_value_ & 0xff; memory[write_back_address_[1]] = write_back_value_ >> 8; write_back_address_[0] = 0; } } } // // Direct write. // template void preauthorised_write(InstructionSet::x86::Source segment, uint16_t offset, IntT value) { // Bytes can be written without further ado. if constexpr (std::is_same_v) { memory[address(segment, offset) & 0xf'ffff] = value; return; } // Words that straddle the segment end must be split in two. if(offset == 0xffff) { memory[address(segment, offset) & 0xf'ffff] = value & 0xff; memory[address(segment, 0x0000) & 0xf'ffff] = value >> 8; return; } const uint32_t target = address(segment, offset) & 0xf'ffff; // Words that straddle the end of physical RAM must also be split in two. if(target == 0xf'ffff) { memory[0xf'ffff] = value & 0xff; memory[0x0'0000] = value >> 8; return; } // It's safe just to write then. *reinterpret_cast(&memory[target]) = value; } // // Helper for instruction fetch. // std::pair next_code() { const uint32_t start = segments_.cs_base_ + registers_.ip(); return std::make_pair(&memory[start], 0x10'000 - start); } std::pair all() { return std::make_pair(memory.data(), 0x10'000); } // // External access. // void install(size_t address, const uint8_t *data, size_t length) { std::copy(data, data + length, memory.begin() + std::vector::difference_type(address)); } uint8_t *at(uint32_t address) { return &memory[address]; } private: std::array memory{0xff}; Registers ®isters_; const Segments &segments_; uint32_t segment_base(InstructionSet::x86::Source segment) { using Source = InstructionSet::x86::Source; switch(segment) { default: return segments_.ds_base_; case Source::ES: return segments_.es_base_; case Source::CS: return segments_.cs_base_; case Source::SS: return segments_.ss_base_; } } uint32_t address(InstructionSet::x86::Source segment, uint16_t offset) { return (segment_base(segment) + offset) & 0xf'ffff; } template typename InstructionSet::x86::Accessor::type split_word(uint32_t low_address, uint32_t high_address) { if constexpr (is_writeable(type)) { write_back_address_[0] = low_address; write_back_address_[1] = high_address; // Prepopulate only if this is a modify. if constexpr (type == AccessType::ReadModifyWrite) { write_back_value_ = uint16_t(memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8)); } return write_back_value_; } else { return uint16_t(memory[low_address] | (memory[high_address] << 8)); } } 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}; uint16_t write_back_value_; }; } #endif /* Memory_hpp */