diff --git a/Components/5380/SCSI.cpp b/Components/5380/SCSI.cpp index f19b70a08..796a0d7f0 100644 --- a/Components/5380/SCSI.cpp +++ b/Components/5380/SCSI.cpp @@ -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_; diff --git a/Components/5380/SCSI.hpp b/Components/5380/SCSI.hpp index 455176dc4..69d087a6e 100644 --- a/Components/5380/SCSI.hpp +++ b/Components/5380/SCSI.hpp @@ -16,7 +16,7 @@ namespace SCSI { typedef int BusState; -static const BusState DefaultBusState = std::numeric_limits::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, }; diff --git a/Components/5380/ncr5380.cpp b/Components/5380/ncr5380.cpp index 99ae9c2e7..954e2d5e1 100644 --- a/Components/5380/ncr5380.cpp +++ b/Components/5380/ncr5380.cpp @@ -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; +} diff --git a/Components/5380/ncr5380.hpp b/Components/5380/ncr5380.hpp index 8a3e48907..57ce6510a 100644 --- a/Components/5380/ncr5380.hpp +++ b/Components/5380/ncr5380.hpp @@ -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); }; } diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 566da8ca5..3e6f83c09 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -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; diff --git a/Components/DiskII/DiskII.hpp b/Components/DiskII/DiskII.hpp index 8f5cd92f8..252f1bbae 100644 --- a/Components/DiskII/DiskII.hpp +++ b/Components/DiskII/DiskII.hpp @@ -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 &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); diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index 63d3433c5..e84689238 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -26,6 +26,7 @@ #include "../../../Outputs/Log.hpp" #include "../../../ClockReceiver/JustInTime.hpp" +#include "../../../ClockReceiver/ClockingHintSource.hpp" //#define LOG_TRACE @@ -59,7 +60,8 @@ template 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 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 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 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 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 class ConcreteMachin Zilog::SCC::z8530 scc_; NCR::NCR5380::NCR5380 scsi_; + bool scsi_is_clocked_ = false; HalfCycles via_clock_; HalfCycles real_time_clock_; diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 5bf2c0bde..d661e14c2 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -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. diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index 47b7bbd1d..b476237f0 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -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;