1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00

Starts thinking out the mechanics of emulating a SCSI-1 bus.

This commit is contained in:
Thomas Harte 2019-08-13 23:09:11 -04:00
parent 0f67e490e8
commit ce1c96d68c
5 changed files with 207 additions and 4 deletions

34
Components/5380/SCSI.cpp Normal file
View File

@ -0,0 +1,34 @@
//
// SCSI.cpp
// Clock Signal
//
// Created by Thomas Harte on 12/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "SCSI.hpp"
using namespace SCSI;
size_t Bus::add_device() {
const auto slot = device_states_.size();
device_states_.push_back(DefaultBusState);
return slot;
}
void Bus::set_device_output(size_t device, BusState output) {
device_states_[device] = output;
state_is_valid_ = false;
}
BusState Bus::get_state() {
if(!state_is_valid_) return state_;
state_is_valid_ = true;
state_ = DefaultBusState;
for(auto state: device_states_) {
state_ &= state;
}
return state_;
}

79
Components/5380/SCSI.hpp Normal file
View File

@ -0,0 +1,79 @@
//
// SCSI.hpp
// Clock Signal
//
// Created by Thomas Harte on 12/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef SCSI_hpp
#define SCSI_hpp
#include <limits>
#include <vector>
namespace SCSI {
typedef int BusState;
static const BusState DefaultBusState = std::numeric_limits<BusState>::max();
/*!
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,
/// Reset to indicate an attention condition. Set 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.
Busy = 1 << 12,
/// Reset if acknowledging a data transfer request. Set otherwise.
Acknowledge = 1 << 13,
/// Reset if a bus reset is being requested. Set otherwise.
Reset = 1 << 14,
/// Set if data is currently input. Reset if it is an output.
Input = 1 << 15,
/// Set during the message phase. Reset otherwise.
MessagePhase = 1 << 16
};
class Bus {
public:
/*!
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();
private:
std::vector<BusState> device_states_;
BusState state_ = DefaultBusState;
bool state_is_valid_ = false;
};
}
#endif /* SCSI_hpp */

View File

@ -12,19 +12,56 @@
using namespace NCR::NCR5380; using namespace NCR::NCR5380;
NCR5380::NCR5380() {
device_id_ = bus_.add_device();
}
void NCR5380::write(int address, uint8_t value) { void NCR5380::write(int address, uint8_t value) {
using Line = SCSI::Line;
switch(address & 7) { switch(address & 7) {
case 0: case 0:
LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value)); LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
data_bus_ = value;
break; break;
case 1: case 1: {
LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value)); LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
break; initiator_command_ = value;
SCSI::BusState mask = SCSI::DefaultBusState;
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;
} break;
case 2: case 2:
LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value)); LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value));
mode_ = value; mode_ = value;
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
// bit 6: 1 = be a SCSI target; 0 = be an initiator
// bit 5: 1 = check parity
// bit 4: 1 = generate an interrupt if parity checking is enabled and an error is found
// bit 3: 1 = generate an interrupt when an EOP is received from the DMA controller
// bit 2: 1 = generate an interrupt and reset low 6 bits of register 1 if an unexpected loss of Line::Busy occurs
// bit 1: 1 = use DMA mode
// bit 0: 1 = begin arbitration mode (device ID should be in register 0)
/*
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), an arbitration delay of 2.2 /Lsec must elapse before the data bus
can be examined to deter- mine if arbitration has been won. This delay must be
implemented in the controlling software driver.
*/
break; break;
case 3: case 3:
@ -47,17 +84,32 @@ void NCR5380::write(int address, uint8_t value) {
LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value)); LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value));
break; break;
} }
// Data is output only if the data bus is asserted.
if(assert_data_bus_) {
bus_output_ &= data_bus_;
} else {
bus_output_ |= SCSI::Line::Data;
}
// In test mode, still nothing is output. Otherwise throw out
// the current value of bus_output_.
if(test_mode_) {
bus_.set_device_output(device_id_, SCSI::DefaultBusState);
} else {
bus_.set_device_output(device_id_, bus_output_);
}
} }
uint8_t NCR5380::read(int address) { uint8_t NCR5380::read(int address) {
switch(address & 7) { switch(address & 7) {
case 0: case 0:
LOG("[SCSI 0] Get current SCSI bus state"); LOG("[SCSI 0] Get current SCSI bus state");
return 0xff; return uint8_t(bus_.get_state());
case 1: case 1:
LOG("[SCSI 1] Initiator command register get"); LOG("[SCSI 1] Initiator command register get");
return 0xff; return initiator_command_;
case 2: case 2:
LOG("[SCSI 2] Get mode"); LOG("[SCSI 2] Get mode");

View File

@ -11,6 +11,10 @@
#include <cstdint> #include <cstdint>
#include "SCSI.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace NCR { namespace NCR {
namespace NCR5380 { namespace NCR5380 {
@ -19,14 +23,40 @@ namespace NCR5380 {
*/ */
class NCR5380 { class NCR5380 {
public: public:
NCR5380();
/*! Writes @c value to @c address. */ /*! Writes @c value to @c address. */
void write(int address, uint8_t value); void write(int address, uint8_t value);
/*! Reads from @c address. */ /*! Reads from @c address. */
uint8_t read(int address); uint8_t read(int address);
/*!
As per its design manual:
"The NCR 5380 is a clockless device. Delays such as bus
free delay, bus set delay and bus settle delay are
implemented using gate delays."
Therefore this fictitious implementation of an NCR5380 needs
a way to track time even though the real one doesn't take in
a clock. This is provided by `run_for`.
Nevertheless, the clocking doesn't need to be very precise.
Please provide a clock that is close to 200,000 Hz.
*/
void run_for(Cycles);
private: private:
SCSI::Bus bus_;
size_t device_id_;
SCSI::BusState bus_output_ = SCSI::DefaultBusState;
uint8_t mode_ = 0xff; uint8_t mode_ = 0xff;
uint8_t initiator_command_ = 0xff;
uint8_t data_bus_ = 0xff;
bool test_mode_ = false;
bool assert_data_bus_ = false;
}; };
} }

View File

@ -300,6 +300,8 @@
4B89453D201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894516201967B4007DE474 /* StaticAnalyser.cpp */; }; 4B89453D201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894516201967B4007DE474 /* StaticAnalyser.cpp */; };
4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; }; 4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
4B89453F201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; }; 4B89453F201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
4B89BCFC23024BB500EA0782 /* SCSI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B89BCFA23024BB500EA0782 /* SCSI.cpp */; };
4B89BCFD23024BB500EA0782 /* SCSI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B89BCFA23024BB500EA0782 /* SCSI.cpp */; };
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; }; 4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; };
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; }; 4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; };
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */; }; 4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */; };
@ -1057,6 +1059,8 @@
4B894516201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; 4B894516201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; 4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = "<group>"; }; 4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = "<group>"; };
4B89BCFA23024BB500EA0782 /* SCSI.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SCSI.cpp; sourceTree = "<group>"; };
4B89BCFB23024BB500EA0782 /* SCSI.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SCSI.hpp; sourceTree = "<group>"; };
4B8A7E85212F988200F2BBC6 /* DeferredQueue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DeferredQueue.hpp; sourceTree = "<group>"; }; 4B8A7E85212F988200F2BBC6 /* DeferredQueue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DeferredQueue.hpp; sourceTree = "<group>"; };
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; }; 4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; };
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; }; 4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; };
@ -3327,6 +3331,8 @@
children = ( children = (
4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */, 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */,
4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */, 4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */,
4B89BCFA23024BB500EA0782 /* SCSI.cpp */,
4B89BCFB23024BB500EA0782 /* SCSI.hpp */,
); );
path = 5380; path = 5380;
sourceTree = "<group>"; sourceTree = "<group>";
@ -4037,6 +4043,7 @@
4B8318B822D3E566006DB630 /* IWM.cpp in Sources */, 4B8318B822D3E566006DB630 /* IWM.cpp in Sources */,
4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */, 4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */,
4B894535201967B4007DE474 /* AddressMapper.cpp in Sources */, 4B894535201967B4007DE474 /* AddressMapper.cpp in Sources */,
4B89BCFD23024BB500EA0782 /* SCSI.cpp in Sources */,
4B055AD41FAE9B0B0060FFFF /* Oric.cpp in Sources */, 4B055AD41FAE9B0B0060FFFF /* Oric.cpp in Sources */,
4B055A921FAE85B50060FFFF /* PRG.cpp in Sources */, 4B055A921FAE85B50060FFFF /* PRG.cpp in Sources */,
4B055AAF1FAE85FD0060FFFF /* UnformattedTrack.cpp in Sources */, 4B055AAF1FAE85FD0060FFFF /* UnformattedTrack.cpp in Sources */,
@ -4194,6 +4201,7 @@
4B894528201967B4007DE474 /* Disk.cpp in Sources */, 4B894528201967B4007DE474 /* Disk.cpp in Sources */,
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */, 4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B89BCFC23024BB500EA0782 /* SCSI.cpp in Sources */,
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */, 4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */, 4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */,