From 9df6d535e2bf3c02031155971e0c8048fd3257d8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 4 Mar 2025 13:52:02 -0500 Subject: [PATCH 1/6] Patch up enough to get an 80286 performer compilable. --- Analyser/Static/PCCompatible/Target.hpp | 7 +- .../x86/Implementation/FlowControl.hpp | 11 ++- .../Implementation/PerformImplementation.hpp | 4 +- InstructionSets/x86/Implementation/Stack.hpp | 2 +- InstructionSets/x86/Instruction.hpp | 2 +- Machines/PCCompatible/Memory.hpp | 49 +++++++++-- Machines/PCCompatible/PCCompatible.cpp | 82 +++++++++++-------- 7 files changed, 107 insertions(+), 50 deletions(-) diff --git a/Analyser/Static/PCCompatible/Target.hpp b/Analyser/Static/PCCompatible/Target.hpp index 3a85b15e3..21cd7e43f 100644 --- a/Analyser/Static/PCCompatible/Target.hpp +++ b/Analyser/Static/PCCompatible/Target.hpp @@ -16,12 +16,15 @@ namespace Analyser::Static::PCCompatible { struct Target: public Analyser::Static::Target, public Reflection::StructImpl { ReflectableEnum(VideoAdaptor, MDA, - CGA); + CGA, + ); VideoAdaptor adaptor = VideoAdaptor::CGA; ReflectableEnum(ModelApproximation, XT, - TurboXT); + TurboXT, + AT + ); ModelApproximation model = ModelApproximation::TurboXT; Target() : Analyser::Static::Target(Machine::PCCompatible) {} diff --git a/InstructionSets/x86/Implementation/FlowControl.hpp b/InstructionSets/x86/Implementation/FlowControl.hpp index 667ab9f4d..a7353625d 100644 --- a/InstructionSets/x86/Implementation/FlowControl.hpp +++ b/InstructionSets/x86/Implementation/FlowControl.hpp @@ -240,11 +240,11 @@ void into( } } -template +template void bound( const InstructionT &instruction, - read_t destination, - read_t source, + read_t destination, + read_t source, ContextT &context ) { using sIntT = typename std::make_signed::type; @@ -252,10 +252,9 @@ void bound( const auto source_segment = instruction.data_segment(); context.memory.preauthorise_read(source_segment, source, 2*sizeof(IntT)); const auto lower_bound = - sIntT(context.memory.template access(source_segment, source)); - source += 2; + sIntT(context.memory.template access(source_segment, source)); const auto upper_bound = - sIntT(context.memory.template access(source_segment, source)); + sIntT(context.memory.template access(source_segment, IntT(source + 2))); if(sIntT(destination) < lower_bound || sIntT(destination) > upper_bound) { interrupt(Interrupt::BoundRangeExceeded, context); diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 4ea1988db..6f479d20a 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -223,7 +223,7 @@ template < } else { static_assert(int(Operation::IDIV_REP) == int(Operation::LEAVE)); if constexpr (std::is_same_v || std::is_same_v) { - Primitive::leave(); + Primitive::leave(context); } } return; @@ -338,7 +338,7 @@ template < break; } else { static_assert(int(Operation::SETMOC) == int(Operation::BOUND)); - Primitive::bound(instruction, destination_r(), source_r(), context); + Primitive::bound(instruction, destination_r(), source_r(), context); } return; diff --git a/InstructionSets/x86/Implementation/Stack.hpp b/InstructionSets/x86/Implementation/Stack.hpp index 54b0a0a9f..00d4a7c22 100644 --- a/InstructionSets/x86/Implementation/Stack.hpp +++ b/InstructionSets/x86/Implementation/Stack.hpp @@ -174,7 +174,7 @@ void enter( context.registers.bp() -= 2; const auto value = context.memory.template preauthorised_read(Source::SS, context.registers.bp()); - push(value); + push(value, context); } // Set final BP. diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 2431da654..d292e9fa2 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -850,7 +850,7 @@ public: /// @returns The dynamic storage size argument supplied to an ENTER. constexpr ImmediateT dynamic_storage_size() const { - return displacement(); + return offset(); } // Standard comparison operator. diff --git a/Machines/PCCompatible/Memory.hpp b/Machines/PCCompatible/Memory.hpp index 831dca126..a8f8d3412 100644 --- a/Machines/PCCompatible/Memory.hpp +++ b/Machines/PCCompatible/Memory.hpp @@ -39,7 +39,10 @@ struct Memory { // Accesses an address based on segment:offset. template - typename InstructionSet::x86::Accessor::type access(InstructionSet::x86::Source segment, uint16_t offset) { + typename InstructionSet::x86::Accessor::type access( + const InstructionSet::x86::Source segment, + const uint16_t offset + ) { const uint32_t physical_address = address(segment, offset); if constexpr (std::is_same_v) { @@ -55,7 +58,7 @@ struct Memory { // Accesses an address based on physical location. template - typename InstructionSet::x86::Accessor::type access(uint32_t address) { + typename InstructionSet::x86::Accessor::type access(const uint32_t address) { // Dispense with the single-byte case trivially. if constexpr (std::is_same_v) { return memory[address]; @@ -78,10 +81,14 @@ struct Memory { } // - // Direct write. + // Direct read and write. // template - void preauthorised_write(InstructionSet::x86::Source segment, uint16_t offset, IntT value) { + void preauthorised_write( + const InstructionSet::x86::Source segment, + const uint16_t offset, + const IntT value + ) { // Bytes can be written without further ado. if constexpr (std::is_same_v) { memory[address(segment, offset) & 0xf'ffff] = value; @@ -105,7 +112,39 @@ struct Memory { } // It's safe just to write then. - *reinterpret_cast(&memory[target]) = value; + *reinterpret_cast(&memory[target]) = value; + } + + template + IntT preauthorised_read( + const InstructionSet::x86::Source segment, + const uint16_t offset + ) { + // Bytes can be written without further ado. + if constexpr (std::is_same_v) { + return memory[address(segment, offset) & 0xf'ffff]; + } + + // Words that straddle the segment end must be split in two. + if(offset == 0xffff) { + return IntT( + memory[address(segment, offset) & 0xf'ffff] | + memory[address(segment, 0x0000) & 0xf'ffff] << 8 + ); + } + + 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) { + return IntT( + memory[0xf'ffff] | + memory[0x0'0000] << 8 + ); + } + + // It's safe just to write then. + return *reinterpret_cast(&memory[target]); } // diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 9b8fded61..0fdc51d21 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -58,7 +58,8 @@ Log::Logger log; using PCModelApproximation = Analyser::Static::PCCompatible::Target::ModelApproximation; constexpr InstructionSet::x86::Model processor_model(PCModelApproximation model) { switch(model) { - default: return InstructionSet::x86::Model::i8086; + default: return InstructionSet::x86::Model::i8086; + case PCModelApproximation::AT: return InstructionSet::x86::Model::i80286; } } @@ -907,25 +908,45 @@ class ConcreteMachine: set_clock_rate(double(pit_frequency)); speaker_.speaker.set_input_rate(double(pit_frequency)); - // Fetch the BIOS. [8088 only, for now] - const auto bios = ROM::Name::PCCompatibleGLaBIOS; - const auto tick = ROM::Name::PCCompatibleGLaTICK; + // Fetch the BIOS. const auto font = Video::FontROM; - ROM::Request request = ROM::Request(bios) && ROM::Request(tick, true) && ROM::Request(font); + constexpr auto biosXT = ROM::Name::PCCompatibleGLaBIOS; + constexpr auto tickXT = ROM::Name::PCCompatibleGLaTICK; + + constexpr auto biosAT = ROM::Name::PCCompatiblePhoenix80286BIOS; + + ROM::Request request = ROM::Request(font); + switch(pc_model) { + default: + request = request && ROM::Request(biosXT) && ROM::Request(tickXT); + break; + case PCModelApproximation::AT: + request = request && ROM::Request(biosAT); + break; + } + auto roms = rom_fetcher(request); if(!request.validate(roms)) { throw ROMMachine::Error::MissingROMs; } - // A BIOS is mandatory. - const auto &bios_contents = roms.find(bios)->second; - context_.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size()); + switch(pc_model) { + default: { + const auto &bios_contents = roms.find(biosXT)->second; + context_.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size()); - // If found, install GlaTICK at 0xd'0000. - auto tick_contents = roms.find(tick); - if(tick_contents != roms.end()) { - context_.memory.install(0xd'0000, tick_contents->second.data(), tick_contents->second.size()); + // If found, install GlaTICK at 0xd'0000. + auto tick_contents = roms.find(tickXT); + if(tick_contents != roms.end()) { + context_.memory.install(0xd'0000, tick_contents->second.data(), tick_contents->second.size()); + } + } break; + + case PCModelApproximation::AT: + const auto &bios_contents = roms.find(biosAT)->second; + context_.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size()); + break; } // Give the video card something to read from. @@ -941,18 +962,9 @@ class ConcreteMachine: } // MARK: - TimedMachine. - void run_for(const Cycles duration) override { - using Model = Target::ModelApproximation; - switch(pc_model) { - case Model::XT: run_for(duration); break; - case Model::TurboXT: run_for(duration); break; - } - } - - template - void run_for(const Cycles duration) { + void run_for(const Cycles duration) final { const auto pit_ticks = duration.as(); - constexpr bool is_fast = model == Target::ModelApproximation::TurboXT; + constexpr bool is_fast = pc_model >= Target::ModelApproximation::TurboXT; int ticks; if constexpr (is_fast) { @@ -1087,15 +1099,15 @@ class ConcreteMachine: } // MARK: - ScanProducer. - void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { video_.set_scan_target(scan_target); } - Outputs::Display::ScanStatus get_scaled_scan_status() const override { + Outputs::Display::ScanStatus get_scaled_scan_status() const final { return video_.get_scaled_scan_status(); } // MARK: - AudioProducer. - Outputs::Speaker::Speaker *get_speaker() override { + Outputs::Speaker::Speaker *get_speaker() final { return &speaker_.speaker; } @@ -1107,7 +1119,7 @@ class ConcreteMachine: } // MARK: - MediaTarget - bool insert_media(const Analyser::Static::Media &media) override { + bool insert_media(const Analyser::Static::Media &media) final { int c = 0; for(auto &disk : media.disks) { fdc_.set_disk(disk, c); @@ -1118,11 +1130,11 @@ class ConcreteMachine: } // MARK: - MappedKeyboardMachine. - MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override { + MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() final { return &keyboard_mapper_; } - void set_key_state(uint16_t key, bool is_pressed) override { + void set_key_state(uint16_t key, bool is_pressed) final { keyboard_.post(uint8_t(key | (is_pressed ? 0x00 : 0x80))); } @@ -1132,25 +1144,25 @@ class ConcreteMachine: } // MARK: - Configuration options. - std::unique_ptr get_options() const override { + std::unique_ptr get_options() const final { auto options = std::make_unique(Configurable::OptionsType::UserFriendly); options->output = get_video_signal_configurable(); return options; } - void set_options(const std::unique_ptr &str) override { + void set_options(const std::unique_ptr &str) final { const auto options = dynamic_cast(str.get()); set_video_signal_configurable(options->output); } - void set_display_type(Outputs::Display::DisplayType display_type) override { + void set_display_type(Outputs::Display::DisplayType display_type) final { video_.set_display_type(display_type); // Give the PPI a shout-out in case it isn't too late to switch to CGA40. ppi_handler_.hint_is_composite(Outputs::Display::is_composite(display_type)); } - Outputs::Display::DisplayType get_display_type() const override { + Outputs::Display::DisplayType get_display_type() const final { return video_.get_display_type(); } @@ -1227,6 +1239,10 @@ std::unique_ptr machine(const Target &target, const ROMMachine::ROMFetc case PCModelApproximation::TurboXT: return std::make_unique> (target, rom_fetcher); + + case PCModelApproximation::AT: + return std::make_unique> + (target, rom_fetcher); } } } From 4e3b0ae3c1c6a0444eda30c43da34338e6063d0d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 4 Mar 2025 14:10:28 -0500 Subject: [PATCH 2/6] Resolve type warnings in ENTER, spurious new lines in PC. --- InstructionSets/x86/Implementation/Stack.hpp | 11 ++++++----- Machines/PCCompatible/PCCompatible.cpp | 18 ++++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/InstructionSets/x86/Implementation/Stack.hpp b/InstructionSets/x86/Implementation/Stack.hpp index 00d4a7c22..bdce4b843 100644 --- a/InstructionSets/x86/Implementation/Stack.hpp +++ b/InstructionSets/x86/Implementation/Stack.hpp @@ -152,18 +152,18 @@ void enter( const auto alloc_size = instruction.dynamic_storage_size(); const auto nesting_level = instruction.nesting_level() & 0x1f; - // Preauthorse contents that'll be fetched via BP. + // Preauthorise contents that'll be fetched via BP. const auto copied_pointers = nesting_level - 2; if(copied_pointers > 0) { context.memory.preauthorise_read( Source::SS, - context.registers.bp() - copied_pointers * sizeof(uint16_t), - copied_pointers * sizeof(uint16_t) + uint16_t(context.registers.bp() - size_t(copied_pointers) * sizeof(uint16_t)), + uint32_t(size_t(copied_pointers) * sizeof(uint16_t)) // TODO: I don't think this can actually be 32 bit. ); } - // Preauthorse stack activity. - context.memory.preauthorise_stack_write((1 + copied_pointers) * sizeof(uint16_t)); + // Preauthorise writes. + context.memory.preauthorise_stack_write(uint32_t(size_t(nesting_level) * sizeof(uint16_t))); // Push BP and grab the end of frame. push(context.registers.bp(), context); @@ -179,6 +179,7 @@ void enter( // Set final BP. context.registers.bp() = frame; + context.registers.sp() -= alloc_size; } template diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 0fdc51d21..13788b6c3 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -122,7 +122,7 @@ class FloppyController { using Command = Intel::i8272::Command; switch(decoder_.command()) { default: - log.error().append("TODO: implement FDC command %d\n", uint8_t(decoder_.command())); + log.error().append("TODO: implement FDC command %d", uint8_t(decoder_.command())); break; case Command::WriteDeletedData: @@ -619,9 +619,9 @@ class IO { switch(port) { default: if constexpr (std::is_same_v) { - log.error().append("Unhandled out: %02x to %04x\n", value, port); + log.error().append("Unhandled out: %02x to %04x", value, port); } else { - log.error().append("Unhandled out: %04x to %04x\n", value, port); + log.error().append("Unhandled out: %04x to %04x", value, port); } break; @@ -630,7 +630,7 @@ class IO { // On the XT the NMI can be masked by setting bit 7 on I/O port 0xA0. case 0x00a0: - log.error().append("TODO: NMIs %s\n", (value & 0x80) ? "masked" : "unmasked"); + log.error().append("TODO: NMIs %s", (value & 0x80) ? "masked" : "unmasked"); break; case 0x0000: dma_.controller.write<0x0>(uint8_t(value)); break; @@ -703,7 +703,7 @@ class IO { fdc_.set_digital_output(uint8_t(value)); break; case 0x03f4: - log.error().append("TODO: FDC write of %02x at %04x\n", value, port); + log.error().append("TODO: FDC write of %02x at %04x", value, port); break; case 0x03f5: fdc_.write(uint8_t(value)); @@ -730,7 +730,7 @@ class IO { template IntT in([[maybe_unused]] uint16_t port) { switch(port) { default: - log.error().append("Unhandled in: %04x\n", port); + log.error().append("Unhandled in: %04x", port); break; case 0x0000: return dma_.controller.read<0x0>(); @@ -844,7 +844,7 @@ class FlowController { halted_ = true; } void wait() { - log.error().append("WAIT ????\n"); + log.error().append("WAIT ????"); } void repeat_last() { @@ -1229,9 +1229,11 @@ class ConcreteMachine: using namespace PCCompatible; namespace { +static constexpr bool ForceAT = false; + template std::unique_ptr machine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) { - switch(target.model) { + switch(ForceAT ? PCModelApproximation::AT : target.model) { case PCModelApproximation::XT: return std::make_unique> (target, rom_fetcher); From 89fd41124fd2262b7cbf37f4b38251ae9a6b9ff9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 4 Mar 2025 17:08:49 -0500 Subject: [PATCH 3/6] Template various bits of hardware on machine type. --- Analyser/Static/PCCompatible/Target.hpp | 16 +- Machines/PCCompatible/DMA.hpp | 6 +- Machines/PCCompatible/Memory.hpp | 360 +++++++++--------- Machines/PCCompatible/PCCompatible.cpp | 355 +++++++++-------- Machines/PCCompatible/PIC.hpp | 3 + Machines/PCCompatible/ProcessorByModel.hpp | 23 ++ Machines/PCCompatible/Registers.hpp | 16 +- Machines/PCCompatible/Segments.hpp | 6 +- .../Clock Signal.xcodeproj/project.pbxproj | 6 + .../StaticAnalyser/CSStaticAnalyser.mm | 4 +- OSBindings/Qt/mainwindow.cpp | 4 +- 11 files changed, 438 insertions(+), 361 deletions(-) create mode 100644 Machines/PCCompatible/ProcessorByModel.hpp diff --git a/Analyser/Static/PCCompatible/Target.hpp b/Analyser/Static/PCCompatible/Target.hpp index 21cd7e43f..10a113992 100644 --- a/Analyser/Static/PCCompatible/Target.hpp +++ b/Analyser/Static/PCCompatible/Target.hpp @@ -13,19 +13,19 @@ namespace Analyser::Static::PCCompatible { +ReflectableEnum(Model, + XT, + TurboXT, + AT +); + struct Target: public Analyser::Static::Target, public Reflection::StructImpl { ReflectableEnum(VideoAdaptor, MDA, CGA, ); VideoAdaptor adaptor = VideoAdaptor::CGA; - - ReflectableEnum(ModelApproximation, - XT, - TurboXT, - AT - ); - ModelApproximation model = ModelApproximation::TurboXT; + Model model = Model::TurboXT; Target() : Analyser::Static::Target(Machine::PCCompatible) {} @@ -33,7 +33,7 @@ private: friend Reflection::StructImpl; void declare_fields() { AnnounceEnum(VideoAdaptor); - AnnounceEnum(ModelApproximation); + AnnounceEnum(Model); DeclareField(adaptor); DeclareField(model); } diff --git a/Machines/PCCompatible/DMA.hpp b/Machines/PCCompatible/DMA.hpp index e0d590b38..955e2fc8e 100644 --- a/Machines/PCCompatible/DMA.hpp +++ b/Machines/PCCompatible/DMA.hpp @@ -8,6 +8,7 @@ #pragma once +#include "Analyser/Static/PCCompatible/Target.hpp" #include "Numeric/RegisterSizes.hpp" #include "Memory.hpp" @@ -287,13 +288,14 @@ class DMAPages { } }; +template class DMA { public: i8237 controller; DMAPages pages; // Memory is set posthoc to resolve a startup time. - void set_memory(Memory *memory) { + void set_memory(Memory *memory) { memory_ = memory; } @@ -310,7 +312,7 @@ class DMA { } private: - Memory *memory_; + Memory *memory_; }; } diff --git a/Machines/PCCompatible/Memory.hpp b/Machines/PCCompatible/Memory.hpp index a8f8d3412..558c95e39 100644 --- a/Machines/PCCompatible/Memory.hpp +++ b/Machines/PCCompatible/Memory.hpp @@ -8,9 +8,11 @@ #pragma once +#include "ProcessorByModel.hpp" #include "Registers.hpp" #include "Segments.hpp" +#include "Analyser/Static/PCCompatible/Target.hpp" #include "InstructionSets/x86/AccessType.hpp" #include @@ -18,198 +20,202 @@ namespace PCCompatible { // TODO: send writes to the ROM area off to nowhere. -struct Memory { - public: - using AccessType = InstructionSet::x86::AccessType; +template +class Memory { + static constexpr auto x86_model = processor_model(model); - // Constructor. - Memory(Registers ®isters, const Segments &segments) : registers_(registers), segments_(segments) {} +public: + using AccessType = InstructionSet::x86::AccessType; - // - // 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) {} + // Constructor. + Memory(Registers ®isters, const Segments &segments) : + registers_(registers), segments_(segments) {} - // - // Access call-ins. - // + // + // 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) {} - // Accesses an address based on segment:offset. - template - typename InstructionSet::x86::Accessor::type access( - const InstructionSet::x86::Source segment, - const uint16_t offset - ) { - const uint32_t physical_address = address(segment, offset); + // + // Access call-ins. + // - 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)); - } - } + // Accesses an address based on segment:offset. + template + typename InstructionSet::x86::Accessor::type access( + const InstructionSet::x86::Source segment, + const uint16_t offset + ) { + const uint32_t physical_address = address(segment, offset); - return access(physical_address); - } - - // Accesses an address based on physical location. - template - typename InstructionSet::x86::Accessor::type access(const 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 read and write. - // - template - void preauthorised_write( - const InstructionSet::x86::Source segment, - const uint16_t offset, - const 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 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) { - 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; - } - - template - IntT preauthorised_read( - const InstructionSet::x86::Source segment, - const uint16_t offset - ) { - // Bytes can be written without further ado. - if constexpr (std::is_same_v) { - return memory[address(segment, offset) & 0xf'ffff]; - } - - // Words that straddle the segment end must be split in two. - if(offset == 0xffff) { - return IntT( - memory[address(segment, offset) & 0xf'ffff] | - memory[address(segment, 0x0000) & 0xf'ffff] << 8 - ); - } - - 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) { - return IntT( - memory[0xf'ffff] | - memory[0x0'0000] << 8 - ); - } - - // It's safe just to write then. - return *reinterpret_cast(&memory[target]); - } - - // - // Helper for instruction fetch. - // - std::pair next_code() const { - const uint32_t start = segments_.cs_base_ + registers_.ip(); - return std::make_pair(&memory[start], 0x10'000 - start); - } - - std::pair all() const { - 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::difference_type(address)); - } - - uint8_t *at(uint32_t address) { - return &memory[address]; - } - - private: - std::array memory{0xff}; - Registers ®isters_; - const Segments &segments_; - - uint32_t segment_base(const InstructionSet::x86::Source segment) const { - 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_; + return split_word(physical_address, address(segment, 0)); } } - uint32_t address(const InstructionSet::x86::Source segment, const uint16_t offset) const { - return (segment_base(segment) + offset) & 0xf'ffff; + return access(physical_address); + } + + // Accesses an address based on physical location. + template + typename InstructionSet::x86::Accessor::type access(const 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 - typename InstructionSet::x86::Accessor::type - split_word(const uint32_t low_address, const 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)); + 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; } } + } - 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_; + // + // Direct read and write. + // + template + void preauthorised_write( + const InstructionSet::x86::Source segment, + const uint16_t offset, + const 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; + } + + template + IntT preauthorised_read( + const InstructionSet::x86::Source segment, + const uint16_t offset + ) { + // Bytes can be written without further ado. + if constexpr (std::is_same_v) { + return memory[address(segment, offset) & 0xf'ffff]; + } + + // Words that straddle the segment end must be split in two. + if(offset == 0xffff) { + return IntT( + memory[address(segment, offset) & 0xf'ffff] | + memory[address(segment, 0x0000) & 0xf'ffff] << 8 + ); + } + + 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) { + return IntT( + memory[0xf'ffff] | + memory[0x0'0000] << 8 + ); + } + + // It's safe just to write then. + return *reinterpret_cast(&memory[target]); + } + + // + // Helper for instruction fetch. + // + std::pair next_code() const { + const uint32_t start = segments_.cs_base_ + registers_.ip(); + return std::make_pair(&memory[start], 0x10'000 - start); + } + + std::pair all() const { + 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::difference_type(address)); + } + + uint8_t *at(uint32_t address) { + return &memory[address]; + } + +private: + std::array memory{0xff}; + Registers ®isters_; + const Segments &segments_; + + uint32_t segment_base(const InstructionSet::x86::Source segment) const { + 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(const InstructionSet::x86::Source segment, const uint16_t offset) const { + return (segment_base(segment) + offset) & 0xf'ffff; + } + + template + typename InstructionSet::x86::Accessor::type + split_word(const uint32_t low_address, const 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_; }; } diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 13788b6c3..f42e899e5 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -8,6 +8,7 @@ #include "PCCompatible.hpp" +#include "ProcessorByModel.hpp" #include "CGA.hpp" #include "DMA.hpp" #include "KeyboardMapper.hpp" @@ -52,17 +53,7 @@ namespace PCCompatible { namespace { - Log::Logger log; - -using PCModelApproximation = Analyser::Static::PCCompatible::Target::ModelApproximation; -constexpr InstructionSet::x86::Model processor_model(PCModelApproximation model) { - switch(model) { - default: return InstructionSet::x86::Model::i8086; - case PCModelApproximation::AT: return InstructionSet::x86::Model::i80286; - } -} - } using Target = Analyser::Static::PCCompatible::Target; @@ -72,9 +63,14 @@ template struct Adaptor; template <> struct Adaptor { using type = MDA; }; template <> struct Adaptor { using type = CGA; }; +template class FloppyController { public: - FloppyController(PIC &pic, DMA &dma, int drive_count) : pic_(pic), dma_(dma) { + FloppyController( + PIC &pic, + DMA &dma, + int drive_count + ) : pic_(pic), dma_(dma) { // Default: one floppy drive only. for(int c = 0; c < 4; c++) { drives_[c].exists = drive_count > c; @@ -107,7 +103,7 @@ class FloppyController { } hold_reset_ = hold_reset; if(hold_reset_) { - pic_.apply_edge<6>(false); + pic_.template apply_edge<6>(false); } } @@ -143,7 +139,7 @@ class FloppyController { // TODO: what if head has changed? drives_[decoder_.target().drive].status = decoder_.drive_head(); drives_[decoder_.target().drive].raised_interrupt = true; - pic_.apply_edge<6>(true); + pic_.template apply_edge<6>(true); } break; case Command::ReadDeletedData: @@ -204,7 +200,7 @@ class FloppyController { // TODO: what if head has changed? drives_[decoder_.target().drive].status = decoder_.drive_head(); drives_[decoder_.target().drive].raised_interrupt = true; - pic_.apply_edge<6>(true); + pic_.template apply_edge<6>(true); } break; case Command::Recalibrate: @@ -212,14 +208,14 @@ class FloppyController { drives_[decoder_.target().drive].raised_interrupt = true; drives_[decoder_.target().drive].status = decoder_.target().drive | uint8_t(Intel::i8272::Status0::SeekEnded); - pic_.apply_edge<6>(true); + pic_.template apply_edge<6>(true); break; case Command::Seek: drives_[decoder_.target().drive].track = decoder_.seek_target(); drives_[decoder_.target().drive].raised_interrupt = true; drives_[decoder_.target().drive].status = decoder_.drive_head() | uint8_t(Intel::i8272::Status0::SeekEnded); - pic_.apply_edge<6>(true); + pic_.template apply_edge<6>(true); break; case Command::SenseInterruptStatus: { @@ -237,7 +233,7 @@ class FloppyController { any_remaining_interrupts |= drives_[c].raised_interrupt; } if(!any_remaining_interrupts) { - pic_.apply_edge<6>(false); + pic_.template apply_edge<6>(false); } } break; case Command::Specify: @@ -309,15 +305,15 @@ class FloppyController { drives_[c].raised_interrupt = true; drives_[c].status = uint8_t(Intel::i8272::Status0::BecameNotReady); } - pic_.apply_edge<6>(true); + pic_.template apply_edge<6>(true); using MainStatus = Intel::i8272::MainStatus; status_.set(MainStatus::DataReady, true); status_.set(MainStatus::DataIsToProcessor, false); } - PIC &pic_; - DMA &dma_; + PIC &pic_; + DMA &dma_; bool hold_reset_ = false; bool enable_dma_ = false; @@ -361,9 +357,10 @@ class FloppyController { Activity::Observer *observer_ = nullptr; }; +template class KeyboardController { public: - KeyboardController(PIC &pic) : pic_(pic) {} + KeyboardController(PIC &pic) : pic_(pic) {} // KB Status Port 61h high bits: //; 01 - normal operation. wait for keypress, when one comes in, @@ -380,13 +377,13 @@ class KeyboardController { switch(mode_) { case Mode::NormalOperation: break; case Mode::NoIRQsIgnoreInput: - pic_.apply_edge<1>(false); + pic_.template apply_edge<1>(false); break; case Mode::Reset: input_.clear(); [[fallthrough]]; case Mode::ClearIRQReset: - pic_.apply_edge<1>(false); + pic_.template apply_edge<1>(false); break; } @@ -408,7 +405,7 @@ class KeyboardController { } uint8_t read() { - pic_.apply_edge<1>(false); + pic_.template apply_edge<1>(false); if(input_.empty()) { return 0; } @@ -416,7 +413,7 @@ class KeyboardController { const uint8_t key = input_.front(); input_.erase(input_.begin()); if(!input_.empty()) { - pic_.apply_edge<1>(true); + pic_.template apply_edge<1>(true); } return key; } @@ -426,7 +423,7 @@ class KeyboardController { return; } input_.push_back(value); - pic_.apply_edge<1>(true); + pic_.template apply_edge<1>(true); } private: @@ -438,7 +435,7 @@ class KeyboardController { } mode_; std::vector input_; - PIC &pic_; + PIC &pic_; int reset_delay_ = 0; }; @@ -486,21 +483,22 @@ struct PCSpeaker { bool output_ = false; }; +template class PITObserver { public: - PITObserver(PIC &pic, PCSpeaker &speaker) : pic_(pic), speaker_(speaker) {} + PITObserver(PIC &pic, PCSpeaker &speaker) : pic_(pic), speaker_(speaker) {} template void update_output(bool new_level) { switch(channel) { default: break; - case 0: pic_.apply_edge<0>(new_level); break; - case 2: speaker_.set_pit(new_level); break; + case 0: pic_.template apply_edge<0>(new_level); break; + case 2: speaker_.set_pit(new_level); break; } } private: - PIC &pic_; + PIC &pic_; PCSpeaker &speaker_; // TODO: @@ -509,11 +507,19 @@ class PITObserver { // channel 1 is used for DRAM refresh (presumably connected to DMA?); // channel 2 is gated by a PPI output and feeds into the speaker. }; -using PIT = i8253; +template +using PIT = i8253>; + +template class i8255PortHandler : public Intel::i8255::PortHandler { public: - i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard, Target::VideoAdaptor adaptor, int drive_count) : + i8255PortHandler( + PCSpeaker &speaker, + KeyboardController &keyboard, + const Target::VideoAdaptor adaptor, + const int drive_count + ) : speaker_(speaker), keyboard_(keyboard) { // High switches: // @@ -600,16 +606,25 @@ class i8255PortHandler : public Intel::i8255::PortHandler { bool use_high_switches_ = false; PCSpeaker &speaker_; - KeyboardController &keyboard_; + KeyboardController &keyboard_; bool enable_keyboard_ = false; }; -using PPI = Intel::i8255::i8255; +template +using PPI = Intel::i8255::i8255>; -template +template class IO { public: - IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, typename Adaptor