1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-09-27 18:55:48 +00:00

Makes an attempt at getting the 5380 past arbitration.

Not entirely successful. Also gets a bit smarter with `final` on ClockingHint::Sources.
This commit is contained in:
Thomas Harte 2019-08-15 23:14:40 -04:00
parent ce1c96d68c
commit f668e4a54c
9 changed files with 146 additions and 32 deletions

View File

@ -27,7 +27,7 @@ BusState Bus::get_state() {
state_is_valid_ = true;
state_ = DefaultBusState;
for(auto state: device_states_) {
state_ &= state;
state_ |= state;
}
return state_;

View File

@ -16,7 +16,7 @@ namespace SCSI {
typedef int BusState;
static const BusState DefaultBusState = std::numeric_limits<BusState>::max();
static const BusState DefaultBusState = 0;
/*!
SCSI bus state is encoded entirely within an int.
@ -33,20 +33,22 @@ enum Line: BusState {
/// Set if the SEL line is currently selecting a target.
/// Reset if it is selecting an initiator.
SelectTarget = 1 << 9,
/// Reset to indicate an attention condition. Set otherwise.
/// Set to indicate an attention condition. Reset otherwise.
Attention = 1 << 10,
/// Set if control is on the bus. Reset if data is on the bus.
Control = 1 << 11,
/// Reset if the bus is busy. Set otherwise.
/// Set if the bus is busy. Reset otherwise.
Busy = 1 << 12,
/// Reset if acknowledging a data transfer request. Set otherwise.
/// Set if acknowledging a data transfer request. Reset otherwise.
Acknowledge = 1 << 13,
/// Reset if a bus reset is being requested. Set otherwise.
/// Set if a bus reset is being requested. Reset otherwise.
Reset = 1 << 14,
/// Set if data is currently input. Reset if it is an output.
/// Set if data is currently an input to the initiator. Reset if it is an output.
Input = 1 << 15,
/// Set during the message phase. Reset otherwise.
MessagePhase = 1 << 16
Message = 1 << 16,
/// Set if requesting a data transfer. Reset otherwise.
Request = 1 << 17,
};

View File

@ -12,7 +12,7 @@
using namespace NCR::NCR5380;
NCR5380::NCR5380() {
NCR5380::NCR5380(int clock_rate) : clock_rate_(clock_rate) {
device_id_ = bus_.add_device();
}
@ -29,15 +29,15 @@ void NCR5380::write(int address, uint8_t value) {
initiator_command_ = value;
SCSI::BusState mask = SCSI::DefaultBusState;
if(value & 0x80) mask &= ~Line::Reset;
test_mode_ = !!(value & 0x40);
if(value & 0x80) mask |= Line::Reset;
test_mode_ = value & 0x40;
/* bit 5 = differential enable if this were a 5381 */
if(value & 0x10) mask &= ~Line::Acknowledge;
if(value & 0x08) mask &= ~Line::Busy;
if(value & 0x04) mask &= ~Line::SelectTarget;
if(value & 0x02) mask &= ~Line::Attention;
assert_data_bus_ = (value & 0x01);
bus_output_ = (bus_output_ | Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention) & mask;
if(value & 0x10) mask |= Line::Acknowledge;
if(value & 0x08) mask |= Line::Busy;
if(value & 0x04) mask |= Line::SelectTarget;
if(value & 0x02) mask |= Line::Attention;
assert_data_bus_ = value & 0x01;
bus_output_ = (bus_output_ & ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention)) | mask;
} break;
case 2:
@ -62,6 +62,16 @@ void NCR5380::write(int address, uint8_t value) {
can be examined to deter- mine if arbitration has been won. This delay must be
implemented in the controlling software driver.
*/
if(mode_ & 1) {
if(state_ == ExecutionState::None) {
set_execution_state(ExecutionState::WatchingBusy);
arbitration_in_progress_ = false;
lost_arbitration_ = false;
}
} else {
set_execution_state(ExecutionState::None);
}
break;
case 3:
@ -109,7 +119,15 @@ uint8_t NCR5380::read(int address) {
case 1:
LOG("[SCSI 1] Initiator command register get");
return initiator_command_;
return
// Bits repeated as they were set.
(initiator_command_ & ~0x60) |
// Arbitration in progress.
(arbitration_in_progress_ ? 0x40 : 0x00) |
// Lost arbitration.
(lost_arbitration_ ? 0x20 : 0x00);
case 2:
LOG("[SCSI 2] Get mode");
@ -119,13 +137,27 @@ uint8_t NCR5380::read(int address) {
LOG("[SCSI 3] Get target command");
return 0xff;
case 4:
case 4: {
LOG("[SCSI 4] Get current bus state");
return 0xff;
const auto bus_state = bus_.get_state();
return
((bus_state & SCSI::Line::Reset) ? 0x80 : 0x00) |
((bus_state & SCSI::Line::Busy) ? 0x40 : 0x00) |
((bus_state & SCSI::Line::Request) ? 0x20 : 0x00) |
((bus_state & SCSI::Line::Message) ? 0x10 : 0x00) |
((bus_state & SCSI::Line::Control) ? 0x08 : 0x00) |
((bus_state & SCSI::Line::Input) ? 0x04 : 0x00) |
((bus_state & SCSI::Line::SelectTarget) ? 0x02 : 0x00) |
((bus_state & SCSI::Line::Parity) ? 0x01 : 0x00);
}
case 5:
case 5: {
LOG("[SCSI 5] Get bus and status");
return 0x03;
const auto bus_state = bus_.get_state();
return
((bus_state & SCSI::Line::Attention) ? 0x02 : 0x00) |
((bus_state & SCSI::Line::Acknowledge) ? 0x01 : 0x00);
}
case 6:
LOG("[SCSI 6] Get input data");
@ -137,3 +169,50 @@ uint8_t NCR5380::read(int address) {
}
return 0;
}
void NCR5380::run_for(Cycles cycles) {
if(state_ == ExecutionState::None) return;
++time_in_state_;
switch(state_) {
default: break;
case ExecutionState::WatchingBusy:
/*
Arbitration is accomplished using a bus-free filter to continuously monitor BSY.
If BSY remains inactive for at least 400 nsec then the SCSI bus is considered free
and arbitration may begin. Arbitration will begin if the bus is free, SEL is inactive
and the ARBITRATION bit (port 2, bit 0) is active. Once arbitration has begun
(BSY asserted)...
*/
if(bus_.get_state() & SCSI::Line::Busy) {
lost_arbitration_ = true;
set_execution_state(ExecutionState::None);
}
// Check for having hit the 400ns state.
if(time_in_state_ == 400 * clock_rate_ / 1000000000) {
arbitration_in_progress_ = false;
if(bus_.get_state() & SCSI::Line::SelectTarget) {
lost_arbitration_ = true;
set_execution_state(ExecutionState::None);
} else {
bus_output_ |= SCSI::Line::Busy;
set_execution_state(ExecutionState::None);
}
}
break;
}
}
void NCR5380::set_execution_state(ExecutionState state) {
time_in_state_ = 0;
update_clocking_observer();
}
ClockingHint::Preference NCR5380::preferred_clocking() {
// Request real-time clocking if any sort of timed bus watching is ongoing,
// given that there's no knowledge in here as to what clocking other devices
// on the SCSI bus might be enjoying.
return (state_ == ExecutionState::None) ? ClockingHint::Preference::RealTime : ClockingHint::Preference::None;
}

View File

@ -13,6 +13,7 @@
#include "SCSI.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
namespace NCR {
@ -21,9 +22,9 @@ namespace NCR5380 {
/*!
Models the NCR 5380, a SCSI interface chip.
*/
class NCR5380 {
class NCR5380 final: public ClockingHint::Source {
public:
NCR5380();
NCR5380(int clock_rate);
/*! Writes @c value to @c address. */
void write(int address, uint8_t value);
@ -47,7 +48,11 @@ class NCR5380 {
*/
void run_for(Cycles);
/// As per ClockingHint::Source.
ClockingHint::Preference preferred_clocking() final;
private:
const int clock_rate_;
SCSI::Bus bus_;
size_t device_id_;
@ -57,6 +62,16 @@ class NCR5380 {
uint8_t data_bus_ = 0xff;
bool test_mode_ = false;
bool assert_data_bus_ = false;
enum class ExecutionState {
None,
WatchingBusy,
} state_ = ExecutionState::None;
int time_in_state_ = 0;
bool lost_arbitration_ = false, arbitration_in_progress_ = false;
void set_execution_state(ExecutionState state);
};
}

View File

@ -24,7 +24,7 @@ class BusHandler {
virtual void set_interrupt(bool irq) {}
};
class i8272: public Storage::Disk::MFMController {
class i8272 : public Storage::Disk::MFMController {
public:
i8272(BusHandler &bus_handler, Cycles clock_rate);
@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController {
void set_dma_acknowledge(bool dack);
void set_terminal_count(bool tc);
ClockingHint::Preference preferred_clocking() override;
ClockingHint::Preference preferred_clocking() final;
protected:
virtual void select_drive(int number) = 0;

View File

@ -26,7 +26,7 @@ namespace Apple {
/*!
Provides an emulation of the Apple Disk II.
*/
class DiskII:
class DiskII final:
public Storage::Disk::Drive::EventDelegate,
public ClockingHint::Source,
public ClockingHint::Observer {
@ -76,7 +76,7 @@ class DiskII:
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
// As per Sleeper.
ClockingHint::Preference preferred_clocking() override;
ClockingHint::Preference preferred_clocking() final;
// The Disk II functions as a potential target for @c Activity::Sources.
void set_activity_observer(Activity::Observer *observer);

View File

@ -26,6 +26,7 @@
#include "../../../Outputs/Log.hpp"
#include "../../../ClockReceiver/JustInTime.hpp"
#include "../../../ClockReceiver/ClockingHintSource.hpp"
//#define LOG_TRACE
@ -59,7 +60,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
public KeyboardMachine::MappedMachine,
public Zilog::SCC::z8530::Delegate,
public Activity::Source,
public DriveSpeedAccumulator::Delegate {
public DriveSpeedAccumulator::Delegate,
public ClockingHint::Observer {
public:
using Target = Analyser::Static::Macintosh::Target;
@ -69,6 +71,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
video_(audio_, drive_speed_accumulator_),
via_(via_port_handler_),
via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_, mouse_),
scsi_(CLOCK_RATE * 2),
drives_{
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke},
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}
@ -129,6 +132,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// Make sure interrupt changes from the SCC are observed.
scc_.set_delegate(this);
// Also watch for changes in clocking requirement from the SCSI chip.
if(model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
scsi_.set_clocking_hint_observer(this);
}
// The Mac runs at 7.8336mHz.
set_clock_rate(double(CLOCK_RATE));
audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f);
@ -477,6 +485,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
}
private:
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
scsi_is_clocked_ = scsi_.preferred_clocking() != ClockingHint::Preference::None;
}
void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) override {
iwm_.flush();
drives_[0].set_rotation_speed(speed);
@ -564,6 +576,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true);
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false);
}
// Update the SCSI if currently active.
if(model == Analyser::Static::Macintosh::Target::Model::MacPlus && scsi_is_clocked_) {
scsi_.run_for(Cycles(duration.as_int()));
}
}
forceinline void update_video() {
@ -704,6 +721,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
Zilog::SCC::z8530 scc_;
NCR::NCR5380::NCR5380 scsi_;
bool scsi_is_clocked_ = false;
HalfCycles via_clock_;
HalfCycles real_time_clock_;

View File

@ -138,7 +138,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
void set_event_delegate(EventDelegate *);
// As per Sleeper.
ClockingHint::Preference preferred_clocking() override;
ClockingHint::Preference preferred_clocking() final;
/// Adds an activity observer; it'll be notified of disk activity.
/// The caller can specify whether to add an LED based on disk motor.

View File

@ -128,7 +128,7 @@ class TapePlayer: public TimedEventLoop, public ClockingHint::Source {
They can also provide a delegate to be notified upon any change in the input level.
*/
class BinaryTapePlayer: public TapePlayer {
class BinaryTapePlayer : public TapePlayer {
public:
BinaryTapePlayer(int input_clock_rate);
void set_motor_control(bool enabled);
@ -145,7 +145,7 @@ class BinaryTapePlayer: public TapePlayer {
};
void set_delegate(Delegate *delegate);
ClockingHint::Preference preferred_clocking() override;
ClockingHint::Preference preferred_clocking() final;
protected:
Delegate *delegate_ = nullptr;