diff --git a/Components/5380/SCSI.cpp b/Components/5380/SCSI.cpp new file mode 100644 index 000000000..f19b70a08 --- /dev/null +++ b/Components/5380/SCSI.cpp @@ -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_; +} diff --git a/Components/5380/SCSI.hpp b/Components/5380/SCSI.hpp new file mode 100644 index 000000000..455176dc4 --- /dev/null +++ b/Components/5380/SCSI.hpp @@ -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 +#include + +namespace SCSI { + +typedef int BusState; + +static const BusState DefaultBusState = std::numeric_limits::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 device_states_; + BusState state_ = DefaultBusState; + bool state_is_valid_ = false; +}; + +} + +#endif /* SCSI_hpp */ diff --git a/Components/5380/ncr5380.cpp b/Components/5380/ncr5380.cpp index 5a3914b12..99ae9c2e7 100644 --- a/Components/5380/ncr5380.cpp +++ b/Components/5380/ncr5380.cpp @@ -12,19 +12,56 @@ using namespace NCR::NCR5380; +NCR5380::NCR5380() { + device_id_ = bus_.add_device(); +} + void NCR5380::write(int address, uint8_t value) { + using Line = SCSI::Line; switch(address & 7) { case 0: LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value)); + data_bus_ = value; break; - case 1: + case 1: { 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: LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(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; 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)); 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) { switch(address & 7) { case 0: LOG("[SCSI 0] Get current SCSI bus state"); - return 0xff; + return uint8_t(bus_.get_state()); case 1: LOG("[SCSI 1] Initiator command register get"); - return 0xff; + return initiator_command_; case 2: LOG("[SCSI 2] Get mode"); diff --git a/Components/5380/ncr5380.hpp b/Components/5380/ncr5380.hpp index df6353433..8a3e48907 100644 --- a/Components/5380/ncr5380.hpp +++ b/Components/5380/ncr5380.hpp @@ -11,6 +11,10 @@ #include +#include "SCSI.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" + + namespace NCR { namespace NCR5380 { @@ -19,14 +23,40 @@ namespace NCR5380 { */ class NCR5380 { public: + NCR5380(); + /*! Writes @c value to @c address. */ void write(int address, uint8_t value); /*! Reads from @c 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: + SCSI::Bus bus_; + size_t device_id_; + + SCSI::BusState bus_output_ = SCSI::DefaultBusState; uint8_t mode_ = 0xff; + uint8_t initiator_command_ = 0xff; + uint8_t data_bus_ = 0xff; + bool test_mode_ = false; + bool assert_data_bus_ = false; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 38bc4eb0d..e95f436e2 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -300,6 +300,8 @@ 4B89453D201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894516201967B4007DE474 /* StaticAnalyser.cpp */; }; 4B89453E201967B4007DE474 /* 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 */; }; 4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.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 = ""; }; 4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; 4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = ""; }; + 4B89BCFA23024BB500EA0782 /* SCSI.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SCSI.cpp; sourceTree = ""; }; + 4B89BCFB23024BB500EA0782 /* SCSI.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SCSI.hpp; sourceTree = ""; }; 4B8A7E85212F988200F2BBC6 /* DeferredQueue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DeferredQueue.hpp; sourceTree = ""; }; 4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = ""; }; 4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = ""; }; @@ -3327,6 +3331,8 @@ children = ( 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */, 4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */, + 4B89BCFA23024BB500EA0782 /* SCSI.cpp */, + 4B89BCFB23024BB500EA0782 /* SCSI.hpp */, ); path = 5380; sourceTree = ""; @@ -4037,6 +4043,7 @@ 4B8318B822D3E566006DB630 /* IWM.cpp in Sources */, 4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */, 4B894535201967B4007DE474 /* AddressMapper.cpp in Sources */, + 4B89BCFD23024BB500EA0782 /* SCSI.cpp in Sources */, 4B055AD41FAE9B0B0060FFFF /* Oric.cpp in Sources */, 4B055A921FAE85B50060FFFF /* PRG.cpp in Sources */, 4B055AAF1FAE85FD0060FFFF /* UnformattedTrack.cpp in Sources */, @@ -4194,6 +4201,7 @@ 4B894528201967B4007DE474 /* Disk.cpp in Sources */, 4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */, 4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */, + 4B89BCFC23024BB500EA0782 /* SCSI.cpp in Sources */, 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, 4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */, 4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */,