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),