mirror of
https://github.com/TomHarte/CLK.git
synced 2024-09-01 17:29:12 +00:00
Merge pull request #725 from TomHarte/FasterDPLL
Improve DPLL implementation.
This commit is contained in:
commit
8c90ec4636
@ -39,7 +39,6 @@
|
|||||||
4B055AA11FAE85DA0060FFFF /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; };
|
4B055AA11FAE85DA0060FFFF /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; };
|
||||||
4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; };
|
4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; };
|
||||||
4B055AA31FAE85DF0060FFFF /* ImplicitSectors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */; };
|
4B055AA31FAE85DF0060FFFF /* ImplicitSectors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */; };
|
||||||
4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */; };
|
|
||||||
4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; };
|
4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; };
|
||||||
4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; };
|
4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; };
|
||||||
4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; };
|
4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; };
|
||||||
@ -189,7 +188,6 @@
|
|||||||
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518771F75E91800926311 /* UnformattedTrack.cpp */; };
|
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518771F75E91800926311 /* UnformattedTrack.cpp */; };
|
||||||
4B4518851F75E91A00926311 /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187A1F75E91900926311 /* DiskController.cpp */; };
|
4B4518851F75E91A00926311 /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187A1F75E91900926311 /* DiskController.cpp */; };
|
||||||
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187C1F75E91900926311 /* MFMDiskController.cpp */; };
|
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187C1F75E91900926311 /* MFMDiskController.cpp */; };
|
||||||
4B4518871F75E91A00926311 /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */; };
|
|
||||||
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45188D1F75FD1B00926311 /* AcornADF.cpp */; };
|
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45188D1F75FD1B00926311 /* AcornADF.cpp */; };
|
||||||
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45188F1F75FD1B00926311 /* CPCDSK.cpp */; };
|
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45188F1F75FD1B00926311 /* CPCDSK.cpp */; };
|
||||||
4B4518A11F75FD1C00926311 /* D64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518911F75FD1B00926311 /* D64.cpp */; };
|
4B4518A11F75FD1C00926311 /* D64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518911F75FD1B00926311 /* D64.cpp */; };
|
||||||
@ -248,7 +246,6 @@
|
|||||||
4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; };
|
4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; };
|
||||||
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
|
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
|
||||||
4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
|
4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
|
||||||
4B778EF223A5DB100000D260 /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */; };
|
|
||||||
4B778EF323A5DB230000D260 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
|
4B778EF323A5DB230000D260 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
|
||||||
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
|
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
|
||||||
4B778EF523A5DB440000D260 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
|
4B778EF523A5DB440000D260 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
|
||||||
@ -1063,7 +1060,6 @@
|
|||||||
4B45187B1F75E91900926311 /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
|
4B45187B1F75E91900926311 /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
|
||||||
4B45187C1F75E91900926311 /* MFMDiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MFMDiskController.cpp; sourceTree = "<group>"; };
|
4B45187C1F75E91900926311 /* MFMDiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MFMDiskController.cpp; sourceTree = "<group>"; };
|
||||||
4B45187D1F75E91900926311 /* MFMDiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MFMDiskController.hpp; sourceTree = "<group>"; };
|
4B45187D1F75E91900926311 /* MFMDiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MFMDiskController.hpp; sourceTree = "<group>"; };
|
||||||
4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DigitalPhaseLockedLoop.cpp; sourceTree = "<group>"; };
|
|
||||||
4B4518801F75E91900926311 /* DigitalPhaseLockedLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DigitalPhaseLockedLoop.hpp; sourceTree = "<group>"; };
|
4B4518801F75E91900926311 /* DigitalPhaseLockedLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DigitalPhaseLockedLoop.hpp; sourceTree = "<group>"; };
|
||||||
4B4518881F75ECB100926311 /* Track.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Track.hpp; sourceTree = "<group>"; };
|
4B4518881F75ECB100926311 /* Track.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Track.hpp; sourceTree = "<group>"; };
|
||||||
4B45188B1F75FD1B00926311 /* DiskImage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskImage.hpp; sourceTree = "<group>"; };
|
4B45188B1F75FD1B00926311 /* DiskImage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskImage.hpp; sourceTree = "<group>"; };
|
||||||
@ -2235,7 +2231,6 @@
|
|||||||
4B45187E1F75E91900926311 /* DPLL */ = {
|
4B45187E1F75E91900926311 /* DPLL */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */,
|
|
||||||
4B4518801F75E91900926311 /* DigitalPhaseLockedLoop.hpp */,
|
4B4518801F75E91900926311 /* DigitalPhaseLockedLoop.hpp */,
|
||||||
);
|
);
|
||||||
path = DPLL;
|
path = DPLL;
|
||||||
@ -4214,7 +4209,6 @@
|
|||||||
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
|
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
|
||||||
4B0ACC3123775819008902D0 /* TIASound.cpp in Sources */,
|
4B0ACC3123775819008902D0 /* TIASound.cpp in Sources */,
|
||||||
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
|
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
|
||||||
4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */,
|
|
||||||
4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */,
|
4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */,
|
||||||
4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
|
4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
|
||||||
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */,
|
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */,
|
||||||
@ -4542,7 +4536,6 @@
|
|||||||
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
||||||
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
||||||
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */,
|
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */,
|
||||||
4B4518871F75E91A00926311 /* DigitalPhaseLockedLoop.cpp in Sources */,
|
|
||||||
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */,
|
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */,
|
||||||
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */,
|
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */,
|
||||||
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */,
|
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */,
|
||||||
@ -4642,7 +4635,6 @@
|
|||||||
4B778F0E23A5EC4F0000D260 /* Tape.cpp in Sources */,
|
4B778F0E23A5EC4F0000D260 /* Tape.cpp in Sources */,
|
||||||
4B778F2D23A5EF190000D260 /* MFMDiskController.cpp in Sources */,
|
4B778F2D23A5EF190000D260 /* MFMDiskController.cpp in Sources */,
|
||||||
4B778F2723A5EEF60000D260 /* BinaryDump.cpp in Sources */,
|
4B778F2723A5EEF60000D260 /* BinaryDump.cpp in Sources */,
|
||||||
4B778EF223A5DB100000D260 /* DigitalPhaseLockedLoop.cpp in Sources */,
|
|
||||||
4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */,
|
4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */,
|
||||||
4B778F5223A5F22F0000D260 /* StaticAnalyser.cpp in Sources */,
|
4B778F5223A5F22F0000D260 /* StaticAnalyser.cpp in Sources */,
|
||||||
4B778F4923A5F1F40000D260 /* StaticAnalyser.cpp in Sources */,
|
4B778F4923A5F1F40000D260 /* StaticAnalyser.cpp in Sources */,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
@interface DigitalPhaseLockedLoopBridge : NSObject
|
@interface DigitalPhaseLockedLoopBridge : NSObject
|
||||||
|
|
||||||
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit historyLength:(NSUInteger)historyLength;
|
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit;
|
||||||
|
|
||||||
- (void)runForCycles:(NSUInteger)cycles;
|
- (void)runForCycles:(NSUInteger)cycles;
|
||||||
- (void)addPulse;
|
- (void)addPulse;
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
- (void)pushBit:(int)value;
|
- (void)pushBit:(int)value;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
class DigitalPhaseLockedLoopDelegate: public Storage::DigitalPhaseLockedLoop::Delegate {
|
class DigitalPhaseLockedLoopDelegate {
|
||||||
public:
|
public:
|
||||||
__weak DigitalPhaseLockedLoopBridge *bridge;
|
__weak DigitalPhaseLockedLoopBridge *bridge;
|
||||||
|
|
||||||
@ -25,14 +25,14 @@ class DigitalPhaseLockedLoopDelegate: public Storage::DigitalPhaseLockedLoop::De
|
|||||||
};
|
};
|
||||||
|
|
||||||
@implementation DigitalPhaseLockedLoopBridge {
|
@implementation DigitalPhaseLockedLoopBridge {
|
||||||
std::unique_ptr<Storage::DigitalPhaseLockedLoop> _digitalPhaseLockedLoop;
|
std::unique_ptr<Storage::DigitalPhaseLockedLoop<DigitalPhaseLockedLoopDelegate>> _digitalPhaseLockedLoop;
|
||||||
DigitalPhaseLockedLoopDelegate _delegate;
|
DigitalPhaseLockedLoopDelegate _delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit historyLength:(NSUInteger)historyLength {
|
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if(self) {
|
if(self) {
|
||||||
_digitalPhaseLockedLoop = std::make_unique<Storage::DigitalPhaseLockedLoop>((unsigned int)clocksPerBit, (unsigned int)historyLength);
|
_digitalPhaseLockedLoop = std::make_unique<Storage::DigitalPhaseLockedLoop<DigitalPhaseLockedLoopDelegate>>((unsigned int)clocksPerBit);
|
||||||
_delegate.bridge = self;
|
_delegate.bridge = self;
|
||||||
_digitalPhaseLockedLoop->set_delegate(&_delegate);
|
_digitalPhaseLockedLoop->set_delegate(&_delegate);
|
||||||
}
|
}
|
||||||
|
@ -26,22 +26,22 @@ class DPLLTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testPerfectInput() {
|
func testPerfectInput() {
|
||||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||||
testRegularNibblesOnPLL(pll!, bitLength: 100)
|
testRegularNibblesOnPLL(pll!, bitLength: 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFastButRegular() {
|
func testFastButRegular() {
|
||||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||||
testRegularNibblesOnPLL(pll!, bitLength: 90)
|
testRegularNibblesOnPLL(pll!, bitLength: 90)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSlowButRegular() {
|
func testSlowButRegular() {
|
||||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||||
testRegularNibblesOnPLL(pll!, bitLength: 110)
|
testRegularNibblesOnPLL(pll!, bitLength: 110)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTwentyPercentSinePattern() {
|
func testTwentyPercentSinePattern() {
|
||||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||||
var angle = 0.0
|
var angle = 0.0
|
||||||
|
|
||||||
// clock in two 1s, a 0, and a 1, 200 times over
|
// clock in two 1s, a 0, and a 1, 200 times over
|
||||||
|
@ -15,10 +15,9 @@ using namespace Storage::Disk;
|
|||||||
Controller::Controller(Cycles clock_rate) :
|
Controller::Controller(Cycles clock_rate) :
|
||||||
clock_rate_multiplier_(128000000 / clock_rate.as_integral()),
|
clock_rate_multiplier_(128000000 / clock_rate.as_integral()),
|
||||||
clock_rate_(clock_rate.as_integral() * clock_rate_multiplier_),
|
clock_rate_(clock_rate.as_integral() * clock_rate_multiplier_),
|
||||||
|
pll_(100, *this),
|
||||||
empty_drive_(new Drive(int(clock_rate.as_integral()), 1, 1)) {
|
empty_drive_(new Drive(int(clock_rate.as_integral()), 1, 1)) {
|
||||||
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
|
set_expected_bit_length(Time(1));
|
||||||
Time one(1);
|
|
||||||
set_expected_bit_length(one);
|
|
||||||
set_drive(empty_drive_);
|
set_drive(empty_drive_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,13 +41,13 @@ Drive &Controller::get_drive() {
|
|||||||
|
|
||||||
void Controller::process_event(const Drive::Event &event) {
|
void Controller::process_event(const Drive::Event &event) {
|
||||||
switch(event.type) {
|
switch(event.type) {
|
||||||
case Track::Event::FluxTransition: pll_->add_pulse(); break;
|
case Track::Event::FluxTransition: pll_.add_pulse(); break;
|
||||||
case Track::Event::IndexHole: process_index_hole(); break;
|
case Track::Event::IndexHole: process_index_hole(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::advance(const Cycles cycles) {
|
void Controller::advance(const Cycles cycles) {
|
||||||
if(is_reading_) pll_->run_for(Cycles(cycles.as_integral() * clock_rate_multiplier_));
|
if(is_reading_) pll_.run_for(Cycles(cycles.as_integral() * clock_rate_multiplier_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::process_write_completed() {
|
void Controller::process_write_completed() {
|
||||||
@ -66,9 +65,8 @@ void Controller::set_expected_bit_length(Time bit_length) {
|
|||||||
|
|
||||||
// this conversion doesn't need to be exact because there's a lot of variation to be taken
|
// 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
|
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
|
||||||
int clocks_per_bit = cycles_per_bit.get<int>();
|
const int clocks_per_bit = cycles_per_bit.get<int>();
|
||||||
pll_ = std::make_unique<DigitalPhaseLockedLoop>(clocks_per_bit, 3);
|
pll_.set_clocks_per_bit(clocks_per_bit);
|
||||||
pll_->set_delegate(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::digital_phase_locked_loop_output_bit(int value) {
|
void Controller::digital_phase_locked_loop_output_bit(int value) {
|
||||||
|
@ -29,7 +29,6 @@ namespace Disk {
|
|||||||
TODO: communication of head size and permissible stepping extents, appropriate simulation of gain.
|
TODO: communication of head size and permissible stepping extents, appropriate simulation of gain.
|
||||||
*/
|
*/
|
||||||
class Controller:
|
class Controller:
|
||||||
public DigitalPhaseLockedLoop::Delegate,
|
|
||||||
public Drive::EventDelegate,
|
public Drive::EventDelegate,
|
||||||
public ClockingHint::Source,
|
public ClockingHint::Source,
|
||||||
public ClockingHint::Observer {
|
public ClockingHint::Observer {
|
||||||
@ -111,7 +110,9 @@ class Controller:
|
|||||||
|
|
||||||
bool is_reading_ = true;
|
bool is_reading_ = true;
|
||||||
|
|
||||||
std::shared_ptr<DigitalPhaseLockedLoop> pll_;
|
DigitalPhaseLockedLoop<Controller> pll_;
|
||||||
|
friend DigitalPhaseLockedLoop<Controller>;
|
||||||
|
|
||||||
std::shared_ptr<Drive> drive_;
|
std::shared_ptr<Drive> drive_;
|
||||||
|
|
||||||
std::shared_ptr<Drive> empty_drive_;
|
std::shared_ptr<Drive> empty_drive_;
|
||||||
@ -124,7 +125,7 @@ class Controller:
|
|||||||
void advance(const Cycles cycles) override ;
|
void advance(const Cycles cycles) override ;
|
||||||
|
|
||||||
// to satisfy DigitalPhaseLockedLoop::Delegate
|
// to satisfy DigitalPhaseLockedLoop::Delegate
|
||||||
void digital_phase_locked_loop_output_bit(int value) override;
|
void digital_phase_locked_loop_output_bit(int value);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
//
|
|
||||||
// DigitalPhaseLockedLoop.cpp
|
|
||||||
// Clock Signal
|
|
||||||
//
|
|
||||||
// Created by Thomas Harte on 11/07/2016.
|
|
||||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "DigitalPhaseLockedLoop.hpp"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
using namespace Storage;
|
|
||||||
|
|
||||||
DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, std::size_t length_of_history) :
|
|
||||||
offset_history_(length_of_history, 0),
|
|
||||||
window_length_(clocks_per_bit),
|
|
||||||
clocks_per_bit_(clocks_per_bit) {}
|
|
||||||
|
|
||||||
void DigitalPhaseLockedLoop::run_for(const Cycles cycles) {
|
|
||||||
offset_ += cycles.as_integral();
|
|
||||||
phase_ += cycles.as_integral();
|
|
||||||
if(phase_ >= window_length_) {
|
|
||||||
auto windows_crossed = phase_ / window_length_;
|
|
||||||
|
|
||||||
// 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_offset(phase_, offset_);
|
|
||||||
offset_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DigitalPhaseLockedLoop::post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) {
|
|
||||||
offset_history_[offset_history_pointer_] = new_offset;
|
|
||||||
offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size();
|
|
||||||
|
|
||||||
// use an unweighted average of the stored offsets to compute current window size,
|
|
||||||
// bucketing them by rounding to the nearest multiple of the base clocks per bit
|
|
||||||
Cycles::IntType total_spacing = 0;
|
|
||||||
Cycles::IntType total_divisor = 0;
|
|
||||||
for(auto offset : offset_history_) {
|
|
||||||
auto multiple = (offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_;
|
|
||||||
if(!multiple) continue;
|
|
||||||
total_divisor += multiple;
|
|
||||||
total_spacing += offset;
|
|
||||||
}
|
|
||||||
if(total_divisor) {
|
|
||||||
window_length_ = total_spacing / total_divisor;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto error = new_phase - (window_length_ >> 1);
|
|
||||||
|
|
||||||
// use a simple spring mechanism as a lowpass filter for phase
|
|
||||||
phase_ -= (error + 1) >> 1;
|
|
||||||
}
|
|
@ -9,6 +9,8 @@
|
|||||||
#ifndef DigitalPhaseLockedLoop_hpp
|
#ifndef DigitalPhaseLockedLoop_hpp
|
||||||
#define DigitalPhaseLockedLoop_hpp
|
#define DigitalPhaseLockedLoop_hpp
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -16,54 +18,117 @@
|
|||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
|
|
||||||
class DigitalPhaseLockedLoop {
|
/*!
|
||||||
|
Template parameters:
|
||||||
|
|
||||||
|
@c bit_handler A class that must implement a method, digital_phase_locked_loop_output_bit(int) for receving bits from the DPLL.
|
||||||
|
@c length_of_history The number of historic pulses to consider in locking to phase.
|
||||||
|
*/
|
||||||
|
template <typename BitHandler, size_t length_of_history = 3> class DigitalPhaseLockedLoop {
|
||||||
public:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Instantiates a @c DigitalPhaseLockedLoop.
|
Instantiates a @c DigitalPhaseLockedLoop.
|
||||||
|
|
||||||
@param clocks_per_bit The expected number of cycles between each bit of input.
|
@param clocks_per_bit The expected number of cycles between each bit of input.
|
||||||
@param length_of_history The number of historic pulses to consider in locking to phase.
|
|
||||||
*/
|
*/
|
||||||
DigitalPhaseLockedLoop(int clocks_per_bit, std::size_t length_of_history);
|
DigitalPhaseLockedLoop(int clocks_per_bit, BitHandler &handler) :
|
||||||
|
bit_handler_(handler), window_length_(clocks_per_bit), clocks_per_bit_(clocks_per_bit) {}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Changes the expected window length.
|
||||||
|
*/
|
||||||
|
void set_clocks_per_bit(int clocks_per_bit) {
|
||||||
|
window_length_ = clocks_per_bit_ = clocks_per_bit;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Runs the loop, impliedly posting no pulses during that period.
|
Runs the loop, impliedly posting no pulses during that period.
|
||||||
|
|
||||||
@c number_of_cycles The time to run the loop for.
|
@c number_of_cycles The time to run the loop for.
|
||||||
*/
|
*/
|
||||||
void run_for(const Cycles cycles);
|
void run_for(const Cycles cycles) {
|
||||||
|
offset_ += cycles.as_integral();
|
||||||
|
phase_ += cycles.as_integral();
|
||||||
|
if(phase_ >= window_length_) {
|
||||||
|
auto windows_crossed = phase_ / window_length_;
|
||||||
|
|
||||||
|
// Check whether this triggers any 0s.
|
||||||
|
if(window_was_filled_) --windows_crossed;
|
||||||
|
for(int c = 0; c < windows_crossed; c++)
|
||||||
|
bit_handler_.digital_phase_locked_loop_output_bit(0);
|
||||||
|
|
||||||
|
window_was_filled_ = false;
|
||||||
|
phase_ %= window_length_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Announces a pulse at the current time.
|
Announces a pulse at the current time.
|
||||||
*/
|
*/
|
||||||
void add_pulse();
|
void add_pulse() {
|
||||||
|
if(!window_was_filled_) {
|
||||||
/*!
|
bit_handler_.digital_phase_locked_loop_output_bit(1);
|
||||||
A receiver for PCM output data; called upon every recognised bit.
|
window_was_filled_ = true;
|
||||||
*/
|
post_phase_offset(phase_, offset_);
|
||||||
class Delegate {
|
offset_ = 0;
|
||||||
public:
|
}
|
||||||
virtual void digital_phase_locked_loop_output_bit(int value) = 0;
|
|
||||||
};
|
|
||||||
void set_delegate(Delegate *delegate) {
|
|
||||||
delegate_ = delegate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Delegate *delegate_ = nullptr;
|
BitHandler &bit_handler_;
|
||||||
|
|
||||||
void post_phase_offset(Cycles::IntType phase, Cycles::IntType offset);
|
void post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) {
|
||||||
|
// Erase the effect of whatever is currently in this slot.
|
||||||
|
total_divisor_ -= offset_history_[offset_history_pointer_].divisor;
|
||||||
|
total_spacing_ -= offset_history_[offset_history_pointer_].spacing;
|
||||||
|
|
||||||
std::vector<Cycles::IntType> offset_history_;
|
// Fill in the new fields.
|
||||||
|
const auto multiple = (new_offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_;
|
||||||
|
offset_history_[offset_history_pointer_].divisor = multiple;
|
||||||
|
offset_history_[offset_history_pointer_].spacing = new_offset;
|
||||||
|
|
||||||
|
// Add in the new values;
|
||||||
|
total_divisor_ += offset_history_[offset_history_pointer_].divisor;
|
||||||
|
total_spacing_ += offset_history_[offset_history_pointer_].spacing;
|
||||||
|
|
||||||
|
// Advance the write slot.
|
||||||
|
offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size();
|
||||||
|
|
||||||
|
// In net: use an unweighted average of the stored offsets to compute current window size,
|
||||||
|
// bucketing them by rounding to the nearest multiple of the base clocks per bit
|
||||||
|
window_length_ = total_spacing_ / total_divisor_;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
bool are_all_filled = true;
|
||||||
|
for(auto offset: offset_history_) {
|
||||||
|
if(offset.spacing == 1) {
|
||||||
|
are_all_filled = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(!are_all_filled || (window_length_ >= ((clocks_per_bit_ * 9) / 10) && window_length_ <= ((clocks_per_bit_ * 11) / 10)));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Also apply a difference to phase, use a simple spring mechanism as a lowpass filter.
|
||||||
|
const auto error = new_phase - (window_length_ >> 1);
|
||||||
|
phase_ -= (error + 1) >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoggedOffset {
|
||||||
|
Cycles::IntType divisor = 1, spacing = 1;
|
||||||
|
};
|
||||||
|
std::array<LoggedOffset, length_of_history> offset_history_;
|
||||||
std::size_t offset_history_pointer_ = 0;
|
std::size_t offset_history_pointer_ = 0;
|
||||||
Cycles::IntType offset_ = 0;
|
|
||||||
|
Cycles::IntType total_spacing_ = length_of_history;
|
||||||
|
Cycles::IntType total_divisor_ = length_of_history;
|
||||||
|
|
||||||
Cycles::IntType phase_ = 0;
|
Cycles::IntType phase_ = 0;
|
||||||
Cycles::IntType window_length_ = 0;
|
Cycles::IntType window_length_ = 0;
|
||||||
|
|
||||||
|
Cycles::IntType offset_ = 0;
|
||||||
bool window_was_filled_ = false;
|
bool window_was_filled_ = false;
|
||||||
|
|
||||||
int clocks_per_bit_ = 0;
|
int clocks_per_bit_ = 0;
|
||||||
int tolerance_ = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,18 +14,19 @@
|
|||||||
// just return a copy of that segment.
|
// just return a copy of that segment.
|
||||||
Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track, Time length_of_a_bit) {
|
Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track, Time length_of_a_bit) {
|
||||||
unsigned int history_size = 16;
|
unsigned int history_size = 16;
|
||||||
DigitalPhaseLockedLoop pll(100, history_size);
|
|
||||||
std::unique_ptr<Track> track_copy(track.clone());
|
std::unique_ptr<Track> track_copy(track.clone());
|
||||||
|
|
||||||
// ResultAccumulator exists to append whatever comes out of the PLL to
|
// ResultAccumulator exists to append whatever comes out of the PLL to
|
||||||
// its PCMSegment.
|
// its PCMSegment.
|
||||||
struct ResultAccumulator: public DigitalPhaseLockedLoop::Delegate {
|
struct ResultAccumulator {
|
||||||
PCMSegment result;
|
PCMSegment result;
|
||||||
|
bool is_recording = false;
|
||||||
void digital_phase_locked_loop_output_bit(int value) {
|
void digital_phase_locked_loop_output_bit(int value) {
|
||||||
result.data.push_back(!!value);
|
if(is_recording) result.data.push_back(!!value);
|
||||||
}
|
}
|
||||||
} result_accumulator;
|
} result_accumulator;
|
||||||
result_accumulator.result.length_of_a_bit = length_of_a_bit;
|
result_accumulator.result.length_of_a_bit = length_of_a_bit;
|
||||||
|
DigitalPhaseLockedLoop<ResultAccumulator> pll(100, result_accumulator);
|
||||||
|
|
||||||
// Obtain a length multiplier which is 100 times the reciprocal
|
// Obtain a length multiplier which is 100 times the reciprocal
|
||||||
// of the expected bit length. So a perfect bit length from
|
// of the expected bit length. So a perfect bit length from
|
||||||
@ -55,7 +56,7 @@ Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track,
|
|||||||
if(!history_size) {
|
if(!history_size) {
|
||||||
track_copy->seek_to(Time(0));
|
track_copy->seek_to(Time(0));
|
||||||
time_error.set_zero();
|
time_error.set_zero();
|
||||||
pll.set_delegate(&result_accumulator);
|
result_accumulator.is_recording = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,13 +66,11 @@ void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
|||||||
|
|
||||||
|
|
||||||
Shifter::Shifter() :
|
Shifter::Shifter() :
|
||||||
pll_(PLLClockRate / 4800, 15),
|
pll_(PLLClockRate / 4800, *this),
|
||||||
was_high_(false),
|
was_high_(false),
|
||||||
input_pattern_(0),
|
input_pattern_(0),
|
||||||
input_bit_counter_(0),
|
input_bit_counter_(0),
|
||||||
delegate_(nullptr) {
|
delegate_(nullptr) {}
|
||||||
pll_.set_delegate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Shifter::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
void Shifter::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||||
pll_.run_for(Cycles(static_cast<int>(static_cast<float>(PLLClockRate) * pulse.length.get<float>())));
|
pll_.run_for(Cycles(static_cast<int>(static_cast<float>(PLLClockRate) * pulse.length.get<float>())));
|
||||||
|
@ -17,7 +17,7 @@ namespace Storage {
|
|||||||
namespace Tape {
|
namespace Tape {
|
||||||
namespace Acorn {
|
namespace Acorn {
|
||||||
|
|
||||||
class Shifter: public Storage::DigitalPhaseLockedLoop::Delegate {
|
class Shifter {
|
||||||
public:
|
public:
|
||||||
Shifter();
|
Shifter();
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ class Shifter: public Storage::DigitalPhaseLockedLoop::Delegate {
|
|||||||
void digital_phase_locked_loop_output_bit(int value);
|
void digital_phase_locked_loop_output_bit(int value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Storage::DigitalPhaseLockedLoop pll_;
|
Storage::DigitalPhaseLockedLoop<Shifter, 15> pll_;
|
||||||
bool was_high_;
|
bool was_high_;
|
||||||
|
|
||||||
unsigned int input_pattern_;
|
unsigned int input_pattern_;
|
||||||
|
Loading…
Reference in New Issue
Block a user