// // PCCompatible.cpp // Clock Signal // // Created by Thomas Harte on 15/11/2023. // Copyright © 2023 Thomas Harte. All rights reserved. // #include "PCCompatible.hpp" #include "CGA.hpp" #include "CPUControl.hpp" #include "DMA.hpp" #include "FloppyController.hpp" #include "KeyboardController.hpp" #include "KeyboardMapper.hpp" #include "LinearMemory.hpp" #include "MDA.hpp" #include "SegmentedMemory.hpp" #include "PIC.hpp" #include "PIT.hpp" #include "ProcessorByModel.hpp" #include "RTC.hpp" #include "Speaker.hpp" #include "Activity/Source.hpp" #include "InstructionSets/x86/Decoder.hpp" #include "InstructionSets/x86/Flags.hpp" #include "InstructionSets/x86/Instruction.hpp" #include "InstructionSets/x86/Interrupts.hpp" #include "InstructionSets/x86/Perform.hpp" #include "Components/8255/i8255.hpp" #include "Numeric/RegisterSizes.hpp" #include "Outputs/CRT/CRT.hpp" #include "Outputs/Log.hpp" #include "Outputs/Speaker/Speaker.hpp" #include "Machines/AudioProducer.hpp" #include "Machines/KeyboardMachine.hpp" #include "Machines/MediaTarget.hpp" #include "Machines/ScanProducer.hpp" #include "Machines/TimedMachine.hpp" #include "Analyser/Static/PCCompatible/Target.hpp" #include #include #include namespace PCCompatible { namespace { Log::Logger log; } using Target = Analyser::Static::PCCompatible::Target; // Map from members of the VideoAdaptor enum to concrete class types. template struct Adaptor; template <> struct Adaptor { using type = MDA; }; template <> struct Adaptor { using type = CGA; }; template class PITObserver { public: PITObserver(PICs &pics, Speaker &speaker) : pics_(pics), speaker_(speaker) {} template void update_output(const bool new_level) { // channel 0 is connected to IRQ 0; // channel 1 is used for DRAM refresh (presumably connected to DMA?); // channel 2 is gated by a PPI output and feeds into the speaker. switch(channel) { default: break; case 0: pics_.pic[0].template apply_edge<0>(new_level); break; case 2: speaker_.set_pit(new_level); break; } } private: PICs &pics_; Speaker &speaker_; }; template using PIT = i8253>; template class i8255PortHandler : public Intel::i8255::PortHandler { public: i8255PortHandler( Speaker &speaker, KeyboardController &keyboard, const Target::VideoAdaptor adaptor, const int drive_count ) : speaker_(speaker), keyboard_(keyboard) { // High switches: // // b3, b2: drive count; 00 = 1, 01 = 2, etc // b1, b0: video mode (00 = ROM; 01 = CGA40; 10 = CGA80; 11 = MDA) switch(adaptor) { default: break; case Target::VideoAdaptor::MDA: high_switches_ |= 0b11; break; case Target::VideoAdaptor::CGA: high_switches_ |= 0b10; break; // Assume 80 columns. } high_switches_ |= uint8_t(drive_count << 2); // Low switches: // // b3, b2: RAM on motherboard (64 * bit pattern) // b1: 1 => FPU present; 0 => absent; // b0: 1 => floppy drive present; 0 => absent. low_switches_ |= 0b1100; // Assume maximum RAM. if(drive_count) low_switches_ |= 0xb0001; } /// Supplies a hint about the user's display choice. If the high switches haven't been read yet and this is a CGA device, /// this hint will be used to select between 40- and 80-column default display. void hint_is_composite(const bool composite) { if(high_switches_observed_) { return; } switch(high_switches_ & 3) { // Do nothing if a non-CGA card is in use. case 0b00: case 0b11: break; default: high_switches_ &= ~0b11; high_switches_ |= composite ? 0b01 : 0b10; break; } } void set_value(const int port, const uint8_t value) { switch(port) { case 1: // b7: 0 => enable keyboard read (and IRQ); 1 => don't; // b6: 0 => hold keyboard clock low; 1 => don't; // b5: 1 => disable IO check; 0 => don't; // b4: 1 => disable memory parity check; 0 => don't; // b3: [5150] cassette motor control; [5160] high or low switches select; // b2: [5150] high or low switches select; [5160] 1 => disable turbo mode; // b1, b0: speaker control. enable_keyboard_ = !(value & 0x80); keyboard_.set_mode(value >> 6); use_high_switches_ = value & 0x08; speaker_.set_control(value & 0x01, value & 0x02); break; } } uint8_t get_value(const int port) { switch(port) { case 0: high_switches_observed_ = true; return enable_keyboard_ ? keyboard_.read() : uint8_t((high_switches_ << 4) | low_switches_); // Guesses that switches is high and low combined as below. case 2: // b7: 1 => memory parity error; 0 => none; // b6: 1 => IO channel error; 0 => none; // b5: timer 2 output; [TODO] // b4: cassette data input; [TODO] // b3...b0: whichever of the high and low switches is selected. high_switches_observed_ |= use_high_switches_; return use_high_switches_ ? high_switches_ : low_switches_; } return 0; }; private: bool high_switches_observed_ = false; uint8_t high_switches_ = 0; uint8_t low_switches_ = 0; bool use_high_switches_ = false; Speaker &speaker_; KeyboardController &keyboard_; bool enable_keyboard_ = false; }; template using PPI = Intel::i8255::i8255>; template class IO { public: IO( PIT &pit, DMA &dma, PPI &ppi, PICs &pics, typename Adaptor