mirror of
https://github.com/TomHarte/CLK.git
synced 2024-09-11 22:56:20 +00:00
180 lines
5.4 KiB
C++
180 lines
5.4 KiB
C++
//
|
|
// 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 <array>
|
|
|
|
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 IntT, AccessType type>
|
|
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset) {
|
|
const uint32_t physical_address = address(segment, offset);
|
|
|
|
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
|
// 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<type>(physical_address, address(segment, 0));
|
|
}
|
|
}
|
|
|
|
return access<IntT, type>(physical_address);
|
|
}
|
|
|
|
// Accesses an address based on physical location.
|
|
template <typename IntT, AccessType type>
|
|
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address) {
|
|
// Dispense with the single-byte case trivially.
|
|
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
|
return memory[address];
|
|
} else if(address != 0xf'ffff) {
|
|
return *reinterpret_cast<IntT *>(&memory[address]);
|
|
} else {
|
|
return split_word<type>(address, 0);
|
|
}
|
|
}
|
|
|
|
template <typename IntT>
|
|
void write_back() {
|
|
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
|
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 <typename IntT>
|
|
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<IntT, uint8_t>) {
|
|
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<uint16_t *>(&memory[target]) = value;
|
|
}
|
|
|
|
//
|
|
// Helper for instruction fetch.
|
|
//
|
|
std::pair<const uint8_t *, size_t> next_code() {
|
|
const uint32_t start = segments_.cs_base_ + registers_.ip();
|
|
return std::make_pair(&memory[start], 0x10'000 - start);
|
|
}
|
|
|
|
std::pair<const uint8_t *, size_t> 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<uint8_t>::difference_type(address));
|
|
}
|
|
|
|
uint8_t *at(uint32_t address) {
|
|
return &memory[address];
|
|
}
|
|
|
|
private:
|
|
std::array<uint8_t, 1024*1024> 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 <AccessType type>
|
|
typename InstructionSet::x86::Accessor<uint16_t, type>::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 */
|