mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-05 21:32:55 +00:00
Merge pull request #41 from TomHarte/DiskDrive
Implements something of the foundation for disk drive emulation
This commit is contained in:
commit
a2ca94431e
@ -364,7 +364,7 @@ class MOS6522IRQDelegate {
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
|
||||
};
|
||||
|
||||
void set_delegate(Delegate *delegate)
|
||||
void set_interrupt_delegate(Delegate *delegate)
|
||||
{
|
||||
_delegate = delegate;
|
||||
}
|
||||
|
@ -7,11 +7,14 @@
|
||||
//
|
||||
|
||||
#include "C1540.hpp"
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
|
||||
|
||||
using namespace Commodore::C1540;
|
||||
|
||||
Machine::Machine()
|
||||
Machine::Machine() :
|
||||
_shift_register(0),
|
||||
Storage::DiskDrive(1000000, 16, 300)
|
||||
{
|
||||
// create a serial port and a VIA to run it
|
||||
_serialPortVIA.reset(new SerialPortVIA);
|
||||
@ -22,8 +25,12 @@ Machine::Machine()
|
||||
_serialPortVIA->set_serial_port(_serialPort);
|
||||
|
||||
// set this instance as the delegate to receive interrupt requests from both VIAs
|
||||
_serialPortVIA->set_delegate(this);
|
||||
_serialPortVIA->set_interrupt_delegate(this);
|
||||
_driveVIA.set_interrupt_delegate(this);
|
||||
_driveVIA.set_delegate(this);
|
||||
|
||||
// set a bit rate
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
|
||||
}
|
||||
|
||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus)
|
||||
@ -33,7 +40,11 @@ void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bu
|
||||
|
||||
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
|
||||
{
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && (address >= 0xF556 && address <= 0xF56D)) printf("%04x\n", address);
|
||||
// static bool log = false;
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && (address == 0xF3C0)) log = true;
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && log) printf("%04x\n", address);
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode) printf("%04x\n", address);
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && (address >= 0xF510 && address <= 0xF553)) printf("%04x\n", address);
|
||||
// if(operation == CPU6502::BusOperation::ReadOpcode && (address == 0xE887)) printf("A: %02x\n", get_value_of_register(CPU6502::Register::A));
|
||||
|
||||
/* static bool log = false;
|
||||
@ -91,9 +102,11 @@ void Machine::set_rom(const uint8_t *rom)
|
||||
memcpy(_rom, rom, sizeof(_rom));
|
||||
}
|
||||
|
||||
void Machine::set_disk(std::shared_ptr<Storage::Disk> disk)
|
||||
void Machine::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
_disk = disk;
|
||||
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
|
||||
if(_driveVIA.get_motor_enabled()) // TODO: motor speed up/down
|
||||
Storage::DiskDrive::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
|
||||
#pragma mark - 6522 delegate
|
||||
@ -103,3 +116,46 @@ void Machine::mos6522_did_change_interrupt_status(void *mos6522)
|
||||
// both VIAs are connected to the IRQ line
|
||||
set_irq_line(_serialPortVIA->get_interrupt_line() || _driveVIA.get_interrupt_line());
|
||||
}
|
||||
|
||||
#pragma mark - Disk drive
|
||||
|
||||
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole)
|
||||
{
|
||||
_shift_register = (_shift_register << 1) | value;
|
||||
if((_shift_register & 0x3ff) == 0x3ff)
|
||||
{
|
||||
_driveVIA.set_sync_detected(true);
|
||||
_bit_window_offset = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_driveVIA.set_sync_detected(false);
|
||||
}
|
||||
_bit_window_offset++;
|
||||
if(_bit_window_offset == 8)
|
||||
{
|
||||
_driveVIA.set_data_input((uint8_t)_shift_register);
|
||||
_bit_window_offset = 0;
|
||||
if(_driveVIA.get_should_set_overflow())
|
||||
{
|
||||
set_overflow_line(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
set_overflow_line(false);
|
||||
}
|
||||
|
||||
// the 1540 does not recognise index holes
|
||||
void Machine::process_index_hole() {}
|
||||
|
||||
#pragma mak - Drive VIA delegate
|
||||
|
||||
void Machine::drive_via_did_step_head(void *driveVIA, int direction)
|
||||
{
|
||||
step(direction);
|
||||
}
|
||||
|
||||
void Machine::drive_via_did_set_data_density(void *driveVIA, int density)
|
||||
{
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density));
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "../SerialBus.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
#include "../../../Storage/Disk/DiskDrive.hpp"
|
||||
|
||||
namespace Commodore {
|
||||
namespace C1540 {
|
||||
@ -100,12 +101,12 @@ class SerialPortVIA: public MOS::MOS6522<SerialPortVIA>, public MOS::MOS6522IRQD
|
||||
An implementation of the drive VIA in a Commodore 1540 — the VIA that is used to interface with the disk.
|
||||
|
||||
It is wired up such that Port B contains:
|
||||
Bits 0/1: head step direction (TODO)
|
||||
Bit 2: motor control (TODO)
|
||||
Bits 0/1: head step direction
|
||||
Bit 2: motor control
|
||||
Bit 3: LED control (TODO)
|
||||
Bit 4: write protect photocell status (TODO)
|
||||
Bits 5/6: write density (TODO)
|
||||
Bit 7: 0 if sync marks are currently being detected, 1 otherwise;
|
||||
Bits 5/6: read/write density
|
||||
Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
|
||||
|
||||
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
|
||||
|
||||
@ -114,28 +115,80 @@ class SerialPortVIA: public MOS::MOS6522<SerialPortVIA>, public MOS::MOS6522IRQD
|
||||
*/
|
||||
class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0;
|
||||
virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate)
|
||||
{
|
||||
_delegate = delegate;
|
||||
}
|
||||
|
||||
using MOS6522IRQDelegate::set_interrupt_status;
|
||||
|
||||
// write protect tab uncovered
|
||||
DriveVIA() : _port_b(0xff), _port_a(0xff), _delegate(nullptr) {}
|
||||
|
||||
uint8_t get_port_input(Port port) {
|
||||
if(port)
|
||||
{
|
||||
return 0xff; // imply not sync, write protect tab uncovered
|
||||
return port ? _port_b : _port_a;
|
||||
}
|
||||
|
||||
void set_sync_detected(bool sync_detected) {
|
||||
_port_b = (_port_b & 0x7f) | (sync_detected ? 0x00 : 0x80);
|
||||
}
|
||||
|
||||
void set_data_input(uint8_t value) {
|
||||
_port_a = value;
|
||||
}
|
||||
|
||||
bool get_should_set_overflow() {
|
||||
return _should_set_overflow;
|
||||
}
|
||||
|
||||
bool get_motor_enabled() {
|
||||
return _drive_motor;
|
||||
}
|
||||
|
||||
void set_control_line_output(Port port, Line line, bool value) {
|
||||
if(port == Port::A && line == Line::Two) {
|
||||
_should_set_overflow = value;
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||
if(port)
|
||||
{
|
||||
// if(value&4)
|
||||
// {
|
||||
// printf("Head step: %d\n", value&3);
|
||||
// printf("Motor: %s\n", value&4 ? "On" : "Off");
|
||||
// record drive motor state
|
||||
_drive_motor = !!(value&4);
|
||||
|
||||
// check for a head step
|
||||
int step_difference = ((value&3) - (_previous_port_b_output&3))&3;
|
||||
if(step_difference)
|
||||
{
|
||||
if(_delegate) _delegate->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
|
||||
}
|
||||
|
||||
// check for a change in density
|
||||
int density_difference = (_previous_port_b_output^value) & (3 << 5);
|
||||
if(density_difference && _delegate)
|
||||
{
|
||||
_delegate->drive_via_did_set_data_density(this, (value >> 5)&3);
|
||||
}
|
||||
|
||||
// TODO: something with the drive LED
|
||||
// printf("LED: %s\n", value&8 ? "On" : "Off");
|
||||
// printf("Density: %d\n", (value >> 5)&3);
|
||||
// }
|
||||
|
||||
_previous_port_b_output = value;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t _port_b, _port_a;
|
||||
bool _should_set_overflow;
|
||||
bool _drive_motor;
|
||||
uint8_t _previous_port_b_output;
|
||||
Delegate *_delegate;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -161,7 +214,9 @@ class SerialPort : public ::Commodore::Serial::Port {
|
||||
*/
|
||||
class Machine:
|
||||
public CPU6502::Processor<Machine>,
|
||||
public MOS::MOS6522IRQDelegate::Delegate {
|
||||
public MOS::MOS6522IRQDelegate::Delegate,
|
||||
public DriveVIA::Delegate,
|
||||
public Storage::DiskDrive {
|
||||
|
||||
public:
|
||||
Machine();
|
||||
@ -176,10 +231,7 @@ class Machine:
|
||||
*/
|
||||
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
|
||||
|
||||
/*!
|
||||
Sets the disk from which this 1540 is reading data.
|
||||
*/
|
||||
void set_disk(std::shared_ptr<Storage::Disk> disk);
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
|
||||
// to satisfy CPU6502::Processor
|
||||
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
@ -187,6 +239,10 @@ class Machine:
|
||||
// to satisfy MOS::MOS6522::Delegate
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522);
|
||||
|
||||
// to satisfy DriveVIA::Delegate
|
||||
void drive_via_did_step_head(void *driveVIA, int direction);
|
||||
void drive_via_did_set_data_density(void *driveVIA, int density);
|
||||
|
||||
private:
|
||||
uint8_t _ram[0x800];
|
||||
uint8_t _rom[0x4000];
|
||||
@ -196,6 +252,10 @@ class Machine:
|
||||
DriveVIA _driveVIA;
|
||||
|
||||
std::shared_ptr<Storage::Disk> _disk;
|
||||
|
||||
int _shift_register, _bit_window_offset;
|
||||
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
|
||||
virtual void process_index_hole();
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ Machine::Machine() :
|
||||
_serialPort->set_user_port_via(_userPortVIA);
|
||||
|
||||
// wire up the 6522s, tape and machine
|
||||
_userPortVIA->set_delegate(this);
|
||||
_keyboardVIA->set_delegate(this);
|
||||
_userPortVIA->set_interrupt_delegate(this);
|
||||
_keyboardVIA->set_interrupt_delegate(this);
|
||||
_tape.set_delegate(this);
|
||||
|
||||
// establish the memory maps
|
||||
@ -169,6 +169,11 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
|
||||
_c1540->set_rom(data);
|
||||
_c1540->run_for_cycles(2000000); // pretend it booted a couple of seconds ago
|
||||
}
|
||||
else
|
||||
{
|
||||
_driveROM.reset(new uint8_t[length]);
|
||||
memcpy(_driveROM.get(), data, length);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -221,6 +226,13 @@ void Machine::set_disk(std::shared_ptr<Storage::Disk> disk)
|
||||
|
||||
// hand it the disk
|
||||
_c1540->set_disk(disk);
|
||||
|
||||
// install the ROM if it was previously set
|
||||
if(_driveROM)
|
||||
{
|
||||
_c1540->set_rom(_driveROM.get());
|
||||
_driveROM.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Typer
|
||||
|
@ -287,6 +287,7 @@ class Machine:
|
||||
uint8_t _screenMemory[0x1000];
|
||||
uint8_t _colorMemory[0x0400];
|
||||
uint8_t _junkMemory[0x0400];
|
||||
std::unique_ptr<uint8_t> _driveROM;
|
||||
|
||||
uint8_t *_videoMemoryMap[16];
|
||||
uint8_t *_processorReadMemoryMap[64];
|
||||
|
@ -1008,7 +1008,7 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
if(_is_running)
|
||||
{
|
||||
TapePlayer::run_for_cycles(number_of_cycles);
|
||||
TapePlayer::run_for_cycles((int)number_of_cycles);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
34
NumberTheory/Factors.cpp
Normal file
34
NumberTheory/Factors.cpp
Normal 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
17
NumberTheory/Factors.hpp
Normal 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 */
|
@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */; };
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
|
||||
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
|
||||
4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
|
||||
@ -39,6 +40,7 @@
|
||||
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; };
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
|
||||
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
|
||||
4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */; };
|
||||
4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B73C7191D036BD90074D992 /* Vic20Document.swift */; };
|
||||
4B73C71D1D036C030074D992 /* Vic20Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B73C71B1D036C030074D992 /* Vic20Document.xib */; };
|
||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
|
||||
@ -312,6 +314,9 @@
|
||||
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 */; };
|
||||
4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */; };
|
||||
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA81B587A5100552FC2 /* Assets.xcassets */; };
|
||||
@ -332,6 +337,8 @@
|
||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
|
||||
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; };
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
|
||||
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -354,6 +361,8 @@
|
||||
/* Begin PBXFileReference section */
|
||||
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = "<group>"; };
|
||||
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; };
|
||||
4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DigitalPhaseLockedLoop.cpp; sourceTree = "<group>"; };
|
||||
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DigitalPhaseLockedLoop.hpp; sourceTree = "<group>"; };
|
||||
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
|
||||
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
|
||||
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
@ -415,6 +424,8 @@
|
||||
4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = "<group>"; };
|
||||
4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = "<group>"; };
|
||||
4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
|
||||
4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskDrive.cpp; sourceTree = "<group>"; };
|
||||
4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskDrive.hpp; sourceTree = "<group>"; };
|
||||
4B73C7191D036BD90074D992 /* Vic20Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20Document.swift; sourceTree = "<group>"; };
|
||||
4B73C71C1D036C030074D992 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Vic20Document.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = "<group>"; };
|
||||
@ -692,6 +703,12 @@
|
||||
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>"; };
|
||||
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreGCR.cpp; path = Encodings/CommodoreGCR.cpp; sourceTree = "<group>"; };
|
||||
4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CommodoreGCR.hpp; path = Encodings/CommodoreGCR.hpp; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
4BB73EA61B587A5100552FC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Atari2600Document.xib; sourceTree = "<group>"; };
|
||||
@ -734,6 +751,9 @@
|
||||
4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = "<group>"; };
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
|
||||
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
|
||||
4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = "<group>"; };
|
||||
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; };
|
||||
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -884,13 +904,15 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B3BA0C51D318B44005DD7A7 /* C1540Bridge.h */,
|
||||
4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */,
|
||||
4B3BA0C71D318B44005DD7A7 /* Clock SignalTests-Bridging-Header.h */,
|
||||
4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */,
|
||||
4B3BA0C81D318B44005DD7A7 /* MOS6522Bridge.h */,
|
||||
4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */,
|
||||
4B3BA0CA1D318B44005DD7A7 /* MOS6532Bridge.h */,
|
||||
4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */,
|
||||
4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */,
|
||||
4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */,
|
||||
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */,
|
||||
4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */,
|
||||
4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */,
|
||||
4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */,
|
||||
);
|
||||
path = Bridges;
|
||||
@ -951,9 +973,11 @@
|
||||
4B69FB391C4D908A00B5F0AA /* Storage */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */,
|
||||
4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */,
|
||||
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */,
|
||||
4BAB62AA1D3272D200DF5BA0 /* Disk */,
|
||||
4B69FB3A1C4D908A00B5F0AA /* Tape */,
|
||||
4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */,
|
||||
);
|
||||
name = Storage;
|
||||
path = ../../Storage;
|
||||
@ -984,11 +1008,16 @@
|
||||
4BAB62AA1D3272D200DF5BA0 /* Disk */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BAB62B21D327F7E00DF5BA0 /* Formats */,
|
||||
4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */,
|
||||
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */,
|
||||
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
|
||||
4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */,
|
||||
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
|
||||
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */,
|
||||
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
|
||||
4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */,
|
||||
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
|
||||
4BB697CF1D4BA44900248BDF /* Encodings */,
|
||||
4BAB62B21D327F7E00DF5BA0 /* Formats */,
|
||||
);
|
||||
path = Disk;
|
||||
sourceTree = "<group>";
|
||||
@ -1274,6 +1303,24 @@
|
||||
path = "Wolfgang Lorenz 6502 test suite";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB697C81D4B559300248BDF /* NumberTheory */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BB697C51D4B558F00248BDF /* Factors.cpp */,
|
||||
4BB697C61D4B558F00248BDF /* Factors.hpp */,
|
||||
);
|
||||
name = NumberTheory;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB697CF1D4BA44900248BDF /* Encodings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */,
|
||||
4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */,
|
||||
);
|
||||
name = Encodings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB73E951B587A5100552FC2 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1283,13 +1330,17 @@
|
||||
4BB73EC01B587A5100552FC2 /* Clock SignalUITests */,
|
||||
4BC9DF4A1D04691600F44158 /* Components */,
|
||||
4BB73EDC1B587CA500552FC2 /* Machines */,
|
||||
4BB697C81D4B559300248BDF /* NumberTheory */,
|
||||
4B366DFD1B5C165F0026627B /* Outputs */,
|
||||
4BB73EDD1B587CA500552FC2 /* Processors */,
|
||||
4BB73E9F1B587A5100552FC2 /* Products */,
|
||||
4B2409591C45DF85004DA684 /* SignalProcessing */,
|
||||
4B69FB391C4D908A00B5F0AA /* Storage */,
|
||||
);
|
||||
indentWidth = 4;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 4;
|
||||
usesTabs = 1;
|
||||
};
|
||||
4BB73E9F1B587A5100552FC2 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
@ -1330,6 +1381,7 @@
|
||||
4B1E85801D176468001EF87D /* 6532Tests.swift */,
|
||||
4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */,
|
||||
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */,
|
||||
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */,
|
||||
4B1414611B58888700E04248 /* KlausDormannTests.swift */,
|
||||
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */,
|
||||
4B3BA0C41D318B44005DD7A7 /* Bridges */,
|
||||
@ -1853,14 +1905,17 @@
|
||||
files = (
|
||||
4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */,
|
||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
|
||||
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
|
||||
4BB697C71D4B558F00248BDF /* Factors.cpp in Sources */,
|
||||
4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */,
|
||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
|
||||
4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */,
|
||||
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */,
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */,
|
||||
4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */,
|
||||
4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */,
|
||||
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */,
|
||||
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,
|
||||
@ -1871,12 +1926,14 @@
|
||||
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
|
||||
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */,
|
||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
|
||||
4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */,
|
||||
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
|
||||
4B1E85751D170228001EF87D /* Typer.cpp in Sources */,
|
||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
|
||||
4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */,
|
||||
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
|
||||
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
|
||||
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
|
||||
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */,
|
||||
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */,
|
||||
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */,
|
||||
@ -1897,10 +1954,12 @@
|
||||
4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */,
|
||||
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */,
|
||||
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */,
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */,
|
||||
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */,
|
||||
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */,
|
||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
|
||||
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */,
|
||||
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */,
|
||||
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */,
|
||||
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
|
||||
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */,
|
||||
|
@ -32,7 +32,7 @@ class Vic20Document: MachineDocument {
|
||||
vic20.setCharactersROM(characters)
|
||||
}
|
||||
|
||||
if let drive = dataForResource("1541", ofType: "bin", inDirectory: "ROMImages/Commodore1540") {
|
||||
if let drive = dataForResource("1540", ofType: "bin", inDirectory: "ROMImages/Commodore1540") {
|
||||
vic20.setDriveROM(drive)
|
||||
}
|
||||
}
|
||||
|
@ -6,3 +6,4 @@
|
||||
#import "MOS6522Bridge.h"
|
||||
#import "MOS6532Bridge.h"
|
||||
#import "C1540Bridge.h"
|
||||
#import "DigitalPhaseLockedLoopBridge.h"
|
||||
|
@ -0,0 +1,20 @@
|
||||
//
|
||||
// DigitalPhaseLockedLoopBridge.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/07/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface DigitalPhaseLockedLoopBridge : NSObject
|
||||
|
||||
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit tolerance:(NSUInteger)tolerance historyLength:(NSUInteger)historyLength;
|
||||
|
||||
- (void)runForCycles:(NSUInteger)cycles;
|
||||
- (void)addPulse;
|
||||
|
||||
@property(nonatomic) NSUInteger stream;
|
||||
|
||||
@end
|
@ -0,0 +1,61 @@
|
||||
//
|
||||
// DigitalPhaseLockedLoopBridge.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/07/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DigitalPhaseLockedLoopBridge.h"
|
||||
|
||||
#include "DigitalPhaseLockedLoop.hpp"
|
||||
#include <memory>
|
||||
|
||||
@interface DigitalPhaseLockedLoopBridge(BitPushing)
|
||||
- (void)pushBit:(int)value;
|
||||
@end
|
||||
|
||||
class DigitalPhaseLockedLoopDelegate: public Storage::DigitalPhaseLockedLoop::Delegate {
|
||||
public:
|
||||
__weak DigitalPhaseLockedLoopBridge *bridge;
|
||||
|
||||
void digital_phase_locked_loop_output_bit(int value)
|
||||
{
|
||||
[bridge pushBit:value ? 1 : 0];
|
||||
}
|
||||
};
|
||||
|
||||
@implementation DigitalPhaseLockedLoopBridge
|
||||
{
|
||||
std::unique_ptr<Storage::DigitalPhaseLockedLoop> _digitalPhaseLockedLoop;
|
||||
DigitalPhaseLockedLoopDelegate _delegate;
|
||||
}
|
||||
|
||||
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit tolerance:(NSUInteger)tolerance historyLength:(NSUInteger)historyLength
|
||||
{
|
||||
self = [super init];
|
||||
if(self)
|
||||
{
|
||||
_digitalPhaseLockedLoop.reset(new Storage::DigitalPhaseLockedLoop((unsigned int)clocksPerBit, (unsigned int)tolerance, (unsigned int)historyLength));
|
||||
_delegate.bridge = self;
|
||||
_digitalPhaseLockedLoop->set_delegate(&_delegate);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)runForCycles:(NSUInteger)cycles
|
||||
{
|
||||
_digitalPhaseLockedLoop->run_for_cycles((unsigned int)cycles);
|
||||
}
|
||||
|
||||
- (void)addPulse
|
||||
{
|
||||
_digitalPhaseLockedLoop->add_pulse();
|
||||
}
|
||||
|
||||
- (void)pushBit:(int)value
|
||||
{
|
||||
_stream = (_stream << 1) | value;
|
||||
}
|
||||
|
||||
@end
|
61
OSBindings/Mac/Clock SignalTests/DPLLTests.swift
Normal file
61
OSBindings/Mac/Clock SignalTests/DPLLTests.swift
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// DPLLTests.swift
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/07/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
class DPLLTests: XCTestCase {
|
||||
|
||||
func testRegularNibblesOnPLL(pll: DigitalPhaseLockedLoopBridge, bitLength: UInt) {
|
||||
// clock in two 1s, a 0, and a 1, 200 times over
|
||||
for _ in 0 ..< 200 {
|
||||
pll.runForCycles(bitLength/2)
|
||||
pll.addPulse()
|
||||
pll.runForCycles(bitLength)
|
||||
pll.addPulse()
|
||||
pll.runForCycles(bitLength*2)
|
||||
pll.addPulse()
|
||||
pll.runForCycles(bitLength/2)
|
||||
}
|
||||
|
||||
XCTAssert((pll.stream&0xffffffff) == 0xdddddddd, "PLL should have synchronised and clocked repeating 0xd nibbles; got \(String(pll.stream, radix: 16, uppercase: false))")
|
||||
}
|
||||
|
||||
func testPerfectInput() {
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3)
|
||||
testRegularNibblesOnPLL(pll, bitLength: 100)
|
||||
}
|
||||
|
||||
func testFastButRegular() {
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3)
|
||||
testRegularNibblesOnPLL(pll, bitLength: 90)
|
||||
}
|
||||
|
||||
func testSlowButRegular() {
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3)
|
||||
testRegularNibblesOnPLL(pll, bitLength: 110)
|
||||
}
|
||||
|
||||
func testTwentyPercentSinePattern() {
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3)
|
||||
var angle = 0.0
|
||||
|
||||
// clock in two 1s, a 0, and a 1, 200 times over
|
||||
for _ in 0 ..< 200 {
|
||||
let bitLength: UInt = UInt(100 + 20 * sin(angle))
|
||||
|
||||
pll.runForCycles(bitLength/2)
|
||||
pll.addPulse()
|
||||
pll.runForCycles((bitLength*3)/2)
|
||||
|
||||
angle = angle + 0.1
|
||||
}
|
||||
|
||||
let endOfStream = pll.stream&0xffffffff;
|
||||
XCTAssert(endOfStream == 0xaaaaaaaa || endOfStream == 0x55555555, "PLL should have synchronised and clocked repeating 0xa or 0x5 nibbles; got \(String(pll.stream, radix: 16, uppercase: false))")
|
||||
}
|
||||
}
|
@ -472,6 +472,7 @@ template <class T> class Processor {
|
||||
bool _ready_line_is_enabled;
|
||||
|
||||
bool _irq_line_is_enabled, _irq_request_history;
|
||||
bool _nmi_line_is_enabled, _set_overflow_line_is_enabled;
|
||||
|
||||
/*!
|
||||
Gets the program representing an RST response.
|
||||
@ -549,7 +550,10 @@ template <class T> class Processor {
|
||||
_interruptFlag(Flag::Interrupt),
|
||||
_s(0),
|
||||
_nextBusOperation(BusOperation::None),
|
||||
_interrupt_requests(InterruptRequestFlags::PowerOn)
|
||||
_interrupt_requests(InterruptRequestFlags::PowerOn),
|
||||
_irq_line_is_enabled(false),
|
||||
_nmi_line_is_enabled(false),
|
||||
_set_overflow_line_is_enabled(false)
|
||||
{
|
||||
// only the interrupt flag is defined upon reset but get_flags isn't going to
|
||||
// mask the other flags so we need to do that, at least
|
||||
@ -1237,6 +1241,19 @@ template <class T> class Processor {
|
||||
_irq_line_is_enabled = active;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current level of the set overflow line.
|
||||
|
||||
@param active @c true if the line is logically active; @c false otherwise.
|
||||
*/
|
||||
inline void set_overflow_line(bool active)
|
||||
{
|
||||
// a leading edge will set the overflow flag
|
||||
if(active && !_set_overflow_line_is_enabled)
|
||||
_overflowFlag = Flag::Overflow;
|
||||
_set_overflow_line_is_enabled = active;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current level of the NMI line.
|
||||
|
||||
@ -1245,7 +1262,9 @@ template <class T> class Processor {
|
||||
inline void set_nmi_line(bool active)
|
||||
{
|
||||
// NMI is edge triggered, not level
|
||||
_interrupt_requests |= (active ? InterruptRequestFlags::NMI : 0);
|
||||
if(active && !_nmi_line_is_enabled)
|
||||
_interrupt_requests |= InterruptRequestFlags::NMI;
|
||||
_nmi_line_is_enabled = active;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -13,19 +13,41 @@
|
||||
|
||||
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.
|
||||
|
||||
Pegs the beginning of both clocks to the time at which the stepper is created. So e.g. a stepper
|
||||
that converts from an input clock of 1200 to an output clock of 2 will first fire on cycle 600.
|
||||
*/
|
||||
class Stepper
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
Establishes a stepper with a one-to-one conversion rate.
|
||||
*/
|
||||
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) :
|
||||
accumulated_error_(0),
|
||||
accumulated_error_(-((int64_t)input_rate << 1)),
|
||||
input_rate_(input_rate),
|
||||
output_rate_(output_rate),
|
||||
whole_step_(output_rate / input_rate),
|
||||
adjustment_up_((int64_t)(output_rate % 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()
|
||||
{
|
||||
uint64_t update = whole_step_;
|
||||
@ -38,11 +60,34 @@ class Stepper
|
||||
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()
|
||||
{
|
||||
return output_rate_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the input rate.
|
||||
*/
|
||||
inline uint64_t get_input_rate()
|
||||
{
|
||||
return input_rate_;
|
||||
|
76
Storage/Disk/DigitalPhaseLockedLoop.cpp
Normal file
76
Storage/Disk/DigitalPhaseLockedLoop.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
//
|
||||
// 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, int tolerance, 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.reset(new std::vector<int>(length_of_history, 0));
|
||||
}
|
||||
|
||||
void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
_phase += number_of_cycles;
|
||||
int windows_crossed = _phase / _window_length;
|
||||
if(windows_crossed)
|
||||
{
|
||||
// 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_error(_phase - (_window_length >> 1));
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalPhaseLockedLoop::post_phase_error(int error)
|
||||
{
|
||||
// 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
|
||||
std::vector<int> *phase_error_history = _phase_error_history.get();
|
||||
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);
|
||||
}
|
69
Storage/Disk/DigitalPhaseLockedLoop.hpp
Normal file
69
Storage/Disk/DigitalPhaseLockedLoop.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
//
|
||||
// DigitalPhaseLockedLoop.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/07/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DigitalPhaseLockedLoop_hpp
|
||||
#define DigitalPhaseLockedLoop_hpp
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Storage {
|
||||
|
||||
class DigitalPhaseLockedLoop {
|
||||
public:
|
||||
/*!
|
||||
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);
|
||||
|
||||
/*!
|
||||
Runs the loop, impliedly posting no pulses during that period.
|
||||
|
||||
@c number_of_cycles The time to run the loop for.
|
||||
*/
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
|
||||
/*!
|
||||
Announces a pulse at the current time.
|
||||
*/
|
||||
void add_pulse();
|
||||
|
||||
/*!
|
||||
A receiver for PCM output data; called upon every recognised bit.
|
||||
*/
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void digital_phase_locked_loop_output_bit(int value) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate)
|
||||
{
|
||||
_delegate = delegate;
|
||||
}
|
||||
|
||||
private:
|
||||
Delegate *_delegate;
|
||||
|
||||
void post_phase_error(int error);
|
||||
std::unique_ptr<std::vector<int>> _phase_error_history;
|
||||
size_t _phase_error_pointer;
|
||||
|
||||
int _phase;
|
||||
int _window_length;
|
||||
bool _window_was_filled;
|
||||
|
||||
int _clocks_per_bit;
|
||||
int _tolerance;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* DigitalPhaseLockedLoop_hpp */
|
118
Storage/Disk/DiskDrive.cpp
Normal file
118
Storage/Disk/DiskDrive.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
//
|
||||
// DiskDrive.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/07/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "DiskDrive.hpp"
|
||||
|
||||
using namespace Storage;
|
||||
|
||||
DiskDrive::DiskDrive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) :
|
||||
_clock_rate(clock_rate * clock_rate_multiplier),
|
||||
_clock_rate_multiplier(clock_rate_multiplier),
|
||||
_revolutions_per_minute(revolutions_per_minute),
|
||||
_head_position(0),
|
||||
|
||||
TimedEventLoop(clock_rate * clock_rate_multiplier)
|
||||
{}
|
||||
|
||||
void DiskDrive::set_expected_bit_length(Time bit_length)
|
||||
{
|
||||
_bit_length = 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)((bit_length.length * _clock_rate) / bit_length.clock_rate);
|
||||
_pll.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3));
|
||||
_pll->set_delegate(this);
|
||||
}
|
||||
|
||||
void DiskDrive::set_disk(std::shared_ptr<Disk> disk)
|
||||
{
|
||||
_disk = disk;
|
||||
set_track();
|
||||
}
|
||||
|
||||
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);
|
||||
set_track();
|
||||
}
|
||||
|
||||
void DiskDrive::set_track()
|
||||
{
|
||||
_track = _disk->get_track_at_position((unsigned int)_head_position);
|
||||
reset_timer();
|
||||
get_next_event();
|
||||
}
|
||||
|
||||
void DiskDrive::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
if(has_disk())
|
||||
{
|
||||
number_of_cycles *= _clock_rate_multiplier;
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
_cycles_since_index_hole ++;
|
||||
_pll->run_for_cycles(1);
|
||||
TimedEventLoop::run_for_cycles(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Track timed event loop
|
||||
|
||||
void DiskDrive::get_next_event()
|
||||
{
|
||||
if(_track)
|
||||
_current_event = _track->get_next_event();
|
||||
else
|
||||
{
|
||||
_current_event.length.length = 1;
|
||||
_current_event.length.clock_rate = 1;
|
||||
_current_event.type = Track::Event::IndexHole;
|
||||
}
|
||||
|
||||
// divide interval, which is in terms of a rotation of the disk, by rotation speed, and
|
||||
// convert it into revolutions per second
|
||||
Time event_interval = _current_event.length;
|
||||
event_interval.length *= 60;
|
||||
event_interval.clock_rate *= _revolutions_per_minute;
|
||||
event_interval.simplify();
|
||||
set_next_event_time_interval(event_interval);
|
||||
}
|
||||
|
||||
void DiskDrive::process_next_event()
|
||||
{
|
||||
switch(_current_event.type)
|
||||
{
|
||||
case Track::Event::FluxTransition:
|
||||
_pll->add_pulse();
|
||||
break;
|
||||
case Track::Event::IndexHole:
|
||||
_cycles_since_index_hole = 0;
|
||||
process_index_hole();
|
||||
break;
|
||||
}
|
||||
get_next_event();
|
||||
}
|
||||
|
||||
#pragma mark - PLL delegate
|
||||
|
||||
void DiskDrive::digital_phase_locked_loop_output_bit(int value)
|
||||
{
|
||||
process_input_bit(value, _cycles_since_index_hole);
|
||||
}
|
62
Storage/Disk/DiskDrive.hpp
Normal file
62
Storage/Disk/DiskDrive.hpp
Normal file
@ -0,0 +1,62 @@
|
||||
//
|
||||
// DiskDrive.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/07/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DiskDrive_hpp
|
||||
#define DiskDrive_hpp
|
||||
|
||||
#include "Disk.hpp"
|
||||
#include "DigitalPhaseLockedLoop.hpp"
|
||||
#include "../TimedEventLoop.hpp"
|
||||
|
||||
namespace Storage {
|
||||
|
||||
class DiskDrive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
|
||||
public:
|
||||
DiskDrive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute);
|
||||
|
||||
void set_expected_bit_length(Time bit_length);
|
||||
|
||||
void set_disk(std::shared_ptr<Disk> disk);
|
||||
bool has_disk();
|
||||
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
|
||||
bool get_is_track_zero();
|
||||
void step(int direction);
|
||||
void set_motor_on(bool motor_on);
|
||||
|
||||
// to satisfy DigitalPhaseLockedLoop::Delegate
|
||||
void digital_phase_locked_loop_output_bit(int value);
|
||||
|
||||
protected:
|
||||
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole) = 0;
|
||||
virtual void process_index_hole() = 0;
|
||||
|
||||
// for TimedEventLoop
|
||||
virtual void process_next_event();
|
||||
|
||||
private:
|
||||
Time _bit_length;
|
||||
unsigned int _clock_rate;
|
||||
unsigned int _clock_rate_multiplier;
|
||||
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 set_track();
|
||||
|
||||
inline void get_next_event();
|
||||
Track::Event _current_event;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* DiskDrive_hpp */
|
20
Storage/Disk/Encodings/CommodoreGCR.cpp
Normal file
20
Storage/Disk/Encodings/CommodoreGCR.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
//
|
||||
// CommodoreGCR.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/07/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CommodoreGCR.hpp"
|
||||
|
||||
using namespace Storage;
|
||||
|
||||
Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned int time_zone)
|
||||
{
|
||||
Time duration;
|
||||
// the speed zone divides a 4Mhz clock by 13, 14, 15 or 16, with higher-numbered zones being faster (i.e. each bit taking less time)
|
||||
duration.length = 16 - time_zone;
|
||||
duration.clock_rate = 4000000;
|
||||
return duration;
|
||||
}
|
22
Storage/Disk/Encodings/CommodoreGCR.hpp
Normal file
22
Storage/Disk/Encodings/CommodoreGCR.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// CommodoreGCR.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/07/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef CommodoreGCR_hpp
|
||||
#define CommodoreGCR_hpp
|
||||
|
||||
#include "../../Storage.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Encodings {
|
||||
namespace CommodoreGCR {
|
||||
Time length_of_a_bit_in_time_zone(unsigned int time_zone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CommodoreGCR_hpp */
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include "../PCMTrack.hpp"
|
||||
#include "../Encodings/CommodoreGCR.hpp"
|
||||
|
||||
using namespace Storage;
|
||||
|
||||
@ -40,7 +41,8 @@ G64::G64(const char *file_name)
|
||||
_maximum_track_size = (uint16_t)fgetc(_file);
|
||||
_maximum_track_size |= (uint16_t)fgetc(_file) << 8;
|
||||
|
||||
get_track_at_position(0);
|
||||
// for(size_t c = 0; c < _number_of_tracks; c++)
|
||||
// get_track_at_position(c);
|
||||
}
|
||||
|
||||
G64::~G64()
|
||||
@ -116,14 +118,14 @@ std::shared_ptr<Track> G64::get_track_at_position(unsigned int position)
|
||||
unsigned int start_byte_in_current_speed = 0;
|
||||
for(unsigned int byte = 0; byte < track_length; byte ++)
|
||||
{
|
||||
unsigned int byte_speed = speed_zone_contents[byte >> 2] >> (6 - (byte&3)*2);
|
||||
unsigned int byte_speed = speed_zone_contents[byte >> 2] >> (6 - (byte&3)*2);
|
||||
if(byte_speed != current_speed || byte == (track_length-1))
|
||||
{
|
||||
unsigned int number_of_bytes = byte - start_byte_in_current_speed;
|
||||
|
||||
PCMSegment segment;
|
||||
segment.duration.length = number_of_bytes * 8;
|
||||
segment.duration.clock_rate = 4000000 / (13 + current_speed); // the speed zone divides a 4Mhz clock by 13, 14, 15 or 16; TODO: is this the right way around? Is zone 3 the fastest or the slowest?
|
||||
segment.number_of_bits = number_of_bytes * 8;
|
||||
segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(current_speed);
|
||||
segment.data.reset(new uint8_t[number_of_bytes]);
|
||||
memcpy(segment.data.get(), &track_contents.get()[start_byte_in_current_speed], number_of_bytes);
|
||||
segments.push_back(std::move(segment));
|
||||
@ -138,8 +140,8 @@ std::shared_ptr<Track> G64::get_track_at_position(unsigned int position)
|
||||
else
|
||||
{
|
||||
PCMSegment segment;
|
||||
segment.duration.length = track_length * 8;
|
||||
segment.duration.clock_rate = 1; // this is arbitrary; if supplying only one PCMSegment then it'll naturally fill the track
|
||||
segment.number_of_bits = track_length * 8;
|
||||
segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)speed_zone_offset);
|
||||
segment.data = std::move(track_contents);
|
||||
|
||||
resulting_track.reset(new PCMTrack(std::move(segment)));
|
||||
|
@ -7,34 +7,10 @@
|
||||
//
|
||||
|
||||
#include "PCMTrack.hpp"
|
||||
#include "../../NumberTheory/Factors.hpp"
|
||||
|
||||
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)
|
||||
{
|
||||
_segments = std::move(segments);
|
||||
@ -55,15 +31,17 @@ PCMTrack::Event PCMTrack::get_next_event()
|
||||
_next_event.length.length = 0;
|
||||
while(_segment_pointer < _segments.size())
|
||||
{
|
||||
unsigned int clock_multiplier = _track_clock_rate / _segments[_segment_pointer].duration.clock_rate;
|
||||
unsigned int clock_multiplier = _track_clock_rate / _segments[_segment_pointer].length_of_a_bit.clock_rate;
|
||||
unsigned int bit_length = clock_multiplier * _segments[_segment_pointer].length_of_a_bit.length;
|
||||
|
||||
const uint8_t *segment_data = _segments[_segment_pointer].data.get();
|
||||
while(_bit_pointer < _segments[_segment_pointer].duration.length)
|
||||
while(_bit_pointer < _segments[_segment_pointer].number_of_bits)
|
||||
{
|
||||
// for timing simplicity, bits are modelled as happening at the end of their window
|
||||
// TODO: should I account for the converse bit ordering? Or can I assume MSB first?
|
||||
int bit = segment_data[_bit_pointer >> 3] & (0x80 >> (_bit_pointer&7));
|
||||
_bit_pointer++;
|
||||
_next_event.length.length += clock_multiplier;
|
||||
_next_event.length.length += bit_length;
|
||||
|
||||
if(bit) return _next_event;
|
||||
}
|
||||
@ -84,18 +62,18 @@ PCMTrack::Event PCMTrack::get_next_event()
|
||||
void PCMTrack::fix_length()
|
||||
{
|
||||
// find the least common multiple of all segment clock rates
|
||||
_track_clock_rate = _segments[0].duration.clock_rate;
|
||||
_track_clock_rate = _segments[0].length_of_a_bit.clock_rate;
|
||||
for(size_t c = 1; c < _segments.size(); c++)
|
||||
{
|
||||
_track_clock_rate = least_common_multiple(_track_clock_rate, _segments[c].duration.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 divisor
|
||||
// thereby determine the total length, storing it to next_event as the track-total divisor
|
||||
_next_event.length.clock_rate = 0;
|
||||
for(size_t c = 0; c < _segments.size(); c++)
|
||||
{
|
||||
unsigned int multiplier = _track_clock_rate / _segments[c].duration.clock_rate;
|
||||
_next_event.length.clock_rate += _segments[c].duration.length * multiplier;
|
||||
unsigned int multiplier = _track_clock_rate / _segments[c].length_of_a_bit.clock_rate;
|
||||
_next_event.length.clock_rate += _segments[c].length_of_a_bit.length * _segments[c].number_of_bits * multiplier;
|
||||
}
|
||||
|
||||
_segment_pointer = _bit_pointer = 0;
|
||||
|
@ -15,22 +15,13 @@
|
||||
namespace Storage {
|
||||
|
||||
/*!
|
||||
A segment of PCM-sampled data. The clock rate in the duration is taken to be relative to all other
|
||||
segments that comprise a track rather than absolute, and the length is taken to be the number of
|
||||
bits from @c data that are actually present.
|
||||
A segment of PCM-sampled data.
|
||||
|
||||
Bits from each byte are taken MSB to LSB.
|
||||
|
||||
Actual segment lengths will be calculated such that all segments that comprise a track exactly fill the track.
|
||||
|
||||
So the segment for a track with only a single segment may supply any clock rate other than 0. It will exactly
|
||||
fill the track, so if it has 7 samples then there will be at most a flux transition every 1/7th of a rotation.
|
||||
|
||||
If a track consists of two segments, one with clock rate 1 and one with clock rate 2, the second will be
|
||||
clocked twice as fast as the first.
|
||||
*/
|
||||
struct PCMSegment {
|
||||
Time duration;
|
||||
Time length_of_a_bit;
|
||||
unsigned int number_of_bits;
|
||||
std::unique_ptr<uint8_t> data;
|
||||
};
|
||||
|
||||
|
@ -9,10 +9,24 @@
|
||||
#ifndef Storage_hpp
|
||||
#define Storage_hpp
|
||||
|
||||
#include "../NumberTheory/Factors.hpp"
|
||||
|
||||
namespace Storage {
|
||||
|
||||
struct Time {
|
||||
unsigned int length, clock_rate;
|
||||
|
||||
inline void simplify()
|
||||
{
|
||||
unsigned int common_divisor = NumberTheory::greatest_common_divisor(length, clock_rate);
|
||||
length /= common_divisor;
|
||||
clock_rate /= common_divisor;
|
||||
}
|
||||
|
||||
inline float get_float()
|
||||
{
|
||||
return (float)length / (float)clock_rate;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#include "Tape.hpp"
|
||||
#include "../../NumberTheory/Factors.hpp"
|
||||
|
||||
using namespace Storage;
|
||||
|
||||
@ -16,12 +17,13 @@ void Tape::seek(Time seek_time)
|
||||
}
|
||||
|
||||
TapePlayer::TapePlayer(unsigned int input_clock_rate) :
|
||||
_input_clock_rate(input_clock_rate)
|
||||
TimedEventLoop(input_clock_rate)
|
||||
{}
|
||||
|
||||
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape> tape)
|
||||
{
|
||||
_tape = tape;
|
||||
reset_timer();
|
||||
get_next_pulse();
|
||||
}
|
||||
|
||||
@ -32,38 +34,34 @@ bool TapePlayer::has_tape()
|
||||
|
||||
void TapePlayer::get_next_pulse()
|
||||
{
|
||||
_input.time_into_pulse = 0;
|
||||
// get the new pulse
|
||||
if(_tape)
|
||||
_input.current_pulse = _tape->get_next_pulse();
|
||||
_current_pulse = _tape->get_next_pulse();
|
||||
else
|
||||
{
|
||||
_input.current_pulse.length.length = 1;
|
||||
_input.current_pulse.length.clock_rate = 1;
|
||||
_input.current_pulse.type = Storage::Tape::Pulse::Zero;
|
||||
}
|
||||
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));
|
||||
_current_pulse.length.length = 1;
|
||||
_current_pulse.length.clock_rate = 1;
|
||||
_current_pulse.type = Storage::Tape::Pulse::Zero;
|
||||
}
|
||||
|
||||
set_next_event_time_interval(_current_pulse.length);
|
||||
}
|
||||
|
||||
void TapePlayer::run_for_cycles(unsigned int number_of_cycles)
|
||||
void TapePlayer::run_for_cycles(int number_of_cycles)
|
||||
{
|
||||
if(has_tape())
|
||||
{
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
_input.time_into_pulse += (unsigned int)_input.pulse_stepper->step();
|
||||
while(_input.time_into_pulse >= _input.current_pulse.length.length)
|
||||
{
|
||||
run_for_input_pulse();
|
||||
}
|
||||
}
|
||||
::TimedEventLoop::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
}
|
||||
|
||||
void TapePlayer::run_for_input_pulse()
|
||||
{
|
||||
process_input_pulse(_input.current_pulse);
|
||||
jump_to_next_event();
|
||||
}
|
||||
|
||||
void TapePlayer::process_next_event()
|
||||
{
|
||||
process_input_pulse(_current_pulse);
|
||||
get_next_pulse();
|
||||
}
|
||||
|
@ -10,8 +10,7 @@
|
||||
#define Tape_hpp
|
||||
|
||||
#include <memory>
|
||||
#include "../../SignalProcessing/Stepper.hpp"
|
||||
#include "../Storage.hpp"
|
||||
#include "../TimedEventLoop.hpp"
|
||||
|
||||
namespace Storage {
|
||||
|
||||
@ -48,29 +47,25 @@ class Tape {
|
||||
Will call @c process_input_pulse instantaneously upon reaching *the end* of a pulse. Therefore a subclass
|
||||
can decode pulses into data within process_input_pulse, using the supplied pulse's @c length and @c type.
|
||||
*/
|
||||
class TapePlayer {
|
||||
class TapePlayer: public TimedEventLoop {
|
||||
public:
|
||||
TapePlayer(unsigned int input_clock_rate);
|
||||
|
||||
void set_tape(std::shared_ptr<Storage::Tape> tape);
|
||||
bool has_tape();
|
||||
|
||||
void run_for_cycles(unsigned int number_of_cycles);
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
void run_for_input_pulse();
|
||||
|
||||
protected:
|
||||
virtual void process_next_event();
|
||||
virtual void process_input_pulse(Tape::Pulse pulse) = 0;
|
||||
|
||||
private:
|
||||
inline void get_next_pulse();
|
||||
|
||||
unsigned int _input_clock_rate;
|
||||
std::shared_ptr<Storage::Tape> _tape;
|
||||
struct {
|
||||
Tape::Pulse current_pulse;
|
||||
std::unique_ptr<SignalProcessing::Stepper> pulse_stepper;
|
||||
uint32_t time_into_pulse;
|
||||
} _input;
|
||||
Tape::Pulse _current_pulse;
|
||||
};
|
||||
|
||||
}
|
||||
|
72
Storage/TimedEventLoop.cpp
Normal file
72
Storage/TimedEventLoop.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
//
|
||||
// TimedEventLoop.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/07/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "TimedEventLoop.hpp"
|
||||
#include "../NumberTheory/Factors.hpp"
|
||||
|
||||
using namespace Storage;
|
||||
|
||||
TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) :
|
||||
_input_clock_rate(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)
|
||||
{
|
||||
process_next_event();
|
||||
}
|
||||
}
|
||||
|
||||
void TimedEventLoop::reset_timer()
|
||||
{
|
||||
_time_into_interval = 0;
|
||||
_stepper.reset();
|
||||
}
|
||||
|
||||
void TimedEventLoop::jump_to_next_event()
|
||||
{
|
||||
reset_timer();
|
||||
process_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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
40
Storage/TimedEventLoop.hpp
Normal file
40
Storage/TimedEventLoop.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// TimedEventLoop.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/07/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TimedEventLoop_hpp
|
||||
#define TimedEventLoop_hpp
|
||||
|
||||
#include "Storage.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include "../SignalProcessing/Stepper.hpp"
|
||||
|
||||
namespace Storage {
|
||||
|
||||
class TimedEventLoop {
|
||||
public:
|
||||
TimedEventLoop(unsigned int input_clock_rate);
|
||||
void run_for_cycles(int number_of_cycles);
|
||||
|
||||
protected:
|
||||
void reset_timer();
|
||||
void set_next_event_time_interval(Time interval);
|
||||
void jump_to_next_event();
|
||||
|
||||
virtual void process_next_event() = 0;
|
||||
|
||||
private:
|
||||
unsigned int _input_clock_rate;
|
||||
Time _event_interval;
|
||||
std::unique_ptr<SignalProcessing::Stepper> _stepper;
|
||||
uint32_t _time_into_interval;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* TimedEventLoop_hpp */
|
Loading…
x
Reference in New Issue
Block a user