diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp index ecb118d91..4ec1fde0a 100644 --- a/Components/6522/6522.hpp +++ b/Components/6522/6522.hpp @@ -364,7 +364,7 @@ class MOS6522IRQDelegate { virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0; }; - void set_delegate(Delegate *delegate) + void set_interrupt_delegate(Delegate *delegate) { _delegate = delegate; } diff --git a/Machines/Commodore/1540/C1540.cpp b/Machines/Commodore/1540/C1540.cpp index 8724a2f2b..0e0e3455b 100644 --- a/Machines/Commodore/1540/C1540.cpp +++ b/Machines/Commodore/1540/C1540.cpp @@ -7,11 +7,14 @@ // #include "C1540.hpp" -#include +#include +#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp" using namespace Commodore::C1540; -Machine::Machine() +Machine::Machine() : + _shift_register(0), + Storage::DiskDrive(1000000, 16, 300) { // create a serial port and a VIA to run it _serialPortVIA.reset(new SerialPortVIA); @@ -22,8 +25,12 @@ Machine::Machine() _serialPortVIA->set_serial_port(_serialPort); // set this instance as the delegate to receive interrupt requests from both VIAs - _serialPortVIA->set_delegate(this); + _serialPortVIA->set_interrupt_delegate(this); + _driveVIA.set_interrupt_delegate(this); _driveVIA.set_delegate(this); + + // set a bit rate + set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3)); } void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) @@ -33,7 +40,11 @@ void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bu unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { -// if(operation == CPU6502::BusOperation::ReadOpcode && (address >= 0xF556 && address <= 0xF56D)) printf("%04x\n", address); +// static bool log = false; +// if(operation == CPU6502::BusOperation::ReadOpcode && (address == 0xF3C0)) log = true; +// if(operation == CPU6502::BusOperation::ReadOpcode && log) printf("%04x\n", address); +// if(operation == CPU6502::BusOperation::ReadOpcode) printf("%04x\n", address); +// if(operation == CPU6502::BusOperation::ReadOpcode && (address >= 0xF510 && address <= 0xF553)) printf("%04x\n", address); // if(operation == CPU6502::BusOperation::ReadOpcode && (address == 0xE887)) printf("A: %02x\n", get_value_of_register(CPU6502::Register::A)); /* static bool log = false; @@ -91,9 +102,11 @@ void Machine::set_rom(const uint8_t *rom) memcpy(_rom, rom, sizeof(_rom)); } -void Machine::set_disk(std::shared_ptr disk) +void Machine::run_for_cycles(int number_of_cycles) { - _disk = disk; + CPU6502::Processor::run_for_cycles(number_of_cycles); + if(_driveVIA.get_motor_enabled()) // TODO: motor speed up/down + Storage::DiskDrive::run_for_cycles(number_of_cycles); } #pragma mark - 6522 delegate @@ -103,3 +116,46 @@ void Machine::mos6522_did_change_interrupt_status(void *mos6522) // both VIAs are connected to the IRQ line set_irq_line(_serialPortVIA->get_interrupt_line() || _driveVIA.get_interrupt_line()); } + +#pragma mark - Disk drive + +void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) +{ + _shift_register = (_shift_register << 1) | value; + if((_shift_register & 0x3ff) == 0x3ff) + { + _driveVIA.set_sync_detected(true); + _bit_window_offset = -1; + } + else + { + _driveVIA.set_sync_detected(false); + } + _bit_window_offset++; + if(_bit_window_offset == 8) + { + _driveVIA.set_data_input((uint8_t)_shift_register); + _bit_window_offset = 0; + if(_driveVIA.get_should_set_overflow()) + { + set_overflow_line(true); + } + } + else + set_overflow_line(false); +} + +// the 1540 does not recognise index holes +void Machine::process_index_hole() {} + +#pragma mak - Drive VIA delegate + +void Machine::drive_via_did_step_head(void *driveVIA, int direction) +{ + step(direction); +} + +void Machine::drive_via_did_set_data_density(void *driveVIA, int density) +{ + set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density)); +} diff --git a/Machines/Commodore/1540/C1540.hpp b/Machines/Commodore/1540/C1540.hpp index 1935b1e5d..f052ea7bc 100644 --- a/Machines/Commodore/1540/C1540.hpp +++ b/Machines/Commodore/1540/C1540.hpp @@ -15,6 +15,7 @@ #include "../SerialBus.hpp" #include "../../../Storage/Disk/Disk.hpp" +#include "../../../Storage/Disk/DiskDrive.hpp" namespace Commodore { namespace C1540 { @@ -100,12 +101,12 @@ class SerialPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQD An implementation of the drive VIA in a Commodore 1540 — the VIA that is used to interface with the disk. It is wired up such that Port B contains: - Bits 0/1: head step direction (TODO) - Bit 2: motor control (TODO) + Bits 0/1: head step direction + Bit 2: motor control Bit 3: LED control (TODO) Bit 4: write protect photocell status (TODO) - Bits 5/6: write density (TODO) - Bit 7: 0 if sync marks are currently being detected, 1 otherwise; + Bits 5/6: read/write density + Bit 7: 0 if sync marks are currently being detected, 1 otherwise. ... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction. @@ -114,28 +115,80 @@ class SerialPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQD */ class DriveVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { public: + class Delegate { + public: + virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0; + virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0; + }; + void set_delegate(Delegate *delegate) + { + _delegate = delegate; + } + using MOS6522IRQDelegate::set_interrupt_status; + // write protect tab uncovered + DriveVIA() : _port_b(0xff), _port_a(0xff), _delegate(nullptr) {} + uint8_t get_port_input(Port port) { - if(port) - { - return 0xff; // imply not sync, write protect tab uncovered + return port ? _port_b : _port_a; + } + + void set_sync_detected(bool sync_detected) { + _port_b = (_port_b & 0x7f) | (sync_detected ? 0x00 : 0x80); + } + + void set_data_input(uint8_t value) { + _port_a = value; + } + + bool get_should_set_overflow() { + return _should_set_overflow; + } + + bool get_motor_enabled() { + return _drive_motor; + } + + void set_control_line_output(Port port, Line line, bool value) { + if(port == Port::A && line == Line::Two) { + _should_set_overflow = value; } - return 0xff; } void set_port_output(Port port, uint8_t value, uint8_t direction_mask) { if(port) { -// if(value&4) -// { -// printf("Head step: %d\n", value&3); -// printf("Motor: %s\n", value&4 ? "On" : "Off"); + // record drive motor state + _drive_motor = !!(value&4); + + // check for a head step + int step_difference = ((value&3) - (_previous_port_b_output&3))&3; + if(step_difference) + { + if(_delegate) _delegate->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1); + } + + // check for a change in density + int density_difference = (_previous_port_b_output^value) & (3 << 5); + if(density_difference && _delegate) + { + _delegate->drive_via_did_set_data_density(this, (value >> 5)&3); + } + + // TODO: something with the drive LED // printf("LED: %s\n", value&8 ? "On" : "Off"); -// printf("Density: %d\n", (value >> 5)&3); -// } + + _previous_port_b_output = value; } } + + private: + uint8_t _port_b, _port_a; + bool _should_set_overflow; + bool _drive_motor; + uint8_t _previous_port_b_output; + Delegate *_delegate; }; /*! @@ -161,7 +214,9 @@ class SerialPort : public ::Commodore::Serial::Port { */ class Machine: public CPU6502::Processor, - public MOS::MOS6522IRQDelegate::Delegate { + public MOS::MOS6522IRQDelegate::Delegate, + public DriveVIA::Delegate, + public Storage::DiskDrive { public: Machine(); @@ -176,10 +231,7 @@ class Machine: */ void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); - /*! - Sets the disk from which this 1540 is reading data. - */ - void set_disk(std::shared_ptr disk); + void run_for_cycles(int number_of_cycles); // to satisfy CPU6502::Processor unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); @@ -187,6 +239,10 @@ class Machine: // to satisfy MOS::MOS6522::Delegate virtual void mos6522_did_change_interrupt_status(void *mos6522); + // to satisfy DriveVIA::Delegate + void drive_via_did_step_head(void *driveVIA, int direction); + void drive_via_did_set_data_density(void *driveVIA, int density); + private: uint8_t _ram[0x800]; uint8_t _rom[0x4000]; @@ -196,6 +252,10 @@ class Machine: DriveVIA _driveVIA; std::shared_ptr _disk; + + int _shift_register, _bit_window_offset; + virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); + virtual void process_index_hole(); }; } diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 2b4f3d3ba..03b24f7ff 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -30,8 +30,8 @@ Machine::Machine() : _serialPort->set_user_port_via(_userPortVIA); // wire up the 6522s, tape and machine - _userPortVIA->set_delegate(this); - _keyboardVIA->set_delegate(this); + _userPortVIA->set_interrupt_delegate(this); + _keyboardVIA->set_interrupt_delegate(this); _tape.set_delegate(this); // establish the memory maps @@ -169,6 +169,11 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) _c1540->set_rom(data); _c1540->run_for_cycles(2000000); // pretend it booted a couple of seconds ago } + else + { + _driveROM.reset(new uint8_t[length]); + memcpy(_driveROM.get(), data, length); + } return; } @@ -221,6 +226,13 @@ void Machine::set_disk(std::shared_ptr disk) // hand it the disk _c1540->set_disk(disk); + + // install the ROM if it was previously set + if(_driveROM) + { + _c1540->set_rom(_driveROM.get()); + _driveROM.reset(); + } } #pragma mark - Typer diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index 279fa5249..fed2b4f11 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -287,6 +287,7 @@ class Machine: uint8_t _screenMemory[0x1000]; uint8_t _colorMemory[0x0400]; uint8_t _junkMemory[0x0400]; + std::unique_ptr _driveROM; uint8_t *_videoMemoryMap[16]; uint8_t *_processorReadMemoryMap[64]; diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c6a0e2688..cc4c02be3 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -1008,7 +1008,7 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) { if(_is_running) { - TapePlayer::run_for_cycles(number_of_cycles); + TapePlayer::run_for_cycles((int)number_of_cycles); } } else diff --git a/NumberTheory/Factors.cpp b/NumberTheory/Factors.cpp new file mode 100644 index 000000000..067615653 --- /dev/null +++ b/NumberTheory/Factors.cpp @@ -0,0 +1,34 @@ +// +// Factors.cpp +// Clock Signal +// +// Created by Thomas Harte on 29/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Factors.hpp" + +unsigned int NumberTheory::greatest_common_divisor(unsigned int a, unsigned int b) +{ + if(a < b) + { + unsigned int swap = b; + b = a; + a = swap; + } + + while(1) { + if(!a) return b; + if(!b) return a; + + unsigned int remainder = a%b; + a = b; + b = remainder; + } +} + +unsigned int NumberTheory::least_common_multiple(unsigned int a, unsigned int b) +{ + unsigned int gcd = greatest_common_divisor(a, b); + return (a*b) / gcd; +} diff --git a/NumberTheory/Factors.hpp b/NumberTheory/Factors.hpp new file mode 100644 index 000000000..bdc92c011 --- /dev/null +++ b/NumberTheory/Factors.hpp @@ -0,0 +1,17 @@ +// +// Factors.hpp +// Clock Signal +// +// Created by Thomas Harte on 29/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Factors_hpp +#define Factors_hpp + +namespace NumberTheory { + unsigned int greatest_common_divisor(unsigned int a, unsigned int b); + unsigned int least_common_multiple(unsigned int a, unsigned int b); +} + +#endif /* Factors_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 06204d82f..124da7db1 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */; }; 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; }; 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; @@ -39,6 +40,7 @@ 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; + 4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */; }; 4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B73C7191D036BD90074D992 /* Vic20Document.swift */; }; 4B73C71D1D036C030074D992 /* Vic20Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B73C71B1D036C030074D992 /* Vic20Document.xib */; }; 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; }; @@ -312,6 +314,9 @@ 4BB299F71B587D8400A49093 /* txan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EB1B587D8400A49093 /* txan */; }; 4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; }; 4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; }; + 4BB697C71D4B558F00248BDF /* Factors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C51D4B558F00248BDF /* Factors.cpp */; }; + 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; }; + 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; }; 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; }; 4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */; }; 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA81B587A5100552FC2 /* Assets.xcassets */; }; @@ -332,6 +337,8 @@ 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; }; 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; + 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; }; + 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -354,6 +361,8 @@ /* Begin PBXFileReference section */ 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = ""; }; 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = ""; }; + 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DigitalPhaseLockedLoop.cpp; sourceTree = ""; }; + 4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DigitalPhaseLockedLoop.hpp; sourceTree = ""; }; 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = ""; }; 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = ""; }; 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = ""; }; @@ -415,6 +424,8 @@ 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = ""; }; 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = ""; }; 4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + 4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskDrive.cpp; sourceTree = ""; }; + 4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskDrive.hpp; sourceTree = ""; }; 4B73C7191D036BD90074D992 /* Vic20Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20Document.swift; sourceTree = ""; }; 4B73C71C1D036C030074D992 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Vic20Document.xib"; sourceTree = SOURCE_ROOT; }; 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = ""; }; @@ -692,6 +703,12 @@ 4BB298EB1B587D8400A49093 /* txan */ = {isa = PBXFileReference; lastKnownFileType = file; path = txan; sourceTree = ""; }; 4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = ""; }; 4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = ""; }; + 4BB697C51D4B558F00248BDF /* Factors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Factors.cpp; path = ../../NumberTheory/Factors.cpp; sourceTree = ""; }; + 4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = ""; }; + 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = ""; }; + 4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = ""; }; + 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreGCR.cpp; path = Encodings/CommodoreGCR.cpp; sourceTree = ""; }; + 4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CommodoreGCR.hpp; path = Encodings/CommodoreGCR.hpp; sourceTree = ""; }; 4BB73E9E1B587A5100552FC2 /* Clock Signal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Clock Signal.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4BB73EA61B587A5100552FC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Atari2600Document.xib; sourceTree = ""; }; @@ -734,6 +751,9 @@ 4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = ""; }; 4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = ""; }; 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = ""; }; + 4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = ""; }; + 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = ""; }; + 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -884,13 +904,15 @@ isa = PBXGroup; children = ( 4B3BA0C51D318B44005DD7A7 /* C1540Bridge.h */, - 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */, 4B3BA0C71D318B44005DD7A7 /* Clock SignalTests-Bridging-Header.h */, + 4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */, 4B3BA0C81D318B44005DD7A7 /* MOS6522Bridge.h */, - 4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */, 4B3BA0CA1D318B44005DD7A7 /* MOS6532Bridge.h */, - 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */, 4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */, + 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */, + 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */, + 4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */, + 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */, 4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */, ); path = Bridges; @@ -951,9 +973,11 @@ 4B69FB391C4D908A00B5F0AA /* Storage */ = { isa = PBXGroup; children = ( + 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */, + 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */, + 4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */, 4BAB62AA1D3272D200DF5BA0 /* Disk */, 4B69FB3A1C4D908A00B5F0AA /* Tape */, - 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */, ); name = Storage; path = ../../Storage; @@ -984,11 +1008,16 @@ 4BAB62AA1D3272D200DF5BA0 /* Disk */ = { isa = PBXGroup; children = ( - 4BAB62B21D327F7E00DF5BA0 /* Formats */, + 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */, 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */, - 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, + 4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */, 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */, + 4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */, + 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, + 4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */, 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, + 4BB697CF1D4BA44900248BDF /* Encodings */, + 4BAB62B21D327F7E00DF5BA0 /* Formats */, ); path = Disk; sourceTree = ""; @@ -1274,6 +1303,24 @@ path = "Wolfgang Lorenz 6502 test suite"; sourceTree = ""; }; + 4BB697C81D4B559300248BDF /* NumberTheory */ = { + isa = PBXGroup; + children = ( + 4BB697C51D4B558F00248BDF /* Factors.cpp */, + 4BB697C61D4B558F00248BDF /* Factors.hpp */, + ); + name = NumberTheory; + sourceTree = ""; + }; + 4BB697CF1D4BA44900248BDF /* Encodings */ = { + isa = PBXGroup; + children = ( + 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */, + 4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */, + ); + name = Encodings; + sourceTree = ""; + }; 4BB73E951B587A5100552FC2 = { isa = PBXGroup; children = ( @@ -1283,13 +1330,17 @@ 4BB73EC01B587A5100552FC2 /* Clock SignalUITests */, 4BC9DF4A1D04691600F44158 /* Components */, 4BB73EDC1B587CA500552FC2 /* Machines */, + 4BB697C81D4B559300248BDF /* NumberTheory */, 4B366DFD1B5C165F0026627B /* Outputs */, 4BB73EDD1B587CA500552FC2 /* Processors */, 4BB73E9F1B587A5100552FC2 /* Products */, 4B2409591C45DF85004DA684 /* SignalProcessing */, 4B69FB391C4D908A00B5F0AA /* Storage */, ); + indentWidth = 4; sourceTree = ""; + tabWidth = 4; + usesTabs = 1; }; 4BB73E9F1B587A5100552FC2 /* Products */ = { isa = PBXGroup; @@ -1330,6 +1381,7 @@ 4B1E85801D176468001EF87D /* 6532Tests.swift */, 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */, 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */, + 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */, 4B1414611B58888700E04248 /* KlausDormannTests.swift */, 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */, 4B3BA0C41D318B44005DD7A7 /* Bridges */, @@ -1853,14 +1905,17 @@ files = ( 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, + 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, + 4BB697C71D4B558F00248BDF /* Factors.cpp in Sources */, 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, 4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */, 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */, + 4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */, 4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, @@ -1871,12 +1926,14 @@ 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */, 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, + 4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */, 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */, 4B1E85751D170228001EF87D /* Typer.cpp in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, 4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, + 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */, 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, @@ -1897,10 +1954,12 @@ 4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */, 4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */, 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */, + 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */, 4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */, 4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */, 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */, 4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */, + 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */, 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */, 4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */, 4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift index 42e9b97ed..0c446427c 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift @@ -32,7 +32,7 @@ class Vic20Document: MachineDocument { vic20.setCharactersROM(characters) } - if let drive = dataForResource("1541", ofType: "bin", inDirectory: "ROMImages/Commodore1540") { + if let drive = dataForResource("1540", ofType: "bin", inDirectory: "ROMImages/Commodore1540") { vic20.setDriveROM(drive) } } diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h b/OSBindings/Mac/Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h index 8333797fa..23c391390 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h +++ b/OSBindings/Mac/Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h @@ -6,3 +6,4 @@ #import "MOS6522Bridge.h" #import "MOS6532Bridge.h" #import "C1540Bridge.h" +#import "DigitalPhaseLockedLoopBridge.h" diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/DigitalPhaseLockedLoopBridge.h b/OSBindings/Mac/Clock SignalTests/Bridges/DigitalPhaseLockedLoopBridge.h new file mode 100644 index 000000000..9f39f0f7d --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/Bridges/DigitalPhaseLockedLoopBridge.h @@ -0,0 +1,20 @@ +// +// DigitalPhaseLockedLoopBridge.h +// Clock Signal +// +// Created by Thomas Harte on 12/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import + +@interface DigitalPhaseLockedLoopBridge : NSObject + +- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit tolerance:(NSUInteger)tolerance historyLength:(NSUInteger)historyLength; + +- (void)runForCycles:(NSUInteger)cycles; +- (void)addPulse; + +@property(nonatomic) NSUInteger stream; + +@end diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/DigitalPhaseLockedLoopBridge.mm b/OSBindings/Mac/Clock SignalTests/Bridges/DigitalPhaseLockedLoopBridge.mm new file mode 100644 index 000000000..7a4068f10 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/Bridges/DigitalPhaseLockedLoopBridge.mm @@ -0,0 +1,61 @@ +// +// DigitalPhaseLockedLoopBridge.m +// Clock Signal +// +// Created by Thomas Harte on 12/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "DigitalPhaseLockedLoopBridge.h" + +#include "DigitalPhaseLockedLoop.hpp" +#include + +@interface DigitalPhaseLockedLoopBridge(BitPushing) +- (void)pushBit:(int)value; +@end + +class DigitalPhaseLockedLoopDelegate: public Storage::DigitalPhaseLockedLoop::Delegate { + public: + __weak DigitalPhaseLockedLoopBridge *bridge; + + void digital_phase_locked_loop_output_bit(int value) + { + [bridge pushBit:value ? 1 : 0]; + } +}; + +@implementation DigitalPhaseLockedLoopBridge +{ + std::unique_ptr _digitalPhaseLockedLoop; + DigitalPhaseLockedLoopDelegate _delegate; +} + +- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit tolerance:(NSUInteger)tolerance historyLength:(NSUInteger)historyLength +{ + self = [super init]; + if(self) + { + _digitalPhaseLockedLoop.reset(new Storage::DigitalPhaseLockedLoop((unsigned int)clocksPerBit, (unsigned int)tolerance, (unsigned int)historyLength)); + _delegate.bridge = self; + _digitalPhaseLockedLoop->set_delegate(&_delegate); + } + return self; +} + +- (void)runForCycles:(NSUInteger)cycles +{ + _digitalPhaseLockedLoop->run_for_cycles((unsigned int)cycles); +} + +- (void)addPulse +{ + _digitalPhaseLockedLoop->add_pulse(); +} + +- (void)pushBit:(int)value +{ + _stream = (_stream << 1) | value; +} + +@end diff --git a/OSBindings/Mac/Clock SignalTests/DPLLTests.swift b/OSBindings/Mac/Clock SignalTests/DPLLTests.swift new file mode 100644 index 000000000..01d99c054 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/DPLLTests.swift @@ -0,0 +1,61 @@ +// +// DPLLTests.swift +// Clock Signal +// +// Created by Thomas Harte on 12/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +import XCTest + +class DPLLTests: XCTestCase { + + func testRegularNibblesOnPLL(pll: DigitalPhaseLockedLoopBridge, bitLength: UInt) { + // clock in two 1s, a 0, and a 1, 200 times over + for _ in 0 ..< 200 { + pll.runForCycles(bitLength/2) + pll.addPulse() + pll.runForCycles(bitLength) + pll.addPulse() + pll.runForCycles(bitLength*2) + pll.addPulse() + pll.runForCycles(bitLength/2) + } + + XCTAssert((pll.stream&0xffffffff) == 0xdddddddd, "PLL should have synchronised and clocked repeating 0xd nibbles; got \(String(pll.stream, radix: 16, uppercase: false))") + } + + func testPerfectInput() { + let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3) + testRegularNibblesOnPLL(pll, bitLength: 100) + } + + func testFastButRegular() { + let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3) + testRegularNibblesOnPLL(pll, bitLength: 90) + } + + func testSlowButRegular() { + let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3) + testRegularNibblesOnPLL(pll, bitLength: 110) + } + + func testTwentyPercentSinePattern() { + let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3) + var angle = 0.0 + + // clock in two 1s, a 0, and a 1, 200 times over + for _ in 0 ..< 200 { + let bitLength: UInt = UInt(100 + 20 * sin(angle)) + + pll.runForCycles(bitLength/2) + pll.addPulse() + pll.runForCycles((bitLength*3)/2) + + angle = angle + 0.1 + } + + let endOfStream = pll.stream&0xffffffff; + XCTAssert(endOfStream == 0xaaaaaaaa || endOfStream == 0x55555555, "PLL should have synchronised and clocked repeating 0xa or 0x5 nibbles; got \(String(pll.stream, radix: 16, uppercase: false))") + } +} diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 4b0434eb8..3134a8965 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -472,6 +472,7 @@ template class Processor { bool _ready_line_is_enabled; bool _irq_line_is_enabled, _irq_request_history; + bool _nmi_line_is_enabled, _set_overflow_line_is_enabled; /*! Gets the program representing an RST response. @@ -549,7 +550,10 @@ template class Processor { _interruptFlag(Flag::Interrupt), _s(0), _nextBusOperation(BusOperation::None), - _interrupt_requests(InterruptRequestFlags::PowerOn) + _interrupt_requests(InterruptRequestFlags::PowerOn), + _irq_line_is_enabled(false), + _nmi_line_is_enabled(false), + _set_overflow_line_is_enabled(false) { // only the interrupt flag is defined upon reset but get_flags isn't going to // mask the other flags so we need to do that, at least @@ -1237,6 +1241,19 @@ template class Processor { _irq_line_is_enabled = active; } + /*! + Sets the current level of the set overflow line. + + @param active @c true if the line is logically active; @c false otherwise. + */ + inline void set_overflow_line(bool active) + { + // a leading edge will set the overflow flag + if(active && !_set_overflow_line_is_enabled) + _overflowFlag = Flag::Overflow; + _set_overflow_line_is_enabled = active; + } + /*! Sets the current level of the NMI line. @@ -1245,7 +1262,9 @@ template class Processor { inline void set_nmi_line(bool active) { // NMI is edge triggered, not level - _interrupt_requests |= (active ? InterruptRequestFlags::NMI : 0); + if(active && !_nmi_line_is_enabled) + _interrupt_requests |= InterruptRequestFlags::NMI; + _nmi_line_is_enabled = active; } /*! diff --git a/SignalProcessing/Stepper.hpp b/SignalProcessing/Stepper.hpp index f91beb2a0..a874d8a51 100644 --- a/SignalProcessing/Stepper.hpp +++ b/SignalProcessing/Stepper.hpp @@ -13,19 +13,41 @@ namespace SignalProcessing { +/*! + Allows a repeating action running at an input rate to determine how many times it should + trigger an action that runs at an unrelated output rate; therefore it allows something with one + clock to sample something with another. + + Uses a Bresenham-like error term internally for full-integral storage with no drift. + + Pegs the beginning of both clocks to the time at which the stepper is created. So e.g. a stepper + that converts from an input clock of 1200 to an output clock of 2 will first fire on cycle 600. +*/ class Stepper { public: + /*! + Establishes a stepper with a one-to-one conversion rate. + */ Stepper() : Stepper(1,1) {} + /*! + Establishes a stepper that will receive steps at the @c input_rate and dictate the number + of steps that should be taken at the @c output_rate. + */ Stepper(uint64_t output_rate, uint64_t input_rate) : - accumulated_error_(0), + accumulated_error_(-((int64_t)input_rate << 1)), input_rate_(input_rate), output_rate_(output_rate), whole_step_(output_rate / input_rate), adjustment_up_((int64_t)(output_rate % input_rate) << 1), adjustment_down_((int64_t)input_rate << 1) {} + /*! + Advances one step at the input rate. + + @returns the number of output steps. + */ inline uint64_t step() { uint64_t update = whole_step_; @@ -38,11 +60,34 @@ class Stepper return update; } + /*! + Advances by @c number_of_steps steps at the input rate. + + @returns the number of output steps. + */ + inline uint64_t step(uint64_t number_of_steps) + { + uint64_t update = whole_step_ * number_of_steps; + accumulated_error_ += adjustment_up_ * (int64_t)number_of_steps; + if(accumulated_error_ > 0) + { + update += 1 + (uint64_t)(accumulated_error_ / adjustment_down_); + accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_; + } + return update; + } + + /*! + @returns the output rate. + */ inline uint64_t get_output_rate() { return output_rate_; } + /*! + @returns the input rate. + */ inline uint64_t get_input_rate() { return input_rate_; diff --git a/Storage/Disk/DigitalPhaseLockedLoop.cpp b/Storage/Disk/DigitalPhaseLockedLoop.cpp new file mode 100644 index 000000000..999e17b2d --- /dev/null +++ b/Storage/Disk/DigitalPhaseLockedLoop.cpp @@ -0,0 +1,76 @@ +// +// DigitalPhaseLockedLoop.cpp +// Clock Signal +// +// Created by Thomas Harte on 11/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "DigitalPhaseLockedLoop.hpp" +#include +#include + +using namespace Storage; + +DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, size_t length_of_history) : + _clocks_per_bit(clocks_per_bit), + _tolerance(tolerance), + + _phase(0), + _window_length(clocks_per_bit), + + _phase_error_pointer(0) +{ + _phase_error_history.reset(new std::vector(length_of_history, 0)); +} + +void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles) +{ + _phase += number_of_cycles; + int windows_crossed = _phase / _window_length; + if(windows_crossed) + { + // check whether this triggers any 0s, if anybody cares + if(_delegate) + { + if(_window_was_filled) windows_crossed--; + for(int c = 0; c < windows_crossed; c++) + _delegate->digital_phase_locked_loop_output_bit(0); + } + + _window_was_filled = false; + _phase %= _window_length; + } +} + +void DigitalPhaseLockedLoop::add_pulse() +{ + if(!_window_was_filled) + { + if(_delegate) _delegate->digital_phase_locked_loop_output_bit(1); + _window_was_filled = true; + post_phase_error(_phase - (_window_length >> 1)); + } +} + +void DigitalPhaseLockedLoop::post_phase_error(int error) +{ + // use a simple spring mechanism as a lowpass filter for phase + _phase -= (error + 1) >> 1; + + // use the average of the last few errors to affect frequency + std::vector *phase_error_history = _phase_error_history.get(); + size_t phase_error_history_size = phase_error_history->size(); + + (*phase_error_history)[_phase_error_pointer] = error; + _phase_error_pointer = (_phase_error_pointer + 1)%phase_error_history_size; + + int total_error = 0; + for(size_t c = 0; c < phase_error_history_size; c++) + { + total_error += (*phase_error_history)[c]; + } + int denominator = (int)(phase_error_history_size * 4); + _window_length += (total_error + (denominator >> 1)) / denominator; + _window_length = std::max(std::min(_window_length, _clocks_per_bit + _tolerance), _clocks_per_bit - _tolerance); +} diff --git a/Storage/Disk/DigitalPhaseLockedLoop.hpp b/Storage/Disk/DigitalPhaseLockedLoop.hpp new file mode 100644 index 000000000..da257b445 --- /dev/null +++ b/Storage/Disk/DigitalPhaseLockedLoop.hpp @@ -0,0 +1,69 @@ +// +// DigitalPhaseLockedLoop.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef DigitalPhaseLockedLoop_hpp +#define DigitalPhaseLockedLoop_hpp + +#include +#include + +namespace Storage { + +class DigitalPhaseLockedLoop { + public: + /*! + Instantiates a @c DigitalPhaseLockedLoop. + + @param clocks_per_bit The expected number of cycles between each bit of input. + @param tolerance The maximum tolerance for bit windows — extremes will be clocks_per_bit ± tolerance. + @param length_of_history The number of historic pulses to consider in locking to phase. + */ + DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, size_t length_of_history); + + /*! + Runs the loop, impliedly posting no pulses during that period. + + @c number_of_cycles The time to run the loop for. + */ + void run_for_cycles(int number_of_cycles); + + /*! + Announces a pulse at the current time. + */ + void add_pulse(); + + /*! + A receiver for PCM output data; called upon every recognised bit. + */ + class Delegate { + public: + virtual void digital_phase_locked_loop_output_bit(int value) = 0; + }; + void set_delegate(Delegate *delegate) + { + _delegate = delegate; + } + + private: + Delegate *_delegate; + + void post_phase_error(int error); + std::unique_ptr> _phase_error_history; + size_t _phase_error_pointer; + + int _phase; + int _window_length; + bool _window_was_filled; + + int _clocks_per_bit; + int _tolerance; +}; + +} + +#endif /* DigitalPhaseLockedLoop_hpp */ diff --git a/Storage/Disk/DiskDrive.cpp b/Storage/Disk/DiskDrive.cpp new file mode 100644 index 000000000..90a11a977 --- /dev/null +++ b/Storage/Disk/DiskDrive.cpp @@ -0,0 +1,118 @@ +// +// DiskDrive.cpp +// Clock Signal +// +// Created by Thomas Harte on 14/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "DiskDrive.hpp" + +using namespace Storage; + +DiskDrive::DiskDrive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) : + _clock_rate(clock_rate * clock_rate_multiplier), + _clock_rate_multiplier(clock_rate_multiplier), + _revolutions_per_minute(revolutions_per_minute), + _head_position(0), + + TimedEventLoop(clock_rate * clock_rate_multiplier) +{} + +void DiskDrive::set_expected_bit_length(Time bit_length) +{ + _bit_length = bit_length; + + // this conversion doesn't need to be exact because there's a lot of variation to be taken + // account of in rotation speed, air turbulence, etc, so a direct conversion will do + int clocks_per_bit = (int)((bit_length.length * _clock_rate) / bit_length.clock_rate); + _pll.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3)); + _pll->set_delegate(this); +} + +void DiskDrive::set_disk(std::shared_ptr disk) +{ + _disk = disk; + set_track(); +} + +bool DiskDrive::has_disk() +{ + return (bool)_disk; +} + +bool DiskDrive::get_is_track_zero() +{ + return _head_position == 0; +} + +void DiskDrive::step(int direction) +{ + _head_position = std::max(_head_position + direction, 0); + set_track(); +} + +void DiskDrive::set_track() +{ + _track = _disk->get_track_at_position((unsigned int)_head_position); + reset_timer(); + get_next_event(); +} + +void DiskDrive::run_for_cycles(int number_of_cycles) +{ + if(has_disk()) + { + number_of_cycles *= _clock_rate_multiplier; + while(number_of_cycles--) + { + _cycles_since_index_hole ++; + _pll->run_for_cycles(1); + TimedEventLoop::run_for_cycles(1); + } + } +} + +#pragma mark - Track timed event loop + +void DiskDrive::get_next_event() +{ + if(_track) + _current_event = _track->get_next_event(); + else + { + _current_event.length.length = 1; + _current_event.length.clock_rate = 1; + _current_event.type = Track::Event::IndexHole; + } + + // divide interval, which is in terms of a rotation of the disk, by rotation speed, and + // convert it into revolutions per second + Time event_interval = _current_event.length; + event_interval.length *= 60; + event_interval.clock_rate *= _revolutions_per_minute; + event_interval.simplify(); + set_next_event_time_interval(event_interval); +} + +void DiskDrive::process_next_event() +{ + switch(_current_event.type) + { + case Track::Event::FluxTransition: + _pll->add_pulse(); + break; + case Track::Event::IndexHole: + _cycles_since_index_hole = 0; + process_index_hole(); + break; + } + get_next_event(); +} + +#pragma mark - PLL delegate + +void DiskDrive::digital_phase_locked_loop_output_bit(int value) +{ + process_input_bit(value, _cycles_since_index_hole); +} diff --git a/Storage/Disk/DiskDrive.hpp b/Storage/Disk/DiskDrive.hpp new file mode 100644 index 000000000..7d3cb560a --- /dev/null +++ b/Storage/Disk/DiskDrive.hpp @@ -0,0 +1,62 @@ +// +// DiskDrive.hpp +// Clock Signal +// +// Created by Thomas Harte on 14/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef DiskDrive_hpp +#define DiskDrive_hpp + +#include "Disk.hpp" +#include "DigitalPhaseLockedLoop.hpp" +#include "../TimedEventLoop.hpp" + +namespace Storage { + +class DiskDrive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop { + public: + DiskDrive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute); + + void set_expected_bit_length(Time bit_length); + + void set_disk(std::shared_ptr disk); + bool has_disk(); + + void run_for_cycles(int number_of_cycles); + + bool get_is_track_zero(); + void step(int direction); + void set_motor_on(bool motor_on); + + // to satisfy DigitalPhaseLockedLoop::Delegate + void digital_phase_locked_loop_output_bit(int value); + + protected: + virtual void process_input_bit(int value, unsigned int cycles_since_index_hole) = 0; + virtual void process_index_hole() = 0; + + // for TimedEventLoop + virtual void process_next_event(); + + private: + Time _bit_length; + unsigned int _clock_rate; + unsigned int _clock_rate_multiplier; + unsigned int _revolutions_per_minute; + + std::shared_ptr _pll; + std::shared_ptr _disk; + std::shared_ptr _track; + int _head_position; + unsigned int _cycles_since_index_hole; + void set_track(); + + inline void get_next_event(); + Track::Event _current_event; +}; + +} + +#endif /* DiskDrive_hpp */ diff --git a/Storage/Disk/Encodings/CommodoreGCR.cpp b/Storage/Disk/Encodings/CommodoreGCR.cpp new file mode 100644 index 000000000..36667141d --- /dev/null +++ b/Storage/Disk/Encodings/CommodoreGCR.cpp @@ -0,0 +1,20 @@ +// +// CommodoreGCR.cpp +// Clock Signal +// +// Created by Thomas Harte on 29/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "CommodoreGCR.hpp" + +using namespace Storage; + +Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned int time_zone) +{ + Time duration; + // the speed zone divides a 4Mhz clock by 13, 14, 15 or 16, with higher-numbered zones being faster (i.e. each bit taking less time) + duration.length = 16 - time_zone; + duration.clock_rate = 4000000; + return duration; +} diff --git a/Storage/Disk/Encodings/CommodoreGCR.hpp b/Storage/Disk/Encodings/CommodoreGCR.hpp new file mode 100644 index 000000000..0617af963 --- /dev/null +++ b/Storage/Disk/Encodings/CommodoreGCR.hpp @@ -0,0 +1,22 @@ +// +// CommodoreGCR.hpp +// Clock Signal +// +// Created by Thomas Harte on 29/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef CommodoreGCR_hpp +#define CommodoreGCR_hpp + +#include "../../Storage.hpp" + +namespace Storage { +namespace Encodings { +namespace CommodoreGCR { + Time length_of_a_bit_in_time_zone(unsigned int time_zone); +} +} +} + +#endif /* CommodoreGCR_hpp */ diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index 67b243137..d68d8c299 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -10,6 +10,7 @@ #include #include "../PCMTrack.hpp" +#include "../Encodings/CommodoreGCR.hpp" using namespace Storage; @@ -40,7 +41,8 @@ G64::G64(const char *file_name) _maximum_track_size = (uint16_t)fgetc(_file); _maximum_track_size |= (uint16_t)fgetc(_file) << 8; - get_track_at_position(0); +// for(size_t c = 0; c < _number_of_tracks; c++) +// get_track_at_position(c); } G64::~G64() @@ -116,14 +118,14 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) unsigned int start_byte_in_current_speed = 0; for(unsigned int byte = 0; byte < track_length; byte ++) { - unsigned int byte_speed = speed_zone_contents[byte >> 2] >> (6 - (byte&3)*2); + unsigned int byte_speed = speed_zone_contents[byte >> 2] >> (6 - (byte&3)*2); if(byte_speed != current_speed || byte == (track_length-1)) { unsigned int number_of_bytes = byte - start_byte_in_current_speed; PCMSegment segment; - segment.duration.length = number_of_bytes * 8; - segment.duration.clock_rate = 4000000 / (13 + current_speed); // the speed zone divides a 4Mhz clock by 13, 14, 15 or 16; TODO: is this the right way around? Is zone 3 the fastest or the slowest? + segment.number_of_bits = number_of_bytes * 8; + segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(current_speed); segment.data.reset(new uint8_t[number_of_bytes]); memcpy(segment.data.get(), &track_contents.get()[start_byte_in_current_speed], number_of_bytes); segments.push_back(std::move(segment)); @@ -138,8 +140,8 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) else { PCMSegment segment; - segment.duration.length = track_length * 8; - segment.duration.clock_rate = 1; // this is arbitrary; if supplying only one PCMSegment then it'll naturally fill the track + segment.number_of_bits = track_length * 8; + segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)speed_zone_offset); segment.data = std::move(track_contents); resulting_track.reset(new PCMTrack(std::move(segment))); diff --git a/Storage/Disk/PCMTrack.cpp b/Storage/Disk/PCMTrack.cpp index a25d31527..32dc79a9d 100644 --- a/Storage/Disk/PCMTrack.cpp +++ b/Storage/Disk/PCMTrack.cpp @@ -7,34 +7,10 @@ // #include "PCMTrack.hpp" +#include "../../NumberTheory/Factors.hpp" using namespace Storage; -unsigned int greatest_common_divisor(unsigned int a, unsigned int b) -{ - if(a < b) - { - unsigned int swap = b; - b = a; - a = swap; - } - - while(1) { - if(!a) return b; - if(!b) return a; - - unsigned int remainder = a%b; - a = b; - b = remainder; - } -} - -unsigned int least_common_multiple(unsigned int a, unsigned int b) -{ - unsigned int gcd = greatest_common_divisor(a, b); - return (a*b) / gcd; -} - PCMTrack::PCMTrack(std::vector segments) { _segments = std::move(segments); @@ -55,15 +31,17 @@ PCMTrack::Event PCMTrack::get_next_event() _next_event.length.length = 0; while(_segment_pointer < _segments.size()) { - unsigned int clock_multiplier = _track_clock_rate / _segments[_segment_pointer].duration.clock_rate; + unsigned int clock_multiplier = _track_clock_rate / _segments[_segment_pointer].length_of_a_bit.clock_rate; + unsigned int bit_length = clock_multiplier * _segments[_segment_pointer].length_of_a_bit.length; + const uint8_t *segment_data = _segments[_segment_pointer].data.get(); - while(_bit_pointer < _segments[_segment_pointer].duration.length) + while(_bit_pointer < _segments[_segment_pointer].number_of_bits) { // for timing simplicity, bits are modelled as happening at the end of their window // TODO: should I account for the converse bit ordering? Or can I assume MSB first? int bit = segment_data[_bit_pointer >> 3] & (0x80 >> (_bit_pointer&7)); _bit_pointer++; - _next_event.length.length += clock_multiplier; + _next_event.length.length += bit_length; if(bit) return _next_event; } @@ -84,18 +62,18 @@ PCMTrack::Event PCMTrack::get_next_event() void PCMTrack::fix_length() { // find the least common multiple of all segment clock rates - _track_clock_rate = _segments[0].duration.clock_rate; + _track_clock_rate = _segments[0].length_of_a_bit.clock_rate; for(size_t c = 1; c < _segments.size(); c++) { - _track_clock_rate = least_common_multiple(_track_clock_rate, _segments[c].duration.clock_rate); + _track_clock_rate = NumberTheory::least_common_multiple(_track_clock_rate, _segments[c].length_of_a_bit.clock_rate); } - // therby determine the total length, storing it to next_event as the divisor + // thereby determine the total length, storing it to next_event as the track-total divisor _next_event.length.clock_rate = 0; for(size_t c = 0; c < _segments.size(); c++) { - unsigned int multiplier = _track_clock_rate / _segments[c].duration.clock_rate; - _next_event.length.clock_rate += _segments[c].duration.length * multiplier; + unsigned int multiplier = _track_clock_rate / _segments[c].length_of_a_bit.clock_rate; + _next_event.length.clock_rate += _segments[c].length_of_a_bit.length * _segments[c].number_of_bits * multiplier; } _segment_pointer = _bit_pointer = 0; diff --git a/Storage/Disk/PCMTrack.hpp b/Storage/Disk/PCMTrack.hpp index 0cfc0f451..758846926 100644 --- a/Storage/Disk/PCMTrack.hpp +++ b/Storage/Disk/PCMTrack.hpp @@ -15,22 +15,13 @@ namespace Storage { /*! - A segment of PCM-sampled data. The clock rate in the duration is taken to be relative to all other - segments that comprise a track rather than absolute, and the length is taken to be the number of - bits from @c data that are actually present. + A segment of PCM-sampled data. Bits from each byte are taken MSB to LSB. - - Actual segment lengths will be calculated such that all segments that comprise a track exactly fill the track. - - So the segment for a track with only a single segment may supply any clock rate other than 0. It will exactly - fill the track, so if it has 7 samples then there will be at most a flux transition every 1/7th of a rotation. - - If a track consists of two segments, one with clock rate 1 and one with clock rate 2, the second will be - clocked twice as fast as the first. */ struct PCMSegment { - Time duration; + Time length_of_a_bit; + unsigned int number_of_bits; std::unique_ptr data; }; diff --git a/Storage/Storage.hpp b/Storage/Storage.hpp index ac65da093..91abe794e 100644 --- a/Storage/Storage.hpp +++ b/Storage/Storage.hpp @@ -9,10 +9,24 @@ #ifndef Storage_hpp #define Storage_hpp +#include "../NumberTheory/Factors.hpp" + namespace Storage { struct Time { unsigned int length, clock_rate; + + inline void simplify() + { + unsigned int common_divisor = NumberTheory::greatest_common_divisor(length, clock_rate); + length /= common_divisor; + clock_rate /= common_divisor; + } + + inline float get_float() + { + return (float)length / (float)clock_rate; + } }; } diff --git a/Storage/Tape/Tape.cpp b/Storage/Tape/Tape.cpp index 822093b1c..8717a5ce4 100644 --- a/Storage/Tape/Tape.cpp +++ b/Storage/Tape/Tape.cpp @@ -7,6 +7,7 @@ // #include "Tape.hpp" +#include "../../NumberTheory/Factors.hpp" using namespace Storage; @@ -16,12 +17,13 @@ void Tape::seek(Time seek_time) } TapePlayer::TapePlayer(unsigned int input_clock_rate) : - _input_clock_rate(input_clock_rate) + TimedEventLoop(input_clock_rate) {} void TapePlayer::set_tape(std::shared_ptr tape) { _tape = tape; + reset_timer(); get_next_pulse(); } @@ -32,38 +34,34 @@ bool TapePlayer::has_tape() void TapePlayer::get_next_pulse() { - _input.time_into_pulse = 0; + // get the new pulse if(_tape) - _input.current_pulse = _tape->get_next_pulse(); + _current_pulse = _tape->get_next_pulse(); else { - _input.current_pulse.length.length = 1; - _input.current_pulse.length.clock_rate = 1; - _input.current_pulse.type = Storage::Tape::Pulse::Zero; - } - if(_input.pulse_stepper == nullptr || _input.current_pulse.length.clock_rate != _input.pulse_stepper->get_output_rate()) - { - _input.pulse_stepper.reset(new SignalProcessing::Stepper(_input.current_pulse.length.clock_rate, _input_clock_rate)); + _current_pulse.length.length = 1; + _current_pulse.length.clock_rate = 1; + _current_pulse.type = Storage::Tape::Pulse::Zero; } + + set_next_event_time_interval(_current_pulse.length); } -void TapePlayer::run_for_cycles(unsigned int number_of_cycles) +void TapePlayer::run_for_cycles(int number_of_cycles) { if(has_tape()) { - while(number_of_cycles--) - { - _input.time_into_pulse += (unsigned int)_input.pulse_stepper->step(); - while(_input.time_into_pulse >= _input.current_pulse.length.length) - { - run_for_input_pulse(); - } - } + ::TimedEventLoop::run_for_cycles(number_of_cycles); } } void TapePlayer::run_for_input_pulse() { - process_input_pulse(_input.current_pulse); + jump_to_next_event(); +} + +void TapePlayer::process_next_event() +{ + process_input_pulse(_current_pulse); get_next_pulse(); } diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index 47babe011..47ebea129 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -10,8 +10,7 @@ #define Tape_hpp #include -#include "../../SignalProcessing/Stepper.hpp" -#include "../Storage.hpp" +#include "../TimedEventLoop.hpp" namespace Storage { @@ -48,29 +47,25 @@ class Tape { Will call @c process_input_pulse instantaneously upon reaching *the end* of a pulse. Therefore a subclass can decode pulses into data within process_input_pulse, using the supplied pulse's @c length and @c type. */ -class TapePlayer { +class TapePlayer: public TimedEventLoop { public: TapePlayer(unsigned int input_clock_rate); void set_tape(std::shared_ptr tape); bool has_tape(); - void run_for_cycles(unsigned int number_of_cycles); + void run_for_cycles(int number_of_cycles); void run_for_input_pulse(); protected: + virtual void process_next_event(); virtual void process_input_pulse(Tape::Pulse pulse) = 0; private: inline void get_next_pulse(); - unsigned int _input_clock_rate; std::shared_ptr _tape; - struct { - Tape::Pulse current_pulse; - std::unique_ptr pulse_stepper; - uint32_t time_into_pulse; - } _input; + Tape::Pulse _current_pulse; }; } diff --git a/Storage/TimedEventLoop.cpp b/Storage/TimedEventLoop.cpp new file mode 100644 index 000000000..b0851b7f8 --- /dev/null +++ b/Storage/TimedEventLoop.cpp @@ -0,0 +1,72 @@ +// +// TimedEventLoop.cpp +// Clock Signal +// +// Created by Thomas Harte on 29/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "TimedEventLoop.hpp" +#include "../NumberTheory/Factors.hpp" + +using namespace Storage; + +TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) : + _input_clock_rate(input_clock_rate) {} + +void TimedEventLoop::run_for_cycles(int number_of_cycles) +{ + _time_into_interval += (unsigned int)_stepper->step((uint64_t)number_of_cycles); + while(_time_into_interval >= _event_interval.length) + { + process_next_event(); + } +} + +void TimedEventLoop::reset_timer() +{ + _time_into_interval = 0; + _stepper.reset(); +} + +void TimedEventLoop::jump_to_next_event() +{ + reset_timer(); + process_next_event(); +} + +void TimedEventLoop::set_next_event_time_interval(Time interval) +{ + // figure out how much time has been run since the last bit ended + if(_stepper) + { + _time_into_interval -= _event_interval.length; + if(_time_into_interval) + { + // simplify the quotient + unsigned int common_divisor = NumberTheory::greatest_common_divisor(_time_into_interval, _event_interval.clock_rate); + _time_into_interval /= common_divisor; + _event_interval.clock_rate /= common_divisor; + + // build a quotient that is the sum of the time overrun plus the incoming time and adjust the time overrun + // to be in terms of the new quotient + unsigned int denominator = NumberTheory::least_common_multiple(_event_interval.clock_rate, interval.clock_rate); + interval.length *= denominator / interval.clock_rate; + interval.clock_rate = denominator; + _time_into_interval *= denominator / _event_interval.clock_rate; + } + } + else + { + _time_into_interval = 0; + } + + // store new interval + _event_interval = interval; + + // adjust stepper if required + if(!_stepper || _event_interval.clock_rate != _stepper->get_output_rate()) + { + _stepper.reset(new SignalProcessing::Stepper(_event_interval.clock_rate, _input_clock_rate)); + } +} diff --git a/Storage/TimedEventLoop.hpp b/Storage/TimedEventLoop.hpp new file mode 100644 index 000000000..232ff8c6d --- /dev/null +++ b/Storage/TimedEventLoop.hpp @@ -0,0 +1,40 @@ +// +// TimedEventLoop.hpp +// Clock Signal +// +// Created by Thomas Harte on 29/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef TimedEventLoop_hpp +#define TimedEventLoop_hpp + +#include "Storage.hpp" + +#include +#include "../SignalProcessing/Stepper.hpp" + +namespace Storage { + + class TimedEventLoop { + public: + TimedEventLoop(unsigned int input_clock_rate); + void run_for_cycles(int number_of_cycles); + + protected: + void reset_timer(); + void set_next_event_time_interval(Time interval); + void jump_to_next_event(); + + virtual void process_next_event() = 0; + + private: + unsigned int _input_clock_rate; + Time _event_interval; + std::unique_ptr _stepper; + uint32_t _time_into_interval; + }; + +} + +#endif /* TimedEventLoop_hpp */