mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-12 15:31:09 +00:00
Merge pull request #242 from TomHarte/8272ReadyInterruption
Improves CPC disk emulation
This commit is contained in:
commit
98adb01721
@ -34,6 +34,7 @@ using namespace Intel::i8272;
|
|||||||
#define SetSeekEnd() (status_[0] |= 0x20)
|
#define SetSeekEnd() (status_[0] |= 0x20)
|
||||||
#define SetEquipmentCheck() (status_[0] |= 0x10)
|
#define SetEquipmentCheck() (status_[0] |= 0x10)
|
||||||
#define SetNotReady() (status_[0] |= 0x08)
|
#define SetNotReady() (status_[0] |= 0x08)
|
||||||
|
#define SetSide2() (status_[0] |= 0x04)
|
||||||
|
|
||||||
#define SetEndOfCylinder() (status_[1] |= 0x80)
|
#define SetEndOfCylinder() (status_[1] |= 0x80)
|
||||||
#define SetDataError() (status_[1] |= 0x20)
|
#define SetDataError() (status_[1] |= 0x20)
|
||||||
@ -43,6 +44,7 @@ using namespace Intel::i8272;
|
|||||||
#define SetMissingAddressMark() (status_[1] |= 0x01)
|
#define SetMissingAddressMark() (status_[1] |= 0x01)
|
||||||
|
|
||||||
#define SetControlMark() (status_[2] |= 0x40)
|
#define SetControlMark() (status_[2] |= 0x40)
|
||||||
|
#define ClearControlMark() (status_[2] &= ~0x40)
|
||||||
#define ControlMark() (status_[2] & 0x40)
|
#define ControlMark() (status_[2] & 0x40)
|
||||||
|
|
||||||
#define SetDataFieldDataError() (status_[2] |= 0x20)
|
#define SetDataFieldDataError() (status_[2] |= 0x20)
|
||||||
@ -158,6 +160,11 @@ void i8272::run_for(Cycles cycles) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for busy plus ready disabled
|
||||||
|
if(is_executing_ && !get_drive().get_is_ready()) {
|
||||||
|
posit_event((int)Event8272::NoLongerReady);
|
||||||
|
}
|
||||||
|
|
||||||
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
|
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
|
||||||
if(is_sleeping_) update_sleep_observer();
|
if(is_sleeping_) update_sleep_observer();
|
||||||
}
|
}
|
||||||
@ -230,6 +237,7 @@ uint8_t i8272::get_register(int address) {
|
|||||||
#define SET_DRIVE_HEAD_MFM() \
|
#define SET_DRIVE_HEAD_MFM() \
|
||||||
active_drive_ = command_[1]&3; \
|
active_drive_ = command_[1]&3; \
|
||||||
active_head_ = (command_[1] >> 2)&1; \
|
active_head_ = (command_[1] >> 2)&1; \
|
||||||
|
status_[0] = (command_[1]&7); \
|
||||||
select_drive(active_drive_); \
|
select_drive(active_drive_); \
|
||||||
get_drive().set_head((unsigned int)active_head_); \
|
get_drive().set_head((unsigned int)active_head_); \
|
||||||
set_is_double_density(command_[0] & 0x40);
|
set_is_double_density(command_[0] & 0x40);
|
||||||
@ -263,6 +271,10 @@ uint8_t i8272::get_register(int address) {
|
|||||||
|
|
||||||
void i8272::posit_event(int event_type) {
|
void i8272::posit_event(int event_type) {
|
||||||
if(event_type == (int)Event::IndexHole) index_hole_count_++;
|
if(event_type == (int)Event::IndexHole) index_hole_count_++;
|
||||||
|
if(event_type == (int)Event8272::NoLongerReady) {
|
||||||
|
SetNotReady();
|
||||||
|
goto abort;
|
||||||
|
}
|
||||||
if(!(interesting_event_mask_ & event_type)) return;
|
if(!(interesting_event_mask_ & event_type)) return;
|
||||||
interesting_event_mask_ &= ~event_type;
|
interesting_event_mask_ &= ~event_type;
|
||||||
|
|
||||||
@ -332,6 +344,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
}
|
}
|
||||||
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
|
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
|
||||||
// cylinder, head, sector and size registers from the command stream.
|
// cylinder, head, sector and size registers from the command stream.
|
||||||
|
is_executing_ = true;
|
||||||
if(!dma_mode_) SetNonDMAExecution();
|
if(!dma_mode_) SetNonDMAExecution();
|
||||||
SET_DRIVE_HEAD_MFM();
|
SET_DRIVE_HEAD_MFM();
|
||||||
LOAD_HEAD();
|
LOAD_HEAD();
|
||||||
@ -425,6 +438,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
// flag doesn't match the sort the command was looking for.
|
// flag doesn't match the sort the command was looking for.
|
||||||
read_data_found_header:
|
read_data_found_header:
|
||||||
FIND_DATA();
|
FIND_DATA();
|
||||||
|
ClearControlMark();
|
||||||
if(event_type == (int)Event::Token) {
|
if(event_type == (int)Event::Token) {
|
||||||
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
|
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
|
||||||
// Something other than a data mark came next — impliedly an ID or index mark.
|
// Something other than a data mark came next — impliedly an ID or index mark.
|
||||||
@ -525,7 +539,6 @@ void i8272::posit_event(int event_type) {
|
|||||||
WAIT_FOR_EVENT(Event::DataWritten);
|
WAIT_FOR_EVENT(Event::DataWritten);
|
||||||
if(!has_input_) {
|
if(!has_input_) {
|
||||||
SetOverrun();
|
SetOverrun();
|
||||||
end_writing();
|
|
||||||
goto abort;
|
goto abort;
|
||||||
}
|
}
|
||||||
write_byte(input_);
|
write_byte(input_);
|
||||||
@ -646,7 +659,6 @@ void i8272::posit_event(int event_type) {
|
|||||||
switch(event_type) {
|
switch(event_type) {
|
||||||
case (int)Event::IndexHole:
|
case (int)Event::IndexHole:
|
||||||
SetOverrun();
|
SetOverrun();
|
||||||
end_writing();
|
|
||||||
goto abort;
|
goto abort;
|
||||||
break;
|
break;
|
||||||
case (int)Event::DataWritten:
|
case (int)Event::DataWritten:
|
||||||
@ -767,10 +779,9 @@ void i8272::posit_event(int event_type) {
|
|||||||
main_status_ &= ~(1 << found_drive);
|
main_status_ &= ~(1 << found_drive);
|
||||||
SetSeekEnd();
|
SetSeekEnd();
|
||||||
|
|
||||||
result_stack_.push_back(drives_[found_drive].head_position);
|
result_stack_ = { drives_[found_drive].head_position, status_[0]};
|
||||||
result_stack_.push_back(status_[0]);
|
|
||||||
} else {
|
} else {
|
||||||
result_stack_.push_back(0x80);
|
result_stack_ = { 0x80 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
goto post_result;
|
goto post_result;
|
||||||
@ -793,24 +804,27 @@ void i8272::posit_event(int event_type) {
|
|||||||
{
|
{
|
||||||
int drive = command_[1] & 3;
|
int drive = command_[1] & 3;
|
||||||
select_drive(drive);
|
select_drive(drive);
|
||||||
result_stack_.push_back(
|
result_stack_= {
|
||||||
(command_[1] & 7) | // drive and head number
|
static_cast<uint8_t>(
|
||||||
0x08 | // single sided
|
(command_[1] & 7) | // drive and head number
|
||||||
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
|
0x08 | // single sided
|
||||||
(get_drive().get_is_ready() ? 0x20 : 0x00) |
|
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
|
||||||
(get_drive().get_is_read_only() ? 0x40 : 0x00)
|
(get_drive().get_is_ready() ? 0x20 : 0x00) |
|
||||||
);
|
(get_drive().get_is_read_only() ? 0x40 : 0x00)
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
goto post_result;
|
goto post_result;
|
||||||
|
|
||||||
// Performs any invalid command.
|
// Performs any invalid command.
|
||||||
invalid:
|
invalid:
|
||||||
// A no-op, but posts ST0 (but which ST0?)
|
// A no-op, but posts ST0 (but which ST0?)
|
||||||
result_stack_.push_back(0x80);
|
result_stack_ = {0x80};
|
||||||
goto post_result;
|
goto post_result;
|
||||||
|
|
||||||
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
|
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
|
||||||
abort:
|
abort:
|
||||||
|
end_writing();
|
||||||
SetAbnormalTermination();
|
SetAbnormalTermination();
|
||||||
goto post_st012chrn;
|
goto post_st012chrn;
|
||||||
|
|
||||||
@ -818,14 +832,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
post_st012chrn:
|
post_st012chrn:
|
||||||
SCHEDULE_HEAD_UNLOAD();
|
SCHEDULE_HEAD_UNLOAD();
|
||||||
|
|
||||||
result_stack_.push_back(size_);
|
result_stack_ = {size_, sector_, head_, cylinder_, status_[2], status_[1], status_[0]};
|
||||||
result_stack_.push_back(sector_);
|
|
||||||
result_stack_.push_back(head_);
|
|
||||||
result_stack_.push_back(cylinder_);
|
|
||||||
|
|
||||||
result_stack_.push_back(status_[2]);
|
|
||||||
result_stack_.push_back(status_[1]);
|
|
||||||
result_stack_.push_back(status_[0]);
|
|
||||||
|
|
||||||
goto post_result;
|
goto post_result;
|
||||||
|
|
||||||
@ -839,6 +846,7 @@ void i8272::posit_event(int event_type) {
|
|||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
||||||
|
is_executing_ = false;
|
||||||
ResetNonDMAExecution();
|
ResetNonDMAExecution();
|
||||||
SetDataRequest();
|
SetDataRequest();
|
||||||
SetDataDirectionToProcessor();
|
SetDataDirectionToProcessor();
|
||||||
|
@ -65,6 +65,7 @@ class i8272: public Storage::Disk::MFMController {
|
|||||||
CommandByte = (1 << 3),
|
CommandByte = (1 << 3),
|
||||||
Timer = (1 << 4),
|
Timer = (1 << 4),
|
||||||
ResultEmpty = (1 << 5),
|
ResultEmpty = (1 << 5),
|
||||||
|
NoLongerReady = (1 << 6)
|
||||||
};
|
};
|
||||||
void posit_event(int type);
|
void posit_event(int type);
|
||||||
int interesting_event_mask_;
|
int interesting_event_mask_;
|
||||||
@ -107,10 +108,11 @@ class i8272: public Storage::Disk::MFMController {
|
|||||||
bool seek_is_satisfied(int drive);
|
bool seek_is_satisfied(int drive);
|
||||||
|
|
||||||
// User-supplied parameters; as per the specify command.
|
// User-supplied parameters; as per the specify command.
|
||||||
int step_rate_time_;
|
int step_rate_time_ = 1;
|
||||||
int head_unload_time_;
|
int head_unload_time_ = 1;
|
||||||
int head_load_time_;
|
int head_load_time_ = 1;
|
||||||
bool dma_mode_;
|
bool dma_mode_ = false;
|
||||||
|
bool is_executing_ = false;
|
||||||
|
|
||||||
// A count of head unload timers currently running.
|
// A count of head unload timers currently running.
|
||||||
int head_timers_running_;
|
int head_timers_running_;
|
||||||
|
@ -587,7 +587,7 @@ class FDC: public Intel::i8272::i8272 {
|
|||||||
public:
|
public:
|
||||||
FDC() :
|
FDC() :
|
||||||
i8272(bus_handler_, Cycles(8000000)),
|
i8272(bus_handler_, Cycles(8000000)),
|
||||||
drive_(new Storage::Disk::Drive(8000000, 300)) {
|
drive_(new Storage::Disk::Drive(8000000, 300, 1)) {
|
||||||
set_drive(drive_);
|
set_drive(drive_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ MachineBase::MachineBase() :
|
|||||||
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
|
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
|
||||||
drive_VIA_(drive_VIA_port_handler_),
|
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)) {
|
drive_(new Storage::Disk::Drive(1000000, 300, 2)) {
|
||||||
// attach the serial port to its VIA and vice versa
|
// attach the serial port to its VIA and vice versa
|
||||||
serial_port_->set_serial_port_via(serial_port_VIA_port_handler_);
|
serial_port_->set_serial_port_via(serial_port_VIA_port_handler_);
|
||||||
serial_port_VIA_port_handler_->set_serial_port(serial_port_);
|
serial_port_VIA_port_handler_->set_serial_port(serial_port_);
|
||||||
|
@ -16,7 +16,7 @@ Plus3::Plus3() : WD1770(P1770) {
|
|||||||
|
|
||||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||||
if(!drives_[drive]) {
|
if(!drives_[drive]) {
|
||||||
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300));
|
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||||
}
|
}
|
||||||
drives_[drive]->set_disk(disk);
|
drives_[drive]->set_disk(disk);
|
||||||
|
@ -30,7 +30,7 @@ Microdisc::Microdisc() :
|
|||||||
|
|
||||||
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||||
if(!drives_[drive]) {
|
if(!drives_[drive]) {
|
||||||
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300));
|
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||||
}
|
}
|
||||||
drives_[drive]->set_disk(disk);
|
drives_[drive]->set_disk(disk);
|
||||||
|
@ -22,7 +22,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
|||||||
std::shared_ptr<Storage::Disk::Drive> drive;
|
std::shared_ptr<Storage::Disk::Drive> drive;
|
||||||
|
|
||||||
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
|
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
|
||||||
drive.reset(new Storage::Disk::Drive(4000000, 300));
|
drive.reset(new Storage::Disk::Drive(4000000, 300, 2));
|
||||||
set_drive(drive);
|
set_drive(drive);
|
||||||
drive->set_motor_on(true);
|
drive->set_motor_on(true);
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ using namespace Storage::Disk;
|
|||||||
Controller::Controller(Cycles clock_rate) :
|
Controller::Controller(Cycles clock_rate) :
|
||||||
clock_rate_multiplier_(128000000 / clock_rate.as_int()),
|
clock_rate_multiplier_(128000000 / clock_rate.as_int()),
|
||||||
clock_rate_(clock_rate.as_int() * clock_rate_multiplier_),
|
clock_rate_(clock_rate.as_int() * clock_rate_multiplier_),
|
||||||
empty_drive_(new Drive((unsigned int)clock_rate.as_int(), 1)) {
|
empty_drive_(new Drive((unsigned int)clock_rate.as_int(), 1, 1)) {
|
||||||
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
|
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
|
||||||
Time one(1);
|
Time one(1);
|
||||||
set_expected_bit_length(one);
|
set_expected_bit_length(one);
|
||||||
@ -104,8 +104,10 @@ void Controller::begin_writing(bool clamp_to_index_hole) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Controller::end_writing() {
|
void Controller::end_writing() {
|
||||||
is_reading_ = true;
|
if(!is_reading_) {
|
||||||
get_drive().end_writing();
|
is_reading_ = true;
|
||||||
|
get_drive().end_writing();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Controller::is_reading() {
|
bool Controller::is_reading() {
|
||||||
|
@ -15,9 +15,10 @@
|
|||||||
|
|
||||||
using namespace Storage::Disk;
|
using namespace Storage::Disk;
|
||||||
|
|
||||||
Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute):
|
Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute, unsigned int number_of_heads):
|
||||||
Storage::TimedEventLoop(input_clock_rate),
|
Storage::TimedEventLoop(input_clock_rate),
|
||||||
rotational_multiplier_(60, revolutions_per_minute) {
|
rotational_multiplier_(60, revolutions_per_minute),
|
||||||
|
available_heads_(number_of_heads) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
|
void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
|
||||||
@ -51,6 +52,7 @@ void Drive::step(int direction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Drive::set_head(unsigned int head) {
|
void Drive::set_head(unsigned int head) {
|
||||||
|
head = std::min(head, available_heads_ - 1);
|
||||||
if(head != head_) {
|
if(head != head_) {
|
||||||
head_ = head;
|
head_ = head;
|
||||||
track_ = nullptr;
|
track_ = nullptr;
|
||||||
@ -73,6 +75,7 @@ bool Drive::get_is_read_only() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Drive::get_is_ready() {
|
bool Drive::get_is_ready() {
|
||||||
|
return true;
|
||||||
return ready_index_count_ == 2;
|
return ready_index_count_ == 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,16 +237,18 @@ void Drive::write_bit(bool value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Drive::end_writing() {
|
void Drive::end_writing() {
|
||||||
is_reading_ = true;
|
if(!is_reading_) {
|
||||||
|
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_) {
|
if(!patched_track_) {
|
||||||
patched_track_.reset(new PCMPatchedTrack(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();
|
||||||
}
|
}
|
||||||
patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_);
|
|
||||||
cycles_since_index_hole_ %= get_input_clock_rate();
|
|
||||||
invalidate_track();
|
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ namespace Disk {
|
|||||||
|
|
||||||
class Drive: public Sleeper, public TimedEventLoop {
|
class Drive: public Sleeper, public TimedEventLoop {
|
||||||
public:
|
public:
|
||||||
Drive(unsigned int input_clock_rate, int revolutions_per_minute);
|
Drive(unsigned int input_clock_rate, int revolutions_per_minute, unsigned int number_of_heads);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Replaces whatever is in the drive with @c disk.
|
Replaces whatever is in the drive with @c disk.
|
||||||
@ -140,6 +140,7 @@ class Drive: public Sleeper, public TimedEventLoop {
|
|||||||
// A record of head position and active head.
|
// A record of head position and active head.
|
||||||
int head_position_ = 0;
|
int head_position_ = 0;
|
||||||
unsigned int head_ = 0;
|
unsigned int head_ = 0;
|
||||||
|
unsigned int available_heads_ = 0;
|
||||||
|
|
||||||
// Motor control state.
|
// Motor control state.
|
||||||
bool motor_is_on_ = false;
|
bool motor_is_on_ = false;
|
||||||
|
@ -244,7 +244,7 @@ Parser::Parser(bool is_mfm) :
|
|||||||
bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks)
|
bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks)
|
||||||
set_expected_bit_length(bit_length);
|
set_expected_bit_length(bit_length);
|
||||||
|
|
||||||
drive_.reset(new Storage::Disk::Drive(4000000, 300));
|
drive_.reset(new Storage::Disk::Drive(4000000, 300, 2));
|
||||||
set_drive(drive_);
|
set_drive(drive_);
|
||||||
drive_->set_motor_on(true);
|
drive_->set_motor_on(true);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user