From afc0ca3f1b4ed811e196c6b5bef4cbfb0229c4a4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 17 Nov 2023 17:35:11 -0500 Subject: [PATCH 01/26] Add XT roadmap. --- Machines/PCCompatible/PCCompatible.cpp | 58 +++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 53030ad27..4bb5c17c1 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -283,10 +283,66 @@ class IO { case 0x00a0: printf("TODO: NMIs %s\n", (value & 0x80) ? "masked" : "unmasked"); break; + + case 0x0000: case 0x0001: case 0x0002: case 0x0003: + case 0x0004: case 0x0005: case 0x0006: case 0x0007: + case 0x0008: case 0x0009: case 0x000a: case 0x000b: + case 0x000c: case 0x000d: case 0x000e: case 0x000f: + printf("TODO: DMA write of %02x at %04x\n", value, port); + break; + + case 0x0060: case 0x0061: case 0x0062: case 0x0063: + case 0x0064: case 0x0065: case 0x0066: case 0x0067: + case 0x0068: case 0x0069: case 0x006a: case 0x006b: + case 0x006c: case 0x006d: case 0x006e: case 0x006f: + printf("TODO: PPI write of %02x at %04x\n", value, port); + break; + + case 0x0080: case 0x0081: case 0x0082: case 0x0083: + case 0x0084: case 0x0085: case 0x0086: case 0x0087: + case 0x0088: case 0x0089: case 0x008a: case 0x008b: + case 0x008c: case 0x008d: case 0x008e: case 0x008f: + printf("TODO: DMA page write of %02x at %04x\n", value, port); + break; + + case 0x03b0: case 0x03b1: case 0x03b2: case 0x03b3: + case 0x03b4: case 0x03b5: case 0x03b6: case 0x03b7: + case 0x03b8: case 0x03b9: case 0x03ba: case 0x03bb: + case 0x03bc: case 0x03bd: case 0x03be: case 0x03bf: + printf("TODO: MDA write of %02x at %04x\n", value, port); + break; + + case 0x03d0: case 0x03d1: case 0x03d2: case 0x03d3: + case 0x03d4: case 0x03d5: case 0x03d6: case 0x03d7: + case 0x03d8: case 0x03d9: case 0x03da: case 0x03db: + case 0x03dc: case 0x03dd: case 0x03de: case 0x03df: + printf("TODO: CGA write of %02x at %04x\n", value, port); + break; + + case 0x0040: case 0x0041: case 0x0042: case 0x0043: + case 0x0044: case 0x0045: case 0x0046: case 0x0047: + printf("TODO: PIT write of %02x at %04x\n", value, port); + break; } } template IntT in([[maybe_unused]] uint16_t port) { - printf("Unhandled in: %04x\n", port); + switch(port) { + default: + printf("Unhandled in: %04x\n", port); + break; + + case 0x0040: case 0x0041: case 0x0042: case 0x0043: + case 0x0044: case 0x0045: case 0x0046: case 0x0047: + printf("TODO: PIT read from %04x\n", port); + break; + + case 0x0060: case 0x0061: case 0x0062: case 0x0063: + case 0x0064: case 0x0065: case 0x0066: case 0x0067: + case 0x0068: case 0x0069: case 0x006a: case 0x006b: + case 0x006c: case 0x006d: case 0x006e: case 0x006f: + printf("TODO: PPI read from %04x\n", port); + break; + } return IntT(~0); } From a91449555ff30ac1b968af4e4aa9aeb9dd783232 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 17 Nov 2023 17:38:17 -0500 Subject: [PATCH 02/26] Add link for future self. --- Machines/PCCompatible/PCCompatible.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 4bb5c17c1..95f7a3f72 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -295,6 +295,7 @@ class IO { case 0x0064: case 0x0065: case 0x0066: case 0x0067: case 0x0068: case 0x0069: case 0x006a: case 0x006b: case 0x006c: case 0x006d: case 0x006e: case 0x006f: + // Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol printf("TODO: PPI write of %02x at %04x\n", value, port); break; From 2b69081fff3d0af30171cfed2194a005b425011a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 19 Nov 2023 07:15:30 -0500 Subject: [PATCH 03/26] Start sketching the PIT. --- Machines/PCCompatible/PCCompatible.cpp | 51 +++++++++++++++++++------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 95f7a3f72..2ec3118bd 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -20,6 +20,27 @@ namespace PCCompatible { +class PIT { + public: + template uint8_t read() { + return 0; + } + + template void write([[maybe_unused]] uint8_t value) { + } + + void set_mode([[maybe_unused]] uint8_t value) { + const int channel = (value >> 6) & 3; + if(channel == 3) { + return; + } + } + + private: + enum class LatchMode { + }; +}; + struct Registers { public: static constexpr bool is_32bit = false; @@ -269,6 +290,8 @@ struct Memory { class IO { public: + IO(PIT &pit) : pit_(pit) {} + template void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) { switch(port) { default: @@ -320,10 +343,10 @@ class IO { printf("TODO: CGA write of %02x at %04x\n", value, port); break; - case 0x0040: case 0x0041: case 0x0042: case 0x0043: - case 0x0044: case 0x0045: case 0x0046: case 0x0047: - printf("TODO: PIT write of %02x at %04x\n", value, port); - break; + case 0x0040: pit_.write<0>(uint8_t(value)); break; + case 0x0041: pit_.write<1>(uint8_t(value)); break; + case 0x0042: pit_.write<2>(uint8_t(value)); break; + case 0x0043: pit_.set_mode(uint8_t(value)); break; } } template IntT in([[maybe_unused]] uint16_t port) { @@ -332,10 +355,9 @@ class IO { printf("Unhandled in: %04x\n", port); break; - case 0x0040: case 0x0041: case 0x0042: case 0x0043: - case 0x0044: case 0x0045: case 0x0046: case 0x0047: - printf("TODO: PIT read from %04x\n", port); - break; + case 0x0040: return pit_.read<0>(); + case 0x0041: return pit_.read<1>(); + case 0x0042: return pit_.read<2>(); case 0x0060: case 0x0061: case 0x0062: case 0x0063: case 0x0064: case 0x0065: case 0x0066: case 0x0067: @@ -348,7 +370,7 @@ class IO { } private: - + PIT &pit_; }; class FlowController { @@ -396,8 +418,8 @@ class ConcreteMachine: public: ConcreteMachine( [[maybe_unused]] const Analyser::Static::Target &target, - [[maybe_unused]] const ROMMachine::ROMFetcher &rom_fetcher - ) { + const ROMMachine::ROMFetcher &rom_fetcher + ) : context(pit_) { // This is actually a MIPS count; try 3 million. set_clock_rate(3'000'000); @@ -451,11 +473,14 @@ class ConcreteMachine: } private: + PIT pit_; + struct Context { - Context() : + Context(PIT &pit) : segments(registers), memory(registers, segments), - flow_controller(registers, segments) + flow_controller(registers, segments), + io(pit) { reset(); } From 56aa9d101a7d9e189c43b6000233d337d256518a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 19 Nov 2023 14:59:52 -0500 Subject: [PATCH 04/26] Decode PIT mode writes. --- Machines/PCCompatible/PCCompatible.cpp | 49 ++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 2ec3118bd..df1607645 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -20,6 +20,7 @@ namespace PCCompatible { +template class PIT { public: template uint8_t read() { @@ -29,16 +30,58 @@ class PIT { template void write([[maybe_unused]] uint8_t value) { } - void set_mode([[maybe_unused]] uint8_t value) { - const int channel = (value >> 6) & 3; - if(channel == 3) { + void set_mode(uint8_t value) { + const int channel_id = (value >> 6) & 3; + if(channel_id == 3) { + read_back_ = is_8254; return; } + + Channel &channel = channels_[channel_id]; + + channel.is_bcd = value & 1; + switch((value >> 1) & 3) { + default: channel.latch_value(); break; + + case 1: channel.latch_mode = LatchMode::LowOnly; break; + case 2: channel.latch_mode = LatchMode::HighOnly; break; + case 3: channel.latch_mode = LatchMode::LowHigh; break; + } + + const auto operating_mode = (value >> 3) & 7; + switch(operating_mode) { + default: channel.mode = OperatingMode(operating_mode); break; + case 6: channel.mode = OperatingMode::RateGenerator; break; + case 7: channel.mode = OperatingMode::SquareWaveGenerator; break; + } } private: + // Supported only on 8254s. + bool read_back_ = false; + enum class LatchMode { + LowOnly, + HighOnly, + LowHigh, }; + + enum class OperatingMode { + InterruptOnTerminalCount = 0, + HardwareRetriggerableOneShot = 1, + RateGenerator = 2, + SquareWaveGenerator = 3, + SoftwareTriggeredStrobe = 4, + HardwareTriggeredStrobe = 5, + }; + + struct Channel { + LatchMode latch_mode = LatchMode::LowHigh; + OperatingMode mode = OperatingMode::InterruptOnTerminalCount; + bool is_bcd = false; + + void latch_value() {} + } channels_[3]; }; struct Registers { From af885ccf0880edb88cd220be59344319ebf4a1c4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 19 Nov 2023 14:59:52 -0500 Subject: [PATCH 05/26] Decode PIT mode writes. --- Machines/PCCompatible/PCCompatible.cpp | 49 ++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 2ec3118bd..df1607645 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -20,6 +20,7 @@ namespace PCCompatible { +template class PIT { public: template uint8_t read() { @@ -29,16 +30,58 @@ class PIT { template void write([[maybe_unused]] uint8_t value) { } - void set_mode([[maybe_unused]] uint8_t value) { - const int channel = (value >> 6) & 3; - if(channel == 3) { + void set_mode(uint8_t value) { + const int channel_id = (value >> 6) & 3; + if(channel_id == 3) { + read_back_ = is_8254; return; } + + Channel &channel = channels_[channel_id]; + + channel.is_bcd = value & 1; + switch((value >> 1) & 3) { + default: channel.latch_value(); break; + + case 1: channel.latch_mode = LatchMode::LowOnly; break; + case 2: channel.latch_mode = LatchMode::HighOnly; break; + case 3: channel.latch_mode = LatchMode::LowHigh; break; + } + + const auto operating_mode = (value >> 3) & 7; + switch(operating_mode) { + default: channel.mode = OperatingMode(operating_mode); break; + case 6: channel.mode = OperatingMode::RateGenerator; break; + case 7: channel.mode = OperatingMode::SquareWaveGenerator; break; + } } private: + // Supported only on 8254s. + bool read_back_ = false; + enum class LatchMode { + LowOnly, + HighOnly, + LowHigh, }; + + enum class OperatingMode { + InterruptOnTerminalCount = 0, + HardwareRetriggerableOneShot = 1, + RateGenerator = 2, + SquareWaveGenerator = 3, + SoftwareTriggeredStrobe = 4, + HardwareTriggeredStrobe = 5, + }; + + struct Channel { + LatchMode latch_mode = LatchMode::LowHigh; + OperatingMode mode = OperatingMode::InterruptOnTerminalCount; + bool is_bcd = false; + + void latch_value() {} + } channels_[3]; }; struct Registers { From 05e93f0eb39c8d11b4d2c14f1b53fe427b324f88 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 19 Nov 2023 15:52:32 -0500 Subject: [PATCH 06/26] Implementing counting for a couple of PIT modes. --- Machines/PCCompatible/PCCompatible.cpp | 150 +++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 10 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index df1607645..6cfb61b00 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -24,16 +24,20 @@ template class PIT { public: template uint8_t read() { - return 0; + return channels_[channel].read(); } - template void write([[maybe_unused]] uint8_t value) { + template void write(uint8_t value) { + printf("Write to %d\n", channel); + channels_[channel].write(value); } void set_mode(uint8_t value) { const int channel_id = (value >> 6) & 3; if(channel_id == 3) { read_back_ = is_8254; + + // TODO: decode rest of read-back command. return; } @@ -47,6 +51,7 @@ class PIT { case 2: channel.latch_mode = LatchMode::HighOnly; break; case 3: channel.latch_mode = LatchMode::LowHigh; break; } + channel.next_write_high = false; const auto operating_mode = (value >> 3) & 7; switch(operating_mode) { @@ -54,6 +59,32 @@ class PIT { case 6: channel.mode = OperatingMode::RateGenerator; break; case 7: channel.mode = OperatingMode::SquareWaveGenerator; break; } + + printf("%d switches to mode %d\n", channel_id, int(channel.mode)); + + // Set up operating mode. + switch(channel.mode) { + case OperatingMode::InterruptOnTerminalCount: + channel.output = false; + channel.awaiting_reload = true; + break; + + case OperatingMode::RateGenerator: + channel.output = true; + channel.awaiting_reload = true; + break; + } + } + + void run_for(Cycles cycles) { + // TODO: be intelligent enough to take ticks outside the loop when appropriate. + auto ticks = cycles.as(); + while(ticks--) { + bool output_changed; + output_changed = channels_[0].advance(1); + output_changed |= channels_[1].advance(1); + output_changed |= channels_[2].advance(1); + } } private: @@ -80,8 +111,100 @@ class PIT { OperatingMode mode = OperatingMode::InterruptOnTerminalCount; bool is_bcd = false; - void latch_value() {} + bool gated = false; + bool awaiting_reload = true; + + uint16_t counter = 0; + uint16_t reload = 0; + uint16_t latch = 0; + bool output = false; + + bool next_write_high = false; + + void latch_value() { + latch = counter; + } + + bool advance(int ticks) { + if(gated || awaiting_reload) return false; + + // TODO: BCD mode is completely ignored below. Possibly not too important. + const bool initial_output = output; + switch(mode) { + case OperatingMode::InterruptOnTerminalCount: + // Output goes permanently high upon a tick from 1 to 0; reload value is not used on wraparound. + output |= counter <= ticks; + counter -= ticks; + break; + + case OperatingMode::RateGenerator: + // Output goes low upon a tick from 2 to 1. It goes high again on 1 to 0, and the reload value is used. + if(counter <= ticks) { + counter = reload - ticks + counter; + } else { + counter -= ticks; + } + output = counter != 1; + break; + + default: + // TODO. + break; + } + + return output != initial_output; + } + + void write(uint8_t value) { + switch(latch_mode) { + case LatchMode::LowOnly: + reload = (reload & 0xff00) | value; + break; + case LatchMode::HighOnly: + reload = (reload & 0x00ff) | (value << 8); + break; + case LatchMode::LowHigh: + if(!next_write_high) { + reload = (reload & 0xff00) | value; + next_write_high = true; + return; + } + + reload = (reload & 0x00ff) | (value << 8); + next_write_high = false; + break; + } + + awaiting_reload = false; + + switch(mode) { + case OperatingMode::InterruptOnTerminalCount: + case OperatingMode::RateGenerator: + counter = reload; + break; + } + } + + uint8_t read() { + switch(latch_mode) { + case LatchMode::LowOnly: return uint8_t(latch); + case LatchMode::HighOnly: return uint8_t(latch >> 8); + default: + case LatchMode::LowHigh: + next_write_high ^= true; + return next_write_high ? uint8_t(latch) : uint8_t(latch >> 8); + break; + } + } } channels_[3]; + + // TODO: + // + // channel 0 is connected to IRQ 0; + // channel 1 is used for DRAM refresh; + // channel 2 is gated by a PPI output and feeds into the speaker. + // + // RateGenerator: output goes high if gated. }; struct Registers { @@ -333,7 +456,7 @@ struct Memory { class IO { public: - IO(PIT &pit) : pit_(pit) {} + IO(PIT &pit) : pit_(pit) {} template void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) { switch(port) { @@ -413,7 +536,7 @@ class IO { } private: - PIT &pit_; + PIT &pit_; }; class FlowController { @@ -459,12 +582,16 @@ class ConcreteMachine: public MachineTypes::ScanProducer { public: + static constexpr int PitMultiplier = 1; + static constexpr int PitDivisor = 3; + ConcreteMachine( [[maybe_unused]] const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher ) : context(pit_) { - // This is actually a MIPS count; try 3 million. - set_clock_rate(3'000'000); + // Use clock rate as a MIPS count; keeping it as a multiple or divisor of the PIT frequency is easy. + static constexpr int pit_frequency = 1'193'182; + set_clock_rate(double(pit_frequency) * double(PitMultiplier) / double(PitDivisor)); // i.e. almost 0.4 MIPS for an XT. // Fetch the BIOS. [8088 only, for now] const auto bios = ROM::Name::PCCompatibleGLaBIOS; @@ -480,9 +607,12 @@ class ConcreteMachine: } // MARK: - TimedMachine. - void run_for([[maybe_unused]] const Cycles cycles) override { + void run_for(const Cycles cycles) override { auto instructions = cycles.as_integral(); while(instructions--) { + // First draft: all hardware runs in lockstep. + pit_.run_for(PitDivisor / PitMultiplier); + // Get the next thing to execute into decoded. if(!context.flow_controller.should_repeat()) { // Decode from the current IP. @@ -516,10 +646,10 @@ class ConcreteMachine: } private: - PIT pit_; + PIT pit_; struct Context { - Context(PIT &pit) : + Context(PIT &pit) : segments(registers), memory(registers, segments), flow_controller(registers, segments), From a8f1c72f5c2d7840235bb90791d11a55d56e4df1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 19 Nov 2023 16:05:44 -0500 Subject: [PATCH 07/26] Take a caveman run at debugging. --- Machines/PCCompatible/PCCompatible.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 6cfb61b00..c3d70feb0 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -24,7 +24,9 @@ template class PIT { public: template uint8_t read() { - return channels_[channel].read(); + const auto result = channels_[channel].read(); + printf("Read from %d; %02x\n", channel, result); + return result; } template void write(uint8_t value) { @@ -41,6 +43,8 @@ class PIT { return; } + printf("Set mode on %d\n", channel_id); + Channel &channel = channels_[channel_id]; channel.is_bcd = value & 1; @@ -60,10 +64,12 @@ class PIT { case 7: channel.mode = OperatingMode::SquareWaveGenerator; break; } - printf("%d switches to mode %d\n", channel_id, int(channel.mode)); - // Set up operating mode. switch(channel.mode) { + default: + printf("%d switches to unimplemented mode %d\n", channel_id, int(channel.mode)); + break; + case OperatingMode::InterruptOnTerminalCount: channel.output = false; channel.awaiting_reload = true; From 4e077701c92a9911373c2e239fe3c717b36c9611 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 19 Nov 2023 16:37:47 -0500 Subject: [PATCH 08/26] Exit without further modification upon latch. --- Machines/PCCompatible/PCCompatible.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index c3d70feb0..372ccf1be 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -46,15 +46,16 @@ class PIT { printf("Set mode on %d\n", channel_id); Channel &channel = channels_[channel_id]; - - channel.is_bcd = value & 1; switch((value >> 1) & 3) { - default: channel.latch_value(); break; + default: + channel.latch_value(); + return; case 1: channel.latch_mode = LatchMode::LowOnly; break; case 2: channel.latch_mode = LatchMode::HighOnly; break; case 3: channel.latch_mode = LatchMode::LowHigh; break; } + channel.is_bcd = value & 1; channel.next_write_high = false; const auto operating_mode = (value >> 3) & 7; From 119c83eb183aad7cb58a80bafc1e8073f525997b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 19 Nov 2023 21:51:27 -0500 Subject: [PATCH 09/26] Fix field decoding. --- Machines/PCCompatible/PCCompatible.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 372ccf1be..975766c40 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -46,7 +46,8 @@ class PIT { printf("Set mode on %d\n", channel_id); Channel &channel = channels_[channel_id]; - switch((value >> 1) & 3) { + channel.next_write_high = false; + switch((value >> 4) & 3) { default: channel.latch_value(); return; @@ -58,7 +59,7 @@ class PIT { channel.is_bcd = value & 1; channel.next_write_high = false; - const auto operating_mode = (value >> 3) & 7; + const auto operating_mode = (value >> 1) & 7; switch(operating_mode) { default: channel.mode = OperatingMode(operating_mode); break; case 6: channel.mode = OperatingMode::RateGenerator; break; From 55f466f2fac10f767585b186f2cc83c96449f329 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 19 Nov 2023 22:55:29 -0500 Subject: [PATCH 10/26] Add enough of the DMA subsystem to trip over in PPI world. --- Machines/PCCompatible/PCCompatible.cpp | 120 +++++++++++++++++++++---- 1 file changed, 103 insertions(+), 17 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 975766c40..8cc452990 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -13,6 +13,8 @@ #include "../../InstructionSets/x86/Instruction.hpp" #include "../../InstructionSets/x86/Perform.hpp" +#include "../../Numeric/RegisterSizes.hpp" + #include "../ScanProducer.hpp" #include "../TimedMachine.hpp" @@ -20,6 +22,71 @@ namespace PCCompatible { +class DMA { + public: + void flip_flop_reset() { + next_access_low = true; + } + + void mask_reset() { + // TODO: set all mask bits off. + } + + void master_reset() { + flip_flop_reset(); + // TODO: clear status, set all mask bits on. + } + + template + void write(uint8_t value) { + constexpr int channel = (address >> 1) & 3; + constexpr bool is_count = address & 1; + + next_access_low ^= true; + if(next_access_low) { + if constexpr (is_count) { + channels_[channel].count.halves.high = value; + } else { + channels_[channel].address.halves.high = value; + } + } else { + if constexpr (is_count) { + channels_[channel].count.halves.low = value; + } else { + channels_[channel].address.halves.low = value; + } + } + } + + template + uint8_t read() { + constexpr int channel = (address >> 1) & 3; + constexpr bool is_count = address & 1; + + next_access_low ^= true; + if(next_access_low) { + if constexpr (is_count) { + return channels_[channel].count.halves.high; + } else { + return channels_[channel].address.halves.high; + } + } else { + if constexpr (is_count) { + return channels_[channel].count.halves.low; + } else { + return channels_[channel].address.halves.low; + } + } + } + + private: + bool next_access_low = true; + + struct Channel { + CPU::RegisterPair16 address, count; + } channels_[4]; +}; + template class PIT { public: @@ -46,7 +113,6 @@ class PIT { printf("Set mode on %d\n", channel_id); Channel &channel = channels_[channel_id]; - channel.next_write_high = false; switch((value >> 4) & 3) { default: channel.latch_value(); @@ -57,7 +123,7 @@ class PIT { case 3: channel.latch_mode = LatchMode::LowHigh; break; } channel.is_bcd = value & 1; - channel.next_write_high = false; + channel.next_access_high = false; const auto operating_mode = (value >> 1) & 7; switch(operating_mode) { @@ -127,7 +193,7 @@ class PIT { uint16_t latch = 0; bool output = false; - bool next_write_high = false; + bool next_access_high = false; void latch_value() { latch = counter; @@ -169,17 +235,16 @@ class PIT { reload = (reload & 0xff00) | value; break; case LatchMode::HighOnly: - reload = (reload & 0x00ff) | (value << 8); + reload = uint16_t((reload & 0x00ff) | (value << 8)); break; case LatchMode::LowHigh: - if(!next_write_high) { + next_access_high ^= true; + if(next_access_high) { reload = (reload & 0xff00) | value; - next_write_high = true; return; } - reload = (reload & 0x00ff) | (value << 8); - next_write_high = false; + reload = uint16_t((reload & 0x00ff) | (value << 8)); break; } @@ -199,8 +264,8 @@ class PIT { case LatchMode::HighOnly: return uint8_t(latch >> 8); default: case LatchMode::LowHigh: - next_write_high ^= true; - return next_write_high ? uint8_t(latch) : uint8_t(latch >> 8); + next_access_high ^= true; + return next_access_high ? uint8_t(latch) : uint8_t(latch >> 8); break; } } @@ -464,7 +529,7 @@ struct Memory { class IO { public: - IO(PIT &pit) : pit_(pit) {} + IO(PIT &pit, DMA &dma) : pit_(pit), dma_(dma) {} template void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) { switch(port) { @@ -481,13 +546,23 @@ class IO { printf("TODO: NMIs %s\n", (value & 0x80) ? "masked" : "unmasked"); break; - case 0x0000: case 0x0001: case 0x0002: case 0x0003: - case 0x0004: case 0x0005: case 0x0006: case 0x0007: + case 0x0000: dma_.write<0>(value); break; + case 0x0001: dma_.write<1>(value); break; + case 0x0002: dma_.write<2>(value); break; + case 0x0003: dma_.write<3>(value); break; + case 0x0004: dma_.write<4>(value); break; + case 0x0005: dma_.write<5>(value); break; + case 0x0006: dma_.write<6>(value); break; + case 0x0007: dma_.write<7>(value); break; + case 0x0008: case 0x0009: case 0x000a: case 0x000b: - case 0x000c: case 0x000d: case 0x000e: case 0x000f: + case 0x000c: case 0x000f: printf("TODO: DMA write of %02x at %04x\n", value, port); break; + case 0x000d: dma_.master_reset(); break; + case 0x000e: dma_.mask_reset(); break; + case 0x0060: case 0x0061: case 0x0062: case 0x0063: case 0x0064: case 0x0065: case 0x0066: case 0x0067: case 0x0068: case 0x0069: case 0x006a: case 0x006b: @@ -529,6 +604,15 @@ class IO { printf("Unhandled in: %04x\n", port); break; + case 0x0000: return dma_.read<0>(); + case 0x0001: return dma_.read<1>(); + case 0x0002: return dma_.read<2>(); + case 0x0003: return dma_.read<3>(); + case 0x0004: return dma_.read<4>(); + case 0x0005: return dma_.read<5>(); + case 0x0006: return dma_.read<6>(); + case 0x0007: return dma_.read<7>(); + case 0x0040: return pit_.read<0>(); case 0x0041: return pit_.read<1>(); case 0x0042: return pit_.read<2>(); @@ -545,6 +629,7 @@ class IO { private: PIT &pit_; + DMA &dma_; }; class FlowController { @@ -596,7 +681,7 @@ class ConcreteMachine: ConcreteMachine( [[maybe_unused]] const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher - ) : context(pit_) { + ) : context(pit_, dma_) { // Use clock rate as a MIPS count; keeping it as a multiple or divisor of the PIT frequency is easy. static constexpr int pit_frequency = 1'193'182; set_clock_rate(double(pit_frequency) * double(PitMultiplier) / double(PitDivisor)); // i.e. almost 0.4 MIPS for an XT. @@ -655,13 +740,14 @@ class ConcreteMachine: private: PIT pit_; + DMA dma_; struct Context { - Context(PIT &pit) : + Context(PIT &pit, DMA &dma) : segments(registers), memory(registers, segments), flow_controller(registers, segments), - io(pit) + io(pit, dma) { reset(); } From 7eed254de9823b134f418cdb79fe17b9382d99e6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Nov 2023 12:13:42 -0500 Subject: [PATCH 11/26] Bring an 8255 into the mix. --- Machines/PCCompatible/PCCompatible.cpp | 33 +++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 8cc452990..134f8710e 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -13,6 +13,8 @@ #include "../../InstructionSets/x86/Instruction.hpp" #include "../../InstructionSets/x86/Perform.hpp" +#include "../../Components/8255/i8255.hpp" + #include "../../Numeric/RegisterSizes.hpp" #include "../ScanProducer.hpp" @@ -22,6 +24,19 @@ namespace PCCompatible { +class i8255PortHandler : public Intel::i8255::PortHandler { + public: + void set_value(int port, uint8_t value) { + printf("PPI: %02x to %d\n", value, port); + } + + uint8_t get_value(int port) { + printf("PPI: from %d\n", port); + return 0; + }; +}; +using PPI = Intel::i8255::i8255; + class DMA { public: void flip_flop_reset() { @@ -529,9 +544,9 @@ struct Memory { class IO { public: - IO(PIT &pit, DMA &dma) : pit_(pit), dma_(dma) {} + IO(PIT &pit, DMA &dma, PPI &ppi) : pit_(pit), dma_(dma), ppi_(ppi) {} - template void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) { + template void out(uint16_t port, IntT value) { switch(port) { default: if constexpr (std::is_same_v) { @@ -568,7 +583,7 @@ class IO { case 0x0068: case 0x0069: case 0x006a: case 0x006b: case 0x006c: case 0x006d: case 0x006e: case 0x006f: // Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol - printf("TODO: PPI write of %02x at %04x\n", value, port); + ppi_.write(port, value); break; case 0x0080: case 0x0081: case 0x0082: case 0x0083: @@ -621,8 +636,7 @@ class IO { case 0x0064: case 0x0065: case 0x0066: case 0x0067: case 0x0068: case 0x0069: case 0x006a: case 0x006b: case 0x006c: case 0x006d: case 0x006e: case 0x006f: - printf("TODO: PPI read from %04x\n", port); - break; + return ppi_.read(port); } return IntT(~0); } @@ -630,6 +644,7 @@ class IO { private: PIT &pit_; DMA &dma_; + PPI &ppi_; }; class FlowController { @@ -681,7 +696,7 @@ class ConcreteMachine: ConcreteMachine( [[maybe_unused]] const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher - ) : context(pit_, dma_) { + ) : ppi_(ppi_handler_), context(pit_, dma_, ppi_) { // Use clock rate as a MIPS count; keeping it as a multiple or divisor of the PIT frequency is easy. static constexpr int pit_frequency = 1'193'182; set_clock_rate(double(pit_frequency) * double(PitMultiplier) / double(PitDivisor)); // i.e. almost 0.4 MIPS for an XT. @@ -741,13 +756,15 @@ class ConcreteMachine: private: PIT pit_; DMA dma_; + i8255PortHandler ppi_handler_; + PPI ppi_; struct Context { - Context(PIT &pit, DMA &dma) : + Context(PIT &pit, DMA &dma, PPI &ppi) : segments(registers), memory(registers, segments), flow_controller(registers, segments), - io(pit, dma) + io(pit, dma, ppi) { reset(); } From a3066fc0405953bd5831c5ecbd91ec774d2ebd6a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Nov 2023 12:21:37 -0500 Subject: [PATCH 12/26] Advance to the missing PIC. --- Machines/PCCompatible/PCCompatible.cpp | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 134f8710e..56a3217e8 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -27,13 +27,34 @@ namespace PCCompatible { class i8255PortHandler : public Intel::i8255::PortHandler { public: void set_value(int port, uint8_t value) { + switch(port) { + case 1: + break; + } printf("PPI: %02x to %d\n", value, port); } uint8_t get_value(int port) { + switch(port) { + 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, b2: RAM on motherboard (64 * bit pattern) + // b1: 1 => FPU present; 0 => absent; + // b0: 1 => floppy drive present; 0 => absent. + return 0b0000'1100; + break; + } printf("PPI: from %d\n", port); return 0; }; + + // Provisionally, possibly: + // + // port 0 = keyboard data output buffer; + // }; using PPI = Intel::i8255::i8255; @@ -578,6 +599,13 @@ class IO { case 0x000d: dma_.master_reset(); break; case 0x000e: dma_.mask_reset(); break; + case 0x0020: case 0x0021: case 0x0022: case 0x0023: + case 0x0024: case 0x0025: case 0x0026: case 0x0027: + case 0x0028: case 0x0029: case 0x002a: case 0x002b: + case 0x002c: case 0x002d: case 0x002e: case 0x002f: + printf("TODO: PIC write of %02x at %04x\n", value, port); + break; + case 0x0060: case 0x0061: case 0x0062: case 0x0063: case 0x0064: case 0x0065: case 0x0066: case 0x0067: case 0x0068: case 0x0069: case 0x006a: case 0x006b: From a689f2b63e3f330dd9833990a346e86c2f834acc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Nov 2023 12:22:30 -0500 Subject: [PATCH 13/26] Relocate comment. --- Machines/PCCompatible/PCCompatible.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 56a3217e8..da7d69052 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -25,6 +25,7 @@ namespace PCCompatible { class i8255PortHandler : public Intel::i8255::PortHandler { + // Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol public: void set_value(int port, uint8_t value) { switch(port) { @@ -610,7 +611,6 @@ class IO { case 0x0064: case 0x0065: case 0x0066: case 0x0067: case 0x0068: case 0x0069: case 0x006a: case 0x006b: case 0x006c: case 0x006d: case 0x006e: case 0x006f: - // Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol ppi_.write(port, value); break; From abf0eead7a4e5227c3047d994f83b7f5ee548c0e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Nov 2023 13:53:44 -0500 Subject: [PATCH 14/26] Add a functionless PIC. --- Machines/PCCompatible/PCCompatible.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index da7d69052..1adf9bdc7 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -24,6 +24,9 @@ namespace PCCompatible { +class PIC { +}; + class i8255PortHandler : public Intel::i8255::PortHandler { // Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol public: @@ -566,7 +569,7 @@ struct Memory { class IO { public: - IO(PIT &pit, DMA &dma, PPI &ppi) : pit_(pit), dma_(dma), ppi_(ppi) {} + IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic) : pit_(pit), dma_(dma), ppi_(ppi), pic_(pic) {} template void out(uint16_t port, IntT value) { switch(port) { @@ -673,6 +676,7 @@ class IO { PIT &pit_; DMA &dma_; PPI &ppi_; + PIC &pic_; }; class FlowController { @@ -724,7 +728,7 @@ class ConcreteMachine: ConcreteMachine( [[maybe_unused]] const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher - ) : ppi_(ppi_handler_), context(pit_, dma_, ppi_) { + ) : ppi_(ppi_handler_), context(pit_, dma_, ppi_, pic_) { // Use clock rate as a MIPS count; keeping it as a multiple or divisor of the PIT frequency is easy. static constexpr int pit_frequency = 1'193'182; set_clock_rate(double(pit_frequency) * double(PitMultiplier) / double(PitDivisor)); // i.e. almost 0.4 MIPS for an XT. @@ -786,13 +790,14 @@ class ConcreteMachine: DMA dma_; i8255PortHandler ppi_handler_; PPI ppi_; + PIC pic_; struct Context { - Context(PIT &pit, DMA &dma, PPI &ppi) : + Context(PIT &pit, DMA &dma, PPI &ppi, PIC &pic) : segments(registers), memory(registers, segments), flow_controller(registers, segments), - io(pit, dma, ppi) + io(pit, dma, ppi, pic) { reset(); } From 18ddc2c83a706784ce429cfe4d00c6080f79af10 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Nov 2023 15:11:22 -0500 Subject: [PATCH 15/26] Route traffic. --- Machines/PCCompatible/PCCompatible.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 1adf9bdc7..1162534a5 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -25,6 +25,17 @@ namespace PCCompatible { class PIC { + public: + template + void write(uint8_t value) { + printf("PIC: %02x to %d\n", value, address); + } + + template + uint8_t read() { + printf("PIC: %read from %d\n", address); + return 0; + } }; class i8255PortHandler : public Intel::i8255::PortHandler { @@ -603,12 +614,8 @@ class IO { case 0x000d: dma_.master_reset(); break; case 0x000e: dma_.mask_reset(); break; - case 0x0020: case 0x0021: case 0x0022: case 0x0023: - case 0x0024: case 0x0025: case 0x0026: case 0x0027: - case 0x0028: case 0x0029: case 0x002a: case 0x002b: - case 0x002c: case 0x002d: case 0x002e: case 0x002f: - printf("TODO: PIC write of %02x at %04x\n", value, port); - break; + case 0x0020: pic_.write<0>(value); break; + case 0x0021: pic_.write<1>(value); break; case 0x0060: case 0x0061: case 0x0062: case 0x0063: case 0x0064: case 0x0065: case 0x0066: case 0x0067: @@ -659,6 +666,9 @@ class IO { case 0x0006: return dma_.read<6>(); case 0x0007: return dma_.read<7>(); + case 0x0020: return pic_.read<0>(); + case 0x0021: return pic_.read<1>(); + case 0x0040: return pit_.read<0>(); case 0x0041: return pit_.read<1>(); case 0x0042: return pit_.read<2>(); From d3e90ce006ee43dd148218e0c640812724bb535c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Nov 2023 15:36:52 -0500 Subject: [PATCH 16/26] Capture some basics. BIOS now seems to get as far as expecting channel 0 to trigger an interrupt, which never comes. --- Machines/PCCompatible/PCCompatible.cpp | 69 ++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 1162534a5..2a42c0a34 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -24,18 +24,77 @@ namespace PCCompatible { +// Cf. https://helppc.netcore2k.net/hardware/pic class PIC { public: template void write(uint8_t value) { + if(address) { + if(config_.word >= 0) { + switch(config_.word) { + case 0: + vector_base_ = value; + break; + case 1: + if(config_.has_fourth_word) { + // TODO: + // + // (1) slave mask if this is a master; + // (2) master interrupt attachment if this is a slave. + } + [[fallthrough]]; + break; + case 2: + auto_eoi_ = value & 2; + break; + } + + ++config_.word; + if(config_.word == (config_.has_fourth_word ? 3 : 2)) { + config_.word = -1; + } + } else { + mask_ = value; + } + } else { + if(value & 0x10) { + config_.word = 0; + config_.has_fourth_word = value & 1; + + if(!config_.has_fourth_word) { + auto_eoi_ = false; + } + + single_pic_ = value & 2; + four_byte_vectors_ = value & 4; + level_triggered_ = value & 8; + } + } printf("PIC: %02x to %d\n", value, address); } template uint8_t read() { - printf("PIC: %read from %d\n", address); + printf("PIC: read from %d\n", address); + if(address) { + return mask_; + } return 0; } + + private: + bool single_pic_ = false; + bool four_byte_vectors_ = false; + bool level_triggered_ = false; + bool auto_eoi_ = false; + + uint8_t vector_base_ = 0; + uint8_t mask_ = 0; + + struct ConfgurationState { + int word; + bool has_fourth_word; + } config_; }; class i8255PortHandler : public Intel::i8255::PortHandler { @@ -143,12 +202,12 @@ class PIT { public: template uint8_t read() { const auto result = channels_[channel].read(); - printf("Read from %d; %02x\n", channel, result); + printf("PIT: read from %d; %02x\n", channel, result); return result; } template void write(uint8_t value) { - printf("Write to %d\n", channel); + printf("PIT: write to %d\n", channel); channels_[channel].write(value); } @@ -161,7 +220,7 @@ class PIT { return; } - printf("Set mode on %d\n", channel_id); + printf("PIT: set mode on %d\n", channel_id); Channel &channel = channels_[channel_id]; switch((value >> 4) & 3) { @@ -186,7 +245,7 @@ class PIT { // Set up operating mode. switch(channel.mode) { default: - printf("%d switches to unimplemented mode %d\n", channel_id, int(channel.mode)); + printf("PIT: %d switches to unimplemented mode %d\n", channel_id, int(channel.mode)); break; case OperatingMode::InterruptOnTerminalCount: From ee6012f6e960aae7cb760c3128e3681b60adb1a3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Nov 2023 19:00:16 -0500 Subject: [PATCH 17/26] Evict the PIT. --- Machines/PCCompatible/PCCompatible.cpp | 199 +---------------- Machines/PCCompatible/PIT.hpp | 209 ++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 4 +- 3 files changed, 218 insertions(+), 194 deletions(-) create mode 100644 Machines/PCCompatible/PIT.hpp diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 2a42c0a34..fae9e7147 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -8,6 +8,8 @@ #include "PCCompatible.hpp" +#include "PIT.hpp" + #include "../../InstructionSets/x86/Decoder.hpp" #include "../../InstructionSets/x86/Flags.hpp" #include "../../InstructionSets/x86/Instruction.hpp" @@ -82,6 +84,10 @@ class PIC { return 0; } + template + void raise() { + } + private: bool single_pic_ = false; bool four_byte_vectors_ = false; @@ -197,199 +203,6 @@ class DMA { } channels_[4]; }; -template -class PIT { - public: - template uint8_t read() { - const auto result = channels_[channel].read(); - printf("PIT: read from %d; %02x\n", channel, result); - return result; - } - - template void write(uint8_t value) { - printf("PIT: write to %d\n", channel); - channels_[channel].write(value); - } - - void set_mode(uint8_t value) { - const int channel_id = (value >> 6) & 3; - if(channel_id == 3) { - read_back_ = is_8254; - - // TODO: decode rest of read-back command. - return; - } - - printf("PIT: set mode on %d\n", channel_id); - - Channel &channel = channels_[channel_id]; - switch((value >> 4) & 3) { - default: - channel.latch_value(); - return; - - case 1: channel.latch_mode = LatchMode::LowOnly; break; - case 2: channel.latch_mode = LatchMode::HighOnly; break; - case 3: channel.latch_mode = LatchMode::LowHigh; break; - } - channel.is_bcd = value & 1; - channel.next_access_high = false; - - const auto operating_mode = (value >> 1) & 7; - switch(operating_mode) { - default: channel.mode = OperatingMode(operating_mode); break; - case 6: channel.mode = OperatingMode::RateGenerator; break; - case 7: channel.mode = OperatingMode::SquareWaveGenerator; break; - } - - // Set up operating mode. - switch(channel.mode) { - default: - printf("PIT: %d switches to unimplemented mode %d\n", channel_id, int(channel.mode)); - break; - - case OperatingMode::InterruptOnTerminalCount: - channel.output = false; - channel.awaiting_reload = true; - break; - - case OperatingMode::RateGenerator: - channel.output = true; - channel.awaiting_reload = true; - break; - } - } - - void run_for(Cycles cycles) { - // TODO: be intelligent enough to take ticks outside the loop when appropriate. - auto ticks = cycles.as(); - while(ticks--) { - bool output_changed; - output_changed = channels_[0].advance(1); - output_changed |= channels_[1].advance(1); - output_changed |= channels_[2].advance(1); - } - } - - private: - // Supported only on 8254s. - bool read_back_ = false; - - enum class LatchMode { - LowOnly, - HighOnly, - LowHigh, - }; - - enum class OperatingMode { - InterruptOnTerminalCount = 0, - HardwareRetriggerableOneShot = 1, - RateGenerator = 2, - SquareWaveGenerator = 3, - SoftwareTriggeredStrobe = 4, - HardwareTriggeredStrobe = 5, - }; - - struct Channel { - LatchMode latch_mode = LatchMode::LowHigh; - OperatingMode mode = OperatingMode::InterruptOnTerminalCount; - bool is_bcd = false; - - bool gated = false; - bool awaiting_reload = true; - - uint16_t counter = 0; - uint16_t reload = 0; - uint16_t latch = 0; - bool output = false; - - bool next_access_high = false; - - void latch_value() { - latch = counter; - } - - bool advance(int ticks) { - if(gated || awaiting_reload) return false; - - // TODO: BCD mode is completely ignored below. Possibly not too important. - const bool initial_output = output; - switch(mode) { - case OperatingMode::InterruptOnTerminalCount: - // Output goes permanently high upon a tick from 1 to 0; reload value is not used on wraparound. - output |= counter <= ticks; - counter -= ticks; - break; - - case OperatingMode::RateGenerator: - // Output goes low upon a tick from 2 to 1. It goes high again on 1 to 0, and the reload value is used. - if(counter <= ticks) { - counter = reload - ticks + counter; - } else { - counter -= ticks; - } - output = counter != 1; - break; - - default: - // TODO. - break; - } - - return output != initial_output; - } - - void write(uint8_t value) { - switch(latch_mode) { - case LatchMode::LowOnly: - reload = (reload & 0xff00) | value; - break; - case LatchMode::HighOnly: - reload = uint16_t((reload & 0x00ff) | (value << 8)); - break; - case LatchMode::LowHigh: - next_access_high ^= true; - if(next_access_high) { - reload = (reload & 0xff00) | value; - return; - } - - reload = uint16_t((reload & 0x00ff) | (value << 8)); - break; - } - - awaiting_reload = false; - - switch(mode) { - case OperatingMode::InterruptOnTerminalCount: - case OperatingMode::RateGenerator: - counter = reload; - break; - } - } - - uint8_t read() { - switch(latch_mode) { - case LatchMode::LowOnly: return uint8_t(latch); - case LatchMode::HighOnly: return uint8_t(latch >> 8); - default: - case LatchMode::LowHigh: - next_access_high ^= true; - return next_access_high ? uint8_t(latch) : uint8_t(latch >> 8); - break; - } - } - } channels_[3]; - - // TODO: - // - // channel 0 is connected to IRQ 0; - // channel 1 is used for DRAM refresh; - // channel 2 is gated by a PPI output and feeds into the speaker. - // - // RateGenerator: output goes high if gated. -}; - struct Registers { public: static constexpr bool is_32bit = false; diff --git a/Machines/PCCompatible/PIT.hpp b/Machines/PCCompatible/PIT.hpp new file mode 100644 index 000000000..ec3126618 --- /dev/null +++ b/Machines/PCCompatible/PIT.hpp @@ -0,0 +1,209 @@ +// +// PIT.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef PIT_hpp +#define PIT_hpp + +namespace PCCompatible { + +template +class PIT { + public: + template uint8_t read() { + const auto result = channels_[channel].read(); + printf("PIT: read from %d; %02x\n", channel, result); + return result; + } + + template void write(uint8_t value) { + printf("PIT: write to %d\n", channel); + channels_[channel].write(value); + } + + void set_mode(uint8_t value) { + const int channel_id = (value >> 6) & 3; + if(channel_id == 3) { + read_back_ = is_8254; + + // TODO: decode rest of read-back command. + return; + } + + printf("PIT: set mode on %d\n", channel_id); + + Channel &channel = channels_[channel_id]; + switch((value >> 4) & 3) { + default: + channel.latch_value(); + return; + + case 1: channel.latch_mode = LatchMode::LowOnly; break; + case 2: channel.latch_mode = LatchMode::HighOnly; break; + case 3: channel.latch_mode = LatchMode::LowHigh; break; + } + channel.is_bcd = value & 1; + channel.next_access_high = false; + + const auto operating_mode = (value >> 1) & 7; + switch(operating_mode) { + default: channel.mode = OperatingMode(operating_mode); break; + case 6: channel.mode = OperatingMode::RateGenerator; break; + case 7: channel.mode = OperatingMode::SquareWaveGenerator; break; + } + + // Set up operating mode. + switch(channel.mode) { + default: + printf("PIT: %d switches to unimplemented mode %d\n", channel_id, int(channel.mode)); + break; + + case OperatingMode::InterruptOnTerminalCount: + channel.output = false; + channel.awaiting_reload = true; + break; + + case OperatingMode::RateGenerator: + channel.output = true; + channel.awaiting_reload = true; + break; + } + } + + void run_for(Cycles cycles) { + // TODO: be intelligent enough to take ticks outside the loop when appropriate. + auto ticks = cycles.as(); + while(ticks--) { + bool output_changed; + output_changed = channels_[0].advance(1); + output_changed |= channels_[1].advance(1); + output_changed |= channels_[2].advance(1); + } + } + + private: + // Supported only on 8254s. + bool read_back_ = false; + + enum class LatchMode { + LowOnly, + HighOnly, + LowHigh, + }; + + enum class OperatingMode { + InterruptOnTerminalCount = 0, + HardwareRetriggerableOneShot = 1, + RateGenerator = 2, + SquareWaveGenerator = 3, + SoftwareTriggeredStrobe = 4, + HardwareTriggeredStrobe = 5, + }; + + struct Channel { + LatchMode latch_mode = LatchMode::LowHigh; + OperatingMode mode = OperatingMode::InterruptOnTerminalCount; + bool is_bcd = false; + + bool gated = false; + bool awaiting_reload = true; + + uint16_t counter = 0; + uint16_t reload = 0; + uint16_t latch = 0; + bool output = false; + + bool next_access_high = false; + + void latch_value() { + latch = counter; + } + + bool advance(int ticks) { + if(gated || awaiting_reload) return false; + + // TODO: BCD mode is completely ignored below. Possibly not too important. + const bool initial_output = output; + switch(mode) { + case OperatingMode::InterruptOnTerminalCount: + // Output goes permanently high upon a tick from 1 to 0; reload value is not used on wraparound. + output |= counter <= ticks; + counter -= ticks; + break; + + case OperatingMode::RateGenerator: + // Output goes low upon a tick from 2 to 1. It goes high again on 1 to 0, and the reload value is used. + if(counter <= ticks) { + counter = reload - ticks + counter; + } else { + counter -= ticks; + } + output = counter != 1; + break; + + default: + // TODO. + break; + } + + return output != initial_output; + } + + void write(uint8_t value) { + switch(latch_mode) { + case LatchMode::LowOnly: + reload = (reload & 0xff00) | value; + break; + case LatchMode::HighOnly: + reload = uint16_t((reload & 0x00ff) | (value << 8)); + break; + case LatchMode::LowHigh: + next_access_high ^= true; + if(next_access_high) { + reload = (reload & 0xff00) | value; + return; + } + + reload = uint16_t((reload & 0x00ff) | (value << 8)); + break; + } + + awaiting_reload = false; + + switch(mode) { + case OperatingMode::InterruptOnTerminalCount: + case OperatingMode::RateGenerator: + counter = reload; + break; + } + } + + uint8_t read() { + switch(latch_mode) { + case LatchMode::LowOnly: return uint8_t(latch); + case LatchMode::HighOnly: return uint8_t(latch >> 8); + default: + case LatchMode::LowHigh: + next_access_high ^= true; + return next_access_high ? uint8_t(latch) : uint8_t(latch >> 8); + break; + } + } + } channels_[3]; + + // TODO: + // + // channel 0 is connected to IRQ 0; + // channel 1 is used for DRAM refresh; + // channel 2 is gated by a PPI output and feeds into the speaker. + // + // RateGenerator: output goes high if gated. +}; + +} + +#endif /* PIT_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index bb8eb4145..45be4a9f8 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1142,6 +1142,7 @@ 425739302AFBE47700B7D1E4 /* InOut.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = InOut.hpp; sourceTree = ""; }; 425739362B051EA800B7D1E4 /* PCCompatible.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCCompatible.hpp; sourceTree = ""; }; 425739372B051EA800B7D1E4 /* PCCompatible.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCCompatible.cpp; sourceTree = ""; }; + 4267A9C72B0C26FA008A59BB /* PIT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIT.hpp; sourceTree = ""; }; 4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = ""; }; 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; @@ -2360,8 +2361,9 @@ 425739352B051EA800B7D1E4 /* PCCompatible */ = { isa = PBXGroup; children = ( - 425739362B051EA800B7D1E4 /* PCCompatible.hpp */, 425739372B051EA800B7D1E4 /* PCCompatible.cpp */, + 425739362B051EA800B7D1E4 /* PCCompatible.hpp */, + 4267A9C72B0C26FA008A59BB /* PIT.hpp */, ); path = PCCompatible; sourceTree = ""; From f0e2ef5e28746211c0c681d0f12d5e1e4fcc5515 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Nov 2023 22:19:18 -0500 Subject: [PATCH 18/26] Attempt to implement square-wave mode. --- Machines/PCCompatible/PIT.hpp | 172 +++++++++++++++++++++------------- 1 file changed, 106 insertions(+), 66 deletions(-) diff --git a/Machines/PCCompatible/PIT.hpp b/Machines/PCCompatible/PIT.hpp index ec3126618..b283d5b92 100644 --- a/Machines/PCCompatible/PIT.hpp +++ b/Machines/PCCompatible/PIT.hpp @@ -15,73 +15,30 @@ template class PIT { public: template uint8_t read() { - const auto result = channels_[channel].read(); - printf("PIT: read from %d; %02x\n", channel, result); - return result; + return channels_[channel].read(); } template void write(uint8_t value) { - printf("PIT: write to %d\n", channel); channels_[channel].write(value); } void set_mode(uint8_t value) { const int channel_id = (value >> 6) & 3; if(channel_id == 3) { - read_back_ = is_8254; - // TODO: decode rest of read-back command. + read_back_ = is_8254; return; } - - printf("PIT: set mode on %d\n", channel_id); - - Channel &channel = channels_[channel_id]; - switch((value >> 4) & 3) { - default: - channel.latch_value(); - return; - - case 1: channel.latch_mode = LatchMode::LowOnly; break; - case 2: channel.latch_mode = LatchMode::HighOnly; break; - case 3: channel.latch_mode = LatchMode::LowHigh; break; - } - channel.is_bcd = value & 1; - channel.next_access_high = false; - - const auto operating_mode = (value >> 1) & 7; - switch(operating_mode) { - default: channel.mode = OperatingMode(operating_mode); break; - case 6: channel.mode = OperatingMode::RateGenerator; break; - case 7: channel.mode = OperatingMode::SquareWaveGenerator; break; - } - - // Set up operating mode. - switch(channel.mode) { - default: - printf("PIT: %d switches to unimplemented mode %d\n", channel_id, int(channel.mode)); - break; - - case OperatingMode::InterruptOnTerminalCount: - channel.output = false; - channel.awaiting_reload = true; - break; - - case OperatingMode::RateGenerator: - channel.output = true; - channel.awaiting_reload = true; - break; - } + channels_[channel_id].set_mode(value); } void run_for(Cycles cycles) { // TODO: be intelligent enough to take ticks outside the loop when appropriate. auto ticks = cycles.as(); while(ticks--) { - bool output_changed; - output_changed = channels_[0].advance(1); - output_changed |= channels_[1].advance(1); - output_changed |= channels_[2].advance(1); + channels_[0].advance(1); + channels_[1].advance(1); + channels_[2].advance(1); } } @@ -123,34 +80,104 @@ class PIT { latch = counter; } - bool advance(int ticks) { - if(gated || awaiting_reload) return false; + void set_mode(uint8_t value) { + switch((value >> 4) & 3) { + default: + latch_value(); + return; - // TODO: BCD mode is completely ignored below. Possibly not too important. - const bool initial_output = output; + case 1: latch_mode = LatchMode::LowOnly; break; + case 2: latch_mode = LatchMode::HighOnly; break; + case 3: latch_mode = LatchMode::LowHigh; break; + } + is_bcd = value & 1; + next_access_high = false; + + const auto operating_mode = (value >> 1) & 7; + switch(operating_mode) { + default: mode = OperatingMode(operating_mode); break; + case 6: mode = OperatingMode::RateGenerator; break; + case 7: mode = OperatingMode::SquareWaveGenerator; break; + } + + // Set up operating mode. switch(mode) { + default: + printf("PIT: unimplemented mode %d\n", int(mode)); + break; + case OperatingMode::InterruptOnTerminalCount: - // Output goes permanently high upon a tick from 1 to 0; reload value is not used on wraparound. - output |= counter <= ticks; - counter -= ticks; + case OperatingMode::HardwareRetriggerableOneShot: + set_output(false); + awaiting_reload = true; break; case OperatingMode::RateGenerator: - // Output goes low upon a tick from 2 to 1. It goes high again on 1 to 0, and the reload value is used. - if(counter <= ticks) { - counter = reload - ticks + counter; - } else { + case OperatingMode::SquareWaveGenerator: + set_output(true); + awaiting_reload = true; + break; + } + } + + void advance(int ticks) { + if(gated || awaiting_reload) return; + + // TODO: BCD mode is completely ignored below. Possibly not too important. + switch(mode) { + case OperatingMode::InterruptOnTerminalCount: + case OperatingMode::HardwareRetriggerableOneShot: + // Output goes permanently high upon a tick from 1 to 0; reload value is not reused. + set_output(output | (counter <= ticks)); + counter -= ticks; + break; + + case OperatingMode::SquareWaveGenerator: { + ticks <<= 1; + do { + // If there's a step from 1 to 0 within the next batch of ticks, + // toggle output and apply a reload. + if(counter && ticks >= counter) { + set_output(output ^ true); + ticks -= counter; + + const uint16_t reload_mask = output ? 0xffff : 0xfffe; + counter = reload & reload_mask; + + continue; + } counter -= ticks; - } - output = counter != 1; + } while(false); + } break; + + case OperatingMode::RateGenerator: + do { + // Check for a step from 2 to 1 within the next batch of ticks, which would cause output + // to go high. + if(counter > 1 && ticks >= counter - 1) { + set_output(true); + ticks -= counter - 1; + counter = 1; + continue; + } + + // If there is a step from 1 to 0, reload and set output back to low. + if(counter && ticks >= counter) { + set_output(false); + ticks -= counter; + counter = reload; + continue; + } + + // Otherwise, just continue. + counter -= ticks; + } while(false); break; default: // TODO. break; } - - return output != initial_output; } void write(uint8_t value) { @@ -165,6 +192,7 @@ class PIT { next_access_high ^= true; if(next_access_high) { reload = (reload & 0xff00) | value; + awaiting_reload = true; return; } @@ -175,10 +203,13 @@ class PIT { awaiting_reload = false; switch(mode) { - case OperatingMode::InterruptOnTerminalCount: - case OperatingMode::RateGenerator: + default: counter = reload; break; + + case OperatingMode::SquareWaveGenerator: + counter = reload & ~1; + break; } } @@ -193,6 +224,15 @@ class PIT { break; } } + + void set_output(bool level) { + if(output == level) { + return; + } + + output = level; + // TODO: notify _someone_. + } } channels_[3]; // TODO: From 695282b838e0b8c5f17ba12a13621c88928e69c9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Nov 2023 22:36:05 -0500 Subject: [PATCH 19/26] PIT output now reaches the PIC. --- Machines/PCCompatible/PCCompatible.cpp | 52 ++++++++++++++++++++----- Machines/PCCompatible/PIT.hpp | 54 +++++++++++++++----------- 2 files changed, 74 insertions(+), 32 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index fae9e7147..00061823b 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -84,8 +84,13 @@ class PIC { return 0; } - template - void raise() { + template + void apply_edge(bool final_level) { + const uint8_t input_mask = 1 << input; + if(mask_ & input_mask) { + return; + } + printf("PIC: Unmasked input %d switches to level %d\n", input, final_level); } private: @@ -103,6 +108,30 @@ class PIC { } config_; }; + +class PITObserver { + public: + PITObserver(PIC &pic) : pic_(pic) {} + + template + void update_output(bool new_level) { + switch(channel) { + default: break; + case 0: pic_.apply_edge<0>(new_level); break; + } + } + + private: + PIC &pic_; + + // TODO: + // + // 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. +}; +using PIT = i8237; + class i8255PortHandler : public Intel::i8255::PortHandler { // Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol public: @@ -452,7 +481,7 @@ struct Memory { class IO { public: - IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic) : pit_(pit), dma_(dma), ppi_(ppi), pic_(pic) {} + IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic) : pit_(pit), dma_(dma), ppi_(ppi), pic_(pic) {} template void out(uint16_t port, IntT value) { switch(port) { @@ -555,7 +584,7 @@ class IO { } private: - PIT &pit_; + PIT &pit_; DMA &dma_; PPI &ppi_; PIC &pic_; @@ -610,7 +639,7 @@ class ConcreteMachine: ConcreteMachine( [[maybe_unused]] const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher - ) : ppi_(ppi_handler_), context(pit_, dma_, ppi_, pic_) { + ) : pit_observer_(pic_), pit_(pit_observer_), ppi_(ppi_handler_), context(pit_, dma_, ppi_, pic_) { // Use clock rate as a MIPS count; keeping it as a multiple or divisor of the PIT frequency is easy. static constexpr int pit_frequency = 1'193'182; set_clock_rate(double(pit_frequency) * double(PitMultiplier) / double(PitDivisor)); // i.e. almost 0.4 MIPS for an XT. @@ -668,14 +697,17 @@ class ConcreteMachine: } private: - PIT pit_; - DMA dma_; - i8255PortHandler ppi_handler_; - PPI ppi_; PIC pic_; + DMA dma_; + + PITObserver pit_observer_; + i8255PortHandler ppi_handler_; + + PIT pit_; + PPI ppi_; struct Context { - Context(PIT &pit, DMA &dma, PPI &ppi, PIC &pic) : + Context(PIT &pit, DMA &dma, PPI &ppi, PIC &pic) : segments(registers), memory(registers, segments), flow_controller(registers, segments), diff --git a/Machines/PCCompatible/PIT.hpp b/Machines/PCCompatible/PIT.hpp index b283d5b92..0261d98f7 100644 --- a/Machines/PCCompatible/PIT.hpp +++ b/Machines/PCCompatible/PIT.hpp @@ -11,15 +11,17 @@ namespace PCCompatible { -template -class PIT { +template +class i8237 { public: + i8237(PITObserver &observer) : observer_(observer) {} + template uint8_t read() { return channels_[channel].read(); } template void write(uint8_t value) { - channels_[channel].write(value); + channels_[channel].template write(observer_, value); } void set_mode(uint8_t value) { @@ -29,20 +31,27 @@ class PIT { read_back_ = is_8254; return; } - channels_[channel_id].set_mode(value); + switch(channel_id) { + case 0: channels_[0].template set_mode<0>(observer_, value); break; + case 1: channels_[1].template set_mode<1>(observer_, value); break; + case 2: channels_[2].template set_mode<2>(observer_, value); break; + } } void run_for(Cycles cycles) { // TODO: be intelligent enough to take ticks outside the loop when appropriate. auto ticks = cycles.as(); while(ticks--) { - channels_[0].advance(1); - channels_[1].advance(1); - channels_[2].advance(1); + channels_[0].template advance<0>(observer_, 1); + channels_[1].template advance<1>(observer_, 1); + channels_[2].template advance<2>(observer_, 1); } } private: + // The target for output changes. + PITObserver &observer_; + // Supported only on 8254s. bool read_back_ = false; @@ -80,7 +89,8 @@ class PIT { latch = counter; } - void set_mode(uint8_t value) { + template + void set_mode(PITObserver &observer, uint8_t value) { switch((value >> 4) & 3) { default: latch_value(); @@ -108,19 +118,20 @@ class PIT { case OperatingMode::InterruptOnTerminalCount: case OperatingMode::HardwareRetriggerableOneShot: - set_output(false); + set_output(observer, false); awaiting_reload = true; break; case OperatingMode::RateGenerator: case OperatingMode::SquareWaveGenerator: - set_output(true); + set_output(observer, true); awaiting_reload = true; break; } } - void advance(int ticks) { + template + void advance(PITObserver &observer, int ticks) { if(gated || awaiting_reload) return; // TODO: BCD mode is completely ignored below. Possibly not too important. @@ -128,7 +139,7 @@ class PIT { case OperatingMode::InterruptOnTerminalCount: case OperatingMode::HardwareRetriggerableOneShot: // Output goes permanently high upon a tick from 1 to 0; reload value is not reused. - set_output(output | (counter <= ticks)); + set_output(observer, output | (counter <= ticks)); counter -= ticks; break; @@ -138,7 +149,7 @@ class PIT { // If there's a step from 1 to 0 within the next batch of ticks, // toggle output and apply a reload. if(counter && ticks >= counter) { - set_output(output ^ true); + set_output(observer, output ^ true); ticks -= counter; const uint16_t reload_mask = output ? 0xffff : 0xfffe; @@ -155,7 +166,7 @@ class PIT { // Check for a step from 2 to 1 within the next batch of ticks, which would cause output // to go high. if(counter > 1 && ticks >= counter - 1) { - set_output(true); + set_output(observer, true); ticks -= counter - 1; counter = 1; continue; @@ -163,7 +174,7 @@ class PIT { // If there is a step from 1 to 0, reload and set output back to low. if(counter && ticks >= counter) { - set_output(false); + set_output(observer, false); ticks -= counter; counter = reload; continue; @@ -180,7 +191,8 @@ class PIT { } } - void write(uint8_t value) { + template + void write([[maybe_unused]] PITObserver &observer, uint8_t value) { switch(latch_mode) { case LatchMode::LowOnly: reload = (reload & 0xff00) | value; @@ -225,22 +237,20 @@ class PIT { } } - void set_output(bool level) { + template + void set_output(PITObserver &observer, bool level) { if(output == level) { return; } + // TODO: how should time be notified? + observer.template update_output(level); output = level; - // TODO: notify _someone_. } } channels_[3]; // TODO: // - // channel 0 is connected to IRQ 0; - // channel 1 is used for DRAM refresh; - // channel 2 is gated by a PPI output and feeds into the speaker. - // // RateGenerator: output goes high if gated. }; From c4e9f757091d5930c8fe808525c491abf1f41ca9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Nov 2023 22:52:20 -0500 Subject: [PATCH 20/26] Edge towards but don't quite reach interrupt. --- Machines/PCCompatible/PCCompatible.cpp | 39 +++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 00061823b..d204ab2e3 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -87,10 +87,20 @@ class PIC { template void apply_edge(bool final_level) { const uint8_t input_mask = 1 << input; - if(mask_ & input_mask) { - return; + + // Guess: level triggered means the request can be forwarded only so long as the + // relevant input is actually high. Whereas edge triggered implies capturing state. + if(level_triggered_) { + requests_ &= ~input_mask; } - printf("PIC: Unmasked input %d switches to level %d\n", input, final_level); + if(final_level) { + requests_ |= input_mask; + } + } + + bool pending() { + // Per the OSDev Wiki, masking is applied after the fact. + return requests_ & ~mask_; } private: @@ -102,6 +112,9 @@ class PIC { uint8_t vector_base_ = 0; uint8_t mask_ = 0; + uint8_t requests_ = 0; + uint8_t in_service_ = 0; + struct ConfgurationState { int word; bool has_fourth_word; @@ -661,12 +674,29 @@ class ConcreteMachine: void run_for(const Cycles cycles) override { auto instructions = cycles.as_integral(); while(instructions--) { + // // First draft: all hardware runs in lockstep. + // + + // Advance the PIT. pit_.run_for(PitDivisor / PitMultiplier); - // Get the next thing to execute into decoded. + // Query for interrupts and apply if pending. + if(pic_.pending() && context.flags.flag()) { + // Regress the IP if a REP is in-progress so as to resume it later. + if(context.flow_controller.should_repeat()) { + context.registers.ip() = decoded_ip_; + context.flow_controller.begin_instruction(); + } + + // TODO: signal interrupt. + printf("TODO: should interrupt\n"); + } + + // 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); @@ -735,6 +765,7 @@ class ConcreteMachine: InstructionSet::x86::Decoder8086 decoder; // InstructionSet::x86::Decoder decoder; + uint16_t decoded_ip_ = 0; std::pair> decoded; }; From 058080f6dedb7171c25773bfabc73527c4406e1b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Nov 2023 23:11:27 -0500 Subject: [PATCH 21/26] Prove to my caveman self that no text is being written. --- Machines/PCCompatible/PCCompatible.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index d204ab2e3..c46acca41 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -158,6 +158,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { uint8_t get_value(int port) { switch(port) { + // TODO: returned value should depend on 'PBSW', a value written... somewhere? case 2: // b7: 1 => memory parity error; 0 => none; // b6: 1 => IO channel error; 0 => none; @@ -167,7 +168,6 @@ class i8255PortHandler : public Intel::i8255::PortHandler { // b1: 1 => FPU present; 0 => absent; // b0: 1 => floppy drive present; 0 => absent. return 0b0000'1100; - break; } printf("PPI: from %d\n", port); return 0; @@ -379,6 +379,10 @@ struct Memory { // Accesses an address based on physical location. template typename InstructionSet::x86::Accessor::type access(uint32_t address) { + if(address >= 0xb'0000 && is_writeable(type)) { + printf("MDA?\n"); + } + // Dispense with the single-byte case trivially. if constexpr (std::is_same_v) { return memory[address]; From 931e6e7a563566e994040a7e1afb0ec806b0e6fa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 Nov 2023 11:19:47 -0500 Subject: [PATCH 22/26] Add, disable, logging detritus. --- Machines/PCCompatible/PCCompatible.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index c46acca41..724d065e4 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -23,6 +23,7 @@ #include "../TimedMachine.hpp" #include +#include namespace PCCompatible { @@ -675,6 +676,8 @@ class ConcreteMachine: } // MARK: - TimedMachine. +// bool log = false; +// std::string previous; void run_for(const Cycles cycles) override { auto instructions = cycles.as_integral(); while(instructions--) { @@ -712,10 +715,20 @@ class ConcreteMachine: } context.registers.ip() += decoded.first; + +// log |= decoded.second.operation() == InstructionSet::x86::Operation::STI; } else { context.flow_controller.begin_instruction(); } +// if(log) { +// const auto next = to_string(decoded, InstructionSet::x86::Model::i8086); +// if(next != previous) { +// std::cout << next << std::endl; +// previous = next; +// } +// } + // Execute it. InstructionSet::x86::perform( decoded.second, From acdf32e8207fa792db11c5044d317d7d67e9c69c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 Nov 2023 11:25:53 -0500 Subject: [PATCH 23/26] Handle low/high switches. --- Machines/PCCompatible/PCCompatible.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 724d065e4..edd1796c0 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -152,6 +152,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { void set_value(int port, uint8_t value) { switch(port) { case 1: + high_switches_ = value & 0x08; break; } printf("PPI: %02x to %d\n", value, port); @@ -159,21 +160,31 @@ class i8255PortHandler : public Intel::i8255::PortHandler { uint8_t get_value(int port) { switch(port) { - // TODO: returned value should depend on 'PBSW', a value written... somewhere? case 2: + // Common: + // // 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, b2: RAM on motherboard (64 * bit pattern) - // b1: 1 => FPU present; 0 => absent; - // b0: 1 => floppy drive present; 0 => absent. - return 0b0000'1100; + return + high_switches_ ? + // b3, b2: drive count; 00 = 1, 01 = 2, etc + // b1, b0: video mode (00 = ROM; 01 = CGA40; 10 = CGA80; 11 = MDA) + 0b0000'0011 + : + // b3, b2: RAM on motherboard (64 * bit pattern) + // b1: 1 => FPU present; 0 => absent; + // b0: 1 => floppy drive present; 0 => absent. + 0b0000'1100; } printf("PPI: from %d\n", port); return 0; }; + private: + bool high_switches_ = false; + // Provisionally, possibly: // // port 0 = keyboard data output buffer; From 83ca9b3af5888a87a5fff99276eb5ddf3b7b983b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 Nov 2023 11:37:36 -0500 Subject: [PATCH 24/26] Hack in some MDA text logging. Boot seems to complete? --- Machines/PCCompatible/PCCompatible.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index edd1796c0..9ea218629 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -389,10 +389,19 @@ struct Memory { } // Accesses an address based on physical location. + int mda_delay = -1; // HACK. template typename InstructionSet::x86::Accessor::type access(uint32_t address) { + + // TEMPORARY HACK. + if(mda_delay > 0) { + --mda_delay; + if(!mda_delay) { + print_mda(); + } + } if(address >= 0xb'0000 && is_writeable(type)) { - printf("MDA?\n"); + mda_delay = 100; } // Dispense with the single-byte case trivially. @@ -466,6 +475,19 @@ struct Memory { std::copy(data, data + length, memory.begin() + std::vector::difference_type(address)); } + + // TEMPORARY HACK. + void print_mda() { + uint32_t pointer = 0xb'0000; + for(int y = 0; y < 25; y++) { + for(int x = 0; x < 80; x++) { + printf("%c", memory[pointer]); + pointer += 2; // MDA goes [character, attributes]...; skip the attributes. + } + printf("\n"); + } + } + private: std::array memory{0xff}; Registers ®isters_; @@ -708,7 +730,7 @@ class ConcreteMachine: } // TODO: signal interrupt. - printf("TODO: should interrupt\n"); +// printf("TODO: should interrupt\n"); } // Get the next thing to execute. From a1e118a1ff2a40f731fdc936984517897835057b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 Nov 2023 15:46:31 -0500 Subject: [PATCH 25/26] Do some interrupt work. --- Machines/PCCompatible/PCCompatible.cpp | 98 +++++++++++++++++++------- 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 9ea218629..7ee0a72cf 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -61,6 +61,10 @@ class PIC { } } else { if(value & 0x10) { + // + // Initialisation Command Word 1. + // + config_.word = 0; config_.has_fourth_word = value & 1; @@ -71,14 +75,33 @@ class PIC { single_pic_ = value & 2; four_byte_vectors_ = value & 4; level_triggered_ = value & 8; + } else if(value & 0x08) { + // + // Operation Control Word 3. + // + + // b6: 1 => use b5; 0 => ignore. + // b5: 1 => set special mask; 0 => clear. + // b2: 1 => poll command issued; 0 => not. + // b1: 1 => use b0; 0 => ignore. + // b0: 1 => read IRR on next read; 0 => read ISR. + } else { + // + // Operation Control Word 2. + // + + // b7, b6, b5: EOI type. + // b2, b1, b0: interrupt level to acknowledge. + if((value >> 5) == 0b001) { + // Non-specific EOI. + awaiting_eoi_ = false; + } } } - printf("PIC: %02x to %d\n", value, address); } template uint8_t read() { - printf("PIC: read from %d\n", address); if(address) { return mask_; } @@ -101,7 +124,28 @@ class PIC { bool pending() { // Per the OSDev Wiki, masking is applied after the fact. - return requests_ & ~mask_; + return !awaiting_eoi_ && (requests_ & ~mask_); + } + + int acknowledge() { + awaiting_eoi_ = true; + + // TODO: there's bound to be a better solution than this search? + // TODO: is this the right priority order? + in_service_ = 0x80; + int id = 7; + while(!(in_service_ & requests_)) { + in_service_ >>= 1; + --id; + } + + if(in_service_) { + requests_ &= ~in_service_; + return vector_base_ + id; + } + + // Spurious interrupt. + return vector_base_ + 7; } private: @@ -112,6 +156,7 @@ class PIC { uint8_t vector_base_ = 0; uint8_t mask_ = 0; + bool awaiting_eoi_ = false; uint8_t requests_ = 0; uint8_t in_service_ = 0; @@ -389,20 +434,20 @@ struct Memory { } // Accesses an address based on physical location. - int mda_delay = -1; // HACK. +// int mda_delay = -1; // HACK. template typename InstructionSet::x86::Accessor::type access(uint32_t address) { // TEMPORARY HACK. - if(mda_delay > 0) { - --mda_delay; - if(!mda_delay) { - print_mda(); - } - } - if(address >= 0xb'0000 && is_writeable(type)) { - mda_delay = 100; - } +// if(mda_delay > 0) { +// --mda_delay; +// if(!mda_delay) { +// print_mda(); +// } +// } +// if(address >= 0xb'0000 && is_writeable(type)) { +// mda_delay = 100; +// } // Dispense with the single-byte case trivially. if constexpr (std::is_same_v) { @@ -477,16 +522,16 @@ struct Memory { // TEMPORARY HACK. - void print_mda() { - uint32_t pointer = 0xb'0000; - for(int y = 0; y < 25; y++) { - for(int x = 0; x < 80; x++) { - printf("%c", memory[pointer]); - pointer += 2; // MDA goes [character, attributes]...; skip the attributes. - } - printf("\n"); - } - } +// void print_mda() { +// uint32_t pointer = 0xb'0000; +// for(int y = 0; y < 25; y++) { +// for(int x = 0; x < 80; x++) { +// printf("%c", memory[pointer]); +// pointer += 2; // MDA goes [character, attributes]...; skip the attributes. +// } +// printf("\n"); +// } +// } private: std::array memory{0xff}; @@ -729,8 +774,11 @@ class ConcreteMachine: context.flow_controller.begin_instruction(); } - // TODO: signal interrupt. -// printf("TODO: should interrupt\n"); + // Signal interrupt. + InstructionSet::x86::interrupt( + pic_.acknowledge(), + context + ); } // Get the next thing to execute. From 375a9f9ff5e0c2bf117b67c8e042e4c2b7a0b539 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 Nov 2023 15:50:38 -0500 Subject: [PATCH 26/26] Pull out the PIC, DMA. --- Machines/PCCompatible/DMA.hpp | 83 +++++++ Machines/PCCompatible/PCCompatible.cpp | 208 +----------------- Machines/PCCompatible/PIC.hpp | 156 +++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 4 + 4 files changed, 245 insertions(+), 206 deletions(-) create mode 100644 Machines/PCCompatible/DMA.hpp create mode 100644 Machines/PCCompatible/PIC.hpp diff --git a/Machines/PCCompatible/DMA.hpp b/Machines/PCCompatible/DMA.hpp new file mode 100644 index 000000000..719bc19f0 --- /dev/null +++ b/Machines/PCCompatible/DMA.hpp @@ -0,0 +1,83 @@ +// +// DMA.hpp +// Clock Signal +// +// Created by Thomas Harte on 21/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef DMA_hpp +#define DMA_hpp + +#include "../../Numeric/RegisterSizes.hpp" + +namespace PCCompatible { + +class DMA { + public: + void flip_flop_reset() { + next_access_low = true; + } + + void mask_reset() { + // TODO: set all mask bits off. + } + + void master_reset() { + flip_flop_reset(); + // TODO: clear status, set all mask bits on. + } + + template + void write(uint8_t value) { + constexpr int channel = (address >> 1) & 3; + constexpr bool is_count = address & 1; + + next_access_low ^= true; + if(next_access_low) { + if constexpr (is_count) { + channels_[channel].count.halves.high = value; + } else { + channels_[channel].address.halves.high = value; + } + } else { + if constexpr (is_count) { + channels_[channel].count.halves.low = value; + } else { + channels_[channel].address.halves.low = value; + } + } + } + + template + uint8_t read() { + constexpr int channel = (address >> 1) & 3; + constexpr bool is_count = address & 1; + + next_access_low ^= true; + if(next_access_low) { + if constexpr (is_count) { + return channels_[channel].count.halves.high; + } else { + return channels_[channel].address.halves.high; + } + } else { + if constexpr (is_count) { + return channels_[channel].count.halves.low; + } else { + return channels_[channel].address.halves.low; + } + } + } + + private: + bool next_access_low = true; + + struct Channel { + CPU::RegisterPair16 address, count; + } channels_[4]; +}; + +} + +#endif /* DMA_hpp */ diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 7ee0a72cf..46eb59e81 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -8,6 +8,8 @@ #include "PCCompatible.hpp" +#include "DMA.hpp" +#include "PIC.hpp" #include "PIT.hpp" #include "../../InstructionSets/x86/Decoder.hpp" @@ -27,147 +29,6 @@ namespace PCCompatible { -// Cf. https://helppc.netcore2k.net/hardware/pic -class PIC { - public: - template - void write(uint8_t value) { - if(address) { - if(config_.word >= 0) { - switch(config_.word) { - case 0: - vector_base_ = value; - break; - case 1: - if(config_.has_fourth_word) { - // TODO: - // - // (1) slave mask if this is a master; - // (2) master interrupt attachment if this is a slave. - } - [[fallthrough]]; - break; - case 2: - auto_eoi_ = value & 2; - break; - } - - ++config_.word; - if(config_.word == (config_.has_fourth_word ? 3 : 2)) { - config_.word = -1; - } - } else { - mask_ = value; - } - } else { - if(value & 0x10) { - // - // Initialisation Command Word 1. - // - - config_.word = 0; - config_.has_fourth_word = value & 1; - - if(!config_.has_fourth_word) { - auto_eoi_ = false; - } - - single_pic_ = value & 2; - four_byte_vectors_ = value & 4; - level_triggered_ = value & 8; - } else if(value & 0x08) { - // - // Operation Control Word 3. - // - - // b6: 1 => use b5; 0 => ignore. - // b5: 1 => set special mask; 0 => clear. - // b2: 1 => poll command issued; 0 => not. - // b1: 1 => use b0; 0 => ignore. - // b0: 1 => read IRR on next read; 0 => read ISR. - } else { - // - // Operation Control Word 2. - // - - // b7, b6, b5: EOI type. - // b2, b1, b0: interrupt level to acknowledge. - if((value >> 5) == 0b001) { - // Non-specific EOI. - awaiting_eoi_ = false; - } - } - } - } - - template - uint8_t read() { - if(address) { - return mask_; - } - return 0; - } - - template - void apply_edge(bool final_level) { - const uint8_t input_mask = 1 << input; - - // Guess: level triggered means the request can be forwarded only so long as the - // relevant input is actually high. Whereas edge triggered implies capturing state. - if(level_triggered_) { - requests_ &= ~input_mask; - } - if(final_level) { - requests_ |= input_mask; - } - } - - bool pending() { - // Per the OSDev Wiki, masking is applied after the fact. - return !awaiting_eoi_ && (requests_ & ~mask_); - } - - int acknowledge() { - awaiting_eoi_ = true; - - // TODO: there's bound to be a better solution than this search? - // TODO: is this the right priority order? - in_service_ = 0x80; - int id = 7; - while(!(in_service_ & requests_)) { - in_service_ >>= 1; - --id; - } - - if(in_service_) { - requests_ &= ~in_service_; - return vector_base_ + id; - } - - // Spurious interrupt. - return vector_base_ + 7; - } - - private: - bool single_pic_ = false; - bool four_byte_vectors_ = false; - bool level_triggered_ = false; - bool auto_eoi_ = false; - - uint8_t vector_base_ = 0; - uint8_t mask_ = 0; - bool awaiting_eoi_ = false; - - uint8_t requests_ = 0; - uint8_t in_service_ = 0; - - struct ConfgurationState { - int word; - bool has_fourth_word; - } config_; -}; - - class PITObserver { public: PITObserver(PIC &pic) : pic_(pic) {} @@ -237,71 +98,6 @@ class i8255PortHandler : public Intel::i8255::PortHandler { }; using PPI = Intel::i8255::i8255; -class DMA { - public: - void flip_flop_reset() { - next_access_low = true; - } - - void mask_reset() { - // TODO: set all mask bits off. - } - - void master_reset() { - flip_flop_reset(); - // TODO: clear status, set all mask bits on. - } - - template - void write(uint8_t value) { - constexpr int channel = (address >> 1) & 3; - constexpr bool is_count = address & 1; - - next_access_low ^= true; - if(next_access_low) { - if constexpr (is_count) { - channels_[channel].count.halves.high = value; - } else { - channels_[channel].address.halves.high = value; - } - } else { - if constexpr (is_count) { - channels_[channel].count.halves.low = value; - } else { - channels_[channel].address.halves.low = value; - } - } - } - - template - uint8_t read() { - constexpr int channel = (address >> 1) & 3; - constexpr bool is_count = address & 1; - - next_access_low ^= true; - if(next_access_low) { - if constexpr (is_count) { - return channels_[channel].count.halves.high; - } else { - return channels_[channel].address.halves.high; - } - } else { - if constexpr (is_count) { - return channels_[channel].count.halves.low; - } else { - return channels_[channel].address.halves.low; - } - } - } - - private: - bool next_access_low = true; - - struct Channel { - CPU::RegisterPair16 address, count; - } channels_[4]; -}; - struct Registers { public: static constexpr bool is_32bit = false; diff --git a/Machines/PCCompatible/PIC.hpp b/Machines/PCCompatible/PIC.hpp new file mode 100644 index 000000000..c89d4a157 --- /dev/null +++ b/Machines/PCCompatible/PIC.hpp @@ -0,0 +1,156 @@ +// +// PIC.hpp +// Clock Signal +// +// Created by Thomas Harte on 21/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef PIC_hpp +#define PIC_hpp + +namespace PCCompatible { + +// Cf. https://helppc.netcore2k.net/hardware/pic +class PIC { + public: + template + void write(uint8_t value) { + if(address) { + if(config_.word >= 0) { + switch(config_.word) { + case 0: + vector_base_ = value; + break; + case 1: + if(config_.has_fourth_word) { + // TODO: + // + // (1) slave mask if this is a master; + // (2) master interrupt attachment if this is a slave. + } + [[fallthrough]]; + break; + case 2: + auto_eoi_ = value & 2; + break; + } + + ++config_.word; + if(config_.word == (config_.has_fourth_word ? 3 : 2)) { + config_.word = -1; + } + } else { + mask_ = value; + } + } else { + if(value & 0x10) { + // + // Initialisation Command Word 1. + // + + config_.word = 0; + config_.has_fourth_word = value & 1; + + if(!config_.has_fourth_word) { + auto_eoi_ = false; + } + + single_pic_ = value & 2; + four_byte_vectors_ = value & 4; + level_triggered_ = value & 8; + } else if(value & 0x08) { + // + // Operation Control Word 3. + // + + // b6: 1 => use b5; 0 => ignore. + // b5: 1 => set special mask; 0 => clear. + // b2: 1 => poll command issued; 0 => not. + // b1: 1 => use b0; 0 => ignore. + // b0: 1 => read IRR on next read; 0 => read ISR. + } else { + // + // Operation Control Word 2. + // + + // b7, b6, b5: EOI type. + // b2, b1, b0: interrupt level to acknowledge. + if((value >> 5) == 0b001) { + // Non-specific EOI. + awaiting_eoi_ = false; + } + } + } + } + + template + uint8_t read() { + if(address) { + return mask_; + } + return 0; + } + + template + void apply_edge(bool final_level) { + const uint8_t input_mask = 1 << input; + + // Guess: level triggered means the request can be forwarded only so long as the + // relevant input is actually high. Whereas edge triggered implies capturing state. + if(level_triggered_) { + requests_ &= ~input_mask; + } + if(final_level) { + requests_ |= input_mask; + } + } + + bool pending() { + // Per the OSDev Wiki, masking is applied after the fact. + return !awaiting_eoi_ && (requests_ & ~mask_); + } + + int acknowledge() { + awaiting_eoi_ = true; + + // TODO: there's bound to be a better solution than this search? + // TODO: is this the right priority order? + in_service_ = 0x80; + int id = 7; + while(!(in_service_ & requests_)) { + in_service_ >>= 1; + --id; + } + + if(in_service_) { + requests_ &= ~in_service_; + return vector_base_ + id; + } + + // Spurious interrupt. + return vector_base_ + 7; + } + + private: + bool single_pic_ = false; + bool four_byte_vectors_ = false; + bool level_triggered_ = false; + bool auto_eoi_ = false; + + uint8_t vector_base_ = 0; + uint8_t mask_ = 0; + bool awaiting_eoi_ = false; + + uint8_t requests_ = 0; + uint8_t in_service_ = 0; + + struct ConfgurationState { + int word; + bool has_fourth_word; + } config_; +}; + +} + +#endif /* PIC_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 45be4a9f8..a1fccf3ee 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1143,6 +1143,8 @@ 425739362B051EA800B7D1E4 /* PCCompatible.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCCompatible.hpp; sourceTree = ""; }; 425739372B051EA800B7D1E4 /* PCCompatible.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCCompatible.cpp; sourceTree = ""; }; 4267A9C72B0C26FA008A59BB /* PIT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIT.hpp; sourceTree = ""; }; + 4267A9C82B0D4EC2008A59BB /* PIC.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIC.hpp; sourceTree = ""; }; + 4267A9C92B0D4F17008A59BB /* DMA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DMA.hpp; sourceTree = ""; }; 4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = ""; }; 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; @@ -2364,6 +2366,8 @@ 425739372B051EA800B7D1E4 /* PCCompatible.cpp */, 425739362B051EA800B7D1E4 /* PCCompatible.hpp */, 4267A9C72B0C26FA008A59BB /* PIT.hpp */, + 4267A9C82B0D4EC2008A59BB /* PIC.hpp */, + 4267A9C92B0D4F17008A59BB /* DMA.hpp */, ); path = PCCompatible; sourceTree = "";