mirror of
https://github.com/TomHarte/CLK.git
synced 2025-09-26 01:16:29 +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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 = {
|
||||
0x0228, 0x022b,
|
||||
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
|
||||
};
|
||||
|
||||
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 = {
|
||||
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
|
||||
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
|
||||
};
|
||||
|
||||
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.
|
||||
*/
|
||||
@@ -100,17 +102,22 @@ static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
|
||||
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;
|
||||
disassemble it to test it for validity.
|
||||
Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
|
||||
use disassembly to test for likely matches.
|
||||
*/
|
||||
|
||||
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
|
||||
if(!sector) return false;
|
||||
if(sector->samples.empty()) return false;
|
||||
|
||||
const std::vector<uint8_t> &first_sample = sector->samples[0];
|
||||
if(first_sample.size() != 256) return false;
|
||||
// Take a copy of the first sampling, and keep only the final 256 bytes (assuming at least that many were found).
|
||||
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.
|
||||
const auto disassembly =
|
||||
@@ -120,14 +127,24 @@ static bool IsJasmin(Storage::Encodings::MFM::Parser &parser) {
|
||||
int register_hits = 0;
|
||||
for(auto list : {disassembly.external_stores, disassembly.external_loads, disassembly.external_modifies}) {
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->machine = Machine::Oric;
|
||||
@@ -146,9 +163,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
|
||||
const Analyser::Static::MOS6502::Disassembly disassembly =
|
||||
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
|
||||
|
||||
int basic10_score = Basic10Score(disassembly);
|
||||
int basic11_score = Basic11Score(disassembly);
|
||||
if(basic10_score > basic11_score) basic10_votes++; else basic11_votes++;
|
||||
if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,17 +173,22 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
|
||||
}
|
||||
|
||||
if(!media.disks.empty()) {
|
||||
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc and
|
||||
// Jasmin formats here.
|
||||
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
|
||||
// Jasmin and BD-DOS formats here.
|
||||
for(auto &disk: media.disks) {
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
if(IsMicrodisc(parser)) {
|
||||
|
||||
if(is_microdisc(parser)) {
|
||||
target->disk_interface = Target::DiskInterface::Microdisc;
|
||||
target->media.disks.push_back(disk);
|
||||
} else if(IsJasmin(parser)) {
|
||||
} else if(is_jasmin(parser)) {
|
||||
target->disk_interface = Target::DiskInterface::Jasmin;
|
||||
target->should_start_jasmin = true;
|
||||
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,
|
||||
Pravetz,
|
||||
Jasmin,
|
||||
BD500,
|
||||
None
|
||||
};
|
||||
|
||||
|
@@ -47,6 +47,7 @@
|
||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||
|
||||
// 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("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
||||
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::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||
|
@@ -336,6 +336,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 6) {
|
||||
LOG("Nothing found to verify");
|
||||
update_status([] (Status &status) {
|
||||
status.seek_error = true;
|
||||
});
|
||||
@@ -823,6 +824,10 @@ void WD1770::set_head_loaded(bool head_loaded) {
|
||||
if(head_loaded) posit_event(int(Event1770::HeadLoad));
|
||||
}
|
||||
|
||||
bool WD1770::get_head_loaded() {
|
||||
return head_is_loaded_;
|
||||
}
|
||||
|
||||
ClockingHint::Preference WD1770::preferred_clocking() {
|
||||
if(status_.busy) return ClockingHint::Preference::RealTime;
|
||||
return Storage::Disk::MFMController::preferred_clocking();
|
||||
|
@@ -80,6 +80,9 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
virtual void set_motor_on(bool motor_on);
|
||||
void set_head_loaded(bool head_loaded);
|
||||
|
||||
/// @returns The last value posted to @c set_head_loaded.
|
||||
bool get_head_loaded();
|
||||
|
||||
private:
|
||||
Personality personality_;
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
select_paged_item();
|
||||
}
|
||||
|
||||
void Jasmin::write(int address, uint8_t value) {
|
||||
@@ -40,23 +32,20 @@ void Jasmin::write(int address, uint8_t value) {
|
||||
|
||||
case 0x3fa: {
|
||||
// If b0, enable overlay RAM.
|
||||
posit_paging_flags((paging_flags_ & BASICDisable) | ((value & 1) ? OverlayRAMEnable : 0));
|
||||
enable_overlay_ram_ = value & 1;
|
||||
select_paged_item();
|
||||
} break;
|
||||
|
||||
case 0x3fb:
|
||||
// If b0, disable BASIC ROM.
|
||||
posit_paging_flags((paging_flags_ & OverlayRAMEnable) | ((value & 1) ? BASICDisable : 0));
|
||||
disable_basic_rom_ = value & 1;
|
||||
select_paged_item();
|
||||
break;
|
||||
|
||||
case 0x3fc: case 0x3fd: case 0x3fe: case 0x3ff: {
|
||||
const size_t new_selected_drive = size_t(address - 0x3fc);
|
||||
|
||||
if(new_selected_drive != selected_drive_) {
|
||||
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_);
|
||||
}
|
||||
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(false);
|
||||
select_drive(size_t(address - 0x3fc));
|
||||
if(drives_[selected_drive_]) drives_[selected_drive_]->set_motor_on(motor_on_);
|
||||
} break;
|
||||
|
||||
default:
|
||||
@@ -67,4 +56,15 @@ void Jasmin::write(int address, uint8_t value) {
|
||||
void Jasmin::set_motor_on(bool on) {
|
||||
motor_on_ = 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 "../../Activity/Observer.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include "DiskController.hpp"
|
||||
|
||||
namespace Oric {
|
||||
|
||||
class Jasmin: public WD::WD1770 {
|
||||
class Jasmin: public DiskController {
|
||||
public:
|
||||
Jasmin();
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
||||
void write(int address, uint8_t value);
|
||||
|
||||
enum PagingFlags {
|
||||
/// 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_; }
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
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;
|
||||
bool motor_on_ = false;
|
||||
|
||||
Activity::Observer *observer_ = nullptr;
|
||||
};
|
||||
|
||||
};
|
||||
|
@@ -18,20 +18,10 @@ namespace {
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
const uint8_t changes = 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)
|
||||
// b1: ROM disable (0 = disable)
|
||||
if(changes & 0x82) {
|
||||
paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodiscDisable : 0);
|
||||
if(delegate_) delegate_->microdisc_did_change_paging_flags(this);
|
||||
enable_overlay_ram_ = control & 0x80;
|
||||
disable_basic_rom_ = !(control & 0x02);
|
||||
select_paged_item();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,23 +112,10 @@ void Microdisc::run_for(const Cycles cycles) {
|
||||
WD::WD1770::run_for(cycles);
|
||||
}
|
||||
|
||||
bool Microdisc::get_drive_is_ready() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Microdisc::set_activity_observer(Activity::Observer *observer) {
|
||||
observer_ = observer;
|
||||
if(observer) {
|
||||
observer->register_led("Microdisc");
|
||||
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 "../../Activity/Observer.hpp"
|
||||
|
||||
#include <array>
|
||||
#include "DiskController.hpp"
|
||||
|
||||
namespace Oric {
|
||||
|
||||
class Microdisc: public WD::WD1770 {
|
||||
class Microdisc: public DiskController {
|
||||
public:
|
||||
Microdisc();
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
||||
void set_control_register(uint8_t control);
|
||||
uint8_t get_interrupt_request_register();
|
||||
uint8_t get_data_request_register();
|
||||
@@ -29,42 +27,19 @@ class Microdisc: public WD::WD1770 {
|
||||
|
||||
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);
|
||||
|
||||
private:
|
||||
void set_control_register(uint8_t control, uint8_t changes);
|
||||
void set_head_load_request(bool head_load) override;
|
||||
bool get_drive_is_ready();
|
||||
void set_head_load_request(bool head_load) final;
|
||||
|
||||
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
|
||||
size_t selected_drive_;
|
||||
void set_control_register(uint8_t control, uint8_t changes);
|
||||
uint8_t last_control_ = 0;
|
||||
bool irq_enable_ = false;
|
||||
int paging_flags_ = BASICDisable;
|
||||
|
||||
Cycles::IntType head_load_request_counter_ = -1;
|
||||
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 "BD500.hpp"
|
||||
#include "Jasmin.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "Microdisc.hpp"
|
||||
@@ -223,8 +224,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Microdisc::Delegate,
|
||||
public Jasmin::Delegate,
|
||||
public DiskController::Delegate,
|
||||
public ClockingHint::Observer,
|
||||
public Activity::Source,
|
||||
public Machine,
|
||||
@@ -244,7 +244,16 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
speaker_.set_input_rate(1000000.0f);
|
||||
via_port_handler_.set_interrupt_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_));
|
||||
for(size_t c = 0; c < sizeof(ram_); ++c) {
|
||||
ram_[c] |= 0x40;
|
||||
}
|
||||
|
||||
if constexpr (disk_interface == DiskInterface::Pravetz) {
|
||||
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;
|
||||
switch(disk_interface) {
|
||||
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:
|
||||
rom_names.emplace_back(machine_name, "the ORIC Jasmin ROM", "jasmin.rom", 2*1024, 0x37220e89);
|
||||
break;
|
||||
@@ -293,13 +305,17 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
case DiskInterface::BD500:
|
||||
disk_rom_ = std::move(*roms[2]);
|
||||
disk_rom_.resize(8192);
|
||||
break;
|
||||
case DiskInterface::Jasmin:
|
||||
jasmin_rom_ = std::move(*roms[2]);
|
||||
jasmin_rom_.resize(2048);
|
||||
disk_rom_ = std::move(*roms[2]);
|
||||
disk_rom_.resize(2048);
|
||||
break;
|
||||
case DiskInterface::Microdisc:
|
||||
microdisc_rom_ = std::move(*roms[2]);
|
||||
microdisc_rom_.resize(8192);
|
||||
disk_rom_ = std::move(*roms[2]);
|
||||
disk_rom_.resize(8192);
|
||||
break;
|
||||
case DiskInterface::Pravetz: {
|
||||
pravetz_rom_ = std::move(*roms[2]);
|
||||
@@ -314,14 +330,15 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
|
||||
switch(target.disk_interface) {
|
||||
default: break;
|
||||
case DiskInterface::Microdisc:
|
||||
microdisc_did_change_paging_flags(µdisc_);
|
||||
microdisc_.set_delegate(this);
|
||||
case DiskInterface::BD500:
|
||||
bd500_.set_delegate(this);
|
||||
break;
|
||||
case DiskInterface::Jasmin:
|
||||
jasmin_did_change_paging_flags(&jasmin_);
|
||||
jasmin_.set_delegate(this);
|
||||
break;
|
||||
case DiskInterface::Microdisc:
|
||||
microdisc_.set_delegate(this);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!target.loading_command.empty()) {
|
||||
@@ -353,7 +370,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
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) {
|
||||
m6502_.set_nmi_line(is_pressed);
|
||||
} 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();
|
||||
}
|
||||
|
||||
@@ -380,7 +397,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
return true;
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override final {
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
bool inserted = false;
|
||||
|
||||
if(!media.tapes.empty()) {
|
||||
@@ -390,16 +407,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
|
||||
if(!media.disks.empty()) {
|
||||
switch(disk_interface) {
|
||||
case DiskInterface::Jasmin:
|
||||
inserted |= insert_disks(media, jasmin_, 4);
|
||||
break;
|
||||
case DiskInterface::Microdisc: {
|
||||
inserted |= insert_disks(media, microdisc_, 4);
|
||||
} break;
|
||||
case DiskInterface::Pravetz: {
|
||||
inserted |= insert_disks(media, diskii_, 2);
|
||||
} break;
|
||||
|
||||
case DiskInterface::BD500: inserted |= insert_disks(media, bd500_, 4); break;
|
||||
case DiskInterface::Jasmin: inserted |= insert_disks(media, jasmin_, 4); break;
|
||||
case DiskInterface::Microdisc: inserted |= insert_disks(media, microdisc_, 4); break;
|
||||
case DiskInterface::Pravetz: inserted |= insert_disks(media, diskii_, 2); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -434,6 +445,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
} else {
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
case DiskInterface::BD500:
|
||||
if(isReadOperation(operation)) *value = bd500_.read(address);
|
||||
else bd500_.write(address, *value);
|
||||
break;
|
||||
case DiskInterface::Jasmin:
|
||||
if(address >= 0x3f4) {
|
||||
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));
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
case DiskInterface::BD500:
|
||||
bd500_.run_for(Cycles(9)); // i.e. effective clock rate of 9Mhz.
|
||||
break;
|
||||
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
|
||||
// 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;
|
||||
case DiskInterface::Microdisc:
|
||||
microdisc_.run_for(Cycles(8));
|
||||
microdisc_.run_for(Cycles(8));; // i.e. effective clock rate of 8Mhz.
|
||||
break;
|
||||
case DiskInterface::Pravetz:
|
||||
if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
|
||||
diskii_.set_data_input(*value);
|
||||
diskii_.run_for(Cycles(2));
|
||||
diskii_.run_for(Cycles(2));; // i.e. effective clock rate of 2Mhz.
|
||||
} else {
|
||||
cycles_since_diskii_update_ += Cycles(2);
|
||||
}
|
||||
@@ -534,74 +552,53 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
void run_for(const Cycles cycles) final {
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, !tape_player->get_input());
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// for Microdisc::Delegate
|
||||
void microdisc_did_change_paging_flags(class Microdisc *microdisc) override final {
|
||||
const int flags = microdisc->get_paging_flags();
|
||||
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.
|
||||
// DiskController::Delegate
|
||||
void disk_controller_did_change_paged_item(DiskController *controller) final {
|
||||
switch(controller->get_paged_item()) {
|
||||
default:
|
||||
ram_top_ = basic_visible_ram_top_;
|
||||
paged_rom_ = rom_.data();
|
||||
break;
|
||||
|
||||
// Overlay RAM enabled, with or without BASIC.
|
||||
case Jasmin::OverlayRAMEnable:
|
||||
case Jasmin::OverlayRAMEnable | Jasmin::BASICDisable:
|
||||
case DiskController::PagedItem::RAM:
|
||||
ram_top_ = basic_invisible_ram_top_;
|
||||
break;
|
||||
|
||||
// BASIC disabled, overlay disabled.
|
||||
case Jasmin::BASICDisable:
|
||||
ram_top_ = 0xf7ff;
|
||||
paged_rom_ = jasmin_rom_.data();
|
||||
case DiskController::PagedItem::DiskROM:
|
||||
ram_top_ = uint16_t(0xffff - disk_rom_.size());
|
||||
paged_rom_ = disk_rom_.data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -649,6 +646,12 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
case DiskInterface::BD500:
|
||||
bd500_.set_activity_observer(observer);
|
||||
break;
|
||||
case DiskInterface::Jasmin:
|
||||
jasmin_.set_activity_observer(observer);
|
||||
break;
|
||||
case DiskInterface::Microdisc:
|
||||
microdisc_.set_activity_observer(observer);
|
||||
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_;
|
||||
|
||||
// RAM and ROM
|
||||
std::vector<uint8_t> rom_, microdisc_rom_, jasmin_rom_;
|
||||
std::vector<uint8_t> rom_, disk_rom_;
|
||||
uint8_t ram_[65536];
|
||||
Cycles cycles_since_video_update_;
|
||||
inline void update_video() {
|
||||
@@ -705,6 +708,9 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
Jasmin jasmin_;
|
||||
int jasmin_reset_counter_ = 0;
|
||||
|
||||
// the BD-500, if in use.
|
||||
BD500 bd500_;
|
||||
|
||||
// the Pravetz/Disk II, if in use.
|
||||
Apple::DiskII diskii_;
|
||||
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::Pravetz: return new ConcreteMachine<DiskInterface::Pravetz>(*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 */; };
|
||||
4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.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 */; };
|
||||
4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.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 */; };
|
||||
4B4518851F75E91A00926311 /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45187A1F75E91900926311 /* DiskController.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 */; };
|
||||
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B45188F1F75FD1B00926311 /* CPCDSK.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 */; };
|
||||
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.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 */; };
|
||||
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.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 */; };
|
||||
4B7BA03023C2B19C00B98D9E /* 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 */; };
|
||||
4B7F188E2154825E00388727 /* 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1182,6 +1180,11 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -2235,7 +2238,6 @@
|
||||
4B45187E1F75E91900926311 /* DPLL */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B45187F1F75E91900926311 /* DigitalPhaseLockedLoop.cpp */,
|
||||
4B4518801F75E91900926311 /* DigitalPhaseLockedLoop.hpp */,
|
||||
);
|
||||
path = DPLL;
|
||||
@@ -2269,6 +2271,7 @@
|
||||
4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */,
|
||||
4B4518991F75FD1B00926311 /* SSD.cpp */,
|
||||
4BE0A3EC237BB170002AB46F /* ST.cpp */,
|
||||
4B7BA03323C58B1E00B98D9E /* STX.cpp */,
|
||||
4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */,
|
||||
4B45188E1F75FD1B00926311 /* AcornADF.hpp */,
|
||||
4B0333AE2094081A0050B93D /* AppleDSK.hpp */,
|
||||
@@ -2285,6 +2288,7 @@
|
||||
4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */,
|
||||
4B45189A1F75FD1B00926311 /* SSD.hpp */,
|
||||
4BE0A3ED237BB170002AB46F /* ST.hpp */,
|
||||
4B7BA03223C58B1E00B98D9E /* STX.hpp */,
|
||||
4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */,
|
||||
4BFDD7891F7F2DB4008579B9 /* Utility */,
|
||||
);
|
||||
@@ -3533,16 +3537,19 @@
|
||||
4BCF1FA51DADC3E10039D2E7 /* Oric */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B7BA03523CEB86000B98D9E /* BD500.cpp */,
|
||||
4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */,
|
||||
4B54C0BD1F8D8F450050900F /* Keyboard.cpp */,
|
||||
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */,
|
||||
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */,
|
||||
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */,
|
||||
4B7BA03623CEB86000B98D9E /* BD500.hpp */,
|
||||
4B7BA02F23C2B19B00B98D9E /* Jasmin.hpp */,
|
||||
4B54C0BE1F8D8F450050900F /* Keyboard.hpp */,
|
||||
4B5FADBF1DE3BF2B00AEC565 /* Microdisc.hpp */,
|
||||
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */,
|
||||
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */,
|
||||
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */,
|
||||
);
|
||||
name = Oric;
|
||||
sourceTree = "<group>";
|
||||
@@ -3838,6 +3845,7 @@
|
||||
};
|
||||
4BB73E9D1B587A5100552FC2 = {
|
||||
CreatedOnToolsVersion = 7.0;
|
||||
DevelopmentTeam = CP2SKEB3XT;
|
||||
LastSwiftMigration = 1020;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
@@ -4214,7 +4222,6 @@
|
||||
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
|
||||
4B0ACC3123775819008902D0 /* TIASound.cpp in Sources */,
|
||||
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
|
||||
4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */,
|
||||
4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */,
|
||||
4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
|
||||
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */,
|
||||
@@ -4456,6 +4463,7 @@
|
||||
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
|
||||
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
|
||||
4B7BA03723CEB86000B98D9E /* BD500.cpp in Sources */,
|
||||
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
|
||||
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
|
||||
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,
|
||||
@@ -4541,8 +4549,8 @@
|
||||
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
||||
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
||||
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
||||
4B7BA03423C58B1F00B98D9E /* STX.cpp in Sources */,
|
||||
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */,
|
||||
4B4518871F75E91A00926311 /* DigitalPhaseLockedLoop.cpp in Sources */,
|
||||
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */,
|
||||
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */,
|
||||
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */,
|
||||
@@ -4642,7 +4650,6 @@
|
||||
4B778F0E23A5EC4F0000D260 /* Tape.cpp in Sources */,
|
||||
4B778F2D23A5EF190000D260 /* MFMDiskController.cpp in Sources */,
|
||||
4B778F2723A5EEF60000D260 /* BinaryDump.cpp in Sources */,
|
||||
4B778EF223A5DB100000D260 /* DigitalPhaseLockedLoop.cpp in Sources */,
|
||||
4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */,
|
||||
4B778F5223A5F22F0000D260 /* StaticAnalyser.cpp in Sources */,
|
||||
4B778F4923A5F1F40000D260 /* StaticAnalyser.cpp in Sources */,
|
||||
@@ -5049,6 +5056,8 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = CP2SKEB3XT;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||
@@ -5091,6 +5100,8 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = CP2SKEB3XT;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||
|
@@ -67,7 +67,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
|
@@ -394,6 +394,7 @@
|
||||
<array>
|
||||
<string>msa</string>
|
||||
<string>st</string>
|
||||
<string>stx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35.png</string>
|
||||
|
@@ -50,7 +50,8 @@ typedef NS_ENUM(NSInteger, CSMachineOricDiskInterface) {
|
||||
CSMachineOricDiskInterfaceNone,
|
||||
CSMachineOricDiskInterfaceMicrodisc,
|
||||
CSMachineOricDiskInterfacePravetz,
|
||||
CSMachineOricDiskInterfaceJasmin
|
||||
CSMachineOricDiskInterfaceJasmin,
|
||||
CSMachineOricDiskInterfaceBD500
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineVic20Region) {
|
||||
|
@@ -103,6 +103,7 @@
|
||||
case CSMachineOricDiskInterfaceMicrodisc: target->disk_interface = Target::DiskInterface::Microdisc; break;
|
||||
case CSMachineOricDiskInterfacePravetz: target->disk_interface = Target::DiskInterface::Pravetz; 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));
|
||||
}
|
||||
|
@@ -331,7 +331,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<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">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -341,6 +341,7 @@ Gw
|
||||
<menuItem title="Microdisc" tag="1" id="1jS-Lz-FRj"/>
|
||||
<menuItem title="Jasmin" tag="3" id="CGU-gd-xov"/>
|
||||
<menuItem title="8DOS" tag="2" id="edb-fl-C8Y"/>
|
||||
<menuItem title="Byte Drive 500" tag="4" id="lkS-Rr-m1D"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
|
@@ -196,6 +196,7 @@ class MachinePicker: NSObject {
|
||||
case 1: diskInterface = .microdisc
|
||||
case 2: diskInterface = .pravetz
|
||||
case 3: diskInterface = .jasmin
|
||||
case 4: diskInterface = .BD500
|
||||
default: break;
|
||||
|
||||
}
|
||||
|
@@ -261,6 +261,32 @@ class CPU::MC68000::ProcessorStorageTests {
|
||||
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 {
|
||||
// Perform an audit of implemented instructions.
|
||||
CPU::MC68000::ProcessorStorageTests storage_tests(
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
@interface DigitalPhaseLockedLoopBridge : NSObject
|
||||
|
||||
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit historyLength:(NSUInteger)historyLength;
|
||||
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit;
|
||||
|
||||
- (void)runForCycles:(NSUInteger)cycles;
|
||||
- (void)addPulse;
|
||||
|
@@ -15,7 +15,7 @@
|
||||
- (void)pushBit:(int)value;
|
||||
@end
|
||||
|
||||
class DigitalPhaseLockedLoopDelegate: public Storage::DigitalPhaseLockedLoop::Delegate {
|
||||
class DigitalPhaseLockedLoopDelegate {
|
||||
public:
|
||||
__weak DigitalPhaseLockedLoopBridge *bridge;
|
||||
|
||||
@@ -25,14 +25,14 @@ class DigitalPhaseLockedLoopDelegate: public Storage::DigitalPhaseLockedLoop::De
|
||||
};
|
||||
|
||||
@implementation DigitalPhaseLockedLoopBridge {
|
||||
std::unique_ptr<Storage::DigitalPhaseLockedLoop> _digitalPhaseLockedLoop;
|
||||
std::unique_ptr<Storage::DigitalPhaseLockedLoop<DigitalPhaseLockedLoopDelegate>> _digitalPhaseLockedLoop;
|
||||
DigitalPhaseLockedLoopDelegate _delegate;
|
||||
}
|
||||
|
||||
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit historyLength:(NSUInteger)historyLength {
|
||||
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit {
|
||||
self = [super init];
|
||||
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;
|
||||
_digitalPhaseLockedLoop->set_delegate(&_delegate);
|
||||
}
|
||||
|
@@ -26,22 +26,22 @@ class DPLLTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testPerfectInput() {
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||
testRegularNibblesOnPLL(pll!, bitLength: 100)
|
||||
}
|
||||
|
||||
func testFastButRegular() {
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||
testRegularNibblesOnPLL(pll!, bitLength: 90)
|
||||
}
|
||||
|
||||
func testSlowButRegular() {
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||
testRegularNibblesOnPLL(pll!, bitLength: 110)
|
||||
}
|
||||
|
||||
func testTwentyPercentSinePattern() {
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, historyLength: 3)
|
||||
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100)
|
||||
var angle = 0.0
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
void reset_cycle_count() {
|
||||
duration_ = HalfCycles(0);
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000::Processor<RAM68000, true, true> m68000_;
|
||||
std::array<uint16_t, 256*1024> ram_{};
|
||||
|
@@ -5,6 +5,11 @@ Expected files:
|
||||
basic10.rom
|
||||
basic11.rom
|
||||
colour.rom
|
||||
microdisc.rom
|
||||
8dos.rom
|
||||
pravetz.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) :
|
||||
clock_rate_multiplier_(128000000 / clock_rate.as_integral()),
|
||||
clock_rate_(clock_rate.as_integral() * clock_rate_multiplier_),
|
||||
pll_(100, *this),
|
||||
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
|
||||
Time one(1);
|
||||
set_expected_bit_length(one);
|
||||
set_expected_bit_length(Time(1));
|
||||
set_drive(empty_drive_);
|
||||
}
|
||||
|
||||
@@ -42,13 +41,13 @@ Drive &Controller::get_drive() {
|
||||
|
||||
void Controller::process_event(const Drive::Event &event) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -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
|
||||
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
|
||||
int clocks_per_bit = cycles_per_bit.get<int>();
|
||||
pll_ = std::make_unique<DigitalPhaseLockedLoop>(clocks_per_bit, 3);
|
||||
pll_->set_delegate(this);
|
||||
const int clocks_per_bit = cycles_per_bit.get<int>();
|
||||
pll_.set_clocks_per_bit(clocks_per_bit);
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
class Controller:
|
||||
public DigitalPhaseLockedLoop::Delegate,
|
||||
public Drive::EventDelegate,
|
||||
public ClockingHint::Source,
|
||||
public ClockingHint::Observer {
|
||||
@@ -111,7 +110,9 @@ class Controller:
|
||||
|
||||
bool is_reading_ = true;
|
||||
|
||||
std::shared_ptr<DigitalPhaseLockedLoop> pll_;
|
||||
DigitalPhaseLockedLoop<Controller> pll_;
|
||||
friend DigitalPhaseLockedLoop<Controller>;
|
||||
|
||||
std::shared_ptr<Drive> drive_;
|
||||
|
||||
std::shared_ptr<Drive> empty_drive_;
|
||||
@@ -124,7 +125,7 @@ class Controller:
|
||||
void advance(const Cycles cycles) override ;
|
||||
|
||||
// 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
|
||||
#define DigitalPhaseLockedLoop_hpp
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -16,54 +18,117 @@
|
||||
|
||||
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:
|
||||
/*!
|
||||
Instantiates a @c DigitalPhaseLockedLoop.
|
||||
|
||||
@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.
|
||||
|
||||
@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.
|
||||
*/
|
||||
void add_pulse();
|
||||
|
||||
/*!
|
||||
A receiver for PCM output data; called upon every recognised bit.
|
||||
*/
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void digital_phase_locked_loop_output_bit(int value) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
void add_pulse() {
|
||||
if(!window_was_filled_) {
|
||||
bit_handler_.digital_phase_locked_loop_output_bit(1);
|
||||
window_was_filled_ = true;
|
||||
post_phase_offset(phase_, offset_);
|
||||
offset_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
Cycles::IntType offset_ = 0;
|
||||
|
||||
Cycles::IntType total_spacing_ = length_of_history;
|
||||
Cycles::IntType total_divisor_ = length_of_history;
|
||||
|
||||
Cycles::IntType phase_ = 0;
|
||||
Cycles::IntType window_length_ = 0;
|
||||
|
||||
Cycles::IntType offset_ = 0;
|
||||
bool window_was_filled_ = false;
|
||||
|
||||
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++) {
|
||||
last_header[byte] = file_.get8();
|
||||
encoder->add_byte(last_header[byte]);
|
||||
track_offset++;
|
||||
++track_offset;
|
||||
if(track_offset == 6250) break;
|
||||
}
|
||||
break;
|
||||
@@ -85,8 +85,12 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address)
|
||||
case 0xfb:
|
||||
for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) {
|
||||
encoder->add_byte(file_.get8());
|
||||
track_offset++;
|
||||
if(track_offset == 6250) break;
|
||||
++track_offset;
|
||||
// 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;
|
||||
}
|
||||
|
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;
|
||||
|
||||
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),
|
||||
available_heads_(number_of_heads) {
|
||||
available_heads_(number_of_heads),
|
||||
ready_type_(rdy_type) {
|
||||
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());
|
||||
@@ -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) {
|
||||
// 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) {
|
||||
if(ready_type_ == ReadyType::ShugartModifiedRDY || ready_type_ == ReadyType::IBMRDY) {
|
||||
is_ready_ = false;
|
||||
}
|
||||
if(disk_) disk_->flush_tracks();
|
||||
disk_ = disk;
|
||||
has_disk_ = !!disk_;
|
||||
@@ -68,7 +72,7 @@ bool Drive::has_disk() const {
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -76,6 +80,10 @@ bool Drive::get_is_track_zero() const {
|
||||
}
|
||||
|
||||
void Drive::step(HeadPosition offset) {
|
||||
if(ready_type_ == ReadyType::IBMRDY) {
|
||||
is_ready_ = true;
|
||||
}
|
||||
|
||||
HeadPosition old_head_position = head_position_;
|
||||
head_position_ += offset;
|
||||
if(head_position_ < HeadPosition(0)) {
|
||||
@@ -142,30 +150,37 @@ bool Drive::get_is_read_only() const {
|
||||
}
|
||||
|
||||
bool Drive::get_is_ready() const {
|
||||
return ready_index_count_ == 2;
|
||||
return is_ready_;
|
||||
}
|
||||
|
||||
void Drive::set_motor_on(bool motor_is_on) {
|
||||
if(motor_is_on_ != motor_is_on) {
|
||||
motor_is_on_ = motor_is_on;
|
||||
// Do nothing if the input hasn't changed.
|
||||
if(motor_input_is_on_ == motor_is_on) return;
|
||||
motor_input_is_on_ = motor_is_on;
|
||||
|
||||
if(observer_) {
|
||||
observer_->set_drive_motor_status(drive_name_, motor_is_on_);
|
||||
if(announce_motor_led_) {
|
||||
observer_->set_led_status(drive_name_, motor_is_on_);
|
||||
}
|
||||
}
|
||||
|
||||
if(!motor_is_on) {
|
||||
ready_index_count_ = 0;
|
||||
if(disk_) disk_->flush_tracks();
|
||||
}
|
||||
update_clocking_observer();
|
||||
// If this now means that the input and the actual state are in harmony,
|
||||
// cancel any planned change and stop.
|
||||
if(disk_is_rotating_ == motor_is_on) {
|
||||
time_until_motor_transition = Cycles(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return motor_is_on_;
|
||||
return motor_input_is_on_;
|
||||
}
|
||||
|
||||
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.
|
||||
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_) {
|
||||
Time zero(0);
|
||||
|
||||
@@ -287,7 +311,10 @@ void Drive::get_next_event(float duration_already_passed) {
|
||||
|
||||
void Drive::process_next_event() {
|
||||
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;
|
||||
}
|
||||
if(
|
||||
@@ -400,6 +427,26 @@ bool Drive::is_writing() const {
|
||||
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) {
|
||||
observer_ = observer;
|
||||
announce_motor_led_ = add_motor_led;
|
||||
@@ -407,11 +454,11 @@ void Drive::set_activity_observer(Activity::Observer *observer, const std::strin
|
||||
drive_name_ = 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) {
|
||||
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 {
|
||||
public:
|
||||
Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads);
|
||||
Drive(int input_clock_rate, int number_of_heads);
|
||||
enum class ReadyType {
|
||||
/// 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();
|
||||
|
||||
/*!
|
||||
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);
|
||||
|
||||
@@ -75,7 +84,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
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;
|
||||
|
||||
@@ -213,7 +222,10 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
int available_heads_ = 0;
|
||||
|
||||
// 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.
|
||||
Cycles index_pulse_remaining_;
|
||||
@@ -229,8 +241,10 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
PCMSegment write_segment_;
|
||||
Time write_start_time_;
|
||||
|
||||
// Indicates progress towards drive ready state.
|
||||
// Indicates progress towards Shugart-style drive ready states.
|
||||
int ready_index_count_ = 0;
|
||||
ReadyType ready_type_;
|
||||
bool is_ready_ = false;
|
||||
|
||||
// Maintains appropriate counting to know when to indicate that writing
|
||||
// is complete.
|
||||
|
@@ -14,18 +14,19 @@
|
||||
// just return a copy of that segment.
|
||||
Storage::Disk::PCMSegment Storage::Disk::track_serialisation(const Track &track, Time length_of_a_bit) {
|
||||
unsigned int history_size = 16;
|
||||
DigitalPhaseLockedLoop pll(100, history_size);
|
||||
std::unique_ptr<Track> track_copy(track.clone());
|
||||
|
||||
// ResultAccumulator exists to append whatever comes out of the PLL to
|
||||
// its PCMSegment.
|
||||
struct ResultAccumulator: public DigitalPhaseLockedLoop::Delegate {
|
||||
struct ResultAccumulator {
|
||||
PCMSegment result;
|
||||
bool is_recording = false;
|
||||
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.length_of_a_bit = length_of_a_bit;
|
||||
DigitalPhaseLockedLoop<ResultAccumulator> pll(100, result_accumulator);
|
||||
|
||||
// Obtain a length multiplier which is 100 times the reciprocal
|
||||
// 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) {
|
||||
track_copy->seek_to(Time(0));
|
||||
time_error.set_zero();
|
||||
pll.set_delegate(&result_accumulator);
|
||||
result_accumulator.is_recording = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@
|
||||
#define FileHolder_hpp
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
@@ -126,6 +127,11 @@ class FileHolder final {
|
||||
/*! Reads @c size bytes and returns them as a vector. */
|
||||
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. */
|
||||
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() :
|
||||
pll_(PLLClockRate / 4800, 15),
|
||||
pll_(PLLClockRate / 4800, *this),
|
||||
was_high_(false),
|
||||
input_pattern_(0),
|
||||
input_bit_counter_(0),
|
||||
delegate_(nullptr) {
|
||||
pll_.set_delegate(this);
|
||||
}
|
||||
delegate_(nullptr) {}
|
||||
|
||||
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>())));
|
||||
|
@@ -17,7 +17,7 @@ namespace Storage {
|
||||
namespace Tape {
|
||||
namespace Acorn {
|
||||
|
||||
class Shifter: public Storage::DigitalPhaseLockedLoop::Delegate {
|
||||
class Shifter {
|
||||
public:
|
||||
Shifter();
|
||||
|
||||
@@ -34,7 +34,7 @@ class Shifter: public Storage::DigitalPhaseLockedLoop::Delegate {
|
||||
void digital_phase_locked_loop_output_bit(int value);
|
||||
|
||||
private:
|
||||
Storage::DigitalPhaseLockedLoop pll_;
|
||||
Storage::DigitalPhaseLockedLoop<Shifter, 15> pll_;
|
||||
bool was_high_;
|
||||
|
||||
unsigned int input_pattern_;
|
||||
|
Reference in New Issue
Block a user