1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-26 09:29:45 +00:00

Implementing counting for a couple of PIT modes.

This commit is contained in:
Thomas Harte 2023-11-19 15:52:32 -05:00
parent af885ccf08
commit 05e93f0eb3

View File

@ -24,16 +24,20 @@ template <bool is_8254>
class PIT {
public:
template <int channel> uint8_t read() {
return 0;
return channels_[channel].read();
}
template <int channel> void write([[maybe_unused]] uint8_t value) {
template <int channel> 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<int>();
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<false> &pit) : pit_(pit) {}
template <typename IntT> void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {
switch(port) {
@ -413,7 +536,7 @@ class IO {
}
private:
PIT &pit_;
PIT<false> &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<false> pit_;
struct Context {
Context(PIT &pit) :
Context(PIT<false> &pit) :
segments(registers),
memory(registers, segments),
flow_controller(registers, segments),