From 3b84299a05e469bf05212d72e477d4c6276fd722 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 15 Nov 2023 15:58:49 -0500 Subject: [PATCH] Edge closer to PCCompatible doing _something_. --- Machines/PCCompatible/PCCompatible.cpp | 305 +++++++++++++++++- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 50 ++- 2 files changed, 327 insertions(+), 28 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index e88ce0d59..c5314d498 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -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 ®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_; +}; + +struct Memory { + public: + using AccessType = InstructionSet::x86::AccessType; + + // Constructor. + Memory(Registers ®isters, 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 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; + } + + private: + std::vector memory; + 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 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 void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {} + template IntT in([[maybe_unused]] uint16_t port) { return IntT(~0); } +}; + +class FlowController { + public: + FlowController(Registers ®isters, 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 ®isters_; + 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 decoder; }; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index fcdd84851..adffeecd0 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -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 ®isters, Segments &segments, Flags &flags) : - memory_(memory), registers_(registers), segments_(segments), flags_(flags) {} + FlowController(Registers ®isters, 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 ®isters_; 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; }