diff --git a/Machines/Electron/Tape.cpp b/Machines/Electron/Tape.cpp index a95d94995..a137738ee 100644 --- a/Machines/Electron/Tape.cpp +++ b/Machines/Electron/Tape.cpp @@ -17,7 +17,9 @@ Tape::Tape() : delegate_(nullptr), output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}), last_posted_interrupt_status_(0), - interrupt_status_(0) {} + interrupt_status_(0) { + shifter_.set_delegate(this); +} void Tape::push_tape_bit(uint16_t bit) { data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10)); @@ -71,30 +73,11 @@ uint8_t Tape::get_data_register() { } void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) { - crossings_[0] = crossings_[1]; - crossings_[1] = crossings_[2]; - crossings_[2] = crossings_[3]; + shifter_.process_pulse(pulse); +} - crossings_[3] = Tape::Unrecognised; - if(pulse.type != Storage::Tape::Tape::Pulse::Zero) { - float pulse_length = (float)pulse.length.length / (float)pulse.length.clock_rate; - if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 1200.0) { - crossings_[3] = pulse_length > 1.0 / 3000.0 ? Tape::Long : Tape::Short; - } -// if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) crossings_[3] = Tape::Short; -// if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) crossings_[3] = Tape::Long; - } - - if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long) { - push_tape_bit(0); - crossings_[0] = crossings_[1] = Tape::Recognised; - } else { - if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short) { - push_tape_bit(1); - crossings_[0] = crossings_[1] = - crossings_[2] = crossings_[3] = Tape::Recognised; - } - } +void Tape::acorn_shifter_output_bit(int value) { + push_tape_bit((uint16_t)value); } void Tape::run_for_cycles(unsigned int number_of_cycles) { diff --git a/Machines/Electron/Tape.hpp b/Machines/Electron/Tape.hpp index a796e7824..2019d3a54 100644 --- a/Machines/Electron/Tape.hpp +++ b/Machines/Electron/Tape.hpp @@ -10,13 +10,16 @@ #define Electron_Tape_h #include "../../Storage/Tape/Tape.hpp" +#include "../../Storage/Tape/Parsers/Acorn.hpp" #include "Interrupts.hpp" #include namespace Electron { -class Tape: public Storage::Tape::TapePlayer { +class Tape: + public Storage::Tape::TapePlayer, + public Storage::Tape::Acorn::Shifter::Delegate { public: Tape(); @@ -39,6 +42,8 @@ class Tape: public Storage::Tape::TapePlayer { inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; } void set_is_in_input_mode(bool is_in_input_mode); + void acorn_shifter_output_bit(int value); + private: void process_input_pulse(Storage::Tape::Tape::Pulse pulse); inline void push_tape_bit(uint16_t bit); @@ -62,9 +67,7 @@ class Tape: public Storage::Tape::TapePlayer { uint8_t interrupt_status_, last_posted_interrupt_status_; Delegate *delegate_; - enum { - Long, Short, Unrecognised, Recognised - } crossings_[4]; + ::Storage::Tape::Acorn::Shifter shifter_; }; } diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm index f7f8999ed..37f64f599 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm +++ b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm @@ -102,7 +102,6 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) { NSMutableArray *_busOperationCaptures; int _timeSeekingReadOpcode; - int _lastOpcodeTime; } #pragma mark - Lifecycle @@ -184,9 +183,6 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) { } - (void)testMachineDidPerformBusOperation:(CPU::Z80::PartialMachineCycle::Operation)operation address:(uint16_t)address value:(uint8_t)value timeStamp:(int)timeStamp { - int length = timeStamp - _lastOpcodeTime; - _lastOpcodeTime = timeStamp; - if(self.captureBusActivity) { CSTestMachineZ80BusOperationCapture *capture = [[CSTestMachineZ80BusOperationCapture alloc] init]; switch(operation) { diff --git a/Storage/Disk/DigitalPhaseLockedLoop.cpp b/Storage/Disk/DigitalPhaseLockedLoop.cpp index 231b71e4a..d29410e8d 100644 --- a/Storage/Disk/DigitalPhaseLockedLoop.cpp +++ b/Storage/Disk/DigitalPhaseLockedLoop.cpp @@ -12,15 +12,16 @@ using namespace Storage; -DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, size_t length_of_history) : +DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, 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_(length_of_history, 0) {} + offset_history_pointer_(0), + offset_history_(length_of_history, 0), + offset_(0) {} void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles) { + offset_ += number_of_cycles; phase_ += number_of_cycles; if(phase_ >= window_length_) { int windows_crossed = phase_ / window_length_; @@ -41,25 +42,31 @@ 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)); + post_phase_offset(phase_, offset_); + offset_ = 0; } } -void DigitalPhaseLockedLoop::post_phase_error(int error) { +void DigitalPhaseLockedLoop::post_phase_offset(int phase, int offset) { + offset_history_[offset_history_pointer_] = 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 + int total_spacing = 0; + int total_divisor = 0; + for(int offset : offset_history_) { + int 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; + } + + int error = phase - (window_length_ >> 1); + // 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 - 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 index d4b38153e..1735a5b15 100644 --- a/Storage/Disk/DigitalPhaseLockedLoop.hpp +++ b/Storage/Disk/DigitalPhaseLockedLoop.hpp @@ -20,10 +20,9 @@ class DigitalPhaseLockedLoop { 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); + DigitalPhaseLockedLoop(int clocks_per_bit, size_t length_of_history); /*! Runs the loop, impliedly posting no pulses during that period. @@ -51,9 +50,11 @@ class DigitalPhaseLockedLoop { private: Delegate *delegate_; - void post_phase_error(int error); - std::vector phase_error_history_; - size_t phase_error_pointer_; + void post_phase_offset(int phase, int offset); + + std::vector offset_history_; + size_t offset_history_pointer_; + int offset_; int phase_; int window_length_; diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index 6304eab60..9f8b47c75 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -166,7 +166,7 @@ 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 // account of in rotation speed, air turbulence, etc, so a direct conversion will do int clocks_per_bit = (int)cycles_per_bit_.get_unsigned_int(); - pll_.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3)); + pll_.reset(new DigitalPhaseLockedLoop(clocks_per_bit, 3)); pll_->set_delegate(this); } diff --git a/Storage/Tape/Parsers/Acorn.cpp b/Storage/Tape/Parsers/Acorn.cpp index 1f7e2d8c7..53cc730e5 100644 --- a/Storage/Tape/Parsers/Acorn.cpp +++ b/Storage/Tape/Parsers/Acorn.cpp @@ -10,9 +10,13 @@ using namespace Storage::Tape::Acorn; -Parser::Parser() : - ::Storage::Tape::Parser(), - crc_(0x1021, 0x0000) {} +namespace { +const int PLLClockRate = 1920000; +} + +Parser::Parser() : crc_(0x1021, 0x0000) { + shifter_.set_delegate(this); +} int Parser::get_next_bit(const std::shared_ptr &tape) { SymbolType symbol = get_next_symbol(tape); @@ -52,38 +56,38 @@ int Parser::get_next_word(const std::shared_ptr &tape) { void Parser::reset_crc() { crc_.reset(); } uint16_t Parser::get_crc() { return crc_.get_value(); } -void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse) { - switch(pulse.type) { - default: break; - case Storage::Tape::Tape::Pulse::High: - case Storage::Tape::Tape::Pulse::Low: - float pulse_length = pulse.length.get_float(); - if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 1200.0) { - push_wave(pulse_length > 1.0 / 3000.0 ? WaveType::Long : WaveType::Short); return; - } - break; - } - - push_wave(WaveType::Unrecognised); +void Parser::acorn_shifter_output_bit(int value) { + push_symbol(value ? SymbolType::One : SymbolType::Zero); } -void Parser::inspect_waves(const std::vector &waves) { - if(waves.size() < 2) return; - - if(waves[0] == WaveType::Long && waves[1] == WaveType::Long) { - push_symbol(SymbolType::Zero, 2); - return; - } - - if(waves.size() < 4) return; - - if( waves[0] == WaveType::Short && - waves[1] == WaveType::Short && - waves[2] == WaveType::Short && - waves[3] == WaveType::Short) { - push_symbol(SymbolType::One, 4); - return; - } - - remove_waves(1); +void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) { + shifter_.process_pulse(pulse); +} + + +Shifter::Shifter() : + pll_(PLLClockRate / 4800, 15), + was_high_(false), + input_pattern_(0), + input_bit_counter_(0) { + pll_.set_delegate(this); +} + +void Shifter::process_pulse(const Storage::Tape::Tape::Pulse &pulse) { + pll_.run_for_cycles((int)((float)PLLClockRate * pulse.length.get_float())); + + bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High; + if(is_high != was_high_) { + pll_.add_pulse(); + } + was_high_ = is_high; +} + +void Shifter::digital_phase_locked_loop_output_bit(int value) { + input_pattern_ = ((input_pattern_ << 1) | (unsigned int)value) & 0xf; + switch(input_pattern_) { + case 0x5: delegate_->acorn_shifter_output_bit(0); input_pattern_ = 0; break; + case 0xf: delegate_->acorn_shifter_output_bit(1); input_pattern_ = 0; break; + default: break;; + } } diff --git a/Storage/Tape/Parsers/Acorn.hpp b/Storage/Tape/Parsers/Acorn.hpp index 71f3ecccc..47d00472e 100644 --- a/Storage/Tape/Parsers/Acorn.hpp +++ b/Storage/Tape/Parsers/Acorn.hpp @@ -11,20 +11,43 @@ #include "TapeParser.hpp" #include "../../../NumberTheory/CRC.hpp" +#include "../../Disk/DigitalPhaseLockedLoop.hpp" namespace Storage { namespace Tape { namespace Acorn { -enum class WaveType { - Short, Long, Unrecognised +class Shifter: public Storage::DigitalPhaseLockedLoop::Delegate { + public: + Shifter(); + + void process_pulse(const Storage::Tape::Tape::Pulse &pulse); + + class Delegate { + public: + virtual void acorn_shifter_output_bit(int value) = 0; + }; + void set_delegate(Delegate *delegate) { + delegate_ = delegate; + } + + void digital_phase_locked_loop_output_bit(int value); + + private: + Storage::DigitalPhaseLockedLoop pll_; + bool was_high_; + + unsigned int input_pattern_; + unsigned int input_bit_counter_; + + Delegate *delegate_; }; enum class SymbolType { One, Zero }; -class Parser: public Storage::Tape::Parser { +class Parser: public Storage::Tape::Parser, public Shifter::Delegate { public: Parser(); @@ -35,10 +58,13 @@ class Parser: public Storage::Tape::Parser { void reset_crc(); uint16_t get_crc(); + void acorn_shifter_output_bit(int value); + void process_pulse(const Storage::Tape::Tape::Pulse &pulse); + private: - void process_pulse(Storage::Tape::Tape::Pulse pulse); - void inspect_waves(const std::vector &waves); + bool did_update_shifter(int new_value, int length); NumberTheory::CRC16 crc_; + Shifter shifter_; }; } diff --git a/Storage/Tape/Parsers/Commodore.cpp b/Storage/Tape/Parsers/Commodore.cpp index 628ad823d..85f8fb659 100644 --- a/Storage/Tape/Parsers/Commodore.cpp +++ b/Storage/Tape/Parsers/Commodore.cpp @@ -12,7 +12,7 @@ using namespace Storage::Tape::Commodore; Parser::Parser() : - Storage::Tape::Parser(), + Storage::Tape::PulseClassificationParser(), wave_period_(0.0f), previous_was_high_(false), parity_byte_(0) {} @@ -262,7 +262,7 @@ uint16_t Parser::get_next_short(const std::shared_ptr &tape indicates a high to low transition, inspects the time since the last transition, to produce a long, medium, short or unrecognised wave period. */ -void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse) +void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) { // The Complete Commodore Inner Space Anthology, P 97, gives half-cycle lengths of: // short: 182µs => 0.000364s cycle diff --git a/Storage/Tape/Parsers/Commodore.hpp b/Storage/Tape/Parsers/Commodore.hpp index 5b36a83c1..48f5fd38d 100644 --- a/Storage/Tape/Parsers/Commodore.hpp +++ b/Storage/Tape/Parsers/Commodore.hpp @@ -56,7 +56,7 @@ struct Data { bool duplicate_matched; }; -class Parser: public Storage::Tape::Parser { +class Parser: public Storage::Tape::PulseClassificationParser { public: Parser(); @@ -126,7 +126,7 @@ class Parser: public Storage::Tape::Parser { indicates a high to low transition, inspects the time since the last transition, to produce a long, medium, short or unrecognised wave period. */ - void process_pulse(Storage::Tape::Tape::Pulse pulse); + void process_pulse(const Storage::Tape::Tape::Pulse &pulse); bool previous_was_high_; float wave_period_; diff --git a/Storage/Tape/Parsers/Oric.cpp b/Storage/Tape/Parsers/Oric.cpp index 8a2f10a72..1c64dc687 100644 --- a/Storage/Tape/Parsers/Oric.cpp +++ b/Storage/Tape/Parsers/Oric.cpp @@ -45,7 +45,7 @@ bool Parser::sync_and_get_encoding_speed(const std::shared_ptr { +class Parser: public Storage::Tape::PulseClassificationParser { public: int get_next_byte(const std::shared_ptr &tape, bool use_fast_encoding); bool sync_and_get_encoding_speed(const std::shared_ptr &tape); private: - void process_pulse(Storage::Tape::Tape::Pulse pulse); + void process_pulse(const Storage::Tape::Tape::Pulse &pulse); void inspect_waves(const std::vector &waves); enum DetectionMode { diff --git a/Storage/Tape/Parsers/TapeParser.hpp b/Storage/Tape/Parsers/TapeParser.hpp index 2ae7b2d13..c696b205d 100644 --- a/Storage/Tape/Parsers/TapeParser.hpp +++ b/Storage/Tape/Parsers/TapeParser.hpp @@ -10,6 +10,7 @@ #define TapeParser_hpp #include "../Tape.hpp" +#include "../../Disk/DigitalPhaseLockedLoop.hpp" #include #include @@ -18,18 +19,9 @@ namespace Storage { namespace Tape { -/*! - A partly-abstract base class to help in the authorship of tape format parsers; - provides hooks for pulse classification from pulses to waves and for symbol identification from - waves. - - Very optional, not intended to box in the approaches taken for analysis. -*/ -template class Parser { +template class Parser { public: - /// Instantiates a new parser with the supplied @c tape. Parser() : has_next_symbol_(false), error_flag_(false) {} - /// Resets the error flag. void reset_error_flag() { error_flag_ = false; } /// @returns @c true if an error has occurred since the error flag was last reset; @c false otherwise. @@ -59,12 +51,18 @@ template class Parser { next_symbol_ = symbol; } - /*! - Should be implemented by subclasses. Consumes @c pulse. Is likely either to call @c push_wave - or to take no action. + @returns `true` if there is no data left on the tape and the WaveType queue has been exhausted; `false` otherwise. */ - virtual void process_pulse(Storage::Tape::Tape::Pulse pulse) = 0; + bool is_at_end(const std::shared_ptr &tape) { + return tape->is_at_end() && !has_next_symbol_; + } + + protected: + /*! + Should be implemented by subclasses. Consumes @c pulse. + */ + virtual void process_pulse(const Storage::Tape::Tape::Pulse &pulse) = 0; /*! An optional implementation for subclasses; called to announce that the tape has ended: that @@ -72,7 +70,50 @@ template class Parser { */ virtual void mark_end() {} + /*! + Sets @c symbol as the newly-recognised symbol. + */ + void push_symbol(SymbolType symbol) { + has_next_symbol_ = true; + next_symbol_ = symbol; + } + + void set_error_flag() { + error_flag_ = true; + } + + bool error_flag_; + SymbolType next_symbol_; + bool has_next_symbol_; +}; + +/*! + A partly-abstract base class to help in the authorship of tape format parsers; + provides hooks for receipt of pulses, which are intended to be classified into waves, + and for symbol identification from waves. + + Very optional, not intended to box in the approaches taken for analysis. See also + the PLLParser. +*/ +template class PulseClassificationParser: public Parser { + public: + virtual void process_pulse(const Storage::Tape::Tape::Pulse &pulse) = 0; + + /* + process_pulse should either call @c push_wave or to take no action. + */ + protected: + /*! + Sets @c symbol as the newly-recognised symbol and removes @c nunber_of_waves waves from the front of the list. + + Expected to be called by subclasses from @c process_pulse when it recognises that the first @c number_of_waves + waves together represent @c symbol. + */ + void push_symbol(SymbolType symbol, int number_of_waves) { + Parser::push_symbol(symbol); + remove_waves(number_of_waves); + } /*! Adds @c wave to the back of the list of recognised waves and calls @c inspect_waves to check for a new symbol. @@ -94,32 +135,7 @@ template class Parser { wave_queue_.erase(wave_queue_.begin(), wave_queue_.begin()+number_of_waves); } - /*! - Sets @c symbol as the newly-recognised symbol and removes @c nunber_of_waves waves from the front of the list. - - Expected to be called by subclasses from @c process_pulse when it recognises that the first @c number_of_waves - waves together represent @c symbol. - */ - void push_symbol(SymbolType symbol, int number_of_waves) { - has_next_symbol_ = true; - next_symbol_ = symbol; - remove_waves(number_of_waves); - } - - void set_error_flag() { - error_flag_ = true; - } - - /*! - @returns `true` if there is no data left on the tape and the WaveType queue has been exhausted; `false` otherwise. - */ - bool is_at_end(const std::shared_ptr &tape) { - return tape->is_at_end() && wave_queue_.empty() && !has_next_symbol_; - } - private: - bool error_flag_; - /*! Should be implemented by subclasses. Inspects @c waves for a potential new symbol. If one is found should call @c push_symbol. May wish alternatively to call @c remove_waves to have entries @@ -129,8 +145,6 @@ template class Parser { virtual void inspect_waves(const std::vector &waves) = 0; std::vector wave_queue_; - SymbolType next_symbol_; - bool has_next_symbol_; }; } diff --git a/Storage/Tape/Parsers/ZX8081.cpp b/Storage/Tape/Parsers/ZX8081.cpp index f4678dc6f..960992dd5 100644 --- a/Storage/Tape/Parsers/ZX8081.cpp +++ b/Storage/Tape/Parsers/ZX8081.cpp @@ -12,7 +12,7 @@ using namespace Storage::Tape::ZX8081; Parser::Parser() : pulse_was_high_(false), pulse_time_(0) {} -void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse) { +void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) { pulse_time_ += pulse.length; bool pulse_is_high = pulse.type == Storage::Tape::Tape::Pulse::High; diff --git a/Storage/Tape/Parsers/ZX8081.hpp b/Storage/Tape/Parsers/ZX8081.hpp index 8fc5873fb..32a607163 100644 --- a/Storage/Tape/Parsers/ZX8081.hpp +++ b/Storage/Tape/Parsers/ZX8081.hpp @@ -29,7 +29,7 @@ enum class SymbolType { One, Zero, FileGap, Unrecognised }; -class Parser: public Storage::Tape::Parser { +class Parser: public Storage::Tape::PulseClassificationParser { public: Parser(); @@ -50,7 +50,7 @@ class Parser: public Storage::Tape::Parser { Time pulse_time_; void post_pulse(); - void process_pulse(Storage::Tape::Tape::Pulse pulse); + void process_pulse(const Storage::Tape::Tape::Pulse &pulse); void mark_end(); void inspect_waves(const std::vector &waves);