1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-08-17 12:29:14 +00:00

Made an attempt to eliminate creeping tape processing accuracy misses, which implied factoring out the GCM and LCD functions, which I then felt didn't really amount to signal processing.

This commit is contained in:
Thomas Harte
2016-07-29 05:19:01 -04:00
parent 5c1614ce7b
commit e55db0cfe8
8 changed files with 197 additions and 34 deletions

34
NumberTheory/Factors.cpp Normal file
View File

@@ -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;
}

17
NumberTheory/Factors.hpp Normal file
View File

@@ -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 */

View File

@@ -314,6 +314,7 @@
4BB299F71B587D8400A49093 /* txan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EB1B587D8400A49093 /* txan */; }; 4BB299F71B587D8400A49093 /* txan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EB1B587D8400A49093 /* txan */; };
4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; }; 4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; };
4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; }; 4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; };
4BB697C71D4B558F00248BDF /* Factors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C51D4B558F00248BDF /* Factors.cpp */; };
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; }; 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; };
4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */; }; 4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */; };
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA81B587A5100552FC2 /* Assets.xcassets */; }; 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA81B587A5100552FC2 /* Assets.xcassets */; };
@@ -700,6 +701,8 @@
4BB298EB1B587D8400A49093 /* txan */ = {isa = PBXFileReference; lastKnownFileType = file; path = txan; sourceTree = "<group>"; }; 4BB298EB1B587D8400A49093 /* txan */ = {isa = PBXFileReference; lastKnownFileType = file; path = txan; sourceTree = "<group>"; };
4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = "<group>"; }; 4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = "<group>"; };
4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = "<group>"; }; 4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = "<group>"; };
4BB697C51D4B558F00248BDF /* Factors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Factors.cpp; path = ../../NumberTheory/Factors.cpp; sourceTree = "<group>"; };
4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = "<group>"; };
4BB73E9E1B587A5100552FC2 /* Clock Signal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Clock Signal.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; }; 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
4BB73EA61B587A5100552FC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Atari2600Document.xib; sourceTree = "<group>"; }; 4BB73EA61B587A5100552FC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Atari2600Document.xib; sourceTree = "<group>"; };
@@ -1291,6 +1294,15 @@
path = "Wolfgang Lorenz 6502 test suite"; path = "Wolfgang Lorenz 6502 test suite";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4BB697C81D4B559300248BDF /* NumberTheory */ = {
isa = PBXGroup;
children = (
4BB697C51D4B558F00248BDF /* Factors.cpp */,
4BB697C61D4B558F00248BDF /* Factors.hpp */,
);
name = NumberTheory;
sourceTree = "<group>";
};
4BB73E951B587A5100552FC2 = { 4BB73E951B587A5100552FC2 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -1300,6 +1312,7 @@
4BB73EC01B587A5100552FC2 /* Clock SignalUITests */, 4BB73EC01B587A5100552FC2 /* Clock SignalUITests */,
4BC9DF4A1D04691600F44158 /* Components */, 4BC9DF4A1D04691600F44158 /* Components */,
4BB73EDC1B587CA500552FC2 /* Machines */, 4BB73EDC1B587CA500552FC2 /* Machines */,
4BB697C81D4B559300248BDF /* NumberTheory */,
4B366DFD1B5C165F0026627B /* Outputs */, 4B366DFD1B5C165F0026627B /* Outputs */,
4BB73EDD1B587CA500552FC2 /* Processors */, 4BB73EDD1B587CA500552FC2 /* Processors */,
4BB73E9F1B587A5100552FC2 /* Products */, 4BB73E9F1B587A5100552FC2 /* Products */,
@@ -1876,6 +1889,7 @@
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
4BB697C71D4B558F00248BDF /* Factors.cpp in Sources */,
4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,

View File

@@ -13,11 +13,25 @@
namespace SignalProcessing { 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.
*/
class Stepper class Stepper
{ {
public: public:
/*!
Establishes a stepper with a one-to-one conversion rate.
*/
Stepper() : Stepper(1,1) {} 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) : Stepper(uint64_t output_rate, uint64_t input_rate) :
accumulated_error_(0), accumulated_error_(0),
input_rate_(input_rate), input_rate_(input_rate),
@@ -26,6 +40,11 @@ class Stepper
adjustment_up_((int64_t)(output_rate % input_rate) << 1), adjustment_up_((int64_t)(output_rate % input_rate) << 1),
adjustment_down_((int64_t)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() inline uint64_t step()
{ {
uint64_t update = whole_step_; uint64_t update = whole_step_;
@@ -38,11 +57,34 @@ class Stepper
return update; 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() inline uint64_t get_output_rate()
{ {
return output_rate_; return output_rate_;
} }
/*!
@returns the input rate.
*/
inline uint64_t get_input_rate() inline uint64_t get_input_rate()
{ {
return input_rate_; return input_rate_;

View File

@@ -12,13 +12,48 @@ using namespace Storage;
DiskDrive::DiskDrive(unsigned int clock_rate, unsigned int revolutions_per_minute) : DiskDrive::DiskDrive(unsigned int clock_rate, unsigned int revolutions_per_minute) :
_clock_rate(clock_rate), _clock_rate(clock_rate),
_revolutions_per_minute(revolutions_per_minute) {} _revolutions_per_minute(revolutions_per_minute),
_head_position(0) {}
void DiskDrive::set_expected_bit_length(Time bit_length) void DiskDrive::set_expected_bit_length(Time bit_length)
{ {
_bit_length = bit_length; _bit_length = bit_length;
} }
void DiskDrive::set_disk(std::shared_ptr<Disk> disk)
{
_disk = disk;
// _track.reset();
}
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);
_track = _disk->get_track_at_position((unsigned int)_head_position);
}
void DiskDrive::digital_phase_locked_loop_output_bit(int value) void DiskDrive::digital_phase_locked_loop_output_bit(int value)
{ {
process_input_bit(value, _cycles_since_index_hole);
}
void DiskDrive::run_for_cycles(unsigned int number_of_cycles)
{
if(has_disk())
{
while(number_of_cycles--)
{
}
}
} }

View File

@@ -11,6 +11,7 @@
#include "Disk.hpp" #include "Disk.hpp"
#include "DigitalPhaseLockedLoop.hpp" #include "DigitalPhaseLockedLoop.hpp"
#include "../../SignalProcessing/Stepper.hpp"
namespace Storage { namespace Storage {
@@ -40,6 +41,20 @@ class DiskDrive: public DigitalPhaseLockedLoop::Delegate {
Time _bit_length; Time _bit_length;
unsigned int _clock_rate; unsigned int _clock_rate;
unsigned int _revolutions_per_minute; unsigned int _revolutions_per_minute;
std::shared_ptr<DigitalPhaseLockedLoop> _pll;
std::shared_ptr<Disk> _disk;
std::shared_ptr<Track> _track;
int _head_position;
unsigned int _cycles_since_index_hole;
void install_track();
struct {
Track::Event current_event;
std::unique_ptr<SignalProcessing::Stepper> pulse_stepper;
uint32_t time_into_pulse;
} _input;
}; };
} }

View File

@@ -7,34 +7,10 @@
// //
#include "PCMTrack.hpp" #include "PCMTrack.hpp"
#include "../../NumberTheory/Factors.hpp"
using namespace Storage; 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<PCMSegment> segments) PCMTrack::PCMTrack(std::vector<PCMSegment> segments)
{ {
_segments = std::move(segments); _segments = std::move(segments);
@@ -87,7 +63,7 @@ void PCMTrack::fix_length()
_track_clock_rate = _segments[0].length_of_a_bit.clock_rate; _track_clock_rate = _segments[0].length_of_a_bit.clock_rate;
for(size_t c = 1; c < _segments.size(); c++) for(size_t c = 1; c < _segments.size(); c++)
{ {
_track_clock_rate = least_common_multiple(_track_clock_rate, _segments[c].length_of_a_bit.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 track-total divisor // therby determine the total length, storing it to next_event as the track-total divisor

View File

@@ -7,6 +7,7 @@
// //
#include "Tape.hpp" #include "Tape.hpp"
#include "../../NumberTheory/Factors.hpp"
using namespace Storage; using namespace Storage;
@@ -22,6 +23,8 @@ TapePlayer::TapePlayer(unsigned int input_clock_rate) :
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape> tape) void TapePlayer::set_tape(std::shared_ptr<Storage::Tape> tape)
{ {
_tape = tape; _tape = tape;
_input.pulse_stepper.reset();
get_next_pulse(); get_next_pulse();
} }
@@ -32,7 +35,18 @@ bool TapePlayer::has_tape()
void TapePlayer::get_next_pulse() void TapePlayer::get_next_pulse()
{ {
unsigned int previous_clock_rate = 0;
// figure out how much time has been run since the last bit ended
if(_input.pulse_stepper == nullptr)
_input.time_into_pulse = 0; _input.time_into_pulse = 0;
else
{
_input.time_into_pulse -= _input.current_pulse.length.length;
previous_clock_rate = _input.current_pulse.length.clock_rate;
}
// get the new pulse
if(_tape) if(_tape)
_input.current_pulse = _tape->get_next_pulse(); _input.current_pulse = _tape->get_next_pulse();
else else
@@ -41,6 +55,24 @@ void TapePlayer::get_next_pulse()
_input.current_pulse.length.clock_rate = 1; _input.current_pulse.length.clock_rate = 1;
_input.current_pulse.type = Storage::Tape::Pulse::Zero; _input.current_pulse.type = Storage::Tape::Pulse::Zero;
} }
// if there was any time left over, map into the new time base
if(_input.pulse_stepper && _input.time_into_pulse)
{
// simplify the quotient
unsigned int common_divisor = NumberTheory::greatest_common_divisor(_input.time_into_pulse, previous_clock_rate);
_input.time_into_pulse /= common_divisor;
previous_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(previous_clock_rate, _input.current_pulse.length.clock_rate);
_input.current_pulse.length.length *= denominator / _input.current_pulse.length.clock_rate;
_input.current_pulse.length.clock_rate = denominator;
_input.time_into_pulse *= denominator / previous_clock_rate;
}
// adjust stepper if required
if(_input.pulse_stepper == nullptr || _input.current_pulse.length.clock_rate != _input.pulse_stepper->get_output_rate()) 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)); _input.pulse_stepper.reset(new SignalProcessing::Stepper(_input.current_pulse.length.clock_rate, _input_clock_rate));
@@ -51,19 +83,17 @@ void TapePlayer::run_for_cycles(unsigned int number_of_cycles)
{ {
if(has_tape()) if(has_tape())
{ {
while(number_of_cycles--) _input.time_into_pulse += (unsigned int)_input.pulse_stepper->step(number_of_cycles);
{
_input.time_into_pulse += (unsigned int)_input.pulse_stepper->step();
while(_input.time_into_pulse >= _input.current_pulse.length.length) while(_input.time_into_pulse >= _input.current_pulse.length.length)
{ {
run_for_input_pulse(); run_for_input_pulse();
} }
} }
}
} }
void TapePlayer::run_for_input_pulse() void TapePlayer::run_for_input_pulse()
{ {
process_input_pulse(_input.current_pulse); process_input_pulse(_input.current_pulse);
get_next_pulse(); get_next_pulse();
_input.time_into_pulse = 0;
} }