mirror of
https://github.com/TomHarte/CLK.git
synced 2025-08-05 08:26:28 +00:00
Introduce a linear-memory holder.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Memory.hpp"
|
||||
#include "SegmentedMemory.hpp"
|
||||
#include "ProcessorByModel.hpp"
|
||||
#include "Registers.hpp"
|
||||
#include "Segments.hpp"
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
CPUControl(
|
||||
Registers<processor_model(model)> ®isters,
|
||||
Segments<processor_model(model)> &segments,
|
||||
Memory<model> &memory
|
||||
SegmentedMemory<model> &memory
|
||||
) : registers_(registers), segments_(segments), memory_(memory) {}
|
||||
|
||||
void reset() {
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
private:
|
||||
Registers<processor_model(model)> ®isters_;
|
||||
Segments<processor_model(model)> &segments_;
|
||||
Memory<model> &memory_;
|
||||
SegmentedMemory<model> &memory_;
|
||||
|
||||
Log::Logger<Log::Source::PCCompatible> log_;
|
||||
};
|
||||
|
@@ -12,7 +12,7 @@
|
||||
#include "Numeric/RegisterSizes.hpp"
|
||||
#include "Outputs/Log.hpp"
|
||||
|
||||
#include "Memory.hpp"
|
||||
#include "SegmentedMemory.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
@@ -302,7 +302,7 @@ public:
|
||||
DMAPages<has_second_dma> pages;
|
||||
|
||||
// Memory is set posthoc to resolve a startup time.
|
||||
void set_memory(Memory<model> *const memory) {
|
||||
void set_memory(SegmentedMemory<model> *const memory) {
|
||||
memory_ = memory;
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Memory<model> *memory_;
|
||||
SegmentedMemory<model> *memory_;
|
||||
};
|
||||
|
||||
}
|
||||
|
143
Machines/PCCompatible/LinearMemory.hpp
Normal file
143
Machines/PCCompatible/LinearMemory.hpp
Normal file
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// 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 <array>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
/*!
|
||||
Provides a mapping from linear addresses to underlying memory.
|
||||
|
||||
Prior to the 80286, linear addresses are presently physical addresses.
|
||||
Some nuance might appear here if/when EGA and VGA and/or EMS are implemented.
|
||||
|
||||
On an 8086 and 80186, addresses are clamped to 20 bits.
|
||||
|
||||
On the 80286 they're clamped to 24 bits.
|
||||
|
||||
From the AT onwards, address line 20 can be enabled or disabled.
|
||||
|
||||
TODO: from the 80386 onwards, memory can be reordered and
|
||||
exceptions might be raised.
|
||||
|
||||
TODO: remove assumption of a little-endian host.
|
||||
|
||||
TODO: allow for read-only areas of memory, paged areas of memory, etc.
|
||||
*/
|
||||
template <InstructionSet::x86::Model model>
|
||||
struct LinearMemory {
|
||||
using AccessType = InstructionSet::x86::AccessType;
|
||||
static constexpr uint32_t MaxAddress =
|
||||
1024 * 1024 * (model >= InstructionSet::x86::Model::i80286 ? 16 : 1);
|
||||
|
||||
/// @returns A suitable accessor for the @c IntT that starts at @c address, subject to address
|
||||
/// wrapping within the bounds `[base, limit]`. There is a chance that some sort of behind-the-scenes
|
||||
/// activity may be necessary to present something as a single @c IntT that is actually split across the
|
||||
///
|
||||
template <typename IntT, AccessType type>
|
||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(
|
||||
const uint32_t address, /// Address within linear memory.
|
||||
uint32_t base, /// Start address of wrapping window within linear memory.
|
||||
const uint32_t limit /// Size of wrapping window.
|
||||
) {
|
||||
static_assert(
|
||||
std::is_same_v<IntT, uint8_t> ||
|
||||
std::is_same_v<IntT, uint16_t> ||
|
||||
std::is_same_v<IntT, uint32_t> ||
|
||||
!is_writeable(type)
|
||||
);
|
||||
assert(limit >= sizeof(IntT));
|
||||
|
||||
base &= MaxAddress - 1;
|
||||
|
||||
// Bytes: always safe.
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
return memory[address];
|
||||
}
|
||||
|
||||
// Larger quantities: split only if they won't fit.
|
||||
const auto bytes_available = base + limit - address;
|
||||
if(bytes_available >= sizeof(IntT)) {
|
||||
return *reinterpret_cast<IntT *>(&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<IntT, uint16_t>) {
|
||||
return uint16_t(memory[address] | (memory[base] << 8));
|
||||
}
|
||||
|
||||
IntT result;
|
||||
auto buffer = reinterpret_cast<uint8_t *>(&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<uint8_t *>(&write_back_value_);
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
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 *reinterpret_cast<IntT *>(&write_back_value_);
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void write_back() {
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(write_back_address_[0] == NoWriteBack) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto buffer = reinterpret_cast<uint8_t *>(&write_back_value_);
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
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:
|
||||
std::array<uint8_t, MaxAddress> memory{0xff};
|
||||
|
||||
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_;
|
||||
uint32_t write_back_value_;
|
||||
};
|
||||
|
||||
}
|
@@ -14,8 +14,9 @@
|
||||
#include "FloppyController.hpp"
|
||||
#include "KeyboardController.hpp"
|
||||
#include "KeyboardMapper.hpp"
|
||||
#include "LinearMemory.hpp"
|
||||
#include "MDA.hpp"
|
||||
#include "Memory.hpp"
|
||||
#include "SegmentedMemory.hpp"
|
||||
#include "PIC.hpp"
|
||||
#include "PIT.hpp"
|
||||
#include "ProcessorByModel.hpp"
|
||||
@@ -933,8 +934,9 @@ private:
|
||||
|
||||
InstructionSet::x86::Flags flags;
|
||||
Registers<x86_model> registers;
|
||||
LinearMemory<x86_model> linear_memory;
|
||||
Segments<x86_model> segments;
|
||||
Memory<pc_model> memory;
|
||||
SegmentedMemory<pc_model> memory;
|
||||
FlowController<pc_model> flow_controller;
|
||||
CPUControl<pc_model> cpu_control;
|
||||
IO<pc_model, video> io;
|
||||
|
@@ -22,7 +22,7 @@ namespace PCCompatible {
|
||||
|
||||
// TODO: send writes to the ROM area off to nowhere.
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class Memory {
|
||||
class SegmentedMemory {
|
||||
static constexpr auto x86_model = processor_model(model);
|
||||
|
||||
public:
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
using Mode = InstructionSet::x86::Mode;
|
||||
|
||||
// Constructor.
|
||||
Memory(Registers<x86_model> ®isters, const Segments<x86_model> &segments) :
|
||||
SegmentedMemory(Registers<x86_model> ®isters, const Segments<x86_model> &segments) :
|
||||
registers_(registers), segments_(segments) {}
|
||||
|
||||
//
|
@@ -1252,7 +1252,7 @@
|
||||
4238200E2B17CBC800964EFE /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4238200F2B17CBC800964EFE /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
423820102B17CBC800964EFE /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
423820132B1A235200964EFE /* Memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Memory.hpp; sourceTree = "<group>"; };
|
||||
423820132B1A235200964EFE /* SegmentedMemory.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentedMemory.hpp; sourceTree = "<group>"; };
|
||||
423820142B1A23C200964EFE /* Registers.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Registers.hpp; sourceTree = "<group>"; };
|
||||
423820152B1A23E100964EFE /* Segments.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Segments.hpp; sourceTree = "<group>"; };
|
||||
423820422B1A90BE00964EFE /* PCBooter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCBooter.cpp; sourceTree = "<group>"; };
|
||||
@@ -1926,6 +1926,7 @@
|
||||
4B9EC0E926B384080060A31F /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
|
||||
4B9F11C82272375400701480 /* qltrace.txt.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = qltrace.txt.gz; sourceTree = "<group>"; };
|
||||
4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = OPCLOGR2.BIN; path = "68000 Coverage/OPCLOGR2.BIN"; sourceTree = "<group>"; };
|
||||
4BA094C82D92339F00BD78A1 /* LinearMemory.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LinearMemory.hpp; sourceTree = "<group>"; };
|
||||
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZX8081.cpp; sourceTree = "<group>"; };
|
||||
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZX8081.hpp; sourceTree = "<group>"; };
|
||||
4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
@@ -2568,14 +2569,15 @@
|
||||
4B1FBE222D7B739700BAC888 /* FloppyController.hpp */,
|
||||
4B1FBE242D7B753000BAC888 /* KeyboardController.hpp */,
|
||||
4267A9CA2B111ED2008A59BB /* KeyboardMapper.hpp */,
|
||||
4BA094C82D92339F00BD78A1 /* LinearMemory.hpp */,
|
||||
429B13622B1FCA96006BB4CB /* MDA.hpp */,
|
||||
423820132B1A235200964EFE /* Memory.hpp */,
|
||||
425739362B051EA800B7D1E4 /* PCCompatible.hpp */,
|
||||
4267A9C82B0D4EC2008A59BB /* PIC.hpp */,
|
||||
4267A9C72B0C26FA008A59BB /* PIT.hpp */,
|
||||
4B1FBE1F2D77AAC500BAC888 /* ProcessorByModel.hpp */,
|
||||
423820142B1A23C200964EFE /* Registers.hpp */,
|
||||
42EB81252B21788200429AF4 /* RTC.hpp */,
|
||||
423820132B1A235200964EFE /* SegmentedMemory.hpp */,
|
||||
423820152B1A23E100964EFE /* Segments.hpp */,
|
||||
4B1FBE232D7B73C800BAC888 /* Speaker.hpp */,
|
||||
);
|
||||
|
Reference in New Issue
Block a user