// // PCCompatible.cpp // Clock Signal // // Created by Thomas Harte on 15/11/2023. // Copyright © 2023 Thomas Harte. All rights reserved. // #include "PCCompatible.hpp" #include "../../InstructionSets/x86/Decoder.hpp" #include "../../InstructionSets/x86/Flags.hpp" #include "../../InstructionSets/x86/Instruction.hpp" #include "../../InstructionSets/x86/Perform.hpp" #include "../ScanProducer.hpp" #include "../TimedMachine.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_; }; 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, public MachineTypes::ScanProducer { public: ConcreteMachine( [[maybe_unused]] const Analyser::Static::Target &target, [[maybe_unused]] const ROMMachine::ROMFetcher &rom_fetcher ) { // This is actually a MIPS count; try 3 million. set_clock_rate(3'000'000); // Fetch the BIOS. [8088 only, for now] ROM::Request request = ROM::Request(ROM::Name::PCCompatibleGLaBIOS); auto roms = rom_fetcher(request); if(!request.validate(roms)) { throw ROMMachine::Error::MissingROMs; } } // MARK: - TimedMachine. 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 {} Outputs::Display::ScanStatus get_scaled_scan_status() const override { return Outputs::Display::ScanStatus(); } 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; }; } using namespace PCCompatible; // See header; constructs and returns an instance of the Amstrad CPC. Machine *Machine::PCCompatible(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { return new PCCompatible::ConcreteMachine(*target, rom_fetcher); } Machine::~Machine() {}