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:
parent
0f67e490e8
commit
ce1c96d68c
34
Components/5380/SCSI.cpp
Normal file
34
Components/5380/SCSI.cpp
Normal 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
79
Components/5380/SCSI.hpp
Normal 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 */
|
@ -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");
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 */,
|
||||||
|
Loading…
Reference in New Issue
Block a user