1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-03 11:30:02 +00:00
CLK/Components/1770/1770.cpp

454 lines
10 KiB
C++
Raw Normal View History

2016-09-17 22:01:00 +00:00
//
// 1770.cpp
// Clock Signal
//
// Created by Thomas Harte on 17/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
using namespace WD;
WD1770::WD1770() :
Storage::Disk::Drive(8000000, 1, 300),
status_(0),
interesting_event_mask_(Event::Command),
resume_point_(0),
delay_time_(0)
{
2016-09-23 01:28:23 +00:00
set_is_double_density(false);
posit_event(Event::Command);
}
void WD1770::set_is_double_density(bool is_double_density)
{
is_double_density_ = is_double_density;
2016-09-23 01:28:23 +00:00
Storage::Time bit_length;
bit_length.length = 1;
bit_length.clock_rate = is_double_density ? 500000 : 250000;
set_expected_bit_length(bit_length);
}
void WD1770::set_register(int address, uint8_t value)
{
switch(address&3)
{
case 0:
command_ = value;
posit_event(Event::Command);
// TODO: is this force interrupt?
break;
case 1: track_ = value; break;
case 2: sector_ = value; break;
case 3: data_ = value; break;
}
}
uint8_t WD1770::get_register(int address)
{
switch(address&3)
{
default: return status_;
case 1: return track_;
case 2: return sector_;
case 3: return data_;
}
}
void WD1770::run_for_cycles(unsigned int number_of_cycles)
{
if(status_ & Flag::MotorOn) Storage::Disk::Drive::run_for_cycles((int)number_of_cycles);
if(delay_time_)
{
if(delay_time_ <= number_of_cycles)
{
delay_time_ = 0;
posit_event(Event::Timer);
}
else
{
delay_time_ -= number_of_cycles;
}
}
// perform one step every eight cycles, arbitrariy as I can find no timing documentation
/* cycles += number_of_cycles;
while(cycles > 8)
{
cycles -= 8;
if(status_ & Flag::MotorOn) Storage::Disk::Drive::run_for_cycles(1);
switch(state_)
{
case State::Waiting:
if(has_command_)
{
has_command_ = false;
if(command_ & 0x80)
state_ = (command_ & 0x40) ? State::BeginType3 : State::BeginType2;
else
state_ = State::BeginType1;
}
continue;
case State::WaitForSixIndexPulses:
status_ |= Flag::MotorOn;
// deliberately empty; logic is in ::process_index_hole
continue;
#pragma mark - Type 1
case State::BeginType1:
status_ |= Flag::Busy;
status_ &= ~(Flag::DataRequest | Flag::CRCError);
set_interrupt_request(false);
state_ = State::BeginType1PostSpin;
if(command_ & 0x08)
{
wait_six_index_pulses_.next_state = state_;
index_hole_count_ = 0;
state_ = State::WaitForSixIndexPulses;
}
continue;
case State::BeginType1PostSpin:
switch(command_ >> 4)
{
case 0: // restore
track_ = 0xff; // deliberate fallthrough
case 1: // seek
data_ = 0x00;
break;
case 2: case 3: // step
break;
case 4: case 5: // step in
is_step_in_ = true;
break;
case 6: case 7: // step out
is_step_in_ = false;
break;
}
if(!(command_ >> 5))
state_ = State::TestTrack;
else
state_ = (command_ & 0x10) ? State::TestDirection : State::TestHead;
continue;
case State::TestTrack:
data_shift_register_ = data_;
if(track_ == data_shift_register_)
state_ = State::TestVerify;
else
{
is_step_in_ = (data_shift_register_ < track_);
state_ = State::TestDirection;
}
continue;
case State::TestDirection:
track_ += is_step_in_ ? -1 : +1;
state_ = State::TestHead;
continue;
case State::TestHead:
if(get_is_track_zero() && !is_step_in_)
{
track_ = 0;
state_ = State::TestVerify;
}
else
{
step(is_step_in_ ? 1 : -1);
state_ = State::StepDelay;
step_delay_.count = 0;
}
continue;
case State::StepDelay:
if(step_delay_.count == (command_&3))
{
state_ = (command_ >> 5) ? State::TestVerify : State::TestTrack;
}
step_delay_.count++;
continue;
case State::TestVerify:
if(command_ & 0x04)
{
state_ = State::VerifyTrack;
}
else
{
set_interrupt_request(true);
status_ &= ~Flag::Busy;
state_ = State::Waiting;
}
continue;
#pragma mark - Type 2
case State::BeginType2:
status_ |= Flag::Busy;
status_ &= ~(Flag::DataRequest | Flag::LostData | Flag::RecordNotFound | 0x60);
state_ = State::TestPause;
if(!(command_&0x08))
{
wait_six_index_pulses_.next_state = state_;
index_hole_count_ = 0;
state_ = State::WaitForSixIndexPulses;
}
continue;
case State::TestPause:
// TODO: pause for 30ms if E is set
state_ = State::TestWriteProtect;
continue;
case State::TestWriteProtect:
if(command_ & 0x20) // TODO: && write protect
{
set_interrupt_request(true);
status_ &= ~Flag::Busy;
status_ |= Flag::WriteProtect;
state_ = State::Waiting;
}
else
{
get_header_.found_id = false;
state_ = State::GetHeader;
}
continue;
case State::GetHeader:
if(index_hole_count_ == 5)
{
set_interrupt_request(true);
status_ &= ~Flag::Busy;
status_ |= Flag::RecordNotFound;
state_ = State::Waiting;
continue;
}
if(get_header_.found_id)
{
if(token_counter_ > 0 && latest_token_.type != Token::Byte)
{
get_header_.found_id = false;
continue;
}
if(token_counter_ == 5)
{
}
else if(token_counter_ > 0)
{
}
}
else
{
if(latest_token_.type == Token::ID)
{
get_header_.found_id = true;
token_counter_ = 0;
}
}
continue;
default:
{
static bool has_hit_error = false;
if(!has_hit_error)
printf("Unhandled state %d!\n", state_);
has_hit_error = true;
}
return;
}
}*/
}
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
{
shift_register_ = (shift_register_ << 1) | value;
bits_since_token_++;
Token::Type token_type = Token::Byte;
if(is_double_density_)
{
switch(shift_register_ & 0xffff)
{
case Storage::Encodings::MFM::FMIndexAddressMark:
token_type = Token::Index;
break;
case Storage::Encodings::MFM::FMIDAddressMark:
token_type = Token::ID;
break;
case Storage::Encodings::MFM::FMDataAddressMark:
token_type = Token::Data;
break;
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
token_type = Token::DeletedData;
break;
default:
break;
}
}
else
{
// TODO: MFM
}
if(token_type != Token::Byte)
{
latest_token_.type = token_type;
bits_since_token_ = 0;
posit_event(Event::Token);
return;
}
if(bits_since_token_ == 16)
{
latest_token_.type = Token::Byte;
latest_token_.byte_value = (uint8_t)(
((shift_register_ & 0x0001) >> 0) |
((shift_register_ & 0x0004) >> 1) |
((shift_register_ & 0x0010) >> 2) |
((shift_register_ & 0x0040) >> 3) |
((shift_register_ & 0x0100) >> 4) |
((shift_register_ & 0x0400) >> 5) |
((shift_register_ & 0x1000) >> 6) |
((shift_register_ & 0x4000) >> 7));
bits_since_token_ = 0;
posit_event(Event::Token);
return;
}
}
void WD1770::process_index_hole()
{
index_hole_count_++;
posit_event(Event::IndexHole);
}
// +------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +------+----------+-------------------------+
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__:
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() 0; }
void WD1770::posit_event(Event type)
{
if(!(interesting_event_mask_ & (int)type)) return;
interesting_event_mask_ &= ~type;
BEGIN_SECTION()
// Wait for a new command, branch to the appropriate handler.
wait_for_command:
WAIT_FOR_EVENT(Event::Command);
status_ |= Flag::Busy;
if(!(command_ & 0x80)) goto begin_type_1;
if(!(command_ & 0x40)) goto begin_type_2;
goto begin_type_3;
/*
Type 1 entry point.
*/
begin_type_1:
// Set initial flags, skip spin-up if possible.
status_ &= ~(Flag::DataRequest | Flag::DataRequest);
set_interrupt_request(false);
if(!(command_&0x08)) goto test_type1_type;
// Perform spin up.
status_ |= Flag::MotorOn;
WAIT_FOR_EVENT(Event::IndexHole);
WAIT_FOR_EVENT(Event::IndexHole);
WAIT_FOR_EVENT(Event::IndexHole);
WAIT_FOR_EVENT(Event::IndexHole);
WAIT_FOR_EVENT(Event::IndexHole);
WAIT_FOR_EVENT(Event::IndexHole);
test_type1_type:
// Set step direction if this is a step in or out.
if((command_ >> 5) == 2) step_direction_ = 1;
if((command_ >> 5) == 3) step_direction_ = 0;
if((command_ >> 5) != 0) goto perform_step_command;
// This is now definitely either a seek or a restore; if it's a restore then set track to 0xff.
if(!(command_ & 0x10)) track_ = 0xff;
data_ = 0;
perform_seek_or_restore_command:
if(track_ == data_) goto verify;
step_direction_ = (data_ < track_);
adjust_track:
if(step_direction_) track_--; else track_++;
perform_step:
if(!step_direction_ && get_is_track_zero())
{
track_ = 0;
goto verify;
}
step(step_direction_);
int time_to_wait;
switch(command_ & 3)
{
default:
case 0: time_to_wait = 6; break; // 2 on a 1772
case 1: time_to_wait = 12; break; // 3 on a 1772
case 2: time_to_wait = 20; break; // 5 on a 1772
case 3: time_to_wait = 30; break; // 6 on a 1772
}
2016-09-25 00:16:23 +00:00
WAIT_FOR_TIME(time_to_wait);
if(command_ >> 5) goto verify;
goto perform_seek_or_restore_command;
perform_step_command:
if(command_ & 0x10) goto adjust_track;
goto perform_step;
verify:
if(!(command_ & 0x04))
{
set_interrupt_request(true);
status_ &= ~(Flag::Busy);
goto wait_for_command;
}
printf("!!!TODO: verify a type 1!!!\n");
begin_type_2:
printf("!!!TODO: type 2 commands!!!\n");
begin_type_3:
printf("!!!TODO: type 3 commands!!!\n");
END_SECTION()
}