1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 23:52:26 +00:00

Merge pull request #241 from TomHarte/DriveEvents

Devolves `TimedEventLoop` ownership from disk controllers to drives
This commit is contained in:
Thomas Harte 2017-09-15 19:15:44 -04:00 committed by GitHub
commit b835cb73e2
24 changed files with 646 additions and 383 deletions

View File

@ -25,7 +25,7 @@ WD1770::Status::Status() :
busy(false) {}
WD1770::WD1770(Personality p) :
Storage::Disk::MFMController(8000000, 16, 300),
Storage::Disk::MFMController(8000000),
interesting_event_mask_((int)Event1770::Command),
resume_point_(0),
delay_time_(0),
@ -75,7 +75,7 @@ uint8_t WD1770::get_register(int address) {
switch(status_.type) {
case Status::One:
status |=
(get_is_track_zero() ? Flag::TrackZero : 0) |
(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) |
(status_.seek_error ? Flag::SeekError : 0);
// TODO: index hole
break;
@ -91,11 +91,11 @@ uint8_t WD1770::get_register(int address) {
}
if(!has_motor_on_line()) {
status |= get_drive_is_ready() ? 0 : Flag::NotReady;
status |= get_drive().get_is_ready() ? 0 : Flag::NotReady;
if(status_.type == Status::One)
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
} else {
status |= (get_motor_on() ? Flag::MotorOn : 0);
status |= (get_drive().get_motor_on() ? Flag::MotorOn : 0);
if(status_.type == Status::One)
status |= (status_.spin_up ? Flag::SpinUp : 0);
}
@ -257,7 +257,7 @@ void WD1770::posit_event(int new_event_type) {
goto test_type1_type;
begin_type1_spin_up:
if((command_&0x08) || get_motor_on()) goto test_type1_type;
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
SPIN_UP();
test_type1_type:
@ -280,11 +280,11 @@ void WD1770::posit_event(int new_event_type) {
if(step_direction_) track_++; else track_--;
perform_step:
if(!step_direction_ && get_is_track_zero()) {
if(!step_direction_ && get_drive().get_is_track_zero()) {
track_ = 0;
goto verify;
}
step(step_direction_ ? 1 : -1);
get_drive().step(step_direction_ ? 1 : -1);
unsigned int time_to_wait;
switch(command_ & 3) {
default:
@ -376,7 +376,7 @@ void WD1770::posit_event(int new_event_type) {
goto test_type2_delay;
begin_type2_spin_up:
if(get_motor_on()) goto test_type2_delay;
if(get_drive().get_motor_on()) goto test_type2_delay;
// Perform spin up.
SPIN_UP();
@ -386,7 +386,7 @@ void WD1770::posit_event(int new_event_type) {
WAIT_FOR_TIME(30);
test_type2_write_protection:
if(command_&0x20 && get_drive_is_read_only()) {
if(command_&0x20 && get_drive().get_is_read_only()) {
update_status([] (Status &status) {
status.write_protect = true;
});
@ -594,7 +594,7 @@ void WD1770::posit_event(int new_event_type) {
goto type3_test_delay;
begin_type3_spin_up:
if((command_&0x08) || get_motor_on()) goto type3_test_delay;
if((command_&0x08) || get_drive().get_motor_on()) goto type3_test_delay;
SPIN_UP();
type3_test_delay:
@ -675,7 +675,7 @@ void WD1770::posit_event(int new_event_type) {
});
write_track_test_write_protect:
if(get_drive_is_read_only()) {
if(get_drive().get_is_read_only()) {
update_status([] (Status &status) {
status.write_protect = true;
});
@ -781,6 +781,7 @@ void WD1770::update_status(std::function<void(Status &)> updater) {
}
void WD1770::set_head_load_request(bool head_load) {}
void WD1770::set_motor_on(bool motor_on) {}
void WD1770::set_head_loaded(bool head_loaded) {
head_is_loaded_ = head_loaded;

View File

@ -76,6 +76,7 @@ class WD1770: public Storage::Disk::MFMController {
protected:
virtual void set_head_load_request(bool head_load);
virtual void set_motor_on(bool motor_on);
void set_head_loaded(bool head_loaded);
private:

View File

@ -75,8 +75,8 @@ namespace {
const uint8_t CommandSenseDriveStatus = 0x04;
}
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute),
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
Storage::Disk::MFMController(clock_rate),
bus_handler_(bus_handler),
main_status_(0),
interesting_event_mask_((int)Event8272::CommandByte),
@ -119,11 +119,12 @@ void i8272::run_for(Cycles cycles) {
// Perform a step.
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
drives_[c].drive->step(direction);
select_drive(c);
get_drive().step(direction);
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
// Check for completion.
if(drives_[c].seek_is_satisfied()) {
if(seek_is_satisfied(c)) {
drives_[c].phase = Drive::CompletedSeeking;
drives_seeking_--;
break;
@ -192,12 +193,6 @@ uint8_t i8272::get_register(int address) {
}
}
void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(drive < 4 && drive >= 0) {
drives_[drive].drive->set_disk(disk);
}
}
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() }
@ -235,10 +230,9 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
#define SET_DRIVE_HEAD_MFM() \
active_drive_ = command_[1]&3; \
active_head_ = (command_[1] >> 2)&1; \
set_drive(drives_[active_drive_].drive); \
drives_[active_drive_].drive->set_head((unsigned int)active_head_); \
set_is_double_density(command_[0] & 0x40); \
invalidate_track();
select_drive(active_drive_); \
get_drive().set_head((unsigned int)active_head_); \
set_is_double_density(command_[0] & 0x40);
#define WAIT_FOR_BYTES(n) \
distance_into_section_ = 0; \
@ -341,6 +335,10 @@ void i8272::posit_event(int event_type) {
if(!dma_mode_) SetNonDMAExecution();
SET_DRIVE_HEAD_MFM();
LOAD_HEAD();
if(!get_drive().get_is_ready()) {
SetNotReady();
goto abort;
}
}
// Jump to the proper place.
@ -504,7 +502,7 @@ void i8272::posit_event(int event_type) {
write_data:
printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
if(drives_[active_drive_].drive->get_is_read_only()) {
if(get_drive().get_is_read_only()) {
SetNotWriteable();
goto abort;
}
@ -619,7 +617,7 @@ void i8272::posit_event(int event_type) {
// Performs format [/write] track.
format_track:
printf("Format track\n");
if(drives_[active_drive_].drive->get_is_read_only()) {
if(get_drive().get_is_read_only()) {
SetNotWriteable();
goto abort;
}
@ -712,6 +710,7 @@ void i8272::posit_event(int event_type) {
seek:
{
int drive = command_[1]&3;
select_drive(drive);
// Increment the seeking count if this drive wasn't already seeking.
if(drives_[drive].phase != Drive::Seeking) {
@ -741,7 +740,7 @@ void i8272::posit_event(int event_type) {
}
// Check whether any steps are even needed; if not then mark as completed already.
if(drives_[drive].seek_is_satisfied()) {
if(seek_is_satisfied(drive)) {
drives_[drive].phase = Drive::CompletedSeeking;
drives_seeking_--;
}
@ -793,12 +792,13 @@ void i8272::posit_event(int event_type) {
printf("Sense drive status\n");
{
int drive = command_[1] & 3;
select_drive(drive);
result_stack_.push_back(
(command_[1] & 7) | // drive and head number
0x08 | // single sided
(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) |
(drives_[drive].drive->get_is_ready() ? 0x20 : 0x00) |
(drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00)
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
(get_drive().get_is_ready() ? 0x20 : 0x00) |
(get_drive().get_is_read_only() ? 0x40 : 0x00)
);
}
goto post_result;
@ -853,9 +853,9 @@ void i8272::posit_event(int event_type) {
END_SECTION()
}
bool i8272::Drive::seek_is_satisfied() {
return (target_head_position == head_position) ||
(target_head_position == -1 && drive->get_is_track_zero());
bool i8272::seek_is_satisfied(int drive) {
return (drives_[drive].target_head_position == drives_[drive].head_position) ||
(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero());
}
void i8272::set_dma_acknowledge(bool dack) {

View File

@ -26,7 +26,7 @@ class BusHandler {
class i8272: public Storage::Disk::MFMController {
public:
i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
i8272(BusHandler &bus_handler, Cycles clock_rate);
void run_for(Cycles);
@ -39,10 +39,11 @@ class i8272: public Storage::Disk::MFMController {
void set_dma_acknowledge(bool dack);
void set_terminal_count(bool tc);
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
bool is_sleeping();
protected:
virtual void select_drive(int number) = 0;
private:
// The bus handler, for interrupt and DMA-driven usage.
BusHandler &bus_handler_;
@ -91,24 +92,20 @@ class i8272: public Storage::Disk::MFMController {
int steps_taken;
int target_head_position; // either an actual number, or -1 to indicate to step until track zero
/// @returns @c true if the currently queued-up seek or recalibrate has reached where it should be.
bool seek_is_satisfied();
// Head state.
int head_unload_delay[2];
bool head_is_loaded[2];
// The connected drive.
std::shared_ptr<Storage::Disk::Drive> drive;
Drive() :
head_position(0), phase(NotSeeking),
drive(new Storage::Disk::Drive),
head_is_loaded{false, false},
head_unload_delay{0, 0} {};
} drives_[4];
int drives_seeking_;
/// @returns @c true if the selected drive, which is number @c drive, can stop seeking.
bool seek_is_satisfied(int drive);
// User-supplied parameters; as per the specify command.
int step_rate_time_;
int head_unload_time_;

View File

@ -582,12 +582,25 @@ class KeyboardState: public GI::AY38910::PortHandler {
class FDC: public Intel::i8272::i8272 {
private:
Intel::i8272::BusHandler bus_handler_;
std::shared_ptr<Storage::Disk::Drive> drive_;
public:
FDC() : i8272(bus_handler_, Cycles(8000000), 16, 300) {}
FDC() :
i8272(bus_handler_, Cycles(8000000)),
drive_(new Storage::Disk::Drive(8000000, 300)) {
set_drive(drive_);
}
void set_motor_on(bool on) {
Intel::i8272::i8272::set_motor_on(on);
drive_->set_motor_on(on);
}
void select_drive(int c) {
// TODO: support more than one drive.
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
drive_->set_disk(disk);
}
};

View File

@ -18,11 +18,12 @@ using namespace Commodore::C1540;
MachineBase::MachineBase() :
m6502_(*this),
shift_register_(0),
Storage::Disk::Controller(1000000, 4, 300),
Storage::Disk::Controller(1000000),
serial_port_(new SerialPort),
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
drive_VIA_(drive_VIA_port_handler_),
serial_port_VIA_(*serial_port_VIA_port_handler_) {
serial_port_VIA_(*serial_port_VIA_port_handler_),
drive_(new Storage::Disk::Drive(1000000, 300)) {
// attach the serial port to its VIA and vice versa
serial_port_->set_serial_port_via(serial_port_VIA_port_handler_);
serial_port_VIA_port_handler_->set_serial_port(serial_port_);
@ -34,6 +35,9 @@ MachineBase::MachineBase() :
// set a bit rate
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
// attach the only drive there is
set_drive(drive_);
}
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
@ -82,16 +86,14 @@ void Machine::set_rom(const std::vector<uint8_t> &rom) {
}
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive);
drive->set_disk(disk);
set_drive(drive);
drive_->set_disk(disk);
}
void Machine::run_for(const Cycles cycles) {
m6502_.run_for(cycles);
bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
set_motor_on(drive_motor);
drive_->set_motor_on(drive_motor);
if(drive_motor)
Storage::Disk::Controller::run_for(cycles);
}
@ -105,7 +107,7 @@ void MachineBase::mos6522_did_change_interrupt_status(void *mos6522) {
#pragma mark - Disk drive
void MachineBase::process_input_bit(int value, unsigned int cycles_since_index_hole) {
void MachineBase::process_input_bit(int value) {
shift_register_ = (shift_register_ << 1) | value;
if((shift_register_ & 0x3ff) == 0x3ff) {
drive_VIA_port_handler_.set_sync_detected(true);
@ -130,7 +132,7 @@ void MachineBase::process_index_hole() {}
#pragma mak - Drive VIA delegate
void MachineBase::drive_via_did_step_head(void *driveVIA, int direction) {
step(direction);
drive_->step(direction);
}
void MachineBase::drive_via_did_set_data_density(void *driveVIA, int density) {

View File

@ -135,6 +135,7 @@ class MachineBase:
protected:
CPU::MOS6502::Processor<MachineBase, false> m6502_;
std::shared_ptr<Storage::Disk::Drive> drive_;
uint8_t ram_[0x800];
uint8_t rom_[0x4000];
@ -147,7 +148,7 @@ class MachineBase:
MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_;
int shift_register_, bit_window_offset_;
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
virtual void process_input_bit(int value);
virtual void process_index_hole();
};

View File

@ -16,7 +16,7 @@ Plus3::Plus3() : WD1770(P1770) {
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(!drives_[drive]) {
drives_[drive].reset(new Storage::Disk::Drive);
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300));
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
@ -42,9 +42,14 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) {
}
}
if(changes & 0x04) {
invalidate_track();
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
}
if(changes & 0x08) set_is_double_density(!(control & 0x08));
}
void Plus3::set_motor_on(bool on) {
// TODO: this status should transfer if the selected drive changes. But the same goes for
// writing state, so plenty of work to do in general here.
get_drive().set_motor_on(on);
}

View File

@ -25,6 +25,8 @@ class Plus3 : public WD::WD1770 {
std::shared_ptr<Storage::Disk::Drive> drives_[2];
int selected_drive_ = 0;
uint8_t last_control_ = 0;
void set_motor_on(bool on);
};
}

View File

@ -30,7 +30,7 @@ Microdisc::Microdisc() :
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(!drives_[drive]) {
drives_[drive].reset(new Storage::Disk::Drive);
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300));
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
@ -95,7 +95,14 @@ uint8_t Microdisc::get_data_request_register() {
}
void Microdisc::set_head_load_request(bool head_load) {
set_motor_on(head_load);
// The drive motors (at present: I believe **all drive motors** regardless of the selected drive) receive
// the current head load request state.
for(int c = 0; c < 4; c++) {
if(drives_[c]) drives_[c]->set_motor_on(head_load);
}
// A request to load the head results in a delay until the head is confirmed loaded. This delay is handled
// in ::run_for. A request to unload the head results in an instant answer that the head is unloaded.
if(head_load) {
head_load_request_counter_ = 0;
} else {

View File

@ -434,6 +434,7 @@
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; };
4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; };
4BE4144C1F65E439006A8D7C /* SingleTrackDisk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE4144A1F65E439006A8D7C /* SingleTrackDisk.cpp */; };
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; };
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; };
4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; };
@ -1042,6 +1043,8 @@
4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = "<group>"; };
4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = "<group>"; };
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
4BE4144A1F65E439006A8D7C /* SingleTrackDisk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SingleTrackDisk.cpp; sourceTree = "<group>"; };
4BE4144B1F65E439006A8D7C /* SingleTrackDisk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SingleTrackDisk.hpp; sourceTree = "<group>"; };
4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = "<group>"; };
4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = "<group>"; };
4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; };
@ -1641,6 +1644,7 @@
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */,
4B121F961E060CF000BFDA12 /* PCMSegment.cpp */,
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
4BE4144A1F65E439006A8D7C /* SingleTrackDisk.cpp */,
4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */,
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */,
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
@ -1650,6 +1654,7 @@
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */,
4B121F971E060CF000BFDA12 /* PCMSegment.hpp */,
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
4BE4144B1F65E439006A8D7C /* SingleTrackDisk.hpp */,
4BAD9B951F43D7E900724854 /* UnformattedTrack.hpp */,
4BB697CF1D4BA44900248BDF /* Encodings */,
4BAB62B21D327F7E00DF5BA0 /* Formats */,
@ -2850,6 +2855,7 @@
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */,
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
4B5FADBD1DE31D1500AEC565 /* OricMFMDSK.cpp in Sources */,
4BE4144C1F65E439006A8D7C /* SingleTrackDisk.cpp in Sources */,
4B14978B1EE4AC5E00CE2596 /* StaticAnalyser.cpp in Sources */,
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,

View File

@ -21,10 +21,10 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
public:
std::shared_ptr<Storage::Disk::Drive> drive;
CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1) {
drive.reset(new Storage::Disk::Drive);
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
drive.reset(new Storage::Disk::Drive(4000000, 300));
set_drive(drive);
set_motor_on(true);
drive->set_motor_on(true);
}
struct Sector {
@ -47,7 +47,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
int direction = difference < 0 ? -1 : 1;
difference *= 2 * direction;
for(int c = 0; c < difference; c++) step(direction);
for(int c = 0; c < difference; c++) get_drive().step(direction);
unsigned int zone = 3;
if(track >= 18) zone = 2;
@ -66,7 +66,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
uint8_t track_;
std::shared_ptr<Sector> sector_cache_[65536];
void process_input_bit(int value, unsigned int cycles_since_index_hole) {
void process_input_bit(int value) {
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0x3ff;
bit_count_++;
}

View File

@ -7,43 +7,19 @@
//
#include "DiskController.hpp"
#include "UnformattedTrack.hpp"
#include "../../NumberTheory/Factors.hpp"
#include <cassert>
using namespace Storage::Disk;
Controller::Controller(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
clock_rate_(clock_rate.as_int() * clock_rate_multiplier),
clock_rate_multiplier_(clock_rate_multiplier),
rotational_multiplier_(60, revolutions_per_minute),
cycles_since_index_hole_(0),
motor_is_on_(false),
is_reading_(true),
TimedEventLoop((unsigned int)(clock_rate.as_int() * clock_rate_multiplier)) {
Controller::Controller(Cycles clock_rate) :
clock_rate_multiplier_(128000000 / clock_rate.as_int()),
clock_rate_(clock_rate.as_int() * clock_rate_multiplier_),
empty_drive_(new Drive((unsigned int)clock_rate.as_int(), 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);
}
void Controller::setup_track() {
track_ = drive_->get_track();
if(!track_) {
track_.reset(new UnformattedTrack);
}
Time offset;
Time track_time_now = get_time_into_track();
assert(track_time_now >= Time(0) && current_event_.length <= Time(1));
Time time_found = track_->seek_to(track_time_now);
assert(time_found >= Time(0) && time_found <= track_time_now);
offset = track_time_now - time_found;
get_next_event(offset);
set_drive(empty_drive_);
}
void Controller::set_component_is_sleeping(void *component, bool is_sleeping) {
@ -51,125 +27,32 @@ void Controller::set_component_is_sleeping(void *component, bool is_sleeping) {
}
bool Controller::is_sleeping() {
return !(drive_ && drive_->has_disk() && motor_is_on_);
return !drive_ || drive_->is_sleeping();
}
void Controller::run_for(const Cycles cycles) {
Time zero(0);
if(drive_) drive_->run_for(cycles);
}
if(drive_ && drive_->has_disk() && motor_is_on_) {
if(!track_) setup_track();
Drive &Controller::get_drive() {
return *drive_.get();
}
int number_of_cycles = clock_rate_multiplier_ * cycles.as_int();
while(number_of_cycles) {
int cycles_until_next_event = (int)get_cycles_until_next_event();
int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles);
if(!is_reading_ && cycles_until_bits_written_ > zero) {
int write_cycles_target = (int)cycles_until_bits_written_.get_unsigned_int();
if(cycles_until_bits_written_.length % cycles_until_bits_written_.clock_rate) write_cycles_target++;
cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target);
}
#pragma mark - Drive::EventDelegate
cycles_since_index_hole_ += (unsigned int)cycles_to_run_for;
number_of_cycles -= cycles_to_run_for;
if(is_reading_) {
pll_->run_for(Cycles(cycles_to_run_for));
} else {
if(cycles_until_bits_written_ > zero) {
Storage::Time cycles_to_run_for_time(cycles_to_run_for);
if(cycles_until_bits_written_ <= cycles_to_run_for_time) {
process_write_completed();
if(cycles_until_bits_written_ <= cycles_to_run_for_time)
cycles_until_bits_written_.set_zero();
else
cycles_until_bits_written_ -= cycles_to_run_for_time;
} else {
cycles_until_bits_written_ -= cycles_to_run_for_time;
}
}
}
TimedEventLoop::run_for(Cycles(cycles_to_run_for));
}
void Controller::process_event(const Track::Event &event) {
switch(event.type) {
case Track::Event::FluxTransition: pll_->add_pulse(); break;
case Track::Event::IndexHole: process_index_hole(); break;
}
}
#pragma mark - Track timed event loop
void Controller::get_next_event(const Time &duration_already_passed) {
if(track_) {
current_event_ = track_->get_next_event();
} else {
current_event_.length.length = 1;
current_event_.length.clock_rate = 1;
current_event_.type = Track::Event::IndexHole;
}
// divide interval, which is in terms of a single rotation of the disk, by rotation speed to
// convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_
assert(current_event_.length <= Time(1) && current_event_.length >= Time(0));
Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_;
set_next_event_time_interval(interval);
void Controller::advance(const Cycles cycles) {
if(is_reading_) pll_->run_for(Cycles(cycles.as_int() * clock_rate_multiplier_));
}
void Controller::process_next_event()
{
switch(current_event_.type) {
case Track::Event::FluxTransition:
if(is_reading_) pll_->add_pulse();
break;
case Track::Event::IndexHole:
// printf("%p %d [/%d = %d]\n", this, cycles_since_index_hole_, clock_rate_multiplier_, cycles_since_index_hole_ / clock_rate_multiplier_);
cycles_since_index_hole_ = 0;
process_index_hole();
break;
}
get_next_event(Time(0));
}
Storage::Time Controller::get_time_into_track() {
// this is proportion of a second
Time result(cycles_since_index_hole_, 8000000 * clock_rate_multiplier_);
result /= rotational_multiplier_;
result.simplify();
return result;
}
#pragma mark - Writing
void Controller::begin_writing(bool clamp_to_index_hole) {
is_reading_ = false;
clamp_writing_to_index_hole_ = clamp_to_index_hole;
write_segment_.length_of_a_bit = bit_length_ / rotational_multiplier_;
write_segment_.data.clear();
write_segment_.number_of_bits = 0;
write_start_time_ = get_time_into_track();
}
void Controller::write_bit(bool value) {
bool needs_new_byte = !(write_segment_.number_of_bits&7);
if(needs_new_byte) write_segment_.data.push_back(0);
if(value) write_segment_.data[write_segment_.number_of_bits >> 3] |= 0x80 >> (write_segment_.number_of_bits & 7);
write_segment_.number_of_bits++;
cycles_until_bits_written_ += cycles_per_bit_;
}
void Controller::end_writing() {
is_reading_ = true;
if(!patched_track_) {
// Avoid creating a new patched track if this one is already patched
patched_track_ = std::dynamic_pointer_cast<PCMPatchedTrack>(track_);
if(!patched_track_) {
patched_track_.reset(new PCMPatchedTrack(track_));
}
}
patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_);
cycles_since_index_hole_ %= 8000000 * clock_rate_multiplier_;
invalidate_track(); // TEMPORARY: to force a seek
void Controller::process_write_completed() {
// Provided for subclasses to override.
}
#pragma mark - PLL control and delegate
@ -178,66 +61,53 @@ void Controller::set_expected_bit_length(Time bit_length) {
bit_length_ = bit_length;
bit_length_.simplify();
cycles_per_bit_ = Storage::Time(clock_rate_) * bit_length;
cycles_per_bit_.simplify();
Time cycles_per_bit = Storage::Time(clock_rate_) * bit_length;
cycles_per_bit.simplify();
// this conversion doesn't need to be exact because there's a lot of variation to be taken
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
int clocks_per_bit = (int)cycles_per_bit_.get_unsigned_int();
int clocks_per_bit = (int)cycles_per_bit.get_unsigned_int();
pll_.reset(new DigitalPhaseLockedLoop(clocks_per_bit, 3));
pll_->set_delegate(this);
}
void Controller::digital_phase_locked_loop_output_bit(int value) {
process_input_bit(value, (unsigned int)cycles_since_index_hole_);
}
#pragma mark - Drive actions
bool Controller::get_is_track_zero() {
if(!drive_) return false;
return drive_->get_is_track_zero();
}
bool Controller::get_drive_is_ready() {
if(!drive_) return false;
return drive_->has_disk();
}
bool Controller::get_drive_is_read_only() {
if(!drive_) return false;
return drive_->get_is_read_only();
}
void Controller::step(int direction) {
invalidate_track();
if(drive_) drive_->step(direction);
}
void Controller::set_motor_on(bool motor_on) {
motor_is_on_ = motor_on;
update_sleep_observer();
}
bool Controller::get_motor_on() {
return motor_is_on_;
if(is_reading_) process_input_bit(value);
}
void Controller::set_drive(std::shared_ptr<Drive> drive) {
if(drive_ != drive) {
invalidate_track();
bool was_sleeping = is_sleeping();
// invalidate_track();
if(drive_) {
drive_->set_event_delegate(nullptr);
drive_->set_sleep_observer(nullptr);
}
drive_ = drive;
drive->set_sleep_observer(this);
update_sleep_observer();
if(drive_) {
drive_->set_event_delegate(this);
drive_->set_sleep_observer(this);
} else {
drive_ = empty_drive_;
}
if(is_sleeping() != was_sleeping) {
update_sleep_observer();
}
}
}
void Controller::invalidate_track() {
track_ = nullptr;
if(patched_track_) {
drive_->set_track(patched_track_);
patched_track_ = nullptr;
}
void Controller::begin_writing(bool clamp_to_index_hole) {
is_reading_ = false;
get_drive().begin_writing(bit_length_, clamp_to_index_hole);
}
void Controller::process_write_completed() {}
void Controller::end_writing() {
is_reading_ = true;
get_drive().end_writing();
}
bool Controller::is_reading() {
return is_reading_;
}

View File

@ -13,7 +13,6 @@
#include "DigitalPhaseLockedLoop.hpp"
#include "PCMSegment.hpp"
#include "PCMPatchedTrack.hpp"
#include "../TimedEventLoop.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/Sleeper.hpp"
@ -30,13 +29,12 @@ namespace Disk {
TODO: communication of head size and permissible stepping extents, appropriate simulation of gain.
*/
class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop, public Sleeper, public Sleeper::SleepObserver {
class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDelegate, public Sleeper, public Sleeper::SleepObserver {
protected:
/*!
Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier,
spinning inserted disks at @c revolutions_per_minute.
Constructs a @c Controller that will be run at @c clock_rate.
*/
Controller(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
Controller(Cycles clock_rate);
/*!
Communicates to the PLL the expected length of a bit as a fraction of a second.
@ -47,54 +45,16 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
Advances the drive by @c number_of_cycles cycles.
*/
void run_for(const Cycles cycles);
using TimedEventLoop::run_for;
/*!
Sets the current drive.
Sets the current drive. This drive is the one the PLL listens to.
*/
void set_drive(std::shared_ptr<Drive> drive);
/*!
Announces that the track the drive sees is about to change for a reason unknownt to the controller.
Should be implemented by subclasses; communicates each bit that the PLL recognises.
*/
void invalidate_track();
/*!
Enables or disables the disk motor.
*/
void set_motor_on(bool motor_on);
/*!
@returns @c true if the motor is on; @c false otherwise.
*/
bool get_motor_on();
/*!
Begins write mode, initiating a PCM sampled region of data. Bits should be written via
@c write_bit. They will be written with the length set via @c set_expected_bit_length.
It is acceptable to supply a backlog of bits. Flux transition events will not be reported
while writing.
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
the index hole. Writing will continue over the index hole otherwise.
*/
void begin_writing(bool clamp_to_index_hole);
/*!
Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing.
*/
void write_bit(bool value);
/*!
Ends write mode, switching back to read mode. The drive will stop overwriting events.
*/
void end_writing();
/*!
Should be implemented by subclasses; communicates each bit that the PLL recognises, also specifying
the amount of time since the index hole was last seen.
*/
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole) = 0;
virtual void process_input_bit(int value) = 0;
/*!
Should be implemented by subclasses; communicates that the index hole has been reached.
@ -107,47 +67,60 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
*/
virtual void process_write_completed();
// for TimedEventLoop
virtual void process_next_event();
/*!
Puts the controller and the drive returned by get_drive() into write mode, supplying to
the drive the current bit length.
// to satisfy DigitalPhaseLockedLoop::Delegate
void digital_phase_locked_loop_output_bit(int value);
While the controller is in write mode it disconnects the PLL. So subclasses will not
receive any calls to @c process_input_bit.
bool get_is_track_zero();
void step(int direction);
virtual bool get_drive_is_ready();
bool get_drive_is_read_only();
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
the index hole. Writing will continue over the index hole otherwise.
*/
void begin_writing(bool clamp_to_index_hole);
/*!
Puts the drive returned by get_drive() out of write mode, and marks the controller
as no longer being in write mode.
*/
void end_writing();
/*!
@returns @c true if the controller is in reading mode; @c false otherwise.
*/
bool is_reading();
/*!
Returns the connected drive or, if none is connected, an invented one. No guarantees are
made about the lifetime or the exclusivity of the invented drive.
*/
Drive &get_drive();
/*!
As per Sleeper.
*/
bool is_sleeping();
private:
Time bit_length_;
int clock_rate_;
int clock_rate_multiplier_;
Time rotational_multiplier_;
int clock_rate_multiplier_ = 1;
int clock_rate_ = 1;
bool is_reading_ = true;
std::shared_ptr<DigitalPhaseLockedLoop> pll_;
std::shared_ptr<Drive> drive_;
std::shared_ptr<Track> track_;
int cycles_since_index_hole_;
inline void get_next_event(const Time &duration_already_passed);
Track::Event current_event_;
bool motor_is_on_;
bool is_reading_;
bool clamp_writing_to_index_hole_;
std::shared_ptr<PCMPatchedTrack> patched_track_;
PCMSegment write_segment_;
Time write_start_time_;
Time cycles_until_bits_written_;
Time cycles_per_bit_;
void setup_track();
Time get_time_into_track();
std::shared_ptr<Drive> empty_drive_;
void set_component_is_sleeping(void *component, bool is_sleeping);
// for Drive::EventDelegate
void process_event(const Track::Event &event);
void advance(const Cycles cycles);
// to satisfy DigitalPhaseLockedLoop::Delegate
void digital_phase_locked_loop_output_bit(int value);
};
}

View File

@ -7,24 +7,24 @@
//
#include "Drive.hpp"
#include "UnformattedTrack.hpp"
#include <algorithm>
#include <cassert>
using namespace Storage::Disk;
Drive::Drive()
: head_position_(0), head_(0), has_disk_(false) {}
Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute):
Storage::TimedEventLoop(input_clock_rate),
rotational_multiplier_(60, revolutions_per_minute) {
}
void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
disk_ = disk;
track_ = nullptr;
has_disk_ = !!disk_;
update_sleep_observer();
}
void Drive::set_disk_with_track(const std::shared_ptr<Track> &track) {
disk_ = nullptr;
track_ = track;
has_disk_ = !!track_;
invalidate_track();
update_sleep_observer();
}
@ -33,7 +33,7 @@ bool Drive::has_disk() {
}
bool Drive::is_sleeping() {
return !has_disk_;
return !motor_is_on_ || !has_disk_;
}
bool Drive::get_is_track_zero() {
@ -41,12 +41,30 @@ bool Drive::get_is_track_zero() {
}
void Drive::step(int direction) {
int old_head_position = head_position_;
head_position_ = std::max(head_position_ + direction, 0);
printf("Head -> %d\n", head_position_);
// If the head moved, flush the old track.
if(head_position_ != old_head_position) {
track_ = nullptr;
}
}
void Drive::set_head(unsigned int head) {
head_ = head;
if(head != head_) {
head_ = head;
track_ = nullptr;
}
}
Storage::Time Drive::get_time_into_track() {
// `result` will initially be amount of time since the index hole was seen as a
// proportion of a second; convert it into proportion of a rotation, simplify and return.
Time result(cycles_since_index_hole_, (int)get_input_clock_rate());
result /= rotational_multiplier_;
result.simplify();
assert(result <= Time(1));
return result;
}
bool Drive::get_is_read_only() {
@ -55,16 +73,177 @@ bool Drive::get_is_read_only() {
}
bool Drive::get_is_ready() {
// TODO: a real test for this.
return disk_ != nullptr;
return ready_index_count_ == 2;
}
void Drive::set_motor_on(bool motor_is_on) {
motor_is_on_ = motor_is_on;
if(!motor_is_on) {
ready_index_count_ = 0;
}
update_sleep_observer();
}
bool Drive::get_motor_on() {
return motor_is_on_;
}
void Drive::set_event_delegate(Storage::Disk::Drive::EventDelegate *delegate) {
event_delegate_ = delegate;
}
void Drive::advance(const Cycles cycles) {
cycles_since_index_hole_ += (unsigned int)cycles.as_int();
if(event_delegate_) event_delegate_->advance(cycles);
}
void Drive::run_for(const Cycles cycles) {
if(has_disk_ && motor_is_on_) {
Time zero(0);
int number_of_cycles = cycles.as_int();
while(number_of_cycles) {
int cycles_until_next_event = (int)get_cycles_until_next_event();
int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles);
if(!is_reading_ && cycles_until_bits_written_ > zero) {
int write_cycles_target = (int)cycles_until_bits_written_.get_unsigned_int();
if(cycles_until_bits_written_.length % cycles_until_bits_written_.clock_rate) write_cycles_target++;
cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target);
}
number_of_cycles -= cycles_to_run_for;
if(!is_reading_) {
if(cycles_until_bits_written_ > zero) {
Storage::Time cycles_to_run_for_time(cycles_to_run_for);
if(cycles_until_bits_written_ <= cycles_to_run_for_time) {
if(event_delegate_) event_delegate_->process_write_completed();
if(cycles_until_bits_written_ <= cycles_to_run_for_time)
cycles_until_bits_written_.set_zero();
else
cycles_until_bits_written_ -= cycles_to_run_for_time;
} else {
cycles_until_bits_written_ -= cycles_to_run_for_time;
}
}
}
TimedEventLoop::run_for(Cycles(cycles_to_run_for));
}
}
}
#pragma mark - Track timed event loop
void Drive::get_next_event(const Time &duration_already_passed) {
// Grab a new track if not already in possession of one. This will recursively call get_next_event,
// supplying a proper duration_already_passed.
if(!track_) {
setup_track();
return;
}
if(track_) {
current_event_ = track_->get_next_event();
} else {
current_event_.length.length = 1;
current_event_.length.clock_rate = 1;
current_event_.type = Track::Event::IndexHole;
}
// divide interval, which is in terms of a single rotation of the disk, by rotation speed to
// convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_
assert(current_event_.length <= Time(1) && current_event_.length >= Time(0));
Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_;
set_next_event_time_interval(interval);
}
void Drive::process_next_event() {
// TODO: ready test here.
if(current_event_.type == Track::Event::IndexHole) {
assert(get_time_into_track() == Time(1) || get_time_into_track() == Time(0));
if(ready_index_count_ < 2) ready_index_count_++;
cycles_since_index_hole_ = 0;
}
if(
event_delegate_ &&
(current_event_.type == Track::Event::IndexHole || is_reading_)
){
event_delegate_->process_event(current_event_);
}
get_next_event(Time(0));
}
#pragma mark - Track management
std::shared_ptr<Track> Drive::get_track() {
if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_);
if(track_) return track_;
return nullptr;
}
void Drive::set_track(const std::shared_ptr<Track> &track) {
if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track);
}
void Drive::setup_track() {
track_ = get_track();
if(!track_) {
track_.reset(new UnformattedTrack);
}
Time offset;
Time track_time_now = get_time_into_track();
assert(track_time_now >= Time(0) && current_event_.length <= Time(1));
Time time_found = track_->seek_to(track_time_now);
assert(time_found >= Time(0) && time_found < Time(1) && time_found <= track_time_now);
offset = track_time_now - time_found;
get_next_event(offset);
}
void Drive::invalidate_track() {
track_ = nullptr;
if(patched_track_) {
set_track(patched_track_);
patched_track_ = nullptr;
}
}
#pragma mark - Writing
void Drive::begin_writing(Time bit_length, bool clamp_to_index_hole) {
is_reading_ = false;
clamp_writing_to_index_hole_ = clamp_to_index_hole;
cycles_per_bit_ = Storage::Time(get_input_clock_rate()) * bit_length;
cycles_per_bit_.simplify();
write_segment_.length_of_a_bit = bit_length / rotational_multiplier_;
write_segment_.data.clear();
write_segment_.number_of_bits = 0;
write_start_time_ = get_time_into_track();
}
void Drive::write_bit(bool value) {
bool needs_new_byte = !(write_segment_.number_of_bits&7);
if(needs_new_byte) write_segment_.data.push_back(0);
if(value) write_segment_.data[write_segment_.number_of_bits >> 3] |= 0x80 >> (write_segment_.number_of_bits & 7);
write_segment_.number_of_bits++;
cycles_until_bits_written_ += cycles_per_bit_;
}
void Drive::end_writing() {
is_reading_ = true;
if(!patched_track_) {
// Avoid creating a new patched track if this one is already patched
patched_track_ = std::dynamic_pointer_cast<PCMPatchedTrack>(track_);
if(!patched_track_) {
patched_track_.reset(new PCMPatchedTrack(track_));
}
}
patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_);
cycles_since_index_hole_ %= get_input_clock_rate();
invalidate_track();
}

View File

@ -9,28 +9,27 @@
#ifndef Drive_hpp
#define Drive_hpp
#include <memory>
#include "Disk.hpp"
#include "PCMSegment.hpp"
#include "PCMPatchedTrack.hpp"
#include "../TimedEventLoop.hpp"
#include "../../ClockReceiver/Sleeper.hpp"
#include <memory>
namespace Storage {
namespace Disk {
class Drive: public Sleeper {
class Drive: public Sleeper, public TimedEventLoop {
public:
Drive();
Drive(unsigned int input_clock_rate, int revolutions_per_minute);
/*!
Replaces whatever is in the drive with @c disk.
*/
void set_disk(const std::shared_ptr<Disk> &disk);
/*!
Replaces whatever is in the drive with a disk that contains endless copies of @c track.
*/
void set_disk_with_track(const std::shared_ptr<Track> &track);
/*!
@returns @c true if a disk is currently inserted; @c false otherwise.
*/
@ -57,6 +56,125 @@ class Drive: public Sleeper {
*/
bool get_is_read_only();
/*!
@returns @c true if the drive is ready; @c false otherwise.
*/
bool get_is_ready();
/*!
Sets whether the disk motor is on.
*/
void set_motor_on(bool);
/*!
@returns @c true if the motor is on; @c false otherwise.
*/
bool get_motor_on();
/*!
Begins write mode, initiating a PCM sampled region of data. Bits should be written via
@c write_bit. They will be written with the length set via @c set_expected_bit_length.
It is acceptable to supply a backlog of bits. Flux transition events will not be reported
while writing.
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
the index hole. Writing will continue over the index hole otherwise.
*/
void begin_writing(Time bit_length, bool clamp_to_index_hole);
/*!
Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing.
*/
void write_bit(bool value);
/*!
Ends write mode, switching back to read mode. The drive will stop overwriting events.
*/
void end_writing();
/*!
Advances the drive by @c number_of_cycles cycles.
*/
void run_for(const Cycles cycles);
/*!
Provides a mechanism to receive track events as they occur, including the synthetic
event of "you told me to output the following data, and I've done that now".
*/
struct EventDelegate {
/// Informs the delegate that @c event has been reached.
virtual void process_event(const Track::Event &event) = 0;
/*!
If the drive is in write mode, announces that all queued bits have now been written.
If the controller provides further bits now then there will be no gap in written data.
*/
virtual void process_write_completed() = 0;
/// Informs the delegate of the passing of @c cycles.
virtual void advance(const Cycles cycles) = 0;
};
/// Sets the current event delegate.
void set_event_delegate(EventDelegate *);
// As per Sleeper.
bool is_sleeping();
private:
// Drives contain an entire disk; from that a certain track
// will be currently under the head.
std::shared_ptr<Disk> disk_;
std::shared_ptr<Track> track_;
bool has_disk_ = false;
// Contains the multiplier that converts between track-relative lengths
// to real-time lengths — so it's the reciprocal of rotation speed.
Time rotational_multiplier_;
// A count of time since the index hole was last seen. Which is used to
// determine how far the drive is into a full rotation when switching to
// a new track.
int cycles_since_index_hole_ = 0;
// A record of head position and active head.
int head_position_ = 0;
unsigned int head_ = 0;
// Motor control state.
bool motor_is_on_ = false;
// If the drive is not currently reading then it is writing. While writing
// it can optionally be told to clamp to the index hole.
bool is_reading_ = true;
bool clamp_writing_to_index_hole_ = false;
// If writing is occurring then the drive will be accumulating a write segment,
// for addition to a patched track.
std::shared_ptr<PCMPatchedTrack> patched_track_;
PCMSegment write_segment_;
Time write_start_time_;
// Indicates progress towards drive ready state.
int ready_index_count_ = 0;
// Maintains appropriate counting to know when to indicate that writing
// is complete.
Time cycles_until_bits_written_;
Time cycles_per_bit_;
// TimedEventLoop call-ins and state.
void process_next_event();
void get_next_event(const Time &duration_already_passed);
void advance(const Cycles cycles);
Track::Event current_event_;
// Helper for track changes.
Time get_time_into_track();
// The target (if any) for track events.
EventDelegate *event_delegate_ = nullptr;
/*!
@returns the track underneath the current head at the location now stepped to.
*/
@ -67,20 +185,8 @@ class Drive: public Sleeper {
*/
void set_track(const std::shared_ptr<Track> &track);
/*!
@returns @c true if the drive is ready; @c false otherwise.
*/
bool get_is_ready();
// As per Sleeper.
bool is_sleeping();
private:
std::shared_ptr<Track> track_;
std::shared_ptr<Disk> disk_;
bool has_disk_;
int head_position_;
unsigned int head_;
void setup_track();
void invalidate_track();
};

View File

@ -9,6 +9,7 @@
#include "MFM.hpp"
#include "../PCMTrack.hpp"
#include "../SingleTrackDisk.hpp"
#include "../../../NumberTheory/CRC.hpp"
#include <set>
@ -234,7 +235,7 @@ std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8
#pragma mark - Parser
Parser::Parser(bool is_mfm) :
Storage::Disk::Controller(4000000, 32, 300),
Storage::Disk::Controller(4000000),
crc_generator_(0x1021, 0xffff),
shift_register_(0), is_mfm_(is_mfm),
track_(0), head_(0) {
@ -243,9 +244,9 @@ Parser::Parser(bool is_mfm) :
bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks)
set_expected_bit_length(bit_length);
drive_.reset(new Storage::Disk::Drive);
drive_.reset(new Storage::Disk::Drive(4000000, 300));
set_drive(drive_);
set_motor_on(true);
drive_->set_motor_on(true);
}
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
@ -255,7 +256,7 @@ Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Track> &track) :
Parser(is_mfm) {
drive_->set_disk_with_track(track);
drive_->set_disk(std::make_shared<Disk::SingleTrackDisk>(track));
}
void Parser::seek_to_track(uint8_t track) {
@ -266,7 +267,7 @@ void Parser::seek_to_track(uint8_t track) {
int direction = difference < 0 ? -1 : 1;
difference *= direction;
for(int c = 0; c < difference; c++) step(direction);
for(int c = 0; c < difference; c++) drive_->step(direction);
}
}
@ -274,7 +275,6 @@ std::shared_ptr<Sector> Parser::get_sector(uint8_t head, uint8_t track, uint8_t
// Switch head and track if necessary.
if(head_ != head) {
drive_->set_head(head);
invalidate_track();
}
seek_to_track(track);
int track_index = get_index(head, track, 0);
@ -314,7 +314,7 @@ std::vector<uint8_t> Parser::get_track(uint8_t track) {
return get_track();
}
void Parser::process_input_bit(int value, unsigned int cycles_since_index_hole) {
void Parser::process_input_bit(int value) {
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff;
bit_count_++;
}
@ -426,7 +426,6 @@ std::vector<uint8_t> Parser::get_track() {
return result;
}
std::shared_ptr<Sector> Parser::get_next_sector() {
std::shared_ptr<Sector> sector(new Sector);
index_count_ = 0;

View File

@ -129,7 +129,7 @@ class Parser: public Storage::Disk::Controller {
bool is_mfm_;
void seek_to_track(uint8_t track);
void process_input_bit(int value, unsigned int cycles_since_index_hole);
void process_input_bit(int value);
void process_index_hole();
uint8_t get_next_byte();

View File

@ -12,8 +12,8 @@
using namespace Storage::Disk;
MFMController::MFMController(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
Storage::Disk::Controller(clock_rate, clock_rate_multiplier, revolutions_per_minute),
MFMController::MFMController(Cycles clock_rate) :
Storage::Disk::Controller(clock_rate),
crc_generator_(0x1021, 0xffff),
data_mode_(DataMode::Scanning),
is_awaiting_marker_value_(false) {
@ -53,7 +53,7 @@ NumberTheory::CRC16 &MFMController::get_crc_generator() {
return crc_generator_;
}
void MFMController::process_input_bit(int value, unsigned int cycles_since_index_hole) {
void MFMController::process_input_bit(int value) {
if(data_mode_ == DataMode::Writing) return;
shift_register_ = (shift_register_ << 1) | value;
@ -156,12 +156,12 @@ void MFMController::process_input_bit(int value, unsigned int cycles_since_index
void MFMController::write_bit(int bit) {
if(is_double_density_) {
Controller::write_bit(!bit && !last_bit_);
Controller::write_bit(!!bit);
get_drive().write_bit(!bit && !last_bit_);
get_drive().write_bit(!!bit);
last_bit_ = bit;
} else {
Controller::write_bit(true);
Controller::write_bit(!!bit);
get_drive().write_bit(true);
get_drive().write_bit(!!bit);
}
}
@ -172,7 +172,7 @@ void MFMController::write_byte(uint8_t byte) {
void MFMController::write_raw_short(uint16_t value) {
for(int c = 0; c < 16; c++) {
Controller::write_bit(!!((value << c)&0x8000));
get_drive().write_bit(!!((value << c)&0x8000));
}
}

View File

@ -22,7 +22,7 @@ namespace Disk {
*/
class MFMController: public Controller {
public:
MFMController(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
MFMController(Cycles clock_rate);
protected:
/// Indicates whether the controller should try to decode double-density MFM content, or single-density FM content.
@ -146,7 +146,7 @@ class MFMController: public Controller {
private:
// Storage::Disk::Controller
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
virtual void process_input_bit(int value);
virtual void process_index_hole();
virtual void process_write_completed();

View File

@ -0,0 +1,22 @@
//
// SingleTrackDisk.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "SingleTrackDisk.hpp"
using namespace Storage::Disk;
SingleTrackDisk::SingleTrackDisk(const std::shared_ptr<Track> &track) :
track_(track) {}
unsigned int SingleTrackDisk::get_head_position_count() {
return 1;
}
std::shared_ptr<Track> SingleTrackDisk::get_uncached_track_at_position(unsigned int head, unsigned int position) {
return track_;
}

View File

@ -0,0 +1,35 @@
//
// SingleTrackDisk.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef SingleTrackDisk_hpp
#define SingleTrackDisk_hpp
#include "Disk.hpp"
namespace Storage {
namespace Disk {
/*!
Provides a disk that has houses a single track.
*/
class SingleTrackDisk: public Disk {
public:
/// Constructs a single-track disk with the track @c track.
SingleTrackDisk(const std::shared_ptr<Track> &track);
private:
std::shared_ptr<Track> track_;
unsigned int get_head_position_count();
std::shared_ptr<Track> get_uncached_track_at_position(unsigned int head, unsigned int position);
};
}
}
#endif /* SingleTrackDisk_hpp */

View File

@ -8,7 +8,9 @@
#include "TimedEventLoop.hpp"
#include "../NumberTheory/Factors.hpp"
#include <algorithm>
#include <cassert>
using namespace Storage;
@ -16,16 +18,41 @@ TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) :
input_clock_rate_(input_clock_rate) {}
void TimedEventLoop::run_for(const Cycles cycles) {
cycles_until_event_ -= cycles.as_int();
while(cycles_until_event_ <= 0) {
int remaining_cycles = cycles.as_int();
#ifndef NDEBUG
int cycles_advanced = 0;
#endif
while(cycles_until_event_ <= remaining_cycles) {
#ifndef NDEBUG
cycles_advanced += cycles_until_event_;
#endif
advance(cycles_until_event_);
remaining_cycles -= cycles_until_event_;
cycles_until_event_ = 0;
process_next_event();
}
if(remaining_cycles) {
cycles_until_event_ -= remaining_cycles;
#ifndef NDEBUG
cycles_advanced += remaining_cycles;
#endif
advance(remaining_cycles);
}
assert(cycles_advanced == cycles.as_int());
assert(cycles_until_event_ > 0);
}
unsigned int TimedEventLoop::get_cycles_until_next_event() {
return (unsigned int)std::max(cycles_until_event_, 0);
}
unsigned int TimedEventLoop::get_input_clock_rate() {
return input_clock_rate_;
}
void TimedEventLoop::reset_timer() {
subcycles_until_event_.set_zero();
cycles_until_event_ = 0;
@ -52,9 +79,12 @@ void TimedEventLoop::set_next_event_time_interval(Time interval) {
// So this event will fire in the integral number of cycles from now, putting us at the remainder
// number of subcycles
cycles_until_event_ = (int)(numerator / denominator);
assert(cycles_until_event_ == 0);
cycles_until_event_ += (int)(numerator / denominator);
assert(cycles_until_event_ >= 0);
subcycles_until_event_.length = (unsigned int)(numerator % denominator);
subcycles_until_event_.clock_rate = (unsigned int)denominator;
subcycles_until_event_.simplify();
}
Time TimedEventLoop::get_time_into_next_event() {

View File

@ -54,6 +54,11 @@ namespace Storage {
*/
unsigned int get_cycles_until_next_event();
/*!
@returns the input clock rate.
*/
unsigned int get_input_clock_rate();
protected:
/*!
Sets the time interval, as a proportion of a second, until the next event should be triggered.
@ -66,6 +71,15 @@ namespace Storage {
*/
virtual void process_next_event() = 0;
/*!
Optionally allows a subclass to track time within run_for periods; if a subclass implements
advnace then it will receive advance increments that add up to the number of cycles supplied
to run_for, but calls to process_next_event will be precisely interspersed. No time will carry
forward between calls into run_for; a subclass can receive arbitrarily many instructions to
advance before receiving a process_next_event.
*/
virtual void advance(const Cycles cycles) {};
/*!
Resets timing, throwing away any current internal state. So clears any fractional ticks
that the event loop is currently tracking.
@ -86,8 +100,8 @@ namespace Storage {
Time get_time_into_next_event();
private:
unsigned int input_clock_rate_;
int cycles_until_event_;
unsigned int input_clock_rate_ = 0;
int cycles_until_event_ = 0;
Time subcycles_until_event_;
};