From e3daf805644124f2b7cc6f66477c923973228207 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 17 Sep 2016 18:01:00 -0400 Subject: [PATCH 01/51] Added a file for the 1770, at least. --- Components/1770/1770.cpp | 9 +++++++++ Components/1770/1770.hpp | 14 ++++++++++++++ .../Mac/Clock Signal.xcodeproj/project.pbxproj | 14 ++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 Components/1770/1770.cpp create mode 100644 Components/1770/1770.hpp diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp new file mode 100644 index 000000000..2c7ba3426 --- /dev/null +++ b/Components/1770/1770.cpp @@ -0,0 +1,9 @@ +// +// 1770.cpp +// Clock Signal +// +// Created by Thomas Harte on 17/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "1770.hpp" diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp new file mode 100644 index 000000000..3d36b6d5e --- /dev/null +++ b/Components/1770/1770.hpp @@ -0,0 +1,14 @@ +// +// 1770.hpp +// Clock Signal +// +// Created by Thomas Harte on 17/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef _770_hpp +#define _770_hpp + +#include + +#endif /* _770_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 08acf0b6d..e578f9243 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -348,6 +348,7 @@ 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; }; 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; }; + 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; }; 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; }; 4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; }; @@ -791,6 +792,8 @@ 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = ""; }; 4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = ""; }; 4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = ../../StaticAnalyser/TapeParser.hpp; sourceTree = ""; }; + 4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = ""; }; + 4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = ""; }; 4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = ""; }; 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = ""; }; 4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = ""; }; @@ -1561,6 +1564,7 @@ 4BC9DF4B1D04691600F44158 /* 6522 */, 4B1E85791D174DEC001EF87D /* 6532 */, 4BC9DF4C1D04691600F44158 /* 6560 */, + 4BD468F81D8DF4290084958B /* 1770 */, ); name = Components; path = ../../Components; @@ -1594,6 +1598,15 @@ name = Acorn; sourceTree = ""; }; + 4BD468F81D8DF4290084958B /* 1770 */ = { + isa = PBXGroup; + children = ( + 4BD468F51D8DF41D0084958B /* 1770.cpp */, + 4BD468F61D8DF41D0084958B /* 1770.hpp */, + ); + name = 1770; + sourceTree = ""; + }; 4BD5F1961D1352A000631CD1 /* Updater */ = { isa = PBXGroup; children = ( @@ -2084,6 +2097,7 @@ 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */, 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */, 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */, + 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, 4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */, From bcf91de7e9b057492a8df43afc33f8d7d5e4c324 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 13:35:54 -0400 Subject: [PATCH 02/51] Declared support for the Acorn disk files, started hammering out an encoder. --- Components/1770/1770.hpp | 15 +++++- .../Clock Signal.xcodeproj/project.pbxproj | 6 +++ OSBindings/Mac/Clock Signal/Info.plist | 14 ++++++ Storage/Disk/Encodings/CommodoreGCR.hpp | 4 +- Storage/Disk/Encodings/MFM.cpp | 50 +++++++++++++++++++ Storage/Disk/Encodings/MFM.hpp | 37 ++++++++++++++ 6 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 Storage/Disk/Encodings/MFM.cpp create mode 100644 Storage/Disk/Encodings/MFM.hpp diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 3d36b6d5e..32941fc1d 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -9,6 +9,19 @@ #ifndef _770_hpp #define _770_hpp -#include +#include "../../Storage/Disk/DiskDrive.hpp" + +namespace WD { + +class WD1770 { + public: + + void set_drive(std::shared_ptr drive); + void set_is_double_density(bool is_double_density); + void set_register(int address, uint8_t value); + void get_register(int address); +}; + +} #endif /* _770_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index ef1d6073c..f0b7f1e0a 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -355,6 +355,7 @@ 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; }; 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; 4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; }; + 4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -805,6 +806,8 @@ 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = ""; }; 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/StaticAnalyser.cpp; sourceTree = ""; }; 4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = ""; }; + 4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = ""; }; + 4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1400,6 +1403,8 @@ children = ( 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */, 4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */, + 4BF8295B1D8F048B001BAE39 /* MFM.cpp */, + 4BF8295C1D8F048B001BAE39 /* MFM.hpp */, ); name = Encodings; sourceTree = ""; @@ -2091,6 +2096,7 @@ 4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */, 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */, + 4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */, 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */, 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */, 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index cd2e9b763..412ff4196 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -130,6 +130,20 @@ NSDocumentClass $(PRODUCT_MODULE_NAME).Vic20Document + + CFBundleTypeExtensions + + ssd + dsd + adf + adl + adm + + CFBundleTypeName + Electron/BBC Disk Image + CFBundleTypeRole + Viewer + CFBundleExecutable $(EXECUTABLE_NAME) diff --git a/Storage/Disk/Encodings/CommodoreGCR.hpp b/Storage/Disk/Encodings/CommodoreGCR.hpp index 34346e253..9b8a0c055 100644 --- a/Storage/Disk/Encodings/CommodoreGCR.hpp +++ b/Storage/Disk/Encodings/CommodoreGCR.hpp @@ -6,8 +6,8 @@ // Copyright © 2016 Thomas Harte. All rights reserved. // -#ifndef CommodoreGCR_hpp -#define CommodoreGCR_hpp +#ifndef Storage_Disk_Encodings_CommodoreGCR_hpp +#define Storage_Disk_Encodings_CommodoreGCR_hpp #include "../../Storage.hpp" #include diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp new file mode 100644 index 000000000..efb0797b2 --- /dev/null +++ b/Storage/Disk/Encodings/MFM.cpp @@ -0,0 +1,50 @@ +// +// MFM.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "MFM.hpp" + +using namespace Storage::Encodings; + +void Shifter::add_sync() +{ + // i.e. 0100 0100 1000 1001 + output = (uint16_t)((output << 15) | 0x4489); +} + +void MFMShifter::shift(uint8_t input) +{ + uint16_t spread_value = + (uint16_t)( + ((input & 0x01) << 0) | + ((input & 0x02) << 1) | + ((input & 0x04) << 2) | + ((input & 0x08) << 3) | + ((input & 0x10) << 4) | + ((input & 0x20) << 5) | + ((input & 0x40) << 6) | + ((input & 0x80) << 7) + ); + uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (output << 15)); + output = spread_value | ((~or_bits) & 0xaaaa); +} + +void FMShifter::shift(uint8_t input) +{ + output = + (uint16_t)( + ((input & 0x01) << 0) | + ((input & 0x02) << 1) | + ((input & 0x04) << 2) | + ((input & 0x08) << 3) | + ((input & 0x10) << 4) | + ((input & 0x20) << 5) | + ((input & 0x40) << 6) | + ((input & 0x80) << 7) | + 0xaaaa + ); +} diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp new file mode 100644 index 000000000..2e3c8f2c4 --- /dev/null +++ b/Storage/Disk/Encodings/MFM.hpp @@ -0,0 +1,37 @@ +// +// MFM.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Storage_Disk_Encodings_MFM_hpp +#define Storage_Disk_Encodings_MFM_hpp + +#include + +namespace Storage { +namespace Encodings { + +class Shifter { + public: + virtual void shift(uint8_t input) = 0; + void add_sync(); + uint16_t output; +}; + +class MFMShifter: public Shifter { + public: + void shift(uint8_t input); +}; + +class FMShifter: public Shifter { + public: + void shift(uint8_t input); +}; + +} +} + +#endif /* MFM_hpp */ From 0089c830c688e3d2a19e681c6aeb6496c6af8897 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 14:17:06 -0400 Subject: [PATCH 03/51] This possibly correctly encapsultes the lowest level of FM and MFM rules. --- Storage/Disk/Encodings/MFM.cpp | 39 -------------- Storage/Disk/Encodings/MFM.hpp | 93 +++++++++++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 47 deletions(-) diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index efb0797b2..4e5180b23 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -9,42 +9,3 @@ #include "MFM.hpp" using namespace Storage::Encodings; - -void Shifter::add_sync() -{ - // i.e. 0100 0100 1000 1001 - output = (uint16_t)((output << 15) | 0x4489); -} - -void MFMShifter::shift(uint8_t input) -{ - uint16_t spread_value = - (uint16_t)( - ((input & 0x01) << 0) | - ((input & 0x02) << 1) | - ((input & 0x04) << 2) | - ((input & 0x08) << 3) | - ((input & 0x10) << 4) | - ((input & 0x20) << 5) | - ((input & 0x40) << 6) | - ((input & 0x80) << 7) - ); - uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (output << 15)); - output = spread_value | ((~or_bits) & 0xaaaa); -} - -void FMShifter::shift(uint8_t input) -{ - output = - (uint16_t)( - ((input & 0x01) << 0) | - ((input & 0x02) << 1) | - ((input & 0x04) << 2) | - ((input & 0x08) << 3) | - ((input & 0x10) << 4) | - ((input & 0x20) << 5) | - ((input & 0x40) << 6) | - ((input & 0x80) << 7) | - 0xaaaa - ); -} diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index 2e3c8f2c4..3c07e5dd2 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -14,21 +14,98 @@ namespace Storage { namespace Encodings { -class Shifter { +template class Shifter { public: - virtual void shift(uint8_t input) = 0; - void add_sync(); - uint16_t output; + virtual void add_byte(uint8_t input) = 0; + virtual void add_index_address_mark() = 0; + virtual void add_ID_address_mark() = 0; + virtual void add_data_address_mark() = 0; + virtual void add_deleted_data_address_mark() = 0; + + protected: + // override me! + void output_short(uint16_t value); }; -class MFMShifter: public Shifter { +template class MFMShifter: public Shifter { public: - void shift(uint8_t input); + void add_byte(uint8_t input) { + uint16_t spread_value = + (uint16_t)( + ((input & 0x01) << 0) | + ((input & 0x02) << 1) | + ((input & 0x04) << 2) | + ((input & 0x08) << 3) | + ((input & 0x10) << 4) | + ((input & 0x20) << 5) | + ((input & 0x40) << 6) | + ((input & 0x80) << 7) + ); + uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (output_ << 15)); + output_ = spread_value | ((~or_bits) & 0xaaaa); + static_cast(this)->output_short(output_); + } + + void add_index_address_mark() { + static_cast(this)->output_short(output_ = 0x5224); + add_byte(0xfc); + } + + void add_ID_address_mark() { + static_cast(this)->output_short(output_ = 0x4489); + add_byte(0xfe); + } + + void add_data_address_mark() { + static_cast(this)->output_short(output_ = 0x4489); + add_byte(0xfb); + } + + void add_deleted_data_address_mark() { + static_cast(this)->output_short(output_ = 0x4489); + add_byte(0xf8); + } + + private: + uint16_t output_; }; -class FMShifter: public Shifter { +template class FMShifter: public Shifter { public: - void shift(uint8_t input); + void add_byte(uint8_t input) { + static_cast(this)->output_short( + (uint16_t)( + ((input & 0x01) << 1) | + ((input & 0x02) << 2) | + ((input & 0x04) << 3) | + ((input & 0x08) << 4) | + ((input & 0x10) << 5) | + ((input & 0x20) << 6) | + ((input & 0x40) << 7) | + ((input & 0x80) << 8) | + 0x5555 + )); + } + + void add_index_address_mark() { + // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 1011 1011 0101 + static_cast(this)->output_short(0xfbb5); + } + + void add_ID_address_mark() { + // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 1010 1011 1101 + static_cast(this)->output_short(0xfabd); + } + + void add_data_address_mark() { + // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 1010 1001 1111 + static_cast(this)->output_short(0xfa9f); + } + + void add_deleted_data_address_mark() { + // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 1010 1001 0101 + static_cast(this)->output_short(0xfa95); + } }; } From 22eed60d2b5a3f6eff0ccf6d5ed4beff2c6a9ec3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 16:53:21 -0400 Subject: [PATCH 04/51] Started sketching out basic MFM track encoding. Which possibly even means that the shifters don't need to be public? --- Storage/Disk/Encodings/MFM.cpp | 58 +++++++++++++++++++++++++++++++++- Storage/Disk/Encodings/MFM.hpp | 12 +++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 4e5180b23..12a0d7721 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -8,4 +8,60 @@ #include "MFM.hpp" -using namespace Storage::Encodings; +#import "../PCMTrack.hpp" + +using namespace Storage::Encodings::MFM; + +std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector §ors) +{ + class MFMVectorShifter: public MFMShifter { + void output_short(uint16_t value) { + data.push_back(value & 0xff); + data.push_back(value >> 8); + } + + std::vector data; + } shifter; + + // output the index mark + shifter.add_index_address_mark(); + + // add the post-index mark + for(int c = 0; c < 50; c++) shifter.add_byte(0x4e); + + // add sectors + for(const Sector §or : sectors) + { + for(int c = 0; c < 12; c++) shifter.add_byte(0x00); + + shifter.add_ID_address_mark(); + shifter.add_byte(sector.track); + shifter.add_byte(sector.side); + shifter.add_byte(sector.sector); + switch(sector.data.size()) + { + default: shifter.add_byte(0); break; + case 256: shifter.add_byte(1); break; + case 512: shifter.add_byte(2); break; + case 1024: shifter.add_byte(3); break; + case 2048: shifter.add_byte(4); break; + case 4196: shifter.add_byte(5); break; + } + // TODO: CRC of bytes since the track number + + for(int c = 0; c < 22; c++) shifter.add_byte(0x4e); + for(int c = 0; c < 12; c++) shifter.add_byte(0x00); + + shifter.add_data_address_mark(); + for(size_t c = 0; c < sector.data.size(); c++) shifter.add_byte(sector.data[c]); + // TODO: CRC of data + + for(int c = 0; c < 18; c++) shifter.add_byte(0x00); + for(int c = 0; c < 32; c++) shifter.add_byte(0x4e); + } + + // TODO: total size check + + Storage::Disk::PCMSegment segment; + return std::shared_ptr(new Storage::Disk::PCMTrack(std::move(segment))); +} diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index 3c07e5dd2..bd5ca8210 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -10,9 +10,12 @@ #define Storage_Disk_Encodings_MFM_hpp #include +#include +#import "../Disk.hpp" namespace Storage { namespace Encodings { +namespace MFM { template class Shifter { public: @@ -108,6 +111,15 @@ template class FMShifter: public Shifter { } }; +struct Sector { + uint8_t track, side, sector; + std::vector data; +}; + +std::shared_ptr GetMFMTrackWithSectors(const std::vector §ors); +std::shared_ptr GetFMTrackWithSectors(const std::vector §ors); + +} } } From 55a7418cbf652b6578ba3d5677719ac935e3b791 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 17:16:20 -0400 Subject: [PATCH 05/51] Parameterised to perform FM and MFM encodings, at least subject to CRCs still being missing. --- Storage/Disk/Encodings/MFM.cpp | 158 ++++++++++++++++++++++++++++++--- Storage/Disk/Encodings/MFM.hpp | 94 -------------------- 2 files changed, 144 insertions(+), 108 deletions(-) diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 12a0d7721..ef6a9846f 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -12,27 +12,120 @@ using namespace Storage::Encodings::MFM; -std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector §ors) -{ - class MFMVectorShifter: public MFMShifter { - void output_short(uint16_t value) { - data.push_back(value & 0xff); - data.push_back(value >> 8); +template class Shifter { + public: + virtual void add_byte(uint8_t input) = 0; + virtual void add_index_address_mark() = 0; + virtual void add_ID_address_mark() = 0; + virtual void add_data_address_mark() = 0; + virtual void add_deleted_data_address_mark() = 0; + + protected: + // override me! + void output_short(uint16_t value); +}; + +template class MFMShifter: public Shifter { + public: + void add_byte(uint8_t input) { + uint16_t spread_value = + (uint16_t)( + ((input & 0x01) << 0) | + ((input & 0x02) << 1) | + ((input & 0x04) << 2) | + ((input & 0x08) << 3) | + ((input & 0x10) << 4) | + ((input & 0x20) << 5) | + ((input & 0x40) << 6) | + ((input & 0x80) << 7) + ); + uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (output_ << 15)); + output_ = spread_value | ((~or_bits) & 0xaaaa); + static_cast(this)->output_short(output_); } - std::vector data; - } shifter; + void add_index_address_mark() { + static_cast(this)->output_short(output_ = 0x5224); + add_byte(0xfc); + } + + void add_ID_address_mark() { + static_cast(this)->output_short(output_ = 0x4489); + add_byte(0xfe); + } + + void add_data_address_mark() { + static_cast(this)->output_short(output_ = 0x4489); + add_byte(0xfb); + } + + void add_deleted_data_address_mark() { + static_cast(this)->output_short(output_ = 0x4489); + add_byte(0xf8); + } + + private: + uint16_t output_; +}; + +template class FMShifter: public Shifter { + public: + void add_byte(uint8_t input) { + static_cast(this)->output_short( + (uint16_t)( + ((input & 0x01) << 1) | + ((input & 0x02) << 2) | + ((input & 0x04) << 3) | + ((input & 0x08) << 4) | + ((input & 0x10) << 5) | + ((input & 0x20) << 6) | + ((input & 0x40) << 7) | + ((input & 0x80) << 8) | + 0x5555 + )); + } + + void add_index_address_mark() { + // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 1011 1011 0101 + static_cast(this)->output_short(0xfbb5); + } + + void add_ID_address_mark() { + // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 1010 1011 1101 + static_cast(this)->output_short(0xfabd); + } + + void add_data_address_mark() { + // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 1010 1001 1111 + static_cast(this)->output_short(0xfa9f); + } + + void add_deleted_data_address_mark() { + // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 1010 1001 0101 + static_cast(this)->output_short(0xfa95); + } +}; + +template std::shared_ptr + GetTrackWithSectors( + const std::vector §ors, + size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value, + size_t pre_address_mark_bytes, size_t post_address_mark_bytes, + size_t pre_data_mark_bytes, size_t post_data_bytes, + size_t inter_sector_gap) +{ + T shifter; // output the index mark shifter.add_index_address_mark(); // add the post-index mark - for(int c = 0; c < 50; c++) shifter.add_byte(0x4e); + for(int c = 0; c < post_index_address_mark_bytes; c++) shifter.add_byte(post_index_address_mark_value); // add sectors for(const Sector §or : sectors) { - for(int c = 0; c < 12; c++) shifter.add_byte(0x00); + for(int c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00); shifter.add_ID_address_mark(); shifter.add_byte(sector.track); @@ -49,15 +142,15 @@ std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSe } // TODO: CRC of bytes since the track number - for(int c = 0; c < 22; c++) shifter.add_byte(0x4e); - for(int c = 0; c < 12; c++) shifter.add_byte(0x00); + for(int c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(0x4e); + for(int c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00); shifter.add_data_address_mark(); for(size_t c = 0; c < sector.data.size(); c++) shifter.add_byte(sector.data[c]); // TODO: CRC of data - for(int c = 0; c < 18; c++) shifter.add_byte(0x00); - for(int c = 0; c < 32; c++) shifter.add_byte(0x4e); + for(int c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00); + for(int c = 0; c < inter_sector_gap; c++) shifter.add_byte(0x4e); } // TODO: total size check @@ -65,3 +158,40 @@ std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSe Storage::Disk::PCMSegment segment; return std::shared_ptr(new Storage::Disk::PCMTrack(std::move(segment))); } + + +std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector §ors) +{ + struct VectorShifter: public FMShifter { + void output_short(uint16_t value) { + data.push_back(value & 0xff); + data.push_back(value >> 8); + } + std::vector data; + }; + + return GetTrackWithSectors( + sectors, + 16, 0x00, + 6, 0, + 17, 14, + 0); +} + +std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector §ors) +{ + struct VectorShifter: public MFMShifter { + void output_short(uint16_t value) { + data.push_back(value & 0xff); + data.push_back(value >> 8); + } + std::vector data; + }; + + return GetTrackWithSectors( + sectors, + 50, 0x4e, + 12, 22, + 12, 18, + 32); +} diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index bd5ca8210..543896559 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -17,100 +17,6 @@ namespace Storage { namespace Encodings { namespace MFM { -template class Shifter { - public: - virtual void add_byte(uint8_t input) = 0; - virtual void add_index_address_mark() = 0; - virtual void add_ID_address_mark() = 0; - virtual void add_data_address_mark() = 0; - virtual void add_deleted_data_address_mark() = 0; - - protected: - // override me! - void output_short(uint16_t value); -}; - -template class MFMShifter: public Shifter { - public: - void add_byte(uint8_t input) { - uint16_t spread_value = - (uint16_t)( - ((input & 0x01) << 0) | - ((input & 0x02) << 1) | - ((input & 0x04) << 2) | - ((input & 0x08) << 3) | - ((input & 0x10) << 4) | - ((input & 0x20) << 5) | - ((input & 0x40) << 6) | - ((input & 0x80) << 7) - ); - uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (output_ << 15)); - output_ = spread_value | ((~or_bits) & 0xaaaa); - static_cast(this)->output_short(output_); - } - - void add_index_address_mark() { - static_cast(this)->output_short(output_ = 0x5224); - add_byte(0xfc); - } - - void add_ID_address_mark() { - static_cast(this)->output_short(output_ = 0x4489); - add_byte(0xfe); - } - - void add_data_address_mark() { - static_cast(this)->output_short(output_ = 0x4489); - add_byte(0xfb); - } - - void add_deleted_data_address_mark() { - static_cast(this)->output_short(output_ = 0x4489); - add_byte(0xf8); - } - - private: - uint16_t output_; -}; - -template class FMShifter: public Shifter { - public: - void add_byte(uint8_t input) { - static_cast(this)->output_short( - (uint16_t)( - ((input & 0x01) << 1) | - ((input & 0x02) << 2) | - ((input & 0x04) << 3) | - ((input & 0x08) << 4) | - ((input & 0x10) << 5) | - ((input & 0x20) << 6) | - ((input & 0x40) << 7) | - ((input & 0x80) << 8) | - 0x5555 - )); - } - - void add_index_address_mark() { - // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 1011 1011 0101 - static_cast(this)->output_short(0xfbb5); - } - - void add_ID_address_mark() { - // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 1010 1011 1101 - static_cast(this)->output_short(0xfabd); - } - - void add_data_address_mark() { - // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 1010 1001 1111 - static_cast(this)->output_short(0xfa9f); - } - - void add_deleted_data_address_mark() { - // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 1010 1001 0101 - static_cast(this)->output_short(0xfa95); - } -}; - struct Sector { uint8_t track, side, sector; std::vector data; From 180c3df2d4e16059e8038e65c3b504981c4f79c4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 18:33:26 -0400 Subject: [PATCH 06/51] Calling it 'number theory' probably isn't accurate but extracted the CRC stuff and started using it for [M]FM encoding. --- NumberTheory/CRC.cpp | 11 +++ NumberTheory/CRC.hpp | 40 ++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 6 ++ Storage/Disk/Encodings/MFM.cpp | 80 +++++++++++++------ 4 files changed, 111 insertions(+), 26 deletions(-) create mode 100644 NumberTheory/CRC.cpp create mode 100644 NumberTheory/CRC.hpp diff --git a/NumberTheory/CRC.cpp b/NumberTheory/CRC.cpp new file mode 100644 index 000000000..2833155fd --- /dev/null +++ b/NumberTheory/CRC.cpp @@ -0,0 +1,11 @@ +// +// CRC.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "CRC.hpp" + +using namespace NumberTheory; diff --git a/NumberTheory/CRC.hpp b/NumberTheory/CRC.hpp new file mode 100644 index 000000000..a705e3d4c --- /dev/null +++ b/NumberTheory/CRC.hpp @@ -0,0 +1,40 @@ +// +// CRC.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef CRC_hpp +#define CRC_hpp + +#include + +namespace NumberTheory { + +class CRC16 { + public: + CRC16(uint16_t polynomial, uint16_t reset_value) : + reset_value_(reset_value), value_(reset_value), polynomial_(polynomial) {} + + inline void reset() { value_ = reset_value_; } + inline void add(uint8_t value) { + // TODO: go table based + value_ ^= (uint16_t)value << 8; + for(int c = 0; c < 8; c++) + { + uint16_t exclusive_or = (value_&0x8000) ? polynomial_ : 0x0000; + value_ = (uint16_t)(value_ << 1) ^ exclusive_or; + } + } + inline uint16_t get_value() { return value_; } + + private: + uint16_t reset_value_, polynomial_; + uint16_t value_; +}; + +} + +#endif /* CRC_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f0b7f1e0a..3728929e5 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -356,6 +356,7 @@ 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; 4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; }; 4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; }; + 4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -808,6 +809,8 @@ 4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = ""; }; 4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = ""; }; 4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = ""; }; + 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRC.cpp; path = ../../NumberTheory/CRC.cpp; sourceTree = ""; }; + 4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1394,6 +1397,8 @@ isa = PBXGroup; children = ( 4BB697C61D4B558F00248BDF /* Factors.hpp */, + 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */, + 4BF8295F1D8F3C87001BAE39 /* CRC.hpp */, ); name = NumberTheory; sourceTree = ""; @@ -2079,6 +2084,7 @@ 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */, + 4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */, 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index ef6a9846f..bcd586bd5 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -9,6 +9,7 @@ #include "MFM.hpp" #import "../PCMTrack.hpp" +#import "../../../NumberTheory/CRC.hpp" using namespace Storage::Encodings::MFM; @@ -106,6 +107,19 @@ template class FMShifter: public Shifter { } }; +static uint8_t logarithmic_size_for_size(size_t size) +{ + switch(size) + { + default: return 0; + case 256: return 1; + case 512: return 2; + case 1024: return 3; + case 2048: return 4; + case 4196: return 5; + } +} + template std::shared_ptr GetTrackWithSectors( const std::vector §ors, @@ -115,6 +129,7 @@ template std::shared_ptr size_t inter_sector_gap) { T shifter; + NumberTheory::CRC16 crc_generator(0x1021, 0xffff); // output the index mark shifter.add_index_address_mark(); @@ -125,30 +140,46 @@ template std::shared_ptr // add sectors for(const Sector §or : sectors) { + // gap for(int c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00); + // sector header shifter.add_ID_address_mark(); shifter.add_byte(sector.track); shifter.add_byte(sector.side); shifter.add_byte(sector.sector); - switch(sector.data.size()) - { - default: shifter.add_byte(0); break; - case 256: shifter.add_byte(1); break; - case 512: shifter.add_byte(2); break; - case 1024: shifter.add_byte(3); break; - case 2048: shifter.add_byte(4); break; - case 4196: shifter.add_byte(5); break; - } - // TODO: CRC of bytes since the track number + uint8_t size = logarithmic_size_for_size(sector.data.size()); + shifter.add_byte(size); + // header CRC + crc_generator.reset(); + crc_generator.add(sector.track); + crc_generator.add(sector.side); + crc_generator.add(sector.sector); + crc_generator.add(size); + uint16_t crc_value = crc_generator.get_value(); + shifter.add_byte(crc_value & 0xff); + shifter.add_byte(crc_value >> 8); + + // gap for(int c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(0x4e); for(int c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00); + // data shifter.add_data_address_mark(); - for(size_t c = 0; c < sector.data.size(); c++) shifter.add_byte(sector.data[c]); - // TODO: CRC of data + crc_generator.reset(); + for(size_t c = 0; c < sector.data.size(); c++) + { + shifter.add_byte(sector.data[c]); + crc_generator.add(sector.data[c]); + } + // data CRC + crc_value = crc_generator.get_value(); + shifter.add_byte(crc_value & 0xff); + shifter.add_byte(crc_value >> 8); + + // gap for(int c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00); for(int c = 0; c < inter_sector_gap; c++) shifter.add_byte(0x4e); } @@ -159,17 +190,19 @@ template std::shared_ptr return std::shared_ptr(new Storage::Disk::PCMTrack(std::move(segment))); } +struct VectorReceiver { + void output_short(uint16_t value) { + data.push_back(value & 0xff); + data.push_back(value >> 8); + } + std::vector data; +}; std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector §ors) { - struct VectorShifter: public FMShifter { - void output_short(uint16_t value) { - data.push_back(value & 0xff); - data.push_back(value >> 8); - } - std::vector data; + struct VectorShifter: public FMShifter, VectorReceiver { + using VectorReceiver::output_short; }; - return GetTrackWithSectors( sectors, 16, 0x00, @@ -180,14 +213,9 @@ std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSec std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector §ors) { - struct VectorShifter: public MFMShifter { - void output_short(uint16_t value) { - data.push_back(value & 0xff); - data.push_back(value >> 8); - } - std::vector data; + struct VectorShifter: public MFMShifter, VectorReceiver { + using VectorReceiver::output_short; }; - return GetTrackWithSectors( sectors, 50, 0x4e, From 5ebca62bba865dfe6f18950143eeeade5b3fabb7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 18:35:50 -0400 Subject: [PATCH 07/51] Now the CRC is a factoring out. --- StaticAnalyser/Acorn/Tape.cpp | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/StaticAnalyser/Acorn/Tape.cpp b/StaticAnalyser/Acorn/Tape.cpp index c91573094..056b05ccb 100644 --- a/StaticAnalyser/Acorn/Tape.cpp +++ b/StaticAnalyser/Acorn/Tape.cpp @@ -10,6 +10,7 @@ #include #include "../TapeParser.hpp" +#include "../../NumberTheory/CRC.hpp" using namespace StaticAnalyser::Acorn; @@ -23,7 +24,9 @@ enum class SymbolType { class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser { public: - Acorn1200BaudTapeParser(const std::shared_ptr &tape) : TapeParser(tape) {} + Acorn1200BaudTapeParser(const std::shared_ptr &tape) : + TapeParser(tape), + _crc(0x1021, 0x0000) {} int get_next_bit() { @@ -49,7 +52,7 @@ class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser GetNextChunk(Acorn1200BaudTapeParser &parser) From d9aaf456f014f76f1a055218403eee6f4dce08f2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 18:46:58 -0400 Subject: [PATCH 08/51] Adapted pervasively to MSB-first output. Which seems to be correct. --- Storage/Disk/Encodings/MFM.cpp | 46 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index bcd586bd5..f875bf4bf 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -22,7 +22,10 @@ template class Shifter { virtual void add_deleted_data_address_mark() = 0; protected: - // override me! + /*! + Intended to be overridden by subclasses; should write value out as PCM data, + MSB first. + */ void output_short(uint16_t value); }; @@ -70,40 +73,41 @@ template class MFMShifter: public Shifter { }; template class FMShifter: public Shifter { + // encodes each 16-bit part as clock, data, clock, data [...] public: void add_byte(uint8_t input) { static_cast(this)->output_short( (uint16_t)( - ((input & 0x01) << 1) | - ((input & 0x02) << 2) | - ((input & 0x04) << 3) | - ((input & 0x08) << 4) | - ((input & 0x10) << 5) | - ((input & 0x20) << 6) | - ((input & 0x40) << 7) | - ((input & 0x80) << 8) | - 0x5555 + ((input & 0x01) << 0) | + ((input & 0x02) << 1) | + ((input & 0x04) << 2) | + ((input & 0x08) << 3) | + ((input & 0x10) << 4) | + ((input & 0x20) << 5) | + ((input & 0x40) << 6) | + ((input & 0x80) << 7) | + 0xaaaa )); } void add_index_address_mark() { - // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 1011 1011 0101 - static_cast(this)->output_short(0xfbb5); + // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 0111 0111 1010 + static_cast(this)->output_short(0xf77a); } void add_ID_address_mark() { - // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 1010 1011 1101 - static_cast(this)->output_short(0xfabd); + // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 0101 0111 1110 + static_cast(this)->output_short(0xf57e); } void add_data_address_mark() { - // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 1010 1001 1111 - static_cast(this)->output_short(0xfa9f); + // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111 + static_cast(this)->output_short(0xf56f); } void add_deleted_data_address_mark() { - // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 1010 1001 0101 - static_cast(this)->output_short(0xfa95); + // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010 + static_cast(this)->output_short(0xf56a); } }; @@ -158,8 +162,8 @@ template std::shared_ptr crc_generator.add(sector.sector); crc_generator.add(size); uint16_t crc_value = crc_generator.get_value(); - shifter.add_byte(crc_value & 0xff); shifter.add_byte(crc_value >> 8); + shifter.add_byte(crc_value & 0xff); // gap for(int c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(0x4e); @@ -176,8 +180,8 @@ template std::shared_ptr // data CRC crc_value = crc_generator.get_value(); - shifter.add_byte(crc_value & 0xff); shifter.add_byte(crc_value >> 8); + shifter.add_byte(crc_value & 0xff); // gap for(int c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00); @@ -192,8 +196,8 @@ template std::shared_ptr struct VectorReceiver { void output_short(uint16_t value) { - data.push_back(value & 0xff); data.push_back(value >> 8); + data.push_back(value & 0xff); } std::vector data; }; From 5409c8ec54f2b0839e2449c9aa21355f420c9ca6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 18:56:35 -0400 Subject: [PATCH 09/51] Switched `PCMSegment`s to `std::vector`; ensured generated [M]FM tracks are correctly sized, thereby making sure the individual flux windows will be correctly sized. --- Storage/Disk/Encodings/MFM.cpp | 21 ++++++++++++--------- Storage/Disk/Formats/D64.cpp | 4 ++-- Storage/Disk/Formats/G64.cpp | 8 ++++---- Storage/Disk/PCMTrack.cpp | 2 +- Storage/Disk/PCMTrack.hpp | 2 +- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index f875bf4bf..b8704946a 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -130,7 +130,8 @@ template std::shared_ptr size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value, size_t pre_address_mark_bytes, size_t post_address_mark_bytes, size_t pre_data_mark_bytes, size_t post_data_bytes, - size_t inter_sector_gap) + size_t inter_sector_gap, + size_t expected_track_bytes) { T shifter; NumberTheory::CRC16 crc_generator(0x1021, 0xffff); @@ -188,18 +189,18 @@ template std::shared_ptr for(int c = 0; c < inter_sector_gap; c++) shifter.add_byte(0x4e); } - // TODO: total size check + while(shifter.segment.data.size() < expected_track_bytes) shifter.add_byte(0x00); - Storage::Disk::PCMSegment segment; - return std::shared_ptr(new Storage::Disk::PCMTrack(std::move(segment))); + shifter.segment.number_of_bits = shifter.segment.data.size() * 8; + return std::shared_ptr(new Storage::Disk::PCMTrack(std::move(shifter.segment))); } struct VectorReceiver { void output_short(uint16_t value) { - data.push_back(value >> 8); - data.push_back(value & 0xff); + segment.data.push_back(value >> 8); + segment.data.push_back(value & 0xff); } - std::vector data; + Storage::Disk::PCMSegment segment; }; std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector §ors) @@ -212,7 +213,8 @@ std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSec 16, 0x00, 6, 0, 17, 14, - 0); + 0, + 6400); } std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector §ors) @@ -225,5 +227,6 @@ std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSe 50, 0x4e, 12, 22, 12, 18, - 32); + 32, + 12800); } diff --git a/Storage/Disk/Formats/D64.cpp b/Storage/Disk/Formats/D64.cpp index 4bba3eb5a..af406c253 100644 --- a/Storage/Disk/Formats/D64.cpp +++ b/Storage/Disk/Formats/D64.cpp @@ -102,8 +102,8 @@ std::shared_ptr D64::get_track_at_position(unsigned int position) PCMSegment track; size_t track_bytes = 349 * (size_t)sectors_by_zone[zone]; track.number_of_bits = (unsigned int)track_bytes * 8; - uint8_t *data = new uint8_t[track_bytes]; - track.data.reset(data); + track.data.resize(track_bytes); + uint8_t *data = &track.data[0]; memset(data, 0, track_bytes); diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index 546155433..920ac856f 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -84,8 +84,8 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) track_length |= (uint16_t)fgetc(_file) << 8; // grab the byte contents of this track - std::unique_ptr track_contents(new uint8_t[track_length]); - fread(track_contents.get(), 1, track_length, _file); + std::vector track_contents(track_length); + fread(&track_contents[0], 1, track_length, _file); // seek to this track's entry in the speed zone table fseek(_file, (long)((position * 4) + 0x15c), SEEK_SET); @@ -123,8 +123,8 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) PCMSegment segment; 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); + segment.data.resize(number_of_bytes); + memcpy(&segment.data[0], &track_contents[start_byte_in_current_speed], number_of_bytes); segments.push_back(std::move(segment)); current_speed = byte_speed; diff --git a/Storage/Disk/PCMTrack.cpp b/Storage/Disk/PCMTrack.cpp index a655ab969..509dae780 100644 --- a/Storage/Disk/PCMTrack.cpp +++ b/Storage/Disk/PCMTrack.cpp @@ -36,7 +36,7 @@ PCMTrack::Event PCMTrack::get_next_event() 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(); + const uint8_t *segment_data = &_segments[_segment_pointer].data[0]; while(_bit_pointer < _segments[_segment_pointer].number_of_bits) { // for timing simplicity, bits are modelled as happening at the end of their window diff --git a/Storage/Disk/PCMTrack.hpp b/Storage/Disk/PCMTrack.hpp index 751473ef5..36d464e50 100644 --- a/Storage/Disk/PCMTrack.hpp +++ b/Storage/Disk/PCMTrack.hpp @@ -23,7 +23,7 @@ namespace Disk { struct PCMSegment { Time length_of_a_bit; unsigned int number_of_bits; - std::unique_ptr data; + std::vector data; }; /*! From 91cd7e143bff82a4c91ced0bf66f64e04253adf4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 19:21:02 -0400 Subject: [PATCH 10/51] Started on the SSD/DSD support. Realised I had ommitted multiple head support from my disk class. Fixed that. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 +++ StaticAnalyser/StaticAnalyser.cpp | 4 +- Storage/Disk/Disk.hpp | 7 ++- Storage/Disk/DiskDrive.cpp | 2 +- Storage/Disk/Encodings/MFM.cpp | 2 +- Storage/Disk/Formats/D64.cpp | 6 +-- Storage/Disk/Formats/D64.hpp | 2 +- Storage/Disk/Formats/G64.cpp | 3 +- Storage/Disk/Formats/G64.hpp | 2 +- Storage/Disk/Formats/SSD.cpp | 51 +++++++++++++++++++ Storage/Disk/Formats/SSD.hpp | 48 +++++++++++++++++ 11 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 Storage/Disk/Formats/SSD.cpp create mode 100644 Storage/Disk/Formats/SSD.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 3728929e5..6628b060a 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -357,6 +357,7 @@ 4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; }; 4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; }; 4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */; }; + 4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829611D8F536B001BAE39 /* SSD.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -811,6 +812,8 @@ 4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = ""; }; 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRC.cpp; path = ../../NumberTheory/CRC.cpp; sourceTree = ""; }; 4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = ""; }; + 4BF829611D8F536B001BAE39 /* SSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SSD.cpp; sourceTree = ""; }; + 4BF829621D8F536B001BAE39 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1117,6 +1120,8 @@ 4BAB62B41D327F7E00DF5BA0 /* G64.hpp */, 4B4C836E1D4F623200CD541F /* D64.cpp */, 4B4C836F1D4F623200CD541F /* D64.hpp */, + 4BF829611D8F536B001BAE39 /* SSD.cpp */, + 4BF829621D8F536B001BAE39 /* SSD.hpp */, ); path = Formats; sourceTree = ""; @@ -2112,6 +2117,7 @@ 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */, 4B1E85751D170228001EF87D /* Typer.cpp in Sources */, + 4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, 4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index 2f57788f2..98d799b05 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -22,6 +22,7 @@ // Disks #include "../Storage/Disk/Formats/D64.hpp" #include "../Storage/Disk/Formats/G64.hpp" +#include "../Storage/Disk/Formats/SSD.hpp" // Tapes #include "../Storage/Tape/Formats/CommodoreTAP.hpp" @@ -81,6 +82,7 @@ std::list StaticAnalyser::GetTargets(const char *file_name) Format("a26", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN Format("d64", disks, Disk::D64, TargetPlatform::Commodore) // D64 + Format("dsd", disks, Disk::SSD, TargetPlatform::Acorn) // DSD Format("g64", disks, Disk::G64, TargetPlatform::Commodore) // G64 // PRG @@ -98,8 +100,8 @@ std::list StaticAnalyser::GetTargets(const char *file_name) } } - // ROM Format("rom", cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM + Format("ssd", disks, Disk::SSD, TargetPlatform::Acorn) // SSD Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP Format("uef", tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) diff --git a/Storage/Disk/Disk.hpp b/Storage/Disk/Disk.hpp index c17712b85..50466161e 100644 --- a/Storage/Disk/Disk.hpp +++ b/Storage/Disk/Disk.hpp @@ -73,10 +73,15 @@ class Disk { */ virtual unsigned int get_head_position_count() = 0; + /*! + Returns the number of heads (and, therefore, impliedly surfaces) available on this disk. + */ + virtual unsigned int get_head_count() { return 1; } + /*! Returns the @c Track at @c position if there are any detectable events there; returns @c nullptr otherwise. */ - virtual std::shared_ptr get_track_at_position(unsigned int position) = 0; + virtual std::shared_ptr get_track_at_position(unsigned int head, unsigned int position) = 0; }; } diff --git a/Storage/Disk/DiskDrive.cpp b/Storage/Disk/DiskDrive.cpp index 681a9e94d..f69788950 100644 --- a/Storage/Disk/DiskDrive.cpp +++ b/Storage/Disk/DiskDrive.cpp @@ -60,7 +60,7 @@ void Drive::step(int direction) void Drive::set_track(Time initial_offset) { - _track = _disk->get_track_at_position((unsigned int)_head_position); + _track = _disk->get_track_at_position(0, (unsigned int)_head_position); // TODO: probably a better implementation of the empty track? Time offset; if(_track && _time_into_track.length > 0) diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index b8704946a..6b7bd7102 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -191,7 +191,7 @@ template std::shared_ptr while(shifter.segment.data.size() < expected_track_bytes) shifter.add_byte(0x00); - shifter.segment.number_of_bits = shifter.segment.data.size() * 8; + shifter.segment.number_of_bits = (unsigned int)(shifter.segment.data.size() * 8); return std::shared_ptr(new Storage::Disk::PCMTrack(std::move(shifter.segment))); } diff --git a/Storage/Disk/Formats/D64.cpp b/Storage/Disk/Formats/D64.cpp index af406c253..f0397b10b 100644 --- a/Storage/Disk/Formats/D64.cpp +++ b/Storage/Disk/Formats/D64.cpp @@ -53,10 +53,10 @@ unsigned int D64::get_head_position_count() return _number_of_tracks*2; } -std::shared_ptr D64::get_track_at_position(unsigned int position) +std::shared_ptr D64::get_track_at_position(unsigned int head, unsigned int position) { - // every other track is missing - if(position&1) + // every other track is missing, as is any head above 0 + if(position&1 || head) return std::shared_ptr(); // figure out where this track starts on the disk diff --git a/Storage/Disk/Formats/D64.hpp b/Storage/Disk/Formats/D64.hpp index 93c4d4cb5..7b3b20291 100644 --- a/Storage/Disk/Formats/D64.hpp +++ b/Storage/Disk/Formats/D64.hpp @@ -35,7 +35,7 @@ class D64: public Disk { // implemented to satisfy @c Disk unsigned int get_head_position_count(); - std::shared_ptr get_track_at_position(unsigned int position); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: FILE *_file; diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index 920ac856f..86df3022f 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -54,13 +54,14 @@ unsigned int G64::get_head_position_count() return _number_of_tracks > 84 ? _number_of_tracks : 84; } -std::shared_ptr G64::get_track_at_position(unsigned int position) +std::shared_ptr G64::get_track_at_position(unsigned int head, unsigned int position) { std::shared_ptr resulting_track; // if there's definitely no track here, return the empty track // (TODO: should be supplying one with an index hole?) if(position >= _number_of_tracks) return resulting_track; + if(head >= 1) return resulting_track; // seek to this track's entry in the track table fseek(_file, (long)((position * 4) + 0xc), SEEK_SET); diff --git a/Storage/Disk/Formats/G64.hpp b/Storage/Disk/Formats/G64.hpp index 82ab9ae97..35db52efb 100644 --- a/Storage/Disk/Formats/G64.hpp +++ b/Storage/Disk/Formats/G64.hpp @@ -37,7 +37,7 @@ class G64: public Disk { // implemented to satisfy @c Disk unsigned int get_head_position_count(); - std::shared_ptr get_track_at_position(unsigned int position); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); private: FILE *_file; diff --git a/Storage/Disk/Formats/SSD.cpp b/Storage/Disk/Formats/SSD.cpp new file mode 100644 index 000000000..490d5ce9d --- /dev/null +++ b/Storage/Disk/Formats/SSD.cpp @@ -0,0 +1,51 @@ +// +// SSD.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "SSD.hpp" + +#include + +using namespace Storage::Disk; + +SSD::SSD(const char *file_name) : _file(nullptr) +{ + struct stat file_stats; + stat(file_name, &file_stats); + + // very loose validation: the file needs to be a multiple of 256 bytes + // and not ungainly large + + if(file_stats.st_size & 255) throw ErrorNotSSD; + if(file_stats.st_size < 512) throw ErrorNotSSD; + if(file_stats.st_size > 800*256) throw ErrorNotSSD; + + _file = fopen(file_name, "rb"); + + if(!_file) throw ErrorCantOpen; +} + +SSD::~SSD() +{ + if(_file) fclose(_file); +} + +unsigned int SSD::get_head_position_count() +{ + return 1; +} + +unsigned int SSD::get_head_count() +{ + return 1; +} + +std::shared_ptr SSD::get_track_at_position(unsigned int head, unsigned int position) +{ + std::shared_ptr track; + return track; +} diff --git a/Storage/Disk/Formats/SSD.hpp b/Storage/Disk/Formats/SSD.hpp new file mode 100644 index 000000000..a45a21721 --- /dev/null +++ b/Storage/Disk/Formats/SSD.hpp @@ -0,0 +1,48 @@ +// +// SSD.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef SSD_hpp +#define SSD_hpp + +#include "../Disk.hpp" + +namespace Storage { +namespace Disk { + +/*! + Provies a @c Disk containing a DSD or SSD disk image — a decoded sector dump of an Acorn DFS disk. +*/ +class SSD: public Disk { + public: + /*! + Construct a @c D64 containing content from the file with name @c file_name. + + @throws ErrorCantOpen if this file can't be opened. + @throws ErrorNotD64 if the file doesn't appear to contain a .D64 format image. + */ + SSD(const char *file_name); + ~SSD(); + + enum { + ErrorCantOpen, + ErrorNotSSD, + }; + + // implemented to satisfy @c Disk + unsigned int get_head_position_count(); + unsigned int get_head_count(); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); + + private: + FILE *_file; +}; + +} +} + +#endif /* SSD_hpp */ From 02c9a82cb5c1dee43af528b34ef4bab7f2d4d210 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 19:32:08 -0400 Subject: [PATCH 11/51] Edging towards SSD/DSD support. Hold on! --- Storage/Disk/Formats/SSD.cpp | 21 +++++++++++++++++++-- Storage/Disk/Formats/SSD.hpp | 2 ++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Storage/Disk/Formats/SSD.cpp b/Storage/Disk/Formats/SSD.cpp index 490d5ce9d..11ccd0a34 100644 --- a/Storage/Disk/Formats/SSD.cpp +++ b/Storage/Disk/Formats/SSD.cpp @@ -9,6 +9,7 @@ #include "SSD.hpp" #include +#include "../Encodings/MFM.hpp" using namespace Storage::Disk; @@ -27,6 +28,12 @@ SSD::SSD(const char *file_name) : _file(nullptr) _file = fopen(file_name, "rb"); if(!_file) throw ErrorCantOpen; + + // this has two heads if the suffix is .dsd, one if it's .ssd + _head_count = (tolower(file_name[strlen(file_name) - 3]) == 'd') ? 2 : 1; + _track_count = (unsigned int)(file_stats.st_size / (256 * 10)); + if(_track_count < 40) _track_count = 40; + else if(_track_count < 80) _track_count = 80; } SSD::~SSD() @@ -36,16 +43,26 @@ SSD::~SSD() unsigned int SSD::get_head_position_count() { - return 1; + return _track_count; } unsigned int SSD::get_head_count() { - return 1; + return _head_count; } std::shared_ptr SSD::get_track_at_position(unsigned int head, unsigned int position) { std::shared_ptr track; + + if(head >= _head_count) return track; + long file_offset = (position * (_head_count ? 2 : 1) + head) * 256 * 10; + fseek(_file, file_offset, SEEK_SET); + +// std::vector< + for(int sector = 0; sector < 10; sector++) + { + } + return track; } diff --git a/Storage/Disk/Formats/SSD.hpp b/Storage/Disk/Formats/SSD.hpp index a45a21721..7fcd8e26d 100644 --- a/Storage/Disk/Formats/SSD.hpp +++ b/Storage/Disk/Formats/SSD.hpp @@ -40,6 +40,8 @@ class SSD: public Disk { private: FILE *_file; + unsigned int _head_count; + unsigned int _track_count; }; } From d1c861d3a5d6bb2a86b840c3f2ed0d8f7c33220b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 21:09:32 -0400 Subject: [PATCH 12/51] That should be that, I hope. --- Storage/Disk/Formats/SSD.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Storage/Disk/Formats/SSD.cpp b/Storage/Disk/Formats/SSD.cpp index 11ccd0a34..bd5655495 100644 --- a/Storage/Disk/Formats/SSD.cpp +++ b/Storage/Disk/Formats/SSD.cpp @@ -59,10 +59,22 @@ std::shared_ptr SSD::get_track_at_position(unsigned int head, unsigned in long file_offset = (position * (_head_count ? 2 : 1) + head) * 256 * 10; fseek(_file, file_offset, SEEK_SET); -// std::vector< + std::vector sectors; for(int sector = 0; sector < 10; sector++) { + Storage::Encodings::MFM::Sector new_sector; + new_sector.track = (uint8_t)position; + new_sector.side = 0; + new_sector.sector = (uint8_t)sector; + + new_sector.data.resize(256); + fread(&new_sector.data[0], 1, 256, _file); + if(feof(_file)) break; + + sectors.push_back(std::move(new_sector)); } + if(sectors.size()) return Storage::Encodings::MFM::GetFMTrackWithSectors(sectors); + return track; } From 0ce901bd487043440559c72a31a0dc67130631cc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 21:18:11 -0400 Subject: [PATCH 13/51] Added necessary wiring to get as far as asking an arm of the analyser to check out an Acorn disk image. --- .../Clock Signal.xcodeproj/project.pbxproj | 20 ++++++-- StaticAnalyser/Acorn/Disk.cpp | 17 +++++++ StaticAnalyser/Acorn/Disk.hpp | 24 ++++++++++ StaticAnalyser/Acorn/File.cpp | 9 ++++ StaticAnalyser/Acorn/File.hpp | 47 +++++++++++++++++++ StaticAnalyser/Acorn/StaticAnalyser.cpp | 6 +++ StaticAnalyser/Acorn/Tape.hpp | 28 +---------- 7 files changed, 120 insertions(+), 31 deletions(-) create mode 100644 StaticAnalyser/Acorn/Disk.cpp create mode 100644 StaticAnalyser/Acorn/Disk.hpp create mode 100644 StaticAnalyser/Acorn/File.cpp create mode 100644 StaticAnalyser/Acorn/File.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 6628b060a..adfd9fa15 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -358,6 +358,8 @@ 4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; }; 4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295E1D8F3C87001BAE39 /* CRC.cpp */; }; 4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829611D8F536B001BAE39 /* SSD.cpp */; }; + 4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829641D8F732B001BAE39 /* Disk.cpp */; }; + 4BF829691D8F7361001BAE39 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829671D8F7361001BAE39 /* File.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -814,6 +816,10 @@ 4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = ""; }; 4BF829611D8F536B001BAE39 /* SSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SSD.cpp; sourceTree = ""; }; 4BF829621D8F536B001BAE39 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = ""; }; + 4BF829641D8F732B001BAE39 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Acorn/Disk.cpp; sourceTree = ""; }; + 4BF829651D8F732B001BAE39 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Acorn/Disk.hpp; sourceTree = ""; }; + 4BF829671D8F7361001BAE39 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Acorn/File.cpp; sourceTree = ""; }; + 4BF829681D8F7361001BAE39 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Acorn/File.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1556,16 +1562,16 @@ 4BC830D21D6E7C6D0000A26F /* Commodore */ = { isa = PBXGroup; children = ( + 4BA22B051D8817CE0008C640 /* Disk.cpp */, + 4BA22B061D8817CE0008C640 /* Disk.hpp */, + 4BE77A2C1D84ADFB00BC3827 /* File.cpp */, + 4BE77A2D1D84ADFB00BC3827 /* File.hpp */, 4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */, 4BC5E4911D7ED365008CF980 /* StaticAnalyser.hpp */, 4BC830CF1D6E7C690000A26F /* Tape.cpp */, 4BC830D01D6E7C690000A26F /* Tape.hpp */, 4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */, 4BC5E4941D7EE0E0008CF980 /* Utilities.hpp */, - 4BE77A2C1D84ADFB00BC3827 /* File.cpp */, - 4BE77A2D1D84ADFB00BC3827 /* File.hpp */, - 4BA22B051D8817CE0008C640 /* Disk.cpp */, - 4BA22B061D8817CE0008C640 /* Disk.hpp */, ); name = Commodore; sourceTree = ""; @@ -1602,6 +1608,10 @@ 4BD14B121D7462810088EAD6 /* Acorn */ = { isa = PBXGroup; children = ( + 4BF829641D8F732B001BAE39 /* Disk.cpp */, + 4BF829651D8F732B001BAE39 /* Disk.hpp */, + 4BF829671D8F7361001BAE39 /* File.cpp */, + 4BF829681D8F7361001BAE39 /* File.hpp */, 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */, 4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */, 4B96F7201D75119A0058BB2D /* Tape.cpp */, @@ -2103,9 +2113,11 @@ 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */, + 4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */, 4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */, 4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */, 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, + 4BF829691D8F7361001BAE39 /* File.cpp in Sources */, 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */, 4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */, 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */, diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp new file mode 100644 index 000000000..8ddaf8ac3 --- /dev/null +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -0,0 +1,17 @@ +// +// Disk.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Disk.hpp" + +using namespace StaticAnalyser::Acorn; + +std::list StaticAnalyser::Acorn::GetDFSFiles(const std::shared_ptr &disk) +{ + std::list files; + return files; +} diff --git a/StaticAnalyser/Acorn/Disk.hpp b/StaticAnalyser/Acorn/Disk.hpp new file mode 100644 index 000000000..92ea3ba1e --- /dev/null +++ b/StaticAnalyser/Acorn/Disk.hpp @@ -0,0 +1,24 @@ +// +// Disk.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef StaticAnalyser_Acorn_Disk_hpp +#define StaticAnalyser_Acorn_Disk_hpp + +#include "File.hpp" +#include "../../Storage/Disk/Disk.hpp" + +namespace StaticAnalyser { +namespace Acorn { + +std::list GetDFSFiles(const std::shared_ptr &disk); +std::list GetADFSFiles(const std::shared_ptr &disk); + +} +} + +#endif /* Disk_hpp */ diff --git a/StaticAnalyser/Acorn/File.cpp b/StaticAnalyser/Acorn/File.cpp new file mode 100644 index 000000000..b175127be --- /dev/null +++ b/StaticAnalyser/Acorn/File.cpp @@ -0,0 +1,9 @@ +// +// File.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "File.hpp" diff --git a/StaticAnalyser/Acorn/File.hpp b/StaticAnalyser/Acorn/File.hpp new file mode 100644 index 000000000..a7f942705 --- /dev/null +++ b/StaticAnalyser/Acorn/File.hpp @@ -0,0 +1,47 @@ +// +// File.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef File_hpp +#define File_hpp + +#include +#include +#include +#include + +namespace StaticAnalyser { +namespace Acorn { + +struct File { + std::string name; + uint32_t load_address; + uint32_t execution_address; + bool is_protected; + std::vector data; + + struct Chunk { + std::string name; + uint32_t load_address; + uint32_t execution_address; + uint16_t block_number; + uint16_t block_length; + uint8_t block_flag; + uint32_t next_address; + + bool header_crc_matched; + bool data_crc_matched; + std::vector data; + }; + + std::list chunks; +}; + +} +} + +#endif /* File_hpp */ diff --git a/StaticAnalyser/Acorn/StaticAnalyser.cpp b/StaticAnalyser/Acorn/StaticAnalyser.cpp index 42efa33be..93d92022f 100644 --- a/StaticAnalyser/Acorn/StaticAnalyser.cpp +++ b/StaticAnalyser/Acorn/StaticAnalyser.cpp @@ -8,6 +8,7 @@ #include "StaticAnalyser.hpp" +#include "Disk.hpp" #include "Tape.hpp" using namespace StaticAnalyser::Acorn; @@ -111,6 +112,11 @@ void StaticAnalyser::Acorn::AddTargets( } // TODO: disks + if(disks.size() > 0) + { + std::shared_ptr disk = disks.front(); + std::list dfs_files = GetDFSFiles(disk); + } if(target.tapes.size() || target.cartridges.size()) destination.push_back(target); diff --git a/StaticAnalyser/Acorn/Tape.hpp b/StaticAnalyser/Acorn/Tape.hpp index 570ebab1e..ac13b6ab3 100644 --- a/StaticAnalyser/Acorn/Tape.hpp +++ b/StaticAnalyser/Acorn/Tape.hpp @@ -9,40 +9,14 @@ #ifndef StaticAnalyser_Acorn_Tape_hpp #define StaticAnalyser_Acorn_Tape_hpp -#include #include -#include -#include +#include "File.hpp" #include "../../Storage/Tape/Tape.hpp" namespace StaticAnalyser { namespace Acorn { -struct File { - std::string name; - uint32_t load_address; - uint32_t execution_address; - bool is_protected; - std::vector data; - - struct Chunk { - std::string name; - uint32_t load_address; - uint32_t execution_address; - uint16_t block_number; - uint16_t block_length; - uint8_t block_flag; - uint32_t next_address; - - bool header_crc_matched; - bool data_crc_matched; - std::vector data; - }; - - std::list chunks; -}; - std::list GetFiles(const std::shared_ptr &tape); } From fbcb59d47a8864e079439de62ac748004e4c520d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 21:18:41 -0400 Subject: [PATCH 14/51] A policy change trial: let the person above deal with any error. --- .../Clock Signal/Document Controller/DocumentController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Document Controller/DocumentController.swift b/OSBindings/Mac/Clock Signal/Document Controller/DocumentController.swift index 4625bdc50..b2c7d2e18 100644 --- a/OSBindings/Mac/Clock Signal/Document Controller/DocumentController.swift +++ b/OSBindings/Mac/Clock Signal/Document Controller/DocumentController.swift @@ -21,6 +21,6 @@ class DocumentController: NSDocumentController { } } - return try! super.makeDocument(withContentsOf: url, ofType: typeName) + return try super.makeDocument(withContentsOf: url, ofType: typeName) } } From b1e7f2dfd0ca5756e39d8ce58929f61710d36417 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 22:03:06 -0400 Subject: [PATCH 15/51] There's no cache and no CRC checking yet, but this is probably a rough outline of an FM parser. --- StaticAnalyser/Acorn/Disk.cpp | 134 +++++++++++++++++++++++++++++++++ Storage/Disk/DiskDrive.hpp | 2 +- Storage/Disk/Encodings/MFM.cpp | 23 +----- Storage/Disk/Encodings/MFM.hpp | 6 ++ 4 files changed, 145 insertions(+), 20 deletions(-) diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index 8ddaf8ac3..0bbfdf2ea 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -7,11 +7,145 @@ // #include "Disk.hpp" +#include "../../Storage/Disk/DiskDrive.hpp" +#include "../../Storage/Disk/Encodings/MFM.hpp" using namespace StaticAnalyser::Acorn; +class FMParser: public Storage::Disk::Drive { + public: + FMParser() : Storage::Disk::Drive(4000000, 1, 300), shift_register_(0), track_(0) + { + // Make sure this drive really is at track '1'. + while(!get_is_track_zero()) step(-1); + + Storage::Time bit_length; + bit_length.length = 1; + bit_length.clock_rate = 250000; // i.e. 250 kbps + set_expected_bit_length(bit_length); + } + + /*! + Attempts to read the sector located at @c track and @c sector. + + @returns a sector if one was found; @c nullptr otherwise. + */ + std::shared_ptr get_sector(uint8_t track, uint8_t sector) + { + int difference = (int)track - (int)track_; + track_ = track; + + if(difference) + { + int direction = difference < 0 ? -1 : 1; + difference *= 2 * direction; + + for(int c = 0; c < difference; c++) step(direction); + } + + return get_sector(sector); + } + + private: + unsigned int shift_register_; + int index_count_; + uint8_t track_; + int bit_count_; + std::shared_ptr sector_cache_[65536]; + + void process_input_bit(int value, unsigned int cycles_since_index_hole) + { + shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff; + bit_count_++; + } + + void process_index_hole() + { + index_count_++; + } + + uint8_t get_next_byte() + { + bit_count_ = 0; + while(bit_count_ < 16) run_for_cycles(1); + return (uint8_t)( + ((shift_register_&0x0001) >> 0) | + ((shift_register_&0x0004) >> 1) | + ((shift_register_&0x0010) >> 2) | + ((shift_register_&0x0040) >> 3) | + ((shift_register_&0x0100) >> 4) | + ((shift_register_&0x0400) >> 5) | + ((shift_register_&0x1000) >> 6) | + ((shift_register_&0x4000) >> 7)); + } + + std::shared_ptr get_next_sector() + { + std::shared_ptr sector(new Storage::Encodings::MFM::Sector); + index_count_ = 0; + + while(index_count_ < 2) + { + // look for an ID address mark + while(1) + { + run_for_cycles(1); + if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) break; + if(index_count_ >= 2) return nullptr; + } + + sector->track = get_next_byte(); + sector->side = get_next_byte(); + sector->sector = get_next_byte(); + uint8_t size = get_next_byte(); + + // look for data mark + while(1) + { + run_for_cycles(1); + if(shift_register_ == Storage::Encodings::MFM::FMDataAddressMark) break; + if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) return nullptr; + if(index_count_ >= 2) return nullptr; + } + + size_t data_size = (size_t)(128 << size); + sector->data.reserve(data_size); + for(size_t c = 0; c < data_size; c++) + { + sector->data.push_back(get_next_byte()); + } + + return sector; + } + + return sector; + } + + std::shared_ptr get_sector(uint8_t sector) + { +// uint16_t sector_address = (uint16_t)((track_ << 8) | sector); +// if(sector_cache_[sector_address]) return sector_cache_[sector_address]; + + std::shared_ptr first_sector = get_next_sector(); + if(!first_sector) return first_sector; + if(first_sector->sector == sector) return first_sector; + + while(1) + { + std::shared_ptr next_sector = get_next_sector(); + if(next_sector->sector == first_sector->sector) return nullptr; + if(next_sector->sector == sector) return next_sector; + } + } +}; + std::list StaticAnalyser::Acorn::GetDFSFiles(const std::shared_ptr &disk) { std::list files; + FMParser parser; + parser.set_disk(disk); + + parser.get_sector(0, 0); + return files; } diff --git a/Storage/Disk/DiskDrive.hpp b/Storage/Disk/DiskDrive.hpp index 017e34464..8c9938d54 100644 --- a/Storage/Disk/DiskDrive.hpp +++ b/Storage/Disk/DiskDrive.hpp @@ -36,7 +36,7 @@ class Drive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop { Drive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute); /*! - Communicates to the PLL the expected length of a bit. + Communicates to the PLL the expected length of a bit as a fraction of a second. */ void set_expected_bit_length(Time bit_length); diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 6b7bd7102..8d04e897f 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -90,25 +90,10 @@ template class FMShifter: public Shifter { )); } - void add_index_address_mark() { - // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 0111 0111 1010 - static_cast(this)->output_short(0xf77a); - } - - void add_ID_address_mark() { - // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 0101 0111 1110 - static_cast(this)->output_short(0xf57e); - } - - void add_data_address_mark() { - // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111 - static_cast(this)->output_short(0xf56f); - } - - void add_deleted_data_address_mark() { - // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010 - static_cast(this)->output_short(0xf56a); - } + void add_index_address_mark() { static_cast(this)->output_short(FMIndexAddressMark); } + void add_ID_address_mark() { static_cast(this)->output_short(FMIDAddressMark); } + void add_data_address_mark() { static_cast(this)->output_short(FMDataAddressMark); } + void add_deleted_data_address_mark() { static_cast(this)->output_short(FMDeletedDataAddressMark); } }; static uint8_t logarithmic_size_for_size(size_t size) diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index 543896559..04662e431 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -17,6 +17,12 @@ namespace Storage { namespace Encodings { namespace MFM { +const uint16_t FMIndexAddressMark = 0xf77a; // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 0111 0111 1010 +const uint16_t FMIDAddressMark = 0xf57e; // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 0101 0111 1110 +const uint16_t FMDataAddressMark = 0xf56f; // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111 +const uint16_t FMDeletedDataAddressMark = 0xf56a; // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010 + + struct Sector { uint8_t track, side, sector; std::vector data; From 231f3dd0f45a93931054afd3b2559d8ea5b6ddf0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 18 Sep 2016 22:18:01 -0400 Subject: [PATCH 16/51] This is exactly as far as I'm going to get with DFS parsing today, I think. --- StaticAnalyser/Acorn/Disk.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index 0bbfdf2ea..1d03067ae 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -141,11 +141,27 @@ class FMParser: public Storage::Disk::Drive { std::list StaticAnalyser::Acorn::GetDFSFiles(const std::shared_ptr &disk) { + // c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format std::list files; FMParser parser; parser.set_disk(disk); - parser.get_sector(0, 0); + std::shared_ptr names = parser.get_sector(0, 0); + std::shared_ptr details = parser.get_sector(0, 1); + + if(!names || !details) return files; + if(names->data.size() != 256 || details->data.size() != 256) return files; + + uint8_t final_file_offset = details->data[5]; + if(final_file_offset&7) return files; + + size_t number_of_files = (final_file_offset >> 3)-1; + for(size_t file = 0; file < number_of_files; file++) + { + char name[10]; + snprintf(name, 10, "%c.%7s", names->data[file * 8 + 7], &names->data[file * 8]); + printf("%s\n", name); + } return files; } From 8a1b805d112bbb66180d577ecb438727451a81f4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 19 Sep 2016 07:34:10 -0400 Subject: [PATCH 17/51] Fixed file offset calculation for single-sided images. --- Storage/Disk/Formats/SSD.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Disk/Formats/SSD.cpp b/Storage/Disk/Formats/SSD.cpp index bd5655495..30e3dd63f 100644 --- a/Storage/Disk/Formats/SSD.cpp +++ b/Storage/Disk/Formats/SSD.cpp @@ -56,7 +56,7 @@ std::shared_ptr SSD::get_track_at_position(unsigned int head, unsigned in std::shared_ptr track; if(head >= _head_count) return track; - long file_offset = (position * (_head_count ? 2 : 1) + head) * 256 * 10; + long file_offset = (position * _head_count + head) * 256 * 10; fseek(_file, file_offset, SEEK_SET); std::vector sectors; From ca4df5dd617f3f34d3b8a6611baabfa61aa5663f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 19 Sep 2016 07:35:02 -0400 Subject: [PATCH 18/51] Introduced a full container for disk catalogues, so as also to capture non-file information, and 'completed' (i.e. albeit that CRC checking is still absent) DFS catalogue decoding. --- StaticAnalyser/Acorn/Disk.cpp | 63 ++++++++++++++++++++----- StaticAnalyser/Acorn/Disk.hpp | 15 +++++- StaticAnalyser/Acorn/StaticAnalyser.cpp | 2 +- 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index 1d03067ae..d3f08e467 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -9,6 +9,7 @@ #include "Disk.hpp" #include "../../Storage/Disk/DiskDrive.hpp" #include "../../Storage/Disk/Encodings/MFM.hpp" +#include using namespace StaticAnalyser::Acorn; @@ -38,7 +39,7 @@ class FMParser: public Storage::Disk::Drive { if(difference) { int direction = difference < 0 ? -1 : 1; - difference *= 2 * direction; + difference *= direction; for(int c = 0; c < difference; c++) step(direction); } @@ -139,29 +140,67 @@ class FMParser: public Storage::Disk::Drive { } }; -std::list StaticAnalyser::Acorn::GetDFSFiles(const std::shared_ptr &disk) +std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr &disk) { // c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format - std::list files; + std::unique_ptr catalogue(new Catalogue); FMParser parser; parser.set_disk(disk); std::shared_ptr names = parser.get_sector(0, 0); std::shared_ptr details = parser.get_sector(0, 1); - if(!names || !details) return files; - if(names->data.size() != 256 || details->data.size() != 256) return files; + if(!names || !details) return catalogue; + if(names->data.size() != 256 || details->data.size() != 256) return nullptr; uint8_t final_file_offset = details->data[5]; - if(final_file_offset&7) return files; + if(final_file_offset&7) return nullptr; - size_t number_of_files = (final_file_offset >> 3)-1; - for(size_t file = 0; file < number_of_files; file++) + char disk_name[13]; + snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]); + catalogue->name = disk_name; + + switch((details->data[6] >> 4)&3) { - char name[10]; - snprintf(name, 10, "%c.%7s", names->data[file * 8 + 7], &names->data[file * 8]); - printf("%s\n", name); + case 0: catalogue->bootOption = Catalogue::BootOption::None; break; + case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break; + case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break; + case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break; } - return files; + // DFS files are stored contiguously, and listed in descending order of distance from track 0. + // So iterating backwards implies the least amount of seeking. + for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) + { + File new_file; + char name[10]; + snprintf(name, 10, "%c.%.7s", names->data[file_offset + 7] & 0x7f, &names->data[file_offset]); + new_file.name = name; + new_file.load_address = (uint32_t)(details->data[file_offset] | (details->data[file_offset+1] << 8) | ((details->data[file_offset+6]&0x0c) << 14)); + new_file.execution_address = (uint32_t)(details->data[file_offset+2] | (details->data[file_offset+3] << 8) | ((details->data[file_offset+6]&0xc0) << 10)); + new_file.is_protected = !!(names->data[file_offset + 7] & 0x80); + + long data_length = (long)(details->data[file_offset+4] | (details->data[file_offset+5] << 8) | ((details->data[file_offset+6]&0x30) << 12)); + int start_sector = details->data[file_offset+7] | ((details->data[file_offset+6]&0x03) << 8); + new_file.data.reserve((size_t)data_length); + + if(start_sector < 2) continue; + + while(data_length > 0) + { + uint8_t sector = (uint8_t)(start_sector % 10); + uint8_t track = (uint8_t)(start_sector / 10); + start_sector++; + + std::shared_ptr next_sector = parser.get_sector(track, sector); + if(!next_sector) break; + + long length_from_sector = std::min(data_length, 256l); + new_file.data.insert(new_file.data.end(), next_sector->data.begin(), next_sector->data.begin() + length_from_sector); + data_length -= length_from_sector; + if(!data_length) catalogue->files.push_front(new_file); + } + } + + return catalogue; } diff --git a/StaticAnalyser/Acorn/Disk.hpp b/StaticAnalyser/Acorn/Disk.hpp index 92ea3ba1e..5b7bc3ed7 100644 --- a/StaticAnalyser/Acorn/Disk.hpp +++ b/StaticAnalyser/Acorn/Disk.hpp @@ -15,8 +15,19 @@ namespace StaticAnalyser { namespace Acorn { -std::list GetDFSFiles(const std::shared_ptr &disk); -std::list GetADFSFiles(const std::shared_ptr &disk); +struct Catalogue { + std::string name; + std::list files; + enum class BootOption { + None, + LoadBOOT, + RunBOOT, + ExecBOOT + } bootOption; +}; + +std::unique_ptr GetDFSCatalogue(const std::shared_ptr &disk); +std::unique_ptr GetADFSCatalogue(const std::shared_ptr &disk); } } diff --git a/StaticAnalyser/Acorn/StaticAnalyser.cpp b/StaticAnalyser/Acorn/StaticAnalyser.cpp index 93d92022f..35f8d3491 100644 --- a/StaticAnalyser/Acorn/StaticAnalyser.cpp +++ b/StaticAnalyser/Acorn/StaticAnalyser.cpp @@ -115,7 +115,7 @@ void StaticAnalyser::Acorn::AddTargets( if(disks.size() > 0) { std::shared_ptr disk = disks.front(); - std::list dfs_files = GetDFSFiles(disk); + std::unique_ptr dfs_catalogue = GetDFSCatalogue(disk); } if(target.tapes.size() || target.cartridges.size()) From c58cdbf1512fe3ee280f2c399b7a608be20023a7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 19 Sep 2016 07:37:11 -0400 Subject: [PATCH 19/51] Minor tweak to support zero-length files. --- StaticAnalyser/Acorn/Disk.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index d3f08e467..c79a08ae1 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -185,7 +185,6 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha new_file.data.reserve((size_t)data_length); if(start_sector < 2) continue; - while(data_length > 0) { uint8_t sector = (uint8_t)(start_sector % 10); @@ -198,8 +197,8 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha long length_from_sector = std::min(data_length, 256l); new_file.data.insert(new_file.data.end(), next_sector->data.begin(), next_sector->data.begin() + length_from_sector); data_length -= length_from_sector; - if(!data_length) catalogue->files.push_front(new_file); } + if(!data_length) catalogue->files.push_front(new_file); } return catalogue; From 64f2538b1f9324ca42f1234e8b46dbb2ab7a45b0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 19 Sep 2016 08:16:06 -0400 Subject: [PATCH 20/51] Added CRC checking to DFS comprehension; fixed a bunch of places where I'd used Objective-C's `#import` rather than `#include`. --- Machines/Commodore/SerialBus.hpp | 2 +- StaticAnalyser/Acorn/Disk.cpp | 19 +++++++++++++++++-- Storage/Disk/Encodings/MFM.cpp | 4 ++-- Storage/Disk/Encodings/MFM.hpp | 2 +- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Machines/Commodore/SerialBus.hpp b/Machines/Commodore/SerialBus.hpp index 324ef235e..381e441fd 100644 --- a/Machines/Commodore/SerialBus.hpp +++ b/Machines/Commodore/SerialBus.hpp @@ -9,7 +9,7 @@ #ifndef SerialBus_hpp #define SerialBus_hpp -#import +#include namespace Commodore { namespace Serial { diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index c79a08ae1..f3dfa1074 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -9,13 +9,17 @@ #include "Disk.hpp" #include "../../Storage/Disk/DiskDrive.hpp" #include "../../Storage/Disk/Encodings/MFM.hpp" +#include "../../NumberTheory/CRC.hpp" #include using namespace StaticAnalyser::Acorn; class FMParser: public Storage::Disk::Drive { public: - FMParser() : Storage::Disk::Drive(4000000, 1, 300), shift_register_(0), track_(0) + FMParser() : + Storage::Disk::Drive(4000000, 1, 300), + crc_generator_(0x1021, 0xffff), + shift_register_(0), track_(0) { // Make sure this drive really is at track '1'. while(!get_is_track_zero()) step(-1); @@ -53,6 +57,7 @@ class FMParser: public Storage::Disk::Drive { uint8_t track_; int bit_count_; std::shared_ptr sector_cache_[65536]; + NumberTheory::CRC16 crc_generator_; void process_input_bit(int value, unsigned int cycles_since_index_hole) { @@ -69,7 +74,7 @@ class FMParser: public Storage::Disk::Drive { { bit_count_ = 0; while(bit_count_ < 16) run_for_cycles(1); - return (uint8_t)( + uint8_t byte = (uint8_t)( ((shift_register_&0x0001) >> 0) | ((shift_register_&0x0004) >> 1) | ((shift_register_&0x0010) >> 2) | @@ -78,6 +83,8 @@ class FMParser: public Storage::Disk::Drive { ((shift_register_&0x0400) >> 5) | ((shift_register_&0x1000) >> 6) | ((shift_register_&0x4000) >> 7)); + crc_generator_.add(byte); + return byte; } std::shared_ptr get_next_sector() @@ -95,10 +102,14 @@ class FMParser: public Storage::Disk::Drive { if(index_count_ >= 2) return nullptr; } + crc_generator_.reset(); sector->track = get_next_byte(); sector->side = get_next_byte(); sector->sector = get_next_byte(); uint8_t size = get_next_byte(); + uint16_t header_crc = crc_generator_.get_value(); + if((header_crc >> 8) != get_next_byte()) continue; + if((header_crc & 0xff) != get_next_byte()) continue; // look for data mark while(1) @@ -111,10 +122,14 @@ class FMParser: public Storage::Disk::Drive { size_t data_size = (size_t)(128 << size); sector->data.reserve(data_size); + crc_generator_.reset(); for(size_t c = 0; c < data_size; c++) { sector->data.push_back(get_next_byte()); } + uint16_t data_crc = crc_generator_.get_value(); + if((data_crc >> 8) != get_next_byte()) continue; + if((data_crc & 0xff) != get_next_byte()) continue; return sector; } diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 8d04e897f..6412e3e2e 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -8,8 +8,8 @@ #include "MFM.hpp" -#import "../PCMTrack.hpp" -#import "../../../NumberTheory/CRC.hpp" +#include "../PCMTrack.hpp" +#include "../../../NumberTheory/CRC.hpp" using namespace Storage::Encodings::MFM; diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index 04662e431..07563cbce 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -11,7 +11,7 @@ #include #include -#import "../Disk.hpp" +#include "../Disk.hpp" namespace Storage { namespace Encodings { From e02576860470e6cee440ab13e2e85a7321731842 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 19 Sep 2016 08:17:07 -0400 Subject: [PATCH 21/51] Two passes of the index hole => no sector found. --- StaticAnalyser/Acorn/Disk.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index f3dfa1074..d4023525e 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -134,7 +134,7 @@ class FMParser: public Storage::Disk::Drive { return sector; } - return sector; + return nullptr; } std::shared_ptr get_sector(uint8_t sector) From c9dd07cecd5baf4e3d87b7a891f0b936b61a4cda Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 19 Sep 2016 08:29:23 -0400 Subject: [PATCH 22/51] DFS disks are now delivered right up to the emulator's front door. So everything's in place to get started on that WD1770 nonsense. --- OSBindings/Mac/Clock Signal/Info.plist | 2 ++ StaticAnalyser/Acorn/StaticAnalyser.cpp | 12 ++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 412ff4196..5db4e8201 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -143,6 +143,8 @@ Electron/BBC Disk Image CFBundleTypeRole Viewer + NSDocumentClass + $(PRODUCT_MODULE_NAME).ElectronDocument CFBundleExecutable diff --git a/StaticAnalyser/Acorn/StaticAnalyser.cpp b/StaticAnalyser/Acorn/StaticAnalyser.cpp index 35f8d3491..bf393945c 100644 --- a/StaticAnalyser/Acorn/StaticAnalyser.cpp +++ b/StaticAnalyser/Acorn/StaticAnalyser.cpp @@ -67,6 +67,8 @@ void StaticAnalyser::Acorn::AddTargets( Target target; target.machine = Target::Electron; target.probability = 1.0; // TODO: a proper estimation + target.acorn.has_dfs = false; + target.acorn.has_adfs = false; // strip out inappropriate cartridges target.cartridges = AcornCartridgesFrom(cartridges); @@ -111,13 +113,19 @@ void StaticAnalyser::Acorn::AddTargets( } } - // TODO: disks if(disks.size() > 0) { std::shared_ptr disk = disks.front(); std::unique_ptr dfs_catalogue = GetDFSCatalogue(disk); + if(dfs_catalogue) + { + target.disks = disks; + target.acorn.has_dfs = true; + + // TODO: what about booting? + } } - if(target.tapes.size() || target.cartridges.size()) + if(target.tapes.size() || target.disks.size() || target.cartridges.size()) destination.push_back(target); } From a9e65e9b7a3dcf4aaf517c3fcad79ea365a469ae Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 19 Sep 2016 22:06:56 -0400 Subject: [PATCH 23/51] Tweaked disk side density, added call-outs to a WD1770 if the Electron had one (albeit without `run_for_cycles` yet as I need to figure out the clock rate), added a shell of the basic functions of the WD1770. No implementation yet. --- Components/1770/1770.cpp | 23 ++ Components/1770/1770.hpp | 5 +- Machines/Electron/Electron.cpp | 530 +++++++++++++++++---------------- Machines/Electron/Electron.hpp | 4 + StaticAnalyser/Acorn/Disk.cpp | 2 +- Storage/Disk/Encodings/MFM.cpp | 4 +- 6 files changed, 308 insertions(+), 260 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 2c7ba3426..62aa5f5e3 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -7,3 +7,26 @@ // #include "1770.hpp" + +using namespace WD; + +void WD1770::set_drive(std::shared_ptr drive) +{ +} + +void WD1770::set_is_double_density(bool is_double_density) +{ +} + +void WD1770::set_register(int address, uint8_t value) +{ +} + +uint8_t WD1770::get_register(int address) +{ + return 0; +} + +void WD1770::run_for_cycles(unsigned int number_of_cycles) +{ +} diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 32941fc1d..48187087e 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -15,11 +15,12 @@ namespace WD { class WD1770 { public: - void set_drive(std::shared_ptr drive); void set_is_double_density(bool is_double_density); void set_register(int address, uint8_t value); - void get_register(int address); + uint8_t get_register(int address); + + void run_for_cycles(unsigned int number_of_cycles); }; } diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 795f288f8..ec44dd248 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -120,276 +120,296 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { - if(address >= 0xc000) + switch(address & 0xff0f) { - if((address & 0xff00) == 0xfe00) - { - switch(address&0xf) - { - case 0x0: - if(isReadOperation(operation)) - { - *value = _interrupt_status; - _interrupt_status &= ~PowerOnReset; - } - else - { - _interrupt_control = (*value) & ~1; - evaluate_interrupts(); - } - break; - case 0x1: - break; - case 0x2: - if(!isReadOperation(operation)) - { - _startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1); - if(!_startScreenAddress) _startScreenAddress |= 0x8000; - } - break; - case 0x3: - if(!isReadOperation(operation)) - { - _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); - if(!_startScreenAddress) _startScreenAddress |= 0x8000; - } - break; - case 0x4: - if(isReadOperation(operation)) - { - *value = _tape.get_data_register(); - _tape.clear_interrupts(Interrupt::ReceiveDataFull); - } - else - { - _tape.set_data_register(*value); - _tape.clear_interrupts(Interrupt::TransmitDataEmpty); - } - break; - case 0x5: - if(!isReadOperation(operation)) - { - const uint8_t interruptDisable = (*value)&0xf0; - if( interruptDisable ) - { - if( interruptDisable&0x10 ) _interrupt_status &= ~Interrupt::DisplayEnd; - if( interruptDisable&0x20 ) _interrupt_status &= ~Interrupt::RealTimeClock; - if( interruptDisable&0x40 ) _interrupt_status &= ~Interrupt::HighToneDetect; - evaluate_interrupts(); - - // TODO: NMI - } - - // latch the paged ROM in case external hardware is being emulated - _active_rom = (Electron::ROMSlot)(*value & 0xf); - - // apply the ULA's test - if(*value & 0x08) - { - if(*value & 0x04) - { - _keyboard_is_active = false; - _basic_is_active = false; - } - else - { - _keyboard_is_active = !(*value & 0x02); - _basic_is_active = !_keyboard_is_active; - } - } - } - break; - case 0x6: - if(!isReadOperation(operation)) - { - update_audio(); - _speaker->set_divider(*value); - _tape.set_counter(*value); - } - break; - case 0x7: - if(!isReadOperation(operation)) - { - // update screen mode - uint8_t new_screen_mode = ((*value) >> 3)&7; - if(new_screen_mode == 7) new_screen_mode = 4; - if(new_screen_mode != _screen_mode) - { -// printf("To mode %d, at %d cycles into field (%d)\n", new_screen_mode, _fieldCycles, _fieldCycles >> 7); - update_display(); - _screen_mode = new_screen_mode; - switch(_screen_mode) - { - case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; - case 3: _screenModeBaseAddress = 0x4000; break; - case 4: case 5: _screenModeBaseAddress = 0x5800; break; - case 6: _screenModeBaseAddress = 0x6000; break; - } - } - - // update speaker mode - bool new_speaker_is_enabled = (*value & 6) == 2; - if(new_speaker_is_enabled != _speaker->get_is_enabled()) - { - update_audio(); - _speaker->set_is_enabled(new_speaker_is_enabled); - _tape.set_is_enabled(!new_speaker_is_enabled); - } - - _tape.set_is_running(((*value)&0x40) ? true : false); - _tape.set_is_in_input_mode(((*value)&0x04) ? false : true); - - // TODO: caps lock LED - } - break; - default: - { - if(!isReadOperation(operation)) - { - update_display(); - - static const int registers[4][4] = { - {10, 8, 2, 0}, - {14, 12, 6, 4}, - {15, 13, 7, 5}, - {11, 9, 3, 1}, - }; - const int index = (address >> 1)&3; - const uint8_t colour = ~(*value); - if(address&1) - { - _palette[registers[index][0]] = (_palette[registers[index][0]]&3) | ((colour >> 1)&4); - _palette[registers[index][1]] = (_palette[registers[index][1]]&3) | ((colour >> 0)&4); - _palette[registers[index][2]] = (_palette[registers[index][2]]&3) | ((colour << 1)&4); - _palette[registers[index][3]] = (_palette[registers[index][3]]&3) | ((colour << 2)&4); - - _palette[registers[index][2]] = (_palette[registers[index][2]]&5) | ((colour >> 4)&2); - _palette[registers[index][3]] = (_palette[registers[index][3]]&5) | ((colour >> 3)&2); - } - else - { - _palette[registers[index][0]] = (_palette[registers[index][0]]&6) | ((colour >> 7)&1); - _palette[registers[index][1]] = (_palette[registers[index][1]]&6) | ((colour >> 6)&1); - _palette[registers[index][2]] = (_palette[registers[index][2]]&6) | ((colour >> 5)&1); - _palette[registers[index][3]] = (_palette[registers[index][3]]&6) | ((colour >> 4)&1); - - _palette[registers[index][0]] = (_palette[registers[index][0]]&5) | ((colour >> 2)&2); - _palette[registers[index][1]] = (_palette[registers[index][1]]&5) | ((colour >> 1)&2); - } - - // regenerate all palette tables for now -#define pack(a, b) (uint8_t)((a << 4) | (b)) - for(int byte = 0; byte < 256; byte++) - { - uint8_t *target = (uint8_t *)&_paletteTables.forty1bpp[byte]; - target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); - target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); - - target = (uint8_t *)&_paletteTables.eighty2bpp[byte]; - target[0] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); - target[1] = pack(_palette[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], _palette[((byte&0x10) >> 1) | ((byte&0x01) << 1)]); - - target = (uint8_t *)&_paletteTables.eighty1bpp[byte]; - target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); - target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); - target[2] = pack(_palette[(byte&0x08) >> 0], _palette[(byte&0x04) << 1]); - target[3] = pack(_palette[(byte&0x02) << 2], _palette[(byte&0x01) << 3]); - - _paletteTables.forty2bpp[byte] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); - _paletteTables.eighty4bpp[byte] = pack( _palette[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)], - _palette[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]); - } -#undef pack - } - } - break; - } - } - else - { + case 0xfe00: if(isReadOperation(operation)) { - if( - _use_fast_tape_hack && - _tape.has_tape() && - (operation == CPU6502::BusOperation::ReadOpcode) && - ( - (address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 - (address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling - (address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM - (address == 0xfa51) || (address == 0xfa52) || // pathway. - - (address == 0xf0a8) // 0xf0a8 is from where a service call would normally be - // dispatched; we can check whether it would be call 14 - // (i.e. read byte) and, if so, whether the OS was about to - // issue a read byte call to a ROM despite being the tape - // FS being selected. If so then this is a get byte that - // we should service synthetically. Put the byte into Y - // and set A to zero to report that action was taken, then - // allow the PC read to return an RTS. - ) - ) + *value = _interrupt_status; + _interrupt_status &= ~PowerOnReset; + } + else + { + _interrupt_control = (*value) & ~1; + evaluate_interrupts(); + } + break; + case 0xfe02: + if(!isReadOperation(operation)) + { + _startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1); + if(!_startScreenAddress) _startScreenAddress |= 0x8000; + } + break; + case 0xfe03: + if(!isReadOperation(operation)) + { + _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); + if(!_startScreenAddress) _startScreenAddress |= 0x8000; + } + break; + case 0xfe04: + if(isReadOperation(operation)) + { + *value = _tape.get_data_register(); + _tape.clear_interrupts(Interrupt::ReceiveDataFull); + } + else + { + _tape.set_data_register(*value); + _tape.clear_interrupts(Interrupt::TransmitDataEmpty); + } + break; + case 0xfe05: + if(!isReadOperation(operation)) + { + const uint8_t interruptDisable = (*value)&0xf0; + if( interruptDisable ) { - uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X); - if(address == 0xf0a8) + if( interruptDisable&0x10 ) _interrupt_status &= ~Interrupt::DisplayEnd; + if( interruptDisable&0x20 ) _interrupt_status &= ~Interrupt::RealTimeClock; + if( interruptDisable&0x40 ) _interrupt_status &= ~Interrupt::HighToneDetect; + evaluate_interrupts(); + + // TODO: NMI + } + + // latch the paged ROM in case external hardware is being emulated + _active_rom = (Electron::ROMSlot)(*value & 0xf); + + // apply the ULA's test + if(*value & 0x08) + { + if(*value & 0x04) { - if(!_ram[0x247] && service_call == 14) - { - _tape.set_delegate(nullptr); - - // TODO: handle tape wrap around. - - int cycles_left_while_plausibly_in_data = 50; - _tape.clear_interrupts(Interrupt::ReceiveDataFull); - while(1) - { - _tape.run_for_input_pulse(); - cycles_left_while_plausibly_in_data--; - if(!cycles_left_while_plausibly_in_data) _fast_load_is_in_data = false; - if( (_tape.get_interrupt_status() & Interrupt::ReceiveDataFull) && - (_fast_load_is_in_data || _tape.get_data_register() == 0x2a) - ) break; - } - _tape.set_delegate(this); - _tape.clear_interrupts(Interrupt::ReceiveDataFull); - _interrupt_status |= _tape.get_interrupt_status(); - - _fast_load_is_in_data = true; - set_value_of_register(CPU6502::Register::A, 0); - set_value_of_register(CPU6502::Register::Y, _tape.get_data_register()); - *value = 0x60; // 0x60 is RTS - } - else - *value = _os[address & 16383]; + _keyboard_is_active = false; + _basic_is_active = false; } else - *value = 0xea; + { + _keyboard_is_active = !(*value & 0x02); + _basic_is_active = !_keyboard_is_active; + } + } + } + break; + case 0xfe06: + if(!isReadOperation(operation)) + { + update_audio(); + _speaker->set_divider(*value); + _tape.set_counter(*value); + } + break; + case 0xfe07: + if(!isReadOperation(operation)) + { + // update screen mode + uint8_t new_screen_mode = ((*value) >> 3)&7; + if(new_screen_mode == 7) new_screen_mode = 4; + if(new_screen_mode != _screen_mode) + { + update_display(); + _screen_mode = new_screen_mode; + switch(_screen_mode) + { + case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; + case 3: _screenModeBaseAddress = 0x4000; break; + case 4: case 5: _screenModeBaseAddress = 0x5800; break; + case 6: _screenModeBaseAddress = 0x6000; break; + } + } + + // update speaker mode + bool new_speaker_is_enabled = (*value & 6) == 2; + if(new_speaker_is_enabled != _speaker->get_is_enabled()) + { + update_audio(); + _speaker->set_is_enabled(new_speaker_is_enabled); + _tape.set_is_enabled(!new_speaker_is_enabled); + } + + _tape.set_is_running(((*value)&0x40) ? true : false); + _tape.set_is_in_input_mode(((*value)&0x04) ? false : true); + + // TODO: caps lock LED + } + break; + case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: + { + if(!isReadOperation(operation)) + { + update_display(); + + static const int registers[4][4] = { + {10, 8, 2, 0}, + {14, 12, 6, 4}, + {15, 13, 7, 5}, + {11, 9, 3, 1}, + }; + const int index = (address >> 1)&3; + const uint8_t colour = ~(*value); + if(address&1) + { + _palette[registers[index][0]] = (_palette[registers[index][0]]&3) | ((colour >> 1)&4); + _palette[registers[index][1]] = (_palette[registers[index][1]]&3) | ((colour >> 0)&4); + _palette[registers[index][2]] = (_palette[registers[index][2]]&3) | ((colour << 1)&4); + _palette[registers[index][3]] = (_palette[registers[index][3]]&3) | ((colour << 2)&4); + + _palette[registers[index][2]] = (_palette[registers[index][2]]&5) | ((colour >> 4)&2); + _palette[registers[index][3]] = (_palette[registers[index][3]]&5) | ((colour >> 3)&2); } else { - *value = _os[address & 16383]; + _palette[registers[index][0]] = (_palette[registers[index][0]]&6) | ((colour >> 7)&1); + _palette[registers[index][1]] = (_palette[registers[index][1]]&6) | ((colour >> 6)&1); + _palette[registers[index][2]] = (_palette[registers[index][2]]&6) | ((colour >> 5)&1); + _palette[registers[index][3]] = (_palette[registers[index][3]]&6) | ((colour >> 4)&1); + + _palette[registers[index][0]] = (_palette[registers[index][0]]&5) | ((colour >> 2)&2); + _palette[registers[index][1]] = (_palette[registers[index][1]]&5) | ((colour >> 1)&2); } - } - } - } - else - { - if(isReadOperation(operation)) - { - *value = _roms[_active_rom][address & 16383]; - if(_keyboard_is_active) - { - *value &= 0xf0; - for(int address_line = 0; address_line < 14; address_line++) + + // regenerate all palette tables for now +#define pack(a, b) (uint8_t)((a << 4) | (b)) + for(int byte = 0; byte < 256; byte++) { - if(!(address&(1 << address_line))) *value |= _key_states[address_line]; + uint8_t *target = (uint8_t *)&_paletteTables.forty1bpp[byte]; + target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); + target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); + + target = (uint8_t *)&_paletteTables.eighty2bpp[byte]; + target[0] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); + target[1] = pack(_palette[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], _palette[((byte&0x10) >> 1) | ((byte&0x01) << 1)]); + + target = (uint8_t *)&_paletteTables.eighty1bpp[byte]; + target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); + target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); + target[2] = pack(_palette[(byte&0x08) >> 0], _palette[(byte&0x04) << 1]); + target[3] = pack(_palette[(byte&0x02) << 2], _palette[(byte&0x01) << 3]); + + _paletteTables.forty2bpp[byte] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); + _paletteTables.eighty4bpp[byte] = pack( _palette[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)], + _palette[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]); } - } - if(_basic_is_active) - { - *value &= _roms[ROMSlotBASIC][address & 16383]; +#undef pack } } + break; + + case 0xfcc4: case 0xfcc5: case 0xfcc6: case 0xfcc7: + if(_wd1770 && (address&0x00f0) == 0x00c0) + { + if(isReadOperation(operation)) + *value = _wd1770->get_register(address); + else + _wd1770->set_register(address, *value); + } + break; + case 0xfcc0: + if(_wd1770 && (address&0x00f0) == 0x00c0) + { + if(isReadOperation(operation)) + *value = 1; + else + { + // TODO: + // bit 0 => enable or disable drive 1 + // bit 1 => enable or disable drive 2 + // bit 2 => side select + // bit 3 => single density select +// _wd1770->set_register(address, *value); + } + } + break; + + default: + if(address >= 0xc000) + { + if(isReadOperation(operation)) + { + if( + _use_fast_tape_hack && + _tape.has_tape() && + (operation == CPU6502::BusOperation::ReadOpcode) && + ( + (address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 + (address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling + (address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM + (address == 0xfa51) || (address == 0xfa52) || // pathway. + + (address == 0xf0a8) // 0xf0a8 is from where a service call would normally be + // dispatched; we can check whether it would be call 14 + // (i.e. read byte) and, if so, whether the OS was about to + // issue a read byte call to a ROM despite being the tape + // FS being selected. If so then this is a get byte that + // we should service synthetically. Put the byte into Y + // and set A to zero to report that action was taken, then + // allow the PC read to return an RTS. + ) + ) + { + uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X); + if(address == 0xf0a8) + { + if(!_ram[0x247] && service_call == 14) + { + _tape.set_delegate(nullptr); + + // TODO: handle tape wrap around. + + int cycles_left_while_plausibly_in_data = 50; + _tape.clear_interrupts(Interrupt::ReceiveDataFull); + while(1) + { + _tape.run_for_input_pulse(); + cycles_left_while_plausibly_in_data--; + if(!cycles_left_while_plausibly_in_data) _fast_load_is_in_data = false; + if( (_tape.get_interrupt_status() & Interrupt::ReceiveDataFull) && + (_fast_load_is_in_data || _tape.get_data_register() == 0x2a) + ) break; + } + _tape.set_delegate(this); + _tape.clear_interrupts(Interrupt::ReceiveDataFull); + _interrupt_status |= _tape.get_interrupt_status(); + + _fast_load_is_in_data = true; + set_value_of_register(CPU6502::Register::A, 0); + set_value_of_register(CPU6502::Register::Y, _tape.get_data_register()); + *value = 0x60; // 0x60 is RTS + } + else + *value = _os[address & 16383]; + } + else + *value = 0xea; + } + else + { + *value = _os[address & 16383]; + } + } + } + else + { + if(isReadOperation(operation)) + { + *value = _roms[_active_rom][address & 16383]; + if(_keyboard_is_active) + { + *value &= 0xf0; + for(int address_line = 0; address_line < 14; address_line++) + { + if(!(address&(1 << address_line))) *value |= _key_states[address_line]; + } + } + if(_basic_is_active) + { + *value &= _roms[ROMSlotBASIC][address & 16383]; + } + } + } + break; } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 10a6f33b8..57c2e6a60 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -10,6 +10,7 @@ #define Electron_hpp #include "../../Processors/6502/CPU6502.hpp" +#include "../../Components/1770/1770.hpp" #include "../../Storage/Tape/Tape.hpp" #include "../ConfigurationTarget.hpp" @@ -227,6 +228,9 @@ class Machine: bool _use_fast_tape_hack; bool _fast_load_is_in_data; + // Disk + std::unique_ptr _wd1770; + // Outputs std::shared_ptr _crt; std::shared_ptr _speaker; diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index d4023525e..72cf144eb 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -26,7 +26,7 @@ class FMParser: public Storage::Disk::Drive { Storage::Time bit_length; bit_length.length = 1; - bit_length.clock_rate = 250000; // i.e. 250 kbps + bit_length.clock_rate = 250000; // i.e. 250 kbps (including clocks) set_expected_bit_length(bit_length); } diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 6412e3e2e..cc48d0520 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -199,7 +199,7 @@ std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSec 6, 0, 17, 14, 0, - 6400); + 6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation } std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector §ors) @@ -213,5 +213,5 @@ std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSe 12, 22, 12, 18, 32, - 12800); + 12500); // unintelligently: double the single-density bytes/rotation (or: 500kps @ 300 rpm) } From cd5939501f6e1013d614d2eaa199a29870ab7ea4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 20 Sep 2016 07:36:57 -0400 Subject: [PATCH 24/51] =?UTF-8?q?Treading=20water=20some=20more,=20ensured?= =?UTF-8?q?=20the=20DFS=20and=20ADFS=20ROMs=20get=20to=20the=20Electron.?= =?UTF-8?q?=20It=20now=20even=20inserts=20the=20DFS=20ROM=20if=20it=20has?= =?UTF-8?q?=20a=20DFS=20disk=20image.=20Might=20need=20to=20make=20it=20a?= =?UTF-8?q?=20sideways=20RAM=20though=3F=20Regardless,=20the=20next=20job?= =?UTF-8?q?=20surely=20=E2=80=94=20surely!=20=E2=80=94=20has=20to=20be=20t?= =?UTF-8?q?o=20stop=20avoiding=20the=201770=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Machines/Electron/Electron.cpp | 19 +++++++++++++++++-- Machines/Electron/Electron.hpp | 6 ++++-- .../Clock Signal.xcodeproj/project.pbxproj | 6 ++++++ .../Documents/ElectronDocument.swift | 16 ++++++++++++++-- .../Clock Signal/Machine/NSData+StdVector.h | 18 ++++++++++++++++++ .../Clock Signal/Machine/NSData+StdVector.mm | 19 +++++++++++++++++++ .../Machine/Wrappers/CSElectron.h | 3 ++- .../Machine/Wrappers/CSElectron.mm | 18 ++++++------------ ROMImages/Electron/readme.txt | 6 +++--- 9 files changed, 89 insertions(+), 22 deletions(-) create mode 100644 OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.h create mode 100644 OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.mm diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index ec44dd248..dbf2ab008 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -485,22 +485,37 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) _tape.set_tape(target.tapes.front()); } + if(target.disks.size()) + { + _wd1770.reset(new WD::WD1770); + + if(target.acorn.has_dfs) + { + set_rom(ROMSlot0, _dfs); + } + + // TODO: actually insert the disk, why not? + } + if(target.loadingCommand.length()) // TODO: and automatic loading option enabled { set_typer_for_string(target.loadingCommand.c_str()); } } -void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) +void Machine::set_rom(ROMSlot slot, std::vector data) { uint8_t *target = nullptr; switch(slot) { + case ROMSlotDFS: _dfs = data; return; + case ROMSlotADFS: _adfs = data; return; + case ROMSlotOS: target = _os; break; default: target = _roms[slot]; break; } - memcpy(target, data, std::min((size_t)16384, length)); + memcpy(target, &data[0], std::min((size_t)16384, data.size())); } inline void Machine::signal_interrupt(Electron::Interrupt interrupt) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 57c2e6a60..2b3b85f14 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -18,6 +18,7 @@ #include "../Typer.hpp" #include +#include namespace Electron { @@ -31,7 +32,7 @@ enum ROMSlot: uint8_t { ROMSlot12, ROMSlot13, ROMSlot14, ROMSlot15, - ROMSlotOS + ROMSlotOS, ROMSlotDFS, ROMSlotADFS }; enum Interrupt: uint8_t { @@ -147,7 +148,7 @@ class Machine: public: Machine(); - void set_rom(ROMSlot slot, size_t length, const uint8_t *data); + void set_rom(ROMSlot slot, std::vector data); void configure_as_target(const StaticAnalyser::Target &target); void set_key_state(Key key, bool isPressed); @@ -189,6 +190,7 @@ class Machine: // Things that directly constitute the memory map. uint8_t _roms[16][16384]; uint8_t _os[16384], _ram[32768]; + std::vector _dfs, _adfs; // Things affected by registers, explicitly or otherwise. uint8_t _interrupt_status, _interrupt_control; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index adfd9fa15..f9b4aa0f6 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; }; 4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; }; 4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA22B051D8817CE0008C640 /* Disk.cpp */; }; + 4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; }; 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA799931D8B656E0045123D /* StaticAnalyser.cpp */; }; 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */; }; 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */; }; @@ -464,6 +465,8 @@ 4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = ""; }; 4BA22B051D8817CE0008C640 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Commodore/Disk.cpp; sourceTree = ""; }; 4BA22B061D8817CE0008C640 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Commodore/Disk.hpp; sourceTree = ""; }; + 4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = ""; }; + 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = ""; }; 4BA799931D8B656E0045123D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.cpp; sourceTree = ""; }; 4BA799941D8B656E0045123D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.hpp; sourceTree = ""; }; 4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = ""; }; @@ -922,6 +925,8 @@ 4B2A53961D117D36003C6002 /* CSMachine.mm */, 4B643F3B1D77AD6D00D431D6 /* StaticAnalyser */, 4B2A53981D117D36003C6002 /* Wrappers */, + 4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */, + 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */, ); path = Machine; sourceTree = ""; @@ -2118,6 +2123,7 @@ 4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */, 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, 4BF829691D8F7361001BAE39 /* File.cpp in Sources */, + 4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */, 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */, 4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */, 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 73aab5fee..a577b2b77 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -31,15 +31,27 @@ class ElectronDocument: MachineDocument { return dataForResource(name, ofType: "rom", inDirectory: "ROMImages/Electron") } - override func windowControllerDidLoadNib(_ aController: NSWindowController) { - super.windowControllerDidLoadNib(aController) + override init() { + super.init(); if let os = rom("os"), let basic = rom("basic") { self.electron.setOSROM(os) self.electron.setBASICROM(basic) } + if let dfs = rom("DFS-1770-2.20") { + self.electron.setDFSROM(dfs) + } + if let adfs1 = rom("ADFS-E00_1"), let adfs2 = rom("ADFS-E00_2") { + var fullADFS = adfs1 + fullADFS.append(adfs2) + self.electron.setADFSROM(fullADFS as Data) + } } +// override func windowControllerDidLoadNib(_ aController: NSWindowController) { +// super.windowControllerDidLoadNib(aController) +// } + override var windowNibName: String? { return "ElectronDocument" } diff --git a/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.h b/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.h new file mode 100644 index 000000000..3e1749ef9 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.h @@ -0,0 +1,18 @@ +// +// NSData+StdVector.h +// Clock Signal +// +// Created by Thomas Harte on 20/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import + +#include +#include + +@interface NSData (StdVector) + +- (std::vector)stdVector8; + +@end diff --git a/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.mm b/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.mm new file mode 100644 index 000000000..88a32c457 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/NSData+StdVector.mm @@ -0,0 +1,19 @@ +// +// NSData+StdVector.m +// Clock Signal +// +// Created by Thomas Harte on 20/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "NSData+StdVector.h" + +@implementation NSData (StdVector) + +- (std::vector)stdVector8 +{ + uint8_t *bytes8 = (uint8_t *)self.bytes; + return std::vector(bytes8, bytes8 + self.length); +} + +@end diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.h index 13e44e4f8..d96647745 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.h @@ -16,7 +16,8 @@ - (void)setOSROM:(nonnull NSData *)rom; - (void)setBASICROM:(nonnull NSData *)rom; -- (void)setROM:(nonnull NSData *)rom slot:(int)slot; +- (void)setDFSROM:(nonnull NSData *)rom; +- (void)setADFSROM:(nonnull NSData *)rom; @property (nonatomic, assign) BOOL useFastLoadingHack; @property (nonatomic, assign) BOOL useTelevisionOutput; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm index d3635d2a5..ad3481dd3 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm @@ -10,6 +10,7 @@ #include "Electron.hpp" #import "CSMachine+Subclassing.h" +#import "NSData+StdVector.h" #include "StaticAnalyser.hpp" #include "TapeUEF.hpp" @@ -25,21 +26,14 @@ StaticAnalyser::GetTargets([url fileSystemRepresentation]); } -- (void)setOSROM:(nonnull NSData *)rom { - @synchronized(self) { - _electron.set_rom(Electron::ROMSlotOS, rom.length, (const uint8_t *)rom.bytes); - } -} - -- (void)setBASICROM:(nonnull NSData *)rom { - @synchronized(self) { - _electron.set_rom(Electron::ROMSlotBASIC, rom.length, (const uint8_t *)rom.bytes); - } -} +- (void)setOSROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotOS]; } +- (void)setBASICROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotBASIC]; } +- (void)setADFSROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotADFS]; } +- (void)setDFSROM:(nonnull NSData *)rom { [self setROM:rom slot:Electron::ROMSlotDFS]; } - (void)setROM:(nonnull NSData *)rom slot:(int)slot { @synchronized(self) { - _electron.set_rom((Electron::ROMSlot)slot, rom.length, (const uint8_t *)rom.bytes); + _electron.set_rom((Electron::ROMSlot)slot, rom.stdVector8); } } diff --git a/ROMImages/Electron/readme.txt b/ROMImages/Electron/readme.txt index 8e81ed5e6..accfc031e 100644 --- a/ROMImages/Electron/readme.txt +++ b/ROMImages/Electron/readme.txt @@ -5,12 +5,12 @@ Expected files: basic.rom os.rom plus1.rom +DFS-1770-2.20.rom +ADFS-E00_1.rom +ADFS-E00_2.rom Likely to be desired in the future: adfs.rom -ADFS-E00_1.rom -ADFS-E00_2.rom -DFSE00r3.rom ElectronExpansionRomPresAP2-v1.23.rom os300.rom From e358057440aee3984b3d62c48b81ef6d86cd4ad4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 20 Sep 2016 07:42:17 -0400 Subject: [PATCH 25/51] Added a WD1770 `run_for_cycles` call. So I really can now move that as far as reporting that it has no disk? --- Machines/Electron/Electron.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index dbf2ab008..8c25148d1 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -468,6 +468,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _tape.run_for_cycles(cycles); if(_typer) _typer->update((int)cycles); + if(_wd1770) _wd1770->run_for_cycles(4*cycles); return cycles; } From 3b97b038b9484b81800d31f8c2037eed406b5e33 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 20 Sep 2016 15:56:31 -0400 Subject: [PATCH 26/51] Corrected and sketched out state machine far enough to get to a complaint about unhandled work. --- Components/1770/1770.cpp | 45 +++++++++++++++++++++++++++++++++- Components/1770/1770.hpp | 31 +++++++++++++++++++++++ Machines/Electron/Electron.cpp | 8 ++++-- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 62aa5f5e3..e1a800c5f 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -10,6 +10,8 @@ using namespace WD; +WD1770::WD1770() : state_(State::Waiting), status_(0), has_command_(false) {} + void WD1770::set_drive(std::shared_ptr drive) { } @@ -20,13 +22,54 @@ void WD1770::set_is_double_density(bool is_double_density) void WD1770::set_register(int address, uint8_t value) { + switch(address&3) + { + case 0: + command_ = value; + has_command_ = true; + // TODO: is this force interrupt? + break; + case 1: track_ = value; break; + case 2: sector_ = value; break; + case 3: data_ = value; break; + } } uint8_t WD1770::get_register(int address) { - return 0; + switch(address&3) + { + default: return status_; + case 1: return track_; + case 2: return sector_; + case 3: return data_; + } } void WD1770::run_for_cycles(unsigned int number_of_cycles) { + // perform one step every eight cycles, arbitrariy as I can find no timing documentation + cycles += number_of_cycles; + while(cycles > 8) + { + cycles -= 8; + + switch(state_) + { + case State::Waiting: + if(has_command_) + { + has_command_ = false; + if(command_ & 0x80) + state_ = (command_ & 0x40) ? State::BeginType3 : State::BeginType2; + else + state_ = State::BeginType1; + } + continue; + + default: + printf("Unhandled state %d!", state_); + return; + } + } } diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 48187087e..735199f3d 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -15,12 +15,43 @@ namespace WD { class WD1770 { public: + WD1770(); + void set_drive(std::shared_ptr drive); void set_is_double_density(bool is_double_density); void set_register(int address, uint8_t value); uint8_t get_register(int address); void run_for_cycles(unsigned int number_of_cycles); + + enum Flag: uint8_t { + MotorOn = 0x80, + WriteProtect = 0x40, + RecordType = 0x20, + SpinUp = 0x20, + RecordNotFound = 0x10, + CRCError = 0x08, + LostData = 0x04, + TrackZero = 0x04, + DataRequest = 0x02, + Index = 0x02, + Busy = 0x01 + }; + + private: + unsigned int cycles; + + enum class State { + Waiting, + BeginType1, BeginType2, BeginType3 + } state_; + + uint8_t status_; + uint8_t track_; + uint8_t sector_; + uint8_t data_; + uint8_t command_; + bool has_command_; }; } diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 8c25148d1..95fd858ca 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -120,6 +120,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { +// if((address >> 8) == 0xfc) +// { +// printf("d"); +// } switch(address & 0xff0f) { case 0xfe00: @@ -297,7 +301,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } break; - case 0xfcc4: case 0xfcc5: case 0xfcc6: case 0xfcc7: + case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: if(_wd1770 && (address&0x00f0) == 0x00c0) { if(isReadOperation(operation)) @@ -306,7 +310,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _wd1770->set_register(address, *value); } break; - case 0xfcc0: + case 0xfc00: if(_wd1770 && (address&0x00f0) == 0x00c0) { if(isReadOperation(operation)) From 7a34ae0da6e55a20a84919b2e798eaf65a50d106 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 20 Sep 2016 16:58:23 -0400 Subject: [PATCH 27/51] This is the start of my slog through type 1 commands. I kind of need to figure out what I'm doing about drives and PLLs now though. --- Components/1770/1770.cpp | 96 +++++++++++++++++++++++++++++++++++++++- Components/1770/1770.hpp | 17 ++++++- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index e1a800c5f..50545fa29 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -67,8 +67,102 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) } continue; + case State::BeginType1: + status_ |= Flag::Busy; + status_ &= ~(Flag::DataRequest | Flag::CRCError); + set_interrupt_request(false); + state_ = State::BeginType1PostSpin; + if(command_ & 0x08) + { + wait_six_index_pulses_.next_state = state_; + wait_six_index_pulses_.count = 0; + state_ = State::WaitForSixIndexPulses; + } + continue; + +// case State::WaitForSixIndexPulses: +// continue; + + case State::BeginType1PostSpin: + switch(command_ >> 4) + { + case 0: // restore + track_ = 0xff; // deliberate fallthrough + case 1: // seek + data_ = 0x00; + break; + case 2: case 3: // step + break; + case 4: case 5: // step in + is_step_in_ = true; + break; + case 6: case 7: // step out + is_step_in_ = false; + break; + } + + if(!(command_ >> 5)) + state_ = State::TestTrack; + else + state_ = (command_ & 0x10) ? State::TestDirection : State::TestHead; + continue; + + case State::TestTrack: + data_shift_register_ = data_; + if(track_ == data_shift_register_) + state_ = State::TestVerify; + else + { + is_step_in_ = (data_shift_register_ < track_); + state_ = State::TestDirection; + } + continue; + + case State::TestDirection: + track_ += is_step_in_ ? -1 : +1; + state_ = State::TestHead; + continue; + +// case State::TestHead: +// break; + + case State::TestVerify: + if(command_ & 0x04) + { + state_ = State::VerifyTrack; + } + else + { + set_interrupt_request(true); + status_ &= ~Flag::Busy; + state_ = State::Waiting; + } + break; + +// +------+----------+-------------------------+ +// ! ! ! BITS ! +// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 ! +// +------+----------+-------------------------+ +// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 ! +// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 ! +// ! 1 ! Step ! 0 0 1 u h v r1 r0 ! +// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 ! +// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 ! +// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 ! +// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 ! +// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 ! +// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 ! +// ! 3 ! Wt track ! 1 1 1 1 h E P 0 ! +// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 ! +// +------+----------+-------------------------+ + default: - printf("Unhandled state %d!", state_); + { + static bool has_hit_error = false; + if(!has_hit_error) + printf("Unhandled state %d!\n", state_); + has_hit_error = true; + } return; } } diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 735199f3d..0bd5e641d 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -43,15 +43,30 @@ class WD1770 { enum class State { Waiting, - BeginType1, BeginType2, BeginType3 + BeginType1, BeginType2, BeginType3, + BeginType1PostSpin, + WaitForSixIndexPulses, + TestTrack, TestDirection, TestHead, + TestVerify, VerifyTrack } state_; + union { + struct { + int count; + State next_state; + } wait_six_index_pulses_; + }; + uint8_t status_; uint8_t track_; uint8_t sector_; uint8_t data_; uint8_t command_; bool has_command_; + + void set_interrupt_request(bool interrupt_request) {} + bool is_step_in_; + uint8_t data_shift_register_; }; } From 8db0030068b386e4141b4726d7b1cfaf1030a59d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 20 Sep 2016 22:14:33 -0400 Subject: [PATCH 28/51] Fixed ROM loading by the Electron, turned the WD1770 into a 'disk drive' (it'll do for now), persuaded it to get all the way through a very specifically convenient type 1 command. --- Components/1770/1770.cpp | 43 +++++++++++++++++++++++++++++----- Components/1770/1770.hpp | 13 +++++++--- Machines/Electron/Electron.cpp | 9 ++++++- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 50545fa29..ff7d8c166 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -8,13 +8,17 @@ #include "1770.hpp" + using namespace WD; -WD1770::WD1770() : state_(State::Waiting), status_(0), has_command_(false) {} +WD1770::WD1770() : + Storage::Disk::Drive(1000000, 8, 300), + state_(State::Waiting), status_(0), has_command_(false) {} -void WD1770::set_drive(std::shared_ptr drive) -{ -} +//void WD1770::set_disk(std::shared_ptr disk) +//{ +// drive_.reset(new Storage::Disk::Drive(1000000, 8, 300)); +//} void WD1770::set_is_double_density(bool is_double_density) { @@ -123,8 +127,27 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) state_ = State::TestHead; continue; -// case State::TestHead: -// break; + case State::TestHead: + if(get_is_track_zero() && !is_step_in_) + { + track_ = 0; + state_ = State::TestVerify; + } + else + { + step(is_step_in_ ? 1 : -1); + state_ = State::StepDelay; + step_delay_.count = 0; + } + break; + + case State::StepDelay: + if(step_delay_.count == (command_&3)) + { + state_ = (command_ >> 5) ? State::TestVerify : State::TestTrack; + } + step_delay_.count++; + break; case State::TestVerify: if(command_ & 0x04) @@ -167,3 +190,11 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) } } } + +void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) +{ +} + +void WD1770::process_index_hole() +{ +} diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 0bd5e641d..74ccc1f5f 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -13,11 +13,11 @@ namespace WD { -class WD1770 { +class WD1770: public Storage::Disk::Drive { public: WD1770(); - void set_drive(std::shared_ptr drive); +// void set_disk(std::shared_ptr disk); void set_is_double_density(bool is_double_density); void set_register(int address, uint8_t value); uint8_t get_register(int address); @@ -47,7 +47,8 @@ class WD1770 { BeginType1PostSpin, WaitForSixIndexPulses, TestTrack, TestDirection, TestHead, - TestVerify, VerifyTrack + TestVerify, VerifyTrack, + StepDelay } state_; union { @@ -55,6 +56,9 @@ class WD1770 { int count; State next_state; } wait_six_index_pulses_; + struct { + int count; + } step_delay_; }; uint8_t status_; @@ -67,6 +71,9 @@ class WD1770 { void set_interrupt_request(bool interrupt_request) {} bool is_step_in_; uint8_t data_shift_register_; + + virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); + virtual void process_index_hole(); }; } diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 95fd858ca..eda25f817 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -499,7 +499,14 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) set_rom(ROMSlot0, _dfs); } - // TODO: actually insert the disk, why not? + _wd1770->set_disk(target.disks.front()); + } + + ROMSlot slot = ROMSlot12; + for(std::shared_ptr cartridge : target.cartridges) + { + set_rom(slot, cartridge->get_segments().front().data); + slot = (ROMSlot)(((int)slot + 1)&15); } if(target.loadingCommand.length()) // TODO: and automatic loading option enabled From 75eb62b577fc0c71e82e5f211322d4485b16b4b4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 22 Sep 2016 21:25:31 -0400 Subject: [PATCH 29/51] One painful step at a time, this now starts the disk rotating and gets as far as deciding whether it's about to head off on a read or a write. --- Components/1770/1770.cpp | 55 ++++++++++++++++++++++++++++++++++------ Components/1770/1770.hpp | 5 ++-- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index ff7d8c166..2beaa2c54 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -13,7 +13,13 @@ using namespace WD; WD1770::WD1770() : Storage::Disk::Drive(1000000, 8, 300), - state_(State::Waiting), status_(0), has_command_(false) {} + state_(State::Waiting), status_(0), has_command_(false) { + Storage::Time bit_length; + // TODO: this should be a function of the double density line + bit_length.length = 1; + bit_length.clock_rate = 500000; + set_expected_bit_length(bit_length); +} //void WD1770::set_disk(std::shared_ptr disk) //{ @@ -57,6 +63,7 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) while(cycles > 8) { cycles -= 8; + if(status_ & Flag::MotorOn) Storage::Disk::Drive::run_for_cycles(1); switch(state_) { @@ -71,6 +78,13 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) } continue; + case State::WaitForSixIndexPulses: + status_ |= Flag::MotorOn; + // deliberately empty; logic is in ::process_index_hole + continue; + +#pragma mark - Type 1 + case State::BeginType1: status_ |= Flag::Busy; status_ &= ~(Flag::DataRequest | Flag::CRCError); @@ -79,14 +93,11 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) if(command_ & 0x08) { wait_six_index_pulses_.next_state = state_; - wait_six_index_pulses_.count = 0; + index_hole_count_ = 0; state_ = State::WaitForSixIndexPulses; } continue; -// case State::WaitForSixIndexPulses: -// continue; - case State::BeginType1PostSpin: switch(command_ >> 4) { @@ -139,7 +150,7 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) state_ = State::StepDelay; step_delay_.count = 0; } - break; + continue; case State::StepDelay: if(step_delay_.count == (command_&3)) @@ -147,7 +158,7 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) state_ = (command_ >> 5) ? State::TestVerify : State::TestTrack; } step_delay_.count++; - break; + continue; case State::TestVerify: if(command_ & 0x04) @@ -160,7 +171,29 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) status_ &= ~Flag::Busy; state_ = State::Waiting; } - break; + continue; + +#pragma mark - Type 2 + + case State::BeginType2: + status_ |= Flag::Busy; + status_ &= ~(Flag::DataRequest | Flag::LostData | Flag::RecordNotFound | 0x60); + state_ = State::TestPause; + if(!(command_&0x08)) + { + wait_six_index_pulses_.next_state = state_; + index_hole_count_ = 0; + state_ = State::WaitForSixIndexPulses; + } + continue; + + case State::TestPause: + // TODO: pause for 30ms if E is set + state_ = State::TestWrite; + continue; + +// case State::TestWrite: +// continue; // +------+----------+-------------------------+ // ! ! ! BITS ! @@ -197,4 +230,10 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) void WD1770::process_index_hole() { + index_hole_count_++; + + if(state_ == State::WaitForSixIndexPulses && index_hole_count_ == 6) + { + state_ = wait_six_index_pulses_.next_state; + } } diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 74ccc1f5f..8f66c1060 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -48,12 +48,11 @@ class WD1770: public Storage::Disk::Drive { WaitForSixIndexPulses, TestTrack, TestDirection, TestHead, TestVerify, VerifyTrack, - StepDelay + StepDelay, TestPause, TestWrite } state_; union { struct { - int count; State next_state; } wait_six_index_pulses_; struct { @@ -61,6 +60,8 @@ class WD1770: public Storage::Disk::Drive { } step_delay_; }; + int index_hole_count_; + uint8_t status_; uint8_t track_; uint8_t sector_; From 88bbeedad369ad9158b5d47cd3b97799d7ed7c1e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 22 Sep 2016 21:28:23 -0400 Subject: [PATCH 30/51] Set actual bit length. --- Components/1770/1770.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 2beaa2c54..daeb906de 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -8,26 +8,20 @@ #include "1770.hpp" - using namespace WD; WD1770::WD1770() : Storage::Disk::Drive(1000000, 8, 300), state_(State::Waiting), status_(0), has_command_(false) { - Storage::Time bit_length; - // TODO: this should be a function of the double density line - bit_length.length = 1; - bit_length.clock_rate = 500000; - set_expected_bit_length(bit_length); + set_is_double_density(false); } -//void WD1770::set_disk(std::shared_ptr disk) -//{ -// drive_.reset(new Storage::Disk::Drive(1000000, 8, 300)); -//} - void WD1770::set_is_double_density(bool is_double_density) { + Storage::Time bit_length; + bit_length.length = 1; + bit_length.clock_rate = is_double_density ? 500000 : 250000; + set_expected_bit_length(bit_length); } void WD1770::set_register(int address, uint8_t value) From 53522e28e216dbb9b6d07c516c818eb3118c8eab Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Sep 2016 20:12:45 -0400 Subject: [PATCH 31/51] Performed an about-face on how this should probably be implemented. Now forsaking the state machine in favour of a macro'd switch-for-cooperative-event-based-messaging implementation. Let's see how this ends up looking. --- Components/1770/1770.cpp | 274 +++++++++++++++++++++++++++++++++++---- Components/1770/1770.hpp | 66 ++++++++-- 2 files changed, 302 insertions(+), 38 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index daeb906de..eceb20479 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -7,17 +7,24 @@ // #include "1770.hpp" +#include "../../Storage/Disk/Encodings/MFM.hpp" using namespace WD; WD1770::WD1770() : - Storage::Disk::Drive(1000000, 8, 300), - state_(State::Waiting), status_(0), has_command_(false) { + Storage::Disk::Drive(8000000, 1, 300), + status_(0), + interesting_event_mask_(Event::Command), + resume_point_(0), + delay_time_(0) +{ set_is_double_density(false); + posit_event(Event::Command); } void WD1770::set_is_double_density(bool is_double_density) { + is_double_density_ = is_double_density; Storage::Time bit_length; bit_length.length = 1; bit_length.clock_rate = is_double_density ? 500000 : 250000; @@ -30,7 +37,7 @@ void WD1770::set_register(int address, uint8_t value) { case 0: command_ = value; - has_command_ = true; + posit_event(Event::Command); // TODO: is this force interrupt? break; case 1: track_ = value; break; @@ -52,8 +59,22 @@ uint8_t WD1770::get_register(int address) void WD1770::run_for_cycles(unsigned int number_of_cycles) { + if(status_ & Flag::MotorOn) Storage::Disk::Drive::run_for_cycles((int)number_of_cycles); + + if(delay_time_) + { + if(delay_time_ <= number_of_cycles) + { + delay_time_ = 0; + posit_event(Event::Timer); + } + else + { + delay_time_ -= number_of_cycles; + } + } // perform one step every eight cycles, arbitrariy as I can find no timing documentation - cycles += number_of_cycles; +/* cycles += number_of_cycles; while(cycles > 8) { cycles -= 8; @@ -183,11 +204,134 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) case State::TestPause: // TODO: pause for 30ms if E is set - state_ = State::TestWrite; + state_ = State::TestWriteProtect; continue; -// case State::TestWrite: -// continue; + case State::TestWriteProtect: + if(command_ & 0x20) // TODO: && write protect + { + set_interrupt_request(true); + status_ &= ~Flag::Busy; + status_ |= Flag::WriteProtect; + state_ = State::Waiting; + } + else + { + get_header_.found_id = false; + state_ = State::GetHeader; + } + continue; + + case State::GetHeader: + if(index_hole_count_ == 5) + { + set_interrupt_request(true); + status_ &= ~Flag::Busy; + status_ |= Flag::RecordNotFound; + state_ = State::Waiting; + continue; + } + + if(get_header_.found_id) + { + if(token_counter_ > 0 && latest_token_.type != Token::Byte) + { + get_header_.found_id = false; + continue; + } + + if(token_counter_ == 5) + { + } + else if(token_counter_ > 0) + { + } + } + else + { + if(latest_token_.type == Token::ID) + { + get_header_.found_id = true; + token_counter_ = 0; + } + } + continue; + + + default: + { + static bool has_hit_error = false; + if(!has_hit_error) + printf("Unhandled state %d!\n", state_); + has_hit_error = true; + } + return; + } + }*/ +} + +void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) +{ + shift_register_ = (shift_register_ << 1) | value; + bits_since_token_++; + + Token::Type token_type = Token::Byte; + if(is_double_density_) + { + switch(shift_register_ & 0xffff) + { + case Storage::Encodings::MFM::FMIndexAddressMark: + token_type = Token::Index; + break; + case Storage::Encodings::MFM::FMIDAddressMark: + token_type = Token::ID; + break; + case Storage::Encodings::MFM::FMDataAddressMark: + token_type = Token::Data; + break; + case Storage::Encodings::MFM::FMDeletedDataAddressMark: + token_type = Token::DeletedData; + break; + default: + break; + } + } + else + { + // TODO: MFM + } + + if(token_type != Token::Byte) + { + latest_token_.type = token_type; + bits_since_token_ = 0; + posit_event(Event::Token); + return; + } + + if(bits_since_token_ == 16) + { + latest_token_.type = Token::Byte; + latest_token_.byte_value = (uint8_t)( + ((shift_register_ & 0x0001) >> 0) | + ((shift_register_ & 0x0004) >> 1) | + ((shift_register_ & 0x0010) >> 2) | + ((shift_register_ & 0x0040) >> 3) | + ((shift_register_ & 0x0100) >> 4) | + ((shift_register_ & 0x0400) >> 5) | + ((shift_register_ & 0x1000) >> 6) | + ((shift_register_ & 0x4000) >> 7)); + bits_since_token_ = 0; + posit_event(Event::Token); + return; + } +} + +void WD1770::process_index_hole() +{ + index_hole_count_++; + posit_event(Event::IndexHole); +} // +------+----------+-------------------------+ // ! ! ! BITS ! @@ -206,28 +350,104 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) // ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 ! // +------+----------+-------------------------+ - default: - { - static bool has_hit_error = false; - if(!has_hit_error) - printf("Unhandled state %d!\n", state_); - has_hit_error = true; - } - return; +#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__: +#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__: +#define BEGIN_SECTION() switch(resume_point_) { default: +#define END_SECTION() 0; } + + +void WD1770::posit_event(Event type) +{ + if(!(interesting_event_mask_ & (int)type)) return; + interesting_event_mask_ &= ~type; + + BEGIN_SECTION() + + // Wait for a new command, branch to the appropriate handler. + wait_for_command: + WAIT_FOR_EVENT(Event::Command); + status_ |= Flag::Busy; + if(!(command_ & 0x80)) goto begin_type_1; + if(!(command_ & 0x40)) goto begin_type_2; + goto begin_type_3; + + + /* + Type 1 entry point. + */ + begin_type_1: + // Set initial flags, skip spin-up if possible. + status_ &= ~(Flag::DataRequest | Flag::DataRequest); + set_interrupt_request(false); + if(!(command_&0x08)) goto test_type1_type; + + // Perform spin up. + status_ |= Flag::MotorOn; + WAIT_FOR_EVENT(Event::IndexHole); + WAIT_FOR_EVENT(Event::IndexHole); + WAIT_FOR_EVENT(Event::IndexHole); + WAIT_FOR_EVENT(Event::IndexHole); + WAIT_FOR_EVENT(Event::IndexHole); + WAIT_FOR_EVENT(Event::IndexHole); + + test_type1_type: + // Set step direction if this is a step in or out. + if((command_ >> 5) == 2) step_direction_ = 1; + if((command_ >> 5) == 3) step_direction_ = 0; + if((command_ >> 5) != 0) goto perform_step_command; + + // This is now definitely either a seek or a restore; if it's a restore then set track to 0xff. + if(!(command_ & 0x10)) track_ = 0xff; + data_ = 0; + + perform_seek_or_restore_command: + if(track_ == data_) goto verify; + step_direction_ = (data_ < track_); + + adjust_track: + if(step_direction_) track_--; else track_++; + + perform_step: + if(!step_direction_ && get_is_track_zero()) + { + track_ = 0; + goto verify; } - } -} + step(step_direction_); + int time_to_wait; + switch(command_ & 3) + { + default: + case 0: time_to_wait = 6; break; // 2 on a 1772 + case 1: time_to_wait = 12; break; // 3 on a 1772 + case 2: time_to_wait = 20; break; // 5 on a 1772 + case 3: time_to_wait = 30; break; // 6 on a 1772 + } + WAIT_FOR_TIME(command_ & 3); + if(command_ >> 5) goto verify; + goto perform_seek_or_restore_command; -void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) -{ -} + perform_step_command: + if(command_ & 0x10) goto adjust_track; + goto perform_step; -void WD1770::process_index_hole() -{ - index_hole_count_++; + verify: + if(!(command_ & 0x04)) + { + set_interrupt_request(true); + status_ &= ~(Flag::Busy); + goto wait_for_command; + } - if(state_ == State::WaitForSixIndexPulses && index_hole_count_ == 6) - { - state_ = wait_six_index_pulses_.next_state; - } + printf("!!!TODO: verify a type 1!!!\n"); + + begin_type_2: + printf("!!!TODO: type 2 commands!!!\n"); + + + begin_type_3: + printf("!!!TODO: type 3 commands!!!\n"); + + + END_SECTION() } diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 8f66c1060..c000f7f5a 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -39,16 +39,23 @@ class WD1770: public Storage::Disk::Drive { }; private: - unsigned int cycles; + uint8_t status_; + uint8_t track_; + uint8_t sector_; + uint8_t data_; + uint8_t command_; - enum class State { + int index_hole_count_; + +/* enum class State { Waiting, BeginType1, BeginType2, BeginType3, BeginType1PostSpin, WaitForSixIndexPulses, TestTrack, TestDirection, TestHead, TestVerify, VerifyTrack, - StepDelay, TestPause, TestWrite + StepDelay, TestPause, TestWriteProtect, + GetHeader, } state_; union { @@ -58,21 +65,58 @@ class WD1770: public Storage::Disk::Drive { struct { int count; } step_delay_; + struct { + bool found_id; + uint8_t value[4]; + } get_header_; }; - int index_hole_count_; + enum class ReadingState { + Idle, + ReadingHeader, + ReadingData + } reading_state_; + struct { + uint8_t track; + uint8_t sector; + uint8_t length; + } header; + bool crc_error_; + + int shift_register_; + int shift_register_duration_; + bool is_double_density_; - uint8_t status_; - uint8_t track_; - uint8_t sector_; - uint8_t data_; - uint8_t command_; bool has_command_; + bool is_step_in_;*/ + int step_direction_; void set_interrupt_request(bool interrupt_request) {} - bool is_step_in_; - uint8_t data_shift_register_; + // Tokeniser + bool is_double_density_; + int shift_register_; + struct Token { + enum Type { + Index, ID, Data, DeletedData, Byte + } type; + uint8_t byte_value; + } latest_token_; + int bits_since_token_; + + // Events + enum Event: int { + Command = (1 << 0), + Token = (1 << 1), + IndexHole = (1 << 2), + Timer = (1 << 3) + }; + void posit_event(Event type); + int interesting_event_mask_; + int resume_point_; + int delay_time_; + + // virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); virtual void process_index_hole(); }; From 7154e8986e8e86e1991efa476e701a887f500af3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Sep 2016 20:16:23 -0400 Subject: [PATCH 32/51] Fixed stepping delay. --- Components/1770/1770.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index eceb20479..276e13de8 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -423,7 +423,7 @@ void WD1770::posit_event(Event type) case 2: time_to_wait = 20; break; // 5 on a 1772 case 3: time_to_wait = 30; break; // 6 on a 1772 } - WAIT_FOR_TIME(command_ & 3); + WAIT_FOR_TIME(time_to_wait); if(command_ >> 5) goto verify; goto perform_seek_or_restore_command; From ce4100e5b9e2ffb08b3a27608b66bca407287e9a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Sep 2016 22:04:54 -0400 Subject: [PATCH 33/51] Fixed slots for DFS and ADFS to sideways RAM; continued working on the 1770 to get as far as trying to get the body of a sector. --- Components/1770/1770.cpp | 88 ++++++++++++++++--- Components/1770/1770.hpp | 5 ++ Machines/Electron/Electron.cpp | 14 ++- Machines/Electron/Electron.hpp | 3 +- .../Machine/Wrappers/CSElectron.mm | 2 +- 5 files changed, 95 insertions(+), 17 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 276e13de8..6603caaec 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -276,7 +276,7 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) bits_since_token_++; Token::Type token_type = Token::Byte; - if(is_double_density_) + if(!is_double_density_) { switch(shift_register_ & 0xffff) { @@ -356,16 +356,19 @@ void WD1770::process_index_hole() #define END_SECTION() 0; } -void WD1770::posit_event(Event type) +void WD1770::posit_event(Event new_event_type) { - if(!(interesting_event_mask_ & (int)type)) return; - interesting_event_mask_ &= ~type; + if(!(interesting_event_mask_ & (int)new_event_type)) return; + interesting_event_mask_ &= ~new_event_type; BEGIN_SECTION() // Wait for a new command, branch to the appropriate handler. wait_for_command: + printf("Idle...\n"); + status_ &= ~Flag::Busy; WAIT_FOR_EVENT(Event::Command); + printf("Starting %02x\n", command_); status_ |= Flag::Busy; if(!(command_ & 0x80)) goto begin_type_1; if(!(command_ & 0x40)) goto begin_type_2; @@ -377,9 +380,9 @@ void WD1770::posit_event(Event type) */ begin_type_1: // Set initial flags, skip spin-up if possible. - status_ &= ~(Flag::DataRequest | Flag::DataRequest); + status_ &= ~(Flag::DataRequest | Flag::DataRequest | Flag::SeekError); set_interrupt_request(false); - if(!(command_&0x08)) goto test_type1_type; + if((command_&0x08) || (status_ & Flag::MotorOn)) goto test_type1_type; // Perform spin up. status_ |= Flag::MotorOn; @@ -389,6 +392,7 @@ void WD1770::posit_event(Event type) WAIT_FOR_EVENT(Event::IndexHole); WAIT_FOR_EVENT(Event::IndexHole); WAIT_FOR_EVENT(Event::IndexHole); + status_ |= Flag::SpinUp; test_type1_type: // Set step direction if this is a step in or out. @@ -402,10 +406,10 @@ void WD1770::posit_event(Event type) perform_seek_or_restore_command: if(track_ == data_) goto verify; - step_direction_ = (data_ < track_); + step_direction_ = (data_ > track_); adjust_track: - if(step_direction_) track_--; else track_++; + if(step_direction_) track_++; else track_--; perform_step: if(!step_direction_ && get_is_track_zero()) @@ -435,15 +439,77 @@ void WD1770::posit_event(Event type) if(!(command_ & 0x04)) { set_interrupt_request(true); - status_ &= ~(Flag::Busy); goto wait_for_command; } printf("!!!TODO: verify a type 1!!!\n"); - begin_type_2: - printf("!!!TODO: type 2 commands!!!\n"); + /* + Type 2 entry point. + */ + begin_type_2: + status_ &= ~(Flag::DataRequest | Flag::LostData | Flag::RecordNotFound | Flag::WriteProtect | Flag::RecordType); + set_interrupt_request(false); + distance_into_header_ = 0; + if((command_&0x08) || (status_ & Flag::MotorOn)) goto test_type2_delay; + + // Perform spin up. + status_ |= Flag::MotorOn; + WAIT_FOR_EVENT(Event::IndexHole); + WAIT_FOR_EVENT(Event::IndexHole); + WAIT_FOR_EVENT(Event::IndexHole); + WAIT_FOR_EVENT(Event::IndexHole); + WAIT_FOR_EVENT(Event::IndexHole); + WAIT_FOR_EVENT(Event::IndexHole); + + test_type2_delay: + index_hole_count_ = 0; + if(!(command_ & 0x04)) goto test_type2_write_protection; + WAIT_FOR_TIME(30); + + test_type2_write_protection: + if(command_&0x20) // TODO:: && is_write_protected + { + set_interrupt_request(true); + status_ |= Flag::WriteProtect; + goto wait_for_command; + } + + type2_get_header: + WAIT_FOR_EVENT(Event::IndexHole | Event::Token); + if(new_event_type == Event::Token) + { + if(!distance_into_header_ && latest_token_.type == Token::ID) distance_into_header_++; + else if(distance_into_header_ && latest_token_.type == Token::Byte) + { + header[distance_into_header_ - 1] = latest_token_.byte_value; + distance_into_header_++; + if(distance_into_header_ == 5) + { + if(header[0] == track_ && header[1] == sector_) + { + // TODO: test CRC + goto type2_read_or_write_data; + } + else + { + distance_into_header_ = 0; + } + } + } + } + else if(index_hole_count_ == 5) + { + set_interrupt_request(true); + status_ |= Flag::RecordNotFound; + goto wait_for_command; + } + goto type2_get_header; + + + type2_read_or_write_data: + printf("!!!TODO: data portion of sector!!!\n"); begin_type_3: printf("!!!TODO: type 3 commands!!!\n"); diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index c000f7f5a..b51bcfbe9 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -30,6 +30,7 @@ class WD1770: public Storage::Disk::Drive { RecordType = 0x20, SpinUp = 0x20, RecordNotFound = 0x10, + SeekError = 0x10, CRCError = 0x08, LostData = 0x04, TrackZero = 0x04, @@ -116,6 +117,10 @@ class WD1770: public Storage::Disk::Drive { int resume_point_; int delay_time_; + // ID buffer + int distance_into_header_; + uint8_t header[5]; + // virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); virtual void process_index_hole(); diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index eda25f817..a29054b37 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -411,6 +411,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { *value &= _roms[ROMSlotBASIC][address & 16383]; } + } else if(_rom_write_masks[_active_rom]) + { + _roms[_active_rom][address & 16383] = *value; } } break; @@ -496,7 +499,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) if(target.acorn.has_dfs) { - set_rom(ROMSlot0, _dfs); + set_rom(ROMSlot0, _dfs, true); } _wd1770->set_disk(target.disks.front()); @@ -505,7 +508,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) ROMSlot slot = ROMSlot12; for(std::shared_ptr cartridge : target.cartridges) { - set_rom(slot, cartridge->get_segments().front().data); + set_rom(slot, cartridge->get_segments().front().data, false); slot = (ROMSlot)(((int)slot + 1)&15); } @@ -515,7 +518,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) } } -void Machine::set_rom(ROMSlot slot, std::vector data) +void Machine::set_rom(ROMSlot slot, std::vector data, bool is_writeable) { uint8_t *target = nullptr; switch(slot) @@ -524,7 +527,10 @@ void Machine::set_rom(ROMSlot slot, std::vector data) case ROMSlotADFS: _adfs = data; return; case ROMSlotOS: target = _os; break; - default: target = _roms[slot]; break; + default: + target = _roms[slot]; + _rom_write_masks[slot] = is_writeable; + break; } memcpy(target, &data[0], std::min((size_t)16384, data.size())); diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 2b3b85f14..0628d92b5 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -148,7 +148,7 @@ class Machine: public: Machine(); - void set_rom(ROMSlot slot, std::vector data); + void set_rom(ROMSlot slot, std::vector data, bool is_writeable); void configure_as_target(const StaticAnalyser::Target &target); void set_key_state(Key key, bool isPressed); @@ -189,6 +189,7 @@ class Machine: // Things that directly constitute the memory map. uint8_t _roms[16][16384]; + bool _rom_write_masks[16]; uint8_t _os[16384], _ram[32768]; std::vector _dfs, _adfs; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm index ad3481dd3..97f3ed865 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm @@ -33,7 +33,7 @@ - (void)setROM:(nonnull NSData *)rom slot:(int)slot { @synchronized(self) { - _electron.set_rom((Electron::ROMSlot)slot, rom.stdVector8); + _electron.set_rom((Electron::ROMSlot)slot, rom.stdVector8, false); } } From 1b69ad0fd4ddf427e322fb8a0272cb5812f888e3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Sep 2016 22:29:02 -0400 Subject: [PATCH 34/51] This attempts to implment sector reading. DFS reports an error. --- Components/1770/1770.cpp | 53 +++++++++++++++++++++++++++++++++------- Components/1770/1770.hpp | 4 +-- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 6603caaec..618f71317 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -53,7 +53,7 @@ uint8_t WD1770::get_register(int address) default: return status_; case 1: return track_; case 2: return sector_; - case 3: return data_; + case 3: status_ &= ~Flag::DataRequest; return data_; } } @@ -451,7 +451,7 @@ void WD1770::posit_event(Event new_event_type) begin_type_2: status_ &= ~(Flag::DataRequest | Flag::LostData | Flag::RecordNotFound | Flag::WriteProtect | Flag::RecordType); set_interrupt_request(false); - distance_into_header_ = 0; + distance_into_section_ = 0; if((command_&0x08) || (status_ & Flag::MotorOn)) goto test_type2_delay; // Perform spin up. @@ -480,21 +480,21 @@ void WD1770::posit_event(Event new_event_type) WAIT_FOR_EVENT(Event::IndexHole | Event::Token); if(new_event_type == Event::Token) { - if(!distance_into_header_ && latest_token_.type == Token::ID) distance_into_header_++; - else if(distance_into_header_ && latest_token_.type == Token::Byte) + if(!distance_into_section_ && latest_token_.type == Token::ID) distance_into_section_++; + else if(distance_into_section_ && latest_token_.type == Token::Byte) { - header[distance_into_header_ - 1] = latest_token_.byte_value; - distance_into_header_++; - if(distance_into_header_ == 5) + header[distance_into_section_ - 1] = latest_token_.byte_value; + distance_into_section_++; + if(distance_into_section_ == 6) { - if(header[0] == track_ && header[1] == sector_) + if(header[0] == track_ && header[2] == sector_) { // TODO: test CRC goto type2_read_or_write_data; } else { - distance_into_header_ = 0; + distance_into_section_ = 0; } } } @@ -509,6 +509,41 @@ void WD1770::posit_event(Event new_event_type) type2_read_or_write_data: + if(command_&0x20) goto type2_write_data; + goto type2_read_data; + + type2_read_data: + WAIT_FOR_EVENT(Event::Token); + // TODO: timeout + if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData) + { + status_ |= (latest_token_.type == Token::DeletedData) ? Flag::RecordType : 0; + distance_into_section_ = 0; + goto type2_read_byte; + } + goto type2_read_data; + + type2_read_byte: + WAIT_FOR_EVENT(Event::Token); + if(latest_token_.type != Token::Byte) goto type2_read_byte; + if(status_ & Flag::DataRequest) status_ |= Flag::LostData; + data_ = latest_token_.byte_value; + status_ |= Flag::DataRequest; + distance_into_section_++; + if(distance_into_section_ == 128 << header[3]) + { + // TODO: check CRC + if(command_ & 0x10) + { + sector_++; + goto test_type2_write_protection; + } + set_interrupt_request(true); + goto wait_for_command; + } + goto type2_read_byte; + + type2_write_data: printf("!!!TODO: data portion of sector!!!\n"); begin_type_3: diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index b51bcfbe9..92e4eb1c6 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -118,8 +118,8 @@ class WD1770: public Storage::Disk::Drive { int delay_time_; // ID buffer - int distance_into_header_; - uint8_t header[5]; + int distance_into_section_; + uint8_t header[6]; // virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); From 07ff5138b7083607152a0ea32467972365b1444b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Sep 2016 22:35:34 -0400 Subject: [PATCH 35/51] The catalogue now loads. --- Components/1770/1770.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 618f71317..d54e45fa6 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -531,6 +531,18 @@ void WD1770::posit_event(Event new_event_type) status_ |= Flag::DataRequest; distance_into_section_++; if(distance_into_section_ == 128 << header[3]) + { + distance_into_section_ = 0; + goto type2_check_crc; + } + goto type2_read_byte; + + type2_check_crc: + WAIT_FOR_EVENT(Event::Token); + if(latest_token_.type != Token::Byte) goto type2_read_byte; + header[distance_into_section_] = latest_token_.byte_value; + distance_into_section_++; + if(distance_into_section_ == 2) { // TODO: check CRC if(command_ & 0x10) @@ -541,7 +553,8 @@ void WD1770::posit_event(Event new_event_type) set_interrupt_request(true); goto wait_for_command; } - goto type2_read_byte; + goto type2_check_crc; + type2_write_data: printf("!!!TODO: data portion of sector!!!\n"); From d50629e6aad81c9bc54e554168b77629f0e09a46 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Sep 2016 22:36:38 -0400 Subject: [PATCH 36/51] This is clearly the winning solution. Edited down. --- Components/1770/1770.cpp | 195 --------------------------------------- Components/1770/1770.hpp | 48 +--------- 2 files changed, 2 insertions(+), 241 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index d54e45fa6..ad7bf4516 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -73,201 +73,6 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) delay_time_ -= number_of_cycles; } } - // perform one step every eight cycles, arbitrariy as I can find no timing documentation -/* cycles += number_of_cycles; - while(cycles > 8) - { - cycles -= 8; - if(status_ & Flag::MotorOn) Storage::Disk::Drive::run_for_cycles(1); - - switch(state_) - { - case State::Waiting: - if(has_command_) - { - has_command_ = false; - if(command_ & 0x80) - state_ = (command_ & 0x40) ? State::BeginType3 : State::BeginType2; - else - state_ = State::BeginType1; - } - continue; - - case State::WaitForSixIndexPulses: - status_ |= Flag::MotorOn; - // deliberately empty; logic is in ::process_index_hole - continue; - -#pragma mark - Type 1 - - case State::BeginType1: - status_ |= Flag::Busy; - status_ &= ~(Flag::DataRequest | Flag::CRCError); - set_interrupt_request(false); - state_ = State::BeginType1PostSpin; - if(command_ & 0x08) - { - wait_six_index_pulses_.next_state = state_; - index_hole_count_ = 0; - state_ = State::WaitForSixIndexPulses; - } - continue; - - case State::BeginType1PostSpin: - switch(command_ >> 4) - { - case 0: // restore - track_ = 0xff; // deliberate fallthrough - case 1: // seek - data_ = 0x00; - break; - case 2: case 3: // step - break; - case 4: case 5: // step in - is_step_in_ = true; - break; - case 6: case 7: // step out - is_step_in_ = false; - break; - } - - if(!(command_ >> 5)) - state_ = State::TestTrack; - else - state_ = (command_ & 0x10) ? State::TestDirection : State::TestHead; - continue; - - case State::TestTrack: - data_shift_register_ = data_; - if(track_ == data_shift_register_) - state_ = State::TestVerify; - else - { - is_step_in_ = (data_shift_register_ < track_); - state_ = State::TestDirection; - } - continue; - - case State::TestDirection: - track_ += is_step_in_ ? -1 : +1; - state_ = State::TestHead; - continue; - - case State::TestHead: - if(get_is_track_zero() && !is_step_in_) - { - track_ = 0; - state_ = State::TestVerify; - } - else - { - step(is_step_in_ ? 1 : -1); - state_ = State::StepDelay; - step_delay_.count = 0; - } - continue; - - case State::StepDelay: - if(step_delay_.count == (command_&3)) - { - state_ = (command_ >> 5) ? State::TestVerify : State::TestTrack; - } - step_delay_.count++; - continue; - - case State::TestVerify: - if(command_ & 0x04) - { - state_ = State::VerifyTrack; - } - else - { - set_interrupt_request(true); - status_ &= ~Flag::Busy; - state_ = State::Waiting; - } - continue; - -#pragma mark - Type 2 - - case State::BeginType2: - status_ |= Flag::Busy; - status_ &= ~(Flag::DataRequest | Flag::LostData | Flag::RecordNotFound | 0x60); - state_ = State::TestPause; - if(!(command_&0x08)) - { - wait_six_index_pulses_.next_state = state_; - index_hole_count_ = 0; - state_ = State::WaitForSixIndexPulses; - } - continue; - - case State::TestPause: - // TODO: pause for 30ms if E is set - state_ = State::TestWriteProtect; - continue; - - case State::TestWriteProtect: - if(command_ & 0x20) // TODO: && write protect - { - set_interrupt_request(true); - status_ &= ~Flag::Busy; - status_ |= Flag::WriteProtect; - state_ = State::Waiting; - } - else - { - get_header_.found_id = false; - state_ = State::GetHeader; - } - continue; - - case State::GetHeader: - if(index_hole_count_ == 5) - { - set_interrupt_request(true); - status_ &= ~Flag::Busy; - status_ |= Flag::RecordNotFound; - state_ = State::Waiting; - continue; - } - - if(get_header_.found_id) - { - if(token_counter_ > 0 && latest_token_.type != Token::Byte) - { - get_header_.found_id = false; - continue; - } - - if(token_counter_ == 5) - { - } - else if(token_counter_ > 0) - { - } - } - else - { - if(latest_token_.type == Token::ID) - { - get_header_.found_id = true; - token_counter_ = 0; - } - } - continue; - - - default: - { - static bool has_hit_error = false; - if(!has_hit_error) - printf("Unhandled state %d!\n", state_); - has_hit_error = true; - } - return; - } - }*/ } void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 92e4eb1c6..91be3cf98 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -17,7 +17,6 @@ class WD1770: public Storage::Disk::Drive { public: WD1770(); -// void set_disk(std::shared_ptr disk); void set_is_double_density(bool is_double_density); void set_register(int address, uint8_t value); uint8_t get_register(int address); @@ -47,50 +46,9 @@ class WD1770: public Storage::Disk::Drive { uint8_t command_; int index_hole_count_; + int bits_since_token_; + int distance_into_section_; -/* enum class State { - Waiting, - BeginType1, BeginType2, BeginType3, - BeginType1PostSpin, - WaitForSixIndexPulses, - TestTrack, TestDirection, TestHead, - TestVerify, VerifyTrack, - StepDelay, TestPause, TestWriteProtect, - GetHeader, - } state_; - - union { - struct { - State next_state; - } wait_six_index_pulses_; - struct { - int count; - } step_delay_; - struct { - bool found_id; - uint8_t value[4]; - } get_header_; - }; - - enum class ReadingState { - Idle, - ReadingHeader, - ReadingData - } reading_state_; - struct { - uint8_t track; - uint8_t sector; - uint8_t length; - } header; - bool crc_error_; - - int shift_register_; - int shift_register_duration_; - bool is_double_density_; - - bool has_command_; - - bool is_step_in_;*/ int step_direction_; void set_interrupt_request(bool interrupt_request) {} @@ -103,7 +61,6 @@ class WD1770: public Storage::Disk::Drive { } type; uint8_t byte_value; } latest_token_; - int bits_since_token_; // Events enum Event: int { @@ -118,7 +75,6 @@ class WD1770: public Storage::Disk::Drive { int delay_time_; // ID buffer - int distance_into_section_; uint8_t header[6]; // From 9e1d4c8b010140f0f8bf2c40c3f29b80435f9bf5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Sep 2016 22:43:12 -0400 Subject: [PATCH 37/51] Fixed an initial setup error with seek. --- Components/1770/1770.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index ad7bf4516..03cdc94ef 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -136,6 +136,9 @@ void WD1770::process_index_hole() { index_hole_count_++; posit_event(Event::IndexHole); + + // motor power-down + if(index_hole_count_ == 9 && !(status_&Flag::Busy)) status_ &= ~Flag::MotorOn; } // +------+----------+-------------------------+ @@ -172,6 +175,7 @@ void WD1770::posit_event(Event new_event_type) wait_for_command: printf("Idle...\n"); status_ &= ~Flag::Busy; + index_hole_count_ = 0; WAIT_FOR_EVENT(Event::Command); printf("Starting %02x\n", command_); status_ |= Flag::Busy; @@ -205,9 +209,12 @@ void WD1770::posit_event(Event new_event_type) if((command_ >> 5) == 3) step_direction_ = 0; if((command_ >> 5) != 0) goto perform_step_command; - // This is now definitely either a seek or a restore; if it's a restore then set track to 0xff. - if(!(command_ & 0x10)) track_ = 0xff; - data_ = 0; + // This is now definitely either a seek or a restore; if it's a restore then set track to 0xff and data to 0x00. + if(!(command_ & 0x10)) + { + track_ = 0xff; + data_ = 0; + } perform_seek_or_restore_command: if(track_ == data_) goto verify; From 091875180223a5a524cc601daee3cccebf1b3610 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Sep 2016 22:48:45 -0400 Subject: [PATCH 38/51] Fixed stepping out. --- Components/1770/1770.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 03cdc94ef..d2e7a0869 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -138,7 +138,7 @@ void WD1770::process_index_hole() posit_event(Event::IndexHole); // motor power-down - if(index_hole_count_ == 9 && !(status_&Flag::Busy)) status_ &= ~Flag::MotorOn; +// if(index_hole_count_ == 9 && !(status_&Flag::Busy)) status_ &= ~Flag::MotorOn; } // +------+----------+-------------------------+ @@ -229,7 +229,7 @@ void WD1770::posit_event(Event new_event_type) track_ = 0; goto verify; } - step(step_direction_); + step(step_direction_ ? 1 : -1); int time_to_wait; switch(command_ & 3) { From 67b54269c9073191a007b8d10d8364b4dd2687ab Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Sep 2016 22:52:49 -0400 Subject: [PATCH 39/51] Added typer support for loading from DFS. It'll do for now. --- StaticAnalyser/Acorn/StaticAnalyser.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/StaticAnalyser/Acorn/StaticAnalyser.cpp b/StaticAnalyser/Acorn/StaticAnalyser.cpp index bf393945c..beb90c065 100644 --- a/StaticAnalyser/Acorn/StaticAnalyser.cpp +++ b/StaticAnalyser/Acorn/StaticAnalyser.cpp @@ -122,7 +122,17 @@ void StaticAnalyser::Acorn::AddTargets( target.disks = disks; target.acorn.has_dfs = true; - // TODO: what about booting? + // TODO: can't I just press shift? + switch(dfs_catalogue->bootOption) + { + default: target.loadingCommand = "*CAT\n"; break; + case Catalogue::BootOption::LoadBOOT: + target.loadingCommand = "*LOAD !BOOT\n"; break; + case Catalogue::BootOption::RunBOOT: + target.loadingCommand = "*RUN !BOOT\n"; break; + case Catalogue::BootOption::ExecBOOT: + target.loadingCommand = "*EXEC !BOOT\n"; break; + } } } From a538b452132ceb192af86a7326607bb4af000945 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Sep 2016 09:54:49 -0400 Subject: [PATCH 40/51] Sought to implement the verify step of type 1 commands. --- Components/1770/1770.cpp | 70 +++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index d2e7a0869..0ef09828d 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -163,6 +163,16 @@ void WD1770::process_index_hole() #define BEGIN_SECTION() switch(resume_point_) { default: #define END_SECTION() 0; } +#define READ_ID() \ + if(new_event_type == Event::Token) \ + { \ + if(!distance_into_section_ && latest_token_.type == Token::ID) distance_into_section_++; \ + else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) \ + { \ + header[distance_into_section_ - 1] = latest_token_.byte_value; \ + distance_into_section_++; \ + } \ + } void WD1770::posit_event(Event new_event_type) { @@ -254,7 +264,31 @@ void WD1770::posit_event(Event new_event_type) goto wait_for_command; } - printf("!!!TODO: verify a type 1!!!\n"); + index_hole_count_ = 0; + + verify_read_data: + WAIT_FOR_EVENT(Event::IndexHole | Event::Token); + READ_ID(); + + if(index_hole_count_ == 6) + { + set_interrupt_request(true); + status_ |= Flag::SeekError; + goto wait_for_command; + } + if(distance_into_section_ == 7) + { + // TODO: CRC check + if(header[0] == track_) + { + status_ &= ~Flag::CRCError; + set_interrupt_request(true); + goto wait_for_command; + } + + distance_into_section_ = 0; + } + goto verify_read_data; /* @@ -290,33 +324,23 @@ void WD1770::posit_event(Event new_event_type) type2_get_header: WAIT_FOR_EVENT(Event::IndexHole | Event::Token); - if(new_event_type == Event::Token) - { - if(!distance_into_section_ && latest_token_.type == Token::ID) distance_into_section_++; - else if(distance_into_section_ && latest_token_.type == Token::Byte) - { - header[distance_into_section_ - 1] = latest_token_.byte_value; - distance_into_section_++; - if(distance_into_section_ == 6) - { - if(header[0] == track_ && header[2] == sector_) - { - // TODO: test CRC - goto type2_read_or_write_data; - } - else - { - distance_into_section_ = 0; - } - } - } - } - else if(index_hole_count_ == 5) + READ_ID(); + + if(index_hole_count_ == 5) { set_interrupt_request(true); status_ |= Flag::RecordNotFound; goto wait_for_command; } + if(distance_into_section_ == 7) + { + if(header[0] == track_ && header[2] == sector_) + { + // TODO: test CRC + goto type2_read_or_write_data; + } + distance_into_section_ = 0; + } goto type2_get_header; From 6084020ab321e3dc42f42099dbac5c05e2e15e0d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Sep 2016 14:11:22 -0400 Subject: [PATCH 41/51] Added shift-break as a better way to boot suitable disks. Continued attempting to clean the 1770. --- Components/1770/1770.cpp | 36 ++++++++++++++----------- Components/1770/1770.hpp | 11 +++++--- Machines/Electron/Electron.cpp | 10 +++++++ Machines/Electron/Electron.hpp | 1 + StaticAnalyser/Acorn/Disk.cpp | 16 ++++++++--- StaticAnalyser/Acorn/StaticAnalyser.cpp | 17 +++++------- StaticAnalyser/StaticAnalyser.hpp | 1 + 7 files changed, 58 insertions(+), 34 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 0ef09828d..c3f000aaf 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -16,7 +16,8 @@ WD1770::WD1770() : status_(0), interesting_event_mask_(Event::Command), resume_point_(0), - delay_time_(0) + delay_time_(0), + index_hole_count_target_(-1) { set_is_double_density(false); posit_event(Event::Command); @@ -136,9 +137,14 @@ void WD1770::process_index_hole() { index_hole_count_++; posit_event(Event::IndexHole); + if(index_hole_count_target_ == index_hole_count_) + { + posit_event(Event::IndexHoleTarget); + index_hole_count_target_ = -1; + } // motor power-down -// if(index_hole_count_ == 9 && !(status_&Flag::Busy)) status_ &= ~Flag::MotorOn; + if(index_hole_count_ == 9 && !(status_&Flag::Busy)) status_ &= ~Flag::MotorOn; } // +------+----------+-------------------------+ @@ -174,6 +180,16 @@ void WD1770::process_index_hole() } \ } +#define CONCATENATE(x, y) x ## y +#define INDIRECT_CONCATENATE(x, y) TOKENPASTE(x, y) +#define LINE_LABEL INDIRECT_CONCATENATE(label, __LINE__) + +#define SPIN_UP() \ + status_ |= Flag::MotorOn; \ + index_hole_count_ = 0; \ + index_hole_count_target_ = 6; \ + WAIT_FOR_EVENT(Event::IndexHoleTarget); + void WD1770::posit_event(Event new_event_type) { if(!(interesting_event_mask_ & (int)new_event_type)) return; @@ -204,13 +220,7 @@ void WD1770::posit_event(Event new_event_type) if((command_&0x08) || (status_ & Flag::MotorOn)) goto test_type1_type; // Perform spin up. - status_ |= Flag::MotorOn; - WAIT_FOR_EVENT(Event::IndexHole); - WAIT_FOR_EVENT(Event::IndexHole); - WAIT_FOR_EVENT(Event::IndexHole); - WAIT_FOR_EVENT(Event::IndexHole); - WAIT_FOR_EVENT(Event::IndexHole); - WAIT_FOR_EVENT(Event::IndexHole); + SPIN_UP(); status_ |= Flag::SpinUp; test_type1_type: @@ -301,13 +311,7 @@ void WD1770::posit_event(Event new_event_type) if((command_&0x08) || (status_ & Flag::MotorOn)) goto test_type2_delay; // Perform spin up. - status_ |= Flag::MotorOn; - WAIT_FOR_EVENT(Event::IndexHole); - WAIT_FOR_EVENT(Event::IndexHole); - WAIT_FOR_EVENT(Event::IndexHole); - WAIT_FOR_EVENT(Event::IndexHole); - WAIT_FOR_EVENT(Event::IndexHole); - WAIT_FOR_EVENT(Event::IndexHole); + SPIN_UP(); test_type2_delay: index_hole_count_ = 0; diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 91be3cf98..fcf806777 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -46,6 +46,7 @@ class WD1770: public Storage::Disk::Drive { uint8_t command_; int index_hole_count_; + int index_hole_count_target_; int bits_since_token_; int distance_into_section_; @@ -64,10 +65,12 @@ class WD1770: public Storage::Disk::Drive { // Events enum Event: int { - Command = (1 << 0), - Token = (1 << 1), - IndexHole = (1 << 2), - Timer = (1 << 3) + Command = (1 << 0), // Indicates receipt of a new command. + Token = (1 << 1), // Indicates recognition of a new token in the flux stream. Interrogate latest_token_ for details. + IndexHole = (1 << 2), // Indicates the passing of a physical index hole. + + Timer = (1 << 3), // Indicates that the delay_time_-powered timer has timed out. + IndexHoleTarget = (1 << 4) // Indicates that index_hole_count_ has reached index_hole_count_target_. }; void posit_event(Event type); int interesting_event_mask_; diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index a29054b37..591edde76 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -304,6 +304,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: if(_wd1770 && (address&0x00f0) == 0x00c0) { + if(is_holding_shift_) + { + is_holding_shift_ = false; + set_key_state(KeyShift, false); + } if(isReadOperation(operation)) *value = _wd1770->get_register(address); else @@ -516,6 +521,11 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) { set_typer_for_string(target.loadingCommand.c_str()); } + if(target.acorn.should_hold_shift) + { + set_key_state(KeyShift, true); + is_holding_shift_ = true; + } } void Machine::set_rom(ROMSlot slot, std::vector data, bool is_writeable) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 0628d92b5..aa99d1cb1 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -233,6 +233,7 @@ class Machine: // Disk std::unique_ptr _wd1770; + bool is_holding_shift_; // Outputs std::shared_ptr _crt; diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index 72cf144eb..65da5793a 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -16,7 +16,7 @@ using namespace StaticAnalyser::Acorn; class FMParser: public Storage::Disk::Drive { public: - FMParser() : + FMParser(bool is_mfm) : Storage::Disk::Drive(4000000, 1, 300), crc_generator_(0x1021, 0xffff), shift_register_(0), track_(0) @@ -26,7 +26,7 @@ class FMParser: public Storage::Disk::Drive { Storage::Time bit_length; bit_length.length = 1; - bit_length.clock_rate = 250000; // i.e. 250 kbps (including clocks) + bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks) set_expected_bit_length(bit_length); } @@ -159,7 +159,7 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha { // c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format std::unique_ptr catalogue(new Catalogue); - FMParser parser; + FMParser parser(false); parser.set_disk(disk); std::shared_ptr names = parser.get_sector(0, 0); @@ -218,3 +218,13 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha return catalogue; } +std::unique_ptr StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr &disk) +{ + std::unique_ptr catalogue(new Catalogue); + FMParser parser(true); + parser.set_disk(disk); + + std::shared_ptr directory = parser.get_sector(0, 2); + + return catalogue; +} diff --git a/StaticAnalyser/Acorn/StaticAnalyser.cpp b/StaticAnalyser/Acorn/StaticAnalyser.cpp index beb90c065..e1c55932c 100644 --- a/StaticAnalyser/Acorn/StaticAnalyser.cpp +++ b/StaticAnalyser/Acorn/StaticAnalyser.cpp @@ -116,22 +116,17 @@ void StaticAnalyser::Acorn::AddTargets( if(disks.size() > 0) { std::shared_ptr disk = disks.front(); - std::unique_ptr dfs_catalogue = GetDFSCatalogue(disk); - if(dfs_catalogue) + std::unique_ptr catalogue = GetDFSCatalogue(disk); + if(catalogue == nullptr) catalogue = GetADFSCatalogue(disk); + if(catalogue) { target.disks = disks; target.acorn.has_dfs = true; - // TODO: can't I just press shift? - switch(dfs_catalogue->bootOption) + switch(catalogue->bootOption) { - default: target.loadingCommand = "*CAT\n"; break; - case Catalogue::BootOption::LoadBOOT: - target.loadingCommand = "*LOAD !BOOT\n"; break; - case Catalogue::BootOption::RunBOOT: - target.loadingCommand = "*RUN !BOOT\n"; break; - case Catalogue::BootOption::ExecBOOT: - target.loadingCommand = "*EXEC !BOOT\n"; break; + case Catalogue::BootOption::None: target.loadingCommand = "*CAT\n"; break; + default: target.acorn.should_hold_shift = true; break; } } } diff --git a/StaticAnalyser/StaticAnalyser.hpp b/StaticAnalyser/StaticAnalyser.hpp index 691d5e364..94df52560 100644 --- a/StaticAnalyser/StaticAnalyser.hpp +++ b/StaticAnalyser/StaticAnalyser.hpp @@ -46,6 +46,7 @@ struct Target { struct { bool has_adfs; bool has_dfs; + bool should_hold_shift; } acorn; }; From de863719d0ad8c2d70ed7ab81dcd4ae65af4f245 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Sep 2016 17:46:11 -0400 Subject: [PATCH 42/51] Made a first attempt at Acorn ADFS support plus the start of a suitable analyser. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 ++ StaticAnalyser/Acorn/Disk.cpp | 34 ++++++-- StaticAnalyser/Acorn/StaticAnalyser.cpp | 12 +-- StaticAnalyser/StaticAnalyser.cpp | 2 + Storage/Disk/Encodings/MFM.cpp | 16 ++-- Storage/Disk/Encodings/MFM.hpp | 7 ++ Storage/Disk/Formats/AcornADF.cpp | 82 +++++++++++++++++++ Storage/Disk/Formats/AcornADF.hpp | 48 +++++++++++ Storage/Disk/Formats/SSD.cpp | 3 +- Storage/Disk/Formats/SSD.hpp | 4 +- 10 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 Storage/Disk/Formats/AcornADF.cpp create mode 100644 Storage/Disk/Formats/AcornADF.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f9b4aa0f6..659c4f7d0 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -350,6 +350,7 @@ 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; }; 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; }; 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; + 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; }; 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; }; 4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; }; 4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; }; @@ -802,6 +803,8 @@ 4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = ""; }; 4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = ""; }; 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = ""; }; + 4BD69F921D98760000243FE1 /* AcornADF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AcornADF.cpp; sourceTree = ""; }; + 4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = ""; }; 4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = ""; }; 4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = ""; }; 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = ""; }; @@ -1133,6 +1136,8 @@ 4B4C836F1D4F623200CD541F /* D64.hpp */, 4BF829611D8F536B001BAE39 /* SSD.cpp */, 4BF829621D8F536B001BAE39 /* SSD.hpp */, + 4BD69F921D98760000243FE1 /* AcornADF.cpp */, + 4BD69F931D98760000243FE1 /* AcornADF.hpp */, ); path = Formats; sourceTree = ""; @@ -2113,6 +2118,7 @@ 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */, 4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */, 4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */, + 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */, 4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index 65da5793a..b670e1e4f 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -19,7 +19,7 @@ class FMParser: public Storage::Disk::Drive { FMParser(bool is_mfm) : Storage::Disk::Drive(4000000, 1, 300), crc_generator_(0x1021, 0xffff), - shift_register_(0), track_(0) + shift_register_(0), track_(0), is_mfm_(is_mfm) { // Make sure this drive really is at track '1'. while(!get_is_track_zero()) step(-1); @@ -58,6 +58,7 @@ class FMParser: public Storage::Disk::Drive { int bit_count_; std::shared_ptr sector_cache_[65536]; NumberTheory::CRC16 crc_generator_; + bool is_mfm_; void process_input_bit(int value, unsigned int cycles_since_index_hole) { @@ -98,7 +99,18 @@ class FMParser: public Storage::Disk::Drive { while(1) { run_for_cycles(1); - if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) break; + if(is_mfm_) + { + if(shift_register_ == Storage::Encodings::MFM::MFMAddressMark) + { + uint8_t mark = get_next_byte(); + if(mark == Storage::Encodings::MFM::MFMIDAddressByte) break; + } + } + else + { + if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) break; + } if(index_count_ >= 2) return nullptr; } @@ -115,8 +127,20 @@ class FMParser: public Storage::Disk::Drive { while(1) { run_for_cycles(1); - if(shift_register_ == Storage::Encodings::MFM::FMDataAddressMark) break; - if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) return nullptr; + if(is_mfm_) + { + if(shift_register_ == Storage::Encodings::MFM::MFMAddressMark) + { + uint8_t mark = get_next_byte(); + if(mark == Storage::Encodings::MFM::MFMDataAddressByte) break; + if(mark == Storage::Encodings::MFM::MFMIDAddressByte) return nullptr; + } + } + else + { + if(shift_register_ == Storage::Encodings::MFM::FMDataAddressMark) break; + if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) return nullptr; + } if(index_count_ >= 2) return nullptr; } @@ -165,7 +189,7 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha std::shared_ptr names = parser.get_sector(0, 0); std::shared_ptr details = parser.get_sector(0, 1); - if(!names || !details) return catalogue; + if(!names || !details) return nullptr; if(names->data.size() != 256 || details->data.size() != 256) return nullptr; uint8_t final_file_offset = details->data[5]; diff --git a/StaticAnalyser/Acorn/StaticAnalyser.cpp b/StaticAnalyser/Acorn/StaticAnalyser.cpp index e1c55932c..0472c9e54 100644 --- a/StaticAnalyser/Acorn/StaticAnalyser.cpp +++ b/StaticAnalyser/Acorn/StaticAnalyser.cpp @@ -69,6 +69,7 @@ void StaticAnalyser::Acorn::AddTargets( target.probability = 1.0; // TODO: a proper estimation target.acorn.has_dfs = false; target.acorn.has_adfs = false; + target.acorn.should_hold_shift = false; // strip out inappropriate cartridges target.cartridges = AcornCartridgesFrom(cartridges); @@ -116,14 +117,15 @@ void StaticAnalyser::Acorn::AddTargets( if(disks.size() > 0) { std::shared_ptr disk = disks.front(); - std::unique_ptr catalogue = GetDFSCatalogue(disk); - if(catalogue == nullptr) catalogue = GetADFSCatalogue(disk); - if(catalogue) + std::unique_ptr dfs_catalogue, adfs_catalogue; + dfs_catalogue = GetDFSCatalogue(disk); + if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); + if(dfs_catalogue || adfs_catalogue) { target.disks = disks; - target.acorn.has_dfs = true; + target.acorn.has_dfs = !!dfs_catalogue; - switch(catalogue->bootOption) + switch((dfs_catalogue ?: adfs_catalogue)->bootOption) { case Catalogue::BootOption::None: target.loadingCommand = "*CAT\n"; break; default: target.acorn.should_hold_shift = true; break; diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index 98d799b05..78647447e 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -20,6 +20,7 @@ #include "../Storage/Cartridge/Formats/PRG.hpp" // Disks +#include "../Storage/Disk/Formats/AcornADF.hpp" #include "../Storage/Disk/Formats/D64.hpp" #include "../Storage/Disk/Formats/G64.hpp" #include "../Storage/Disk/Formats/SSD.hpp" @@ -80,6 +81,7 @@ std::list StaticAnalyser::GetTargets(const char *file_name) } Format("a26", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 + Format("adf", disks, Disk::AcornADF, TargetPlatform::Acorn) // ADF Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN Format("d64", disks, Disk::D64, TargetPlatform::Commodore) // D64 Format("dsd", disks, Disk::SSD, TargetPlatform::Acorn) // DSD diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index cc48d0520..949588528 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -49,23 +49,23 @@ template class MFMShifter: public Shifter { } void add_index_address_mark() { - static_cast(this)->output_short(output_ = 0x5224); - add_byte(0xfc); + static_cast(this)->output_short(output_ = MFMIndexAddressMark); + add_byte(MFMIndexAddressByte); } void add_ID_address_mark() { - static_cast(this)->output_short(output_ = 0x4489); - add_byte(0xfe); + static_cast(this)->output_short(output_ = MFMAddressMark); + add_byte(MFMIDAddressByte); } void add_data_address_mark() { - static_cast(this)->output_short(output_ = 0x4489); - add_byte(0xfb); + static_cast(this)->output_short(output_ = MFMAddressMark); + add_byte(MFMDataAddressByte); } void add_deleted_data_address_mark() { - static_cast(this)->output_short(output_ = 0x4489); - add_byte(0xf8); + static_cast(this)->output_short(output_ = MFMAddressMark); + add_byte(MFMDeletedDataAddressByte); } private: diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index 07563cbce..affb2fb78 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -22,6 +22,13 @@ const uint16_t FMIDAddressMark = 0xf57e; // data 0xfe, with clock 0xc7 => 1111 const uint16_t FMDataAddressMark = 0xf56f; // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111 const uint16_t FMDeletedDataAddressMark = 0xf56a; // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010 +const uint16_t MFMIndexAddressMark = 0x5224; +const uint16_t MFMAddressMark = 0x4489; +const uint8_t MFMIndexAddressByte = 0xfc; +const uint8_t MFMIDAddressByte = 0xfe; +const uint8_t MFMDataAddressByte = 0xfb; +const uint8_t MFMDeletedDataAddressByte = 0xf8; + struct Sector { uint8_t track, side, sector; diff --git a/Storage/Disk/Formats/AcornADF.cpp b/Storage/Disk/Formats/AcornADF.cpp new file mode 100644 index 000000000..69d95a99f --- /dev/null +++ b/Storage/Disk/Formats/AcornADF.cpp @@ -0,0 +1,82 @@ +// +// AcornADF.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "AcornADF.hpp" + +#include +#include "../Encodings/MFM.hpp" + +using namespace Storage::Disk; + +AcornADF::AcornADF(const char *file_name) : _file(nullptr) +{ + struct stat file_stats; + stat(file_name, &file_stats); + + // very loose validation: the file needs to be a multiple of 256 bytes + // and not ungainly large + if(file_stats.st_size & 255) throw ErrorNotAcornADF; + if(file_stats.st_size < 2048) throw ErrorNotAcornADF; + + _file = fopen(file_name, "rb"); + if(!_file) throw ErrorCantOpen; + + // check that the initial directory's 'Hugo's are present + fseek(_file, 513, SEEK_SET); + uint8_t bytes[4]; + fread(bytes, 1, 4, _file); + if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF; + + fseek(_file, 0x6fb, SEEK_SET); + fread(bytes, 1, 4, _file); + if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF; +} + +AcornADF::~AcornADF() +{ + if(_file) fclose(_file); +} + +unsigned int AcornADF::get_head_position_count() +{ + return 80; +} + +unsigned int AcornADF::get_head_count() +{ + return 2; +} + +std::shared_ptr AcornADF::get_track_at_position(unsigned int head, unsigned int position) +{ + std::shared_ptr track; + + if(head >= 2) return track; + long file_offset = (position * 2 + head) * 256 * 16; + fseek(_file, file_offset, SEEK_SET); + + std::vector sectors; + for(int sector = 0; sector < 16; sector++) + { + Storage::Encodings::MFM::Sector new_sector; + new_sector.track = (uint8_t)position; + new_sector.side = 0; + new_sector.sector = (uint8_t)sector; + + new_sector.data.resize(256); + fread(&new_sector.data[0], 1, 256, _file); + if(feof(_file)) + break; + + sectors.push_back(std::move(new_sector)); + } + + if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors); + + return track; +} diff --git a/Storage/Disk/Formats/AcornADF.hpp b/Storage/Disk/Formats/AcornADF.hpp new file mode 100644 index 000000000..64aeb54b1 --- /dev/null +++ b/Storage/Disk/Formats/AcornADF.hpp @@ -0,0 +1,48 @@ +// +// AcornADF.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef AcornADF_hpp +#define AcornADF_hpp + +#include "../Disk.hpp" + +namespace Storage { +namespace Disk { + +/*! + Provies a @c Disk containing an ADF disk image — a decoded sector dump of an Acorn ADFS disk. +*/ +class AcornADF: public Disk { + public: + /*! + Construct an @c AcornADF containing content from the file with name @c file_name. + + @throws ErrorCantOpen if this file can't be opened. + @throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image. + */ + AcornADF(const char *file_name); + ~AcornADF(); + + enum { + ErrorCantOpen, + ErrorNotAcornADF, + }; + + // implemented to satisfy @c Disk + unsigned int get_head_position_count(); + unsigned int get_head_count(); + std::shared_ptr get_track_at_position(unsigned int head, unsigned int position); + + private: + FILE *_file; +}; + +} +} + +#endif /* AcornADF_hpp */ diff --git a/Storage/Disk/Formats/SSD.cpp b/Storage/Disk/Formats/SSD.cpp index 30e3dd63f..333fac821 100644 --- a/Storage/Disk/Formats/SSD.cpp +++ b/Storage/Disk/Formats/SSD.cpp @@ -69,7 +69,8 @@ std::shared_ptr SSD::get_track_at_position(unsigned int head, unsigned in new_sector.data.resize(256); fread(&new_sector.data[0], 1, 256, _file); - if(feof(_file)) break; + if(feof(_file)) + break; sectors.push_back(std::move(new_sector)); } diff --git a/Storage/Disk/Formats/SSD.hpp b/Storage/Disk/Formats/SSD.hpp index 7fcd8e26d..543e554ea 100644 --- a/Storage/Disk/Formats/SSD.hpp +++ b/Storage/Disk/Formats/SSD.hpp @@ -20,10 +20,10 @@ namespace Disk { class SSD: public Disk { public: /*! - Construct a @c D64 containing content from the file with name @c file_name. + Construct an @c SSD containing content from the file with name @c file_name. @throws ErrorCantOpen if this file can't be opened. - @throws ErrorNotD64 if the file doesn't appear to contain a .D64 format image. + @throws ErrorNotSSD if the file doesn't appear to contain a .SSD format image. */ SSD(const char *file_name); ~SSD(); From 523dbb9678b39723a5a356771d509c5c0fe5292d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Sep 2016 18:32:26 -0400 Subject: [PATCH 43/51] This'll do for getting the ADF into the machine. --- StaticAnalyser/Acorn/Disk.cpp | 25 ++++++++++++++++++++++++- Storage/Disk/Formats/AcornADF.cpp | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index b670e1e4f..61b078c2a 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -248,7 +248,30 @@ std::unique_ptr StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh FMParser parser(true); parser.set_disk(disk); - std::shared_ptr directory = parser.get_sector(0, 2); + std::shared_ptr free_space_map_second_half = parser.get_sector(0, 1); + if(!free_space_map_second_half) return nullptr; + + std::vector root_directory; + root_directory.reserve(5 * 256); + for(uint8_t c = 2; c < 7; c++) + { + std::shared_ptr sector = parser.get_sector(0, c); + if(!sector) return nullptr; + root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end()); + } + + // Quick sanity checks. + if(root_directory[0x4cb]) return nullptr; + if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr; + if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr; + + switch(free_space_map_second_half->data[0xfd]) + { + default: catalogue->bootOption = Catalogue::BootOption::None; break; + case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break; + case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break; + case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break; + } return catalogue; } diff --git a/Storage/Disk/Formats/AcornADF.cpp b/Storage/Disk/Formats/AcornADF.cpp index 69d95a99f..13956ed3d 100644 --- a/Storage/Disk/Formats/AcornADF.cpp +++ b/Storage/Disk/Formats/AcornADF.cpp @@ -21,7 +21,7 @@ AcornADF::AcornADF(const char *file_name) : _file(nullptr) // very loose validation: the file needs to be a multiple of 256 bytes // and not ungainly large if(file_stats.st_size & 255) throw ErrorNotAcornADF; - if(file_stats.st_size < 2048) throw ErrorNotAcornADF; + if(file_stats.st_size < 7 * 256) throw ErrorNotAcornADF; _file = fopen(file_name, "rb"); if(!_file) throw ErrorCantOpen; From 7f4c78139c50ac75fda6ffddf022f68a7c0f5770 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Sep 2016 19:59:46 -0400 Subject: [PATCH 44/51] Attempted to ensure that ADFS disks get to an appropriately-configured Electron. Not immediately sure why the shift press isn't working; doesn't need to be dealt with immediately. --- Machines/Electron/Electron.cpp | 7 ++++++- StaticAnalyser/Acorn/StaticAnalyser.cpp | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 591edde76..95727d700 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -304,7 +304,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: if(_wd1770 && (address&0x00f0) == 0x00c0) { - if(is_holding_shift_) + if(is_holding_shift_ && address == 0xfcc4) { is_holding_shift_ = false; set_key_state(KeyShift, false); @@ -506,6 +506,11 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) { set_rom(ROMSlot0, _dfs, true); } + if(target.acorn.has_adfs) + { + set_rom(ROMSlot1, _adfs, true); + set_rom(ROMSlot2, std::vector(_adfs.begin() + 16384, _adfs.end()), true); + } _wd1770->set_disk(target.disks.front()); } diff --git a/StaticAnalyser/Acorn/StaticAnalyser.cpp b/StaticAnalyser/Acorn/StaticAnalyser.cpp index 0472c9e54..bd2680a7c 100644 --- a/StaticAnalyser/Acorn/StaticAnalyser.cpp +++ b/StaticAnalyser/Acorn/StaticAnalyser.cpp @@ -124,6 +124,7 @@ void StaticAnalyser::Acorn::AddTargets( { target.disks = disks; target.acorn.has_dfs = !!dfs_catalogue; + target.acorn.has_adfs = !!adfs_catalogue; switch((dfs_catalogue ?: adfs_catalogue)->bootOption) { From 9bbcbd10017e4c7d99512c0187e559bd6d4637a0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Sep 2016 20:05:56 -0400 Subject: [PATCH 45/51] Renamed class, intending to turn a `Disk::Drive` into literally just that, and have a thing with a PLL that consumes events be a `Controller`. --- Components/1770/1770.cpp | 4 +-- Components/1770/1770.hpp | 4 +-- Machines/Commodore/1540/C1540.cpp | 4 +-- Machines/Commodore/1540/C1540.hpp | 4 +-- Machines/Electron/Electron.cpp | 4 +-- .../Clock Signal.xcodeproj/project.pbxproj | 12 ++++----- StaticAnalyser/Acorn/Disk.cpp | 6 ++--- StaticAnalyser/Commodore/Disk.cpp | 6 ++--- .../{DiskDrive.cpp => DiskController.cpp} | 26 +++++++++---------- .../{DiskDrive.hpp => DiskController.hpp} | 10 +++---- 10 files changed, 39 insertions(+), 41 deletions(-) rename Storage/Disk/{DiskDrive.cpp => DiskController.cpp} (82%) rename Storage/Disk/{DiskDrive.hpp => DiskController.hpp} (90%) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index c3f000aaf..e7442d962 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -12,7 +12,7 @@ using namespace WD; WD1770::WD1770() : - Storage::Disk::Drive(8000000, 1, 300), + Storage::Disk::Controller(8000000, 1, 300), status_(0), interesting_event_mask_(Event::Command), resume_point_(0), @@ -60,7 +60,7 @@ uint8_t WD1770::get_register(int address) void WD1770::run_for_cycles(unsigned int number_of_cycles) { - if(status_ & Flag::MotorOn) Storage::Disk::Drive::run_for_cycles((int)number_of_cycles); + if(status_ & Flag::MotorOn) Storage::Disk::Controller::run_for_cycles((int)number_of_cycles); if(delay_time_) { diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index fcf806777..9bd73a50c 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -9,11 +9,11 @@ #ifndef _770_hpp #define _770_hpp -#include "../../Storage/Disk/DiskDrive.hpp" +#include "../../Storage/Disk/DiskController.hpp" namespace WD { -class WD1770: public Storage::Disk::Drive { +class WD1770: public Storage::Disk::Controller { public: WD1770(); diff --git a/Machines/Commodore/1540/C1540.cpp b/Machines/Commodore/1540/C1540.cpp index 2756ad058..eee5ffb30 100644 --- a/Machines/Commodore/1540/C1540.cpp +++ b/Machines/Commodore/1540/C1540.cpp @@ -14,7 +14,7 @@ using namespace Commodore::C1540; Machine::Machine() : _shift_register(0), - Storage::Disk::Drive(1000000, 4, 300) + Storage::Disk::Controller(1000000, 4, 300) { // create a serial port and a VIA to run it _serialPortVIA.reset(new SerialPortVIA); @@ -106,7 +106,7 @@ void Machine::run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); if(_driveVIA.get_motor_enabled()) // TODO: motor speed up/down - Storage::Disk::Drive::run_for_cycles(number_of_cycles); + Storage::Disk::Controller::run_for_cycles(number_of_cycles); } #pragma mark - 6522 delegate diff --git a/Machines/Commodore/1540/C1540.hpp b/Machines/Commodore/1540/C1540.hpp index 7a1b5e0e4..0311e945a 100644 --- a/Machines/Commodore/1540/C1540.hpp +++ b/Machines/Commodore/1540/C1540.hpp @@ -15,7 +15,7 @@ #include "../SerialBus.hpp" #include "../../../Storage/Disk/Disk.hpp" -#include "../../../Storage/Disk/DiskDrive.hpp" +#include "../../../Storage/Disk/DiskController.hpp" namespace Commodore { namespace C1540 { @@ -216,7 +216,7 @@ class Machine: public CPU6502::Processor, public MOS::MOS6522IRQDelegate::Delegate, public DriveVIA::Delegate, - public Storage::Disk::Drive { + public Storage::Disk::Controller { public: Machine(); diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 95727d700..c391f83eb 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -318,9 +318,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0xfc00: if(_wd1770 && (address&0x00f0) == 0x00c0) { - if(isReadOperation(operation)) - *value = 1; - else + if(!isReadOperation(operation)) { // TODO: // bit 0 => enable or disable drive 1 diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 659c4f7d0..4490fcf2d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -45,7 +45,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 */; }; + 4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.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 */; }; @@ -457,8 +457,8 @@ 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = ""; }; 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = ""; }; 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 = ""; }; - 4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskDrive.hpp; sourceTree = ""; }; + 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskController.cpp; sourceTree = ""; }; + 4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = ""; }; 4B73C7191D036BD90074D992 /* Vic20Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20Document.swift; sourceTree = ""; }; 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 = ""; }; @@ -1115,11 +1115,11 @@ children = ( 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */, 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */, - 4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */, + 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */, 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */, 4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */, 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, - 4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */, + 4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */, 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, 4BB697CF1D4BA44900248BDF /* Encodings */, 4BAB62B21D327F7E00DF5BA0 /* Formats */, @@ -2137,7 +2137,7 @@ 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, - 4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */, + 4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */, 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */, 4B1E85751D170228001EF87D /* Typer.cpp in Sources */, diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index 61b078c2a..a3825eeef 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -7,17 +7,17 @@ // #include "Disk.hpp" -#include "../../Storage/Disk/DiskDrive.hpp" +#include "../../Storage/Disk/DiskController.hpp" #include "../../Storage/Disk/Encodings/MFM.hpp" #include "../../NumberTheory/CRC.hpp" #include using namespace StaticAnalyser::Acorn; -class FMParser: public Storage::Disk::Drive { +class FMParser: public Storage::Disk::Controller { public: FMParser(bool is_mfm) : - Storage::Disk::Drive(4000000, 1, 300), + Storage::Disk::Controller(4000000, 1, 300), crc_generator_(0x1021, 0xffff), shift_register_(0), track_(0), is_mfm_(is_mfm) { diff --git a/StaticAnalyser/Commodore/Disk.cpp b/StaticAnalyser/Commodore/Disk.cpp index ee7e8b457..969898ac2 100644 --- a/StaticAnalyser/Commodore/Disk.cpp +++ b/StaticAnalyser/Commodore/Disk.cpp @@ -7,7 +7,7 @@ // #include "Disk.hpp" -#include "../../Storage/Disk/DiskDrive.hpp" +#include "../../Storage/Disk/DiskController.hpp" #include "../../Storage/Disk/Encodings/CommodoreGCR.hpp" #include "Utilities.hpp" @@ -17,9 +17,9 @@ using namespace StaticAnalyser::Commodore; -class CommodoreGCRParser: public Storage::Disk::Drive { +class CommodoreGCRParser: public Storage::Disk::Controller { public: - CommodoreGCRParser() : Storage::Disk::Drive(4000000, 1, 300), shift_register_(0), track_(1) + CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1) { // Make sure this drive really is at track '1'. while(!get_is_track_zero()) step(-1); diff --git a/Storage/Disk/DiskDrive.cpp b/Storage/Disk/DiskController.cpp similarity index 82% rename from Storage/Disk/DiskDrive.cpp rename to Storage/Disk/DiskController.cpp index f69788950..8a7185601 100644 --- a/Storage/Disk/DiskDrive.cpp +++ b/Storage/Disk/DiskController.cpp @@ -1,16 +1,16 @@ // -// DiskDrive.cpp +// DiskController.cpp // Clock Signal // // Created by Thomas Harte on 14/07/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // -#include "DiskDrive.hpp" +#include "DiskController.hpp" using namespace Storage::Disk; -Drive::Drive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) : +Controller::Controller(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), _head_position(0), @@ -22,7 +22,7 @@ Drive::Drive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsign _rotational_multiplier.simplify(); } -void Drive::set_expected_bit_length(Time bit_length) +void Controller::set_expected_bit_length(Time bit_length) { _bit_length = bit_length; @@ -33,23 +33,23 @@ void Drive::set_expected_bit_length(Time bit_length) _pll->set_delegate(this); } -void Drive::set_disk(std::shared_ptr disk) +void Controller::set_disk(std::shared_ptr disk) { _disk = disk; set_track(Time()); } -bool Drive::has_disk() +bool Controller::has_disk() { return (bool)_disk; } -bool Drive::get_is_track_zero() +bool Controller::get_is_track_zero() { return _head_position == 0; } -void Drive::step(int direction) +void Controller::step(int direction) { _head_position = std::max(_head_position + direction, 0); Time extra_time = get_time_into_next_event() / _rotational_multiplier; @@ -58,7 +58,7 @@ void Drive::step(int direction) set_track(_time_into_track); } -void Drive::set_track(Time initial_offset) +void Controller::set_track(Time initial_offset) { _track = _disk->get_track_at_position(0, (unsigned int)_head_position); // TODO: probably a better implementation of the empty track? @@ -80,7 +80,7 @@ void Drive::set_track(Time initial_offset) reset_timer_to_offset(offset * _rotational_multiplier); } -void Drive::run_for_cycles(int number_of_cycles) +void Controller::run_for_cycles(int number_of_cycles) { if(has_disk()) { @@ -99,7 +99,7 @@ void Drive::run_for_cycles(int number_of_cycles) #pragma mark - Track timed event loop -void Drive::get_next_event() +void Controller::get_next_event() { if(_track) _current_event = _track->get_next_event(); @@ -115,7 +115,7 @@ void Drive::get_next_event() set_next_event_time_interval(_current_event.length * _rotational_multiplier); } -void Drive::process_next_event() +void Controller::process_next_event() { switch(_current_event.type) { @@ -134,7 +134,7 @@ void Drive::process_next_event() #pragma mark - PLL delegate -void Drive::digital_phase_locked_loop_output_bit(int value) +void Controller::digital_phase_locked_loop_output_bit(int value) { process_input_bit(value, _cycles_since_index_hole); } diff --git a/Storage/Disk/DiskDrive.hpp b/Storage/Disk/DiskController.hpp similarity index 90% rename from Storage/Disk/DiskDrive.hpp rename to Storage/Disk/DiskController.hpp index 8c9938d54..f966f8453 100644 --- a/Storage/Disk/DiskDrive.hpp +++ b/Storage/Disk/DiskController.hpp @@ -1,13 +1,13 @@ // -// DiskDrive.hpp +// DiskController.hpp // Clock Signal // // Created by Thomas Harte on 14/07/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // -#ifndef DiskDrive_hpp -#define DiskDrive_hpp +#ifndef Storage_Disk_Controller_hpp +#define Storage_Disk_Controller_hpp #include "Disk.hpp" #include "DigitalPhaseLockedLoop.hpp" @@ -27,13 +27,13 @@ namespace Disk { TODO: double sided disks, communication of head size and permissible stepping extents, appropriate simulation of gain. */ -class Drive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop { +class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop { public: /*! Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier, spinning inserted disks at @c revolutions_per_minute. */ - Drive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute); + Controller(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute); /*! Communicates to the PLL the expected length of a bit as a fraction of a second. From 572d5587d950c0d8df92d56c9f1f1560d5203d5e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Sep 2016 21:24:16 -0400 Subject: [PATCH 46/51] Made a first stab at enabling multi-disk machines and thereby obeying (some of) the Plus 3's status register. --- Components/1770/1770.cpp | 9 +- Machines/Commodore/1540/C1540.cpp | 8 ++ Machines/Commodore/1540/C1540.hpp | 1 + Machines/Electron/Electron.cpp | 21 ++-- Machines/Electron/Electron.hpp | 4 +- Machines/Electron/Plus3.cpp | 35 +++++++ Machines/Electron/Plus3.hpp | 28 ++++++ .../Clock Signal.xcodeproj/project.pbxproj | 12 +++ StaticAnalyser/Acorn/Disk.cpp | 13 ++- StaticAnalyser/Commodore/Disk.cpp | 8 +- Storage/Disk/DiskController.cpp | 97 +++++++++++-------- Storage/Disk/DiskController.hpp | 50 ++++------ Storage/Disk/Drive.cpp | 46 +++++++++ Storage/Disk/Drive.hpp | 59 +++++++++++ 14 files changed, 291 insertions(+), 100 deletions(-) create mode 100644 Machines/Electron/Plus3.cpp create mode 100644 Machines/Electron/Plus3.hpp create mode 100644 Storage/Disk/Drive.cpp create mode 100644 Storage/Disk/Drive.hpp diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index e7442d962..e40cdca44 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -60,7 +60,7 @@ uint8_t WD1770::get_register(int address) void WD1770::run_for_cycles(unsigned int number_of_cycles) { - if(status_ & Flag::MotorOn) Storage::Disk::Controller::run_for_cycles((int)number_of_cycles); + Storage::Disk::Controller::run_for_cycles((int)number_of_cycles); if(delay_time_) { @@ -144,7 +144,11 @@ void WD1770::process_index_hole() } // motor power-down - if(index_hole_count_ == 9 && !(status_&Flag::Busy)) status_ &= ~Flag::MotorOn; + if(index_hole_count_ == 9 && !(status_&Flag::Busy)) + { + status_ &= ~Flag::MotorOn; + set_motor_on(false); + } } // +------+----------+-------------------------+ @@ -186,6 +190,7 @@ void WD1770::process_index_hole() #define SPIN_UP() \ status_ |= Flag::MotorOn; \ + set_motor_on(true); \ index_hole_count_ = 0; \ index_hole_count_target_ = 6; \ WAIT_FOR_EVENT(Event::IndexHoleTarget); diff --git a/Machines/Commodore/1540/C1540.cpp b/Machines/Commodore/1540/C1540.cpp index eee5ffb30..fc52a4006 100644 --- a/Machines/Commodore/1540/C1540.cpp +++ b/Machines/Commodore/1540/C1540.cpp @@ -102,9 +102,17 @@ void Machine::set_rom(const uint8_t *rom) memcpy(_rom, rom, sizeof(_rom)); } +void Machine::set_disk(std::shared_ptr disk) +{ + std::shared_ptr drive(new Storage::Disk::Drive); + drive->set_disk(disk); + set_drive(drive); +} + void Machine::run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); + set_motor_on(_driveVIA.get_motor_enabled()); if(_driveVIA.get_motor_enabled()) // TODO: motor speed up/down Storage::Disk::Controller::run_for_cycles(number_of_cycles); } diff --git a/Machines/Commodore/1540/C1540.hpp b/Machines/Commodore/1540/C1540.hpp index 0311e945a..cddefaeb8 100644 --- a/Machines/Commodore/1540/C1540.hpp +++ b/Machines/Commodore/1540/C1540.hpp @@ -232,6 +232,7 @@ class Machine: void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); void run_for_cycles(int number_of_cycles); + void set_disk(std::shared_ptr disk); // to satisfy CPU6502::Processor unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c391f83eb..ba43dfdcf 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -302,7 +302,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin break; case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: - if(_wd1770 && (address&0x00f0) == 0x00c0) + if(_plus3 && (address&0x00f0) == 0x00c0) { if(is_holding_shift_ && address == 0xfcc4) { @@ -310,22 +310,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin set_key_state(KeyShift, false); } if(isReadOperation(operation)) - *value = _wd1770->get_register(address); + *value = _plus3->get_register(address); else - _wd1770->set_register(address, *value); + _plus3->set_register(address, *value); } break; case 0xfc00: - if(_wd1770 && (address&0x00f0) == 0x00c0) + if(_plus3 && (address&0x00f0) == 0x00c0) { if(!isReadOperation(operation)) { - // TODO: - // bit 0 => enable or disable drive 1 - // bit 1 => enable or disable drive 2 - // bit 2 => side select - // bit 3 => single density select -// _wd1770->set_register(address, *value); + _plus3->set_control_register(*value); } } break; @@ -478,7 +473,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _tape.run_for_cycles(cycles); if(_typer) _typer->update((int)cycles); - if(_wd1770) _wd1770->run_for_cycles(4*cycles); + if(_plus3) _plus3->run_for_cycles(4*cycles); return cycles; } @@ -498,7 +493,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) if(target.disks.size()) { - _wd1770.reset(new WD::WD1770); + _plus3.reset(new Plus3); if(target.acorn.has_dfs) { @@ -510,7 +505,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) set_rom(ROMSlot2, std::vector(_adfs.begin() + 16384, _adfs.end()), true); } - _wd1770->set_disk(target.disks.front()); + _plus3->set_disk(target.disks.front(), 0); } ROMSlot slot = ROMSlot12; diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index aa99d1cb1..7b88f775a 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -10,12 +10,12 @@ #define Electron_hpp #include "../../Processors/6502/CPU6502.hpp" -#include "../../Components/1770/1770.hpp" #include "../../Storage/Tape/Tape.hpp" #include "../ConfigurationTarget.hpp" #include "../CRTMachine.hpp" #include "../Typer.hpp" +#include "Plus3.hpp" #include #include @@ -232,7 +232,7 @@ class Machine: bool _fast_load_is_in_data; // Disk - std::unique_ptr _wd1770; + std::unique_ptr _plus3; bool is_holding_shift_; // Outputs diff --git a/Machines/Electron/Plus3.cpp b/Machines/Electron/Plus3.cpp new file mode 100644 index 000000000..809846dcb --- /dev/null +++ b/Machines/Electron/Plus3.cpp @@ -0,0 +1,35 @@ +// +// Plus3.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Plus3.hpp" + +using namespace Electron; + +void Plus3::set_disk(std::shared_ptr disk, int drive) +{ + if(!_drives[drive]) _drives[drive].reset(new Storage::Disk::Drive); + _drives[drive]->set_disk(disk); +} + +void Plus3::set_control_register(uint8_t control) +{ + // TODO: + // bit 0 => enable or disable drive 1 + // bit 1 => enable or disable drive 2 + // bit 2 => side select + // bit 3 => single density select + switch(control&3) + { + case 0: set_drive(nullptr); break; + default: set_drive(_drives[0]); break; + case 2: set_drive(_drives[1]); break; + } + if(_drives[0]) _drives[0]->set_head((control & 0x04) ? 1 : 0); + if(_drives[1]) _drives[1]->set_head((control & 0x04) ? 1 : 0); + set_is_double_density(!(control & 0x08)); +} diff --git a/Machines/Electron/Plus3.hpp b/Machines/Electron/Plus3.hpp new file mode 100644 index 000000000..e7613367b --- /dev/null +++ b/Machines/Electron/Plus3.hpp @@ -0,0 +1,28 @@ +// +// Plus3.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Plus3_hpp +#define Plus3_hpp + +#include "../../Components/1770/1770.hpp" + +namespace Electron { + +class Plus3 : public WD::WD1770 { + public: + void set_disk(std::shared_ptr disk, int drive); + void set_control_register(uint8_t control); + + private: + std::shared_ptr _drives[2]; +}; + +} + +#endif /* Plus3_hpp */ + diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 4490fcf2d..8f4624052 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -26,6 +26,8 @@ 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 */; }; + 4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; }; + 4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */; }; 4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */; }; 4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; }; 4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */; }; @@ -423,6 +425,10 @@ 4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = ""; }; 4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Electron.cpp; path = Electron/Electron.cpp; sourceTree = ""; }; 4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Electron.hpp; path = Electron/Electron.hpp; sourceTree = ""; }; + 4B30512B1D989E2200B4FED8 /* Drive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Drive.cpp; sourceTree = ""; }; + 4B30512C1D989E2200B4FED8 /* Drive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Drive.hpp; sourceTree = ""; }; + 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Plus3.cpp; path = Electron/Plus3.cpp; sourceTree = ""; }; + 4B30512F1D98ACC600B4FED8 /* Plus3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Plus3.hpp; path = Electron/Plus3.hpp; sourceTree = ""; }; 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BinaryDump.cpp; sourceTree = ""; }; 4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = ""; }; 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = ""; }; @@ -962,6 +968,8 @@ children = ( 4B2E2D9B1C3A070400138695 /* Electron.cpp */, 4B2E2D9C1C3A070400138695 /* Electron.hpp */, + 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */, + 4B30512F1D98ACC600B4FED8 /* Plus3.hpp */, ); name = Electron; sourceTree = ""; @@ -1116,10 +1124,12 @@ 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */, 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */, 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */, + 4B30512B1D989E2200B4FED8 /* Drive.cpp */, 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */, 4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */, 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, 4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */, + 4B30512C1D989E2200B4FED8 /* Drive.hpp */, 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, 4BB697CF1D4BA44900248BDF /* Encodings */, 4BAB62B21D327F7E00DF5BA0 /* Formats */, @@ -2151,6 +2161,8 @@ 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */, 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, + 4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */, + 4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */, 4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */, 4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */, 4B4C83701D4F623200CD541F /* D64.cpp in Sources */, diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index a3825eeef..20fa8a0a6 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -16,18 +16,21 @@ using namespace StaticAnalyser::Acorn; class FMParser: public Storage::Disk::Controller { public: + std::shared_ptr drive; + FMParser(bool is_mfm) : Storage::Disk::Controller(4000000, 1, 300), crc_generator_(0x1021, 0xffff), shift_register_(0), track_(0), is_mfm_(is_mfm) { - // Make sure this drive really is at track '1'. - while(!get_is_track_zero()) step(-1); - Storage::Time bit_length; bit_length.length = 1; bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks) set_expected_bit_length(bit_length); + + drive.reset(new Storage::Disk::Drive); + set_drive(drive); + set_motor_on(true); } /*! @@ -184,7 +187,7 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha // c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format std::unique_ptr catalogue(new Catalogue); FMParser parser(false); - parser.set_disk(disk); + parser.drive->set_disk(disk); std::shared_ptr names = parser.get_sector(0, 0); std::shared_ptr details = parser.get_sector(0, 1); @@ -246,7 +249,7 @@ std::unique_ptr StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh { std::unique_ptr catalogue(new Catalogue); FMParser parser(true); - parser.set_disk(disk); + parser.drive->set_disk(disk); std::shared_ptr free_space_map_second_half = parser.get_sector(0, 1); if(!free_space_map_second_half) return nullptr; diff --git a/StaticAnalyser/Commodore/Disk.cpp b/StaticAnalyser/Commodore/Disk.cpp index 969898ac2..5929fc65e 100644 --- a/StaticAnalyser/Commodore/Disk.cpp +++ b/StaticAnalyser/Commodore/Disk.cpp @@ -19,10 +19,12 @@ using namespace StaticAnalyser::Commodore; class CommodoreGCRParser: public Storage::Disk::Controller { public: + std::shared_ptr drive; + CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1) { - // Make sure this drive really is at track '1'. - while(!get_is_track_zero()) step(-1); + drive.reset(new Storage::Disk::Drive); + set_drive(drive); } struct Sector @@ -186,7 +188,7 @@ std::list StaticAnalyser::Commodore::GetFiles(const std::shared_ptr files; CommodoreGCRParser parser; - parser.set_disk(disk); + parser.drive->set_disk(disk); // find any sector whatsoever to establish the current track std::shared_ptr sector; diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index 8a7185601..d8302e605 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -13,56 +13,25 @@ using namespace Storage::Disk; Controller::Controller(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), - _head_position(0), TimedEventLoop(clock_rate * clock_rate_multiplier) { _rotational_multiplier.length = 60; _rotational_multiplier.clock_rate = revolutions_per_minute; _rotational_multiplier.simplify(); + + // seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later + Time one; + set_expected_bit_length(one); } -void Controller::set_expected_bit_length(Time bit_length) +void Controller::setup_track() // Time initial_offset { - _bit_length = bit_length; + _track = _drive->get_track(); +// _track = _disk->get_track_at_position(0, (unsigned int)_head_position); - // 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 Controller::set_disk(std::shared_ptr disk) -{ - _disk = disk; - set_track(Time()); -} - -bool Controller::has_disk() -{ - return (bool)_disk; -} - -bool Controller::get_is_track_zero() -{ - return _head_position == 0; -} - -void Controller::step(int direction) -{ - _head_position = std::max(_head_position + direction, 0); - Time extra_time = get_time_into_next_event() / _rotational_multiplier; - extra_time.simplify(); - _time_into_track += extra_time; - set_track(_time_into_track); -} - -void Controller::set_track(Time initial_offset) -{ - _track = _disk->get_track_at_position(0, (unsigned int)_head_position); // TODO: probably a better implementation of the empty track? - Time offset; +/* Time offset; if(_track && _time_into_track.length > 0) { Time time_found = _track->seek_to(_time_into_track).simplify(); @@ -73,17 +42,18 @@ void Controller::set_track(Time initial_offset) { offset = _time_into_track; _time_into_track.set_zero(); - } + }*/ reset_timer(); get_next_event(); - reset_timer_to_offset(offset * _rotational_multiplier); +// reset_timer_to_offset(offset * _rotational_multiplier); } void Controller::run_for_cycles(int number_of_cycles) { - if(has_disk()) + if(_drive && _drive->has_disk() && _motor_is_on) { + if(!_track) setup_track(); number_of_cycles *= _clock_rate_multiplier; while(number_of_cycles) { @@ -132,9 +102,50 @@ void Controller::process_next_event() get_next_event(); } -#pragma mark - PLL delegate +#pragma mark - PLL control and delegate + +void Controller::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 Controller::digital_phase_locked_loop_output_bit(int value) { process_input_bit(value, _cycles_since_index_hole); } + +#pragma mark - Drive actions + +bool Controller::get_is_track_zero() +{ + if(!_drive) return false; + return _drive->get_is_track_zero(); +} + +void Controller::step(int direction) +{ + if(_drive) _drive->step(direction); + invalidate_track(); +} + +void Controller::set_motor_on(bool motor_on) +{ + _motor_is_on = motor_on; +} + +void Controller::set_drive(std::shared_ptr drive) +{ + _drive = drive; + invalidate_track(); +} + +void Controller::invalidate_track() +{ + _track = nullptr; +} diff --git a/Storage/Disk/DiskController.hpp b/Storage/Disk/DiskController.hpp index f966f8453..b83165c98 100644 --- a/Storage/Disk/DiskController.hpp +++ b/Storage/Disk/DiskController.hpp @@ -9,7 +9,7 @@ #ifndef Storage_Disk_Controller_hpp #define Storage_Disk_Controller_hpp -#include "Disk.hpp" +#include "Drive.hpp" #include "DigitalPhaseLockedLoop.hpp" #include "../TimedEventLoop.hpp" @@ -17,18 +17,16 @@ namespace Storage { namespace Disk { /*! - Provides the shell for emulating a disk drive — something that takes a disk and has a drive head - that steps between tracks, using a phase locked loop ('PLL') to decode a bit stream from the surface of - the disk. + Provides the shell for emulating a disk controller — something that is connected to a disk drive and uses a + phase locked loop ('PLL') to decode a bit stream from the surface of the disk. Partly abstract; it is expected that subclasses will provide methods to deal with receiving a newly-recognised bit from the PLL and with crossing the index hole. - TODO: double sided disks, communication of head size and permissible stepping extents, appropriate - simulation of gain. + TODO: communication of head size and permissible stepping extents, appropriate simulation of gain. */ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop { - public: + protected: /*! Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier, spinning inserted disks at @c revolutions_per_minute. @@ -40,41 +38,22 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop */ void set_expected_bit_length(Time bit_length); - /*! - Inserts @c disk into the drive. - */ - void set_disk(std::shared_ptr disk); - - /*! - @returns @c true if a disk is currently inserted; @c false otherwise. - */ - bool has_disk(); - /*! Advances the drive by @c number_of_cycles cycles. */ void run_for_cycles(int number_of_cycles); /*! - @returns @c true if the drive head is currently at track zero; @c false otherwise. + Sets the current drive. */ - bool get_is_track_zero(); - - /*! - Steps the disk head the specified number of tracks. Positive numbers step inwards, negative numbers - step outwards. - */ - void step(int direction); + void set_drive(std::shared_ptr drive); + void invalidate_track(); /*! Enables or disables the disk motor. */ void set_motor_on(bool motor_on); - // to satisfy DigitalPhaseLockedLoop::Delegate - void digital_phase_locked_loop_output_bit(int value); - - protected: /*! Should be implemented by subclasses; communicates each bit that the PLL recognises, also specifying the amount of time since the index hole was last seen. @@ -89,6 +68,12 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop // for TimedEventLoop virtual void process_next_event(); + // to satisfy DigitalPhaseLockedLoop::Delegate + void digital_phase_locked_loop_output_bit(int value); + + bool get_is_track_zero(); + void step(int direction); + private: Time _bit_length; unsigned int _clock_rate; @@ -96,15 +81,16 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop Time _rotational_multiplier; std::shared_ptr _pll; - std::shared_ptr _disk; + std::shared_ptr _drive; std::shared_ptr _track; - int _head_position; unsigned int _cycles_since_index_hole; - void set_track(Time initial_offset); inline void get_next_event(); Track::Event _current_event; Time _time_into_track; + bool _motor_is_on; + + void setup_track(); }; } diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp new file mode 100644 index 000000000..564dee21b --- /dev/null +++ b/Storage/Disk/Drive.cpp @@ -0,0 +1,46 @@ +// +// Drive.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Drive.hpp" +#include + +using namespace Storage::Disk; + +Drive::Drive() + : _head_position(0), _head(0) {} + +void Drive::set_disk(std::shared_ptr disk) +{ + _disk = disk; +} + +bool Drive::has_disk() +{ + return (bool)_disk; +} + +bool Drive::get_is_track_zero() +{ + return _head_position == 0; +} + +void Drive::step(int direction) +{ + _head_position = std::max(_head_position + direction, 0); +} + +void Drive::set_head(unsigned int head) +{ + _head = head; +} + +std::shared_ptr Drive::get_track() +{ + if(_disk) return _disk->get_track_at_position(_head, (unsigned int)_head_position); + return nullptr; +} diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp new file mode 100644 index 000000000..e63434f7f --- /dev/null +++ b/Storage/Disk/Drive.hpp @@ -0,0 +1,59 @@ +// +// Drive.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Drive_hpp +#define Drive_hpp + +#include +#include "Disk.hpp" + +namespace Storage { +namespace Disk { + +class Drive { + public: + Drive(); + + /*! + Inserts @c disk into the drive. + */ + void set_disk(std::shared_ptr disk); + + /*! + @returns @c true if a disk is currently inserted; @c false otherwise. + */ + bool has_disk(); + + /*! + @returns @c true if the drive head is currently at track zero; @c false otherwise. + */ + bool get_is_track_zero(); + + /*! + Steps the disk head the specified number of tracks. Positive numbers step inwards, negative numbers + step outwards. + */ + void step(int direction); + + /*! + */ + void set_head(unsigned int head); + + std::shared_ptr get_track(); + + private: + std::shared_ptr _disk; + int _head_position; + unsigned int _head; +}; + + +} +} + +#endif /* Drive_hpp */ From 4db086949a3b59c9d7f09a5153ddc5f9d42f5444 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Sep 2016 21:38:52 -0400 Subject: [PATCH 47/51] Made an attempt to add MFM decoding to the 1770; ensured something is returned when reading the Plus 3 status register again. --- Components/1770/1770.cpp | 41 ++++++++++++++++++++++++++++++++-- Components/1770/1770.hpp | 1 + Machines/Electron/Electron.cpp | 2 ++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index e40cdca44..f3590fa24 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -17,7 +17,8 @@ WD1770::WD1770() : interesting_event_mask_(Event::Command), resume_point_(0), delay_time_(0), - index_hole_count_target_(-1) + index_hole_count_target_(-1), + is_awaiting_marker_value_(false) { set_is_double_density(false); posit_event(Event::Command); @@ -30,6 +31,8 @@ void WD1770::set_is_double_density(bool is_double_density) bit_length.length = 1; bit_length.clock_rate = is_double_density ? 500000 : 250000; set_expected_bit_length(bit_length); + + if(!is_double_density) is_awaiting_marker_value_ = false; } void WD1770::set_register(int address, uint8_t value) @@ -104,7 +107,19 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) } else { - // TODO: MFM + switch(shift_register_ & 0xffff) + { + case Storage::Encodings::MFM::MFMIndexAddressMark: + bits_since_token_ = 0; + is_awaiting_marker_value_ = true; + return; + case Storage::Encodings::MFM::MFMAddressMark: + bits_since_token_ = 0; + is_awaiting_marker_value_ = true; + return; + default: + break; + } } if(token_type != Token::Byte) @@ -128,6 +143,28 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) ((shift_register_ & 0x1000) >> 6) | ((shift_register_ & 0x4000) >> 7)); bits_since_token_ = 0; + + if(is_awaiting_marker_value_ && is_double_density_) + { + is_awaiting_marker_value_ = false; + switch(latest_token_.byte_value) + { + case Storage::Encodings::MFM::MFMIndexAddressByte: + latest_token_.type = Token::Index; + break; + case Storage::Encodings::MFM::MFMIDAddressByte: + latest_token_.type = Token::ID; + break; + case Storage::Encodings::MFM::MFMDataAddressByte: + latest_token_.type = Token::Data; + break; + case Storage::Encodings::MFM::MFMDeletedDataAddressByte: + latest_token_.type = Token::DeletedData; + break; + default: break; + } + } + posit_event(Event::Token); return; } diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 9bd73a50c..0a73865d3 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -49,6 +49,7 @@ class WD1770: public Storage::Disk::Controller { int index_hole_count_target_; int bits_since_token_; int distance_into_section_; + bool is_awaiting_marker_value_; int step_direction_; void set_interrupt_request(bool interrupt_request) {} diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index ba43dfdcf..bd3efa1d2 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -322,6 +322,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { _plus3->set_control_register(*value); } + else + *value = 1; } break; From 6330e5706c410cf75b3fae024b0f5dd6a627577c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Sep 2016 21:44:46 -0400 Subject: [PATCH 48/51] Fixed seek verify. First ADFS directory is visible. --- Components/1770/1770.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index f3590fa24..da7895cd1 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -317,6 +317,7 @@ void WD1770::posit_event(Event new_event_type) } index_hole_count_ = 0; + distance_into_section_ = 0; verify_read_data: WAIT_FOR_EVENT(Event::IndexHole | Event::Token); From ca53fac73256c26ecccab7ef8492df32be45c6ff Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 26 Sep 2016 21:20:30 -0400 Subject: [PATCH 49/51] Switched to assuming a single-sided disk, moved out magic constants. --- Storage/Disk/Formats/AcornADF.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Storage/Disk/Formats/AcornADF.cpp b/Storage/Disk/Formats/AcornADF.cpp index 13956ed3d..c82d27e65 100644 --- a/Storage/Disk/Formats/AcornADF.cpp +++ b/Storage/Disk/Formats/AcornADF.cpp @@ -11,6 +11,11 @@ #include #include "../Encodings/MFM.hpp" +namespace { + static const unsigned int sectors_per_track = 16; + static const unsigned int bytes_per_sector = 256; +} + using namespace Storage::Disk; AcornADF::AcornADF(const char *file_name) : _file(nullptr) @@ -20,8 +25,8 @@ AcornADF::AcornADF(const char *file_name) : _file(nullptr) // very loose validation: the file needs to be a multiple of 256 bytes // and not ungainly large - if(file_stats.st_size & 255) throw ErrorNotAcornADF; - if(file_stats.st_size < 7 * 256) throw ErrorNotAcornADF; + if(file_stats.st_size % bytes_per_sector) throw ErrorNotAcornADF; + if(file_stats.st_size < 7 * bytes_per_sector) throw ErrorNotAcornADF; _file = fopen(file_name, "rb"); if(!_file) throw ErrorCantOpen; @@ -49,7 +54,7 @@ unsigned int AcornADF::get_head_position_count() unsigned int AcornADF::get_head_count() { - return 2; + return 1; } std::shared_ptr AcornADF::get_track_at_position(unsigned int head, unsigned int position) @@ -57,19 +62,19 @@ std::shared_ptr AcornADF::get_track_at_position(unsigned int head, unsign std::shared_ptr track; if(head >= 2) return track; - long file_offset = (position * 2 + head) * 256 * 16; + long file_offset = (position * 1 + head) * bytes_per_sector * sectors_per_track; fseek(_file, file_offset, SEEK_SET); std::vector sectors; - for(int sector = 0; sector < 16; sector++) + for(int sector = 0; sector < sectors_per_track; sector++) { Storage::Encodings::MFM::Sector new_sector; new_sector.track = (uint8_t)position; - new_sector.side = 0; + new_sector.side = (uint8_t)head; new_sector.sector = (uint8_t)sector; - new_sector.data.resize(256); - fread(&new_sector.data[0], 1, 256, _file); + new_sector.data.resize(bytes_per_sector); + fread(&new_sector.data[0], 1, bytes_per_sector, _file); if(feof(_file)) break; From 91235c7fd7d128a26e7fbd3d222f679f873e0c93 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 27 Sep 2016 07:36:37 -0400 Subject: [PATCH 50/51] Fixed issue whereby parts of data that merely looked like index or ID address marks within tracks caused a resynchronisation of the tokeniser. --- Components/1770/1770.cpp | 88 ++++++++++++++++++++++------------------ Components/1770/1770.hpp | 1 + 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index da7895cd1..0b9409c8d 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -18,7 +18,8 @@ WD1770::WD1770() : resume_point_(0), delay_time_(0), index_hole_count_target_(-1), - is_awaiting_marker_value_(false) + is_awaiting_marker_value_(false), + is_reading_data_(false) { set_is_double_density(false); posit_event(Event::Command); @@ -85,49 +86,52 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) bits_since_token_++; Token::Type token_type = Token::Byte; - if(!is_double_density_) + if(!is_reading_data_) { - switch(shift_register_ & 0xffff) + if(!is_double_density_) { - case Storage::Encodings::MFM::FMIndexAddressMark: - token_type = Token::Index; - break; - case Storage::Encodings::MFM::FMIDAddressMark: - token_type = Token::ID; - break; - case Storage::Encodings::MFM::FMDataAddressMark: - token_type = Token::Data; - break; - case Storage::Encodings::MFM::FMDeletedDataAddressMark: - token_type = Token::DeletedData; - break; - default: - break; + switch(shift_register_ & 0xffff) + { + case Storage::Encodings::MFM::FMIndexAddressMark: + token_type = Token::Index; + break; + case Storage::Encodings::MFM::FMIDAddressMark: + token_type = Token::ID; + break; + case Storage::Encodings::MFM::FMDataAddressMark: + token_type = Token::Data; + break; + case Storage::Encodings::MFM::FMDeletedDataAddressMark: + token_type = Token::DeletedData; + break; + default: + break; + } } - } - else - { - switch(shift_register_ & 0xffff) + else { - case Storage::Encodings::MFM::MFMIndexAddressMark: - bits_since_token_ = 0; - is_awaiting_marker_value_ = true; - return; - case Storage::Encodings::MFM::MFMAddressMark: - bits_since_token_ = 0; - is_awaiting_marker_value_ = true; - return; - default: - break; + switch(shift_register_ & 0xffff) + { + case Storage::Encodings::MFM::MFMIndexAddressMark: + bits_since_token_ = 0; + is_awaiting_marker_value_ = true; + return; + case Storage::Encodings::MFM::MFMAddressMark: + bits_since_token_ = 0; + is_awaiting_marker_value_ = true; + return; + default: + break; + } } - } - if(token_type != Token::Byte) - { - latest_token_.type = token_type; - bits_since_token_ = 0; - posit_event(Event::Token); - return; + if(token_type != Token::Byte) + { + latest_token_.type = token_type; + bits_since_token_ = 0; + posit_event(Event::Token); + return; + } } if(bits_since_token_ == 16) @@ -213,7 +217,7 @@ void WD1770::process_index_hole() #define READ_ID() \ if(new_event_type == Event::Token) \ { \ - if(!distance_into_section_ && latest_token_.type == Token::ID) distance_into_section_++; \ + if(!distance_into_section_ && latest_token_.type == Token::ID) {is_reading_data_ = true; distance_into_section_++; } \ else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) \ { \ header[distance_into_section_ - 1] = latest_token_.byte_value; \ @@ -242,6 +246,7 @@ void WD1770::posit_event(Event new_event_type) // Wait for a new command, branch to the appropriate handler. wait_for_command: printf("Idle...\n"); + is_reading_data_ = false; status_ &= ~Flag::Busy; index_hole_count_ = 0; WAIT_FOR_EVENT(Event::Command); @@ -331,9 +336,11 @@ void WD1770::posit_event(Event new_event_type) } if(distance_into_section_ == 7) { + is_reading_data_ = false; // TODO: CRC check if(header[0] == track_) { + printf("Reached track %d\n", track_); status_ &= ~Flag::CRCError; set_interrupt_request(true); goto wait_for_command; @@ -381,6 +388,7 @@ void WD1770::posit_event(Event new_event_type) } if(distance_into_section_ == 7) { + is_reading_data_ = false; if(header[0] == track_ && header[2] == sector_) { // TODO: test CRC @@ -402,6 +410,7 @@ void WD1770::posit_event(Event new_event_type) { status_ |= (latest_token_.type == Token::DeletedData) ? Flag::RecordType : 0; distance_into_section_ = 0; + is_reading_data_ = true; goto type2_read_byte; } goto type2_read_data; @@ -434,6 +443,7 @@ void WD1770::posit_event(Event new_event_type) goto test_type2_write_protection; } set_interrupt_request(true); + printf("Read sector %d\n", sector_); goto wait_for_command; } goto type2_check_crc; diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 0a73865d3..c6fabf718 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -55,6 +55,7 @@ class WD1770: public Storage::Disk::Controller { void set_interrupt_request(bool interrupt_request) {} // Tokeniser + bool is_reading_data_; bool is_double_density_; int shift_register_; struct Token { From 79412dc84d9655f95f650505b50a5db0d2c4e81a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 28 Sep 2016 21:28:34 -0400 Subject: [PATCH 51/51] Upped MFM clock cycles, switched back to using the typer for the ADFS and adjusted ADFS ROM slots. --- Components/1770/1770.cpp | 2 +- Machines/Electron/Electron.cpp | 4 ++-- StaticAnalyser/Acorn/StaticAnalyser.cpp | 15 ++++++++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 0b9409c8d..4c3f2ab4f 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -12,7 +12,7 @@ using namespace WD; WD1770::WD1770() : - Storage::Disk::Controller(8000000, 1, 300), + Storage::Disk::Controller(8000000, 16, 300), status_(0), interesting_event_mask_(Event::Command), resume_point_(0), diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index bd3efa1d2..efd60ceb3 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -503,8 +503,8 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) } if(target.acorn.has_adfs) { - set_rom(ROMSlot1, _adfs, true); - set_rom(ROMSlot2, std::vector(_adfs.begin() + 16384, _adfs.end()), true); + set_rom(ROMSlot4, _adfs, true); + set_rom(ROMSlot5, std::vector(_adfs.begin() + 16384, _adfs.end()), true); } _plus3->set_disk(target.disks.front(), 0); diff --git a/StaticAnalyser/Acorn/StaticAnalyser.cpp b/StaticAnalyser/Acorn/StaticAnalyser.cpp index bd2680a7c..f15bf1a65 100644 --- a/StaticAnalyser/Acorn/StaticAnalyser.cpp +++ b/StaticAnalyser/Acorn/StaticAnalyser.cpp @@ -126,11 +126,20 @@ void StaticAnalyser::Acorn::AddTargets( target.acorn.has_dfs = !!dfs_catalogue; target.acorn.has_adfs = !!adfs_catalogue; - switch((dfs_catalogue ?: adfs_catalogue)->bootOption) + std::string adfs_command; + Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; + switch(bootOption) { - case Catalogue::BootOption::None: target.loadingCommand = "*CAT\n"; break; - default: target.acorn.should_hold_shift = true; break; + case Catalogue::BootOption::None: adfs_command = "*CAT\n"; break; + case Catalogue::BootOption::LoadBOOT: adfs_command = "*MOUNT\n*LOAD !BOOT\n"; break; + case Catalogue::BootOption::RunBOOT: adfs_command = "*MOUNT\n*RUN !BOOT\n"; break; + case Catalogue::BootOption::ExecBOOT: adfs_command = "*MOUNT\n*EXEC !BOOT\n"; break; } + + if(target.acorn.has_dfs && bootOption != Catalogue::BootOption::None) + target.acorn.should_hold_shift = true; + else + target.loadingCommand = adfs_command; } }