From 3aa40212f348741a276b0f1d2966ff3c7e4bac41 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 15 Aug 2016 19:37:21 -0400 Subject: [PATCH 01/15] Fixed minor documentation error; admitted that this class didn't invent the idea of a pulse. --- Storage/Tape/Formats/CommodoreTAP.cpp | 2 +- Storage/Tape/Formats/CommodoreTAP.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Storage/Tape/Formats/CommodoreTAP.cpp b/Storage/Tape/Formats/CommodoreTAP.cpp index 503f84b40..f574a3fd7 100644 --- a/Storage/Tape/Formats/CommodoreTAP.cpp +++ b/Storage/Tape/Formats/CommodoreTAP.cpp @@ -64,7 +64,7 @@ void CommodoreTAP::reset() _current_pulse.type = Pulse::High; } -CommodoreTAP::Pulse CommodoreTAP::get_next_pulse() +Tape::Pulse CommodoreTAP::get_next_pulse() { if(_current_pulse.type == Pulse::High) { diff --git a/Storage/Tape/Formats/CommodoreTAP.hpp b/Storage/Tape/Formats/CommodoreTAP.hpp index b76055259..7d4a9de17 100644 --- a/Storage/Tape/Formats/CommodoreTAP.hpp +++ b/Storage/Tape/Formats/CommodoreTAP.hpp @@ -15,7 +15,7 @@ namespace Storage { /*! - Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of zero crossings. + Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of downward-going zero crossings. */ class CommodoreTAP: public Tape { public: From 38aec44d854d430b017a977da190d6485c385426 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 15 Aug 2016 19:44:41 -0400 Subject: [PATCH 02/15] Made sufficient changes for the Vic itself to believe it can recast a PRG as a tape and insert it that way. So now the ball is in the court of: how the heck are Commodore tapes encoded? --- Machines/Commodore/Vic-20/Vic20.cpp | 15 ++--- Machines/Commodore/Vic-20/Vic20.hpp | 2 +- .../Clock Signal.xcodeproj/project.pbxproj | 6 ++ .../Documents/Vic20Document.swift | 5 +- .../Clock Signal/Machine/Wrappers/CSVic20.h | 2 +- .../Clock Signal/Machine/Wrappers/CSVic20.mm | 10 +++- Storage/Tape/Formats/TapePRG.cpp | 49 ++++++++++++++++ Storage/Tape/Formats/TapePRG.hpp | 58 +++++++++++++++++++ 8 files changed, 128 insertions(+), 19 deletions(-) create mode 100644 Storage/Tape/Formats/TapePRG.cpp create mode 100644 Storage/Tape/Formats/TapePRG.hpp diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 555ae488c..380009ba9 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -9,6 +9,7 @@ #include "Vic20.hpp" #include +#include "../../../Storage/Tape/Formats/TapePRG.hpp" using namespace Commodore::Vic20; @@ -218,7 +219,7 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) } } -void Machine::add_prg(size_t length, const uint8_t *data) +void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) { if(length > 2) { @@ -237,16 +238,8 @@ void Machine::add_prg(size_t length, const uint8_t *data) } else { - // TODO: write to virtual media (tape, probably?), load normally. - data += 2; - while(_rom_length) - { - uint8_t *ram = _processorWriteMemoryMap[_rom_address >> 10]; - if(ram) ram[_rom_address & 0x3ff] = *data; - data++; - _rom_length--; - _rom_address++; - } + // if it's not a ROM then insert it as a tape + set_tape(std::shared_ptr(new Storage::TapePRG(file_name))); } } } diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index e4fb58386..bb4121dd9 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -260,7 +260,7 @@ class Machine: ~Machine(); void set_rom(ROMSlot slot, size_t length, const uint8_t *data); - void add_prg(size_t length, const uint8_t *data); + void set_prg(const char *file_name, size_t length, const uint8_t *data); void set_tape(std::shared_ptr tape); void set_disk(std::shared_ptr disk); diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 9a9652a66..060d6ef83 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; }; 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; }; + 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; }; 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; @@ -393,6 +394,8 @@ 4B2A539C1D117D36003C6002 /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = ""; }; 4B2A539D1D117D36003C6002 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = ""; }; 4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = ""; }; + 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = ""; }; + 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = ""; }; 4B2E2D941C399D1200138695 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ElectronDocument.xib; sourceTree = ""; }; 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = ""; }; 4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = ""; }; @@ -1004,6 +1007,8 @@ 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */, 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */, 4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */, + 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */, + 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */, ); path = Formats; sourceTree = ""; @@ -1908,6 +1913,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */, 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift index b55a42427..6eb656b3b 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift @@ -47,6 +47,7 @@ class Vic20Document: MachineDocument { case "tap": vic20.openTAPAtURL(url) case "g64": vic20.openG64AtURL(url) case "d64": vic20.openD64AtURL(url) + case "prg": vic20.openPRGAtURL(url) default: let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0)) try self.readFromFileWrapper(fileWrapper, ofType: typeName) @@ -59,10 +60,6 @@ class Vic20Document: MachineDocument { return dataForResource(name, ofType: "bin", inDirectory: "ROMImages/Vic20") } - override func readFromData(data: NSData, ofType typeName: String) throws { - vic20.setPRG(data) - } - // MARK: automatic loading tick box @IBOutlet var loadAutomaticallyButton: NSButton? var autoloadingUserDefaultsKey: String { diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h index c8ade6c34..c2e17fe61 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h @@ -30,7 +30,7 @@ typedef NS_ENUM(NSInteger, CSVic20MemorySize) - (void)setCharactersROM:(nonnull NSData *)rom; - (void)setDriveROM:(nonnull NSData *)rom; -- (void)setPRG:(nonnull NSData *)prg; +- (BOOL)openPRGAtURL:(nonnull NSURL *)URL; - (BOOL)openTAPAtURL:(nonnull NSURL *)URL; - (BOOL)openG64AtURL:(nonnull NSURL *)URL; - (BOOL)openD64AtURL:(nonnull NSURL *)URL; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm index 5108741a1..0f03fb7e2 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm @@ -82,9 +82,15 @@ using namespace Commodore::Vic20; } } -- (void)setPRG:(nonnull NSData *)prg { +- (BOOL)openPRGAtURL:(NSURL *)URL { + NSData *prg = [NSData dataWithContentsOfURL:URL]; @synchronized(self) { - _vic20.add_prg(prg.length, (const uint8_t *)prg.bytes); + try { + _vic20.set_prg(URL.fileSystemRepresentation, prg.length, (const uint8_t *)prg.bytes); + return YES; + } catch(...) { + return NO; + } } } diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp new file mode 100644 index 000000000..07d89ee2f --- /dev/null +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -0,0 +1,49 @@ +// +// TapePRG.cpp +// Clock Signal +// +// Created by Thomas Harte on 14/08/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "TapePRG.hpp" + +#include + +using namespace Storage; + +TapePRG::TapePRG(const char *file_name) : _file(nullptr) +{ + struct stat file_stats; + stat(file_name, &file_stats); + + // There's really no way to validate other than that if this file is larger than 64kb, + // of if load address + length > 65536 then it's broken. + if(file_stats.st_size >= 65538 || file_stats.st_size < 3) + throw ErrorBadFormat; + + _file = fopen(file_name, "rb"); + if(!_file) throw ErrorBadFormat; + + _load_address = (uint16_t)fgetc(_file); + _load_address |= (uint16_t)fgetc(_file) << 8; + + if (_load_address + file_stats.st_size >= 65536) + throw ErrorBadFormat; +} + +TapePRG::~TapePRG() +{ + if(_file) fclose(_file); +} + +Tape::Pulse TapePRG::get_next_pulse() +{ + Tape::Pulse pulse; + return pulse; +} + +void TapePRG::reset() +{ + // TODO +} diff --git a/Storage/Tape/Formats/TapePRG.hpp b/Storage/Tape/Formats/TapePRG.hpp new file mode 100644 index 000000000..1aaabfc80 --- /dev/null +++ b/Storage/Tape/Formats/TapePRG.hpp @@ -0,0 +1,58 @@ +// +// TapePRG.hpp +// Clock Signal +// +// Created by Thomas Harte on 14/08/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef TapePRG_hpp +#define TapePRG_hpp + +#include "../Tape.hpp" +#include + +namespace Storage { + +/*! + Provides a @c Tape containing a .PRG, which is a direct local file. +*/ +class TapePRG: public Tape { + public: + /*! + Constructs a @c T64 containing content from the file with name @c file_name, of type @c type. + + @param file_name The name of the file to load. + @param type The type of data the file should contain. + @throws ErrorBadFormat if this file could not be opened and recognised as the specified type. + */ + TapePRG(const char *file_name); + ~TapePRG(); + + enum { + ErrorBadFormat + }; + + // implemented to satisfy @c Tape + Pulse get_next_pulse(); + void reset(); + private: + FILE *_file; + uint16_t _load_address; + + enum FilePhase { + FilePhaseLeadIn, + FilePhaseHeader + } _filePhase; + + enum BitPhase { + BitPhase0, + BitPhase1, + BitPhase2, + BitPhase3 + } _bitPhase; +}; + +} + +#endif /* T64_hpp */ From ca2dc6b6c4d2c3d2f7487d476cb31501ac6bdcb3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 15 Aug 2016 19:56:01 -0400 Subject: [PATCH 03/15] Ensured ROMs survive in the new memory model. --- Machines/Commodore/Vic-20/Vic20.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 380009ba9..91d590fb5 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -63,7 +63,7 @@ void Machine::set_memory_size(MemorySize size) break; } - // install the ROMs and VIC-visible memory + // install the system ROMs and VIC-visible memory write_to_map(_processorReadMemoryMap, _userBASICMemory, 0x0000, sizeof(_userBASICMemory)); write_to_map(_processorReadMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory)); write_to_map(_processorReadMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory)); @@ -74,6 +74,12 @@ void Machine::set_memory_size(MemorySize size) write_to_map(_processorWriteMemoryMap, _userBASICMemory, 0x0000, sizeof(_userBASICMemory)); write_to_map(_processorWriteMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory)); write_to_map(_processorWriteMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory)); + + // install the inserted ROM if there is one + if(_rom) + { + write_to_map(_processorReadMemoryMap, _rom, _rom_address, _rom_length); + } } void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) @@ -225,12 +231,9 @@ void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) { _rom_address = (uint16_t)(data[0] | (data[1] << 8)); _rom_length = (uint16_t)(length - 2); - if(_rom_address >= 0x1000 && _rom_address+_rom_length < 0x2000 && _should_automatically_load_media) - { - set_typer_for_string("RUN\n"); - } - if(_rom_address == 0xa000) + // install in the ROM area if this looks like a ROM; otherwise put on tape and throw into that mechanism + if(_rom_address == 0xa000 && _rom_length == 0x1000) { _rom = new uint8_t[length - 2]; memcpy(_rom, &data[2], length - 2); @@ -238,7 +241,6 @@ void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) } else { - // if it's not a ROM then insert it as a tape set_tape(std::shared_ptr(new Storage::TapePRG(file_name))); } } From 7e2b4554eacccec3d0db226741bba77439d0253b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 15 Aug 2016 20:08:50 -0400 Subject: [PATCH 04/15] This likely forms the Commodore dipoles correctly. It just stays stuck in leader tone forever is all. --- Storage/Tape/Formats/TapePRG.cpp | 27 +++++++++++++++++++++++++-- Storage/Tape/Formats/TapePRG.hpp | 14 ++++++++------ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index 07d89ee2f..6c1370cdf 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -12,7 +12,7 @@ using namespace Storage; -TapePRG::TapePRG(const char *file_name) : _file(nullptr) +TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3) { struct stat file_stats; stat(file_name, &file_stats); @@ -39,11 +39,34 @@ TapePRG::~TapePRG() Tape::Pulse TapePRG::get_next_pulse() { + static const unsigned int leader_zero_length = 179; + static const unsigned int zero_length = 169; + static const unsigned int one_length = 247; + static const unsigned int word_marker_length = 328; + + _bitPhase = (_bitPhase+1)&3; + if(!_bitPhase) get_next_output_token(); + Tape::Pulse pulse; + switch(_outputToken) + { + case Leader: pulse.length.length = leader_zero_length; break; + case Zero: pulse.length.length = (_bitPhase&2) ? one_length : zero_length; break; + case One: pulse.length.length = (_bitPhase&2) ? zero_length : one_length; break; + case WordMarker: pulse.length.length = (_bitPhase&2) ? one_length : word_marker_length; break; + } + pulse.length.clock_rate = 1000000; + pulse.type = (_bitPhase&1) ? Pulse::Low : Pulse::High; return pulse; } void TapePRG::reset() { - // TODO + _bitPhase = 3; + fseek(_file, 2, SEEK_SET); +} + +void TapePRG::get_next_output_token() +{ + _outputToken = Leader; } diff --git a/Storage/Tape/Formats/TapePRG.hpp b/Storage/Tape/Formats/TapePRG.hpp index 1aaabfc80..44493437f 100644 --- a/Storage/Tape/Formats/TapePRG.hpp +++ b/Storage/Tape/Formats/TapePRG.hpp @@ -45,12 +45,14 @@ class TapePRG: public Tape { FilePhaseHeader } _filePhase; - enum BitPhase { - BitPhase0, - BitPhase1, - BitPhase2, - BitPhase3 - } _bitPhase; + int _bitPhase; + enum OutputToken { + Leader, + Zero, + One, + WordMarker + } _outputToken; + void get_next_output_token(); }; } From 7994148f558acbdaccd8e1c50ad41ad6727fa1e1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 15 Aug 2016 22:10:53 -0400 Subject: [PATCH 05/15] I'm starting to make a little headway, I think: this performs lead-ins and countdowns, though with no actual data or anything as helpful as that. --- Storage/Tape/Formats/TapePRG.cpp | 54 ++++++++++++++++++++++++++++++-- Storage/Tape/Formats/TapePRG.hpp | 7 +++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index 6c1370cdf..cb648e3c6 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -12,7 +12,7 @@ using namespace Storage; -TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3) +TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePhase(FilePhaseLeadIn), _phaseOffset(0) { struct stat file_stats; stat(file_name, &file_stats); @@ -64,9 +64,59 @@ void TapePRG::reset() { _bitPhase = 3; fseek(_file, 2, SEEK_SET); + _filePhase = FilePhaseLeadIn; + _phaseOffset = 0; } void TapePRG::get_next_output_token() { - _outputToken = Leader; + // the lead-in is 20,000 instances of the lead-in pair; every other phase begins with 5000 + // before doing whatever it should be doing + if(_filePhase == FilePhaseLeadIn || _phaseOffset < 5000) + { + _outputToken = Leader; + _phaseOffset++; + if(_filePhase == FilePhaseLeadIn && _phaseOffset == 20000) + { + _phaseOffset = 0; + _filePhase = FilePhaseHeader; + } + return; + } + + // determine whether a new byte needs to be queued up + int block_offset = _phaseOffset - 5000; + int bit_offset = block_offset % 10; + int byte_offset = block_offset / 10; + + if(bit_offset == 0) + { + // the first nine bytes are countdown; the high bit is set if this is a header + if(byte_offset < 9) + { + _output_byte = (uint8_t)(9 - block_offset) | 0x80; + } + else + { + } + } + + switch(bit_offset) + { + case 0: + _outputToken = WordMarker; + break; + default: + _outputToken = (_output_byte & (1 << (bit_offset - 1))) ? One : Zero; + break; + case 1: + { + uint8_t parity = _outputToken; + parity ^= (parity >> 4); + parity ^= (parity >> 2); + parity ^= (parity >> 1); + _outputToken = (parity&1) ? One : Zero; + } + break; + } } diff --git a/Storage/Tape/Formats/TapePRG.hpp b/Storage/Tape/Formats/TapePRG.hpp index 44493437f..100982f82 100644 --- a/Storage/Tape/Formats/TapePRG.hpp +++ b/Storage/Tape/Formats/TapePRG.hpp @@ -42,17 +42,20 @@ class TapePRG: public Tape { enum FilePhase { FilePhaseLeadIn, - FilePhaseHeader + FilePhaseHeader, + FilePhaseData, } _filePhase; + int _phaseOffset; int _bitPhase; enum OutputToken { Leader, Zero, One, - WordMarker + WordMarker, } _outputToken; void get_next_output_token(); + uint8_t _output_byte; }; } From dbe47f3f45133e7c273cd49513cf4f455f5813e3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 15 Aug 2016 22:40:05 -0400 Subject: [PATCH 06/15] I've obviously misunderstood something as, as far as I'm concerned, this should at least get me a 'LOADING'. --- Storage/Tape/Formats/TapePRG.cpp | 56 ++++++++++++++++++++++++++++---- Storage/Tape/Formats/TapePRG.hpp | 3 ++ 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index cb648e3c6..2fc74b9e9 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -27,8 +27,9 @@ TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePha _load_address = (uint16_t)fgetc(_file); _load_address |= (uint16_t)fgetc(_file) << 8; + _length = (uint16_t)(file_stats.st_size - 2); - if (_load_address + file_stats.st_size >= 65536) + if (_load_address + _length >= 65536) throw ErrorBadFormat; } @@ -42,7 +43,7 @@ Tape::Pulse TapePRG::get_next_pulse() static const unsigned int leader_zero_length = 179; static const unsigned int zero_length = 169; static const unsigned int one_length = 247; - static const unsigned int word_marker_length = 328; + static const unsigned int marker_length = 328; _bitPhase = (_bitPhase+1)&3; if(!_bitPhase) get_next_output_token(); @@ -50,10 +51,11 @@ Tape::Pulse TapePRG::get_next_pulse() Tape::Pulse pulse; switch(_outputToken) { - case Leader: pulse.length.length = leader_zero_length; break; - case Zero: pulse.length.length = (_bitPhase&2) ? one_length : zero_length; break; - case One: pulse.length.length = (_bitPhase&2) ? zero_length : one_length; break; - case WordMarker: pulse.length.length = (_bitPhase&2) ? one_length : word_marker_length; break; + case Leader: pulse.length.length = leader_zero_length; break; + case Zero: pulse.length.length = (_bitPhase&2) ? one_length : zero_length; break; + case One: pulse.length.length = (_bitPhase&2) ? zero_length : one_length; break; + case WordMarker: pulse.length.length = (_bitPhase&2) ? one_length : marker_length; break; + case EndOfBlock: pulse.length.length = (_bitPhase&2) ? zero_length : marker_length; break; } pulse.length.clock_rate = 1000000; pulse.type = (_bitPhase&1) ? Pulse::Low : Pulse::High; @@ -89,16 +91,56 @@ void TapePRG::get_next_output_token() int bit_offset = block_offset % 10; int byte_offset = block_offset / 10; + if(byte_offset == 200) + { + _phaseOffset = 0; + _filePhase = FilePhaseData; // TODO + _outputToken = EndOfBlock; + return; + } + + _phaseOffset++; if(bit_offset == 0) { // the first nine bytes are countdown; the high bit is set if this is a header if(byte_offset < 9) { - _output_byte = (uint8_t)(9 - block_offset) | 0x80; + _output_byte = (uint8_t)(9 - byte_offset) | 0x80; + _check_digit = 0; + } + else if(byte_offset == 199) + { + _output_byte = _check_digit; } else { + if(_filePhase == FilePhaseHeader) + { + switch(byte_offset - 9) + { + case 0: _output_byte = 0x03; break; + case 1: _output_byte = _load_address & 0xff; break; + case 2: _output_byte = (_load_address >> 8)&0xff; break; + case 3: _output_byte = (_load_address + _length) & 0xff; break; + case 4: _output_byte = ((_load_address + _length) >> 8) & 0xff; break; + + case 5: _output_byte = 0x50; break; // P + case 6: _output_byte = 0x72; break; // R + case 7: _output_byte = 0x67; break; // G + default: + _output_byte = 0x20; + break; + } + } + else + { + _output_byte = (uint8_t)fgetc(_file); + if(feof(_file)) _output_byte = 0x20; + } } + + printf("%02x ", _output_byte); + _check_digit ^= _output_byte; } switch(bit_offset) diff --git a/Storage/Tape/Formats/TapePRG.hpp b/Storage/Tape/Formats/TapePRG.hpp index 100982f82..d05f8f3e3 100644 --- a/Storage/Tape/Formats/TapePRG.hpp +++ b/Storage/Tape/Formats/TapePRG.hpp @@ -39,6 +39,7 @@ class TapePRG: public Tape { private: FILE *_file; uint16_t _load_address; + uint16_t _length; enum FilePhase { FilePhaseLeadIn, @@ -53,9 +54,11 @@ class TapePRG: public Tape { Zero, One, WordMarker, + EndOfBlock } _outputToken; void get_next_output_token(); uint8_t _output_byte; + uint8_t _check_digit; }; } From 3a23d5d8cf096654f9cdf1f9dbca8a92f46e3b66 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 15 Aug 2016 22:44:36 -0400 Subject: [PATCH 07/15] Adjusted the check digit. --- Storage/Tape/Formats/TapePRG.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index 2fc74b9e9..750ac6ade 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -106,7 +106,6 @@ void TapePRG::get_next_output_token() if(byte_offset < 9) { _output_byte = (uint8_t)(9 - byte_offset) | 0x80; - _check_digit = 0; } else if(byte_offset == 199) { @@ -114,6 +113,7 @@ void TapePRG::get_next_output_token() } else { + if(byte_offset == 9) _check_digit = 0; if(_filePhase == FilePhaseHeader) { switch(byte_offset - 9) @@ -137,10 +137,11 @@ void TapePRG::get_next_output_token() _output_byte = (uint8_t)fgetc(_file); if(feof(_file)) _output_byte = 0x20; } + + _check_digit ^= _output_byte; } printf("%02x ", _output_byte); - _check_digit ^= _output_byte; } switch(bit_offset) From 12f8aff65b43cc67241378818076eb0eaa6c066a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Aug 2016 19:46:53 -0400 Subject: [PATCH 08/15] Lengths I'd taken seem to have been for dipoles, not single poles. So I just doubled the clock rate. Also I was producing each dipole as high then low, when they should probably be low then high. The Vic now at least recognises that something is happening on the tape. --- Machines/Commodore/Vic-20/Vic20.cpp | 2 +- Storage/Tape/Formats/TapePRG.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 91d590fb5..d330e224e 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -102,7 +102,7 @@ Machine::~Machine() unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { // static int logCount = 0; -// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xee17) logCount = 500; +// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xf957) logCount = 500; // if(operation == CPU6502::BusOperation::ReadOpcode && logCount) { // logCount--; // printf("%04x\n", address); diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index 750ac6ade..9fe5fca87 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -57,8 +57,8 @@ Tape::Pulse TapePRG::get_next_pulse() case WordMarker: pulse.length.length = (_bitPhase&2) ? one_length : marker_length; break; case EndOfBlock: pulse.length.length = (_bitPhase&2) ? zero_length : marker_length; break; } - pulse.length.clock_rate = 1000000; - pulse.type = (_bitPhase&1) ? Pulse::Low : Pulse::High; + pulse.length.clock_rate = 2000000; + pulse.type = (_bitPhase&1) ? Pulse::High : Pulse::Low; return pulse; } @@ -125,8 +125,8 @@ void TapePRG::get_next_output_token() case 4: _output_byte = ((_load_address + _length) >> 8) & 0xff; break; case 5: _output_byte = 0x50; break; // P - case 6: _output_byte = 0x72; break; // R - case 7: _output_byte = 0x67; break; // G + case 6: _output_byte = 0x52; break; // R + case 7: _output_byte = 0x47; break; // G default: _output_byte = 0x20; break; From dfe9fb83efdb17cf4a1e8e4f0e767fd256eb0f02 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Aug 2016 21:09:50 -0400 Subject: [PATCH 09/15] This proves that bytes are being deposited properly. For the first 36 anyway, and with no announcement. --- Machines/Commodore/Vic-20/Vic20.cpp | 5 +++++ Storage/Tape/Formats/TapePRG.cpp | 25 +++++++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index d330e224e..dde517942 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -108,6 +108,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // printf("%04x\n", address); // } + if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192)) + { + printf("\n[%04x] <- %02x\n", address, *value); + } + // run the phase-1 part of this cycle, in which the VIC accesses memory _mos6560->run_for_cycles(1); diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index 9fe5fca87..e684fc33c 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -40,6 +40,7 @@ TapePRG::~TapePRG() Tape::Pulse TapePRG::get_next_pulse() { + // these are all microseconds per pole static const unsigned int leader_zero_length = 179; static const unsigned int zero_length = 169; static const unsigned int one_length = 247; @@ -57,7 +58,7 @@ Tape::Pulse TapePRG::get_next_pulse() case WordMarker: pulse.length.length = (_bitPhase&2) ? one_length : marker_length; break; case EndOfBlock: pulse.length.length = (_bitPhase&2) ? zero_length : marker_length; break; } - pulse.length.clock_rate = 2000000; + pulse.length.clock_rate = 1000000; pulse.type = (_bitPhase&1) ? Pulse::High : Pulse::Low; return pulse; } @@ -72,6 +73,9 @@ void TapePRG::reset() void TapePRG::get_next_output_token() { + static const int block_length = 192; // not counting the checksum + static const int countdown_bytes = 9; + // the lead-in is 20,000 instances of the lead-in pair; every other phase begins with 5000 // before doing whatever it should be doing if(_filePhase == FilePhaseLeadIn || _phaseOffset < 5000) @@ -90,33 +94,34 @@ void TapePRG::get_next_output_token() int block_offset = _phaseOffset - 5000; int bit_offset = block_offset % 10; int byte_offset = block_offset / 10; + _phaseOffset++; - if(byte_offset == 200) + if(byte_offset == block_length + countdown_bytes + 1) // i.e. after the checksum { _phaseOffset = 0; _filePhase = FilePhaseData; // TODO _outputToken = EndOfBlock; + printf("\n===\n"); return; } - _phaseOffset++; if(bit_offset == 0) { // the first nine bytes are countdown; the high bit is set if this is a header - if(byte_offset < 9) + if(byte_offset < countdown_bytes) { - _output_byte = (uint8_t)(9 - byte_offset) | 0x80; + _output_byte = (uint8_t)(countdown_bytes - byte_offset) | 0x80; } - else if(byte_offset == 199) + else if(byte_offset == countdown_bytes + block_length) { _output_byte = _check_digit; } else { - if(byte_offset == 9) _check_digit = 0; + if(byte_offset == countdown_bytes) _check_digit = 0; if(_filePhase == FilePhaseHeader) { - switch(byte_offset - 9) + switch(byte_offset - countdown_bytes) { case 0: _output_byte = 0x03; break; case 1: _output_byte = _load_address & 0xff; break; @@ -149,10 +154,10 @@ void TapePRG::get_next_output_token() case 0: _outputToken = WordMarker; break; - default: + default: // i.e. 1–8 _outputToken = (_output_byte & (1 << (bit_offset - 1))) ? One : Zero; break; - case 1: + case 9: { uint8_t parity = _outputToken; parity ^= (parity >> 4); From 1bca9aa2bb9bab9755cc7b2763cf25475d6b220f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Aug 2016 21:41:09 -0400 Subject: [PATCH 10/15] Fixed parity: now calculated from the actual byte and works the other way around. The Vic now believes it is loading the actual program. So I guess bytes, headers and the lead-in is working. --- Storage/Tape/Formats/TapePRG.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index e684fc33c..e3bdf3297 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -146,7 +146,7 @@ void TapePRG::get_next_output_token() _check_digit ^= _output_byte; } - printf("%02x ", _output_byte); + printf(" %02x", _output_byte); } switch(bit_offset) @@ -159,11 +159,12 @@ void TapePRG::get_next_output_token() break; case 9: { - uint8_t parity = _outputToken; + uint8_t parity = _output_byte; parity ^= (parity >> 4); parity ^= (parity >> 2); parity ^= (parity >> 1); - _outputToken = (parity&1) ? One : Zero; + _outputToken = (parity&1) ? Zero : One; + printf("[%d]", parity&1); } break; } From e5523dbbed72edb4e225a5a1e70aefafcf8932cc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Aug 2016 22:17:08 -0400 Subject: [PATCH 11/15] This gets through loading the first(/only?) copy. I tried repeating it with a bit-flipped header, now I'm trying repeating the header with a different file type. More documentation searching to do, I guess. --- Storage/Tape/Formats/TapePRG.cpp | 24 +++++++++++++++++++----- Storage/Tape/Formats/TapePRG.hpp | 2 ++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index e3bdf3297..02c73f966 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -12,7 +12,7 @@ using namespace Storage; -TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePhase(FilePhaseLeadIn), _phaseOffset(0) +TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePhase(FilePhaseLeadIn), _phaseOffset(0), _file_type(0x03) { struct stat file_stats; stat(file_name, &file_stats); @@ -29,6 +29,9 @@ TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePha _load_address |= (uint16_t)fgetc(_file) << 8; _length = (uint16_t)(file_stats.st_size - 2); + fseek(_file, 2, SEEK_SET); + + if (_load_address + _length >= 65536) throw ErrorBadFormat; } @@ -69,6 +72,7 @@ void TapePRG::reset() fseek(_file, 2, SEEK_SET); _filePhase = FilePhaseLeadIn; _phaseOffset = 0; + _file_type = 0x03; } void TapePRG::get_next_output_token() @@ -98,9 +102,19 @@ void TapePRG::get_next_output_token() if(byte_offset == block_length + countdown_bytes + 1) // i.e. after the checksum { - _phaseOffset = 0; - _filePhase = FilePhaseData; // TODO _outputToken = EndOfBlock; + + _phaseOffset = 0; + if(feof(_file)) + { + _file_type ^= 0x5 ^ 0x3; + _filePhase = (_file_type == 0x5) ? FilePhaseHeader : FilePhaseLeadIn; + fseek(_file, 2, SEEK_SET); + } + else + { + _filePhase = FilePhaseData; + } printf("\n===\n"); return; } @@ -123,7 +137,7 @@ void TapePRG::get_next_output_token() { switch(byte_offset - countdown_bytes) { - case 0: _output_byte = 0x03; break; + case 0: _output_byte = _file_type; break; case 1: _output_byte = _load_address & 0xff; break; case 2: _output_byte = (_load_address >> 8)&0xff; break; case 3: _output_byte = (_load_address + _length) & 0xff; break; @@ -140,7 +154,7 @@ void TapePRG::get_next_output_token() else { _output_byte = (uint8_t)fgetc(_file); - if(feof(_file)) _output_byte = 0x20; + if(feof(_file)) _output_byte = 0x00; } _check_digit ^= _output_byte; diff --git a/Storage/Tape/Formats/TapePRG.hpp b/Storage/Tape/Formats/TapePRG.hpp index d05f8f3e3..a13d9254f 100644 --- a/Storage/Tape/Formats/TapePRG.hpp +++ b/Storage/Tape/Formats/TapePRG.hpp @@ -59,6 +59,8 @@ class TapePRG: public Tape { void get_next_output_token(); uint8_t _output_byte; uint8_t _check_digit; +// uint8_t _copy_mask; + uint8_t _file_type; }; } From 2935848f35a4778e61b93d233baee49175c3c238 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Aug 2016 08:03:34 -0400 Subject: [PATCH 12/15] Adopted header/header/data/data pattern. But still not complete joy. --- Machines/Commodore/Vic-20/Vic20.cpp | 2 +- Storage/Tape/Formats/TapePRG.cpp | 45 ++++++++++++++++++----------- Storage/Tape/Formats/TapePRG.hpp | 4 +-- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index dde517942..a0fb7ad28 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -238,7 +238,7 @@ void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) _rom_length = (uint16_t)(length - 2); // install in the ROM area if this looks like a ROM; otherwise put on tape and throw into that mechanism - if(_rom_address == 0xa000 && _rom_length == 0x1000) + if(_rom_address == 0xa000 && (_rom_length == 0x1000 || _rom_length == 0x2000)) { _rom = new uint8_t[length - 2]; memcpy(_rom, &data[2], length - 2); diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index 02c73f966..d511c4b69 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -12,7 +12,7 @@ using namespace Storage; -TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePhase(FilePhaseLeadIn), _phaseOffset(0), _file_type(0x03) +TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePhase(FilePhaseLeadIn), _phaseOffset(0), _copy_mask(0x80) { struct stat file_stats; stat(file_name, &file_stats); @@ -72,7 +72,7 @@ void TapePRG::reset() fseek(_file, 2, SEEK_SET); _filePhase = FilePhaseLeadIn; _phaseOffset = 0; - _file_type = 0x03; + _copy_mask = 0x80; } void TapePRG::get_next_output_token() @@ -82,20 +82,23 @@ void TapePRG::get_next_output_token() // the lead-in is 20,000 instances of the lead-in pair; every other phase begins with 5000 // before doing whatever it should be doing - if(_filePhase == FilePhaseLeadIn || _phaseOffset < 5000) + if((_filePhase == FilePhaseLeadIn || _filePhase == FilePhaseHeaderDataGap) || _phaseOffset < 50) { _outputToken = Leader; _phaseOffset++; - if(_filePhase == FilePhaseLeadIn && _phaseOffset == 20000) + if( + (_filePhase == FilePhaseLeadIn && _phaseOffset == 20000) || + (_filePhase == FilePhaseHeaderDataGap && _phaseOffset == 5586) + ) { _phaseOffset = 0; - _filePhase = FilePhaseHeader; + _filePhase = (_filePhase == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData; } return; } // determine whether a new byte needs to be queued up - int block_offset = _phaseOffset - 5000; + int block_offset = _phaseOffset - 50; int bit_offset = block_offset % 10; int byte_offset = block_offset / 10; _phaseOffset++; @@ -103,17 +106,25 @@ void TapePRG::get_next_output_token() if(byte_offset == block_length + countdown_bytes + 1) // i.e. after the checksum { _outputToken = EndOfBlock; - _phaseOffset = 0; - if(feof(_file)) + + switch(_filePhase) { - _file_type ^= 0x5 ^ 0x3; - _filePhase = (_file_type == 0x5) ? FilePhaseHeader : FilePhaseLeadIn; - fseek(_file, 2, SEEK_SET); - } - else - { - _filePhase = FilePhaseData; + default: break; +// case FilePhaseLeadIn: +// _filePhase = FilePhaseHeader; +// break; + case FilePhaseHeader: + _copy_mask ^= 0x80; + if(_copy_mask) _filePhase = FilePhaseHeaderDataGap; + break; + case FilePhaseData: + if(feof(_file)) + { + _copy_mask ^= 0x80; + fseek(_file, 2, SEEK_SET); + } + break; } printf("\n===\n"); return; @@ -124,7 +135,7 @@ void TapePRG::get_next_output_token() // the first nine bytes are countdown; the high bit is set if this is a header if(byte_offset < countdown_bytes) { - _output_byte = (uint8_t)(countdown_bytes - byte_offset) | 0x80; + _output_byte = (uint8_t)(countdown_bytes - byte_offset) | _copy_mask; } else if(byte_offset == countdown_bytes + block_length) { @@ -137,7 +148,7 @@ void TapePRG::get_next_output_token() { switch(byte_offset - countdown_bytes) { - case 0: _output_byte = _file_type; break; + case 0: _output_byte = 0x03; break; case 1: _output_byte = _load_address & 0xff; break; case 2: _output_byte = (_load_address >> 8)&0xff; break; case 3: _output_byte = (_load_address + _length) & 0xff; break; diff --git a/Storage/Tape/Formats/TapePRG.hpp b/Storage/Tape/Formats/TapePRG.hpp index a13d9254f..51fca91d7 100644 --- a/Storage/Tape/Formats/TapePRG.hpp +++ b/Storage/Tape/Formats/TapePRG.hpp @@ -44,6 +44,7 @@ class TapePRG: public Tape { enum FilePhase { FilePhaseLeadIn, FilePhaseHeader, + FilePhaseHeaderDataGap, FilePhaseData, } _filePhase; int _phaseOffset; @@ -59,8 +60,7 @@ class TapePRG: public Tape { void get_next_output_token(); uint8_t _output_byte; uint8_t _check_digit; -// uint8_t _copy_mask; - uint8_t _file_type; + uint8_t _copy_mask; }; } From 902c0f967a463b6bc033492ec0eb08c43e138d1d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Aug 2016 08:09:48 -0400 Subject: [PATCH 13/15] Implements known phase pattern for PAL output. --- Components/6560/6560.hpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index c09518130..3b14c40be 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -48,7 +48,8 @@ template class MOS6560 { _horizontal_counter(0), _vertical_counter(0), _cycles_since_speaker_update(0), - _is_odd_frame(false) + _is_odd_frame(false), + _is_odd_line(false) { _crt->set_composite_sampling_function( "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" @@ -82,6 +83,7 @@ template class MOS6560 { */ void set_output_mode(OutputMode output_mode) { + _output_mode = output_mode; uint8_t luminances[16] = { // range is 0–4 0, 4, 1, 3, 2, 2, 1, 3, 2, 1, 2, 1, 2, 3, 2, 3 @@ -160,6 +162,7 @@ template class MOS6560 { } _horizontal_counter = 0; + if(_output_mode == OutputMode::PAL) _is_odd_line ^= true; _horizontal_drawing_latch = false; _vertical_counter ++; @@ -168,7 +171,7 @@ template class MOS6560 { _vertical_counter = 0; _full_frame_counter = 0; - _is_odd_frame ^= true; + if(_output_mode == OutputMode::NTSC) _is_odd_frame ^= true; _current_row = 0; _rows_this_field = -1; _vertical_drawing_latch = false; @@ -250,10 +253,10 @@ template class MOS6560 { { switch(_output_state) { - case State::Sync: _crt->output_sync(_cycles_in_state * 4); break; - case State::ColourBurst: _crt->output_colour_burst(_cycles_in_state * 4, _is_odd_frame ? 128 : 0, 0); break; - case State::Border: output_border(_cycles_in_state * 4); break; - case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break; + case State::Sync: _crt->output_sync(_cycles_in_state * 4); break; + case State::ColourBurst: _crt->output_colour_burst(_cycles_in_state * 4, (_is_odd_frame || _is_odd_line) ? 128 : 0, 0); break; + case State::Border: output_border(_cycles_in_state * 4); break; + case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break; } _output_state = _this_state; _cycles_in_state = 0; @@ -456,7 +459,7 @@ template class MOS6560 { // data latched from the bus uint8_t _character_code, _character_colour, _character_value; - bool _is_odd_frame; + bool _is_odd_frame, _is_odd_line; // lookup table from 6560 colour index to appropriate PAL/NTSC value uint8_t _colours[16]; @@ -475,6 +478,7 @@ template class MOS6560 { int lines_per_progressive_field; bool supports_interlacing; } _timing; + OutputMode _output_mode; }; } From 4edd1214f1ce4701e93154c9b12e80d21745a404 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Aug 2016 10:58:42 -0400 Subject: [PATCH 14/15] This has now successfully loaded its first PRG-as-a-tape. --- Machines/Commodore/Vic-20/Vic20.cpp | 14 ++--- Storage/Tape/Formats/TapePRG.cpp | 93 ++++++++++++++++------------- Storage/Tape/Formats/TapePRG.hpp | 3 +- 3 files changed, 60 insertions(+), 50 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index a0fb7ad28..93edde52d 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -108,10 +108,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // printf("%04x\n", address); // } - if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192)) - { - printf("\n[%04x] <- %02x\n", address, *value); - } +// if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192)) +// { +// printf("\n[%04x] <- %02x\n", address, *value); +// } // run the phase-1 part of this cycle, in which the VIC accesses memory _mos6560->run_for_cycles(1); @@ -238,11 +238,11 @@ void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) _rom_length = (uint16_t)(length - 2); // install in the ROM area if this looks like a ROM; otherwise put on tape and throw into that mechanism - if(_rom_address == 0xa000 && (_rom_length == 0x1000 || _rom_length == 0x2000)) + if(_rom_address == 0xa000) { - _rom = new uint8_t[length - 2]; + _rom = new uint8_t[0x2000]; memcpy(_rom, &data[2], length - 2); - write_to_map(_processorReadMemoryMap, _rom, _rom_address, _rom_length); + write_to_map(_processorReadMemoryMap, _rom, _rom_address, 0x2000); } else { diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index d511c4b69..5b121eaf9 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -29,9 +29,6 @@ TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePha _load_address |= (uint16_t)fgetc(_file) << 8; _length = (uint16_t)(file_stats.st_size - 2); - fseek(_file, 2, SEEK_SET); - - if (_load_address + _length >= 65536) throw ErrorBadFormat; } @@ -53,6 +50,8 @@ Tape::Pulse TapePRG::get_next_pulse() if(!_bitPhase) get_next_output_token(); Tape::Pulse pulse; + pulse.length.clock_rate = 1000000; + pulse.type = (_bitPhase&1) ? Pulse::High : Pulse::Low; switch(_outputToken) { case Leader: pulse.length.length = leader_zero_length; break; @@ -60,9 +59,8 @@ Tape::Pulse TapePRG::get_next_pulse() case One: pulse.length.length = (_bitPhase&2) ? zero_length : one_length; break; case WordMarker: pulse.length.length = (_bitPhase&2) ? one_length : marker_length; break; case EndOfBlock: pulse.length.length = (_bitPhase&2) ? zero_length : marker_length; break; + case Silence: pulse.type = Pulse::Zero; pulse.length.length = 5000; break; } - pulse.length.clock_rate = 1000000; - pulse.type = (_bitPhase&1) ? Pulse::High : Pulse::Low; return pulse; } @@ -79,17 +77,23 @@ void TapePRG::get_next_output_token() { static const int block_length = 192; // not counting the checksum static const int countdown_bytes = 9; + static const int leadin_length = 20000; + static const int block_leadin_length = 5000; + + if(_filePhase == FilePhaseHeaderDataGap) + { + _outputToken = Silence; + _filePhase = FilePhaseData; + return; + } // the lead-in is 20,000 instances of the lead-in pair; every other phase begins with 5000 // before doing whatever it should be doing - if((_filePhase == FilePhaseLeadIn || _filePhase == FilePhaseHeaderDataGap) || _phaseOffset < 50) + if(_filePhase == FilePhaseLeadIn || _phaseOffset < block_leadin_length) { _outputToken = Leader; _phaseOffset++; - if( - (_filePhase == FilePhaseLeadIn && _phaseOffset == 20000) || - (_filePhase == FilePhaseHeaderDataGap && _phaseOffset == 5586) - ) + if(_filePhase == FilePhaseLeadIn && _phaseOffset == leadin_length) { _phaseOffset = 0; _filePhase = (_filePhase == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData; @@ -98,12 +102,17 @@ void TapePRG::get_next_output_token() } // determine whether a new byte needs to be queued up - int block_offset = _phaseOffset - 50; + int block_offset = _phaseOffset - block_leadin_length; int bit_offset = block_offset % 10; int byte_offset = block_offset / 10; _phaseOffset++; - if(byte_offset == block_length + countdown_bytes + 1) // i.e. after the checksum + if(!bit_offset && + ( + (_filePhase == FilePhaseHeader && byte_offset == block_length + countdown_bytes + 1) || + feof(_file) + ) + ) { _outputToken = EndOfBlock; _phaseOffset = 0; @@ -111,22 +120,16 @@ void TapePRG::get_next_output_token() switch(_filePhase) { default: break; -// case FilePhaseLeadIn: -// _filePhase = FilePhaseHeader; -// break; case FilePhaseHeader: _copy_mask ^= 0x80; if(_copy_mask) _filePhase = FilePhaseHeaderDataGap; break; case FilePhaseData: - if(feof(_file)) - { - _copy_mask ^= 0x80; - fseek(_file, 2, SEEK_SET); - } + _copy_mask ^= 0x80; + fseek(_file, 2, SEEK_SET); + if(_copy_mask) reset(); break; } - printf("\n===\n"); return; } @@ -137,41 +140,48 @@ void TapePRG::get_next_output_token() { _output_byte = (uint8_t)(countdown_bytes - byte_offset) | _copy_mask; } - else if(byte_offset == countdown_bytes + block_length) - { - _output_byte = _check_digit; - } else { - if(byte_offset == countdown_bytes) _check_digit = 0; if(_filePhase == FilePhaseHeader) { - switch(byte_offset - countdown_bytes) + if(byte_offset == countdown_bytes + block_length) { - case 0: _output_byte = 0x03; break; - case 1: _output_byte = _load_address & 0xff; break; - case 2: _output_byte = (_load_address >> 8)&0xff; break; - case 3: _output_byte = (_load_address + _length) & 0xff; break; - case 4: _output_byte = ((_load_address + _length) >> 8) & 0xff; break; + _output_byte = _check_digit; + } + else + { + if(byte_offset == countdown_bytes) _check_digit = 0; + if(_filePhase == FilePhaseHeader) + { + switch(byte_offset - countdown_bytes) + { + case 0: _output_byte = 0x03; break; + case 1: _output_byte = _load_address & 0xff; break; + case 2: _output_byte = (_load_address >> 8)&0xff; break; + case 3: _output_byte = (_load_address + _length) & 0xff; break; + case 4: _output_byte = ((_load_address + _length) >> 8) & 0xff; break; - case 5: _output_byte = 0x50; break; // P - case 6: _output_byte = 0x52; break; // R - case 7: _output_byte = 0x47; break; // G - default: - _output_byte = 0x20; - break; + case 5: _output_byte = 0x50; break; // P + case 6: _output_byte = 0x52; break; // R + case 7: _output_byte = 0x47; break; // G + default: + _output_byte = 0x20; + break; + } + } } } else { _output_byte = (uint8_t)fgetc(_file); - if(feof(_file)) _output_byte = 0x00; + if(feof(_file)) + { + _output_byte = _check_digit; + } } _check_digit ^= _output_byte; } - - printf(" %02x", _output_byte); } switch(bit_offset) @@ -189,7 +199,6 @@ void TapePRG::get_next_output_token() parity ^= (parity >> 2); parity ^= (parity >> 1); _outputToken = (parity&1) ? Zero : One; - printf("[%d]", parity&1); } break; } diff --git a/Storage/Tape/Formats/TapePRG.hpp b/Storage/Tape/Formats/TapePRG.hpp index 51fca91d7..e516a961f 100644 --- a/Storage/Tape/Formats/TapePRG.hpp +++ b/Storage/Tape/Formats/TapePRG.hpp @@ -55,7 +55,8 @@ class TapePRG: public Tape { Zero, One, WordMarker, - EndOfBlock + EndOfBlock, + Silence } _outputToken; void get_next_output_token(); uint8_t _output_byte; From 73ce67bee8c64f4cf8b88d16e6aa238d9c969b3a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Aug 2016 11:04:59 -0400 Subject: [PATCH 15/15] Added some documentation of the intention here. --- Storage/Tape/Formats/TapePRG.cpp | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index 5b121eaf9..cc3f5db04 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -8,6 +8,42 @@ #include "TapePRG.hpp" +/* + My interpretation of Commodore's tape format is such that a PRG is encoded as: + + [long block of lead-in tone] + [short block of lead-in tone] + [count down][header; 192 bytes fixed length] + [short block of lead-in tone] + [count down][copy of header; 192 bytes fixed length] + [gap] + [short block of lead-in tone] + [count down][data; length as in file] + [short block of lead-in tone] + [count down][copy of data] + ... and repeat ... + + Individual bytes are composed of: + + word marker + least significant bit + ... + most significant bit + parity bit + + Both the header and data blocks additionally end with an end-of-block marker. + + Encoding is via square-wave cycles of four lengths, in ascending order: lead-in, zero, one, marker. + + Lead-in tone is always just repetitions of the lead-in wave. + A word marker is a marker wave followed by a one wave. + An end-of-block marker is a marker wave followed by a zero wave. + A zero bit is a zero wave followed by a one wave. + A one bit is a one wave followed by a zero wave. + + Parity is 1 if there are an even number of bits in the byte; 0 otherwise. +*/ + #include using namespace Storage;