mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-26 08:29:33 +00:00
Hack in enough that disk contents end up in RAM.
This commit is contained in:
parent
5feac8ef14
commit
d101483714
@ -127,6 +127,37 @@ class i8237 {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Interface for reading/writing via DMA.
|
||||||
|
//
|
||||||
|
static constexpr auto NotAvailable = uint32_t(~0);
|
||||||
|
|
||||||
|
/// Provides the next target address for @c channel if performing either a write (if @c is_write is @c true) or read (otherwise).
|
||||||
|
///
|
||||||
|
/// @returns Either a 16-bit address or @c NotAvailable if the requested channel isn't set up to perform a read or write at present.
|
||||||
|
uint32_t access(size_t channel, bool is_write) {
|
||||||
|
if(channels_[channel].transfer_complete) {
|
||||||
|
return NotAvailable;
|
||||||
|
}
|
||||||
|
if(is_write && channels_[channel].transfer != Channel::Transfer::Write) {
|
||||||
|
return NotAvailable;
|
||||||
|
}
|
||||||
|
if(!is_write && channels_[channel].transfer != Channel::Transfer::Read) {
|
||||||
|
return NotAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto address = channels_[channel].address.full;
|
||||||
|
channels_[channel].address.full += channels_[channel].address_decrement ? -1 : 1;
|
||||||
|
|
||||||
|
--channels_[channel].count.full;
|
||||||
|
channels_[channel].transfer_complete = (channels_[channel].count.full == 0xffff);
|
||||||
|
if(channels_[channel].transfer_complete) {
|
||||||
|
// TODO: _something_ with mode.
|
||||||
|
}
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Low/high byte latch.
|
// Low/high byte latch.
|
||||||
bool next_access_low_ = true;
|
bool next_access_low_ = true;
|
||||||
@ -173,7 +204,7 @@ class DMAPages {
|
|||||||
return pages_[page_for_index(index)];
|
return pages_[page_for_index(index)];
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t channel_page(int channel) {
|
uint8_t channel_page(size_t channel) {
|
||||||
return pages_[channel];
|
return pages_[channel];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
177
Machines/PCCompatible/Memory.hpp
Normal file
177
Machines/PCCompatible/Memory.hpp
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
//
|
||||||
|
// 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"
|
||||||
|
|
||||||
|
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 */
|
@ -12,6 +12,7 @@
|
|||||||
#include "PIC.hpp"
|
#include "PIC.hpp"
|
||||||
#include "PIT.hpp"
|
#include "PIT.hpp"
|
||||||
#include "DMA.hpp"
|
#include "DMA.hpp"
|
||||||
|
#include "Memory.hpp"
|
||||||
|
|
||||||
#include "../../InstructionSets/x86/Decoder.hpp"
|
#include "../../InstructionSets/x86/Decoder.hpp"
|
||||||
#include "../../InstructionSets/x86/Flags.hpp"
|
#include "../../InstructionSets/x86/Flags.hpp"
|
||||||
@ -45,11 +46,6 @@
|
|||||||
|
|
||||||
namespace PCCompatible {
|
namespace PCCompatible {
|
||||||
|
|
||||||
//bool log = false;
|
|
||||||
//std::string previous;
|
|
||||||
|
|
||||||
struct Memory;
|
|
||||||
|
|
||||||
class DMA {
|
class DMA {
|
||||||
public:
|
public:
|
||||||
i8237 controller;
|
i8237 controller;
|
||||||
@ -60,6 +56,19 @@ class DMA {
|
|||||||
memory_ = memory;
|
memory_ = memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this permits only 8-bit DMA. Fix that.
|
||||||
|
bool write(size_t channel, uint8_t value) {
|
||||||
|
auto address = controller.access(channel, true);
|
||||||
|
if(address == i8237::NotAvailable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
address |= uint32_t(pages.channel_page(channel) << 16);
|
||||||
|
*memory_->at(address) = value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Memory *memory_;
|
Memory *memory_;
|
||||||
};
|
};
|
||||||
@ -127,6 +136,7 @@ class FloppyController {
|
|||||||
|
|
||||||
// Search for a matching sector.
|
// Search for a matching sector.
|
||||||
const auto target = decoder_.geometry();
|
const auto target = decoder_.geometry();
|
||||||
|
bool found_sector = false;
|
||||||
for(auto &pair: drives_[decoder_.target().drive].cached_track) {
|
for(auto &pair: drives_[decoder_.target().drive].cached_track) {
|
||||||
if(
|
if(
|
||||||
(pair.second.address.track == target.cylinder) &&
|
(pair.second.address.track == target.cylinder) &&
|
||||||
@ -134,9 +144,27 @@ class FloppyController {
|
|||||||
(pair.second.address.side == target.head) &&
|
(pair.second.address.side == target.head) &&
|
||||||
(pair.second.size == target.size)
|
(pair.second.size == target.size)
|
||||||
) {
|
) {
|
||||||
// `printf("");
|
found_sector = true;
|
||||||
|
bool wrote_in_full = true;
|
||||||
|
|
||||||
|
for(int c = 0; c < 128 << target.size; c++) {
|
||||||
|
if(!dma_.write(2, pair.second.samples[0].data()[c])) {
|
||||||
|
wrote_in_full = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(wrote_in_full) {
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!found_sector) {
|
||||||
|
}
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Command::Seek:
|
case Command::Seek:
|
||||||
@ -682,257 +710,6 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
|||||||
};
|
};
|
||||||
using PPI = Intel::i8255::i8255<i8255PortHandler>;
|
using PPI = Intel::i8255::i8255<i8255PortHandler>;
|
||||||
|
|
||||||
struct Registers {
|
|
||||||
public:
|
|
||||||
static constexpr bool is_32bit = false;
|
|
||||||
|
|
||||||
uint8_t &al() { return ax_.halves.low; }
|
|
||||||
uint8_t &ah() { return ax_.halves.high; }
|
|
||||||
uint16_t &ax() { return ax_.full; }
|
|
||||||
|
|
||||||
CPU::RegisterPair16 &axp() { return ax_; }
|
|
||||||
|
|
||||||
uint8_t &cl() { return cx_.halves.low; }
|
|
||||||
uint8_t &ch() { return cx_.halves.high; }
|
|
||||||
uint16_t &cx() { return cx_.full; }
|
|
||||||
|
|
||||||
uint8_t &dl() { return dx_.halves.low; }
|
|
||||||
uint8_t &dh() { return dx_.halves.high; }
|
|
||||||
uint16_t &dx() { return dx_.full; }
|
|
||||||
|
|
||||||
uint8_t &bl() { return bx_.halves.low; }
|
|
||||||
uint8_t &bh() { return bx_.halves.high; }
|
|
||||||
uint16_t &bx() { return bx_.full; }
|
|
||||||
|
|
||||||
uint16_t &sp() { return sp_; }
|
|
||||||
uint16_t &bp() { return bp_; }
|
|
||||||
uint16_t &si() { return si_; }
|
|
||||||
uint16_t &di() { return di_; }
|
|
||||||
|
|
||||||
uint16_t &ip() { return ip_; }
|
|
||||||
|
|
||||||
uint16_t &es() { return es_; }
|
|
||||||
uint16_t &cs() { return cs_; }
|
|
||||||
uint16_t &ds() { return ds_; }
|
|
||||||
uint16_t &ss() { return ss_; }
|
|
||||||
uint16_t es() const { return es_; }
|
|
||||||
uint16_t cs() const { return cs_; }
|
|
||||||
uint16_t ds() const { return ds_; }
|
|
||||||
uint16_t ss() const { return ss_; }
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
cs_ = 0xffff;
|
|
||||||
ip_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
CPU::RegisterPair16 ax_;
|
|
||||||
CPU::RegisterPair16 cx_;
|
|
||||||
CPU::RegisterPair16 dx_;
|
|
||||||
CPU::RegisterPair16 bx_;
|
|
||||||
|
|
||||||
uint16_t sp_;
|
|
||||||
uint16_t bp_;
|
|
||||||
uint16_t si_;
|
|
||||||
uint16_t di_;
|
|
||||||
uint16_t es_, cs_, ds_, ss_;
|
|
||||||
uint16_t ip_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Segments {
|
|
||||||
public:
|
|
||||||
Segments(const Registers ®isters) : registers_(registers) {}
|
|
||||||
|
|
||||||
using Source = InstructionSet::x86::Source;
|
|
||||||
|
|
||||||
/// Posted by @c perform after any operation which *might* have affected a segment register.
|
|
||||||
void did_update(Source segment) {
|
|
||||||
switch(segment) {
|
|
||||||
default: break;
|
|
||||||
case Source::ES: es_base_ = uint32_t(registers_.es()) << 4; break;
|
|
||||||
case Source::CS: cs_base_ = uint32_t(registers_.cs()) << 4; break;
|
|
||||||
case Source::DS: ds_base_ = uint32_t(registers_.ds()) << 4; break;
|
|
||||||
case Source::SS: ss_base_ = uint32_t(registers_.ss()) << 4; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
did_update(Source::ES);
|
|
||||||
did_update(Source::CS);
|
|
||||||
did_update(Source::DS);
|
|
||||||
did_update(Source::SS);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t es_base_, cs_base_, ds_base_, ss_base_;
|
|
||||||
|
|
||||||
bool operator ==(const Segments &rhs) const {
|
|
||||||
return
|
|
||||||
es_base_ == rhs.es_base_ &&
|
|
||||||
cs_base_ == rhs.cs_base_ &&
|
|
||||||
ds_base_ == rhs.ds_base_ &&
|
|
||||||
ss_base_ == rhs.ss_base_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const Registers ®isters_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
const 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_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IO {
|
class IO {
|
||||||
public:
|
public:
|
||||||
IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda, FloppyController &fdc) :
|
IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda, FloppyController &fdc) :
|
||||||
|
73
Machines/PCCompatible/Registers.hpp
Normal file
73
Machines/PCCompatible/Registers.hpp
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
//
|
||||||
|
// Registers.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 01/12/2023.
|
||||||
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Registers_hpp
|
||||||
|
#define Registers_hpp
|
||||||
|
|
||||||
|
namespace PCCompatible {
|
||||||
|
|
||||||
|
struct Registers {
|
||||||
|
public:
|
||||||
|
static constexpr bool is_32bit = false;
|
||||||
|
|
||||||
|
uint8_t &al() { return ax_.halves.low; }
|
||||||
|
uint8_t &ah() { return ax_.halves.high; }
|
||||||
|
uint16_t &ax() { return ax_.full; }
|
||||||
|
|
||||||
|
CPU::RegisterPair16 &axp() { return ax_; }
|
||||||
|
|
||||||
|
uint8_t &cl() { return cx_.halves.low; }
|
||||||
|
uint8_t &ch() { return cx_.halves.high; }
|
||||||
|
uint16_t &cx() { return cx_.full; }
|
||||||
|
|
||||||
|
uint8_t &dl() { return dx_.halves.low; }
|
||||||
|
uint8_t &dh() { return dx_.halves.high; }
|
||||||
|
uint16_t &dx() { return dx_.full; }
|
||||||
|
|
||||||
|
uint8_t &bl() { return bx_.halves.low; }
|
||||||
|
uint8_t &bh() { return bx_.halves.high; }
|
||||||
|
uint16_t &bx() { return bx_.full; }
|
||||||
|
|
||||||
|
uint16_t &sp() { return sp_; }
|
||||||
|
uint16_t &bp() { return bp_; }
|
||||||
|
uint16_t &si() { return si_; }
|
||||||
|
uint16_t &di() { return di_; }
|
||||||
|
|
||||||
|
uint16_t &ip() { return ip_; }
|
||||||
|
|
||||||
|
uint16_t &es() { return es_; }
|
||||||
|
uint16_t &cs() { return cs_; }
|
||||||
|
uint16_t &ds() { return ds_; }
|
||||||
|
uint16_t &ss() { return ss_; }
|
||||||
|
uint16_t es() const { return es_; }
|
||||||
|
uint16_t cs() const { return cs_; }
|
||||||
|
uint16_t ds() const { return ds_; }
|
||||||
|
uint16_t ss() const { return ss_; }
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
cs_ = 0xffff;
|
||||||
|
ip_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
CPU::RegisterPair16 ax_;
|
||||||
|
CPU::RegisterPair16 cx_;
|
||||||
|
CPU::RegisterPair16 dx_;
|
||||||
|
CPU::RegisterPair16 bx_;
|
||||||
|
|
||||||
|
uint16_t sp_;
|
||||||
|
uint16_t bp_;
|
||||||
|
uint16_t si_;
|
||||||
|
uint16_t di_;
|
||||||
|
uint16_t es_, cs_, ds_, ss_;
|
||||||
|
uint16_t ip_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Registers_hpp */
|
58
Machines/PCCompatible/Segments.hpp
Normal file
58
Machines/PCCompatible/Segments.hpp
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// Segments.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 01/12/2023.
|
||||||
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Segments_hpp
|
||||||
|
#define Segments_hpp
|
||||||
|
|
||||||
|
#include "Registers.hpp"
|
||||||
|
|
||||||
|
#include "../../InstructionSets/x86/Instruction.hpp"
|
||||||
|
|
||||||
|
namespace PCCompatible {
|
||||||
|
|
||||||
|
class Segments {
|
||||||
|
public:
|
||||||
|
Segments(const Registers ®isters) : registers_(registers) {}
|
||||||
|
|
||||||
|
using Source = InstructionSet::x86::Source;
|
||||||
|
|
||||||
|
/// Posted by @c perform after any operation which *might* have affected a segment register.
|
||||||
|
void did_update(Source segment) {
|
||||||
|
switch(segment) {
|
||||||
|
default: break;
|
||||||
|
case Source::ES: es_base_ = uint32_t(registers_.es()) << 4; break;
|
||||||
|
case Source::CS: cs_base_ = uint32_t(registers_.cs()) << 4; break;
|
||||||
|
case Source::DS: ds_base_ = uint32_t(registers_.ds()) << 4; break;
|
||||||
|
case Source::SS: ss_base_ = uint32_t(registers_.ss()) << 4; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
did_update(Source::ES);
|
||||||
|
did_update(Source::CS);
|
||||||
|
did_update(Source::DS);
|
||||||
|
did_update(Source::SS);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t es_base_, cs_base_, ds_base_, ss_base_;
|
||||||
|
|
||||||
|
bool operator ==(const Segments &rhs) const {
|
||||||
|
return
|
||||||
|
es_base_ == rhs.es_base_ &&
|
||||||
|
cs_base_ == rhs.cs_base_ &&
|
||||||
|
ds_base_ == rhs.ds_base_ &&
|
||||||
|
ss_base_ == rhs.ss_base_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Registers ®isters_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Segments_hpp */
|
@ -1133,6 +1133,9 @@
|
|||||||
4238200E2B17CBC800964EFE /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
||||||
|
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>"; };
|
||||||
423BDC492AB24699008E37B6 /* 8088Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 8088Tests.mm; sourceTree = "<group>"; };
|
423BDC492AB24699008E37B6 /* 8088Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 8088Tests.mm; sourceTree = "<group>"; };
|
||||||
42437B342ACF02A9006DFED1 /* Flags.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Flags.hpp; sourceTree = "<group>"; };
|
42437B342ACF02A9006DFED1 /* Flags.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Flags.hpp; sourceTree = "<group>"; };
|
||||||
42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = "<group>"; };
|
42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = "<group>"; };
|
||||||
@ -2383,11 +2386,14 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
425739372B051EA800B7D1E4 /* PCCompatible.cpp */,
|
425739372B051EA800B7D1E4 /* PCCompatible.cpp */,
|
||||||
425739362B051EA800B7D1E4 /* PCCompatible.hpp */,
|
|
||||||
4267A9C72B0C26FA008A59BB /* PIT.hpp */,
|
|
||||||
4267A9C82B0D4EC2008A59BB /* PIC.hpp */,
|
|
||||||
4267A9C92B0D4F17008A59BB /* DMA.hpp */,
|
4267A9C92B0D4F17008A59BB /* DMA.hpp */,
|
||||||
4267A9CA2B111ED2008A59BB /* KeyboardMapper.hpp */,
|
4267A9CA2B111ED2008A59BB /* KeyboardMapper.hpp */,
|
||||||
|
423820132B1A235200964EFE /* Memory.hpp */,
|
||||||
|
425739362B051EA800B7D1E4 /* PCCompatible.hpp */,
|
||||||
|
4267A9C82B0D4EC2008A59BB /* PIC.hpp */,
|
||||||
|
4267A9C72B0C26FA008A59BB /* PIT.hpp */,
|
||||||
|
423820142B1A23C200964EFE /* Registers.hpp */,
|
||||||
|
423820152B1A23E100964EFE /* Segments.hpp */,
|
||||||
);
|
);
|
||||||
path = PCCompatible;
|
path = PCCompatible;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user