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:
commit
b835cb73e2
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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) {
|
||||
|
@ -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_;
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 */,
|
||||
|
@ -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_++;
|
||||
}
|
||||
|
@ -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_;
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
22
Storage/Disk/SingleTrackDisk.cpp
Normal file
22
Storage/Disk/SingleTrackDisk.cpp
Normal 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_;
|
||||
}
|
35
Storage/Disk/SingleTrackDisk.hpp
Normal file
35
Storage/Disk/SingleTrackDisk.hpp
Normal 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 */
|
@ -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() {
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user