1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-10 12:29:01 +00:00

Merge pull request #242 from TomHarte/8272ReadyInterruption

Improves CPC disk emulation
This commit is contained in:
Thomas Harte 2017-09-16 18:28:00 -04:00 committed by GitHub
commit 98adb01721
11 changed files with 63 additions and 45 deletions

View File

@ -34,6 +34,7 @@ using namespace Intel::i8272;
#define SetSeekEnd() (status_[0] |= 0x20)
#define SetEquipmentCheck() (status_[0] |= 0x10)
#define SetNotReady() (status_[0] |= 0x08)
#define SetSide2() (status_[0] |= 0x04)
#define SetEndOfCylinder() (status_[1] |= 0x80)
#define SetDataError() (status_[1] |= 0x20)
@ -43,6 +44,7 @@ using namespace Intel::i8272;
#define SetMissingAddressMark() (status_[1] |= 0x01)
#define SetControlMark() (status_[2] |= 0x40)
#define ClearControlMark() (status_[2] &= ~0x40)
#define ControlMark() (status_[2] & 0x40)
#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_;
if(is_sleeping_) update_sleep_observer();
}
@ -230,6 +237,7 @@ uint8_t i8272::get_register(int address) {
#define SET_DRIVE_HEAD_MFM() \
active_drive_ = command_[1]&3; \
active_head_ = (command_[1] >> 2)&1; \
status_[0] = (command_[1]&7); \
select_drive(active_drive_); \
get_drive().set_head((unsigned int)active_head_); \
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) {
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;
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
// cylinder, head, sector and size registers from the command stream.
is_executing_ = true;
if(!dma_mode_) SetNonDMAExecution();
SET_DRIVE_HEAD_MFM();
LOAD_HEAD();
@ -425,6 +438,7 @@ void i8272::posit_event(int event_type) {
// flag doesn't match the sort the command was looking for.
read_data_found_header:
FIND_DATA();
ClearControlMark();
if(event_type == (int)Event::Token) {
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.
@ -525,7 +539,6 @@ void i8272::posit_event(int event_type) {
WAIT_FOR_EVENT(Event::DataWritten);
if(!has_input_) {
SetOverrun();
end_writing();
goto abort;
}
write_byte(input_);
@ -646,7 +659,6 @@ void i8272::posit_event(int event_type) {
switch(event_type) {
case (int)Event::IndexHole:
SetOverrun();
end_writing();
goto abort;
break;
case (int)Event::DataWritten:
@ -767,10 +779,9 @@ void i8272::posit_event(int event_type) {
main_status_ &= ~(1 << found_drive);
SetSeekEnd();
result_stack_.push_back(drives_[found_drive].head_position);
result_stack_.push_back(status_[0]);
result_stack_ = { drives_[found_drive].head_position, status_[0]};
} else {
result_stack_.push_back(0x80);
result_stack_ = { 0x80 };
}
}
goto post_result;
@ -793,24 +804,27 @@ void i8272::posit_event(int event_type) {
{
int drive = command_[1] & 3;
select_drive(drive);
result_stack_.push_back(
(command_[1] & 7) | // drive and head number
0x08 | // single sided
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
(get_drive().get_is_ready() ? 0x20 : 0x00) |
(get_drive().get_is_read_only() ? 0x40 : 0x00)
);
result_stack_= {
static_cast<uint8_t>(
(command_[1] & 7) | // drive and head number
0x08 | // single sided
(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;
// Performs any invalid command.
invalid:
// A no-op, but posts ST0 (but which ST0?)
result_stack_.push_back(0x80);
result_stack_ = {0x80};
goto post_result;
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
abort:
end_writing();
SetAbnormalTermination();
goto post_st012chrn;
@ -818,14 +832,7 @@ void i8272::posit_event(int event_type) {
post_st012chrn:
SCHEDULE_HEAD_UNLOAD();
result_stack_.push_back(size_);
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]);
result_stack_ = {size_, sector_, head_, cylinder_, status_[2], status_[1], status_[0]};
goto post_result;
@ -839,6 +846,7 @@ void i8272::posit_event(int event_type) {
printf("\n");
// Set ready to send data to the processor, no longer in non-DMA execution phase.
is_executing_ = false;
ResetNonDMAExecution();
SetDataRequest();
SetDataDirectionToProcessor();

View File

@ -65,6 +65,7 @@ class i8272: public Storage::Disk::MFMController {
CommandByte = (1 << 3),
Timer = (1 << 4),
ResultEmpty = (1 << 5),
NoLongerReady = (1 << 6)
};
void posit_event(int type);
int interesting_event_mask_;
@ -107,10 +108,11 @@ class i8272: public Storage::Disk::MFMController {
bool seek_is_satisfied(int drive);
// User-supplied parameters; as per the specify command.
int step_rate_time_;
int head_unload_time_;
int head_load_time_;
bool dma_mode_;
int step_rate_time_ = 1;
int head_unload_time_ = 1;
int head_load_time_ = 1;
bool dma_mode_ = false;
bool is_executing_ = false;
// A count of head unload timers currently running.
int head_timers_running_;

View File

@ -587,7 +587,7 @@ class FDC: public Intel::i8272::i8272 {
public:
FDC() :
i8272(bus_handler_, Cycles(8000000)),
drive_(new Storage::Disk::Drive(8000000, 300)) {
drive_(new Storage::Disk::Drive(8000000, 300, 1)) {
set_drive(drive_);
}

View File

@ -23,7 +23,7 @@ MachineBase::MachineBase() :
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
drive_VIA_(drive_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
serial_port_->set_serial_port_via(serial_port_VIA_port_handler_);
serial_port_VIA_port_handler_->set_serial_port(serial_port_);

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(8000000, 300));
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);

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(8000000, 300));
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);

View File

@ -22,7 +22,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
std::shared_ptr<Storage::Disk::Drive> drive;
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);
drive->set_motor_on(true);
}

View File

@ -15,7 +15,7 @@ using namespace Storage::Disk;
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)) {
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
Time one(1);
set_expected_bit_length(one);
@ -104,8 +104,10 @@ void Controller::begin_writing(bool clamp_to_index_hole) {
}
void Controller::end_writing() {
is_reading_ = true;
get_drive().end_writing();
if(!is_reading_) {
is_reading_ = true;
get_drive().end_writing();
}
}
bool Controller::is_reading() {

View File

@ -15,9 +15,10 @@
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),
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) {
@ -51,6 +52,7 @@ void Drive::step(int direction) {
}
void Drive::set_head(unsigned int head) {
head = std::min(head, available_heads_ - 1);
if(head != head_) {
head_ = head;
track_ = nullptr;
@ -73,6 +75,7 @@ bool Drive::get_is_read_only() {
}
bool Drive::get_is_ready() {
return true;
return ready_index_count_ == 2;
}
@ -234,16 +237,18 @@ void Drive::write_bit(bool value) {
}
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_) {
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();
}

View File

@ -23,7 +23,7 @@ namespace Disk {
class Drive: public Sleeper, public TimedEventLoop {
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.
@ -140,6 +140,7 @@ class Drive: public Sleeper, public TimedEventLoop {
// A record of head position and active head.
int head_position_ = 0;
unsigned int head_ = 0;
unsigned int available_heads_ = 0;
// Motor control state.
bool motor_is_on_ = false;

View File

@ -244,7 +244,7 @@ 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(4000000, 300));
drive_.reset(new Storage::Disk::Drive(4000000, 300, 2));
set_drive(drive_);
drive_->set_motor_on(true);
}