1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-19 07:31:15 +00:00

Merge pull request #1213 from TomHarte/PITClock

Standardise on the PIT clock as the definition of time.
This commit is contained in:
Thomas Harte 2023-11-21 22:51:14 -05:00 committed by GitHub
commit 2af6259963
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 12 deletions

View File

@ -17,10 +17,13 @@
#include "../../InstructionSets/x86/Instruction.hpp"
#include "../../InstructionSets/x86/Perform.hpp"
#include "../../Components/AudioToggle/AudioToggle.hpp"
#include "../../Components/8255/i8255.hpp"
#include "../../Numeric/RegisterSizes.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../AudioProducer.hpp"
#include "../ScanProducer.hpp"
#include "../TimedMachine.hpp"
@ -29,20 +32,65 @@
namespace PCCompatible {
struct PCSpeaker {
PCSpeaker() :
toggle(queue),
speaker(toggle) {}
void update() {
speaker.run_for(queue, cycles_since_update);
cycles_since_update = 0;
}
void set_pit(bool pit_input) {
pit_input_ = pit_input;
set_level();
}
void set_control(bool pit_mask, bool level) {
pit_mask_ = pit_mask;
level_ = level;
set_level();
}
void set_level() {
// TODO: eliminate complete guess of mixing function here.
const bool new_output = (pit_mask_ & pit_input_) ^ level_;
if(new_output != output_) {
update();
toggle.set_output(new_output);
output_ = new_output;
}
}
Concurrency::AsyncTaskQueue<false> queue;
Audio::Toggle toggle;
Outputs::Speaker::PullLowpass<Audio::Toggle> speaker;
Cycles cycles_since_update = 0;
bool pit_input_ = false;
bool pit_mask_ = false;
bool level_ = false;
bool output_ = false;
};
class PITObserver {
public:
PITObserver(PIC &pic) : pic_(pic) {}
PITObserver(PIC &pic, PCSpeaker &speaker) : pic_(pic), speaker_(speaker) {}
template <int channel>
void update_output(bool new_level) {
switch(channel) {
default: break;
case 0: pic_.apply_edge<0>(new_level); break;
case 2: speaker_.set_pit(new_level); break;
}
}
private:
PIC &pic_;
PCSpeaker &speaker_;
// TODO:
//
@ -55,10 +103,13 @@ using PIT = i8237<false, PITObserver>;
class i8255PortHandler : public Intel::i8255::PortHandler {
// Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol
public:
i8255PortHandler(PCSpeaker &speaker) : speaker_(speaker) {}
void set_value(int port, uint8_t value) {
switch(port) {
case 1:
high_switches_ = value & 0x08;
speaker_.set_control(value & 0x01, value & 0x02);
break;
}
printf("PPI: %02x to %d\n", value, port);
@ -90,6 +141,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
private:
bool high_switches_ = false;
PCSpeaker &speaker_;
// Provisionally, possibly:
//
@ -522,19 +574,24 @@ class FlowController {
class ConcreteMachine:
public Machine,
public MachineTypes::TimedMachine,
public MachineTypes::AudioProducer,
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
) : pit_observer_(pic_), pit_(pit_observer_), ppi_(ppi_handler_), context(pit_, dma_, ppi_, pic_) {
) :
pit_observer_(pic_, speaker_),
ppi_handler_(speaker_),
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.
set_clock_rate(double(pit_frequency));
speaker_.speaker.set_input_rate(double(pit_frequency));
// Fetch the BIOS. [8088 only, for now]
const auto bios = ROM::Name::PCCompatibleGLaBIOS;
@ -549,18 +606,38 @@ class ConcreteMachine:
context.memory.install(0x10'0000 - bios_contents.size(), bios_contents.data(), bios_contents.size());
}
~ConcreteMachine() {
speaker_.queue.flush();
}
// MARK: - TimedMachine.
// bool log = false;
// std::string previous;
void run_for(const Cycles cycles) override {
auto instructions = cycles.as_integral();
while(instructions--) {
void run_for(const Cycles duration) override {
const auto pit_ticks = duration.as_integral();
cpu_divisor_ += pit_ticks;
int ticks = cpu_divisor_ / 3;
cpu_divisor_ %= 3;
while(ticks--) {
//
// First draft: all hardware runs in lockstep.
// First draft: all hardware runs in lockstep, as a multiple or divisor of the PIT frequency.
//
// Advance the PIT.
pit_.run_for(PitDivisor / PitMultiplier);
//
// Advance the PIT and audio.
//
pit_.run_for(1);
++speaker_.cycles_since_update;
pit_.run_for(1);
++speaker_.cycles_since_update;
pit_.run_for(1);
++speaker_.cycles_since_update;
//
// Perform one CPU instruction every three PIT cycles.
// i.e. CPU instruction rate is 1/3 * ~1.19Mhz ~= 0.4 MIPS.
//
// Query for interrupts and apply if pending.
if(pic_.pending() && context.flags.flag<InstructionSet::x86::Flag::Interrupt>()) {
@ -620,9 +697,22 @@ class ConcreteMachine:
return Outputs::Display::ScanStatus();
}
// MARK: - AudioProducer.
Outputs::Speaker::Speaker *get_speaker() override {
return &speaker_.speaker;
}
void flush_output(int outputs) final {
if(outputs & Output::Audio) {
speaker_.update();
speaker_.queue.perform();
}
}
private:
PIC pic_;
DMA dma_;
PCSpeaker speaker_;
PITObserver pit_observer_;
i8255PortHandler ppi_handler_;
@ -661,6 +751,8 @@ class ConcreteMachine:
uint16_t decoded_ip_ = 0;
std::pair<int, InstructionSet::x86::Instruction<false>> decoded;
int cpu_divisor_ = 0;
};

View File

@ -6702,6 +6702,7 @@
"$(OTHER_CFLAGS)",
"-Wreorder",
);
OTHER_SWIFT_FLAGS = "-D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-Signal";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h";

View File

@ -86,11 +86,13 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
// TEMPORARY: remove the Apple IIgs and PC compatible options.
// Neither is yet a fully-working machine.
#if !DEBUG
for hidden in ["appleiigs", "pc"] {
let tabIndex = machineSelector.indexOfTabViewItem(withIdentifier: hidden)
machineSelector.removeTabViewItem(machineSelector.tabViewItem(at: tabIndex))
}
machineNameTable.reloadData()
#endif
// Machine type
if let machineIdentifier = standardUserDefaults.string(forKey: "new.machine") {