mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-30 11:34:54 +00:00
Merge pull request #50 from TomHarte/FasterDisk
Introduces a slightly less expensive timed event loop
This commit is contained in:
commit
656d0c7a69
@ -1,36 +0,0 @@
|
||||
//
|
||||
// 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)
|
||||
{
|
||||
if(a == b) return a;
|
||||
|
||||
unsigned int gcd = greatest_common_divisor(a, b);
|
||||
return (a / gcd) * (b / gcd) * gcd;
|
||||
}
|
@ -13,13 +13,33 @@ namespace NumberTheory {
|
||||
/*!
|
||||
@returns The greatest common divisor of @c a and @c b as computed by Euclid's algorithm.
|
||||
*/
|
||||
unsigned int greatest_common_divisor(unsigned int a, unsigned int b);
|
||||
template<class T> T greatest_common_divisor(T a, T b) {
|
||||
if(a < b) {
|
||||
T swap = b;
|
||||
b = a;
|
||||
a = swap;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
if(!a) return b;
|
||||
if(!b) return a;
|
||||
|
||||
T remainder = a%b;
|
||||
a = b;
|
||||
b = remainder;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns The least common multiple of @c a and @c b computed indirectly via Euclid's greatest
|
||||
common divisor algorithm.
|
||||
*/
|
||||
unsigned int least_common_multiple(unsigned int a, unsigned int b);
|
||||
template<class T> T least_common_multiple(T a, T b) {
|
||||
if(a == b) return a;
|
||||
|
||||
T gcd = greatest_common_divisor<T>(a, b);
|
||||
return (a / gcd) * (b / gcd) * gcd;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Factors_hpp */
|
||||
|
@ -322,7 +322,6 @@
|
||||
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 */; };
|
||||
@ -736,7 +735,6 @@
|
||||
4BB298EB1B587D8400A49093 /* txan */ = {isa = PBXFileReference; lastKnownFileType = file; path = txan; sourceTree = "<group>"; };
|
||||
4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; 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>"; };
|
||||
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = "<group>"; };
|
||||
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = "<group>"; };
|
||||
@ -1389,7 +1387,6 @@
|
||||
4BB697C81D4B559300248BDF /* NumberTheory */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BB697C51D4B558F00248BDF /* Factors.cpp */,
|
||||
4BB697C61D4B558F00248BDF /* Factors.hpp */,
|
||||
);
|
||||
name = NumberTheory;
|
||||
@ -2063,7 +2060,6 @@
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
|
||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
||||
4BB697C71D4B558F00248BDF /* Factors.cpp in Sources */,
|
||||
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
|
||||
4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */,
|
||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||
|
@ -38,7 +38,7 @@ class CommodoreGCRParser: public Storage::Disk::Drive {
|
||||
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
std::unique_ptr<Sector> get_sector(uint8_t track, uint8_t sector)
|
||||
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector)
|
||||
{
|
||||
int difference = (int)track - (int)track_;
|
||||
track_ = track;
|
||||
@ -65,6 +65,7 @@ class CommodoreGCRParser: public Storage::Disk::Drive {
|
||||
int index_count_;
|
||||
int bit_count_;
|
||||
uint8_t track_;
|
||||
std::shared_ptr<Sector> sector_cache_[65536];
|
||||
|
||||
void process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
{
|
||||
@ -115,23 +116,26 @@ class CommodoreGCRParser: public Storage::Disk::Drive {
|
||||
index_count_++;
|
||||
}
|
||||
|
||||
std::unique_ptr<Sector> get_sector(uint8_t sector)
|
||||
std::shared_ptr<Sector> get_sector(uint8_t sector)
|
||||
{
|
||||
std::unique_ptr<Sector> first_sector = get_next_sector();
|
||||
uint16_t sector_address = (uint16_t)((track_ << 8) | sector);
|
||||
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
|
||||
|
||||
std::shared_ptr<Sector> first_sector = get_next_sector();
|
||||
if(!first_sector) return first_sector;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
|
||||
while(1)
|
||||
{
|
||||
std::unique_ptr<Sector> next_sector = get_next_sector();
|
||||
std::shared_ptr<Sector> next_sector = get_next_sector();
|
||||
if(next_sector->sector == first_sector->sector) return nullptr;
|
||||
if(next_sector->sector == sector) return next_sector;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Sector> get_next_sector()
|
||||
std::shared_ptr<Sector> get_next_sector()
|
||||
{
|
||||
std::unique_ptr<Sector> sector(new Sector);
|
||||
std::shared_ptr<Sector> sector(new Sector);
|
||||
index_count_ = 0;
|
||||
|
||||
while(index_count_ < 2)
|
||||
@ -166,7 +170,12 @@ class CommodoreGCRParser: public Storage::Disk::Drive {
|
||||
checksum ^= sector->data[c];
|
||||
}
|
||||
|
||||
if(checksum == get_next_byte()) return sector;
|
||||
if(checksum == get_next_byte())
|
||||
{
|
||||
uint16_t sector_address = (uint16_t)((sector->track << 8) | sector->sector);
|
||||
sector_cache_[sector_address] = sector;
|
||||
return sector;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@ -180,7 +189,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
|
||||
parser.set_disk(disk);
|
||||
|
||||
// find any sector whatsoever to establish the current track
|
||||
std::unique_ptr<CommodoreGCRParser::Sector> sector;
|
||||
std::shared_ptr<CommodoreGCRParser::Sector> sector;
|
||||
|
||||
// assemble directory
|
||||
std::vector<uint8_t> directory;
|
||||
|
@ -85,11 +85,14 @@ void Drive::run_for_cycles(int number_of_cycles)
|
||||
if(has_disk())
|
||||
{
|
||||
number_of_cycles *= _clock_rate_multiplier;
|
||||
while(number_of_cycles--)
|
||||
while(number_of_cycles)
|
||||
{
|
||||
_cycles_since_index_hole ++;
|
||||
_pll->run_for_cycles(1);
|
||||
TimedEventLoop::run_for_cycles(1);
|
||||
int cycles_until_next_event = (int)get_cycles_until_next_event();
|
||||
int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles);
|
||||
_cycles_since_index_hole += (unsigned int)cycles_to_run_for;
|
||||
number_of_cycles -= cycles_to_run_for;
|
||||
_pll->run_for_cycles(cycles_to_run_for);
|
||||
TimedEventLoop::run_for_cycles(cycles_to_run_for);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,7 @@
|
||||
static float gzgetfloat(gzFile file)
|
||||
{
|
||||
uint8_t bytes[4];
|
||||
bytes[0] = (uint8_t)gzgetc(file);
|
||||
bytes[1] = (uint8_t)gzgetc(file);
|
||||
bytes[2] = (uint8_t)gzgetc(file);
|
||||
bytes[3] = (uint8_t)gzgetc(file);
|
||||
gzread(file, bytes, 4);
|
||||
|
||||
/* assume a four byte array named Float exists, where Float[0]
|
||||
was the first byte read from the UEF, Float[1] the second, etc */
|
||||
@ -45,25 +42,33 @@ static float gzgetfloat(gzFile file)
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t gzget8(gzFile file)
|
||||
{
|
||||
// This is a workaround for gzgetc, which seems to be broken in ZLib 1.2.8.
|
||||
uint8_t result;
|
||||
gzread(file, &result, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int gzget16(gzFile file)
|
||||
{
|
||||
int result = gzgetc(file);
|
||||
result |= (gzgetc(file) << 8);
|
||||
return result;
|
||||
uint8_t bytes[2];
|
||||
gzread(file, bytes, 2);
|
||||
return bytes[0] | (bytes[1] << 8);
|
||||
}
|
||||
|
||||
static int gzget24(gzFile file)
|
||||
{
|
||||
int result = gzget16(file);
|
||||
result |= (gzgetc(file) << 16);
|
||||
return result;
|
||||
uint8_t bytes[3];
|
||||
gzread(file, bytes, 3);
|
||||
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16);
|
||||
}
|
||||
|
||||
static int gzget32(gzFile file)
|
||||
{
|
||||
int result = gzget16(file);
|
||||
result |= (gzget16(file) << 16);
|
||||
return result;
|
||||
uint8_t bytes[4];
|
||||
gzread(file, bytes, 4);
|
||||
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
|
||||
}
|
||||
|
||||
using namespace Storage::Tape;
|
||||
@ -83,11 +88,10 @@ UEF::UEF(const char *file_name) :
|
||||
throw ErrorNotUEF;
|
||||
}
|
||||
|
||||
int minor, major;
|
||||
minor = gzgetc(_file);
|
||||
major = gzgetc(_file);
|
||||
uint8_t version[2];
|
||||
gzread(_file, version, 2);
|
||||
|
||||
if(major > 0 || minor > 10 || major < 0 || minor < 0)
|
||||
if(version[1] > 0 || version[0] > 10)
|
||||
{
|
||||
throw ErrorNotUEF;
|
||||
}
|
||||
@ -199,17 +203,17 @@ void UEF::queue_implicit_bit_pattern(uint32_t length)
|
||||
{
|
||||
while(length--)
|
||||
{
|
||||
queue_implicit_byte((uint8_t)gzgetc(_file));
|
||||
queue_implicit_byte(gzget8(_file));
|
||||
}
|
||||
}
|
||||
|
||||
void UEF::queue_explicit_bit_pattern(uint32_t length)
|
||||
{
|
||||
size_t length_in_bits = (length << 3) - (size_t)gzgetc(_file);
|
||||
size_t length_in_bits = (length << 3) - (size_t)gzget8(_file);
|
||||
uint8_t current_byte = 0;
|
||||
for(size_t bit = 0; bit < length_in_bits; bit++)
|
||||
{
|
||||
if(!(bit&7)) current_byte = (uint8_t)gzgetc(_file);
|
||||
if(!(bit&7)) current_byte = gzget8(_file);
|
||||
queue_bit(current_byte&1);
|
||||
current_byte >>= 1;
|
||||
}
|
||||
@ -250,13 +254,13 @@ void UEF::queue_carrier_tone_with_dummy()
|
||||
void UEF::queue_security_cycles()
|
||||
{
|
||||
int number_of_cycles = gzget24(_file);
|
||||
bool first_is_pulse = gzgetc(_file) == 'P';
|
||||
bool last_is_pulse = gzgetc(_file) == 'P';
|
||||
bool first_is_pulse = gzget8(_file) == 'P';
|
||||
bool last_is_pulse = gzget8(_file) == 'P';
|
||||
|
||||
uint8_t current_byte = 0;
|
||||
for(int cycle = 0; cycle < number_of_cycles; cycle++)
|
||||
{
|
||||
if(!(cycle&7)) current_byte = (uint8_t)gzgetc(_file);
|
||||
if(!(cycle&7)) current_byte = gzget8(_file);
|
||||
int bit = (current_byte >> 7);
|
||||
current_byte <<= 1;
|
||||
|
||||
@ -284,9 +288,9 @@ void UEF::queue_defined_data(uint32_t length)
|
||||
{
|
||||
if(length < 3) return;
|
||||
|
||||
int bits_per_packet = gzgetc(_file);
|
||||
char parity_type = (char)gzgetc(_file);
|
||||
int number_of_stop_bits = gzgetc(_file);
|
||||
int bits_per_packet = gzget8(_file);
|
||||
char parity_type = (char)gzget8(_file);
|
||||
int number_of_stop_bits = gzget8(_file);
|
||||
|
||||
bool has_extra_stop_wave = (number_of_stop_bits < 0);
|
||||
number_of_stop_bits = abs(number_of_stop_bits);
|
||||
@ -294,7 +298,7 @@ void UEF::queue_defined_data(uint32_t length)
|
||||
length -= 3;
|
||||
while(length--)
|
||||
{
|
||||
uint8_t byte = (uint8_t)gzgetc(_file);
|
||||
uint8_t byte = gzget8(_file);
|
||||
|
||||
uint8_t parity_value = byte;
|
||||
parity_value ^= (parity_value >> 4);
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "TimedEventLoop.hpp"
|
||||
#include "../NumberTheory/Factors.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Storage;
|
||||
|
||||
@ -16,29 +17,27 @@ TimedEventLoop::TimedEventLoop(unsigned int 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)
|
||||
_cycles_until_event -= number_of_cycles;
|
||||
while(_cycles_until_event <= 0)
|
||||
{
|
||||
process_next_event();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int TimedEventLoop::get_cycles_until_next_event()
|
||||
{
|
||||
return (unsigned int)std::max(_cycles_until_event, 0);
|
||||
}
|
||||
|
||||
void TimedEventLoop::reset_timer()
|
||||
{
|
||||
_time_into_interval = 0;
|
||||
_stepper.reset();
|
||||
_subcycles_until_event.set_zero();
|
||||
_cycles_until_event = 0;
|
||||
}
|
||||
|
||||
void TimedEventLoop::reset_timer_to_offset(Time offset)
|
||||
{
|
||||
unsigned int common_clock_rate = NumberTheory::least_common_multiple(offset.clock_rate, _event_interval.clock_rate);
|
||||
_time_into_interval = offset.length * (common_clock_rate / offset.clock_rate);
|
||||
_event_interval.length *= common_clock_rate / _event_interval.clock_rate;
|
||||
_event_interval.clock_rate = common_clock_rate;
|
||||
if(common_clock_rate != _stepper->get_output_rate())
|
||||
{
|
||||
_stepper.reset(new SignalProcessing::Stepper(_event_interval.clock_rate, _input_clock_rate));
|
||||
}
|
||||
// TODO: apply
|
||||
}
|
||||
|
||||
void TimedEventLoop::jump_to_next_event()
|
||||
@ -49,43 +48,27 @@ void TimedEventLoop::jump_to_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;
|
||||
// Calculate [interval]*[input clock rate] + [subcycles until this event].
|
||||
int64_t denominator = (int64_t)interval.clock_rate * (int64_t)_subcycles_until_event.clock_rate;
|
||||
int64_t numerator =
|
||||
(int64_t)_subcycles_until_event.clock_rate * (int64_t)_input_clock_rate * (int64_t)interval.length +
|
||||
(int64_t)interval.clock_rate * (int64_t)_subcycles_until_event.length;
|
||||
|
||||
// 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;
|
||||
}
|
||||
// Simplify now, to prepare for stuffing into possibly 32-bit quantities
|
||||
int64_t common_divisor = NumberTheory::greatest_common_divisor(numerator % denominator, denominator);
|
||||
denominator /= common_divisor;
|
||||
numerator /= common_divisor;
|
||||
|
||||
// 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));
|
||||
}
|
||||
// So this event will fire in the integral number of cycles from now, putting us at the remainder
|
||||
// number of subcycles
|
||||
_cycles_until_event = (int)(numerator / denominator);
|
||||
_subcycles_until_event.length = (unsigned int)(numerator % denominator);
|
||||
_subcycles_until_event.clock_rate = (unsigned int)denominator;
|
||||
}
|
||||
|
||||
Time TimedEventLoop::get_time_into_next_event()
|
||||
{
|
||||
Time result = _event_interval;
|
||||
result.length = _time_into_interval;
|
||||
return result;
|
||||
// TODO: calculate, presumably as [length of interval] - ([cycles left] + [subcycles left])
|
||||
Time zero;
|
||||
return zero;
|
||||
}
|
||||
|
@ -48,6 +48,11 @@ namespace Storage {
|
||||
*/
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
|
||||
/*!
|
||||
@returns the number of whole cycles remaining until the next event is triggered.
|
||||
*/
|
||||
unsigned int get_cycles_until_next_event();
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Sets the time interval, as a proportion of a second, until the next event should be triggered.
|
||||
@ -86,9 +91,9 @@ namespace Storage {
|
||||
|
||||
private:
|
||||
unsigned int _input_clock_rate;
|
||||
int _cycles_until_event;
|
||||
Time _subcycles_until_event;
|
||||
Time _event_interval;
|
||||
std::unique_ptr<SignalProcessing::Stepper> _stepper;
|
||||
uint32_t _time_into_interval;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user