1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-05 10:28:58 +00:00

Merge pull request #41 from TomHarte/DiskDrive

Implements something of the foundation for disk drive emulation
This commit is contained in:
Thomas Harte 2016-08-01 04:43:07 -04:00 committed by GitHub
commit a2ca94431e
30 changed files with 1023 additions and 120 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View File

@ -0,0 +1,34 @@
//
// Factors.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Factors.hpp"
unsigned int NumberTheory::greatest_common_divisor(unsigned int a, unsigned int b)
{
if(a < b)
{
unsigned int swap = b;
b = a;
a = swap;
}
while(1) {
if(!a) return b;
if(!b) return a;
unsigned int remainder = a%b;
a = b;
b = remainder;
}
}
unsigned int NumberTheory::least_common_multiple(unsigned int a, unsigned int b)
{
unsigned int gcd = greatest_common_divisor(a, b);
return (a*b) / gcd;
}

17
NumberTheory/Factors.hpp Normal file
View File

@ -0,0 +1,17 @@
//
// Factors.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Factors_hpp
#define Factors_hpp
namespace NumberTheory {
unsigned int greatest_common_divisor(unsigned int a, unsigned int b);
unsigned int least_common_multiple(unsigned int a, unsigned int b);
}
#endif /* Factors_hpp */

View File

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

View File

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

View File

@ -6,3 +6,4 @@
#import "MOS6522Bridge.h"
#import "MOS6532Bridge.h"
#import "C1540Bridge.h"
#import "DigitalPhaseLockedLoopBridge.h"

View File

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

View File

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

View 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))")
}
}

View File

@ -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;
}
/*!

View File

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

View 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);
}

View 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
View 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);
}

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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));
}
}

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