using Contralto.CPU; using Contralto.Logging; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Contralto.IO { /// /// Implements the Alto's Trident disk controller ("Tricon"). /// This can talk to up to 8 (technically 16, but the software doesn't /// appear to support more than 8) T-80 (80mb) or T-300 (300mb) drives. /// /// TODO: This implementation is preliminary and still experimental. /// There are a few major issues: /// - The state machine for the Output FIFO is a tangled mess. /// - TFU refuses to talk to more than drive 0. Unsure if this is /// a bug in the Trident emulation, a bug elsewhere in Contralto /// or user error. TriEx seems to be fine. /// - TriEx gets many errors of status "00000" (meaning that TriEx /// didn't get a response from the controller when it expected to.) /// TFU works fine and can certify, erase, and exercise packs all day. /// The TriEx issue seems to be related in some way to the timing of /// the output FIFO. Probably a subtle issue with microcode wakeups. /// Untangle the output FIFO state machine and revisit this. /// - There is at this time no way to toggle the Read Only switch /// on the emulated drive. (All drives are read/write). /// - The Trident sector wakeup signal needs to go away when the TriCon /// is put to sleep (for performance reasons, no sense keeping the /// scheduler busy for no reason.) /// public class TridentController { public TridentController(AltoSystem system) { _system = system; // // We initialize 16 drives even though the // controller only technically supports 8. // TODO: detail // _drives = new TridentDrive[16]; for(int i=0;i<_drives.Length;i++) { _drives[i] = new TridentDrive(system); } Reset(); } public void Reset() { _runEnable = false; _seekIncomplete = false; _headOverflow = false; _deviceCheck = false; _notSelected = false; _sectorOverflow = false; _outputLate = false; _inputLate = false; _compareError = false; _readOnly = false; _offset = false; _selectedDrive = 0; _sector = 0; _checkDone = false; _empty = false; _pauseOutputProcessing = false; _readState = ReadState.Idle; _writeState = WriteState.Idle; _outputFifo = new Queue(); _inputFifo = new Queue(); _sectorEvent = new Event(0, null, SectorCallback); _outputFifoEvent = new Event(0, null, OutputFifoCallback); _readWordEvent = new Event(0, null, ReadWordCallback); // And schedule the first sector pulse. _system.Scheduler.Schedule(_sectorEvent); } /// /// "RESET INPUT FIFO AND CLEAR ERRORS" /// public void ControllerReset() { _seekIncomplete = false; _headOverflow = false; _deviceCheck = false; _notSelected = false; _sectorOverflow = false; _outputLate = false; _inputLate = false; _compareError = false; _readOnly = false; _offset = false; _writeState = WriteState.Idle; _readState = ReadState.Idle; _empty = false; //_pauseOutputProcessing = false; ClearInputFIFO(); } public void CommitDisk(int drive) { TridentDrive d = _drives[drive]; if (d.IsLoaded) { try { d.Pack.Save(); } catch (Exception e) { // TODO: this does not really belong here. System.Windows.Forms.MessageBox.Show(String.Format("Unable to save Trident disk {0}'s contents. Error {1}. Any changes have been lost.", drive, e.Message), "Disk save error"); } } } public TridentDrive[] Drives { get { return _drives; } } public void STARTF(ushort value) { // // "An SIO with bit 10 set will cause Run-Enable to be set. // An SIO with bit 11 set to one will cause Run-Enable to be // reset." // if ((value & 0x10) != 0) { _runEnable = false; _system.CPU.BlockTask(TaskType.TridentInput); _system.CPU.BlockTask(TaskType.TridentOutput); //ClearOutputFIFO(); //ClearInputFIFO(); } if ((value & 0x20) != 0) { _runEnable = true; // "Issuing an SIO with bit 10 set will wake up the microcode // once and thus may report status in the absence of sector // pulses from the disk." // So do that now. // Based on a reading of the microcode and schematics it looks like the // Write (Output) task is woken up here. // // Clear error flags. // _inputLate = false; _outputLate = false; _sectorOverflow = false; // _empty = false; // _pauseOutputProcessing = false; // // Clear the output FIFO ClearOutputFIFO(); ClearInputFIFO(); //_system.CPU.WakeupTask(TaskType.TridentInput); _system.CPU.WakeupTask(TaskType.TridentOutput); //_system.CPU.BlockTask(TaskType.TridentInput); //_system.CPU.BlockTask(TaskType.TridentOutput); } Log.Write(LogComponent.TridentController, "Trident STARTF {0}", Conversion.ToOctal(value)); } public void Stop() { _runEnable = false; _system.CPU.BlockTask(TaskType.TridentInput); _system.CPU.BlockTask(TaskType.TridentOutput); } public ushort KDTA { get { Log.Write(LogComponent.TridentController, "Trident KDTA read"); return DequeueInputFIFO(); } set { Log.Write(LogComponent.TridentController, "------> Trident KDTA queued {0}", Conversion.ToOctal(value)); WriteOutputFifo(value); } } public ushort STATUS { get { // // The status bits from the controller are: // 0 - Seek Incomplete : drive was unable to correctly position the heads, a Rezero // must be issued to clear this error. // 1 - Head Overflow : head address given to the drive is invalid. // 2 - Device check : One of the following errors occurred // a) Head select or Cylinder select or Write commands and disk not ready // b) An illegal cylinder address. // c) Offset active and cylinder select command. // d) Read-Only and write // e) Certain errors during writing -- multiple heads selected, various read errors) // 3 - Not selected : The selected drive is off-line or not powered up. // 4 - Not Online : The drive is in test mode or the heads are not loaded. // 5 - Not Ready : There is a cylinder seek in progress or the heads are not loaded // 6 - Sector Overflow : The controller detected a write command was active when the next // sector pulse occurred. // 7 - Output Late : The 16 word output buffer became empty while a read or write command // was in progress. // 8 - Input Late : The 16 word input buffer became full. // 9 - Compare Error : The data read during a "Read and Compare" operation did not match // the data read off the disk. // 10 - Read Only : The "Read-Only" switch on the disk drive is on. // 11 - Offset : The cylinder position is currently offset. This is a mode used for // recovery of bad data. (NOTE: Not emulated.) // 12-15 Sector count : A value from 0 to count-1, where count is the number of sectors // implemented in the disk drive. The value returned here is the sector // count for the *next sector* on the disk. ushort status = (ushort)( (_seekIncomplete ? 0x8000 : 0) | (_headOverflow ? 0x4000 : 0) | (_deviceCheck ? 0x2000 : 0) | (_notSelected ? 0x1000 : 0) | (NotOnline() ? 0x0800 : 0) | (NotReady() ? 0x0400 : 0) | (_sectorOverflow ? 0x0200 : 0) | (_outputLate ? 0x0100 : 0) | (_inputLate ? 0x0080 : 0) | (_compareError ? 0x0040 : 0) | (SelectedDrive.ReadOnly ? 0x0020 : 0) | (_offset ? 0x0010 : 0) | ((_sector) & 0x000f)); Log.Write(LogComponent.TridentController, "STATUS word is {0}", Conversion.ToOctal(status)); return status; } } public void TagInstruction(ushort tag) { Log.Write(LogComponent.TridentController, "------> Trident tag instruction queued {0}", Conversion.ToOctal(tag)); // // Add tag to the output FIFO, with bit 16 set, identifying it as a tag. // WriteOutputFifo(0x10000 | tag); } public void WaitForEmpty() { // Block the Output task; it will be awoken when the output FIFO is emptied. if (_outputFifo.Count > 0) { _system.CPU.BlockTask(TaskType.TridentOutput); _empty = true; Log.Write(LogComponent.TridentController, "Trident output task blocked until Output FIFO emptied."); } } private bool NotReady() { return SelectedDrive.NotReady | !SelectedDrive.IsLoaded | _selectedDrive > 7; } private bool NotOnline() { return _selectedDrive < 8 ? !SelectedDrive.IsLoaded : true; } private void WriteOutputFifo(int value) { if (_outputFifo.Count > 16) { Log.Write(LogType.Error, LogComponent.TridentController, "Output FIFO full, dropping word."); return; } EnqueueOutputFIFO(value); // // If this is the first word written to an empty FIFO, and we aren't waiting for the next // sector pulse, queue up the fifo callback to pull words and process them. // if (!_pauseOutputProcessing && _outputFifo.Count == 1) { _outputFifoEvent.TimestampNsec = _outputFifoDuration; _system.Scheduler.Schedule(_outputFifoEvent); } } private void OutputFifoCallback(ulong timeNsec, ulong skewNsec, object context) { ProcessCommandOutput(); } private void ProcessCommandOutput() { if (_outputFifo.Count == 0) { if (_writeState == WriteState.Writing) { // // We have run out of data on a write. // _outputLate = true; Log.Write(LogComponent.TridentController, "Output FIFO underflowed."); } return; } if (_readState != ReadState.Reading) { int fifoWord = DequeueOutputFIFO(); if ((fifoWord & 0x10000) != 0) { // // This is a Tag word; process it accordingly. // Log.Write(LogComponent.TridentController, "Tag word {0} pulled from Output FIFO.", Conversion.ToOctal((ushort)fifoWord)); // // From the TRICON schematic (page 16): // Bit 3 is the Enable bit, which enables selection of one of four commands // to the drive, specified in bits 1 and 2: // (and as usual, these are in the backwards Nova/Alto bit ordering scheme) // Bits 1 and 2 decode to: // 0 0 - Control // 0 1 - Set Head // 1 0 - Set Cylinder // 1 1 - Set Drive // // Head, Cylinder, Drive are straightforward -- the lower bits of the tag // word contain the data for the command. // // The Control bits are described in the Trident T300 Theory of Operations manual, // Page 3-13 and are the lower 10 bits of the tag command: // // 0 - Strobe Late : Skews read data detection 4ns late for attempted read-error recovery. // 1 - Strobe Early : Same as above, but early. // 2 - Write : Turns on circuits to write data, // 3 - Read : Turns on read circuits an resets Attention interrupts. // 4 - Address Mark : Commands an address mark to be generated, if writing; or // enables the address mark detector, if reading. // 5 - Reset Head Register : Resets HAR to 0 // 6 - Device Check Reset : Resets most types of Device Check errors unless an error // condition is still present. // 7 - Head Select : Tuns on the head-selection circuits. Head select must be active 5 // or 15 microseconds before Write or Read is commanded, respectively. // 8 - Rezero : Repositions the heads to cylinder 000, selects Head Address 0, and resets // some types of Device Checks. // 9 - Head Advance : Increases Head Address count by one. // // // Bit 0 of the Tag word, if set, tells the controller to hold off Output FIFO processing // until the next sector pulse. // if ((fifoWord & 0x8000) != 0) { _pauseOutputProcessing = true; Log.Write(LogComponent.TridentController, "Output FIFO processing paused until next sector pulse."); } // See if the enable bit (3) is set, in which case this is a command to the drive // if ((fifoWord & 0x1000) != 0) { // // Switch on the specific command switch ((fifoWord & 0x6000) >> 13) { case 0: // Control Log.Write(LogComponent.TridentController, "Control word."); if ((fifoWord & 0x0001) != 0) // 9 - Head advance { if (!SelectedDrive.IsLoaded) { _deviceCheck = true; } else { if (SelectedDrive.Head + 1 >= SelectedDrive.Pack.Geometry.Heads) { _headOverflow = true; _deviceCheck = true; Log.Write(LogComponent.TridentController, "Head {0} is out of range on Head Advance.", SelectedDrive.Head + 1); } else { SelectedDrive.Head++; Log.Write(LogComponent.TridentController, "Control: Head Advance. Head is now {0}", SelectedDrive.Head); } } } if ((fifoWord & 0x0002) != 0) // 8 - Rezero { _deviceCheck = false; SelectedDrive.Head = 0; InitSeek(0); Log.Write(LogComponent.TridentController, "Control: Rezero."); } if ((fifoWord & 0x0004) != 0) // 7 - Head select { Log.Write(LogComponent.TridentController, "Control: Head Select."); if (!SelectedDrive.IsLoaded) { _deviceCheck = true; } // TODO: technically this needs to be active before a write or read is selected. Do I care? } if ((fifoWord & 0x0008) != 0) // 6 - Device Check Reset { Log.Write(LogComponent.TridentController, "Control: Device Check Reset."); _deviceCheck = false; } if ((fifoWord & 0x0010) != 0) // 5 - Reset Head Register { Log.Write(LogComponent.TridentController, "Control: Reset Head Register."); SelectedDrive.Head = 0; } if ((fifoWord & 0x0020) != 0) // 4 - Address Mark { Log.Write(LogComponent.TridentController, "Control: Address mark."); // Not much to do here, emulation-wise. } if ((fifoWord & 0x0040) != 0) // 3 - Read { Log.Write(LogComponent.TridentController, "Control: Read."); // // Commence reading -- start reading a word at a time into the input FIFO, // Waking up the Input task as necessary. // if (NotReady()) { _deviceCheck = true; } else { InitRead(); } } if ((fifoWord & 0x0080) != 0) // 2 - Write { Log.Write(LogComponent.TridentController, "Control: Write."); // // Commence writing -- start pulling a word at a time out of the output FIFO, // Waking up the Output task as necessary. // if (NotReady()) { _deviceCheck = true; } else { InitWrite(); } } if ((fifoWord & 0x0100) != 0) // 1 - Strobe Early { Log.Write(LogComponent.TridentController, "Control: Strobe Early."); // Not going to emulate this, as fun as it sounds. } if ((fifoWord & 0x0200) != 0) // 0 - Strobe Late { Log.Write(LogComponent.TridentController, "Control: Strobe Late."); // Not going to emulate this either. } break; case 1: // Set Head int head = fifoWord & 0x1f; // low 5 bits Log.Write(LogComponent.TridentController, "Command is Set Head {0}", head); if (!SelectedDrive.IsLoaded) { _deviceCheck = true; } else { if (head >= SelectedDrive.Pack.Geometry.Heads) { _headOverflow = true; _deviceCheck = true; Log.Write(LogComponent.TridentController, "Head {0} is out of range.", head); } else { SelectedDrive.Head = head; } } break; case 2: // Set Cylinder int cyl = fifoWord & 0x3ff; // low 10 bits Log.Write(LogComponent.TridentController, "Command is Set Cylinder {0}.", cyl); if (NotReady()) { _deviceCheck = true; } else { InitSeek(cyl); } break; case 3: // Set Drive // We take all four drive-select bits even though only 8 drives are actually supported. // The high bit is used by many trident utilities to select an invalid drive to test for // the presence of the 8-drive multiplexer. _selectedDrive = fifoWord & 0xf; if (_selectedDrive > 7) { _system.CPU.BlockTask(TaskType.TridentOutput); _notSelected = true; } else { _notSelected = false; } Log.Write(LogComponent.TridentController, "Command is Set Drive {0}", _selectedDrive); break; } } else { // // There are two values we expect to see with the enable bit off: // 0 is sent to reset the tag bus (to the drive) to zeros, we simply ignore it. // 2 is sent because the drive expects the Rezero signal to be held for a time // after the Control signal is dropped. We will commence a Rezero operation // if we get one here. // if (fifoWord == 2) { Log.Write(LogComponent.TridentController, "Rezero started."); } else if (fifoWord != 0) { Log.Write(LogComponent.TridentController, "Unexpected Tag word without enable bit set: {0}", Conversion.ToOctal(fifoWord)); } } } else { // // This is a data word. // Log.Write(LogComponent.TridentController, "Data word {0} pulled from Output FIFO.", Conversion.ToOctal((ushort)fifoWord)); if (_writeState != WriteState.Idle) { switch (_writeState) { case WriteState.Initialized: // This word should be the MAX number of words to check. _writeWordCount = fifoWord - 1; _writeState = WriteState.WaitingForStartBit; Log.Write(LogComponent.TridentController, "Write initializing {0} words (max) to write.", _writeWordCount); break; case WriteState.WaitingForStartBit: if (fifoWord == 1) { Log.Write(LogComponent.TridentController, "Start bit recognized, commencing write operation."); _writeState = WriteState.Writing; } break; case WriteState.Writing: ProcessDiskWrite(fifoWord); break; } } if (_readState != ReadState.Idle) { switch (_readState) { case ReadState.Initialized: _readWordCount = fifoWord - 1; _readState = ReadState.Reading; Log.Write(LogType.Verbose, LogComponent.TridentController, "Read initializing {0} words (max) to read.", _readWordCount); break; default: throw new InvalidOperationException("Unexpected ReadState in ProcessCommandOutput."); } } } } if (!_pauseOutputProcessing && _outputFifo.Count > 0) { // // Schedule next FIFO wakeup. _outputFifoEvent.TimestampNsec = _outputFifoDuration; _system.Scheduler.Schedule(_outputFifoEvent); } } private void InitSeek(int destCylinder) { _deviceCheck = !SelectedDrive.Seek(destCylinder); } private void InitRead() { if (_readState == ReadState.Idle) { _readState = ReadState.Initialized; _checkedWordCount = 0; _readIndex = 0; _readWordCount = 0; _checkDone = false; // Clear the FIFO: TODO check schematics. // ClearInputFIFO(); _readWordEvent.TimestampNsec = _readWordDuration * 10; _system.Scheduler.Schedule(_readWordEvent); } else { // Unexpected, throw for now. throw new InvalidOperationException("Unexpected Read command while read active."); } } private void ReadWordCallback(ulong timeNsec, ulong skewNsec, object context) { if (_readWordCount > 0) { // Enqueue data from disk. ushort dataWord; switch (_sectorBlock) { case SectorBlock.Header: dataWord = SelectedDrive.ReadHeader(_readIndex); break; case SectorBlock.Label: dataWord = SelectedDrive.ReadLabel(_readIndex); break; case SectorBlock.Data: dataWord = SelectedDrive.ReadData(_readIndex); break; default: throw new InvalidOperationException(String.Format("Unexpected Sector Block of {0} on read.", _sectorBlock)); } Log.Write(LogType.Verbose, LogComponent.TridentController, "Read word {0}:{1}", _readIndex, Conversion.ToOctal(dataWord)); EnqueueInputFIFO(dataWord); // // Compare data with check data in output fifo, if any. // From the microcode comments: // "Note that the first two words of a block are always checked, // followed by additional words until a zero word or the end of the block." // // If we hit a tag word, the check is also completed. // TODO: verify this w/schematic & microcode. if (_outputFifo.Count > 0) { int checkWord = _outputFifo.Peek(); if ((checkWord & 0x10000) == 0 && (!_checkDone || _checkedWordCount < 2)) { // Actually pull the word off checkWord = (ushort)DequeueOutputFIFO(); Log.Write(LogType.Verbose, LogComponent.TridentController, "Pulled checkword {0} from output FIFO", Conversion.ToOctal(checkWord)); // A zero word indicates the check is complete (we will // still read the minimum two words in) if (checkWord == 0) { _checkDone = true; } // Compare didn't. else if (checkWord != dataWord) { _compareError = true; } _checkedWordCount++; } } } else { // Last four words (0 through -3) are checksum and ECC words. // The checksum words are ignored by the microcode, and // since we're not simulating faulty disks, these are always zero. EnqueueInputFIFO(0); Log.Write(LogComponent.TridentController, "Read ECC/checksum word 0"); } _readIndex++; _readWordCount--; if (_readWordCount > -4) { // More words to read, queue up the next. _readWordEvent.TimestampNsec = _readWordDuration; _system.Scheduler.Schedule(_readWordEvent); } else { Log.Write(LogComponent.TridentController, "CHS {0}/{1}/{2} {3} read from drive {4} complete.", SelectedDrive.Cylinder, SelectedDrive.Head, _sector, _sectorBlock, _selectedDrive); _readState = ReadState.Idle; _sectorBlock++; } } private void InitWrite() { if (_writeState == WriteState.Idle) { Log.Write(LogComponent.TridentController, "Write primed, waiting for start bit."); _writeState = WriteState.Initialized; _writeIndex = 0; } else { // Unexpected, throw for now. throw new InvalidOperationException("Unexpected Write command while write active."); } } private void ProcessDiskWrite(int dataWord) { // // Sanity check: if there's a tag bit set, we've picked up some // invalid data... // if ((dataWord & 0x10000) != 0) { Log.Write(LogType.Error, LogComponent.TridentController, "Tag bit set in data word during Write ({0}).", Conversion.ToOctal(dataWord)); } Log.Write(LogComponent.TridentController, "Write word {0}:{1}", _writeIndex, Conversion.ToOctal(dataWord)); // // Commit to the proper block in the sector: // switch(_sectorBlock) { case SectorBlock.Header: SelectedDrive.WriteHeader(_writeIndex, (ushort)dataWord); break; case SectorBlock.Label: SelectedDrive.WriteLabel(_writeIndex, (ushort)dataWord); break; case SectorBlock.Data: SelectedDrive.WriteData(_writeIndex, (ushort)dataWord); break; default: throw new InvalidOperationException(String.Format("Unexpected Sector Block of {0} on write.", _sectorBlock)); } _writeIndex++; _writeWordCount--; if (_writeWordCount <= 0) { Console.WriteLine( "CHS {0}/{1}/{2} {3} write to drive {4} complete. {5} words written.", SelectedDrive.Cylinder, SelectedDrive.Head, _sector, _sectorBlock, _selectedDrive, _writeIndex); _writeState = WriteState.Idle; _writeIndex = 0; // Move to the next block _sectorBlock++; } } private void SectorCallback(ulong timeNsec, ulong skewNsec, object context) { // Move to the next sector. if (_runEnable && !NotReady()) { _sector = (_sector + 1) % 9; SelectedDrive.Sector = _sector; // Reset to the first block (header) in the sector. _sectorBlock = SectorBlock.Header; // Wake up the Output task _system.CPU.WakeupTask(TaskType.TridentOutput); } else { // Keep the output task asleep. _system.CPU.BlockTask(TaskType.TridentOutput); } // // Schedule the next sector pulse // _sectorEvent.TimestampNsec = _sectorDuration - skewNsec; _system.Scheduler.Schedule(_sectorEvent); // // If output fifo processing was paused and there's data in the FIFO // to be dealt with, wake up the callback now. // if (_pauseOutputProcessing && _outputFifo.Count > 0) { _pauseOutputProcessing = false; _outputFifoEvent.TimestampNsec = _outputFifoDuration; _system.Scheduler.Schedule(_outputFifoEvent); } } private TridentDrive SelectedDrive { get { return _drives[_selectedDrive]; } } // // Input FIFO semantics // private void ClearInputFIFO() { _inputFifo.Clear(); _system.CPU.BlockTask(TaskType.TridentInput); } private void EnqueueInputFIFO(ushort word) { if (_inputFifo.Count == 16) { // We have overflowed the input FIFO. Set the requisite error // flags and shut this thing down. _inputLate = true; Log.Write(LogComponent.TridentController, "Input FIFO overflowed."); } else { _inputFifo.Enqueue(word); if (_inputFifo.Count >= 4) { _system.CPU.WakeupTask(TaskType.TridentInput); } else { _system.CPU.BlockTask(TaskType.TridentInput); } } } private ushort DequeueInputFIFO() { ushort word = 0; if (_inputFifo.Count == 0) { Log.Write(LogType.Warning, LogComponent.TridentController, "Input FIFO underflowed, returning 0."); } else { word = _inputFifo.Dequeue(); } if (_inputFifo.Count < 4) { _system.CPU.BlockTask(TaskType.TridentInput); } else { _system.CPU.WakeupTask(TaskType.TridentInput); } return word; } // // Output FIFO semantics // private void ClearOutputFIFO() { _outputFifo.Clear(); _system.CPU.WakeupTask(TaskType.TridentOutput); } private void EnqueueOutputFIFO(int word) { if (_outputFifo.Count == 16) { Log.Write(LogComponent.TridentController, "Output FIFO overflowed, dropping word."); } else { _outputFifo.Enqueue(word); Log.Write(LogComponent.TridentController, "Output FIFO enqueued, queue depth is now {0}", _outputFifo.Count); } if (_outputFifo.Count <= 12) { _system.CPU.WakeupTask(TaskType.TridentOutput); } else { _system.CPU.BlockTask(TaskType.TridentOutput); } } private int DequeueOutputFIFO() { int word = 0; if (_outputFifo.Count == 0) { Log.Write(LogType.Warning, LogComponent.TridentController, "Output FIFO underflowed, returning 0."); } else { word = _outputFifo.Dequeue(); Log.Write(LogComponent.TridentController, "Output FIFO dequeued, queue depth is now {0}", _outputFifo.Count); } if (_empty) { // Only wake up the Output task when the output FIFO goes completely empty. if (_outputFifo.Count == 0) { _empty = false; _system.CPU.WakeupTask(TaskType.TridentOutput); Log.Write(LogComponent.TridentController, "Output FIFO emptied, waking Output task."); } else { _system.CPU.BlockTask(TaskType.TridentOutput); } } else { if (_outputFifo.Count <= 12) { _system.CPU.WakeupTask(TaskType.TridentOutput); } else { _system.CPU.BlockTask(TaskType.TridentOutput); } } return word; } // Wakeup signals private bool _runEnable; // Status bits private bool _seekIncomplete; private bool _headOverflow; private bool _deviceCheck; private bool _notSelected; private bool _sectorOverflow; private bool _outputLate; private bool _inputLate; private bool _compareError; private bool _readOnly; private bool _offset; // Current sector private int _sector; /// /// Drive timings /// private static ulong _sectorDuration = (ulong)((16.66666 / 9.0) * Conversion.MsecToNsec); // time in nsec for one sector -- 9 sectors, 16.66ms per rotation private Event _sectorEvent; // Output FIFO. This is actually 17-bits wide (the 17th bit is the Tag bit). private Queue _outputFifo; private Event _outputFifoEvent; private bool _pauseOutputProcessing; private bool _empty; private static ulong _outputFifoDuration = (ulong)(_sectorDuration / 2048.0); // time in nsec for one word -- 1120 words per sector. // Input FIFO. private Queue _inputFifo; // // Read timings // private static ulong _readWordDuration = (ulong)(_sectorDuration / 1120.0); // time in nsec for one word -- 1120 words per sector. private Event _readWordEvent; private int _readIndex; private int _readWordCount; private bool _checkDone; private int _checkedWordCount; // // Write timings // private static ulong _writeWordDuration = (ulong)(_sectorDuration / 1120.0); // time in nsec for one word -- 1120 words per sector. private int _writeIndex; private int _writeWordCount; private enum WriteState { Idle, Initialized, WaitingForStartBit, Writing, } private WriteState _writeState; private enum ReadState { Idle, Initialized, WaitingForStartBit, Reading, } private ReadState _readState; private enum SectorBlock { Header, Label, Data, } private SectorBlock _sectorBlock; // // Attached drives // private TridentDrive[] _drives; private int _selectedDrive; private AltoSystem _system; } }