using Contralto.IO; using Contralto.Logging; using System; namespace Contralto.CPU { public partial class AltoCPU { /// /// DiskTask provides implementation for disk-specific special functions /// (for both Disk Sector and Disk Word tasks, since the special functions are /// identical between the two) /// private sealed class DiskTask : Task { public DiskTask(AltoCPU cpu, bool diskSectorTask) : base(cpu) { _taskType = diskSectorTask ? TaskType.DiskSector : TaskType.DiskWord; _wakeup = false; _diskController = _cpu._system.DiskController; } protected override InstructionCompletion ExecuteInstruction(MicroInstruction instruction) { // Log.Write(LogComponent.Debug, "{0}: {1}", Conversion.ToOctal(_mpc), UCodeDisassembler.DisassembleInstruction(instruction, _taskType)); InstructionCompletion completion = base.ExecuteInstruction(instruction); // Deal with SECLATE semantics: If the Disk Sector task wakes up and runs before // the Disk Controller hits the SECLATE trigger time, then SECLATE remains false. // Otherwise, when the trigger time is hit SECLATE is raised until // the beginning of the next sector. if (_taskType == TaskType.DiskSector) { // Sector task is running; clear enable for seclate signal _diskController.DisableSeclate(); } return completion; } protected override ushort GetBusSource(int bs) { DiskBusSource dbs = (DiskBusSource)bs; switch (dbs) { case DiskBusSource.ReadKSTAT: return _diskController.KSTAT; case DiskBusSource.ReadKDATA: return _diskController.KDATA; default: throw new InvalidOperationException(String.Format("Unhandled bus source {0}", bs)); } } protected override void ExecuteSpecialFunction1(MicroInstruction instruction) { DiskF1 df1 = (DiskF1)instruction.F1; switch (df1) { case DiskF1.LoadKDATA: // "The KDATA register is loaded from BUS[0-15]." _diskController.KDATA = _busData; break; case DiskF1.LoadKADR: // "This causes the KADR register to be loaded from BUS[8-14]. // in addition, it causes the head address bit to be loaded from KDATA[13]." // (the latter is done by DiskController) _diskController.KADR = (ushort)((_busData & 0xff)); break; case DiskF1.LoadKCOMM: _diskController.KCOM = (ushort)((_busData & 0x7c00) >> 10); break; case DiskF1.CLRSTAT: _diskController.ClearStatus(); break; case DiskF1.INCRECNO: _diskController.IncrementRecord(); break; case DiskF1.LoadKSTAT: // "KSTAT[12-15] are loaded from BUS[12-15]. (Actually BUS[13] is ORed onto // KSTAT[13].)" // From the schematic (and ucode source, based on the values it actually uses for BUS[13]), BUS[13] // is also inverted. So there's that, too. // build BUS[12-15] with bit 13 flipped. int modifiedBusData = (_busData & 0xb) | ((~_busData) & 0x4); // OR in BUS[12-15] after masking in KSTAT[13] so it is ORed in properly. _diskController.KSTAT = (ushort)(((_diskController.KSTAT & 0xfff4)) | modifiedBusData); break; case DiskF1.STROBE: _diskController.Strobe(); break; default: throw new InvalidOperationException(String.Format("Unhandled disk special function 1 {0}", df1)); } } protected override void ExecuteSpecialFunction2(MicroInstruction instruction) { DiskF2 df2 = (DiskF2)instruction.F2; switch (df2) { case DiskF2.INIT: _nextModifier |= GetInitModifier(instruction); break; case DiskF2.RWC: // "NEXT<-NEXT OR (IF current record to be written THEN 3 ELSE IF // current record to be checked THEN 2 ELSE 0.") // Current record is in bits 8-9 of the command register; this is shifted // by INCREC by the microcode to present the next set of bits. int command = (_diskController.KADR & 0x00c0) >> 6; _nextModifier |= GetInitModifier(instruction); switch (command) { case 0: // read, no modification. break; case 1: // check, OR in 2 _nextModifier |= 0x2; break; case 2: case 3: // write, OR in 3 _nextModifier |= 0x3; break; } break; case DiskF2.XFRDAT: // "NEXT <- NEXT OR (IF current command wants data transfer THEN 1 ELSE 0) _nextModifier |= GetInitModifier(instruction); if (_diskController.DataXfer) { _nextModifier |= 0x1; } break; case DiskF2.RECNO: _nextModifier |= GetInitModifier(instruction); _nextModifier |= _diskController.RECNO; break; case DiskF2.NFER: // "NEXT <- NEXT OR (IF fatal error in latches THEN 0 ELSE 1)" _nextModifier |= GetInitModifier(instruction); if (!_diskController.FatalError) { _nextModifier |= 0x1; } else { Console.WriteLine("fatal disk error on disk {0}", _diskController.Drive); } break; case DiskF2.STROBON: // "NEXT <- NEXT OR (IF seek strobe still on THEN 1 ELSE 0)" _nextModifier |= GetInitModifier(instruction); if ((_diskController.KSTAT & DiskController.STROBE) != 0) { _nextModifier |= 0x1; } break; case DiskF2.SWRNRDY: // "NEXT <- NEXT OR (IF disk not ready to accept command THEN 1 ELSE 0) _nextModifier |= GetInitModifier(instruction); if (!_diskController.Ready) { Console.WriteLine("disk {0} not ready", _diskController.Drive); _nextModifier |= 0x1; } break; default: throw new InvalidOperationException(String.Format("Unhandled disk special function 2 {0}", df2)); } } protected override void ExecuteBlock() { // // Update the WDINIT signal; this is based on WDALLOW (!_wdInhib) which sets WDINIT (this is done // in KCOM way above). // WDINIT is reset when BLOCK (a BLOCK F1 is being executed) and WDTSKACT (the disk word task is running) are 1. // if (_taskType == TaskType.DiskWord) { _diskController.WDINIT = false; } } /// /// The status of the INIT flag /// /// private ushort GetInitModifier(MicroInstruction instruction) { // // "NEXT<-NEXT OR (if WDTASKACT AND WDINIT) then 37B else 0." // // // A brief discussion of the INIT signal since it isn't really covered in the Alto Hardware docs in any depth // (and in fact is completely skipped over in the description of RWC, a rather important detail!) // This is where the Alto ref's suggestion to have the uCode *and* the schematic on hand is really quite a // valid recommendation. // // WDINIT is initially set whenever the WDINHIB bit (set via KCOM<-) is cleared (this is the WDALLOW signal). // This signals that the microcode is "INITializing" a data transfer (so to speak). During this period, // INIT or RWC instructions in the Disk Word task will OR in 37B to the Next field, causing the uCode to jump // to the requisite initialization paths. WDINIT is cleared whenever a BLOCK instruction occurs during the Disk Word task, // causing INIT to OR in 0 and RWC to or in 0, 2 or 3 (For read, check, or write respectively.) // return (_taskType == TaskType.DiskWord && _diskController.WDINIT) ? (ushort)0x1f : (ushort)0x0; } private DiskController _diskController; } } }