1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00
CLK/Storage/MassStorage/SCSI/SCSI.hpp
2024-01-16 23:40:40 -05:00

169 lines
5.3 KiB
C++

//
// SCSI.hpp
// Clock Signal
//
// Created by Thomas Harte on 12/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#pragma once
#include <array>
#include <limits>
#include <vector>
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../ClockReceiver/ClockingHintSource.hpp"
#include "../../../Activity/Source.hpp"
namespace SCSI {
/// Provides the current state of the SCSI bus, being comprised of a bitwise combination
/// of zero or more of the @c BusState flags defined below.
typedef int BusState;
constexpr BusState DefaultBusState = 0;
/*!
SCSI bus state is encoded entirely within an int.
Bits correlate mostly but not exactly to the real SCSI bus.
TODO: validate levels below. The bus uses open collector logic,
so active low needs to be respected.
*/
enum Line: BusState {
/// Provides the value currently on the data lines.
Data = 0xff,
/// Parity of the data lines.
Parity = 1 << 8,
/// Set if the SEL line is currently selecting a target.
/// Reset if it is selecting an initiator.
SelectTarget = 1 << 9,
/// 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,
/// Set if the bus is busy. Reset otherwise.
Busy = 1 << 12,
/// Set if acknowledging a data transfer request. Reset otherwise.
Acknowledge = 1 << 13,
/// Set if a bus reset is being requested. Reset otherwise.
Reset = 1 << 14,
/// 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.
Message = 1 << 16,
/// Set if requesting a data transfer. Reset otherwise.
Request = 1 << 17,
};
constexpr double us(double t) { return t / 1'000'000.0; }
constexpr double ns(double t) { return t / 1'000'000'000.0; }
/// The minimum amount of time that reset must be held for.
constexpr double ResetHoldTime = us(25.0);
/// The minimum amount of time a SCSI device must wait after asserting ::Busy
/// until the data bus can be inspected to see whether arbitration has been won.
constexpr double ArbitrationDelay = us(1.7);
/// The maximum amount of time a SCSI device can take from a detection that the
/// bus is free until it asserts ::Busy and its ID for the purposes of arbitration.
constexpr double BusSetDelay = us(1.1);
/// The maximum amount of time a SCSI device is permitted to take to stop driving
/// all bus signals after: (i) the release of ::Busy ushering in a bus free phase;
/// or (ii) some other device has asserted ::Select during an arbitration phase.
constexpr double BusClearDelay = ns(650.0);
/// The minimum amount of time to wait for the bus to settle after changing
/// "certain control signals". TODO: which?
constexpr double BusSettleDelay = ns(450.0);
/// The minimum amount of time a SCSI must wait from detecting that the bus is free
/// and asserting ::Busy if starting an arbitration phase.
constexpr double BusFreeDelay = ns(100.0);
/// The minimum amount of time required for deskew of "certain signals". TODO: which?
constexpr double DeskewDelay = ns(45.0);
/// The maximum amount of time that propagation of a SCSI bus signal can take between
/// any two devices.
constexpr double CableSkew = ns(10.0);
/*!
@returns The value of the data lines per @c state.
*/
constexpr uint8_t data_lines(BusState state) {
return uint8_t(state & 0xff);
}
#undef ns
#undef us
class Bus: public ClockingHint::Source, public Activity::Source {
public:
Bus(HalfCycles clock_rate);
/*!
Adds a device to the bus, returning the index it should use
to refer to itself in subsequent calls to set_device_output.
*/
size_t add_device();
/*!
Sets the current output for @c device.
*/
void set_device_output(size_t device, BusState output);
/*!
@returns the current state of the bus.
*/
BusState get_state() const;
struct Observer {
/// Reports to an observer that the bus changed from a previous state to @c new_state,
/// along with the time since that change was observed. The time is in seconds, and is
/// intended for comparison with the various constants defined at namespace scope:
/// ArbitrationDelay et al. Observers will be notified each time one of the thresholds
/// defined by those constants is crossed.
virtual void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) = 0;
};
/*!
Adds an observer.
*/
void add_observer(Observer *);
/*!
SCSI buses don't have a clock. But devices on the bus are concerned with time-based factors,
and `run_for` is the way that time propagates within this emulator. So please permit this
fiction.
*/
void run_for(HalfCycles);
/*!
Forces a `scsi_bus_did_change` propagation now.
*/
void update_observers();
// As per ClockingHint::Source.
ClockingHint::Preference preferred_clocking() const final;
// Fulfilling public Activity::Source.
void set_activity_observer(Activity::Observer *observer) final;
private:
HalfCycles time_in_state_;
double cycles_to_time_ = 1.0;
size_t dispatch_index_ = 0;
std::array<Cycles::IntType, 8> dispatch_times_;
std::vector<BusState> device_states_;
BusState state_ = DefaultBusState;
std::vector<Observer *> observers_;
Activity::Observer *activity_observer_ = nullptr;
};
}