1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-02 20:30:00 +00:00

Reduced possibility of overflow on LCM, improved commenting widely, removed one stale piece of G64 bootstrapping caveman stuff.

This commit is contained in:
Thomas Harte 2016-08-01 06:04:55 -04:00
parent a2ca94431e
commit 2799a87218
11 changed files with 202 additions and 16 deletions

View File

@ -30,5 +30,5 @@ unsigned int NumberTheory::greatest_common_divisor(unsigned int a, unsigned int
unsigned int NumberTheory::least_common_multiple(unsigned int a, unsigned int b)
{
unsigned int gcd = greatest_common_divisor(a, b);
return (a*b) / gcd;
return (a / gcd) * (b / gcd);
}

View File

@ -10,7 +10,15 @@
#define Factors_hpp
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);
/*!
@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);
}

View File

@ -15,26 +15,74 @@
namespace Storage {
/*!
Provides the shell for emulating a disk drive something that takes a disk and has a drive head
that steps between tracks, using a phase locked loop ('PLL') to decode a bit stream from the surface of
the disk.
Partly abstract; it is expected that subclasses will provide methods to deal with receiving a newly-recognised
bit from the PLL and with crossing the index hole.
TODO: double sided disks, communication of head size and permissible stepping extents, appropriate
simulation of gain.
*/
class DiskDrive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
public:
/*!
Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier,
spinning inserted disks at @c revolutions_per_minute.
*/
DiskDrive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute);
/*!
Communicates to the PLL the expected length of a bit.
*/
void set_expected_bit_length(Time bit_length);
/*!
Inserts @c disk into the drive.
*/
void set_disk(std::shared_ptr<Disk> disk);
/*!
@returns @c true if a disk is currently inserted; @c false otherwise.
*/
bool has_disk();
/*!
Advances the drive by @c number_of_cycles cycles.
*/
void run_for_cycles(int number_of_cycles);
/*!
@returns @c true if the drive head is currently at track zero; @c false otherwise.
*/
bool get_is_track_zero();
/*!
Steps the disk head the specified number of tracks. Positive numbers step inwards, negative numbers
step outwards.
*/
void step(int direction);
/*!
Enables or disables the disk motor.
*/
void set_motor_on(bool motor_on);
// to satisfy DigitalPhaseLockedLoop::Delegate
void digital_phase_locked_loop_output_bit(int value);
protected:
/*!
Should be implemented by subclasses; communicates each bit that the PLL recognises, also specifying
the amount of time since the index hole was last seen.
*/
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole) = 0;
/*!
Should be implemented by subcalsses; communicates that the index hole has been reached.
*/
virtual void process_index_hole() = 0;
// for TimedEventLoop

View File

@ -18,3 +18,34 @@ Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned int
duration.clock_rate = 4000000;
return duration;
}
unsigned int Storage::Encodings::CommodoreGCR::encoding_for_nibble(uint8_t nibble)
{
switch(nibble & 0xf)
{
case 0x0: return 0x0a;
case 0x1: return 0x0b;
case 0x2: return 0x12;
case 0x3: return 0x13;
case 0x4: return 0x0e;
case 0x5: return 0x0f;
case 0x6: return 0x16;
case 0x7: return 0x17;
case 0x8: return 0x09;
case 0x9: return 0x19;
case 0xa: return 0x1a;
case 0xb: return 0x1b;
case 0xc: return 0x0d;
case 0xd: return 0x1d;
case 0xe: return 0x1e;
case 0xf: return 0x15;
// for the benefit of the compiler; clearly unreachable
default: return 0xff;
}
}
unsigned int Storage::Encodings::CommodoreGCR::encoding_for_byte(uint8_t byte)
{
return encoding_for_nibble(byte) | (encoding_for_nibble(byte >> 4) << 5);
}

View File

@ -10,11 +10,27 @@
#define CommodoreGCR_hpp
#include "../../Storage.hpp"
#include <cstdint>
namespace Storage {
namespace Encodings {
namespace CommodoreGCR {
/*!
@returns the proportion of a second that each bit of data within the specified @c time_zone
should idiomatically occupy.
*/
Time length_of_a_bit_in_time_zone(unsigned int time_zone);
/*!
@returns the five-bit GCR encoding for the low four bits of @c nibble.
*/
unsigned int encoding_for_nibble(uint8_t nibble);
/*!
@returns the ten-bit GCR encoding for @c byte.
*/
unsigned int encoding_for_byte(uint8_t byte);
}
}
}

View File

@ -19,15 +19,15 @@ G64::G64(const char *file_name)
_file = fopen(file_name, "rb");
if(!_file)
throw ErrorNotGCR;
throw ErrorCantOpen;
// read and check the file signature
char signature[8];
if(fread(signature, 1, 8, _file) != 8)
throw ErrorNotGCR;
throw ErrorNotG64;
if(memcmp(signature, "GCR-1541", 8))
throw ErrorNotGCR;
throw ErrorNotG64;
// check the version number
int version = fgetc(_file);
@ -40,9 +40,6 @@ G64::G64(const char *file_name)
_number_of_tracks = (uint8_t)fgetc(_file);
_maximum_track_size = (uint16_t)fgetc(_file);
_maximum_track_size |= (uint16_t)fgetc(_file) << 8;
// for(size_t c = 0; c < _number_of_tracks; c++)
// get_track_at_position(c);
}
G64::~G64()

View File

@ -13,16 +13,28 @@
namespace Storage {
/*!
Provies a @c Disk containing a G64 disk image a raw but perfectly-clocked GCR stream.
*/
class G64: public Disk {
public:
/*!
Construct a @c G64 containing content from the file with name @c file_name.
@throws ErrorCantOpen if this file can't be opened.
@throws ErrorNotG64 if the file doesn't appear to contain a .G64 format image.
@throws ErrorUnknownVersion if this file appears to be a .G64 but has an unrecognised version number.
*/
G64(const char *file_name);
~G64();
enum {
ErrorNotGCR,
ErrorCantOpen,
ErrorNotG64,
ErrorUnknownVersion
};
// implemented to satisfy @c Disk
unsigned int get_head_position_count();
std::shared_ptr<Track> get_track_at_position(unsigned int position);

View File

@ -13,9 +13,17 @@
namespace Storage {
/*!
Contains either an absolute time or a time interval, described as a quotient, in terms of a
clock rate to which the time is relative and its length in cycles based on that clock rate.
*/
struct Time {
unsigned int length, clock_rate;
/*!
Reduces this @c Time to its simplest form eliminates all common factors from @c length
and @c clock_rate.
*/
inline void simplify()
{
unsigned int common_divisor = NumberTheory::greatest_common_divisor(length, clock_rate);
@ -23,6 +31,9 @@ struct Time {
clock_rate /= common_divisor;
}
/*!
Returns the floating point conversion of this @c Time. This will often be less precise.
*/
inline float get_float()
{
return (float)length / (float)clock_rate;

View File

@ -14,18 +14,27 @@
namespace Storage {
/*!
Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of zero crossings.
*/
class CommodoreTAP: public Tape {
public:
/*!
Constructs a @c CommodoreTAP containing content from the file with name @c file_name.
@throws ErrorNotCommodoreTAP if this file could not be opened and recognised as a valid Commodore-format TAP.
*/
CommodoreTAP(const char *file_name);
~CommodoreTAP();
Pulse get_next_pulse();
void reset();
enum {
ErrorNotCommodoreTAP
};
// implemented to satisfy @c Tape
Pulse get_next_pulse();
void reset();
private:
FILE *_file;
bool _updated_layout;

View File

@ -15,18 +15,27 @@
namespace Storage {
/*!
Provides a @c Tape containing a UEF tape image.
*/
class UEF : public Tape {
public:
/*!
Constructs a @c UEF containing content from the file with name @c file_name.
@throws ErrorNotUEF if this file could not be opened and recognised as a valid UEF.
*/
UEF(const char *file_name);
~UEF();
Pulse get_next_pulse();
void reset();
enum {
ErrorNotUEF
};
// implemented to satisfy @c Tape
Pulse get_next_pulse();
void reset();
private:
gzFile _file;
unsigned int _time_base;

View File

@ -16,18 +16,63 @@
namespace Storage {
/*!
Provides a mechanism for arbitrarily timed events to be processed according to a fixed-base
discrete clock signal, ensuring correct timing.
Subclasses are responsible for calling @c set_next_event_time_interval to establish the time
until a next event; @c process_next_event will be called when that event occurs, with progression
determined via @c run_for_cycles.
Due to the aggregation of total timing information between events e.g. if an event loop has
a clock rate of 1000 ticks per second and a steady stream of events that occur 10,000 times a second,
bookkeeping is necessary to ensure that 10 events are triggered per tick subclasses should call
@c reset_timer if there is a discontinuity in events.
Subclasses may also call @c jump_to_next_event to cause the next event to be communicated instantly.
Subclasses are therefore expected to call @c set_next_event_time_interval upon obtaining an event stream,
and again in response to each call to @c process_next_event while events are ongoing. They may use
@c reset_timer to initiate a distinctly-timed stream or @c jump_to_next_event to short-circuit the timing
loop and fast forward immediately to the next event.
*/
class TimedEventLoop {
public:
/*!
Constructs a timed event loop that will be clocked at @c input_clock_rate.
*/
TimedEventLoop(unsigned int input_clock_rate);
/*!
Advances the event loop by @c number_of_cycles cycles.
*/
void run_for_cycles(int number_of_cycles);
protected:
void reset_timer();
/*!
Sets the time interval, as a proportion of a second, until the next event should be triggered.
*/
void set_next_event_time_interval(Time interval);
void jump_to_next_event();
/*!
Communicates that the next event is triggered. A subclass will idiomatically process that event
and make a fresh call to @c set_next_event_time_interval to keep the event loop running.
*/
virtual void process_next_event() = 0;
/*!
Resets timing, throwing away any current internal state. So clears any fractional ticks
that the event loop is currently tracking.
*/
void reset_timer();
/*!
Causes an immediate call to @c process_next_event and a call to @c reset_timer with the
net effect of processing the current event immediately and fast forwarding exactly to the
start of the interval prior to the next event.
*/
void jump_to_next_event();
private:
unsigned int _input_clock_rate;
Time _event_interval;