mirror of
https://github.com/TomHarte/CLK.git
synced 2025-09-27 07:16:46 +00:00
Compare commits
41 Commits
2020-01-06
...
2020-01-16
Author | SHA1 | Date | |
---|---|---|---|
|
1422f8a93a | ||
|
f0da75f8e9 | ||
|
cb8a7a4137 | ||
|
efd684dc56 | ||
|
aeac6b5888 | ||
|
9bb294a023 | ||
|
1972ca00a4 | ||
|
6a185a574a | ||
|
c606931c93 | ||
|
93cecf0882 | ||
|
aac3d27c10 | ||
|
99122efbbc | ||
|
30e856b9e4 | ||
|
91fae86e73 | ||
|
f5c194386c | ||
|
98f7662185 | ||
|
62c3720c97 | ||
|
6b08239199 | ||
|
f258fc2971 | ||
|
6b84ae3095 | ||
|
5dd8c677f1 | ||
|
1cbcd5355f | ||
|
9799250f2c | ||
|
fab35b360a | ||
|
80fcf5b5c0 | ||
|
b3b2e18c4b | ||
|
2d233b6358 | ||
|
83ed36eb08 | ||
|
89f4032ffc | ||
|
8c90ec4636 | ||
|
514141f8c5 | ||
|
8e3a618619 | ||
|
6df6af09de | ||
|
f42655a0fc | ||
|
f81a7f0faf | ||
|
2b4c924399 | ||
|
64517a02b7 | ||
|
b4befd57a9 | ||
|
2c742a051e | ||
|
6595f8f527 | ||
|
985b36da73 |
@@ -20,7 +20,9 @@
|
|||||||
|
|
||||||
using namespace Analyser::Static::Oric;
|
using namespace Analyser::Static::Oric;
|
||||||
|
|
||||||
static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
|
namespace {
|
||||||
|
|
||||||
|
int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
|
||||||
int score = 0;
|
int score = 0;
|
||||||
|
|
||||||
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
|
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
|
||||||
@@ -30,7 +32,7 @@ static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, cons
|
|||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||||
const std::set<uint16_t> rom_functions = {
|
const std::set<uint16_t> rom_functions = {
|
||||||
0x0228, 0x022b,
|
0x0228, 0x022b,
|
||||||
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
|
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
|
||||||
@@ -51,10 +53,10 @@ static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembl
|
|||||||
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
|
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
|
||||||
};
|
};
|
||||||
|
|
||||||
return Score(disassembly, rom_functions, variable_locations);
|
return score(disassembly, rom_functions, variable_locations);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
||||||
const std::set<uint16_t> rom_functions = {
|
const std::set<uint16_t> rom_functions = {
|
||||||
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
|
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
|
||||||
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
|
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
|
||||||
@@ -76,10 +78,10 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl
|
|||||||
0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
|
0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
|
||||||
};
|
};
|
||||||
|
|
||||||
return Score(disassembly, rom_functions, variable_locations);
|
return score(disassembly, rom_functions, variable_locations);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
|
bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
|
||||||
/*
|
/*
|
||||||
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
|
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
|
||||||
*/
|
*/
|
||||||
@@ -100,17 +102,22 @@ static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
|
|||||||
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
|
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool IsJasmin(Storage::Encodings::MFM::Parser &parser) {
|
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
|
||||||
/*
|
/*
|
||||||
The Jasmin boot sector is sector 1 of track 0 and is loaded at $400;
|
Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
|
||||||
disassemble it to test it for validity.
|
use disassembly to test for likely matches.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
|
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
|
||||||
if(!sector) return false;
|
if(!sector) return false;
|
||||||
if(sector->samples.empty()) return false;
|
if(sector->samples.empty()) return false;
|
||||||
|
|
||||||
const std::vector<uint8_t> &first_sample = sector->samples[0];
|
// Take a copy of the first sampling, and keep only the final 256 bytes (assuming at least that many were found).
|
||||||
if(first_sample.size() != 256) return false;
|
std::vector<uint8_t> first_sample = sector->samples[0];
|
||||||
|
if(first_sample.size() < 256) return false;
|
||||||
|
if(first_sample.size() > 256) {
|
||||||
|
first_sample.erase(first_sample.end() - 256, first_sample.end());
|
||||||
|
}
|
||||||
|
|
||||||
// Grab a disassembly.
|
// Grab a disassembly.
|
||||||
const auto disassembly =
|
const auto disassembly =
|
||||||
@@ -120,14 +127,24 @@ static bool IsJasmin(Storage::Encodings::MFM::Parser &parser) {
|
|||||||
int register_hits = 0;
|
int register_hits = 0;
|
||||||
for(auto list : {disassembly.external_stores, disassembly.external_loads, disassembly.external_modifies}) {
|
for(auto list : {disassembly.external_stores, disassembly.external_loads, disassembly.external_modifies}) {
|
||||||
for(auto address : list) {
|
for(auto address : list) {
|
||||||
register_hits += (address >= 0x3f4 && address <= 0x3ff);
|
register_hits += (address >= range_start && address <= range_end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arbitrary, sure, but as long as at least two accesses to Jasmin registers are found, accept this.
|
// Arbitrary, sure, but as long as at least two accesses to the requested register range are found, accept this.
|
||||||
return register_hits >= 2;
|
return register_hits >= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_jasmin(Storage::Encodings::MFM::Parser &parser) {
|
||||||
|
return is_400_loader(parser, 0x3f4, 0x3ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
|
||||||
|
return is_400_loader(parser, 0x310, 0x323);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||||
auto target = std::make_unique<Target>();
|
auto target = std::make_unique<Target>();
|
||||||
target->machine = Machine::Oric;
|
target->machine = Machine::Oric;
|
||||||
@@ -146,9 +163,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
|
|||||||
const Analyser::Static::MOS6502::Disassembly disassembly =
|
const Analyser::Static::MOS6502::Disassembly disassembly =
|
||||||
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
|
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
|
||||||
|
|
||||||
int basic10_score = Basic10Score(disassembly);
|
if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
|
||||||
int basic11_score = Basic11Score(disassembly);
|
|
||||||
if(basic10_score > basic11_score) basic10_votes++; else basic11_votes++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,17 +173,22 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!media.disks.empty()) {
|
if(!media.disks.empty()) {
|
||||||
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc and
|
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
|
||||||
// Jasmin formats here.
|
// Jasmin and BD-DOS formats here.
|
||||||
for(auto &disk: media.disks) {
|
for(auto &disk: media.disks) {
|
||||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||||
if(IsMicrodisc(parser)) {
|
|
||||||
|
if(is_microdisc(parser)) {
|
||||||
target->disk_interface = Target::DiskInterface::Microdisc;
|
target->disk_interface = Target::DiskInterface::Microdisc;
|
||||||
target->media.disks.push_back(disk);
|
target->media.disks.push_back(disk);
|
||||||
} else if(IsJasmin(parser)) {
|
} else if(is_jasmin(parser)) {
|
||||||
target->disk_interface = Target::DiskInterface::Jasmin;
|
target->disk_interface = Target::DiskInterface::Jasmin;
|
||||||
target->should_start_jasmin = true;
|
target->should_start_jasmin = true;
|
||||||
target->media.disks.push_back(disk);
|
target->media.disks.push_back(disk);
|
||||||
|
} else if(is_bd500(parser)) {
|
||||||
|
target->disk_interface = Target::DiskInterface::BD500;
|
||||||
|
target->media.disks.push_back(disk);
|
||||||
|
target->rom = Target::ROM::BASIC10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@ struct Target: public ::Analyser::Static::Target {
|
|||||||
Microdisc,
|
Microdisc,
|
||||||
Pravetz,
|
Pravetz,
|
||||||
Jasmin,
|
Jasmin,
|
||||||
|
BD500,
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -47,6 +47,7 @@
|
|||||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||||
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
|
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
|
||||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||||
|
|
||||||
// Mass Storage Devices (i.e. usually, hard disks)
|
// Mass Storage Devices (i.e. usually, hard disks)
|
||||||
@@ -147,6 +148,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
|||||||
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
|
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
|
||||||
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
||||||
Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST) // ST
|
Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST) // ST
|
||||||
|
Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST) // STX
|
||||||
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||||
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||||
|
@@ -336,6 +336,7 @@ void WD1770::posit_event(int new_event_type) {
|
|||||||
READ_ID();
|
READ_ID();
|
||||||
|
|
||||||
if(index_hole_count_ == 6) {
|
if(index_hole_count_ == 6) {
|
||||||
|
LOG("Nothing found to verify");
|
||||||
update_status([] (Status &status) {
|
update_status([] (Status &status) {
|
||||||
status.seek_error = true;
|
status.seek_error = true;
|
||||||
});
|
});
|
||||||
@@ -823,6 +824,10 @@ void WD1770::set_head_loaded(bool head_loaded) {
|
|||||||
if(head_loaded) posit_event(int(Event1770::HeadLoad));
|
if(head_loaded) posit_event(int(Event1770::HeadLoad));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WD1770::get_head_loaded() {
|
||||||
|
return head_is_loaded_;
|
||||||
|
}
|
||||||
|
|
||||||
ClockingHint::Preference WD1770::preferred_clocking() {
|
ClockingHint::Preference WD1770::preferred_clocking() {
|
||||||
if(status_.busy) return ClockingHint::Preference::RealTime;
|
if(status_.busy) return ClockingHint::Preference::RealTime;
|
||||||
return Storage::Disk::MFMController::preferred_clocking();
|
return Storage::Disk::MFMController::preferred_clocking();
|
||||||
|
@@ -80,6 +80,9 @@ class WD1770: public Storage::Disk::MFMController {
|
|||||||
virtual void set_motor_on(bool motor_on);
|
virtual void set_motor_on(bool motor_on);
|
||||||
void set_head_loaded(bool head_loaded);
|
void set_head_loaded(bool head_loaded);
|
||||||
|
|
||||||
|
/// @returns The last value posted to @c set_head_loaded.
|
||||||
|
bool get_head_loaded();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Personality personality_;
|
Personality personality_;
|
||||||
inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); }
|
inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); }
|
||||||
|
146
Machines/Oric/BD500.cpp
Normal file
146
Machines/Oric/BD500.cpp
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
//
|
||||||
|
// BD500.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 14/01/2020.
|
||||||
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "BD500.hpp"
|
||||||
|
|
||||||
|
using namespace Oric;
|
||||||
|
|
||||||
|
BD500::BD500() : DiskController(P1793, 9000000, Storage::Disk::Drive::ReadyType::ShugartModifiedRDY) {
|
||||||
|
disable_basic_rom_ = true;
|
||||||
|
select_paged_item();
|
||||||
|
set_is_double_density(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BD500::write(int address, uint8_t value) {
|
||||||
|
access(address);
|
||||||
|
|
||||||
|
if(address >= 0x0320 && address <= 0x0323) {
|
||||||
|
// if(address == 0x320) printf("Command %02x\n", value);
|
||||||
|
WD::WD1770::write(address, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t BD500::read(int address) {
|
||||||
|
access(address);
|
||||||
|
|
||||||
|
switch(address) {
|
||||||
|
default: return 0xff;
|
||||||
|
|
||||||
|
case 0x0320: case 0x0321: case 0x0322: case 0x0323:
|
||||||
|
return WD::WD1770::read(address);
|
||||||
|
|
||||||
|
case 0x312: return (get_data_request_line() ? 0x80 : 0x00) | (get_interrupt_request_line() ? 0x40 : 0x00);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BD500::access(int address) {
|
||||||
|
// Determine whether to perform a command.
|
||||||
|
switch(address) {
|
||||||
|
case 0x0320: case 0x0321: case 0x0322: case 0x0323: case 0x0312:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 0x310: enable_overlay_ram_ = true; break;
|
||||||
|
case 0x313: enable_overlay_ram_ = false; break;
|
||||||
|
case 0x317: disable_basic_rom_ = false; break; // Could be 0x311.
|
||||||
|
|
||||||
|
default:
|
||||||
|
// printf("Switch %04x???\n", address);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
select_paged_item();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The following was used when trying to find appropriate soft switch locations. It is preserved
|
||||||
|
as the values I have above are unlikely to be wholly correct and further research might be
|
||||||
|
desirable.
|
||||||
|
|
||||||
|
void BD500::access(int address) {
|
||||||
|
// 0,1,4,5,10,11 -> 64kb Atmos
|
||||||
|
// 2,3,9 -> 56kb Atmos.
|
||||||
|
// Broken: 6, 7, 8
|
||||||
|
|
||||||
|
int order = 5;
|
||||||
|
int commands[4];
|
||||||
|
std::vector<int> available_commands = {0, 1, 2, 3};
|
||||||
|
const int modulos[] = {6, 2, 1, 1};
|
||||||
|
|
||||||
|
for(int c = 0; c < 4; ++c) {
|
||||||
|
const int index = order / modulos[c];
|
||||||
|
commands[c] = available_commands[size_t(index)];
|
||||||
|
available_commands.erase(available_commands.begin() + index);
|
||||||
|
order %= modulos[c];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Determine whether to perform a command.
|
||||||
|
int index = -1;
|
||||||
|
switch(address) {
|
||||||
|
case 0x0320: case 0x0321: case 0x0322: case 0x0323: case 0x0312:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 0x310: index = 0; break;
|
||||||
|
case 0x313: index = 1; break;
|
||||||
|
case 0x314: index = 2; break;
|
||||||
|
case 0x317: index = 3; break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
printf("Switch %04x???\n", address);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
select_paged_item();
|
||||||
|
|
||||||
|
if(index >= 0) {
|
||||||
|
switch(commands[index]) {
|
||||||
|
case 0: enable_overlay_ram_ = true; break; // +RAM
|
||||||
|
case 1: disable_basic_rom_ = false; break; // -rom
|
||||||
|
case 2: disable_basic_rom_ = true; break; // +rom
|
||||||
|
case 3: enable_overlay_ram_ = false; break; // -RAM
|
||||||
|
|
||||||
|
}
|
||||||
|
select_paged_item();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
void BD500::set_head_load_request(bool head_load) {
|
||||||
|
// Turn all motors on or off; if off then unload the head instantly.
|
||||||
|
is_loading_head_ |= head_load;
|
||||||
|
for(auto &drive : drives_) {
|
||||||
|
if(drive) drive->set_motor_on(head_load);
|
||||||
|
}
|
||||||
|
if(!head_load) set_head_loaded(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BD500::run_for(const Cycles cycles) {
|
||||||
|
// If a head load is in progress and the selected drive is now ready,
|
||||||
|
// declare head loaded.
|
||||||
|
if(is_loading_head_ && drives_[selected_drive_] && drives_[selected_drive_]->get_is_ready()) {
|
||||||
|
set_head_loaded(true);
|
||||||
|
is_loading_head_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WD::WD1770::run_for(cycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BD500::set_activity_observer(Activity::Observer *observer) {
|
||||||
|
observer_ = observer;
|
||||||
|
if(observer) {
|
||||||
|
observer->register_led("BD-500");
|
||||||
|
observer_->set_led_status("BD-500", get_head_loaded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BD500::set_head_loaded(bool loaded) {
|
||||||
|
WD::WD1770::set_head_loaded(loaded);
|
||||||
|
if(observer_) {
|
||||||
|
observer_->set_led_status("BD-500", loaded);
|
||||||
|
}
|
||||||
|
}
|
43
Machines/Oric/BD500.hpp
Normal file
43
Machines/Oric/BD500.hpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// BD500.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 14/01/2020.
|
||||||
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BD500_hpp
|
||||||
|
#define BD500_hpp
|
||||||
|
|
||||||
|
#include "../../Components/1770/1770.hpp"
|
||||||
|
#include "../../Activity/Observer.hpp"
|
||||||
|
#include "DiskController.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Oric {
|
||||||
|
|
||||||
|
class BD500: public DiskController {
|
||||||
|
public:
|
||||||
|
BD500();
|
||||||
|
|
||||||
|
void write(int address, uint8_t value);
|
||||||
|
uint8_t read(int address);
|
||||||
|
|
||||||
|
void run_for(const Cycles cycles);
|
||||||
|
|
||||||
|
void set_activity_observer(Activity::Observer *observer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void set_head_load_request(bool head_load) final;
|
||||||
|
bool is_loading_head_ = false;
|
||||||
|
Activity::Observer *observer_ = nullptr;
|
||||||
|
|
||||||
|
void access(int address);
|
||||||
|
void set_head_loaded(bool loaded);
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* BD500_hpp */
|
85
Machines/Oric/DiskController.hpp
Normal file
85
Machines/Oric/DiskController.hpp
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// DiskController.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 14/01/2020.
|
||||||
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef DiskController_h
|
||||||
|
#define DiskController_h
|
||||||
|
|
||||||
|
namespace Oric {
|
||||||
|
|
||||||
|
class DiskController: public WD::WD1770 {
|
||||||
|
public:
|
||||||
|
DiskController(WD::WD1770::Personality personality, int clock_rate, Storage::Disk::Drive::ReadyType ready_type) :
|
||||||
|
WD::WD1770(personality), clock_rate_(clock_rate), ready_type_(ready_type) {}
|
||||||
|
|
||||||
|
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
|
||||||
|
const size_t drive = size_t(d);
|
||||||
|
if(!drives_[drive]) {
|
||||||
|
drives_[drive] = std::make_unique<Storage::Disk::Drive>(clock_rate_, 300, 2, ready_type_);
|
||||||
|
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||||
|
}
|
||||||
|
drives_[drive]->set_disk(disk);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class PagedItem {
|
||||||
|
DiskROM,
|
||||||
|
BASIC,
|
||||||
|
RAM
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Delegate: public WD1770::Delegate {
|
||||||
|
virtual void disk_controller_did_change_paged_item(DiskController *controller) = 0;
|
||||||
|
};
|
||||||
|
inline void set_delegate(Delegate *delegate) {
|
||||||
|
delegate_ = delegate;
|
||||||
|
WD1770::set_delegate(delegate);
|
||||||
|
if(delegate) delegate->disk_controller_did_change_paged_item(this);
|
||||||
|
}
|
||||||
|
inline PagedItem get_paged_item() {
|
||||||
|
return paged_item_;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
|
||||||
|
size_t selected_drive_ = 0;
|
||||||
|
void select_drive(size_t drive) {
|
||||||
|
if(drive != selected_drive_) {
|
||||||
|
selected_drive_ = drive;
|
||||||
|
set_drive(drives_[selected_drive_]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Delegate *delegate_ = nullptr;
|
||||||
|
|
||||||
|
bool enable_overlay_ram_ = false;
|
||||||
|
bool disable_basic_rom_ = false;
|
||||||
|
void select_paged_item() {
|
||||||
|
PagedItem item = PagedItem::RAM;
|
||||||
|
if(!enable_overlay_ram_) {
|
||||||
|
item = disable_basic_rom_ ? PagedItem::DiskROM : PagedItem::BASIC;
|
||||||
|
}
|
||||||
|
set_paged_item(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
PagedItem paged_item_ = PagedItem::DiskROM;
|
||||||
|
int clock_rate_;
|
||||||
|
Storage::Disk::Drive::ReadyType ready_type_;
|
||||||
|
|
||||||
|
inline void set_paged_item(PagedItem item) {
|
||||||
|
if(paged_item_ == item) return;
|
||||||
|
paged_item_ = item;
|
||||||
|
if(delegate_) {
|
||||||
|
delegate_->disk_controller_did_change_paged_item(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* DiskController_h */
|
@@ -12,17 +12,9 @@ using namespace Oric;
|
|||||||
|
|
||||||
// NB: there's some controversy here on WD1770 versus WD1772, but between those two I think
|
// NB: there's some controversy here on WD1770 versus WD1772, but between those two I think
|
||||||
// the only difference is stepping rates, and it says 1770 on the schematic I'm looking at.
|
// the only difference is stepping rates, and it says 1770 on the schematic I'm looking at.
|
||||||
Jasmin::Jasmin() : WD1770(P1770) {
|
Jasmin::Jasmin() : DiskController(P1770, 8000000, Storage::Disk::Drive::ReadyType::ShugartRDY) {
|
||||||
set_is_double_density(true);
|
set_is_double_density(true);
|
||||||
}
|
select_paged_item();
|
||||||
|
|
||||||
void Jasmin::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
|
|
||||||
const size_t drive = size_t(d);
|
|
||||||
if(!drives_[drive]) {
|
|
||||||
drives_[drive] = std::make_unique<Storage::Disk::Drive>(8000000, 300, 2);
|
|
||||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
|
||||||
}
|
|
||||||
drives_[drive]->set_disk(disk);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Jasmin::write(int address, uint8_t value) {
|
void Jasmin::write(int address, uint8_t value) {
|
||||||
@@ -40,23 +32,20 @@ void Jasmin::write(int address, uint8_t value) {
|
|||||||
|
|
||||||
case 0x3fa: {
|
case 0x3fa: {
|
||||||
// If b0, enable overlay RAM.
|
// If b0, enable overlay RAM.
|
||||||
posit_paging_flags((paging_flags_ & BASICDisable) | ((value & 1) ? OverlayRAMEnable : 0));
|
enable_overlay_ram_ = value & 1;
|
||||||
|
select_paged_item();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case 0x3fb:
|
case 0x3fb:
|
||||||
// If b0, disable BASIC ROM.
|
// If b0, disable BASIC ROM.
|
||||||
posit_paging_flags((paging_flags_ & OverlayRAMEnable) | ((value & 1) ? BASICDisable : 0));
|
disable_basic_rom_ = value & 1;
|
||||||
|
select_paged_item();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x3fc: case 0x3fd: case 0x3fe: case 0x3ff: {
|
case 0x3fc: case 0x3fd: case 0x3fe: case 0x3ff: {
|
||||||
const size_t new_selected_drive = size_t(address - 0x3fc);
|
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(false);
|
||||||
|
select_drive(size_t(address - 0x3fc));
|
||||||
if(new_selected_drive != selected_drive_) {
|
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
|
||||||
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(false);
|
|
||||||
selected_drive_ = new_selected_drive;
|
|
||||||
set_drive(drives_[selected_drive_]);
|
|
||||||
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -67,4 +56,15 @@ void Jasmin::write(int address, uint8_t value) {
|
|||||||
void Jasmin::set_motor_on(bool on) {
|
void Jasmin::set_motor_on(bool on) {
|
||||||
motor_on_ = on;
|
motor_on_ = on;
|
||||||
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
|
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
|
||||||
|
if(observer_) {
|
||||||
|
observer_->set_led_status("Microdisc", on);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Jasmin::set_activity_observer(Activity::Observer *observer) {
|
||||||
|
observer_ = observer;
|
||||||
|
if(observer) {
|
||||||
|
observer->register_led("Jasmin");
|
||||||
|
observer_->set_led_status("Jasmin", motor_on_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,48 +11,23 @@
|
|||||||
|
|
||||||
#include "../../Components/1770/1770.hpp"
|
#include "../../Components/1770/1770.hpp"
|
||||||
#include "../../Activity/Observer.hpp"
|
#include "../../Activity/Observer.hpp"
|
||||||
|
#include "DiskController.hpp"
|
||||||
#include <array>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace Oric {
|
namespace Oric {
|
||||||
|
|
||||||
class Jasmin: public WD::WD1770 {
|
class Jasmin: public DiskController {
|
||||||
public:
|
public:
|
||||||
Jasmin();
|
Jasmin();
|
||||||
|
|
||||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
|
||||||
void write(int address, uint8_t value);
|
void write(int address, uint8_t value);
|
||||||
|
|
||||||
enum PagingFlags {
|
void set_activity_observer(Activity::Observer *observer);
|
||||||
/// Indicates that overlay RAM is enabled, implying no ROM is visible.
|
|
||||||
OverlayRAMEnable = (1 << 0),
|
|
||||||
|
|
||||||
/// Indicates that the BASIC ROM is disabled, implying that the JASMIN ROM
|
|
||||||
/// fills its space.
|
|
||||||
BASICDisable = (1 << 1)
|
|
||||||
};
|
|
||||||
struct Delegate: public WD1770::Delegate {
|
|
||||||
virtual void jasmin_did_change_paging_flags(Jasmin *jasmin) = 0;
|
|
||||||
};
|
|
||||||
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; WD1770::set_delegate(delegate); }
|
|
||||||
inline int get_paging_flags() { return paging_flags_; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
|
|
||||||
size_t selected_drive_;
|
|
||||||
int paging_flags_ = 0;
|
|
||||||
Delegate *delegate_ = nullptr;
|
|
||||||
|
|
||||||
void posit_paging_flags(int new_flags) {
|
|
||||||
if(new_flags != paging_flags_) {
|
|
||||||
paging_flags_ = new_flags;
|
|
||||||
if(delegate_) delegate_->jasmin_did_change_paging_flags(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_motor_on(bool on) final;
|
void set_motor_on(bool on) final;
|
||||||
bool motor_on_ = false;
|
bool motor_on_ = false;
|
||||||
|
|
||||||
|
Activity::Observer *observer_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@@ -18,20 +18,10 @@ namespace {
|
|||||||
const Cycles::IntType head_load_request_counter_target = 7653333;
|
const Cycles::IntType head_load_request_counter_target = 7653333;
|
||||||
}
|
}
|
||||||
|
|
||||||
Microdisc::Microdisc() : WD1770(P1793) {
|
Microdisc::Microdisc() : DiskController(P1793, 8000000, Storage::Disk::Drive::ReadyType::ShugartRDY) {
|
||||||
set_control_register(last_control_, 0xff);
|
set_control_register(last_control_, 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int d) {
|
|
||||||
const size_t drive = size_t(d);
|
|
||||||
if(!drives_[drive]) {
|
|
||||||
drives_[drive] = std::make_unique<Storage::Disk::Drive>(8000000, 300, 2);
|
|
||||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
|
||||||
drives_[drive]->set_activity_observer(observer_, drive_name(drive), false);
|
|
||||||
}
|
|
||||||
drives_[drive]->set_disk(disk);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Microdisc::set_control_register(uint8_t control) {
|
void Microdisc::set_control_register(uint8_t control) {
|
||||||
const uint8_t changes = last_control_ ^ control;
|
const uint8_t changes = last_control_ ^ control;
|
||||||
last_control_ = control;
|
last_control_ = control;
|
||||||
@@ -73,8 +63,9 @@ void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
|
|||||||
// b7: EPROM select (0 = select)
|
// b7: EPROM select (0 = select)
|
||||||
// b1: ROM disable (0 = disable)
|
// b1: ROM disable (0 = disable)
|
||||||
if(changes & 0x82) {
|
if(changes & 0x82) {
|
||||||
paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodiscDisable : 0);
|
enable_overlay_ram_ = control & 0x80;
|
||||||
if(delegate_) delegate_->microdisc_did_change_paging_flags(this);
|
disable_basic_rom_ = !(control & 0x02);
|
||||||
|
select_paged_item();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,23 +112,10 @@ void Microdisc::run_for(const Cycles cycles) {
|
|||||||
WD::WD1770::run_for(cycles);
|
WD::WD1770::run_for(cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Microdisc::get_drive_is_ready() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Microdisc::set_activity_observer(Activity::Observer *observer) {
|
void Microdisc::set_activity_observer(Activity::Observer *observer) {
|
||||||
observer_ = observer;
|
observer_ = observer;
|
||||||
if(observer) {
|
if(observer) {
|
||||||
observer->register_led("Microdisc");
|
observer->register_led("Microdisc");
|
||||||
observer_->set_led_status("Microdisc", head_load_request_);
|
observer_->set_led_status("Microdisc", head_load_request_);
|
||||||
}
|
}
|
||||||
size_t c = 0;
|
|
||||||
for(auto &drive : drives_) {
|
|
||||||
if(drive) drive->set_activity_observer(observer, drive_name(c), false);
|
|
||||||
++c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Microdisc::drive_name(size_t index) {
|
|
||||||
return "Drive " + std::to_string(index);
|
|
||||||
}
|
}
|
||||||
|
@@ -11,16 +11,14 @@
|
|||||||
|
|
||||||
#include "../../Components/1770/1770.hpp"
|
#include "../../Components/1770/1770.hpp"
|
||||||
#include "../../Activity/Observer.hpp"
|
#include "../../Activity/Observer.hpp"
|
||||||
|
#include "DiskController.hpp"
|
||||||
#include <array>
|
|
||||||
|
|
||||||
namespace Oric {
|
namespace Oric {
|
||||||
|
|
||||||
class Microdisc: public WD::WD1770 {
|
class Microdisc: public DiskController {
|
||||||
public:
|
public:
|
||||||
Microdisc();
|
Microdisc();
|
||||||
|
|
||||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
|
||||||
void set_control_register(uint8_t control);
|
void set_control_register(uint8_t control);
|
||||||
uint8_t get_interrupt_request_register();
|
uint8_t get_interrupt_request_register();
|
||||||
uint8_t get_data_request_register();
|
uint8_t get_data_request_register();
|
||||||
@@ -29,42 +27,19 @@ class Microdisc: public WD::WD1770 {
|
|||||||
|
|
||||||
void run_for(const Cycles cycles);
|
void run_for(const Cycles cycles);
|
||||||
|
|
||||||
enum PagingFlags {
|
|
||||||
/// Indicates that the BASIC ROM should be disabled; if this is set then either
|
|
||||||
/// the Microdisc ROM or overlay RAM will be visible. If it is not set, BASIC
|
|
||||||
/// should be visible.
|
|
||||||
BASICDisable = (1 << 0),
|
|
||||||
|
|
||||||
/// Indicates that the Microdisc ROM is disabled. If BASIC is disabled and the Microdisc
|
|
||||||
/// is also disabled, overlay RAM should be visible.
|
|
||||||
MicrodiscDisable = (1 << 1)
|
|
||||||
};
|
|
||||||
|
|
||||||
class Delegate: public WD1770::Delegate {
|
|
||||||
public:
|
|
||||||
virtual void microdisc_did_change_paging_flags(Microdisc *microdisc) = 0;
|
|
||||||
};
|
|
||||||
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; WD1770::set_delegate(delegate); }
|
|
||||||
inline int get_paging_flags() { return paging_flags_; }
|
|
||||||
|
|
||||||
void set_activity_observer(Activity::Observer *observer);
|
void set_activity_observer(Activity::Observer *observer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void set_control_register(uint8_t control, uint8_t changes);
|
void set_head_load_request(bool head_load) final;
|
||||||
void set_head_load_request(bool head_load) override;
|
|
||||||
bool get_drive_is_ready();
|
|
||||||
|
|
||||||
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
|
void set_control_register(uint8_t control, uint8_t changes);
|
||||||
size_t selected_drive_;
|
uint8_t last_control_ = 0;
|
||||||
bool irq_enable_ = false;
|
bool irq_enable_ = false;
|
||||||
int paging_flags_ = BASICDisable;
|
|
||||||
Cycles::IntType head_load_request_counter_ = -1;
|
Cycles::IntType head_load_request_counter_ = -1;
|
||||||
bool head_load_request_ = false;
|
bool head_load_request_ = false;
|
||||||
Delegate *delegate_ = nullptr;
|
|
||||||
uint8_t last_control_ = 0;
|
|
||||||
Activity::Observer *observer_ = nullptr;
|
|
||||||
|
|
||||||
std::string drive_name(size_t index);
|
Activity::Observer *observer_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "Oric.hpp"
|
#include "Oric.hpp"
|
||||||
|
|
||||||
|
#include "BD500.hpp"
|
||||||
#include "Jasmin.hpp"
|
#include "Jasmin.hpp"
|
||||||
#include "Keyboard.hpp"
|
#include "Keyboard.hpp"
|
||||||
#include "Microdisc.hpp"
|
#include "Microdisc.hpp"
|
||||||
@@ -223,8 +224,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||||
public Utility::TypeRecipient,
|
public Utility::TypeRecipient,
|
||||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||||
public Microdisc::Delegate,
|
public DiskController::Delegate,
|
||||||
public Jasmin::Delegate,
|
|
||||||
public ClockingHint::Observer,
|
public ClockingHint::Observer,
|
||||||
public Activity::Source,
|
public Activity::Source,
|
||||||
public Machine,
|
public Machine,
|
||||||
@@ -244,7 +244,16 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
speaker_.set_input_rate(1000000.0f);
|
speaker_.set_input_rate(1000000.0f);
|
||||||
via_port_handler_.set_interrupt_delegate(this);
|
via_port_handler_.set_interrupt_delegate(this);
|
||||||
tape_player_.set_delegate(this);
|
tape_player_.set_delegate(this);
|
||||||
|
|
||||||
|
// Slight hack here: I'm unclear what RAM should look like at startup.
|
||||||
|
// Actually, I think completely random might be right since the Microdisc
|
||||||
|
// sort of assumes it, but also the BD-500 never explicitly sets PAL mode
|
||||||
|
// so I can't have any switch-to-NTSC bytes in the display area. Hence:
|
||||||
|
// disallow all atributes.
|
||||||
Memory::Fuzz(ram_, sizeof(ram_));
|
Memory::Fuzz(ram_, sizeof(ram_));
|
||||||
|
for(size_t c = 0; c < sizeof(ram_); ++c) {
|
||||||
|
ram_[c] |= 0x40;
|
||||||
|
}
|
||||||
|
|
||||||
if constexpr (disk_interface == DiskInterface::Pravetz) {
|
if constexpr (disk_interface == DiskInterface::Pravetz) {
|
||||||
diskii_.set_clocking_hint_observer(this);
|
diskii_.set_clocking_hint_observer(this);
|
||||||
@@ -266,6 +275,9 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
size_t diskii_state_machine_index = 0;
|
size_t diskii_state_machine_index = 0;
|
||||||
switch(disk_interface) {
|
switch(disk_interface) {
|
||||||
default: break;
|
default: break;
|
||||||
|
case DiskInterface::BD500:
|
||||||
|
rom_names.emplace_back(machine_name, "the ORIC Byte Drive 500 ROM", "bd500.rom", 8*1024, 0x61952e34);
|
||||||
|
break;
|
||||||
case DiskInterface::Jasmin:
|
case DiskInterface::Jasmin:
|
||||||
rom_names.emplace_back(machine_name, "the ORIC Jasmin ROM", "jasmin.rom", 2*1024, 0x37220e89);
|
rom_names.emplace_back(machine_name, "the ORIC Jasmin ROM", "jasmin.rom", 2*1024, 0x37220e89);
|
||||||
break;
|
break;
|
||||||
@@ -293,13 +305,17 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
|
|
||||||
switch(disk_interface) {
|
switch(disk_interface) {
|
||||||
default: break;
|
default: break;
|
||||||
|
case DiskInterface::BD500:
|
||||||
|
disk_rom_ = std::move(*roms[2]);
|
||||||
|
disk_rom_.resize(8192);
|
||||||
|
break;
|
||||||
case DiskInterface::Jasmin:
|
case DiskInterface::Jasmin:
|
||||||
jasmin_rom_ = std::move(*roms[2]);
|
disk_rom_ = std::move(*roms[2]);
|
||||||
jasmin_rom_.resize(2048);
|
disk_rom_.resize(2048);
|
||||||
break;
|
break;
|
||||||
case DiskInterface::Microdisc:
|
case DiskInterface::Microdisc:
|
||||||
microdisc_rom_ = std::move(*roms[2]);
|
disk_rom_ = std::move(*roms[2]);
|
||||||
microdisc_rom_.resize(8192);
|
disk_rom_.resize(8192);
|
||||||
break;
|
break;
|
||||||
case DiskInterface::Pravetz: {
|
case DiskInterface::Pravetz: {
|
||||||
pravetz_rom_ = std::move(*roms[2]);
|
pravetz_rom_ = std::move(*roms[2]);
|
||||||
@@ -314,14 +330,15 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
|
|
||||||
switch(target.disk_interface) {
|
switch(target.disk_interface) {
|
||||||
default: break;
|
default: break;
|
||||||
case DiskInterface::Microdisc:
|
case DiskInterface::BD500:
|
||||||
microdisc_did_change_paging_flags(µdisc_);
|
bd500_.set_delegate(this);
|
||||||
microdisc_.set_delegate(this);
|
|
||||||
break;
|
break;
|
||||||
case DiskInterface::Jasmin:
|
case DiskInterface::Jasmin:
|
||||||
jasmin_did_change_paging_flags(&jasmin_);
|
|
||||||
jasmin_.set_delegate(this);
|
jasmin_.set_delegate(this);
|
||||||
break;
|
break;
|
||||||
|
case DiskInterface::Microdisc:
|
||||||
|
microdisc_.set_delegate(this);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!target.loading_command.empty()) {
|
if(!target.loading_command.empty()) {
|
||||||
@@ -353,7 +370,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
audio_queue_.flush();
|
audio_queue_.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_key_state(uint16_t key, bool is_pressed) override final {
|
void set_key_state(uint16_t key, bool is_pressed) final {
|
||||||
if(key == KeyNMI) {
|
if(key == KeyNMI) {
|
||||||
m6502_.set_nmi_line(is_pressed);
|
m6502_.set_nmi_line(is_pressed);
|
||||||
} else {
|
} else {
|
||||||
@@ -361,7 +378,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear_all_keys() override final {
|
void clear_all_keys() final {
|
||||||
keyboard_.clear_all_keys();
|
keyboard_.clear_all_keys();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,7 +397,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
bool insert_media(const Analyser::Static::Media &media) final {
|
||||||
bool inserted = false;
|
bool inserted = false;
|
||||||
|
|
||||||
if(!media.tapes.empty()) {
|
if(!media.tapes.empty()) {
|
||||||
@@ -390,16 +407,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
|
|
||||||
if(!media.disks.empty()) {
|
if(!media.disks.empty()) {
|
||||||
switch(disk_interface) {
|
switch(disk_interface) {
|
||||||
case DiskInterface::Jasmin:
|
case DiskInterface::BD500: inserted |= insert_disks(media, bd500_, 4); break;
|
||||||
inserted |= insert_disks(media, jasmin_, 4);
|
case DiskInterface::Jasmin: inserted |= insert_disks(media, jasmin_, 4); break;
|
||||||
break;
|
case DiskInterface::Microdisc: inserted |= insert_disks(media, microdisc_, 4); break;
|
||||||
case DiskInterface::Microdisc: {
|
case DiskInterface::Pravetz: inserted |= insert_disks(media, diskii_, 2); break;
|
||||||
inserted |= insert_disks(media, microdisc_, 4);
|
|
||||||
} break;
|
|
||||||
case DiskInterface::Pravetz: {
|
|
||||||
inserted |= insert_disks(media, diskii_, 2);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,6 +445,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
} else {
|
} else {
|
||||||
switch(disk_interface) {
|
switch(disk_interface) {
|
||||||
default: break;
|
default: break;
|
||||||
|
case DiskInterface::BD500:
|
||||||
|
if(isReadOperation(operation)) *value = bd500_.read(address);
|
||||||
|
else bd500_.write(address, *value);
|
||||||
|
break;
|
||||||
case DiskInterface::Jasmin:
|
case DiskInterface::Jasmin:
|
||||||
if(address >= 0x3f4) {
|
if(address >= 0x3f4) {
|
||||||
if(isReadOperation(operation)) *value = jasmin_.read(address);
|
if(isReadOperation(operation)) *value = jasmin_.read(address);
|
||||||
@@ -497,8 +512,11 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
tape_player_.run_for(Cycles(1));
|
tape_player_.run_for(Cycles(1));
|
||||||
switch(disk_interface) {
|
switch(disk_interface) {
|
||||||
default: break;
|
default: break;
|
||||||
|
case DiskInterface::BD500:
|
||||||
|
bd500_.run_for(Cycles(9)); // i.e. effective clock rate of 9Mhz.
|
||||||
|
break;
|
||||||
case DiskInterface::Jasmin:
|
case DiskInterface::Jasmin:
|
||||||
jasmin_.run_for(Cycles(8));
|
jasmin_.run_for(Cycles(8));; // i.e. effective clock rate of 8Mhz.
|
||||||
|
|
||||||
// Jasmin autostart hack: wait for a period, then trigger a reset, having forced
|
// Jasmin autostart hack: wait for a period, then trigger a reset, having forced
|
||||||
// the Jasmin to page its ROM in first. I assume the latter being what the Jasmin's
|
// the Jasmin to page its ROM in first. I assume the latter being what the Jasmin's
|
||||||
@@ -511,12 +529,12 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DiskInterface::Microdisc:
|
case DiskInterface::Microdisc:
|
||||||
microdisc_.run_for(Cycles(8));
|
microdisc_.run_for(Cycles(8));; // i.e. effective clock rate of 8Mhz.
|
||||||
break;
|
break;
|
||||||
case DiskInterface::Pravetz:
|
case DiskInterface::Pravetz:
|
||||||
if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
|
if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
|
||||||
diskii_.set_data_input(*value);
|
diskii_.set_data_input(*value);
|
||||||
diskii_.run_for(Cycles(2));
|
diskii_.run_for(Cycles(2));; // i.e. effective clock rate of 2Mhz.
|
||||||
} else {
|
} else {
|
||||||
cycles_since_diskii_update_ += Cycles(2);
|
cycles_since_diskii_update_ += Cycles(2);
|
||||||
}
|
}
|
||||||
@@ -534,74 +552,53 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// to satisfy CRTMachine::Machine
|
// to satisfy CRTMachine::Machine
|
||||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||||
video_output_.set_scan_target(scan_target);
|
video_output_.set_scan_target(scan_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||||
video_output_.set_display_type(display_type);
|
video_output_.set_display_type(display_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
Outputs::Speaker::Speaker *get_speaker() override final {
|
Outputs::Speaker::Speaker *get_speaker() final {
|
||||||
return &speaker_;
|
return &speaker_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void run_for(const Cycles cycles) override final {
|
void run_for(const Cycles cycles) final {
|
||||||
m6502_.run_for(cycles);
|
m6502_.run_for(cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// to satisfy MOS::MOS6522IRQDelegate::Delegate
|
// to satisfy MOS::MOS6522IRQDelegate::Delegate
|
||||||
void mos6522_did_change_interrupt_status(void *mos6522) override final {
|
void mos6522_did_change_interrupt_status(void *mos6522) final {
|
||||||
set_interrupt_line();
|
set_interrupt_line();
|
||||||
}
|
}
|
||||||
|
|
||||||
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
|
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
|
||||||
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) override final {
|
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) final {
|
||||||
// set CB1
|
// set CB1
|
||||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, !tape_player->get_input());
|
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, !tape_player->get_input());
|
||||||
}
|
}
|
||||||
|
|
||||||
// for Utility::TypeRecipient::Delegate
|
// for Utility::TypeRecipient::Delegate
|
||||||
void type_string(const std::string &string) override final {
|
void type_string(const std::string &string) final {
|
||||||
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
|
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for Microdisc::Delegate
|
// DiskController::Delegate
|
||||||
void microdisc_did_change_paging_flags(class Microdisc *microdisc) override final {
|
void disk_controller_did_change_paged_item(DiskController *controller) final {
|
||||||
const int flags = microdisc->get_paging_flags();
|
switch(controller->get_paged_item()) {
|
||||||
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
|
|
||||||
ram_top_ = basic_visible_ram_top_;
|
|
||||||
paged_rom_ = rom_.data();
|
|
||||||
} else {
|
|
||||||
if(flags&Microdisc::PagingFlags::MicrodiscDisable) {
|
|
||||||
ram_top_ = basic_invisible_ram_top_;
|
|
||||||
} else {
|
|
||||||
ram_top_ = 0xdfff;
|
|
||||||
paged_rom_ = microdisc_rom_.data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jasmin::Delegate
|
|
||||||
void jasmin_did_change_paging_flags(Jasmin *jasmin) override final {
|
|
||||||
const int flags = jasmin->get_paging_flags();
|
|
||||||
switch(flags) {
|
|
||||||
// BASIC enabled, overlay disabled.
|
|
||||||
default:
|
default:
|
||||||
ram_top_ = basic_visible_ram_top_;
|
ram_top_ = basic_visible_ram_top_;
|
||||||
paged_rom_ = rom_.data();
|
paged_rom_ = rom_.data();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Overlay RAM enabled, with or without BASIC.
|
case DiskController::PagedItem::RAM:
|
||||||
case Jasmin::OverlayRAMEnable:
|
|
||||||
case Jasmin::OverlayRAMEnable | Jasmin::BASICDisable:
|
|
||||||
ram_top_ = basic_invisible_ram_top_;
|
ram_top_ = basic_invisible_ram_top_;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// BASIC disabled, overlay disabled.
|
case DiskController::PagedItem::DiskROM:
|
||||||
case Jasmin::BASICDisable:
|
ram_top_ = uint16_t(0xffff - disk_rom_.size());
|
||||||
ram_top_ = 0xf7ff;
|
paged_rom_ = disk_rom_.data();
|
||||||
paged_rom_ = jasmin_rom_.data();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -649,6 +646,12 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
void set_activity_observer(Activity::Observer *observer) override {
|
void set_activity_observer(Activity::Observer *observer) override {
|
||||||
switch(disk_interface) {
|
switch(disk_interface) {
|
||||||
default: break;
|
default: break;
|
||||||
|
case DiskInterface::BD500:
|
||||||
|
bd500_.set_activity_observer(observer);
|
||||||
|
break;
|
||||||
|
case DiskInterface::Jasmin:
|
||||||
|
jasmin_.set_activity_observer(observer);
|
||||||
|
break;
|
||||||
case DiskInterface::Microdisc:
|
case DiskInterface::Microdisc:
|
||||||
microdisc_.set_activity_observer(observer);
|
microdisc_.set_activity_observer(observer);
|
||||||
break;
|
break;
|
||||||
@@ -669,7 +672,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||||
|
|
||||||
// RAM and ROM
|
// RAM and ROM
|
||||||
std::vector<uint8_t> rom_, microdisc_rom_, jasmin_rom_;
|
std::vector<uint8_t> rom_, disk_rom_;
|
||||||
uint8_t ram_[65536];
|
uint8_t ram_[65536];
|
||||||
Cycles cycles_since_video_update_;
|
Cycles cycles_since_video_update_;
|
||||||
inline void update_video() {
|
inline void update_video() {
|
||||||
@@ -705,6 +708,9 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
|||||||
Jasmin jasmin_;
|
Jasmin jasmin_;
|
||||||
int jasmin_reset_counter_ = 0;
|
int jasmin_reset_counter_ = 0;
|
||||||
|
|
||||||
|
// the BD-500, if in use.
|
||||||
|
BD500 bd500_;
|
||||||
|
|
||||||
// the Pravetz/Disk II, if in use.
|
// the Pravetz/Disk II, if in use.
|
||||||
Apple::DiskII diskii_;
|
Apple::DiskII diskii_;
|
||||||
Cycles cycles_since_diskii_update_;
|
Cycles cycles_since_diskii_update_;
|
||||||
@@ -771,6 +777,7 @@ Machine *Machine::Oric(const Analyser::Static::Target *target_hint, const ROMMac
|
|||||||
case DiskInterface::Microdisc: return new ConcreteMachine<DiskInterface::Microdisc>(*oric_target, rom_fetcher);
|
case DiskInterface::Microdisc: return new ConcreteMachine<DiskInterface::Microdisc>(*oric_target, rom_fetcher);
|
||||||
case DiskInterface::Pravetz: return new ConcreteMachine<DiskInterface::Pravetz>(*oric_target, rom_fetcher);
|
case DiskInterface::Pravetz: return new ConcreteMachine<DiskInterface::Pravetz>(*oric_target, rom_fetcher);
|
||||||
case DiskInterface::Jasmin: return new ConcreteMachine<DiskInterface::Jasmin>(*oric_target, rom_fetcher);
|
case DiskInterface::Jasmin: return new ConcreteMachine<DiskInterface::Jasmin>(*oric_target, rom_fetcher);
|
||||||
|
case DiskInterface::BD500: return new ConcreteMachine<DiskInterface::BD500>(*oric_target, rom_fetcher);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -39,7 +39,6 @@
|
|||||||
4B055AA11FAE85DA0060FFFF /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; };
|
4B055AA11FAE85DA0060FFFF /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; };
|
||||||
4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; };
|
4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; };
|
||||||
4B055AA31FAE85DF0060FFFF /* ImplicitSectors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */; };
|
4B055AA31FAE85DF0060FFFF /* ImplicitSectors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */; };
|
||||||
4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */; };
|
|
||||||
4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; };
|
4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; };
|
||||||
4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; };
|
4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; };
|
||||||
4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; };
|
4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; };
|
||||||
@@ -189,7 +188,6 @@
|
|||||||
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518771F75E91800926311 /* UnformattedTrack.cpp */; };
|
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518771F75E91800926311 /* UnformattedTrack.cpp */; };
|
||||||
4B4518851F75E91A00926311 /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187A1F75E91900926311 /* DiskController.cpp */; };
|
4B4518851F75E91A00926311 /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187A1F75E91900926311 /* DiskController.cpp */; };
|
||||||
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187C1F75E91900926311 /* MFMDiskController.cpp */; };
|
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187C1F75E91900926311 /* MFMDiskController.cpp */; };
|
||||||
4B4518871F75E91A00926311 /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */; };
|
|
||||||
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45188D1F75FD1B00926311 /* AcornADF.cpp */; };
|
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45188D1F75FD1B00926311 /* AcornADF.cpp */; };
|
||||||
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45188F1F75FD1B00926311 /* CPCDSK.cpp */; };
|
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45188F1F75FD1B00926311 /* CPCDSK.cpp */; };
|
||||||
4B4518A11F75FD1C00926311 /* D64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518911F75FD1B00926311 /* D64.cpp */; };
|
4B4518A11F75FD1C00926311 /* D64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518911F75FD1B00926311 /* D64.cpp */; };
|
||||||
@@ -248,7 +246,6 @@
|
|||||||
4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; };
|
4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; };
|
||||||
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
|
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
|
||||||
4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
|
4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
|
||||||
4B778EF223A5DB100000D260 /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */; };
|
|
||||||
4B778EF323A5DB230000D260 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
|
4B778EF323A5DB230000D260 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
|
||||||
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
|
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
|
||||||
4B778EF523A5DB440000D260 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
|
4B778EF523A5DB440000D260 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
|
||||||
@@ -371,6 +368,8 @@
|
|||||||
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; };
|
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; };
|
||||||
4B7BA03023C2B19C00B98D9E /* Jasmin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */; };
|
4B7BA03023C2B19C00B98D9E /* Jasmin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */; };
|
||||||
4B7BA03123C2B19C00B98D9E /* Jasmin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */; };
|
4B7BA03123C2B19C00B98D9E /* Jasmin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */; };
|
||||||
|
4B7BA03423C58B1F00B98D9E /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; };
|
||||||
|
4B7BA03723CEB86000B98D9E /* BD500.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03523CEB86000B98D9E /* BD500.cpp */; };
|
||||||
4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; };
|
4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; };
|
||||||
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
|
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
|
||||||
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
|
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
|
||||||
@@ -1063,7 +1062,6 @@
|
|||||||
4B45187B1F75E91900926311 /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
|
4B45187B1F75E91900926311 /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
|
||||||
4B45187C1F75E91900926311 /* MFMDiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MFMDiskController.cpp; sourceTree = "<group>"; };
|
4B45187C1F75E91900926311 /* MFMDiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MFMDiskController.cpp; sourceTree = "<group>"; };
|
||||||
4B45187D1F75E91900926311 /* MFMDiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MFMDiskController.hpp; sourceTree = "<group>"; };
|
4B45187D1F75E91900926311 /* MFMDiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MFMDiskController.hpp; sourceTree = "<group>"; };
|
||||||
4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DigitalPhaseLockedLoop.cpp; sourceTree = "<group>"; };
|
|
||||||
4B4518801F75E91900926311 /* DigitalPhaseLockedLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DigitalPhaseLockedLoop.hpp; sourceTree = "<group>"; };
|
4B4518801F75E91900926311 /* DigitalPhaseLockedLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DigitalPhaseLockedLoop.hpp; sourceTree = "<group>"; };
|
||||||
4B4518881F75ECB100926311 /* Track.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Track.hpp; sourceTree = "<group>"; };
|
4B4518881F75ECB100926311 /* Track.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Track.hpp; sourceTree = "<group>"; };
|
||||||
4B45188B1F75FD1B00926311 /* DiskImage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskImage.hpp; sourceTree = "<group>"; };
|
4B45188B1F75FD1B00926311 /* DiskImage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskImage.hpp; sourceTree = "<group>"; };
|
||||||
@@ -1182,6 +1180,11 @@
|
|||||||
4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||||
4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Jasmin.cpp; path = Oric/Jasmin.cpp; sourceTree = "<group>"; };
|
4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Jasmin.cpp; path = Oric/Jasmin.cpp; sourceTree = "<group>"; };
|
||||||
4B7BA02F23C2B19B00B98D9E /* Jasmin.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Jasmin.hpp; path = Oric/Jasmin.hpp; sourceTree = "<group>"; };
|
4B7BA02F23C2B19B00B98D9E /* Jasmin.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Jasmin.hpp; path = Oric/Jasmin.hpp; sourceTree = "<group>"; };
|
||||||
|
4B7BA03223C58B1E00B98D9E /* STX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = STX.hpp; sourceTree = "<group>"; };
|
||||||
|
4B7BA03323C58B1E00B98D9E /* STX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = STX.cpp; sourceTree = "<group>"; };
|
||||||
|
4B7BA03523CEB86000B98D9E /* BD500.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BD500.cpp; path = Oric/BD500.cpp; sourceTree = "<group>"; };
|
||||||
|
4B7BA03623CEB86000B98D9E /* BD500.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BD500.hpp; path = Oric/BD500.hpp; sourceTree = "<group>"; };
|
||||||
|
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DiskController.hpp; path = Oric/DiskController.hpp; sourceTree = "<group>"; };
|
||||||
4B7F188C2154825D00388727 /* MasterSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MasterSystem.cpp; sourceTree = "<group>"; };
|
4B7F188C2154825D00388727 /* MasterSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MasterSystem.cpp; sourceTree = "<group>"; };
|
||||||
4B7F188D2154825D00388727 /* MasterSystem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MasterSystem.hpp; sourceTree = "<group>"; };
|
4B7F188D2154825D00388727 /* MasterSystem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MasterSystem.hpp; sourceTree = "<group>"; };
|
||||||
4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||||
@@ -2235,7 +2238,6 @@
|
|||||||
4B45187E1F75E91900926311 /* DPLL */ = {
|
4B45187E1F75E91900926311 /* DPLL */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */,
|
|
||||||
4B4518801F75E91900926311 /* DigitalPhaseLockedLoop.hpp */,
|
4B4518801F75E91900926311 /* DigitalPhaseLockedLoop.hpp */,
|
||||||
);
|
);
|
||||||
path = DPLL;
|
path = DPLL;
|
||||||
@@ -2269,6 +2271,7 @@
|
|||||||
4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */,
|
4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */,
|
||||||
4B4518991F75FD1B00926311 /* SSD.cpp */,
|
4B4518991F75FD1B00926311 /* SSD.cpp */,
|
||||||
4BE0A3EC237BB170002AB46F /* ST.cpp */,
|
4BE0A3EC237BB170002AB46F /* ST.cpp */,
|
||||||
|
4B7BA03323C58B1E00B98D9E /* STX.cpp */,
|
||||||
4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */,
|
4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */,
|
||||||
4B45188E1F75FD1B00926311 /* AcornADF.hpp */,
|
4B45188E1F75FD1B00926311 /* AcornADF.hpp */,
|
||||||
4B0333AE2094081A0050B93D /* AppleDSK.hpp */,
|
4B0333AE2094081A0050B93D /* AppleDSK.hpp */,
|
||||||
@@ -2285,6 +2288,7 @@
|
|||||||
4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */,
|
4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */,
|
||||||
4B45189A1F75FD1B00926311 /* SSD.hpp */,
|
4B45189A1F75FD1B00926311 /* SSD.hpp */,
|
||||||
4BE0A3ED237BB170002AB46F /* ST.hpp */,
|
4BE0A3ED237BB170002AB46F /* ST.hpp */,
|
||||||
|
4B7BA03223C58B1E00B98D9E /* STX.hpp */,
|
||||||
4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */,
|
4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */,
|
||||||
4BFDD7891F7F2DB4008579B9 /* Utility */,
|
4BFDD7891F7F2DB4008579B9 /* Utility */,
|
||||||
);
|
);
|
||||||
@@ -3533,16 +3537,19 @@
|
|||||||
4BCF1FA51DADC3E10039D2E7 /* Oric */ = {
|
4BCF1FA51DADC3E10039D2E7 /* Oric */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4B7BA03523CEB86000B98D9E /* BD500.cpp */,
|
||||||
4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */,
|
4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */,
|
||||||
4B54C0BD1F8D8F450050900F /* Keyboard.cpp */,
|
4B54C0BD1F8D8F450050900F /* Keyboard.cpp */,
|
||||||
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */,
|
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */,
|
||||||
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */,
|
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */,
|
||||||
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */,
|
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */,
|
||||||
|
4B7BA03623CEB86000B98D9E /* BD500.hpp */,
|
||||||
4B7BA02F23C2B19B00B98D9E /* Jasmin.hpp */,
|
4B7BA02F23C2B19B00B98D9E /* Jasmin.hpp */,
|
||||||
4B54C0BE1F8D8F450050900F /* Keyboard.hpp */,
|
4B54C0BE1F8D8F450050900F /* Keyboard.hpp */,
|
||||||
4B5FADBF1DE3BF2B00AEC565 /* Microdisc.hpp */,
|
4B5FADBF1DE3BF2B00AEC565 /* Microdisc.hpp */,
|
||||||
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */,
|
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */,
|
||||||
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */,
|
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */,
|
||||||
|
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */,
|
||||||
);
|
);
|
||||||
name = Oric;
|
name = Oric;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -3838,6 +3845,7 @@
|
|||||||
};
|
};
|
||||||
4BB73E9D1B587A5100552FC2 = {
|
4BB73E9D1B587A5100552FC2 = {
|
||||||
CreatedOnToolsVersion = 7.0;
|
CreatedOnToolsVersion = 7.0;
|
||||||
|
DevelopmentTeam = CP2SKEB3XT;
|
||||||
LastSwiftMigration = 1020;
|
LastSwiftMigration = 1020;
|
||||||
SystemCapabilities = {
|
SystemCapabilities = {
|
||||||
com.apple.Sandbox = {
|
com.apple.Sandbox = {
|
||||||
@@ -4214,7 +4222,6 @@
|
|||||||
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
|
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
|
||||||
4B0ACC3123775819008902D0 /* TIASound.cpp in Sources */,
|
4B0ACC3123775819008902D0 /* TIASound.cpp in Sources */,
|
||||||
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
|
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
|
||||||
4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */,
|
|
||||||
4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */,
|
4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */,
|
||||||
4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
|
4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
|
||||||
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */,
|
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */,
|
||||||
@@ -4456,6 +4463,7 @@
|
|||||||
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||||
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
|
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
|
||||||
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
|
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
|
||||||
|
4B7BA03723CEB86000B98D9E /* BD500.cpp in Sources */,
|
||||||
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
|
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
|
||||||
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
|
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
|
||||||
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,
|
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,
|
||||||
@@ -4541,8 +4549,8 @@
|
|||||||
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
||||||
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
||||||
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
||||||
|
4B7BA03423C58B1F00B98D9E /* STX.cpp in Sources */,
|
||||||
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */,
|
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */,
|
||||||
4B4518871F75E91A00926311 /* DigitalPhaseLockedLoop.cpp in Sources */,
|
|
||||||
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */,
|
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */,
|
||||||
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */,
|
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */,
|
||||||
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */,
|
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */,
|
||||||
@@ -4642,7 +4650,6 @@
|
|||||||
4B778F0E23A5EC4F0000D260 /* Tape.cpp in Sources */,
|
4B778F0E23A5EC4F0000D260 /* Tape.cpp in Sources */,
|
||||||
4B778F2D23A5EF190000D260 /* MFMDiskController.cpp in Sources */,
|
4B778F2D23A5EF190000D260 /* MFMDiskController.cpp in Sources */,
|
||||||
4B778F2723A5EEF60000D260 /* BinaryDump.cpp in Sources */,
|
4B778F2723A5EEF60000D260 /* BinaryDump.cpp in Sources */,
|
||||||
4B778EF223A5DB100000D260 /* DigitalPhaseLockedLoop.cpp in Sources */,
|
|
||||||
4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */,
|
4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */,
|
||||||
4B778F5223A5F22F0000D260 /* StaticAnalyser.cpp in Sources */,
|
4B778F5223A5F22F0000D260 /* StaticAnalyser.cpp in Sources */,
|
||||||
4B778F4923A5F1F40000D260 /* StaticAnalyser.cpp in Sources */,
|
4B778F4923A5F1F40000D260 /* StaticAnalyser.cpp in Sources */,
|
||||||
@@ -5049,6 +5056,8 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "-";
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
DEVELOPMENT_TEAM = CP2SKEB3XT;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||||
@@ -5091,6 +5100,8 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "-";
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
DEVELOPMENT_TEAM = CP2SKEB3XT;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||||
|
@@ -67,7 +67,7 @@
|
|||||||
</Testables>
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Release"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
enableASanStackUseAfterReturn = "YES"
|
enableASanStackUseAfterReturn = "YES"
|
||||||
|
@@ -394,6 +394,7 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>msa</string>
|
<string>msa</string>
|
||||||
<string>st</string>
|
<string>st</string>
|
||||||
|
<string>stx</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeIconFile</key>
|
<key>CFBundleTypeIconFile</key>
|
||||||
<string>floppy35.png</string>
|
<string>floppy35.png</string>
|
||||||
|
@@ -50,7 +50,8 @@ typedef NS_ENUM(NSInteger, CSMachineOricDiskInterface) {
|
|||||||
CSMachineOricDiskInterfaceNone,
|
CSMachineOricDiskInterfaceNone,
|
||||||
CSMachineOricDiskInterfaceMicrodisc,
|
CSMachineOricDiskInterfaceMicrodisc,
|
||||||
CSMachineOricDiskInterfacePravetz,
|
CSMachineOricDiskInterfacePravetz,
|
||||||
CSMachineOricDiskInterfaceJasmin
|
CSMachineOricDiskInterfaceJasmin,
|
||||||
|
CSMachineOricDiskInterfaceBD500
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, CSMachineVic20Region) {
|
typedef NS_ENUM(NSInteger, CSMachineVic20Region) {
|
||||||
|
@@ -103,6 +103,7 @@
|
|||||||
case CSMachineOricDiskInterfaceMicrodisc: target->disk_interface = Target::DiskInterface::Microdisc; break;
|
case CSMachineOricDiskInterfaceMicrodisc: target->disk_interface = Target::DiskInterface::Microdisc; break;
|
||||||
case CSMachineOricDiskInterfacePravetz: target->disk_interface = Target::DiskInterface::Pravetz; break;
|
case CSMachineOricDiskInterfacePravetz: target->disk_interface = Target::DiskInterface::Pravetz; break;
|
||||||
case CSMachineOricDiskInterfaceJasmin: target->disk_interface = Target::DiskInterface::Jasmin; break;
|
case CSMachineOricDiskInterfaceJasmin: target->disk_interface = Target::DiskInterface::Jasmin; break;
|
||||||
|
case CSMachineOricDiskInterfaceBD500: target->disk_interface = Target::DiskInterface::BD500; break;
|
||||||
}
|
}
|
||||||
_targets.push_back(std::move(target));
|
_targets.push_back(std::move(target));
|
||||||
}
|
}
|
||||||
|
@@ -331,7 +331,7 @@ Gw
|
|||||||
</popUpButtonCell>
|
</popUpButtonCell>
|
||||||
</popUpButton>
|
</popUpButton>
|
||||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fYL-p6-wyn">
|
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fYL-p6-wyn">
|
||||||
<rect key="frame" x="111" y="36" width="97" height="25"/>
|
<rect key="frame" x="111" y="36" width="129" height="25"/>
|
||||||
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="XhK-Jh-oTW" id="aYb-m1-H9X">
|
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="XhK-Jh-oTW" id="aYb-m1-H9X">
|
||||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||||
<font key="font" metaFont="system"/>
|
<font key="font" metaFont="system"/>
|
||||||
@@ -341,6 +341,7 @@ Gw
|
|||||||
<menuItem title="Microdisc" tag="1" id="1jS-Lz-FRj"/>
|
<menuItem title="Microdisc" tag="1" id="1jS-Lz-FRj"/>
|
||||||
<menuItem title="Jasmin" tag="3" id="CGU-gd-xov"/>
|
<menuItem title="Jasmin" tag="3" id="CGU-gd-xov"/>
|
||||||
<menuItem title="8DOS" tag="2" id="edb-fl-C8Y"/>
|
<menuItem title="8DOS" tag="2" id="edb-fl-C8Y"/>
|
||||||
|
<menuItem title="Byte Drive 500" tag="4" id="lkS-Rr-m1D"/>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</popUpButtonCell>
|
</popUpButtonCell>
|
||||||
|
@@ -196,6 +196,7 @@ class MachinePicker: NSObject {
|
|||||||
case 1: diskInterface = .microdisc
|
case 1: diskInterface = .microdisc
|
||||||
case 2: diskInterface = .pravetz
|
case 2: diskInterface = .pravetz
|
||||||
case 3: diskInterface = .jasmin
|
case 3: diskInterface = .jasmin
|
||||||
|
case 4: diskInterface = .BD500
|
||||||
default: break;
|
default: break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -261,6 +261,32 @@ class CPU::MC68000::ProcessorStorageTests {
|
|||||||
XCTAssertEqual(stack_frame[6], 0x1004);
|
XCTAssertEqual(stack_frame[6], 0x1004);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)testShiftDuration {
|
||||||
|
//
|
||||||
|
_machine->set_program({
|
||||||
|
0x7004, // MOVE.l #$4, D0
|
||||||
|
0x7207, // MOVE.l #$7, D1
|
||||||
|
0x7401, // MOVE.l #$1, D2
|
||||||
|
|
||||||
|
0xe16e, // lsl d0, d6
|
||||||
|
0xe36e, // lsl d1, d6
|
||||||
|
0xe56e, // lsl d2, d6
|
||||||
|
});
|
||||||
|
_machine->run_for_instructions(3);
|
||||||
|
|
||||||
|
_machine->reset_cycle_count();
|
||||||
|
_machine->run_for_instructions(1);
|
||||||
|
XCTAssertEqual(_machine->get_cycle_count(), 6 + 8);
|
||||||
|
_machine->reset_cycle_count();
|
||||||
|
|
||||||
|
_machine->run_for_instructions(1);
|
||||||
|
XCTAssertEqual(_machine->get_cycle_count(), 6 + 14);
|
||||||
|
_machine->reset_cycle_count();
|
||||||
|
|
||||||
|
_machine->run_for_instructions(1);
|
||||||
|
XCTAssertEqual(_machine->get_cycle_count(), 6 + 2);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)testOpcodeCoverage {
|
- (void)testOpcodeCoverage {
|
||||||
// Perform an audit of implemented instructions.
|
// Perform an audit of implemented instructions.
|
||||||
CPU::MC68000::ProcessorStorageTests storage_tests(
|
CPU::MC68000::ProcessorStorageTests storage_tests(
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
@interface DigitalPhaseLockedLoopBridge : NSObject
|
@interface DigitalPhaseLockedLoopBridge : NSObject
|
||||||
|
|
||||||
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit historyLength:(NSUInteger)historyLength;
|
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit;
|
||||||
|
|
||||||
- (void)runForCycles:(NSUInteger)cycles;
|
- (void)runForCycles:(NSUInteger)cycles;
|
||||||
- (void)addPulse;
|
- (void)addPulse;
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
- (void)pushBit:(int)value;
|
- (void)pushBit:(int)value;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
class DigitalPhaseLockedLoopDelegate: public Storage::DigitalPhaseLockedLoop::Delegate {
|
class DigitalPhaseLockedLoopDelegate {
|
||||||
public:
|
public:
|
||||||
__weak DigitalPhaseLockedLoopBridge *bridge;
|
__weak DigitalPhaseLockedLoopBridge *bridge;
|
||||||
|
|
||||||
@@ -25,14 +25,14 @@ class DigitalPhaseLockedLoopDelegate: public Storage::DigitalPhaseLockedLoop::De
|
|||||||
};
|
};
|
||||||
|
|
||||||
@implementation DigitalPhaseLockedLoopBridge {
|
@implementation DigitalPhaseLockedLoopBridge {
|
||||||
std::unique_ptr<Storage::DigitalPhaseLockedLoop> _digitalPhaseLockedLoop;
|
std::unique_ptr<Storage::DigitalPhaseLockedLoop<DigitalPhaseLockedLoopDelegate>> _digitalPhaseLockedLoop;
|
||||||
DigitalPhaseLockedLoopDelegate _delegate;
|
DigitalPhaseLockedLoopDelegate _delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit historyLength:(NSUInteger)historyLength {
|
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if(self) {
|
if(self) {
|
||||||
_digitalPhaseLockedLoop = std::make_unique<Storage::DigitalPhaseLockedLoop>((unsigned int)clocksPerBit, (unsigned int)historyLength);
|
_digitalPhaseLockedLoop = std::make_unique<Storage::DigitalPhaseLockedLoop<DigitalPhaseLockedLoopDelegate>>((unsigned int)clocksPerBit);
|
||||||
_delegate.bridge = self;
|
_delegate.bridge = self;
|
||||||
_digitalPhaseLockedLoop->set_delegate(&_delegate);
|
_digitalPhaseLockedLoop->set_delegate(&_delegate);
|
||||||
}
|
}
|
||||||
|
@@ -26,22 +26,22 @@ class DPLLTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testPerfectInput() {
|
func testPerfectInput() {
|
||||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||||
testRegularNibblesOnPLL(pll!, bitLength: 100)
|
testRegularNibblesOnPLL(pll!, bitLength: 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFastButRegular() {
|
func testFastButRegular() {
|
||||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||||
testRegularNibblesOnPLL(pll!, bitLength: 90)
|
testRegularNibblesOnPLL(pll!, bitLength: 90)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSlowButRegular() {
|
func testSlowButRegular() {
|
||||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||||
testRegularNibblesOnPLL(pll!, bitLength: 110)
|
testRegularNibblesOnPLL(pll!, bitLength: 110)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTwentyPercentSinePattern() {
|
func testTwentyPercentSinePattern() {
|
||||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||||
var angle = 0.0
|
var angle = 0.0
|
||||||
|
|
||||||
// clock in two 1s, a 0, and a 1, 200 times over
|
// clock in two 1s, a 0, and a 1, 200 times over
|
||||||
|
@@ -130,6 +130,10 @@ class RAM68000: public CPU::MC68000::BusHandler {
|
|||||||
return int(duration_.as_integral()) >> 1;
|
return int(duration_.as_integral()) >> 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset_cycle_count() {
|
||||||
|
duration_ = HalfCycles(0);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CPU::MC68000::Processor<RAM68000, true, true> m68000_;
|
CPU::MC68000::Processor<RAM68000, true, true> m68000_;
|
||||||
std::array<uint16_t, 256*1024> ram_{};
|
std::array<uint16_t, 256*1024> ram_{};
|
||||||
|
@@ -5,6 +5,11 @@ Expected files:
|
|||||||
basic10.rom
|
basic10.rom
|
||||||
basic11.rom
|
basic11.rom
|
||||||
colour.rom
|
colour.rom
|
||||||
microdisc.rom
|
pravetz.rom
|
||||||
8dos.rom
|
|
||||||
pravetz.rom
|
Also potentially required:
|
||||||
|
|
||||||
|
* microdisc.rom, for loading Microdisc software;
|
||||||
|
* 8dos.rom, for loading Pravetz 8-DOS software;
|
||||||
|
* jasmin.rom, for loading Jasmin disk interface software; and
|
||||||
|
* bd500.rom, for loading Byte Drive 500 software.
|
@@ -15,10 +15,9 @@ using namespace Storage::Disk;
|
|||||||
Controller::Controller(Cycles clock_rate) :
|
Controller::Controller(Cycles clock_rate) :
|
||||||
clock_rate_multiplier_(128000000 / clock_rate.as_integral()),
|
clock_rate_multiplier_(128000000 / clock_rate.as_integral()),
|
||||||
clock_rate_(clock_rate.as_integral() * clock_rate_multiplier_),
|
clock_rate_(clock_rate.as_integral() * clock_rate_multiplier_),
|
||||||
|
pll_(100, *this),
|
||||||
empty_drive_(new Drive(int(clock_rate.as_integral()), 1, 1)) {
|
empty_drive_(new Drive(int(clock_rate.as_integral()), 1, 1)) {
|
||||||
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
|
set_expected_bit_length(Time(1));
|
||||||
Time one(1);
|
|
||||||
set_expected_bit_length(one);
|
|
||||||
set_drive(empty_drive_);
|
set_drive(empty_drive_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,13 +41,13 @@ Drive &Controller::get_drive() {
|
|||||||
|
|
||||||
void Controller::process_event(const Drive::Event &event) {
|
void Controller::process_event(const Drive::Event &event) {
|
||||||
switch(event.type) {
|
switch(event.type) {
|
||||||
case Track::Event::FluxTransition: pll_->add_pulse(); break;
|
case Track::Event::FluxTransition: pll_.add_pulse(); break;
|
||||||
case Track::Event::IndexHole: process_index_hole(); break;
|
case Track::Event::IndexHole: process_index_hole(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::advance(const Cycles cycles) {
|
void Controller::advance(const Cycles cycles) {
|
||||||
if(is_reading_) pll_->run_for(Cycles(cycles.as_integral() * clock_rate_multiplier_));
|
if(is_reading_) pll_.run_for(Cycles(cycles.as_integral() * clock_rate_multiplier_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::process_write_completed() {
|
void Controller::process_write_completed() {
|
||||||
@@ -66,9 +65,8 @@ void Controller::set_expected_bit_length(Time bit_length) {
|
|||||||
|
|
||||||
// this conversion doesn't need to be exact because there's a lot of variation to be taken
|
// 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
|
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
|
||||||
int clocks_per_bit = cycles_per_bit.get<int>();
|
const int clocks_per_bit = cycles_per_bit.get<int>();
|
||||||
pll_ = std::make_unique<DigitalPhaseLockedLoop>(clocks_per_bit, 3);
|
pll_.set_clocks_per_bit(clocks_per_bit);
|
||||||
pll_->set_delegate(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::digital_phase_locked_loop_output_bit(int value) {
|
void Controller::digital_phase_locked_loop_output_bit(int value) {
|
||||||
|
@@ -29,7 +29,6 @@ namespace Disk {
|
|||||||
TODO: 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:
|
class Controller:
|
||||||
public DigitalPhaseLockedLoop::Delegate,
|
|
||||||
public Drive::EventDelegate,
|
public Drive::EventDelegate,
|
||||||
public ClockingHint::Source,
|
public ClockingHint::Source,
|
||||||
public ClockingHint::Observer {
|
public ClockingHint::Observer {
|
||||||
@@ -111,7 +110,9 @@ class Controller:
|
|||||||
|
|
||||||
bool is_reading_ = true;
|
bool is_reading_ = true;
|
||||||
|
|
||||||
std::shared_ptr<DigitalPhaseLockedLoop> pll_;
|
DigitalPhaseLockedLoop<Controller> pll_;
|
||||||
|
friend DigitalPhaseLockedLoop<Controller>;
|
||||||
|
|
||||||
std::shared_ptr<Drive> drive_;
|
std::shared_ptr<Drive> drive_;
|
||||||
|
|
||||||
std::shared_ptr<Drive> empty_drive_;
|
std::shared_ptr<Drive> empty_drive_;
|
||||||
@@ -124,7 +125,7 @@ class Controller:
|
|||||||
void advance(const Cycles cycles) override ;
|
void advance(const Cycles cycles) override ;
|
||||||
|
|
||||||
// to satisfy DigitalPhaseLockedLoop::Delegate
|
// to satisfy DigitalPhaseLockedLoop::Delegate
|
||||||
void digital_phase_locked_loop_output_bit(int value) override;
|
void digital_phase_locked_loop_output_bit(int value);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,69 +0,0 @@
|
|||||||
//
|
|
||||||
// DigitalPhaseLockedLoop.cpp
|
|
||||||
// Clock Signal
|
|
||||||
//
|
|
||||||
// Created by Thomas Harte on 11/07/2016.
|
|
||||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "DigitalPhaseLockedLoop.hpp"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
using namespace Storage;
|
|
||||||
|
|
||||||
DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, std::size_t length_of_history) :
|
|
||||||
offset_history_(length_of_history, 0),
|
|
||||||
window_length_(clocks_per_bit),
|
|
||||||
clocks_per_bit_(clocks_per_bit) {}
|
|
||||||
|
|
||||||
void DigitalPhaseLockedLoop::run_for(const Cycles cycles) {
|
|
||||||
offset_ += cycles.as_integral();
|
|
||||||
phase_ += cycles.as_integral();
|
|
||||||
if(phase_ >= window_length_) {
|
|
||||||
auto windows_crossed = phase_ / window_length_;
|
|
||||||
|
|
||||||
// check whether this triggers any 0s, if anybody cares
|
|
||||||
if(delegate_) {
|
|
||||||
if(window_was_filled_) --windows_crossed;
|
|
||||||
for(int c = 0; c < windows_crossed; c++)
|
|
||||||
delegate_->digital_phase_locked_loop_output_bit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
window_was_filled_ = false;
|
|
||||||
phase_ %= window_length_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DigitalPhaseLockedLoop::add_pulse() {
|
|
||||||
if(!window_was_filled_) {
|
|
||||||
if(delegate_) delegate_->digital_phase_locked_loop_output_bit(1);
|
|
||||||
window_was_filled_ = true;
|
|
||||||
post_phase_offset(phase_, offset_);
|
|
||||||
offset_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DigitalPhaseLockedLoop::post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) {
|
|
||||||
offset_history_[offset_history_pointer_] = new_offset;
|
|
||||||
offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size();
|
|
||||||
|
|
||||||
// use an unweighted average of the stored offsets to compute current window size,
|
|
||||||
// bucketing them by rounding to the nearest multiple of the base clocks per bit
|
|
||||||
Cycles::IntType total_spacing = 0;
|
|
||||||
Cycles::IntType total_divisor = 0;
|
|
||||||
for(auto offset : offset_history_) {
|
|
||||||
auto multiple = (offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_;
|
|
||||||
if(!multiple) continue;
|
|
||||||
total_divisor += multiple;
|
|
||||||
total_spacing += offset;
|
|
||||||
}
|
|
||||||
if(total_divisor) {
|
|
||||||
window_length_ = total_spacing / total_divisor;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto error = new_phase - (window_length_ >> 1);
|
|
||||||
|
|
||||||
// use a simple spring mechanism as a lowpass filter for phase
|
|
||||||
phase_ -= (error + 1) >> 1;
|
|
||||||
}
|
|
@@ -9,6 +9,8 @@
|
|||||||
#ifndef DigitalPhaseLockedLoop_hpp
|
#ifndef DigitalPhaseLockedLoop_hpp
|
||||||
#define DigitalPhaseLockedLoop_hpp
|
#define DigitalPhaseLockedLoop_hpp
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -16,54 +18,117 @@
|
|||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
|
|
||||||
class DigitalPhaseLockedLoop {
|
/*!
|
||||||
|
Template parameters:
|
||||||
|
|
||||||
|
@c bit_handler A class that must implement a method, digital_phase_locked_loop_output_bit(int) for receving bits from the DPLL.
|
||||||
|
@c length_of_history The number of historic pulses to consider in locking to phase.
|
||||||
|
*/
|
||||||
|
template <typename BitHandler, size_t length_of_history = 3> class DigitalPhaseLockedLoop {
|
||||||
public:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Instantiates a @c DigitalPhaseLockedLoop.
|
Instantiates a @c DigitalPhaseLockedLoop.
|
||||||
|
|
||||||
@param clocks_per_bit The expected number of cycles between each bit of input.
|
@param clocks_per_bit The expected number of cycles between each bit of input.
|
||||||
@param length_of_history The number of historic pulses to consider in locking to phase.
|
|
||||||
*/
|
*/
|
||||||
DigitalPhaseLockedLoop(int clocks_per_bit, std::size_t length_of_history);
|
DigitalPhaseLockedLoop(int clocks_per_bit, BitHandler &handler) :
|
||||||
|
bit_handler_(handler), window_length_(clocks_per_bit), clocks_per_bit_(clocks_per_bit) {}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Changes the expected window length.
|
||||||
|
*/
|
||||||
|
void set_clocks_per_bit(int clocks_per_bit) {
|
||||||
|
window_length_ = clocks_per_bit_ = clocks_per_bit;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Runs the loop, impliedly posting no pulses during that period.
|
Runs the loop, impliedly posting no pulses during that period.
|
||||||
|
|
||||||
@c number_of_cycles The time to run the loop for.
|
@c number_of_cycles The time to run the loop for.
|
||||||
*/
|
*/
|
||||||
void run_for(const Cycles cycles);
|
void run_for(const Cycles cycles) {
|
||||||
|
offset_ += cycles.as_integral();
|
||||||
|
phase_ += cycles.as_integral();
|
||||||
|
if(phase_ >= window_length_) {
|
||||||
|
auto windows_crossed = phase_ / window_length_;
|
||||||
|
|
||||||
|
// Check whether this triggers any 0s.
|
||||||
|
if(window_was_filled_) --windows_crossed;
|
||||||
|
for(int c = 0; c < windows_crossed; c++)
|
||||||
|
bit_handler_.digital_phase_locked_loop_output_bit(0);
|
||||||
|
|
||||||
|
window_was_filled_ = false;
|
||||||
|
phase_ %= window_length_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Announces a pulse at the current time.
|
Announces a pulse at the current time.
|
||||||
*/
|
*/
|
||||||
void add_pulse();
|
void add_pulse() {
|
||||||
|
if(!window_was_filled_) {
|
||||||
/*!
|
bit_handler_.digital_phase_locked_loop_output_bit(1);
|
||||||
A receiver for PCM output data; called upon every recognised bit.
|
window_was_filled_ = true;
|
||||||
*/
|
post_phase_offset(phase_, offset_);
|
||||||
class Delegate {
|
offset_ = 0;
|
||||||
public:
|
}
|
||||||
virtual void digital_phase_locked_loop_output_bit(int value) = 0;
|
|
||||||
};
|
|
||||||
void set_delegate(Delegate *delegate) {
|
|
||||||
delegate_ = delegate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Delegate *delegate_ = nullptr;
|
BitHandler &bit_handler_;
|
||||||
|
|
||||||
void post_phase_offset(Cycles::IntType phase, Cycles::IntType offset);
|
void post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) {
|
||||||
|
// Erase the effect of whatever is currently in this slot.
|
||||||
|
total_divisor_ -= offset_history_[offset_history_pointer_].divisor;
|
||||||
|
total_spacing_ -= offset_history_[offset_history_pointer_].spacing;
|
||||||
|
|
||||||
std::vector<Cycles::IntType> offset_history_;
|
// Fill in the new fields.
|
||||||
|
const auto multiple = std::max((new_offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_, Cycles::IntType(1));
|
||||||
|
offset_history_[offset_history_pointer_].divisor = multiple;
|
||||||
|
offset_history_[offset_history_pointer_].spacing = new_offset;
|
||||||
|
|
||||||
|
// Add in the new values;
|
||||||
|
total_divisor_ += offset_history_[offset_history_pointer_].divisor;
|
||||||
|
total_spacing_ += offset_history_[offset_history_pointer_].spacing;
|
||||||
|
|
||||||
|
// Advance the write slot.
|
||||||
|
offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size();
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
Cycles::IntType td = 0, ts = 0;
|
||||||
|
for(auto offset: offset_history_) {
|
||||||
|
td += offset.divisor;
|
||||||
|
ts += offset.spacing;
|
||||||
|
}
|
||||||
|
assert(ts == total_spacing_);
|
||||||
|
assert(td == total_divisor_);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// In net: use an unweighted average of the stored offsets to compute current window size,
|
||||||
|
// bucketing them by rounding to the nearest multiple of the base clocks per bit
|
||||||
|
window_length_ = total_spacing_ / total_divisor_;
|
||||||
|
|
||||||
|
// Also apply a difference to phase, use a simple spring mechanism as a lowpass filter.
|
||||||
|
const auto error = new_phase - (window_length_ >> 1);
|
||||||
|
phase_ -= (error + 1) >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoggedOffset {
|
||||||
|
Cycles::IntType divisor = 1, spacing = 1;
|
||||||
|
};
|
||||||
|
std::array<LoggedOffset, length_of_history> offset_history_;
|
||||||
std::size_t offset_history_pointer_ = 0;
|
std::size_t offset_history_pointer_ = 0;
|
||||||
Cycles::IntType offset_ = 0;
|
|
||||||
|
Cycles::IntType total_spacing_ = length_of_history;
|
||||||
|
Cycles::IntType total_divisor_ = length_of_history;
|
||||||
|
|
||||||
Cycles::IntType phase_ = 0;
|
Cycles::IntType phase_ = 0;
|
||||||
Cycles::IntType window_length_ = 0;
|
Cycles::IntType window_length_ = 0;
|
||||||
|
|
||||||
|
Cycles::IntType offset_ = 0;
|
||||||
bool window_was_filled_ = false;
|
bool window_was_filled_ = false;
|
||||||
|
|
||||||
int clocks_per_bit_ = 0;
|
int clocks_per_bit_ = 0;
|
||||||
int tolerance_ = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -77,7 +77,7 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address)
|
|||||||
for(int byte = 0; byte < 6; byte++) {
|
for(int byte = 0; byte < 6; byte++) {
|
||||||
last_header[byte] = file_.get8();
|
last_header[byte] = file_.get8();
|
||||||
encoder->add_byte(last_header[byte]);
|
encoder->add_byte(last_header[byte]);
|
||||||
track_offset++;
|
++track_offset;
|
||||||
if(track_offset == 6250) break;
|
if(track_offset == 6250) break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -85,8 +85,12 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address)
|
|||||||
case 0xfb:
|
case 0xfb:
|
||||||
for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) {
|
for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) {
|
||||||
encoder->add_byte(file_.get8());
|
encoder->add_byte(file_.get8());
|
||||||
track_offset++;
|
++track_offset;
|
||||||
if(track_offset == 6250) break;
|
// Special exception: don't interrupt a sector body if it seems to
|
||||||
|
// be about to run over the end of the track. It seems like BD-500
|
||||||
|
// disks break the usual 6250-byte rule, pushing out to just less
|
||||||
|
// than 6400 bytes total.
|
||||||
|
if(track_offset == 6400) break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
478
Storage/Disk/DiskImage/Formats/STX.cpp
Normal file
478
Storage/Disk/DiskImage/Formats/STX.cpp
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
//
|
||||||
|
// STX.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 13/11/2019.
|
||||||
|
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "STX.hpp"
|
||||||
|
|
||||||
|
#include "../../Encodings/MFM/Constants.hpp"
|
||||||
|
#include "../../Encodings/MFM/Shifter.hpp"
|
||||||
|
#include "../../Encodings/MFM/Encoder.hpp"
|
||||||
|
#include "../../Track/PCMTrack.hpp"
|
||||||
|
|
||||||
|
#include "Utility/ImplicitSectors.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
using namespace Storage::Disk;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class TrackConstructor {
|
||||||
|
public:
|
||||||
|
constexpr static uint16_t NoFirstOffset = std::numeric_limits<uint16_t>::max();
|
||||||
|
|
||||||
|
struct Sector {
|
||||||
|
// Records explicitly present in the sector table.
|
||||||
|
uint32_t data_offset = 0;
|
||||||
|
size_t bit_position = 0;
|
||||||
|
uint16_t data_duration = 0;
|
||||||
|
std::array<uint8_t, 6> address = {0, 0, 0, 0, 0, 0};
|
||||||
|
uint8_t status = 0;
|
||||||
|
|
||||||
|
// Other facts that will either be supplied by the STX or which
|
||||||
|
// will be empty.
|
||||||
|
std::vector<uint8_t> fuzzy_mask;
|
||||||
|
std::vector<uint8_t> contents;
|
||||||
|
std::vector<uint16_t> timing;
|
||||||
|
|
||||||
|
// Accessors.
|
||||||
|
|
||||||
|
/// @returns The byte size of this sector, according to its address mark.
|
||||||
|
uint32_t data_size() const {
|
||||||
|
return uint32_t(128 << address[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @returns The byte stream this sector address would produce if a WD read track command were to observe it.
|
||||||
|
std::vector<uint8_t> get_track_address_image() const {
|
||||||
|
return track_encoding(address.begin(), address.begin() + 4, {0xa1, 0xa1, 0xfe});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @returns The byte stream this sector data would produce if a WD read track command were to observe it.
|
||||||
|
std::vector<uint8_t> get_track_data_image() const {
|
||||||
|
return track_encoding(contents.begin(), contents.end(), {0xa1, 0xa1, 0xfb});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// @returns The effect of encoding @c prefix followed by the bytes from @c begin to @c end as MFM data and then decoding them as if
|
||||||
|
/// observed by a WD read track command.
|
||||||
|
template <typename T> static std::vector<uint8_t> track_encoding(T begin, T end, std::initializer_list<uint8_t> prefix) {
|
||||||
|
std::vector<uint8_t> result;
|
||||||
|
result.reserve(size_t(end - begin) + prefix.size());
|
||||||
|
|
||||||
|
PCMSegment segment;
|
||||||
|
std::unique_ptr<Storage::Encodings::MFM::Encoder> encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data);
|
||||||
|
|
||||||
|
// Encode prefix.
|
||||||
|
for(auto c: prefix) {
|
||||||
|
encoder->add_byte(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode body.
|
||||||
|
while(begin != end) {
|
||||||
|
encoder->add_byte(*begin);
|
||||||
|
++begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode, obeying false syncs.
|
||||||
|
using Shifter = Storage::Encodings::MFM::Shifter;
|
||||||
|
Shifter shifter;
|
||||||
|
shifter.set_should_obey_syncs(true);
|
||||||
|
|
||||||
|
// Add whatever comes from the track.
|
||||||
|
for(auto bit: segment.data) {
|
||||||
|
shifter.add_input_bit(int(bit));
|
||||||
|
|
||||||
|
if(shifter.get_token() != Shifter::None) {
|
||||||
|
result.push_back(shifter.get_byte());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TrackConstructor(const std::vector<uint8_t> &track_data, const std::vector<Sector> §ors, size_t track_size, uint16_t first_sync) :
|
||||||
|
track_data_(track_data), sectors_(sectors), track_size_(track_size), first_sync_(first_sync) {
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<PCMTrack> get_track() {
|
||||||
|
// If no contents are supplied, return an unformatted track.
|
||||||
|
if(sectors_.empty() && track_data_.empty()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no sectors are on this track, just encode the track data. STX allows speed
|
||||||
|
// changes and fuzzy bits in sectors only.
|
||||||
|
if(sectors_.empty()) {
|
||||||
|
PCMSegment segment;
|
||||||
|
std::unique_ptr<Storage::Encodings::MFM::Encoder> encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data);
|
||||||
|
for(auto c: track_data_) {
|
||||||
|
encoder->add_byte(c);
|
||||||
|
}
|
||||||
|
return std::make_shared<PCMTrack>(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, seek to encode the sectors, using the track data to
|
||||||
|
// fill in the gaps (if provided).
|
||||||
|
std::unique_ptr<Storage::Encodings::MFM::Encoder> encoder;
|
||||||
|
std::unique_ptr<PCMSegment> segment;
|
||||||
|
|
||||||
|
// To reconcile the list of sectors with the WD get track-style track image,
|
||||||
|
// use sector bodies as definitive and refer to the track image for in-fill.
|
||||||
|
auto track_position = track_data_.begin();
|
||||||
|
const auto address_mark = {0xa1, 0xa1, 0xfe};
|
||||||
|
const auto track_mark = {0xa1, 0xa1, 0xfb};
|
||||||
|
struct Location {
|
||||||
|
enum Type {
|
||||||
|
Address, Data
|
||||||
|
} type;
|
||||||
|
std::vector<uint8_t>::const_iterator position;
|
||||||
|
const Sector §or;
|
||||||
|
|
||||||
|
Location(Type type, std::vector<uint8_t>::const_iterator position, const Sector §or) : type(type), position(position), sector(sector) {}
|
||||||
|
};
|
||||||
|
std::vector<Location> locations;
|
||||||
|
for(const auto §or: sectors_) {
|
||||||
|
{
|
||||||
|
// Find out what the address would look like, if found in a read track.
|
||||||
|
const auto track_address = sector.get_track_address_image();
|
||||||
|
|
||||||
|
// Try to locate the header within the track image; if it can't be found then settle for
|
||||||
|
// the next thing that looks like a header of any sort.
|
||||||
|
auto address_position = std::search(track_position, track_data_.end(), track_address.begin(), track_address.end());
|
||||||
|
if(address_position == track_data_.end()) {
|
||||||
|
address_position = std::search(track_position, track_data_.end(), address_mark.begin(), address_mark.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop now if there's nowhere obvious to put this sector.
|
||||||
|
if(address_position == track_data_.end()) break;
|
||||||
|
locations.emplace_back(Location::Address, address_position, sector);
|
||||||
|
|
||||||
|
// Advance the track position.
|
||||||
|
track_position = address_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do much the same thing for the data, if it exists.
|
||||||
|
if(!(sector.status & 0x10)) {
|
||||||
|
const auto track_data = sector.get_track_data_image();
|
||||||
|
|
||||||
|
auto data_position = std::search(track_position, track_data_.end(), track_data.begin(), track_data.end());
|
||||||
|
if(data_position == track_data_.end()) {
|
||||||
|
data_position = std::search(track_position, track_data_.end(), track_mark.begin(), track_mark.end());
|
||||||
|
}
|
||||||
|
if(data_position == track_data_.end()) break;
|
||||||
|
|
||||||
|
locations.emplace_back(Location::Data, data_position, sector);
|
||||||
|
track_position = data_position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just create an encoder if one doesn't exist. TODO: factor in data rate.
|
||||||
|
if(!encoder) {
|
||||||
|
segment.reset(new PCMSegment);
|
||||||
|
encoder = Storage::Encodings::MFM::GetMFMEncoder(segment->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out, being wary of potential overlapping sectors, and copying from track_data_ to fill in gaps.
|
||||||
|
auto location = locations.begin();
|
||||||
|
track_position = track_data_.begin();
|
||||||
|
while(location != locations.end()) {
|
||||||
|
// Advance to location.position.
|
||||||
|
while(track_position != location->position) {
|
||||||
|
encoder->add_byte(*track_position);
|
||||||
|
++track_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the relevant mark and fill in a default number of bytes to write.
|
||||||
|
size_t bytes_to_write;
|
||||||
|
switch(location->type) {
|
||||||
|
default:
|
||||||
|
case Location::Address:
|
||||||
|
encoder->add_ID_address_mark();
|
||||||
|
bytes_to_write = 6;
|
||||||
|
break;
|
||||||
|
case Location::Data:
|
||||||
|
if(location->sector.status & 0x20)
|
||||||
|
encoder->add_deleted_data_address_mark();
|
||||||
|
else
|
||||||
|
encoder->add_data_address_mark();
|
||||||
|
bytes_to_write = location->sector.data_size() + 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
track_position += 3;
|
||||||
|
|
||||||
|
// Decide how much data to write for real; this [partially] allows for overlapping sectors.
|
||||||
|
auto next_location = location + 1;
|
||||||
|
if(next_location != locations.end()) {
|
||||||
|
bytes_to_write = std::min(bytes_to_write, size_t(next_location->position - track_position));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip that many bytes from the underlying track image.
|
||||||
|
track_position += ssize_t(bytes_to_write);
|
||||||
|
|
||||||
|
// Write bytes.
|
||||||
|
switch(location->type) {
|
||||||
|
default:
|
||||||
|
case Location::Address:
|
||||||
|
for(size_t c = 0; c < bytes_to_write; ++c)
|
||||||
|
encoder->add_byte(location->sector.address[c]);
|
||||||
|
break;
|
||||||
|
case Location::Data: {
|
||||||
|
const auto body_bytes = std::min(bytes_to_write, size_t(location->sector.data_size()));
|
||||||
|
for(size_t c = 0; c < body_bytes; ++c)
|
||||||
|
encoder->add_byte(location->sector.contents[c]);
|
||||||
|
|
||||||
|
// Add a CRC only if it fits (TODO: crop if necessary?).
|
||||||
|
if(bytes_to_write & 127) {
|
||||||
|
encoder->add_crc((location->sector.status & 0x18) == 0x10);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance location.
|
||||||
|
++location;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write anything remaining from the track image.
|
||||||
|
while(track_position < track_data_.end()) {
|
||||||
|
encoder->add_byte(*track_position);
|
||||||
|
++track_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write generic padding up until the specified track size.
|
||||||
|
while(segment->data.size() < track_size_ * 16) {
|
||||||
|
encoder->add_byte(0x4e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad out to the minimum size a WD can actually make sense of.
|
||||||
|
// I've no idea why it's valid for tracks to be shorter than this,
|
||||||
|
// so likely I'm suffering a comprehansion deficiency.
|
||||||
|
// TODO: determine why this isn't correct (or, possibly, is).
|
||||||
|
while(segment->data.size() < 5750 * 16) {
|
||||||
|
encoder->add_byte(0x4e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_shared<PCMTrack>(*segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::vector<uint8_t> &track_data_;
|
||||||
|
const std::vector<Sector> §ors_;
|
||||||
|
const size_t track_size_;
|
||||||
|
const uint16_t first_sync_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
STX::STX(const std::string &file_name) : file_(file_name) {
|
||||||
|
// Require that this be a version 3 Pasti.
|
||||||
|
if(!file_.check_signature("RSY", 4)) throw Error::InvalidFormat;
|
||||||
|
if(file_.get16le() != 3) throw Error::InvalidFormat;
|
||||||
|
|
||||||
|
// Skip: tool used, 2 reserved bytes.
|
||||||
|
file_.seek(4, SEEK_CUR);
|
||||||
|
|
||||||
|
// Skip the track count, test for a new-style encoding, skip a reserved area.
|
||||||
|
file_.seek(1, SEEK_CUR);
|
||||||
|
is_new_format_ = file_.get8() == 2;
|
||||||
|
file_.seek(4, SEEK_CUR);
|
||||||
|
|
||||||
|
// Set all tracks absent.
|
||||||
|
memset(offset_by_track_, 0, sizeof(offset_by_track_));
|
||||||
|
|
||||||
|
// Parse the tracks table to fill in offset_by_track_. The only available documentation
|
||||||
|
// for STX is unofficial and makes no promise about track order. Hence the bucket sort,
|
||||||
|
// effectively putting them into track order.
|
||||||
|
//
|
||||||
|
// Track descriptor layout:
|
||||||
|
//
|
||||||
|
// 0 4 Record size.
|
||||||
|
// 4 4 Number of bytes in fuzzy mask record.
|
||||||
|
// 8 2 Number of sectors on track.
|
||||||
|
// 10 2 Track flags.
|
||||||
|
// 12 2 Total number of bytes on track.
|
||||||
|
// 14 1 Track number (b7 = side, b0-b6 = track).
|
||||||
|
// 15 1 Track type.
|
||||||
|
track_count_ = 0;
|
||||||
|
head_count_ = 1;
|
||||||
|
while(true) {
|
||||||
|
const long offset = file_.tell();
|
||||||
|
const uint32_t size = file_.get32le();
|
||||||
|
if(file_.eof()) break;
|
||||||
|
|
||||||
|
// Skip fields other than track position, then fill in table position and advance.
|
||||||
|
file_.seek(10, SEEK_CUR);
|
||||||
|
|
||||||
|
const uint8_t track_position = file_.get8();
|
||||||
|
offset_by_track_[track_position] = offset;
|
||||||
|
|
||||||
|
// Update the maximum surface dimensions.
|
||||||
|
track_count_ = std::max(track_count_, track_position & 0x7f);
|
||||||
|
head_count_ = std::max(head_count_, ((track_position & 0x80) >> 6));
|
||||||
|
|
||||||
|
// Seek next track start.
|
||||||
|
file_.seek(offset + size, SEEK_SET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadPosition STX::get_maximum_head_position() {
|
||||||
|
return HeadPosition(track_count_ + 1); // Same issue as MSA; must fix!
|
||||||
|
}
|
||||||
|
|
||||||
|
int STX::get_head_count() {
|
||||||
|
return head_count_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<::Storage::Disk::Track> STX::get_track_at_position(::Storage::Disk::Track::Address address) {
|
||||||
|
// These images have two sides, at most.
|
||||||
|
if(address.head > 1) return nullptr;
|
||||||
|
|
||||||
|
// If no track was found, there's nothing to do here.
|
||||||
|
const int track_index = (address.head * 0x80) + address.position.as_int();
|
||||||
|
if(!offset_by_track_[track_index]) return nullptr;
|
||||||
|
|
||||||
|
// Seek to the track (skipping the record size field).
|
||||||
|
file_.seek(offset_by_track_[track_index] + 4, SEEK_SET);
|
||||||
|
|
||||||
|
// Grab the track description.
|
||||||
|
const uint32_t fuzzy_size = file_.get32le();
|
||||||
|
const uint16_t sector_count = file_.get16le();
|
||||||
|
const uint16_t flags = file_.get16le();
|
||||||
|
const size_t track_length = file_.get16le();
|
||||||
|
file_.seek(2, SEEK_CUR); // Skip track type; despite being named, it's apparently unused.
|
||||||
|
|
||||||
|
// If this is a trivial .ST-style sector dump, life is easy.
|
||||||
|
if(!(flags & 1)) {
|
||||||
|
const auto sector_contents = file_.read(sector_count * 512);
|
||||||
|
return track_for_sectors(sector_contents.data(), sector_count, uint8_t(address.position.as_int()), uint8_t(address.head), 1, 2, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab sector records, if provided.
|
||||||
|
std::vector<TrackConstructor::Sector> sectors;
|
||||||
|
std::vector<uint8_t> track_data;
|
||||||
|
uint16_t first_sync = TrackConstructor::NoFirstOffset;
|
||||||
|
|
||||||
|
// Sector records come first.
|
||||||
|
for(uint16_t c = 0; c < sector_count; ++c) {
|
||||||
|
sectors.emplace_back();
|
||||||
|
sectors.back().data_offset = file_.get32le();
|
||||||
|
sectors.back().bit_position = file_.get16le();
|
||||||
|
sectors.back().data_duration = file_.get16le();
|
||||||
|
file_.read(sectors.back().address);
|
||||||
|
sectors.back().status = file_.get8();
|
||||||
|
file_.seek(1, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If fuzzy masks are specified, attach them to their corresponding sectors.
|
||||||
|
if(fuzzy_size) {
|
||||||
|
uint32_t fuzzy_bytes_read = 0;
|
||||||
|
for(auto §or: sectors) {
|
||||||
|
// Check for the fuzzy bit mask; if it's not set then
|
||||||
|
// there's nothing for this sector.
|
||||||
|
if(!(sector.status & 0x80)) continue;
|
||||||
|
|
||||||
|
// Make sure there are enough bytes left.
|
||||||
|
const uint32_t expected_bytes = sector.data_size();
|
||||||
|
if(fuzzy_bytes_read + expected_bytes > fuzzy_size) break;
|
||||||
|
|
||||||
|
// Okay, there are, so read them.
|
||||||
|
sector.fuzzy_mask = file_.read(expected_bytes);
|
||||||
|
fuzzy_bytes_read += expected_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It should be true that the number of fuzzy masks caused
|
||||||
|
// exactly the correct number of fuzzy bytes to be read.
|
||||||
|
// But, just in case, check and possibly skip some.
|
||||||
|
file_.seek(long(fuzzy_size) - fuzzy_bytes_read, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// There may or may not be a track image. Grab it if so.
|
||||||
|
|
||||||
|
// Grab the read-track-esque track contents, if available.
|
||||||
|
long sector_start = file_.tell();
|
||||||
|
if(flags & 0x40) {
|
||||||
|
// Bit 6 => there is a track to read;
|
||||||
|
// bit
|
||||||
|
if(flags & 0x80) {
|
||||||
|
first_sync = file_.get16le();
|
||||||
|
const uint16_t image_size = file_.get16le();
|
||||||
|
track_data = file_.read(image_size);
|
||||||
|
} else {
|
||||||
|
const uint16_t image_size = file_.get16le();
|
||||||
|
track_data = file_.read(image_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab sector contents.
|
||||||
|
long end_of_data = file_.tell();
|
||||||
|
for(auto §or: sectors) {
|
||||||
|
// If the FDC record-not-found flag is set, there's no sector body to find.
|
||||||
|
// Otherwise there's a sector body in the file somewhere.
|
||||||
|
if(!(sector.status & 0x10)) {
|
||||||
|
file_.seek(sector.data_offset + sector_start, SEEK_SET);
|
||||||
|
sector.contents = file_.read(sector.data_size());
|
||||||
|
end_of_data = std::max(end_of_data, file_.tell());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_.seek(end_of_data, SEEK_SET);
|
||||||
|
|
||||||
|
// Grab timing info if available.
|
||||||
|
file_.seek(4, SEEK_CUR); // Skip the timing descriptor, as it includes no new information.
|
||||||
|
for(auto §or: sectors) {
|
||||||
|
// Skip any sector with no intra-sector bit width variation.
|
||||||
|
if(!(sector.status&1)) continue;
|
||||||
|
|
||||||
|
const auto timing_record_size = sector.data_size() >> 4; // Use one entry per 16 bytes.
|
||||||
|
sector.timing.resize(timing_record_size);
|
||||||
|
|
||||||
|
if(!is_new_format_) {
|
||||||
|
// Generate timing records for Macrodos/Speedlock.
|
||||||
|
// Timing is specified in quarters. Which might or might not be
|
||||||
|
// quantities of 128 bytes, who knows?
|
||||||
|
for(size_t c = 0; c < timing_record_size; ++c) {
|
||||||
|
if(c < (timing_record_size >> 2)) {
|
||||||
|
sector.timing[c] = 127;
|
||||||
|
} else if(c < ((timing_record_size*2) >> 2)) {
|
||||||
|
sector.timing[c] = 133;
|
||||||
|
} else if(c < ((timing_record_size*3) >> 2)) {
|
||||||
|
sector.timing[c] = 121;
|
||||||
|
} else {
|
||||||
|
sector.timing[c] = 127;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is going to be a new-format record.
|
||||||
|
for(size_t c = 0; c < timing_record_size; ++c) {
|
||||||
|
sector.timing[c] = file_.get16be(); // These values are big endian, unlike the rest of the file.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the sectors by starting position. It's perfectly possible that they're always
|
||||||
|
// sorted in STX but, again, the reverse-engineered documentation doesn't make the
|
||||||
|
// promise, so that's that.
|
||||||
|
std::sort(sectors.begin(), sectors.end(),
|
||||||
|
[] (TrackConstructor::Sector &lhs, TrackConstructor::Sector &rhs) {
|
||||||
|
return lhs.bit_position < rhs.bit_position;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Having reached here, the actual stuff of parsing the file structure should be done.
|
||||||
|
So hand off to the TrackConstructor.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
TrackConstructor constructor(track_data, sectors, track_length, first_sync);
|
||||||
|
return constructor.get_track();
|
||||||
|
}
|
50
Storage/Disk/DiskImage/Formats/STX.hpp
Normal file
50
Storage/Disk/DiskImage/Formats/STX.hpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// STX.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 13/11/2019.
|
||||||
|
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef STX_hpp
|
||||||
|
#define STX_hpp
|
||||||
|
|
||||||
|
#include "../DiskImage.hpp"
|
||||||
|
#include "../../../FileHolder.hpp"
|
||||||
|
|
||||||
|
namespace Storage {
|
||||||
|
namespace Disk {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a @c Disk containing an STX disk image: sector contents plus a bunch of annotations as to sizing,
|
||||||
|
placement, bit density, fuzzy bits, etc.
|
||||||
|
*/
|
||||||
|
class STX: public DiskImage {
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
Construct an @c STX containing content from the file with name @c file_name.
|
||||||
|
|
||||||
|
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||||
|
@throws Error::InvalidFormat if the file doesn't appear to contain a .STX format image.
|
||||||
|
*/
|
||||||
|
STX(const std::string &file_name);
|
||||||
|
|
||||||
|
HeadPosition get_maximum_head_position() final;
|
||||||
|
int get_head_count() final;
|
||||||
|
|
||||||
|
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FileHolder file_;
|
||||||
|
|
||||||
|
int track_count_;
|
||||||
|
int head_count_;
|
||||||
|
|
||||||
|
bool is_new_format_;
|
||||||
|
long offset_by_track_[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* STX_hpp */
|
@@ -18,9 +18,10 @@
|
|||||||
|
|
||||||
using namespace Storage::Disk;
|
using namespace Storage::Disk;
|
||||||
|
|
||||||
Drive::Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads):
|
Drive::Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads, ReadyType rdy_type):
|
||||||
Storage::TimedEventLoop(input_clock_rate),
|
Storage::TimedEventLoop(input_clock_rate),
|
||||||
available_heads_(number_of_heads) {
|
available_heads_(number_of_heads),
|
||||||
|
ready_type_(rdy_type) {
|
||||||
set_rotation_speed(revolutions_per_minute);
|
set_rotation_speed(revolutions_per_minute);
|
||||||
|
|
||||||
const auto seed = static_cast<std::default_random_engine::result_type>(std::chrono::system_clock::now().time_since_epoch().count());
|
const auto seed = static_cast<std::default_random_engine::result_type>(std::chrono::system_clock::now().time_since_epoch().count());
|
||||||
@@ -35,7 +36,7 @@ Drive::Drive(int input_clock_rate, int revolutions_per_minute, int number_of_hea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Drive::Drive(int input_clock_rate, int number_of_heads) : Drive(input_clock_rate, 300, number_of_heads) {}
|
Drive::Drive(int input_clock_rate, int number_of_heads, ReadyType rdy_type) : Drive(input_clock_rate, 300, number_of_heads, rdy_type) {}
|
||||||
|
|
||||||
void Drive::set_rotation_speed(float revolutions_per_minute) {
|
void Drive::set_rotation_speed(float revolutions_per_minute) {
|
||||||
// Rationalise the supplied speed so that cycles_per_revolution_ is exact.
|
// Rationalise the supplied speed so that cycles_per_revolution_ is exact.
|
||||||
@@ -54,6 +55,9 @@ Drive::~Drive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
|
void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
|
||||||
|
if(ready_type_ == ReadyType::ShugartModifiedRDY || ready_type_ == ReadyType::IBMRDY) {
|
||||||
|
is_ready_ = false;
|
||||||
|
}
|
||||||
if(disk_) disk_->flush_tracks();
|
if(disk_) disk_->flush_tracks();
|
||||||
disk_ = disk;
|
disk_ = disk;
|
||||||
has_disk_ = !!disk_;
|
has_disk_ = !!disk_;
|
||||||
@@ -68,7 +72,7 @@ bool Drive::has_disk() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClockingHint::Preference Drive::preferred_clocking() {
|
ClockingHint::Preference Drive::preferred_clocking() {
|
||||||
return (!motor_is_on_ || !has_disk_) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
return (!has_disk_ || (time_until_motor_transition == Cycles(0) && !disk_is_rotating_)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Drive::get_is_track_zero() const {
|
bool Drive::get_is_track_zero() const {
|
||||||
@@ -76,6 +80,10 @@ bool Drive::get_is_track_zero() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Drive::step(HeadPosition offset) {
|
void Drive::step(HeadPosition offset) {
|
||||||
|
if(ready_type_ == ReadyType::IBMRDY) {
|
||||||
|
is_ready_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
HeadPosition old_head_position = head_position_;
|
HeadPosition old_head_position = head_position_;
|
||||||
head_position_ += offset;
|
head_position_ += offset;
|
||||||
if(head_position_ < HeadPosition(0)) {
|
if(head_position_ < HeadPosition(0)) {
|
||||||
@@ -142,30 +150,37 @@ bool Drive::get_is_read_only() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Drive::get_is_ready() const {
|
bool Drive::get_is_ready() const {
|
||||||
return ready_index_count_ == 2;
|
return is_ready_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::set_motor_on(bool motor_is_on) {
|
void Drive::set_motor_on(bool motor_is_on) {
|
||||||
if(motor_is_on_ != motor_is_on) {
|
// Do nothing if the input hasn't changed.
|
||||||
motor_is_on_ = motor_is_on;
|
if(motor_input_is_on_ == motor_is_on) return;
|
||||||
|
motor_input_is_on_ = motor_is_on;
|
||||||
|
|
||||||
if(observer_) {
|
// If this now means that the input and the actual state are in harmony,
|
||||||
observer_->set_drive_motor_status(drive_name_, motor_is_on_);
|
// cancel any planned change and stop.
|
||||||
if(announce_motor_led_) {
|
if(disk_is_rotating_ == motor_is_on) {
|
||||||
observer_->set_led_status(drive_name_, motor_is_on_);
|
time_until_motor_transition = Cycles(0);
|
||||||
}
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if(!motor_is_on) {
|
|
||||||
ready_index_count_ = 0;
|
|
||||||
if(disk_) disk_->flush_tracks();
|
|
||||||
}
|
|
||||||
update_clocking_observer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is a transition to on, start immediately.
|
||||||
|
// TODO: spin-up?
|
||||||
|
// TODO: momentum.
|
||||||
|
if(motor_is_on) {
|
||||||
|
set_disk_is_rotating(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a transition from on to off. Simulate momentum (ha!)
|
||||||
|
// by delaying the time until complete standstill.
|
||||||
|
if(time_until_motor_transition == Cycles(0))
|
||||||
|
time_until_motor_transition = get_input_clock_rate();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Drive::get_motor_on() const {
|
bool Drive::get_motor_on() const {
|
||||||
return motor_is_on_;
|
return motor_input_is_on_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Drive::get_index_pulse() const {
|
bool Drive::get_index_pulse() const {
|
||||||
@@ -185,7 +200,16 @@ void Drive::run_for(const Cycles cycles) {
|
|||||||
// Assumed: the index pulse pulses even if the drive has stopped spinning.
|
// Assumed: the index pulse pulses even if the drive has stopped spinning.
|
||||||
index_pulse_remaining_ = std::max(index_pulse_remaining_ - cycles, Cycles(0));
|
index_pulse_remaining_ = std::max(index_pulse_remaining_ - cycles, Cycles(0));
|
||||||
|
|
||||||
if(motor_is_on_) {
|
if(time_until_motor_transition > Cycles(0)) {
|
||||||
|
if(time_until_motor_transition > cycles) {
|
||||||
|
time_until_motor_transition -= cycles;
|
||||||
|
} else {
|
||||||
|
time_until_motor_transition = Cycles(0);
|
||||||
|
set_disk_is_rotating(!disk_is_rotating_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(disk_is_rotating_) {
|
||||||
if(has_disk_) {
|
if(has_disk_) {
|
||||||
Time zero(0);
|
Time zero(0);
|
||||||
|
|
||||||
@@ -287,7 +311,10 @@ void Drive::get_next_event(float duration_already_passed) {
|
|||||||
|
|
||||||
void Drive::process_next_event() {
|
void Drive::process_next_event() {
|
||||||
if(current_event_.type == Track::Event::IndexHole) {
|
if(current_event_.type == Track::Event::IndexHole) {
|
||||||
if(ready_index_count_ < 2) ready_index_count_++;
|
++ready_index_count_;
|
||||||
|
if(ready_index_count_ == 2 && (ready_type_ == ReadyType::ShugartRDY || ready_type_ == ReadyType::ShugartModifiedRDY)) {
|
||||||
|
is_ready_ = true;
|
||||||
|
}
|
||||||
cycles_since_index_hole_ = 0;
|
cycles_since_index_hole_ = 0;
|
||||||
}
|
}
|
||||||
if(
|
if(
|
||||||
@@ -400,6 +427,26 @@ bool Drive::is_writing() const {
|
|||||||
return !is_reading_;
|
return !is_reading_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Drive::set_disk_is_rotating(bool is_rotating) {
|
||||||
|
disk_is_rotating_ = is_rotating;
|
||||||
|
|
||||||
|
if(observer_) {
|
||||||
|
observer_->set_drive_motor_status(drive_name_, motor_input_is_on_);
|
||||||
|
if(announce_motor_led_) {
|
||||||
|
observer_->set_led_status(drive_name_, motor_input_is_on_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!is_rotating) {
|
||||||
|
if(ready_type_ == ReadyType::ShugartRDY) {
|
||||||
|
is_ready_ = false;
|
||||||
|
}
|
||||||
|
ready_index_count_ = 0;
|
||||||
|
if(disk_) disk_->flush_tracks();
|
||||||
|
}
|
||||||
|
update_clocking_observer();
|
||||||
|
}
|
||||||
|
|
||||||
void Drive::set_activity_observer(Activity::Observer *observer, const std::string &name, bool add_motor_led) {
|
void Drive::set_activity_observer(Activity::Observer *observer, const std::string &name, bool add_motor_led) {
|
||||||
observer_ = observer;
|
observer_ = observer;
|
||||||
announce_motor_led_ = add_motor_led;
|
announce_motor_led_ = add_motor_led;
|
||||||
@@ -407,11 +454,11 @@ void Drive::set_activity_observer(Activity::Observer *observer, const std::strin
|
|||||||
drive_name_ = name;
|
drive_name_ = name;
|
||||||
|
|
||||||
observer->register_drive(drive_name_);
|
observer->register_drive(drive_name_);
|
||||||
observer->set_drive_motor_status(drive_name_, motor_is_on_);
|
observer->set_drive_motor_status(drive_name_, disk_is_rotating_);
|
||||||
|
|
||||||
if(add_motor_led) {
|
if(add_motor_led) {
|
||||||
observer->register_led(drive_name_);
|
observer->register_led(drive_name_);
|
||||||
observer->set_led_status(drive_name_, motor_is_on_);
|
observer->set_led_status(drive_name_, disk_is_rotating_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,12 +24,21 @@ namespace Disk {
|
|||||||
|
|
||||||
class Drive: public ClockingHint::Source, public TimedEventLoop {
|
class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||||
public:
|
public:
|
||||||
Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads);
|
enum class ReadyType {
|
||||||
Drive(int input_clock_rate, int number_of_heads);
|
/// Indicates that RDY will go active when the motor is on and two index holes have passed; it will go inactive when the motor is off.
|
||||||
|
ShugartRDY,
|
||||||
|
/// Indicates that RDY will go active when the motor is on and two index holes have passed; it will go inactive when the disk is ejected.
|
||||||
|
ShugartModifiedRDY,
|
||||||
|
/// Indicates that RDY will go active when the head steps; it will go inactive when the disk is ejected.
|
||||||
|
IBMRDY,
|
||||||
|
};
|
||||||
|
|
||||||
|
Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads, ReadyType rdy_type = ReadyType::ShugartRDY);
|
||||||
|
Drive(int input_clock_rate, int number_of_heads, ReadyType rdy_type = ReadyType::ShugartRDY);
|
||||||
~Drive();
|
~Drive();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Replaces whatever is in the drive with @c disk.
|
Replaces whatever is in the drive with @c disk. Supply @c nullptr to eject any current disk and leave none inserted.
|
||||||
*/
|
*/
|
||||||
void set_disk(const std::shared_ptr<Disk> &disk);
|
void set_disk(const std::shared_ptr<Disk> &disk);
|
||||||
|
|
||||||
@@ -75,7 +84,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
|||||||
void set_motor_on(bool);
|
void set_motor_on(bool);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@returns @c true if the motor is on; @c false otherwise.
|
@returns @c true if the motor on input is active; @c false otherwise. This does not necessarily indicate whether the drive is spinning, due to momentum.
|
||||||
*/
|
*/
|
||||||
bool get_motor_on() const;
|
bool get_motor_on() const;
|
||||||
|
|
||||||
@@ -213,7 +222,10 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
|||||||
int available_heads_ = 0;
|
int available_heads_ = 0;
|
||||||
|
|
||||||
// Motor control state.
|
// Motor control state.
|
||||||
bool motor_is_on_ = false;
|
bool motor_input_is_on_ = false;
|
||||||
|
bool disk_is_rotating_ = false;
|
||||||
|
Cycles time_until_motor_transition;
|
||||||
|
void set_disk_is_rotating(bool);
|
||||||
|
|
||||||
// Current state of the index pulse output.
|
// Current state of the index pulse output.
|
||||||
Cycles index_pulse_remaining_;
|
Cycles index_pulse_remaining_;
|
||||||
@@ -229,8 +241,10 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
|||||||
PCMSegment write_segment_;
|
PCMSegment write_segment_;
|
||||||
Time write_start_time_;
|
Time write_start_time_;
|
||||||
|
|
||||||
// Indicates progress towards drive ready state.
|
// Indicates progress towards Shugart-style drive ready states.
|
||||||
int ready_index_count_ = 0;
|
int ready_index_count_ = 0;
|
||||||
|
ReadyType ready_type_;
|
||||||
|
bool is_ready_ = false;
|
||||||
|
|
||||||
// Maintains appropriate counting to know when to indicate that writing
|
// Maintains appropriate counting to know when to indicate that writing
|
||||||
// is complete.
|
// is complete.
|
||||||
|
@@ -14,18 +14,19 @@
|
|||||||
// just return a copy of that segment.
|
// just return a copy of that segment.
|
||||||
Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track, Time length_of_a_bit) {
|
Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track, Time length_of_a_bit) {
|
||||||
unsigned int history_size = 16;
|
unsigned int history_size = 16;
|
||||||
DigitalPhaseLockedLoop pll(100, history_size);
|
|
||||||
std::unique_ptr<Track> track_copy(track.clone());
|
std::unique_ptr<Track> track_copy(track.clone());
|
||||||
|
|
||||||
// ResultAccumulator exists to append whatever comes out of the PLL to
|
// ResultAccumulator exists to append whatever comes out of the PLL to
|
||||||
// its PCMSegment.
|
// its PCMSegment.
|
||||||
struct ResultAccumulator: public DigitalPhaseLockedLoop::Delegate {
|
struct ResultAccumulator {
|
||||||
PCMSegment result;
|
PCMSegment result;
|
||||||
|
bool is_recording = false;
|
||||||
void digital_phase_locked_loop_output_bit(int value) {
|
void digital_phase_locked_loop_output_bit(int value) {
|
||||||
result.data.push_back(!!value);
|
if(is_recording) result.data.push_back(!!value);
|
||||||
}
|
}
|
||||||
} result_accumulator;
|
} result_accumulator;
|
||||||
result_accumulator.result.length_of_a_bit = length_of_a_bit;
|
result_accumulator.result.length_of_a_bit = length_of_a_bit;
|
||||||
|
DigitalPhaseLockedLoop<ResultAccumulator> pll(100, result_accumulator);
|
||||||
|
|
||||||
// Obtain a length multiplier which is 100 times the reciprocal
|
// Obtain a length multiplier which is 100 times the reciprocal
|
||||||
// of the expected bit length. So a perfect bit length from
|
// of the expected bit length. So a perfect bit length from
|
||||||
@@ -55,7 +56,7 @@ Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track,
|
|||||||
if(!history_size) {
|
if(!history_size) {
|
||||||
track_copy->seek_to(Time(0));
|
track_copy->seek_to(Time(0));
|
||||||
time_error.set_zero();
|
time_error.set_zero();
|
||||||
pll.set_delegate(&result_accumulator);
|
result_accumulator.is_recording = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@
|
|||||||
#define FileHolder_hpp
|
#define FileHolder_hpp
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <array>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -126,6 +127,11 @@ class FileHolder final {
|
|||||||
/*! Reads @c size bytes and returns them as a vector. */
|
/*! Reads @c size bytes and returns them as a vector. */
|
||||||
std::vector<uint8_t> read(std::size_t size);
|
std::vector<uint8_t> read(std::size_t size);
|
||||||
|
|
||||||
|
/*! Reads @c a.size() bytes into @c a.data(). */
|
||||||
|
template <size_t size> std::size_t read(std::array<uint8_t, size> &a) {
|
||||||
|
return read(a.data(), a.size());
|
||||||
|
}
|
||||||
|
|
||||||
/*! Reads @c size bytes and writes them to @c buffer. */
|
/*! Reads @c size bytes and writes them to @c buffer. */
|
||||||
std::size_t read(uint8_t *buffer, std::size_t size);
|
std::size_t read(uint8_t *buffer, std::size_t size);
|
||||||
|
|
||||||
|
@@ -66,13 +66,11 @@ void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
|||||||
|
|
||||||
|
|
||||||
Shifter::Shifter() :
|
Shifter::Shifter() :
|
||||||
pll_(PLLClockRate / 4800, 15),
|
pll_(PLLClockRate / 4800, *this),
|
||||||
was_high_(false),
|
was_high_(false),
|
||||||
input_pattern_(0),
|
input_pattern_(0),
|
||||||
input_bit_counter_(0),
|
input_bit_counter_(0),
|
||||||
delegate_(nullptr) {
|
delegate_(nullptr) {}
|
||||||
pll_.set_delegate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Shifter::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
void Shifter::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||||
pll_.run_for(Cycles(static_cast<int>(static_cast<float>(PLLClockRate) * pulse.length.get<float>())));
|
pll_.run_for(Cycles(static_cast<int>(static_cast<float>(PLLClockRate) * pulse.length.get<float>())));
|
||||||
|
@@ -17,7 +17,7 @@ namespace Storage {
|
|||||||
namespace Tape {
|
namespace Tape {
|
||||||
namespace Acorn {
|
namespace Acorn {
|
||||||
|
|
||||||
class Shifter: public Storage::DigitalPhaseLockedLoop::Delegate {
|
class Shifter {
|
||||||
public:
|
public:
|
||||||
Shifter();
|
Shifter();
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ class Shifter: public Storage::DigitalPhaseLockedLoop::Delegate {
|
|||||||
void digital_phase_locked_loop_output_bit(int value);
|
void digital_phase_locked_loop_output_bit(int value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Storage::DigitalPhaseLockedLoop pll_;
|
Storage::DigitalPhaseLockedLoop<Shifter, 15> pll_;
|
||||||
bool was_high_;
|
bool was_high_;
|
||||||
|
|
||||||
unsigned int input_pattern_;
|
unsigned int input_pattern_;
|
||||||
|
Reference in New Issue
Block a user