From 3a103217d14f52f7f009012dbca782335b0ae799 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 8 Dec 2023 09:38:55 -0500 Subject: [PATCH 1/9] Add a compiletime 'turbo' flag for sort-of-80286 speeds. --- Machines/PCCompatible/PCCompatible.cpp | 142 +++++++++++++++---------- 1 file changed, 87 insertions(+), 55 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 46eca6354..c32b02345 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -908,7 +908,7 @@ class FlowController { bool halted_ = false; }; -template +template class ConcreteMachine: public Machine, public MachineTypes::TimedMachine, @@ -975,9 +975,15 @@ class ConcreteMachine: // MARK: - TimedMachine. void run_for(const Cycles duration) override { const auto pit_ticks = duration.as_integral(); - cpu_divisor_ += pit_ticks; - int ticks = cpu_divisor_ / 3; - cpu_divisor_ %= 3; + + int ticks; + if constexpr (!turbo) { + cpu_divisor_ += pit_ticks; + ticks = cpu_divisor_ / 3; + cpu_divisor_ %= 3; + } else { + ticks = pit_ticks; + } while(ticks--) { // @@ -989,23 +995,30 @@ class ConcreteMachine: // pit_.run_for(1); ++speaker_.cycles_since_update; - pit_.run_for(1); - ++speaker_.cycles_since_update; - pit_.run_for(1); - ++speaker_.cycles_since_update; + + if constexpr (!turbo) { + pit_.run_for(1); + ++speaker_.cycles_since_update; + pit_.run_for(1); + ++speaker_.cycles_since_update; + } // // Advance CRTC at a more approximate rate. // - video_.run_for(Cycles(3)); + video_.run_for(turbo ? Cycles(1) : Cycles(3)); + + // + // Give the keyboard a notification of passing time; it's very approximately clocked, + // really just including 'some' delays to avoid being instant. + // + keyboard_.run_for(Cycles(1)); // // Perform one CPU instruction every three PIT cycles. // i.e. CPU instruction rate is 1/3 * ~1.19Mhz ~= 0.4 MIPS. // - keyboard_.run_for(Cycles(1)); - // Query for interrupts and apply if pending. if(pic_.pending() && context.flags.template flag()) { // Regress the IP if a REP is in-progress so as to resume it later. @@ -1022,58 +1035,77 @@ class ConcreteMachine: ); } - // Do nothing if halted. + // Do nothing if currently halted. if(context.flow_controller.halted()) { continue; } - // Get the next thing to execute. - if(!context.flow_controller.should_repeat()) { - // Decode from the current IP. - decoded_ip_ = context.registers.ip(); - const auto remainder = context.memory.next_code(); - decoded = decoder.decode(remainder.first, remainder.second); - - // If that didn't yield a whole instruction then the end of memory must have been hit; - // continue from the beginning. - if(decoded.first <= 0) { - const auto all = context.memory.all(); - decoded = decoder.decode(all.first, all.second); - } - - context.registers.ip() += decoded.first; + if constexpr (turbo) { + // There's no divider applied, so this makes for 2*PI = around 2.4 MIPS. + // That's broadly 80286 speed, if MIPS were a valid measure. + perform_instruction(); + perform_instruction(); } else { - context.flow_controller.begin_instruction(); + // With the clock divider above, this makes for a net of PIT/3 = around 0.4 MIPS. + // i.e. a shade more than 8086 speed, if MIPS were meaningful. + perform_instruction(); } -/* if(decoded_ip_ >= 0x7c00 && decoded_ip_ < 0x7c00 + 1024) { - const auto next = to_string(decoded, InstructionSet::x86::Model::i8086); -// if(next != previous) { - std::cout << std::hex << decoded_ip_ << " " << next; - - if(decoded.second.operation() == InstructionSet::x86::Operation::INT) { - std::cout << " dl:" << std::hex << +context.registers.dl() << "; "; - std::cout << "ah:" << std::hex << +context.registers.ah() << "; "; - std::cout << "ch:" << std::hex << +context.registers.ch() << "; "; - std::cout << "cl:" << std::hex << +context.registers.cl() << "; "; - std::cout << "dh:" << std::hex << +context.registers.dh() << "; "; - std::cout << "es:" << std::hex << +context.registers.es() << "; "; - std::cout << "bx:" << std::hex << +context.registers.bx(); - } - - std::cout << std::endl; -// previous = next; -// } - }*/ - - // Execute it. - InstructionSet::x86::perform( - decoded.second, - context - ); + // Other inevitably broad and fuzzy and inconsistent MIPS counts for my own potential future play: + // + // 80386 @ 20Mhz: 4–5 MIPS. + // 80486 @ 66Mhz: 25 MIPS. + // Pentium @ 100Mhz: 188 MIPS. } } + void perform_instruction() { + // Get the next thing to execute. + if(!context.flow_controller.should_repeat()) { + // Decode from the current IP. + decoded_ip_ = context.registers.ip(); + const auto remainder = context.memory.next_code(); + decoded = decoder.decode(remainder.first, remainder.second); + + // If that didn't yield a whole instruction then the end of memory must have been hit; + // continue from the beginning. + if(decoded.first <= 0) { + const auto all = context.memory.all(); + decoded = decoder.decode(all.first, all.second); + } + + context.registers.ip() += decoded.first; + } else { + context.flow_controller.begin_instruction(); + } + +/* if(decoded_ip_ >= 0x7c00 && decoded_ip_ < 0x7c00 + 1024) { + const auto next = to_string(decoded, InstructionSet::x86::Model::i8086); +// if(next != previous) { + std::cout << std::hex << decoded_ip_ << " " << next; + + if(decoded.second.operation() == InstructionSet::x86::Operation::INT) { + std::cout << " dl:" << std::hex << +context.registers.dl() << "; "; + std::cout << "ah:" << std::hex << +context.registers.ah() << "; "; + std::cout << "ch:" << std::hex << +context.registers.ch() << "; "; + std::cout << "cl:" << std::hex << +context.registers.cl() << "; "; + std::cout << "dh:" << std::hex << +context.registers.dh() << "; "; + std::cout << "es:" << std::hex << +context.registers.es() << "; "; + std::cout << "bx:" << std::hex << +context.registers.bx(); + } + + std::cout << std::endl; +// previous = next; +// } + }*/ + + // Execute it. + InstructionSet::x86::perform( + decoded.second, + context + ); + } + // MARK: - ScanProducer. void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { video_.set_scan_target(scan_target); @@ -1205,8 +1237,8 @@ Machine *Machine::PCCompatible(const Analyser::Static::Target *target, const ROM const Target *const pc_target = dynamic_cast(target); switch(pc_target->adaptor) { - case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine(*pc_target, rom_fetcher); - case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine(*pc_target, rom_fetcher); + case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine(*pc_target, rom_fetcher); + case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine(*pc_target, rom_fetcher); default: return nullptr; } } From 664890cead276631b9e0484f9435968478bb975b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 8 Dec 2023 10:46:59 -0500 Subject: [PATCH 2/9] Expose speed as an option. --- Analyser/Static/PCCompatible/Target.hpp | 9 ++++++++- Machines/PCCompatible/PCCompatible.cpp | 16 ++++++++++++---- .../xcshareddata/xcschemes/Clock Signal.xcscheme | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Analyser/Static/PCCompatible/Target.hpp b/Analyser/Static/PCCompatible/Target.hpp index 0d7c3f759..271bdabcb 100644 --- a/Analyser/Static/PCCompatible/Target.hpp +++ b/Analyser/Static/PCCompatible/Target.hpp @@ -20,10 +20,17 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl(target); - switch(pc_target->adaptor) { - case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine(*pc_target, rom_fetcher); - case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine(*pc_target, rom_fetcher); - default: return nullptr; + if(pc_target->speed == Target::Speed::Fast) { + switch(pc_target->adaptor) { + case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine(*pc_target, rom_fetcher); + case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine(*pc_target, rom_fetcher); + default: return nullptr; + } + } else { + switch(pc_target->adaptor) { + case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine(*pc_target, rom_fetcher); + case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine(*pc_target, rom_fetcher); + default: return nullptr; + } } } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 474fa352e..19b54b90e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -62,7 +62,7 @@ Date: Fri, 8 Dec 2023 11:02:41 -0500 Subject: [PATCH 3/9] Tie snow avoidance to avoiding DEN. --- Machines/PCCompatible/CGA.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/PCCompatible/CGA.hpp b/Machines/PCCompatible/CGA.hpp index 85a061159..34c544c03 100644 --- a/Machines/PCCompatible/CGA.hpp +++ b/Machines/PCCompatible/CGA.hpp @@ -67,7 +67,7 @@ class CGA { // b1: 1 => positive edge from light pen has set trigger; // b0: 1 => safe to write to VRAM now without causing snow. (crtc_.get_bus_state().vsync ? 0b1001 : 0b0000) | - (crtc_.get_bus_state().hsync ? 0b0001 : 0b0000) | + (crtc_.get_bus_state().display_enable ? 0b0000 : 0b0001) | 0b0100; default: return 0xff; From 2ee873d1a17a76fef8af3af6ff878fb3b82f6f4d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 8 Dec 2023 11:07:14 -0500 Subject: [PATCH 4/9] This isn't intended to be exact. --- Analyser/Static/PCCompatible/Target.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Analyser/Static/PCCompatible/Target.hpp b/Analyser/Static/PCCompatible/Target.hpp index 271bdabcb..95e44ce78 100644 --- a/Analyser/Static/PCCompatible/Target.hpp +++ b/Analyser/Static/PCCompatible/Target.hpp @@ -21,9 +21,9 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl Date: Fri, 8 Dec 2023 11:55:00 -0500 Subject: [PATCH 5/9] Revise colour burst phase guess. --- Machines/PCCompatible/CGA.hpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Machines/PCCompatible/CGA.hpp b/Machines/PCCompatible/CGA.hpp index 34c544c03..4d0742b70 100644 --- a/Machines/PCCompatible/CGA.hpp +++ b/Machines/PCCompatible/CGA.hpp @@ -178,6 +178,7 @@ class CGA { // Determine new output state. update_hsync(state.hsync); const OutputState new_state = implied_state(state); + static constexpr uint8_t colour_phase = 200; // Upon either a state change or just having accumulated too much local time... if( @@ -190,7 +191,7 @@ class CGA { // (1) flush preexisting state. if(count) { switch(output_state) { - case OutputState::Sync: crt.output_sync(count * active_clock_divider); break; + case OutputState::Sync: crt.output_sync(count * active_clock_divider); break; case OutputState::Border: if(active_border_colour) { crt.output_blank(count * active_clock_divider); @@ -198,8 +199,8 @@ class CGA { crt.output_level(count * active_clock_divider, active_border_colour); } break; - case OutputState::ColourBurst: crt.output_colour_burst(count * active_clock_divider, 0); break; - case OutputState::Pixels: flush_pixels(); break; + case OutputState::ColourBurst: crt.output_colour_burst(count * active_clock_divider, colour_phase); break; + case OutputState::Pixels: flush_pixels(); break; } } @@ -306,15 +307,18 @@ class CGA { // Apply blink or background intensity. if(control_ & 0x20) { - // Potentially remap dark yellow to brown. - colours[0] = yellow_to_brown(colours[0]); - + // Set both colours to black if within a blink; otherwise consider a yellow-to-brown conversion. if((attributes & 0x80) && (state.field_count & 16)) { - std::swap(colours[0], colours[1]); + colours[0] = colours[1] = 0; + } else { + colours[0] = yellow_to_brown(colours[0]); } } else { if(attributes & 0x80) { colours[0] = bright(colours[0]); + } else { + // Yellow to brown definitely doesn't apply if the colour has been brightened. + colours[0] = yellow_to_brown(colours[0]); } } From 0bf453199cf75e8ebe09c885569b44f991e0699d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 8 Dec 2023 12:51:55 -0500 Subject: [PATCH 6/9] Default to 'fast'. --- Analyser/Static/PCCompatible/Target.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analyser/Static/PCCompatible/Target.hpp b/Analyser/Static/PCCompatible/Target.hpp index 95e44ce78..de84c8fa0 100644 --- a/Analyser/Static/PCCompatible/Target.hpp +++ b/Analyser/Static/PCCompatible/Target.hpp @@ -23,7 +23,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl Date: Fri, 8 Dec 2023 14:03:29 -0500 Subject: [PATCH 7/9] Undo accidental build-type switch. --- .../xcshareddata/xcschemes/Clock Signal.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 19b54b90e..474fa352e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -62,7 +62,7 @@ Date: Fri, 8 Dec 2023 14:23:27 -0500 Subject: [PATCH 8/9] Connect option to macOS UI. --- .../Machine/StaticAnalyser/CSStaticAnalyser.h | 7 +++-- .../StaticAnalyser/CSStaticAnalyser.mm | 6 +++- .../Base.lproj/MachinePicker.xib | 29 ++++++++++++++++++- .../MachinePicker/MachinePicker.swift | 10 ++++++- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h index 1dca59a35..1df0c77b7 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h @@ -123,8 +123,9 @@ typedef NS_ENUM(NSInteger, CSMachineMSXRegion) { CSMachineMSXRegionJapanese, }; -typedef NS_ENUM(NSInteger, CSPCCompatibleModel) { - CSPCCompatibleModelTurboXT, +typedef NS_ENUM(NSInteger, CSPCCompatibleSpeed) { + CSPCCompatibleSpeedOriginal, + CSPCCompatibleSpeedTurbo, }; typedef NS_ENUM(NSInteger, CSPCCompatibleVideoAdaptor) { @@ -152,7 +153,7 @@ typedef int Kilobytes; - (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540; - (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM; - (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize; -- (instancetype)initWithPCCompatibleModel:(CSPCCompatibleModel)model videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor; +- (instancetype)initWithPCCompatibleSpeed:(CSPCCompatibleSpeed)speed videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor; @property(nonatomic, readonly, nullable) NSString *optionsNibName; @property(nonatomic, readonly) NSString *displayName; diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 7ed70dbde..9bdcc1f9e 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -273,7 +273,7 @@ return self; } -- (instancetype)initWithPCCompatibleModel:(CSPCCompatibleModel)model videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor { +- (instancetype)initWithPCCompatibleSpeed:(CSPCCompatibleSpeed)speed videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor { self = [super init]; if(self) { using Target = Analyser::Static::PCCompatible::Target; @@ -282,6 +282,10 @@ case CSPCCompatibleVideoAdaptorMDA: target->adaptor = Target::VideoAdaptor::MDA; break; case CSPCCompatibleVideoAdaptorCGA: target->adaptor = Target::VideoAdaptor::CGA; break; } + switch(speed) { + case CSPCCompatibleSpeedOriginal: target->speed = Target::Speed::ApproximatelyOriginal; break; + case CSPCCompatibleSpeedTurbo: target->speed = Target::Speed::Fast; break; + } _targets.push_back(std::move(target)); } return self; diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index 58ae4b8f1..d2ae6f057 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib +++ b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib @@ -750,13 +750,39 @@ Gw + + + + + + + + + + + + + + + + + + + + + + + + + - + + @@ -1069,6 +1095,7 @@ Gw + diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift index 168b2fc85..fa2462c33 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift +++ b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift @@ -65,6 +65,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate { // MARK: - PC compatible properties @IBOutlet var pcVideoAdaptorButton: NSPopUpButton! + @IBOutlet var pcSpeedButton: NSPopUpButton! // MARK: - Spectrum properties @IBOutlet var spectrumModelTypeButton: NSPopUpButton! @@ -155,6 +156,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate { // PC settings pcVideoAdaptorButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.pcVideoAdaptor")) + pcSpeedButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.pcSpeed")) // Spectrum settings spectrumModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.spectrumModel")) @@ -224,6 +226,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate { // PC settings standardUserDefaults.set(pcVideoAdaptorButton.selectedTag(), forKey: "new.pcVideoAdaptor") + standardUserDefaults.set(pcSpeedButton.selectedTag(), forKey: "new.pcSpeed") // Spectrum settings standardUserDefaults.set(spectrumModelTypeButton.selectedTag(), forKey: "new.spectrumModel") @@ -416,7 +419,12 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate { case 1: videoAdaptor = .CGA default: break } - return CSStaticAnalyser(pcCompatibleModel: .turboXT, videoAdaptor: videoAdaptor) + var speed: CSPCCompatibleSpeed = .original + switch pcSpeedButton.selectedTag() { + case 80286: speed = .turbo + default: break + } + return CSStaticAnalyser(pcCompatibleSpeed: speed, videoAdaptor: videoAdaptor) case "spectrum": var model: CSMachineSpectrumModel = .plus2a From f823310fe34b42259ca74cc0797af2da250c486b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 8 Dec 2023 14:29:23 -0500 Subject: [PATCH 9/9] Add an appropriate crop. --- Machines/PCCompatible/MDA.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/PCCompatible/MDA.hpp b/Machines/PCCompatible/MDA.hpp index ff18706ce..72a0a5a36 100644 --- a/Machines/PCCompatible/MDA.hpp +++ b/Machines/PCCompatible/MDA.hpp @@ -90,7 +90,7 @@ class MDA { // TODO: really this should be a Luminance8 and set an appropriate modal tint colour; // consider whether that's worth building into the scan target. { -// crt.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f)); + crt.set_visible_area(Outputs::Display::Rect(0.028f, 0.025f, 0.98f, 0.98f)); crt.set_display_type(Outputs::Display::DisplayType::RGB); }