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 = "";