1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-09-29 16:55:59 +00:00

Edge closer to PCCompatible doing _something_.

This commit is contained in:
Thomas Harte 2023-11-15 15:58:49 -05:00
parent 6f48ffba16
commit 3b84299a05
2 changed files with 327 additions and 28 deletions

View File

@ -7,6 +7,9 @@
//
#include "PCCompatible.hpp"
#include "../../InstructionSets/x86/Decoder.hpp"
#include "../../InstructionSets/x86/Flags.hpp"
#include "../../InstructionSets/x86/Instruction.hpp"
#include "../../InstructionSets/x86/Perform.hpp"
@ -15,6 +18,277 @@
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_;
};
class Segments {
public:
Segments(const Registers &registers) : 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 &registers_;
};
struct Memory {
public:
using AccessType = InstructionSet::x86::AccessType;
// Constructor.
Memory(Registers &registers, const Segments &segments) : registers_(registers), segments_(segments) {
memory.resize(1024*1024);
}
//
// 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;
}
private:
std::vector<uint8_t> memory;
Registers &registers_;
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 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_;
};
struct IO {
template <typename IntT> void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {}
template <typename IntT> IntT in([[maybe_unused]] uint16_t port) { return IntT(~0); }
};
class FlowController {
public:
FlowController(Registers &registers, Segments &segments) :
registers_(registers), segments_(segments) {}
// Requirements for perform.
void jump(uint16_t address) {
registers_.ip() = address;
}
void jump(uint16_t segment, uint16_t address) {
registers_.cs() = segment;
segments_.did_update(Segments::Source::CS);
registers_.ip() = address;
}
void halt() {}
void wait() {}
void repeat_last() {
should_repeat_ = true;
}
// Other actions.
void begin_instruction() {
should_repeat_ = false;
}
bool should_repeat() const {
return should_repeat_;
}
private:
Registers &registers_;
Segments &segments_;
bool should_repeat_ = false;
};
class ConcreteMachine:
public Machine,
public MachineTypes::TimedMachine,
@ -37,7 +311,13 @@ class ConcreteMachine:
}
// MARK: - TimedMachine.
void run_for([[maybe_unused]] const Cycles cycles) override {}
void run_for([[maybe_unused]] const Cycles cycles) override {
auto instructions = cycles.as_integral();
while(instructions--) {
// const auto decoded = decoder.decode(data.data(), data.size());
}
}
// MARK: - ScanProducer.
void set_scan_target([[maybe_unused]] Outputs::Display::ScanTarget *scan_target) override {}
@ -46,6 +326,29 @@ class ConcreteMachine:
}
private:
struct Context {
Context() :
segments(registers),
memory(registers, segments),
flow_controller(registers, segments)
{
reset();
}
void reset() {
registers.reset();
segments.reset();
}
InstructionSet::x86::Flags flags;
Registers registers;
Segments segments;
Memory memory;
FlowController flow_controller;
IO io;
static constexpr auto model = InstructionSet::x86::Model::i8086;
} context;
InstructionSet::x86::Decoder<Context::model> decoder;
};

View File

@ -59,7 +59,6 @@ struct Registers {
uint16_t &si() { return si_; }
uint16_t &di() { return di_; }
uint16_t ip_;
uint16_t &ip() { return ip_; }
uint16_t &es() { return es_; }
@ -89,9 +88,7 @@ struct Registers {
ip_ == rhs.ip_;
}
// TODO: make the below private and use a friend class for test population, to ensure Perform
// is free of direct accesses.
// private:
private:
CPU::RegisterPair16 ax_;
CPU::RegisterPair16 cx_;
CPU::RegisterPair16 dx_;
@ -102,6 +99,7 @@ struct Registers {
uint16_t si_;
uint16_t di_;
uint16_t es_, cs_, ds_, ss_;
uint16_t ip_;
};
class Segments {
public:
@ -369,18 +367,18 @@ struct IO {
};
class FlowController {
public:
FlowController(Memory &memory, Registers &registers, Segments &segments, Flags &flags) :
memory_(memory), registers_(registers), segments_(segments), flags_(flags) {}
FlowController(Registers &registers, Segments &segments) :
registers_(registers), segments_(segments) {}
// Requirements for perform.
void jump(uint16_t address) {
registers_.ip_ = address;
registers_.ip() = address;
}
void jump(uint16_t segment, uint16_t address) {
registers_.cs_ = segment;
registers_.cs() = segment;
segments_.did_update(Segments::Source::CS);
registers_.ip_ = address;
registers_.ip() = address;
}
void halt() {}
@ -399,10 +397,8 @@ class FlowController {
}
private:
Memory &memory_;
Registers &registers_;
Segments &segments_;
Flags &flags_;
bool should_repeat_ = false;
};
@ -418,7 +414,7 @@ struct ExecutionSupport {
ExecutionSupport():
memory(registers, segments),
segments(registers),
flow_controller(memory, registers, segments, flags) {}
flow_controller(registers, segments) {}
void clear() {
memory.clear();
@ -580,20 +576,20 @@ struct FailedExecution {
}
- (void)populate:(Registers &)registers flags:(Flags &)flags value:(NSDictionary *)value {
registers.ax_.full = [value[@"ax"] intValue];
registers.bx_.full = [value[@"bx"] intValue];
registers.cx_.full = [value[@"cx"] intValue];
registers.dx_.full = [value[@"dx"] intValue];
registers.ax() = [value[@"ax"] intValue];
registers.bx() = [value[@"bx"] intValue];
registers.cx() = [value[@"cx"] intValue];
registers.dx() = [value[@"dx"] intValue];
registers.bp_ = [value[@"bp"] intValue];
registers.cs_ = [value[@"cs"] intValue];
registers.di_ = [value[@"di"] intValue];
registers.ds_ = [value[@"ds"] intValue];
registers.es_ = [value[@"es"] intValue];
registers.si_ = [value[@"si"] intValue];
registers.sp_ = [value[@"sp"] intValue];
registers.ss_ = [value[@"ss"] intValue];
registers.ip_ = [value[@"ip"] intValue];
registers.bp() = [value[@"bp"] intValue];
registers.cs() = [value[@"cs"] intValue];
registers.di() = [value[@"di"] intValue];
registers.ds() = [value[@"ds"] intValue];
registers.es() = [value[@"es"] intValue];
registers.si() = [value[@"si"] intValue];
registers.sp() = [value[@"sp"] intValue];
registers.ss() = [value[@"ss"] intValue];
registers.ip() = [value[@"ip"] intValue];
const uint16_t flags_value = [value[@"flags"] intValue];
flags.set(flags_value);
@ -646,7 +642,7 @@ struct FailedExecution {
//
// TODO: enquire of the actual mechanism of repetition; if it were stateful as below then
// would it survive interrupts? So is it just IP adjustment?
execution_support.registers.ip_ += decoded.first;
execution_support.registers.ip() += decoded.first;
do {
execution_support.flow_controller.begin_instruction();
InstructionSet::x86::perform(
@ -718,7 +714,7 @@ struct FailedExecution {
// More research required, but for now I'm not treating this as a roadblock.
if(decoded.second.operation() == Operation::IDIV_REP) {
Registers advanced_registers = intended_registers;
advanced_registers.ip_ += decoded.first;
advanced_registers.ip() += decoded.first;
if(advanced_registers == execution_support.registers && ramEqual && flagsEqual) {
failure_list = &permitted_failures;
}