From 59d98d1909e7f66258dd650cd9bfd644435fd2dd Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Fri, 28 Aug 2015 18:07:59 -0700 Subject: [PATCH] Refinement to CPU, implemented very rough diassembler and began annotation of official Xerox ucode sources with PROM addresses. --- Contralto/CPU/ALU.cs | 5 + Contralto/CPU/CPU.cs | 445 ++++- Contralto/CPU/ConstantMemory.cs | 2 - Contralto/CPU/Disassembler.cs | 410 +++++ Contralto/CPU/MicroInstruction.cs | 33 + Contralto/CPU/Shifter.cs | 19 +- Contralto/Contralto.csproj | 4 + Contralto/Disassembly/altoIIcode3.mu | 2232 ++++++++++++++++++++++++++ Contralto/Disassembly/altocode24.mu | Bin 0 -> 53323 bytes Contralto/OctalHelpers.cs | 16 + Contralto/Program.cs | 7 + 11 files changed, 3086 insertions(+), 87 deletions(-) create mode 100644 Contralto/CPU/Disassembler.cs create mode 100644 Contralto/Disassembly/altoIIcode3.mu create mode 100644 Contralto/Disassembly/altocode24.mu create mode 100644 Contralto/OctalHelpers.cs diff --git a/Contralto/CPU/ALU.cs b/Contralto/CPU/ALU.cs index deceb0f..2f8a20f 100644 --- a/Contralto/CPU/ALU.cs +++ b/Contralto/CPU/ALU.cs @@ -13,6 +13,11 @@ namespace Contralto.CPU static class ALU { static ALU() + { + Reset(); + } + + public static void Reset() { _carry = 0; } diff --git a/Contralto/CPU/CPU.cs b/Contralto/CPU/CPU.cs index a4fa88f..dafc92e 100644 --- a/Contralto/CPU/CPU.cs +++ b/Contralto/CPU/CPU.cs @@ -6,14 +6,24 @@ using System.Text; using System.Threading.Tasks; namespace Contralto.CPU -{ - +{ + public enum TaskType + { + Emulator = 0, + DiskSector = 4, + Ethernet = 5, + DiskWord = 9, + Cursor = 10, + DisplayHorizontal = 11, + DisplayVertical = 12, + Refresh = 14, + } public class AltoCPU { public AltoCPU() { - _tasks[0] = new EmulatorTask(this); + _tasks[(int)TaskType.Emulator] = new EmulatorTask(this); Reset(); } @@ -22,14 +32,22 @@ namespace Contralto.CPU { // Reset registers _r = new ushort[32]; - _s = new ushort[32]; + _s = new ushort[8][]; + + for(int i=0;i<_s.Length;i++) + { + _s[i] = new ushort[32]; + } + _t = 0; _l = 0; + _m = 0; _ir = 0; _aluC0 = 0; + _rb = 0; // Reset tasks. - for(int i=0;i<_tasks.Length;i++) + for (int i=0;i<_tasks.Length;i++) { if (_tasks[i] != null) { @@ -66,6 +84,32 @@ namespace Contralto.CPU _clocks++; } + /// + /// Used by hardware devices to cause a specific task to have its + /// "wakeup" signal triggered + /// + /// + public void WakeupTask(int task) + { + if (_tasks[task] != null) + { + _tasks[task].WakeupTask(); + } + } + + /// + /// Used by hardware devices to cause a specific task to have its + /// "wakeup" signal cleared + /// + /// + public void BlockTask(int task) + { + if (_tasks[task] != null) + { + _tasks[task].BlockTask(); + } + } + private void TaskSwitch() { // Select the highest-priority eligible task @@ -110,12 +154,17 @@ namespace Contralto.CPU _mpc = (ushort)_priority; } - public virtual void Block() + public virtual void BlockTask() { // Used only by hardware interfaces, where applicable _wakeup = false; } + public virtual void WakeupTask() + { + _wakeup = true; + } + public bool ExecuteNext() { // TODO: cache microinstructions (or pre-decode them) to save consing all these up every time. @@ -123,11 +172,6 @@ namespace Contralto.CPU return ExecuteInstruction(instruction); } - public virtual string DisassembleInstruction(ushort addr) - { - return String.Empty; - } - /// /// ExecuteInstruction causes the Task to execute the next instruction (the one /// _mpc is pointing to). The base implementation covers non-task specific logic; subclasses may @@ -135,12 +179,17 @@ namespace Contralto.CPU /// /// True if a task switch has been requested by a TASK instruction, false otherwise. protected virtual bool ExecuteInstruction(MicroInstruction instruction) - { - ushort busData = 0; // from BUS - ushort aluData = 0; // from ALU + { bool nextTask = false; - ushort nextModifier = 0; // for branches (OR'd into NEXT field) - + bool loadR = false; + ushort aluData = 0; + _loadS = false; + _rSelect = 0; + _busData = 0; + + + Shifter.SetMagic(false); + // // Wait for memory state machine if a memory operation is requested by this instruction and // the memory isn't ready yet. @@ -148,12 +197,17 @@ namespace Contralto.CPU if ((instruction.BS == BusSource.ReadMD || instruction.F1 == SpecialFunction1.LoadMAR || instruction.F2 == SpecialFunction2.StoreMD) - && !MemoryBus.Ready()) + && !MemoryBus.Ready(MemoryOperation.Load)) //TODO: fix { // Suspend operation for this cycle. return false; } + _rSelect = instruction.RSELECT; + + // Give tasks the chance to modify parameters early on (like RSELECT) + ExecuteSpecialFunction2Early((int)instruction.F2); + // Select BUS data. if (instruction.F1 != SpecialFunction1.Constant && instruction.F2 != SpecialFunction2.Constant) @@ -162,44 +216,46 @@ namespace Contralto.CPU switch (instruction.BS) { case BusSource.ReadR: - busData = _cpu._r[instruction.RSELECT]; + _busData = _cpu._r[_rSelect]; break; case BusSource.LoadR: - busData = 0; // "Loading R forces the BUS to 0 so that an ALU function of 0 and T may be executed simultaneously" + _busData = 0; // "Loading R forces the BUS to 0 so that an ALU function of 0 and T may be executed simultaneously" + loadR = true; break; case BusSource.None: - busData = 0xffff; // "Enables no source to the BUS, leaving it all ones" + _busData = 0xffff; // "Enables no source to the BUS, leaving it all ones" break; case BusSource.TaskSpecific1: case BusSource.TaskSpecific2: - busData = GetBusSource((int)instruction.BS); // task specific -- call into specific implementation + _busData = GetBusSource((int)instruction.BS); // task specific -- call into specific implementation break; case BusSource.ReadMD: - // TODO: wait for MD if not ready. - busData = MemoryBus.ReadMD(); + _busData = MemoryBus.ReadMD(); break; case BusSource.ReadMouse: throw new NotImplementedException("ReadMouse bus source not implemented."); - busData = 0; // TODO: implement + _busData = 0; // TODO: implement break; case BusSource.ReadDisp: throw new NotImplementedException("ReadDisp bus source not implemented."); - busData = 0; // TODO: implement; + _busData = 0; // TODO: implement; break; default: - throw new InvalidOperationException(String.Format("Unhandled bus source {0}", instruction.BS)); + throw new InvalidOperationException(String.Format("Unhandled bus source {0}.", instruction.BS)); + break; } } else { - busData = ConstantMemory.ConstantROM[instruction.RSELECT | ((uint)instruction.BS << 5)]; + // See also comments below. + _busData = ConstantMemory.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)]; } // Constant ROM access: @@ -208,15 +264,19 @@ namespace Contralto.CPU // facility, particularly for the <-MOUSE and <-DISP bus sources. This works because the processor bus ANDs if // more than one source is gated to it. Up to 32 such mask contans can be provided for each of the four bus sources // > 4. + // NOTE also: + // "Note that the [emulator task F2] functions which replace the low bits of RSELECT with IR aaffect only the + // selection of R; they do not affect the address supplied to the constant ROM." + // Hence we use the unmodified RSELECT value here and above. if ((int)instruction.BS > 4 || instruction.F1 == SpecialFunction1.Constant || instruction.F2 == SpecialFunction2.Constant) { - busData &= ConstantMemory.ConstantROM[instruction.RSELECT | ((uint)instruction.BS << 5)]; + _busData &= ConstantMemory.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)]; } // Do ALU operation - aluData = ALU.Execute(instruction.ALUF, busData, _cpu._t); + aluData = ALU.Execute(instruction.ALUF, _busData, _cpu._t); // Reset shifter op Shifter.SetOperation(ShifterOp.None, 0); @@ -272,9 +332,9 @@ namespace Contralto.CPU break; case SpecialFunction2.BusEq0: - if (busData == 0) + if (_busData == 0) { - nextModifier = 1; + _nextModifier = 1; } break; @@ -286,34 +346,34 @@ namespace Contralto.CPU // // Since we haven't modifed L yet, and we've selected the shifter function above, we're good to go here. // - if ((short)Shifter.DoOperation(_cpu._l) < 0) + if ((short)Shifter.DoOperation(_cpu._l, _cpu._t) < 0) { - nextModifier = 1; + _nextModifier = 1; } break; case SpecialFunction2.ShEq0: // See note above. - if (Shifter.DoOperation(_cpu._l) == 0) + if (Shifter.DoOperation(_cpu._l, _cpu._t) == 0) { - nextModifier = 1; + _nextModifier = 1; } break; case SpecialFunction2.Bus: // Select bits 6-15 (bits 0-9 in modern parlance) of the bus - nextModifier = (ushort)(busData & 0x3ff); + _nextModifier = (ushort)(_busData & 0x3ff); break; case SpecialFunction2.ALUCY: // ALUC0 is the carry produced by the ALU during the most recent microinstruction // that loaded L. It is *not* the carry produced during the execution of the microinstruction // that contains the ALUCY function. - nextModifier = _cpu._aluC0; + _nextModifier = _cpu._aluC0; break; case SpecialFunction2.StoreMD: - MemoryBus.LoadMD(busData); + MemoryBus.LoadMD(_busData); break; case SpecialFunction2.Constant: @@ -348,41 +408,76 @@ namespace Contralto.CPU break; } - _cpu._t = loadTFromALU ? aluData : busData; + _cpu._t = loadTFromALU ? aluData : _busData; } - // Load L + // Load L (and M) from ALU if (instruction.LoadL) { _cpu._l = aluData; + _cpu._m = aluData; // Save ALUC0 for use in the next ALUCY special function. _cpu._aluC0 = (ushort)ALU.Carry; } - - // Do shifter - + // Do writeback to selected R register from shifter output - _cpu._r[instruction.RSELECT] = Shifter.DoOperation(_cpu._l); + if (loadR) + { + _cpu._r[_rSelect] = Shifter.DoOperation(_cpu._l, _cpu._t); + } + + // Do writeback to selected R register from M + if (_loadS) + { + _cpu._s[_cpu._rb][_rSelect] = _cpu._m; + } // - // Select next address + // Select next address -- TODO: this is incorrect! _nextModifer should be applied to the *NEXT* instruction, not the current one! // - _mpc = (ushort)(instruction.NEXT | nextModifier); + _mpc = (ushort)(instruction.NEXT | _nextModifier); return nextTask; - } - + } protected abstract ushort GetBusSource(int bs); protected abstract void ExecuteSpecialFunction1(int f1); + + /// + /// Used to allow Task-specific F2s that need to modify RSELECT to do so. + /// + /// + protected virtual void ExecuteSpecialFunction2Early(int f2) + { + // Nothing by default. + } + protected abstract void ExecuteSpecialFunction2(int f2); + // + // Per uInstruction Task Data: + // Modified by both the base Task implementation and any subclasses + // + // TODO: maybe instead of these being shared (which feels kinda bad) + // these could be encapsulated in an object and passed to subclass implementations? + protected ushort _busData; // Data placed onto the bus (ANDed from multiple sources) + protected ushort _nextModifier; // Bits ORed onto the NEXT field of the current instruction + protected uint _rSelect; // RSELECT field from current instruction, potentially modified by task + protected bool _loadS; // Whether to load S from M at and of cycle + + + // + // Global Task Data + // protected AltoCPU _cpu; protected ushort _mpc; protected int _priority; protected bool _wakeup; } + /// + /// EmulatorTask provides emulator (NOVA instruction set) specific operations. + /// private class EmulatorTask : Task { public EmulatorTask(AltoCPU cpu) : base(cpu) @@ -393,66 +488,250 @@ namespace Contralto.CPU _wakeup = true; } - public override string DisassembleInstruction(ushort addr) + public override void BlockTask() { - return base.DisassembleInstruction(addr); + throw new InvalidOperationException("The emulator task cannot be blocked."); } + public override void WakeupTask() + { + throw new InvalidOperationException("The emulator task is always in wakeup state."); + } + protected override ushort GetBusSource(int bs) { - throw new NotImplementedException(); + EmulatorBusSource ebs = (EmulatorBusSource)bs; + + switch(ebs) + { + case EmulatorBusSource.ReadSLocation: + return _cpu._s[_cpu._rb][_rSelect]; + + case EmulatorBusSource.LoadSLocation: + _loadS = true; + return 0; // TODO: technically this is an "undefined value" not zero. + + default: + throw new InvalidOperationException(String.Format("Unhandled bus source {0}", bs)); + } } protected override void ExecuteSpecialFunction1(int f1) { - throw new NotImplementedException(); + EmulatorF1 ef1 = (EmulatorF1)f1; + switch (ef1) + { + case EmulatorF1.RSNF: + // TODO: make configurable + // "...decoded by the Ethernet interface, which gates the host address wired on the + // backplane onto BUS[8-15]. BUS[0-7] is not driven and will therefore be -1. If + // no Ethernet interface is present, BUS will be -1. + // + _busData &= (0xff00 | 0x42); + break; + + case EmulatorF1.STARTF: + // Dispatch function to I/O based on contents of AC0... (TBD: what are these?) + throw new NotImplementedException(); + break; + + default: + throw new InvalidOperationException(String.Format("Unhandled emulator F1 {0}.", ef1)); + } + } + + protected override void ExecuteSpecialFunction2Early(int f2) + { + EmulatorF2 ef2 = (EmulatorF2)f2; + switch (ef2) + { + case EmulatorF2.ACSOURCE: + // Early: modify R select field: + // "...it replaces the two-low order bits of the R select field with + // the complement of the SrcAC field of IR, (IR[1-2] XOR 3), allowing the emulator + // to address its accumulators (which are assigned to R0-R3)." + _rSelect = (_rSelect & 0xfffc) | ((((uint)_cpu._ir & 0x6000) >> 13) ^ 3); + break; + + case EmulatorF2.ACDEST: + // "...causes (IR[3-4] XOR 3) to be used as the low-order two bits of the RSELECT field. + // This address the accumulators from the destination field of the instruction. The selected + // register may be loaded or read." + _rSelect = (_rSelect & 0xfffc) | ((((uint)_cpu._ir & 0x1800) >> 11) ^ 3); + break; + + } } protected override void ExecuteSpecialFunction2(int f2) { - throw new NotImplementedException(); - } + EmulatorF2 ef2 = (EmulatorF2)f2; + switch (ef2) + { + case EmulatorF2.LoadIR: + // based on block diagram, this always comes from the bus + _cpu._ir = _busData; - enum EmulatorF1 - { - SWMODE = 8, - WRTRAM = 9, - RDRAM = 10, - LoadRMR = 11, - Unused = 12, - LoadESRB = 13, - RSNF = 14, - STARTF = 15, - } + // "IR<- also merges bus bits 0, 5, 6 and 7 into NEXT[6-9] which does a first level + // instruction dispatch." + // TODO: is this an AND or an OR operation? (how is the "merge" done?) + // Assuming for now this is an OR operation like everything else that modifies NEXT. + _nextModifier = (ushort)(((_busData & 0x8000) >> 6) | ((_busData & 0x0700) >> 2)); + break; - enum EmulatorF2 - { - BUSODD = 8, - MAGIC = 9, - LoadDNS = 10, - ACDEST = 11, - LoadIR = 12, - IDISP = 13, - ACSOURCE = 14, - Unused = 15, - } + case EmulatorF2.IDISP: + // "The IDISP function (F2=15B) does a 16 way dispatch under control of a PROM and a + // multiplexer. The values are tabulated below: + // Conditions ORed onto NEXT Comment + // + // if IR[0] = 1 3-IR[8-9] complement of SH field of IR + // elseif IR[1-2] = 0 IR[3-4] JMP, JSR, ISZ, DSZ + // elseif IR[1-2] = 1 4 LDA + // elseif IR[1-2] = 2 5 STA + // elseif IR[4-7] = 0 1 + // elseif IR[4-7] = 1 0 + // elseif IR[4-7] = 6 16B CONVERT + // elseif IR[4-7] = 16B 6 + // else IR[4-7] + // NB: as always, Xerox labels bits in the opposite order from modern convention; + // (bit 0 is bit 15...) + if ((_cpu._ir & 0x8000) != 0) + { + _nextModifier = (ushort)(3 - ((_cpu._ir & 0xc0) >> 6)); + } + else if((_cpu._ir & 0x6000) == 0) + { + _nextModifier = (ushort)((_cpu._ir & 0x1800) >> 11); + } + else if((_cpu._ir & 0x6000) == 0x4000) + { + _nextModifier = 4; + } + else if ((_cpu._ir & 0x6000) == 0x6000) + { + _nextModifier = 5; + } + else if ((_cpu._ir & 0x0f00) == 0) + { + _nextModifier = 1; + } + else if ((_cpu._ir & 0x0f00) == 0x0100) + { + _nextModifier = 0; + } + else if ((_cpu._ir & 0x0f00) == 0x0600) + { + _nextModifier = 0xe; + } + else if ((_cpu._ir & 0x0f00) == 0x0e00) + { + _nextModifier = 0x6; + } + else + { + _nextModifier = (ushort)((_cpu._ir & 0x0f00) >> 8); + } + break; - enum EmulatorBusSource - { + case EmulatorF2.ACSOURCE: + // Late: + // "...a dispatch is performed: + // Conditions ORed onto NEXT Comment + // + // if IR[0] = 1 3-IR[8-9] complement of SH field of IR + // if IR[1-2] = 3 IR[5] the Indirect bit of R + // if IR[3-7] = 0 2 CYCLE + // if IR[3-7] = 1 5 RAMTRAP + // if IR[3-7] = 2 3 NOPAR -- parameterless opcode group + // if IR[3-7] = 3 6 RAMTRAP + // if IR[3-7] = 4 7 RAMTRAP + // if IR[3-7] = 11B 4 JSRII + // if IR[3-7] = 12B 4 JSRIS + // if IR[3-7] = 16B 1 CONVERT + // if IR[3-7] = 37B 17B ROMTRAP -- used by Swat, the debugger + // else 16B ROMTRAP + if ((_cpu._ir & 0x8000) != 0) + { + _nextModifier = (ushort)(3 - ((_cpu._ir & 0xc0) >> 6)); + } + else if ((_cpu._ir & 0xc000) == 0xc000) + { + _nextModifier = (ushort)((_cpu._ir & 0x400) >> 10); + } + else if ((_cpu._ir & 0x1f00) == 0) + { + _nextModifier = 2; + } + else if ((_cpu._ir & 0x1f00) == 0x0100) + { + _nextModifier = 5; + } + else if ((_cpu._ir & 0x1f00) == 0x0200) + { + _nextModifier = 3; + } + else if ((_cpu._ir & 0x1f00) == 0x0300) + { + _nextModifier = 6; + } + else if ((_cpu._ir & 0x1f00) == 0x0400) + { + _nextModifier = 7; + } + else if ((_cpu._ir & 0x1f00) == 0x0900) + { + _nextModifier = 4; + } + else if ((_cpu._ir & 0x1f00) == 0x0a00) + { + _nextModifier = 4; + } + else if ((_cpu._ir & 0x1f00) == 0x0e00) + { + _nextModifier = 1; + } + else if ((_cpu._ir & 0x1f00) == 0x1f00) + { + _nextModifier = 0xf; + } + else + { + _nextModifier = 0xe; + } + break; - } + case EmulatorF2.ACDEST: + // Handled in early handler + break; + + case EmulatorF2.BUSODD: + // "...merges BUS[15] into NEXT[9]." + // TODO: is this an AND or an OR? + _nextModifier = (ushort)((_nextModifier & 0xffbf) | ((_busData & 0x1) << 6)); + break; + + case EmulatorF2.MAGIC: + Shifter.SetMagic(true); + break; + + + default: + throw new InvalidOperationException(String.Format("Unhandled emulator F2 {0}.", ef2)); + } + } } // AltoCPU registers ushort _t; ushort _l; - ushort _m; - ushort _mar; + ushort _m; ushort _ir; - + + // R and S register files and bank select ushort[] _r; - ushort[] _s; + ushort[][] _s; + ushort _rb; // S register bank select // Stores the last carry from the ALU on a Load L private ushort _aluC0; diff --git a/Contralto/CPU/ConstantMemory.cs b/Contralto/CPU/ConstantMemory.cs index e96d046..6e4a15d 100644 --- a/Contralto/CPU/ConstantMemory.cs +++ b/Contralto/CPU/ConstantMemory.cs @@ -7,8 +7,6 @@ using System.Threading.Tasks; namespace Contralto.CPU { - - static class ConstantMemory { static ConstantMemory() diff --git a/Contralto/CPU/Disassembler.cs b/Contralto/CPU/Disassembler.cs new file mode 100644 index 0000000..2473ceb --- /dev/null +++ b/Contralto/CPU/Disassembler.cs @@ -0,0 +1,410 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Contralto.CPU +{ + // BUG: register assignments should come from L (not 0) + public static class Disassembler + { + + /// + /// Disassembles the specified microinstruction for the specified Task type. + /// + /// The microinstruction to disassemble + /// The task to interpret the microinstruction for + public static string DisassembleInstruction(MicroInstruction instruction, TaskType task) + { + StringBuilder disassembly = new StringBuilder(); + + uint rSelect = instruction.RSELECT; + bool loadR = false; + bool loadS = false; + string source = string.Empty; + string operation = string.Empty; + string f1 = string.Empty; + string f2 = string.Empty; + string loadT = string.Empty; + string loadL = string.Empty; + + // Select BUS data. + if (instruction.F1 != SpecialFunction1.Constant && + instruction.F2 != SpecialFunction2.Constant) + { + // Normal BUS data (not constant ROM access). + switch (instruction.BS) + { + case BusSource.ReadR: + source = String.Format("R[{0}] ", OctalHelpers.ToOctal((int)rSelect)); + break; + + case BusSource.LoadR: + source = "0 "; + loadR = true; + break; + + case BusSource.None: + source = "177777 "; + break; + + case BusSource.TaskSpecific1: + case BusSource.TaskSpecific2: + source = DisassembleBusSource(instruction, task, out loadS); // task specific -- call into specific implementation + break; + + case BusSource.ReadMD: + source = "MD "; + break; + + case BusSource.ReadMouse: + source = "MOUSE "; + break; + + case BusSource.ReadDisp: + source = "DISP "; + break; + } + } + + if ((int)instruction.BS > 4 || + instruction.F1 == SpecialFunction1.Constant || + instruction.F2 == SpecialFunction2.Constant) + { + source += String.Format("C({0})", + OctalHelpers.ToOctal(ConstantMemory.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)])); + } + + switch (instruction.ALUF) + { + case AluFunction.Bus: + operation = source; + break; + + case AluFunction.T: + operation = "T "; + break; + + case AluFunction.BusOrT: + operation = String.Format("{0} or T ", source); + break; + + case AluFunction.BusAndT: + operation = String.Format("{0} and T ", source); + break; + + case AluFunction.BusXorT: + operation = String.Format("{0} xor T ", source); + break; + + case AluFunction.BusPlus1: + operation = String.Format("{0} + 1 ", source); + break; + + case AluFunction.BusMinus1: + operation = String.Format("{0} - 1 ", source); + break; + + case AluFunction.BusPlusT: + operation = String.Format("{0} + T ", source); + break; + + case AluFunction.BusMinusT: + operation = String.Format("{0} - T ", source); + break; + + case AluFunction.BusMinusTMinus1: + operation = String.Format("{0} - T - 1 ", source); + break; + + case AluFunction.BusPlusTPlus1: + operation = String.Format("{0} + T + 1 ", source); + break; + + case AluFunction.BusPlusSkip: + operation = String.Format("{0} + SKIP ", source); + break; + + case AluFunction.AluBusAndT: + operation = String.Format("{0}.T ", source); + break; + + case AluFunction.BusAndNotT: + operation = String.Format("{0} and not T ", source); + break; + + default: + operation = "Undefined ALU operation "; + break; + } + + switch (instruction.F1) + { + case SpecialFunction1.None: + f1 = string.Empty; + break; + + case SpecialFunction1.LoadMAR: + f1 = "MAR<- "; + break; + + case SpecialFunction1.Task: + f1 = "TASK "; + break; + + case SpecialFunction1.Block: + // "...this function is reserved by convention only; it is *not* done by the microprocessor" + f1 = "BLOCK "; // throw new InvalidOperationException("BLOCK should never be invoked by microcode."); + break; + + case SpecialFunction1.LLSH1: + f1 = "<-L LSH 1 "; + break; + + case SpecialFunction1.LRSH1: + f1 = "<-L RSH 1 "; + break; + + case SpecialFunction1.LLCY8: + f1 = "<-L LCY 8 "; + break; + + case SpecialFunction1.Constant: + // Ignored here; handled by Constant ROM access logic above. + break; + + default: + // Let the specific task implementation take a crack at this. + f1 = DisassembleSpecialFunction1(instruction, task); + break; + } + + switch (instruction.F2) + { + case SpecialFunction2.None: + f2 = string.Empty; + break; + + case SpecialFunction2.BusEq0: + f2 = "BUS=0 "; + break; + + case SpecialFunction2.ShLt0: + f2 = "SH<0 "; + break; + + case SpecialFunction2.ShEq0: + f2 = "SH=0 "; + break; + + case SpecialFunction2.Bus: + f2 = "BUS "; + break; + + case SpecialFunction2.ALUCY: + f2 = "ALUCY "; + break; + + case SpecialFunction2.StoreMD: + f2 = "MD<- "; + break; + + case SpecialFunction2.Constant: + // Ignored here; handled by Constant ROM access logic above. + break; + + default: + // Let the specific task implementation take a crack at this. + f2 = DisassembleSpecialFunction2(instruction, task); + break; + } + + // + // Write back to registers: + // + + // Load T + if (instruction.LoadT) + { + // Does this operation change the source for T? + bool loadTFromALU = false; + switch (instruction.ALUF) + { + case AluFunction.Bus: + case AluFunction.BusOrT: + case AluFunction.BusPlus1: + case AluFunction.BusMinus1: + case AluFunction.BusPlusTPlus1: + case AluFunction.BusPlusSkip: + case AluFunction.AluBusAndT: + loadTFromALU = true; + break; + } + + loadT = String.Format("T<- {0}", loadTFromALU ? operation : source); + } + + // Load L (and M) from ALU + if (instruction.LoadL) + { + if (string.IsNullOrEmpty(loadT)) + { + loadL = String.Format("L<- {0}", operation); + } + else + { + loadL = String.Format("L<- {0}", loadT); + } + } + + // Do writeback to selected R register from shifter output + if (loadR) + { + loadL = String.Format("R[{0}]<- {1}", OctalHelpers.ToOctal((int)rSelect), loadL != String.Empty ? loadL : operation); + } + + // Do writeback to selected S register from M + if (loadS) + { + loadL = String.Format("S[{0}]<- {1}", OctalHelpers.ToOctal((int)rSelect), loadL); + } + + if (!string.IsNullOrEmpty(loadL) || !string.IsNullOrEmpty(loadT)) + { + disassembly.AppendFormat("{0}{1}{2}{3} :{4}", f1, f2, loadT, loadL, OctalHelpers.ToOctal(instruction.NEXT)); + } + else + { + disassembly.AppendFormat("{0}{1}{2} :{3}", f1, f2, operation, OctalHelpers.ToOctal(instruction.NEXT)); + } + + + return disassembly.ToString(); + } + + private static string DisassembleBusSource(MicroInstruction instruction, TaskType task, out bool loadS) + { + switch(task) + { + case TaskType.Emulator: + return DisassembleEmulatorBusSource(instruction, out loadS); + + default: + loadS = false; + return String.Format("BS{0}", OctalHelpers.ToOctal((int)instruction.BS)); + } + } + + private static string DisassembleSpecialFunction1(MicroInstruction instruction, TaskType task) + { + switch (task) + { + case TaskType.Emulator: + return DisassembleEmulatorSpecialFunction1(instruction); + + default: + return String.Format("F1{0}", OctalHelpers.ToOctal((int)instruction.F1)); + } + } + + private static string DisassembleSpecialFunction2(MicroInstruction instruction, TaskType task) + { + switch (task) + { + case TaskType.Emulator: + return DisassembleEmulatorSpecialFunction2(instruction); + + default: + return String.Format("F2{0}", OctalHelpers.ToOctal((int)instruction.F2)); + } + } + + private static string DisassembleEmulatorBusSource(MicroInstruction instruction, out bool loadS) + { + EmulatorBusSource bs = (EmulatorBusSource)instruction.BS; + + switch(bs) + { + case EmulatorBusSource.ReadSLocation: + loadS = false; + return String.Format("<-S[{0}]", OctalHelpers.ToOctal((int)instruction.RSELECT)); + + case EmulatorBusSource.LoadSLocation: + loadS = true; + return String.Empty; + + default: + loadS = false; + throw new InvalidOperationException(String.Format("Unhandled Emulator BS {0}", bs)); + + } + + } + + private static string DisassembleEmulatorSpecialFunction1(MicroInstruction instruction) + { + EmulatorF1 ef1 = (EmulatorF1)instruction.F1; + + switch(ef1) + { + case EmulatorF1.SWMODE: + return "SWMODE "; + + case EmulatorF1.WRTRAM: + return "WRTRAM "; + + case EmulatorF1.RDRAM: + return "RDRAM "; + + case EmulatorF1.LoadRMR: + return "RMR<- "; + + case EmulatorF1.LoadESRB: + return "ESRB<- "; + + case EmulatorF1.RSNF: + return "RSNF "; + + case EmulatorF1.STARTF: + return "STARTF "; + + default: + return String.Format("F1{0}", OctalHelpers.ToOctal((int)ef1)); + } + + } + + private static string DisassembleEmulatorSpecialFunction2(MicroInstruction instruction) + { + EmulatorF2 ef2 = (EmulatorF2)instruction.F2; + + switch (ef2) + { + case EmulatorF2.ACDEST: + return "ACDEST "; + + case EmulatorF2.ACSOURCE: + return "ACSOURCE "; + + case EmulatorF2.MAGIC: + return "MAGIC "; + + case EmulatorF2.LoadDNS: + return "DNS<- "; + + case EmulatorF2.BUSODD: + return "BUSODD "; + + case EmulatorF2.LoadIR: + return "IR<- "; + + case EmulatorF2.IDISP: + return "IDISP "; + + default: + return String.Format("F2{0}", OctalHelpers.ToOctal((int)ef2)); + } + } + } +} diff --git a/Contralto/CPU/MicroInstruction.cs b/Contralto/CPU/MicroInstruction.cs index 9f61efc..cfbea64 100644 --- a/Contralto/CPU/MicroInstruction.cs +++ b/Contralto/CPU/MicroInstruction.cs @@ -66,6 +66,39 @@ namespace Contralto.CPU Undefined2 = 15, } + // + // Task-specific enumerations follow + // + enum EmulatorF1 + { + SWMODE = 8, + WRTRAM = 9, + RDRAM = 10, + LoadRMR = 11, + Unused = 12, + LoadESRB = 13, + RSNF = 14, + STARTF = 15, + } + + enum EmulatorF2 + { + BUSODD = 8, + MAGIC = 9, + LoadDNS = 10, + ACDEST = 11, + LoadIR = 12, + IDISP = 13, + ACSOURCE = 14, + Unused = 15, + } + + enum EmulatorBusSource + { + ReadSLocation = 3, // <-SLOCATION: read from S reg into M + LoadSLocation = 4, // SLOCATION<- store to S reg from M + } + public class MicroInstruction { public MicroInstruction(UInt32 code) diff --git a/Contralto/CPU/Shifter.cs b/Contralto/CPU/Shifter.cs index 7113226..8e6cb35 100644 --- a/Contralto/CPU/Shifter.cs +++ b/Contralto/CPU/Shifter.cs @@ -13,7 +13,7 @@ namespace Contralto.CPU ShiftLeft, ShiftRight, RotateLeft, - RotateRight + RotateRight, } public static class Shifter @@ -21,14 +21,28 @@ namespace Contralto.CPU static Shifter() { _op = ShifterOp.Invalid; + _count = 0; + _magic = false; } public static void SetOperation(ShifterOp op, int count) { _op = op; + _count = count; } - public static ushort DoOperation(ushort input) + public static void SetMagic(bool magic) + { + _magic = magic; + } + + /// + /// Does the last specified operation to the specified inputs + /// + /// Normal input to be shifted + /// CPU t register, for MAGIC shifts only + /// + public static ushort DoOperation(ushort input, ushort t) { ushort output = 0; switch(_op) @@ -77,5 +91,6 @@ namespace Contralto.CPU private static ShifterOp _op; private static int _count; + private static bool _magic; } } diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj index 6a03537..ec85f9a 100644 --- a/Contralto/Contralto.csproj +++ b/Contralto/Contralto.csproj @@ -44,17 +44,21 @@ + + + + Always diff --git a/Contralto/Disassembly/altoIIcode3.mu b/Contralto/Disassembly/altoIIcode3.mu new file mode 100644 index 0000000..dfb72dc --- /dev/null +++ b/Contralto/Disassembly/altoIIcode3.mu @@ -0,0 +1,2232 @@ +; A L T O I I C O D E 3 . M U +; Copyright Xerox Corporation 1979 + +;***Derived from ALTOIICODE2.MU, as last modified by +;***Tobol, August 5, 1976 12:13 PM -- fix DIOG2 bug +;***modified by Ingalls, September 6, 1977 +; BitBLT fixed (LREG bug) and extended for new memory +;***modified by Boggs and Taft September 15, 1977 10:10 PM +; Modified MRT to refresh 16K chips and added XMSTA and XMLDA. +; Fixed two bugs in DEXCH and a bug in the interval timer. +; Moved symbol and constant definitions into AltoConsts23.mu. +; MRT split and moved into two 'get' files. +;***modified by Boggs and Taft November 21, 1977 5:10 PM +; Fixed a bug in the Ethernet input main loop. +;***modified by Boggs November 28, 1977 3:53 PM +; Mess with the information returned by VERS +;***modified by Dersch, August 26, 2015 4:04 PM +; Annotated with PROM addresses and Tasks for use in Contralto + +;Get the symbol and constant definitions +#AltoConsts23.mu; + +;LABEL PREDEFINITIONS + +;The reset locations of the tasks: + +!17,20,NOVEM,,,,KSEC,,,EREST,MRT,DWT,CURT,DHT,DVT,PART,KWDX,; + +;Locations which may need to be accessible from the Ram, or Ram +; locations which are accessed from the Rom (TRAP1): +!37,20,START,RAMRET,RAMCYCX,,,,,,,,,,,,,TRAP1; + +;Macro-op dispatch table: +!37,20,DOINS,DOIND,EMCYCLE,NOPAR,JSRII,U5,U6,U7,,,,,,,RAMTRAP,TRAP; + +;Parameterless macro-op sub-table: +!37,40,DIR,EIR,BRI,RCLK,SIO,BLT,BLKS,SIT,JMPR,RDRM,WTRM,DIRS,VERS,DREAD,DWRITE,DEXCH,MUL,DIV,DIOG1,DIOG2,BITBLT,XMLDA,XMSTA,,,,,,,,,; + +;Cycle dispatch table: +!37,20,L0,L1,L2,L3,L4,L5,L6,L7,L8,R7,R6,R5,R4,R3X,R2X,R1X; + +;some global R-Registers +$NWW $R4; State of interrupt system +$R37 $R37; Used by MRT, interval timer and EIA +$MTEMP $R25; Public temporary R-Register + + +;The Display Controller + +; its R-Registers: +$CBA $R22; +$AECL $R23; +$SLC $R24; +$HTAB $R26; +$YPOS $R27; +$DWA $R30; +$CURX $R20; +$CURDATA $R21; + +; its task specific functions: +$EVENFIELD $L024010,000000,000000; F2 = 10 DHT DVT +$SETMODE $L024011,000000,000000; F2 = 11 DHT +$DDR $L026010,000000,124100; F2 = 10 DWT + +!1,2,DVT1,DVT11; +!1,2,MOREB,NOMORE; +!1,2,NORMX,HALFX; +!1,2,NODD,NEVEN; +!1,2,DHT0,DHT1; +!1,2,NORMODE,HALFMODE; +!1,2,DWTZ,DWTY; +!1,2,DOTAB,NOTAB; +!1,2,XNOMORE,DOMORE; + +;Display Vertical Task + +DV00014> DVT: MAR<- L<- DASTART+1; +DV00001> CBA<- L, L<- 0; +DV00005> CURDATA<- L; +DV00006> SLC<- L; +DV00017> T<- MD; CAUSE A VERTICAL FIELD INTERRUPT +DV00023> L<- NWW OR T; +DV00036> MAR<- CURLOC; SET UP THE CURSOR +DV00046> NWW<- L, T<- 0-1; +DV00047> L<- MD XOR T; HARDWARE EXPECTS X COMPLEMENTED +DV00050> T<- MD, EVENFIELD; +DV00051> CURX<- L, :DVT1; + +DV00002> DVT1: L<- BIAS-T-1, TASK, :DVT2; BIAS THE Y COORDINATE +DV00003> DVT11: L<- BIAS-T, TASK; + +DV00052> DVT2: YPOS<- L, :DVT; + +;Display Horizontal Task. +;11 cycles if no block change, 17 if new control block. + +DH00013> DHT: MAR<- CBA-1; +DH00053> L<- SLC -1, BUS=0; +DH00054> SLC<- L, :DHT0; + +DH00032> DHT0: T<- 37400; MORE TO DO IN THIS BLOCK +DH00055> SINK<- MD; +DH00056> L<- T<- MD AND T, SETMODE; +DH00057> HTAB<- L LCY 8, :NORMODE; + +DH00034> NORMODE:L<- T<- 377 . T; +DH00070> AECL<- L, :REST; + +DH00035> HALFMODE: L<- T<- 377 . T; +DH00071> AECL<- L, :REST, T<- 0; + +DH00072> REST: L<- DWA + T,TASK; INCREMENT DWA BY 0 OR NWRDS +DH00073> NDNX: DWA<- L, :DHT; + +DH00033> DHT1: L<- T<- MD+1, BUS=0; +DH00074> CBA<- L, MAR<- T, :MOREB; + +DH00025> NOMORE: BLOCK, :DNX; +DH00024> MOREB: T<- 37400; +DH00075> L<- T<- MD AND T, SETMODE; +DH00127> MAR<- CBA+1, :NORMX, EVENFIELD; + +DH00026> NORMX: HTAB<- L LCY 8, :NODD; +DH00027> HALFX: HTAB_ L LCY 8, :NEVEN; + +DH00030> NODD: L<-T<- 377 . T; +DH00130> AECL<- L, :XREST; ODD FIELD, FULL RESOLUTION + +DH00031> NEVEN: L_ 377 AND T; EVEN FIELD OR HALF RESOLUTION +DH00131> AECL<-L, T<-0; + +DH00132> XREST: L<- MD+T; +DH00133> T_MD-1; +DH00134> DNX: DWA<-L, L<-T, TASK; +DH00135> SLC<-L, :DHT; + +;Display Word Task + +DW00011> DWT: T<- DWA; +DW00136> T<- -3+T+1; +DW00137> L<- AECL+T,BUS=0,TASK; AECL CONTAINS NWRDS AT THIS TIME +DW00140> AECL<-L, :DWTZ; + +DW00041> DWTY: BLOCK; +DW00141> TASK, :DWTF; + +DW00040> DWTZ: L<-HTAB-1, BUS=0,TASK; +DW00142> HTAB<-L, :DOTAB; + +DW00042> DOTAB: DDR<-0, :DWTZ; +DW00043> NOTAB: MAR_T_DWA; +DW00143> L<-AECL-T-1; +DW00144> ALUCY, L<-2+T; +DW00145> DWA<-L, :XNOMORE; + +DW00045> DOMORE: DDR<-MD, TASK; +DW00146> DDR_MD, :NOTAB; + +DW00144> XNOMORE:DDR<- MD, BLOCK; +DW00147> DDR<- MD, TASK; + +DW00150> DWTF: :DWT; + +;Alto Ethernet Microcode, Version III, Boggs and Metcalfe + +;4-way branches using NEXT6 and NEXT7 +!17,20,EIFB00,EODOK,EOEOK,ENOCMD,EIFB01,EODPST,EOEPST,EOREST,EIFB10,EODCOL,EOECOL,EIREST,EIFB11,EODUGH,EOEUGH,ERBRES; + +;2-way branches using NEXT7 +;EOCDW1, EOCDWX, and EIGO are all related. Be careful! +!7,10,,EIFOK,,EOCDW1,,EIFBAD,EOCDWX,EIGO; + +;Miscellaneous address constraints +!7,10,,EOCDW0,EODATA,EIDFUL,EIDZ4,EOCDRS,EIDATA,EPOST; +!7,10,,EIDOK,,,EIDMOR,EIDPST; +!1,1,EIFB1; +!1,1,EIFRST; + +;2-way branches using NEXT9 +!1,2,EOINPR,EOINPN; +!1,2,EODMOR,EODEND; +!1,2,EOLDOK,EOLDBD; +!1,2,EIFCHK,EIFPRM; +!1,2,EOCDWT,EOCDGO; +!1,2,ECNTOK,ECNTZR; +!1,2,EIFIGN,EISET; +!1,2,EIFNBC,EIFBC; + +;R Memory Locations + +$ECNTR $R12; Remaining words in buffer +$EPNTR $R13; points BEFORE next word in buffer + +;Ethernet microcode Status codes + +$ESIDON $377; Input Done +$ESODON $777; Output Done +$ESIFUL $1377; Input Buffer full - words lost from tail of packet +$ESLOAD $1777; Load location overflowed +$ESCZER $2377; Zero word count for input or output command +$ESABRT $2777; Abort - usually caused by reset command +$ESNEVR $3377; Never Happen - Very bad if it does + +;Main memory locations in page 1 reserved for Ethernet + +$EPLOC $600; Post location +$EBLOC $601; Interrupt bit mask + +$EELOC $602; Ending count location +$ELLOC $603; Load location + +$EICLOC $604; Input buffer Count +$EIPLOC $605; Input buffer Pointer + +$EOCLOC $606; Output buffer Count +$EOPLOC $607; Output buffer Pointer + +$EHLOC $610; Host Address + +;Function Definitions + +$EIDFCT $L000000,014004,000100; BS = 4, Input data +$EILFCT $L016013,070013,000100; F1 = 13, Input Look +$EPFCT $L016014,070014,000100; F1 = 14, Post +$EWFCT $L016015,000000,000000; F1 = 15, Wake-Up + +$EODFCT $L026010,000000,124000; F2 = 10, Output data +$EOSFCT $L024011,000000,000000; F2 = 11, Start output +$ERBFCT $L024012,000000,000000; F2 = 12, Rest branch +$EEFCT $L024013,000000,000000; F2 = 13, End of output +$EBFCT $L024014,000000,000000; F2 = 14, Branch +$ECBFCT $L024015,000000,000000; F2 = 15, Countdown branch +$EISFCT $L024016,000000,000000; F2 = 16, Start input + +; - Whenever a label has a pending branch, the list of possible +; destination addresses is shown in brackets in the comment field. +; - Special functions are explained in a comment near their first use. +; - To avoid naming conflicts, all labels and special functions +; have "E" as the first letter. + +;Top of Ethernet Task loop + +;Ether Rest Branch Function - ERBFCT +;merge ICMD and OCMD Flip Flops into NEXT6 and NEXT7 +;ICMD and OCMD are set from AC0 [14:15] by the SIO instruction +; 00 neither +; 01 OCMD - Start output +; 10 ICMD - Start input +; 11 Both - Reset interface + +;in preparation for a hack at EIREST, zero EPNTR + +EN00007> EREST: L<- 0,ERBFCT; What's happening ? +EN00152> EPNTR<- L,:ENOCMD; [ENOCMD,EOREST,EIREST,ERBRES] + +EN00203> ENOCMD: L<- ESNEVR,:EPOST; Shouldn't happen +ERBRES: L_ ESABRT,:EPOST; Reset Command + +;Post status and halt. Microcode status in L. +;Put microstatus,,hardstatus in EPLOC, merge c(EBLOC) into NWW. +;Note that we write EPLOC and read EBLOC in one operation + +;Ether Post Function - EPFCT. Gate the hardware status +;(LOW TRUE) to Bus [10:15], reset interface. + +EN00237> EPOST: MAR<- EELOC; +EN00220> EPNTR<- L,TASK; Save microcode status in EPNTR +EN00222> MD<- ECNTR; Save ending count + +EN00224> MAR<- EPLOC; double word reference +EN00230> T<- NWW; +EN00240> MD<- EPNTR,EPFCT; BUS AND EPNTR with Status +EN00260> L<- MD OR T,TASK; NWW OR c(EBLOC) +EN00261> NWW<- L,:EREST; Done. Wait for next command + +;This is a subroutine called from both input and output (EOCDGO +;and EISET). The return address is determined by testing ECBFCT, +;which will branch if the buffer has any words in it, which can +;only happen during input. + +ESETUP: NOP; + L_ MD,BUS=0; check for zero length + T_ MD-1,:ECNTOK; [ECNTOK,ECNTZR] start-1 + +ECNTZR: L_ ESCZER,:EPOST; Zero word count. Abort + +;Ether Countdown Branch Function - ECBFCT. +;NEXT7 = Interface buffer not empty. + +ECNTOK: ECNTR_ L,L_ T,ECBFCT,TASK; + EPNTR_ L,:EODATA; [EODATA,EIDATA] + +;Ethernet Input + +;It turns out that starting the receiver for the first time and +;restarting it after ignoring a packet do the same things. + +EIREST: :EIFIGN; Hack + +;Address filtering code. + +;When the first word of a packet is available in the interface +;buffer, a wakeup request is generated. The microcode then +;decides whether to accept the packet. Decision must be reached +;before the buffer overflows, within about 14*5.44 usec. +;if EHLOC is zero, machine is 'promiscuous' - accept all packets +;if destination byte is zero, it is a 'broadcast' packet, accept. +;if destination byte equals EHLOC, packet is for us, accept. + +;EIFRST is really a subroutine that can be called from EIREST +;or from EIGO, output countdown wait. If a packet is ignored +;and EPNTR is zero, EIFRST loops back and waits for more +;packets, else it returns to the countdown code. + +;Ether Branch Function - EBFCT +;NEXT7 = IDL % OCMD % ICMD % OUTGONE % INGONE (also known as POST) +;NEXT6 = COLLision - Can't happen during input + +EIFRST: MAR_ EHLOC; Get Ethernet address + T_ 377,EBFCT; What's happening? + L_ MD AND T,BUS=0,:EIFOK;[EIFOK,EIFBAD] promiscuous? + +EIFOK: MTEMP_ LLCY8,:EIFCHK; [EIFCHK,EIFPRM] Data wakeup + +EIFBAD: ERBFCT,TASK,:EIFB1; [EIFB1] POST wakeup; xCMD FF set? +EIFB1: :EIFB00; [EIFB00,EIFB01,EIFB10,EIFB11] + +EIFB00: :EIFIGN; IDL or INGONE, restart rcvr +EIFB01: L_ ESABRT,:EPOST; OCMD, abort +EIFB10: L_ ESABRT,:EPOST; ICMD, abort +EIFB11: L_ ESABRT,:EPOST; ICMD and OCMD, abort + +EIFPRM: TASK,:EIFBC; Promiscuous. Accept + +;Ether Look Function - EILFCT. Gate the first word of the +;data buffer to the bus, but do not increment the read pointer. + +EIFCHK: L_ T_ 177400,EILFCT; Mask off src addr byte (BUS AND) + L_ MTEMP-T,SH=0; Broadcast? + SH=0,TASK,:EIFNBC; [EIFNBC,EIFBC] Our Address? + +EIFNBC: :EIFIGN; [EIFIGN,EISET] + +EIFBC: :EISET; [EISET] Enter input main loop + +;Ether Input Start Function - EISFCT. Start receiver. Interface +;will generate a data wakeup when the first word of the next +;packet arrives, ignoring any packet currently passing. + +EIFIGN: SINK_ EPNTR,BUS=0,EPFCT;Reset; Called from output? + EISFCT,TASK,:EOCDWX; [EOCDWX,EIGO] Restart rcvr + +EOCDWX: EWFCT,:EOCDWT; Return to countdown wait loop + +EISET: MAR_ EICLOC,:ESETUP; Double word reference + +;Input Main Loop + +;Ether Input Data Function - EIDFCT. Gate a word of data to +;the bus from the interface data buffer, increment the read ptr. +; * * * * * W A R N I N G * * * * * +;The delay from decoding EIDFCT to gating data to the bus is +;marginal. Some logic in the interface detects the situation +;(which only happens occasionally) and stops SysClk for one cycle. +;Since memory data must be available during cycle 4, and SysClk +;may stop for one cycle, this means that the MD_ EIDFCT must +;happen in cycle 3. There is a bug in this logic which occasionally +;stops the clock in the instruction following the EIDFCT, so +;the EIDFCT instruction should not be the last one of the task, +;or it may screw up someone else (such as RDRAM). + +;EIDOK, EIDMOR, and EIDPST must have address bits in the pattern: +;xxx1 xxx4 xxx5 +;ECBFCT is used to force an unconditional branch on NEXT7 + +EIDATA: T_ ECNTR-1, BUS=0; + MAR_ L_ EPNTR+1, EBFCT; [EIDMOR,EIDPST] What's happening +EIDMOR: EPNTR_ L, L_ T, ECBFCT; [EIDOK,EIDPST] Guaranteed to branch +EIDOK: MD_ EIDFCT, TASK; [EIDZ4] Read a word from the interface +EIDZ4: ECNTR_ L, :EIDATA; + +; We get to EIDPST for one of two reasons: +; (1) The buffer is full. In this case, an EBFCT (NEXT[7]) is pending. +; We want to post "full" if this is a normal data wakeup (no branch) +; but just "input done" if hardware input terminated (branch). +; (2) Hardware input terminated while the buffer was not full. +; In this case, an unconditional branch on NEXT[7] is pending, so +; we always terminate with "input done". +EIDPST: L_ ESIDON, :EIDFUL; [EIDFUL,EPOST] Presumed to be INGONE +EIDFUL: L_ ESIFUL, :EPOST; Input buffer overrun + +;Ethernet output + +;It is possible to get here due to a collision. If a collision +;happened, the interface was reset (EPFCT) to shut off the +;transmitter. EOSFCT is issued to guarantee more wakeups while +;generating the countdown. When this is done, the interface is +;again reset, without really doing an output. + +EN00207> EOREST: MAR<- ELLOC; Get load +EN00274> L<- R37; Use clock as random # gen +EN00275> EPNTR<- LLSH1; Use bits [2:9] +EN00276> L<- MD,EOSFCT; L<- current load +EN00277> SH<0,ECNTR<- L; Overflowed? +EN00300> MTEMP<- LLSH1,:EOLDOK; [EOLDOK,EOLDBD] + +EOLDBD: L_ ESLOAD,:EPOST; Load overlow + +EOLDOK: L_ MTEMP+1; Write updated load + MAR_ ELLOC; + MTEMP_ L,TASK; + MD_ MTEMP,:EORST1; New load = (old lshift 1) + 1 + +EORST1: L_ EPNTR; Continue making random # + EPNTR_ LRSH1; + T_ 377; + L_ EPNTR AND T,TASK; + EPNTR_ L,:EORST2; + +;At this point, EPNTR has 0,,random number, ENCTR has old load. + +EORST2: MAR_ EICLOC; Has an input buffer been set up? + T_ ECNTR; + L_ EPNTR AND T; L_ Random & Load + SINK_ MD,BUS=0; + ECNTR_ L,SH=0,EPFCT,:EOINPR;[EOINPR,EOINPN] + +EOINPR: EISFCT,:EOCDWT; [EOCDWT,EOCDGO] Enable in under out + +EOINPN: :EOCDWT; [EOCDWT,EOCDGO] No input. + +;Countdown wait loop. MRT will generate a wakeup every +;37 usec which will decrement ECNTR. When it is zero, start +;the transmitter. + +;Ether Wake Function - EWFCT. Sets a flip flop which will cause +;a wakeup to this task the next time MRT wakes up (every 37 usec). +;Wakeup is cleared when Ether task next runs. EWFCT must be +;issued in the instruction AFTER a task. + +EOCDWT: L_ 177400,EBFCT; What's happening? + EPNTR_ L,ECBFCT,:EOCDW0;[EOCDW0,EOCDRS] Packet coming in? +EOCDW0: L_ ECNTR-1,BUS=0,TASK,:EOCDW1; [EOCDW1,EIGO] +EOCDW1: ECNTR_ L,EWFCT,:EOCDWT; [EOCDWT,EOCDGO] + +EOCDRS: L_ ESABRT,:EPOST; [EPOST] POST event + +EIGO: :EIFRST; [EIFRST] Input under output + +;Output main loop setup + +EOCDGO: MAR_ EOCLOC; Double word reference + EPFCT; Reset interface + EOSFCT,:ESETUP; Start Transmitter + +;Ether Output Start Function - EOSFCT. The interface will generate +;a burst of data requests until the interface buffer is full or the +;memory buffer is empty, wait for silence on the Ether, and begin +;transmitting. Thereafter it will request a word every 5.44 us. + +;Ether Output Data Function - EODFCT. Copy the bus into the +;interface data buffer, increment the write pointer, clears wakeup +;request if the buffer is now nearly full (one slot available). + +;Output main loop + +EODATA: L_ MAR_ EPNTR+1,EBFCT; What's happening? + T_ ECNTR-1,BUS=0,:EODOK; [EODOK,EODPST,EODCOL,EODUGH] +EODOK: EPNTR_ L,L_ T,:EODMOR; [EODMOR,EODEND] +EODMOR: ECNTR_ L,TASK; + EODFCT_ MD,:EODATA; Output word to transmitter + +EODPST: L_ ESABRT,:EPOST; [EPOST] POST event + +EODCOL: EPFCT,:EOREST; [EOREST] Collision + +EODUGH: L_ ESABRT,:EPOST; [EPOST] POST + Collision + +;Ether EOT Function - EEFCT. Stop generating output data wakeups, +;the interface has all of the packet. When the data buffer runs +;dry, the interface will append the CRC and then generate an +;OUTGONE post wakeup. + +EODEND: EEFCT; Disable data wakeups + TASK; Wait for EEFCT to take + :EOEOT; Wait for Outgone + +;Output completion. We are waiting for the interface buffer to +;empty, and the interface to generate an OUTGONE Post wakeup. + +EOEOT: EBFCT; What's happening? + :EOEOK; [EOEOK,EOEPST,EOECOL,EOEUGH] + +EOEOK: L_ ESNEVR,:EPOST; Runaway Transmitter. Never Never. + +EOEPST: L_ ESODON,:EPOST; POST event. Output done + +EOECOL: EPFCT,:EOREST; Collision + +EOEUGH: L_ ESABRT,:EPOST; POST + Collision + + +;Memory Refresh Task, +;Mouse Handler, +;EIA Handler, +;Interval Timer, +;Calender Clock, and +;part of the cursor. + +!17,20,TX0,TX6,TX3,TX2,TX8,TX5,TX1,TX7,TX4,,,,,,,; +!1,2,DOTIMER,NOTIMER; +!1,2,NOTIMERINT,TIMERINT; +!1,2,DOCUR,NOCUR; +!1,2,SHOWC,WAITC; +!1,2,SPCHK,NOSPCHK; + +!1,2,NOCLK,CLOCK; +!1,1,MRTLAST; +!1,2,CNOTLAST,CLAST; + +$CLOCKTEMP $R11; +$REFIIMSK $7777; + +; * * * A T T E N T I O N * * * +;There are two versions of the Memory refresh code: +; AltoIIMRT4K.mu for refreshing 4K chips +; AltoIIMRT16K.mu for refreshing 16K chips +;You must name one or the other 'AltoIIMRT.mu'. +;I suggest the following convention for naming the resulting .MB file: +; AltoIICode3.MB for the 4K version +; AltoIICode3XM.MB for the 16K version + +#AltoIIMRT.mu; + +CLOCK: MAR_ CLOCKLOC; R37 OVERFLOWED. + NOP; + L_ MD+1; INCREMENT CLOCK IM MEMORY + MAR_ CLOCKLOC; + MTEMP_ L, TASK; + MD_ MTEMP, :NOCLK; + +DOCUR: L_ T_ YPOS; CHECK FOR VISIBLE CURSOR ON THIS SCAN + SH<0, L_ 20-T-1; ***x13 change: the constant 20 was 17 + SH<0, L_ 2+T, :SHOWC; [SHOWC,WAITC] + +WAITC: YPOS_ L, L_ 0, TASK, :MRTLAST; SQUASHES PENDING BRANCH +SHOWC: MAR_ CLOCKLOC+T+1, :CNOTLAST; + +CNOTLAST: T_ CURX, :CURF; +CLAST: T_ 0; +CURF: YPOS_ L, L_ T; + CURX_ L; + L_ MD, TASK; + CURDATA_ L, :MRT; + +;AFTER THIS DISPATCH, T WILL CONTAIN XCHANGE, L WILL CONTAIN YCHANGE-1 + +TX1: L_ T_ ONE +T, :M00; Y=0, X=1 +TX2: L_ T_ ALLONES, :M00; Y=0, X=-1 +TX3: L_ T_ 0, :M00; Y=1, X=0 +TX4: L_ T_ ONE AND T, :M00; Y=1, X=1 +TX5: L_ T_ ALLONES XOR T, :M00; Y=1, X=-1 +TX6: T_ 0, :M00; Y=-1, X=0 +TX7: T_ ONE, :M00; Y=-1, X=1 +TX8: T_ ALLONES, :M00; Y=-1, X=-1 + +M00: MAR_ MOUSELOC; START THE FETCH OF THE COORDINATES + MTEMP_ L; YCHANGE -1 + L_ MD+ T; X+ XCHANGE + T_ MD; Y + T_ MTEMP+ T+1; Y+ (YCHANGE-1) + 1 + MTEMP_ L, L_ T; + MAR_ MOUSELOC; NOW RESTORE THE UPDATED COORDINATES + CLOCKTEMP_ L; + MD_ MTEMP, TASK; + MD_ CLOCKTEMP, :MRTA; + + +;CURSOR TASK + +;Cursor task specific functions +$XPREG $L026010,000000,124000; F2 = 10 +$CSR $L026011,000000,124000; F2 = 11 + +CU00012> CURT: XPREG<- CURX, TASK; +CU00437> CSR<- CURDATA, :CURT; + + +;PREDEFINITION FOR PARITY TASK. +;THE CODE IS AT THE END OF THE FILE +!17,20,PR0,,PR2,PR3,PR4,PR5,PR6,PR7,PR8,,,,,,,; + +;NOVA EMULATOR + +$SAD $R5; +$PC $R6; USED BY MEMORY INIT + + +!7,10,Q0,Q1,Q2,Q3,Q4,Q5,Q6,Q7; +!1,2,FINSTO,INCPC; +!1,2,EReRead,FINJMP; ***X21 addition. +!1,2,EReadDone,EContRead; ***X21 addition. +!1,2,EtherBoot,DiskBoot; ***X21 addition. + +EM00000> NOVEM: IR<-L<-MAR<-0, :INXB,SAD<- L; LOAD SAD TO ZERO THE BUS. STORE PC AT 0 +EM00460> Q0: L<- ONE, :INXA; EXECUTED TWICE +EM00461> Q1: L<- TOTUWC, :INXA; +EM00462> Q2: L<-402, :INXA; FIRST READ HEADER INTO 402, THEN +EM00463> Q3: L<- 402, :INXA; STORE LABEL AT 402 +EM00464> Q4: L<- ONE, :INXA; STORE DATA PAGE STARTING AT 1 +EM00465> Q5: L<-377+1, :INXE; Store Ethernet Input Buffer Length ***X21. +EM00466> Q6: L<-ONE, :INXE; Store Ethernet Input Buffer Pointer ***X21. +EM00467> Q7: MAR<- DASTART; CLEAR THE DISPLAY POINTER +EM00441> L<- 0; +EM00451> R37<- L; +EM00472> MD<- 0; +EM00473> MAR<- 177034; FETCH KEYBOARD +EM00474> L<- 100000; +EM00475> NWW<- L, T<- 0-1; +EM00476> L<- MD XOR T, BUSODD; *** X21 change. +EM00477> MAR<- BDAD, :EtherBoot; [EtherBoot, DiskBoot] *** X21 change. + ; BOOT DISK ADDRESS GOES IN LOCATION 12 +EM00471> DiskBoot: SAD<- L, L<- 0+1; +EM00500> MD<- SAD; +EM00501> MAR<- KBLKADR, :FINSTO; + + +; Ethernet boot section added in X21. +$NegBreathM1 $177175; +$EthNovaGo $3; First data location of incoming packet + +EM00470> EtherBoot: L<-EthNovaGo, :EReRead; [EReRead, FINJMP] + +EM00454> EReRead:MAR_ EHLOC; Set the host address to 377 for breath packets +EM00502> TASK; +EM00503> MD_ 377; + +EM00504> MAR_ EPLOC; Zero the status word and start 'er up +EM00505> SINK_ 2, STARTF; +EM00506> MD _ 0; + +EM00457> EContRead: MAR_ EPLOC; See if status is still 0 +EM00507> T_ 377; Status for correct read +EM00510> L_ MD XOR T, TASK, BUS=0; +EM00511> SAD_ L, :EReadDone; [EReadDone, EContRead] + +EM00456> EReadDone: MAR_ 2; Check the packet type +EM00512> T_ NegBreathM1; -(Breath-of-life)-1 +EM00513> T_MD+T+1; +EM00514> L_SAD OR T; +EM00515> SH=0, :EtherBoot; + + +; SUBROUTINE USED BY INITIALIZATION TO SET UP BLOCKS OF MEMORY +$EIOffset $576; + +EM00516> INXA: T<-ONE, :INXCom; ***X21 change. +EM00517> INXE: T<-EIOffset, :INXCom; ***X21 addition. + +EM00520> INXCom: MAR<-T<-IR<- SAD+T; *** X21 addition. +EM00521> PC<- L, L<- 0+T+1; *** X21 change. +EM00522> INXB: SAD<- L; +EM00523> SINK<- DISP, BUS,TASK; +EM00524> SAD<- L, :Q0; + + +;REGISTERS USED BY NOVA EMULATOR +$AC0 $R3; AC'S ARE BACKWARDS BECAUSE THE HARDWARE SUPPLIES THE +$AC1 $R2; COMPLEMENT ADDRESS WHEN ADDRESSING FROM IR +$AC2 $R1; +$AC3 $R0; +$XREG $R7; + + +;PREDEFINITIONS FOR NOVA + +!17,20,GETAD,G1,G2,G3,G4,G5,G6,G7,G10,G11,G12,G13,G14,G15,G16,G17; +!17,20,XCTAB,XJSR,XISZ,XDSZ,XLDA,XSTA,CONVERT,,,,,,,,,; +!3,4,SHIFT,SH1,SH2,SH3; +!1,2,MAYBE,NOINT; +!1,2,DOINT,DIS0; +!1,2,SOMEACTIVE,NOACTIVE; +!1,2,IEXIT,NIEXIT; +!17,1,ODDCX; +!1,2,EIR0,EIR1; +!7,1,INTCODE; +!1,2,INTSOFF,INTSON; ***X21 addition for DIRS +!7,10,EMCYCRET,RAMCYCRET,CYX2,CYX3,CYX4,CONVCYCRET,,; +!7,2,MOREBLT,FINBLT; +!1,2,DOIT,DISABLED; + +; ALL INSTRUCTIONS RETURN TO START WHEN DONE + +EM00020> START: T<- MAR_PC+SKIP; +EM00525> START1: L<- NWW, BUS=0; BUS# 0 MEANS DISABLED OR SOMETHING TO DO +EM00576> :MAYBE, SH<0, L<- 0+T+1; SH<0 MEANS DISABLED +EM00526> MAYBE: PC<- L, L<- T, :DOINT; +EM00527> NOINT: PC<- L, :DIS0; + +EM00534> DOINT: MAR<- WWLOC, :INTCODE; TRY TO CAUSE AN INTERRUPT + +;DISPATCH ON FUNCTION FIELD IF ARITHMETIC INSTRUCTION, +;OTHERWISE ON INDIRECT BIT AND INDEX FIELD + +EM00535> DIS0: L<- T<- IR<- MD; SKIP CLEARED HERE + +;DISPATCH ON SHIFT FIELD IF ARITHMETIC INSTRUCTION, +;OTHERWISE ON THE INDIRECT BIT OR IR[3-7] + +EM00612> DIS1: T<- ACSOURCE, :GETAD; + +;GETAD MUST BE 0 MOD 20 +EM00540> GETAD: T<- 0, :DOINS; PAGE 0 +EM00541> G1: T<- PC -1, :DOINS; RELATIVE +EM00542> G2: T<- AC2, :DOINS; AC2 RELATIVE +EM00543> G3: T<- AC3, :DOINS; AC3 RELATIVE +EM00544> G4: T<- 0, :DOINS; PAGE 0 INDIRECT +EM00545> G5: T<- PC -1, :DOINS; RELATIVE INDIRECT +EM00546> G6: T<- AC2, :DOINS; AC2 RELATIVE INDIRECT +EM00547> G7: T<- AC3, :DOINS; AC3 RELATIVE INDIRECT +EM00550> G10: L<- 0-T-1, TASK, :SHIFT; COMPLEMENT +EM00551> G11: L<- 0-T, TASK, :SHIFT; NEGATE +EM00552> G12: L<- 0+T, TASK, :SHIFT; MOVE +EM00553> G13: L<- 0+T+1, TASK, :SHIFT; INCREMENT +EM00554> G14: L<- ACDEST-T-1, TASK, :SHIFT; ADD COMPLEMENT +EM00555> G15: L<- ACDEST-T, TASK, :SHIFT; SUBTRACT +EM00556> G16: L<- ACDEST+T, TASK, :SHIFT; ADD +EM00557> G17: L<- ACDEST AND T, TASK, :SHIFT; + +EM00530> SHIFT: DNS<- L LCY 8, :START; SWAP BYTES +EM00531> SH1: DNS<- L RSH 1, :START; RIGHT 1 +EM00532> SH2: DNS<- L LSH 1, :START; LEFT 1 +EM00533> SH3: DNS<- L, :START; NO SHIFT + +EM00060> DOINS: L<- DISP + T, TASK, :SAVAD, IDISP; DIRECT INSTRUCTIONS +EM00061> DOIND: L<- MAR<- DISP+T; INDIRECT INSTRUCTIONS +EM00613> XREG<- L; +EM00614> L<- MD, TASK, IDISP, :SAVAD; + +EM00102> BRI: L<- MAR<- PCLOC ;INTERRUPT RETURN BRANCH +EM00615> BRI0: T<- 77777; +EM00616> L<- NWW AND T, SH < 0; +EM00617> NWW<- L, :EIR0; BOTH EIR AND BRI MUST CHECK FOR INTERRUPT +; REQUESTS WHICH MAY HAVE COME IN WHILE +; INTERRUPTS WERE OFF + +EM00572> EIR0: L<- MD, :DOINT; +EM00573> EIR1: L<- PC, :DOINT; + +;***X21 addition +; DIRS - 61013 - Disable Interrupts and Skip if they were On +EM00113> DIRS: T<-100000; +EM00620> L<-NWW AND T; +EM00621> L<-PC+1, SH=0; + +; DIR - 61000 - Disable Interrupts +EM00100> DIR: T<- 100000, :INTSOFF; +EM00574> INTSOFF: L<- NWW OR T, TASK, :INTZ; + +EM00575> INTSON: PC<-L, :INTSOFF; + +;EIR - 61001 - Enable Interrupts +EM00101> EIR: L<- 100000, :BRI0; + +;SIT - 61007 - Start Interval Timer +EM00107> SIT: T<- AC0; +EM00622> L<- R37 OR T, TASK; +EM00623> R37<- L, :START; + + +EM00624> FINJSR: L<- PC; +EM00625> AC3<- L, L_ T, TASK; +EM00455> FINJMP: PC<- L, :START; +EM00626> SAVAD: SAD<- L, :XCTAB; + +;JSRII - 64400 - JSR double indirect, PC relative. Must have X=1 in opcode +;JSRIS - 65000 - JSR double indirect, AC2 relative. Must have X=2 in opcode +EM00064> JSRII: MAR<- DISP+T; FIRST LEVEL +EM00627> IR<- JSRCX; +EM00630> T_ MD, :DOIND; THE IR_ INSTRUCTION WILL NOT BRANCH + + +;TRAP ON UNIMPLEMENTED OPCODES. SAVES PC AT +;TRAPPC, AND DOES A JMP@ TRAPVEC ! OPCODE. +EM00077> TRAP: XREG<- L LCY 8; THE INSTRUCTION +EM00037> TRAP1: MAR<- TRAPPC;***X13 CHANGE: TAG 'TRAP1' ADDED +EM00631> IR<- T<- 37; +EM00632> MD<- PC; +EM00633> T<- XREG.T; +EM00634> T<- TRAPCON+T+1, :DOIND; T NOW CONTAINS 471+OPCODE +; THIS WILL DO JMP@ 530+OPCODE + +;***X21 CHANGE: ADDED TAG RAMTRAP +EM00076> RAMTRAP: SWMODE, :TRAP; + +; Parameterless operations come here for dispatch. + +!1,2,NPNOTRAP,NPTRAP; + +EM00063> NOPAR: XREG<-L LCY 8; ***X21 change. Checks < 27. +EM00635> T<-27; ***IIX3. Greatest defined op is 26. +EM00640> L<-DISP-T; +EM00641> ALUCY; +EM00642> SINK<-DISP, SINK<-X37, BUS, TASK, :NPNOTRAP; + +EM00636> NPNOTRAP: :DIR; + +EM00637> NPTRAP: :TRAP1; + +;***X21 addition for debugging w/ expanded DISP Prom +EM00065> U5: :RAMTRAP; +EM00066> U6: :RAMTRAP; +EM00067> U7: :RAMTRAP; + +;MAIN INSTRUCTION TABLE. GET HERE: +; (1) AFTER AN INDIRECTION +; (2) ON DIRECT INSTRUCTIONS + +EM00560> XCTAB: L<- SAD, TASK, :FINJMP; JMP +EM00561> XJSR: T<- SAD, :FINJSR; JSR +EM00562> XISZ: MAR<- SAD, :ISZ1; ISZ +EM00563> XDSZ: MAR<- SAD, :DSZ1; DSZ +EM00564> XLDA: MAR<- SAD, :FINLOAD; LDA 0-3 +EM00565> XSTA: MAR<- SAD; /*NORMAL +EM00643> XSTA1: L<- ACDEST, :FINSTO; /*NORMAL + +; BOUNDS-CHECKING VERSION OF STORE +; SUBST ";**" TO ";**" TO ENABLE THIS CODE: +;** !1,2,XSTA1,XSTA2; +;** !1,2,DOSTA,TRAPSTA; +;**XSTA: MAR_ 10; LOCS 10,11 CONTAINS HI,LO BOUNDS +;** T_ SAD +;** L_ MD-T; HIGHBOUND-ADDR +;** T_ MD, ALUCY; +;** L_ SAD-T, :XSTA1; ADDR-LOWBOUND +;**XSTA1: TASK, :XSTA3; +;**XSTA2: ALUCY, TASK; +;**XSTA3: L_ 177, :DOSTA; +;**TRAPSTA: XREG_ L, :TRAP1; CAUSE A SWAT +;**DOSTA: MAR_ SAD; DO THE STORE NORMALLY +;** L_ ACDEST, :FINSTO; +;** + +EM00644> DSZ1: T<- ALLONES, :FINISZ; +EM00645> ISZ1: T<- ONE, :FINISZ; + +EM00452> FINSTO: SAD<- L,TASK; +EM00646> FINST1: MD<-SAD, :START; + +EM00647> FINLOAD: NOP; +EM00650> LOADX: L<- MD, TASK; +EM00651> LOADD: ACDEST<- L, :START; + +EM00652> FINISZ: L<- MD+T; +EM00653> MAR<- SAD, SH=0; +EM00654> SAD<- L, :FINSTO; + +EM00453> INCPC: MD<- SAD; +EM00655> L<- PC+1, TASK; +EM00656> PC<- L, :START; + +;DIVIDE. THIS DIVIDE IS IDENTICAL TO THE NOVA DIVIDE EXCEPT THAT +;IF THE DIVIDE CANNOT BE DONE, THE INSTRUCTION FAILS TO SKIP, OTHERWISE +;IT DOES. CARRY IS UNDISTURBED. + +!1,2,DODIV,NODIV; +!1,2,DIVL,ENDDIV; +!1,2,NOOVF,OVF; +!1,2,DX0,DX1; +!1,2,NOSUB,DOSUB; + +EM00121> DIV: T<- AC2; +EM00657> DIVX: L<- AC0 - T; DO THE DIVIDE ONLY IF AC2>AC0 +EM00672> ALUCY, TASK, SAD<- L, L<- 0+1; +EM00673> :DODIV, SAD<- L LSH 1; SAD<- 2. COUNT THE LOOP BY SHIFTING + +EM00661> NODIV: :FINBLT; ***X21 change. +EM00660> DODIV: L<- AC0, :DIV1; + +EM00662> DIVL: L<- AC0; +EM00674> DIV1: SH<0, T<- AC1; WILL THE LEFT SHIFT OF THE DIVIDEND OVERFLOW? +EM00675> :NOOVF, AC0<- L MLSH 1, L<- T<- 0+T; L<- AC1, T<- 0 + +EM00665> OVF: AC1<- L LSH 1, L<- 0+INCT, :NOV1; L_ 1. SHIFT OVERFLOWED +EM00664> NOOVF: AC1<- L LSH 1 , L<- T; L_ 0. SHIFT OK + +EM00676> NOV1: T<- AC2, SH=0; +EM00677> L<- AC0-T, :DX0; + +EM00667> DX1: ALUCY; DO THE TEST ONLY IF THE SHIFT DIDN'T OVERFLOW. IF +; IT DID, L IS STILL CORRECT, BUT THE TEST WOULD GO +; THE WRONG WAY. +EM00700> :NOSUB, T<- AC1; + +EM00666> DX0: :DOSUB, T<- AC1; + +EM00671> DOSUB: AC0<- L, L<- 0+INCT; DO THE SUBTRACT +EM00701> AC1<- L; AND PUT A 1 IN THE QUOTIENT + +EM00670> NOSUB: L<- SAD, BUS=0, TASK; +EM00702> SAD<- L LSH 1, :DIVL; + +EM00663> ENDDIV: L<- PC+1, TASK, :DOIT; ***X21 change. Skip if divide was done. + + +;MULTIPLY. THIS IS AN EXACT EMULATION OF NOVA HARDWARE MULTIPLY. +;AC2 IS THE MULTIPLIER, AC1 IS THE MULTIPLICAND. +;THE PRODUCT IS IN AC0 (HIGH PART), AND AC1 (LOW PART). +;PRECISELY: AC0,AC1 <- AC1*AC2 + AC0 + +!1,2,DOMUL,NOMUL; +!1,2,MPYL,MPYA; +!1,2,NOADDIER,ADDIER; +!1,2,NOSPILL,SPILL; +!1,2,NOADDX,ADDX; +!1,2,NOSPILLX,SPILLX; + + +EM00120> MUL: L<- AC2-1, BUS=0; +EM00703> MPYX: XREG<-L,L<- 0, :DOMUL; GET HERE WITH AC2-1 IN L. DON'T MUL IF AC2=0 +EM00704> DOMUL: TASK, L<- -10+1; +EM00720> SAD<- L; COUNT THE LOOP IN SAD + +EM00706> MPYL: L<- AC1, BUSODD; +EM00721> T<- AC0, :NOADDIER; + +EM00710> NOADDIER: AC1<- L MRSH 1, L<- T, T<- 0, :NOSPILL; +EM00711> ADDIER: L<- T<- XREG+INCT; +EM00722> L<- AC1, ALUCY, :NOADDIER; + +EM00713> SPILL: T<- ONE; +EM00712> NOSPILL: AC0<- L MRSH 1; +EM00723> L<- AC1, BUSODD; +EM00724> T<- AC0, :NOADDX; + +EM00714> NOADDX: AC1<- L MRSH 1, L<- T, T<- 0, :NOSPILLX; +EM00715> ADDX: L<- T<- XREG+ INCT; +EM00725> L<- AC1,ALUCY, :NOADDX; + +EM00717> SPILLX: T<- ONE; +EM00716> NOSPILLX: AC0<- L MRSH 1; +EM00726> L<- SAD+1, BUS=0, TASK; +EM00727> SAD<- L, :MPYL; + +EM00705> NOMUL: T<- AC0; +EM00730> AC0<- L, L<- T, TASK; CLEAR AC0 +EM00731> AC1<- L; AND REPLACE AC1 WITH AC0 +EM00707> MPYA: :FINBLT; ***X21 change. + +;CYCLE AC0 LEFT BY DISP MOD 20B, UNLESS DISP=0, IN WHICH +;CASE CYCLE BY AC1 MOD 20B +;LEAVES AC1=CYCLE COUNT-1 MOD 20B + +$CYRET $R5; Shares space with SAD. +$CYCOUT $R7; Shares space with XREG. + +!1,2,EMCYCX,ACCYCLE; +!1,1,Y1; +!1,1,Y2; +!1,1,Y3; +!1,1,Z1; +!1,1,Z2; +!1,1,Z3; + +EM00062> EMCYCLE: L<- DISP, SINK<- X17, BUS=0; CONSTANT WITH BS=7 +EM00734> CYCP: T<- AC0, :EMCYCX; + +EM00733> ACCYCLE: T<- AC1; +EM00736> L<- 17 AND T, :CYCP; + +EM00732> EMCYCX: CYCOUT<-L, L<-0, :RETCYCX; + +RAMCYCX: CYCOUT_L, L_0+1; + +EM00740> RETCYCX: CYRET<-L, L<-0+T; +EM00742> SINK<-CYCOUT, BUS; +EM00744> TASK, :L0; + +;TABLE FOR CYCLE +EM00174> R4: CYCOUT<- L MRSH 1; +EM00741> Y3: L<- T<- CYCOUT, TASK; +EM00175> R3X: CYCOUT<- L MRSH 1; +EM00737> Y2: L<- T<- CYCOUT, TASK; +EM00176> R2X: CYCOUT<- L MRSH 1; +EM00735> Y1: L<- T<- CYCOUT, TASK; +EM00177> R1X: CYCOUT<- L MRSH 1, :ENDCYCLE; + +EM00164> L4: CYCOUT<- L MLSH 1; +EM00747> Z3: L<- T<- CYCOUT, TASK; +EM00163> L3: CYCOUT<- L MLSH 1; +EM00745> Z2: L<- T<- CYCOUT, TASK; +EM00162> L2: CYCOUT<- L MLSH 1; +EM00743> Z1: L<- T<- CYCOUT, TASK; +EM00161> L1: CYCOUT<- L MLSH 1, :ENDCYCLE; +EM00160> L0: CYCOUT<- L, :ENDCYCLE; + +EM00164> L8: CYCOUT<- L LCY 8, :ENDCYCLE; +EM00165> L7: CYCOUT<- L LCY 8, :Y1; +EM00166> L6: CYCOUT<- L LCY 8, :Y2; +EM00165> L5: CYCOUT<- L LCY 8, :Y3; + +EM00171> R7: CYCOUT<- L LCY 8, :Z1; +EM00172> R6: CYCOUT<- L LCY 8, :Z2; +EM00173> R5: CYCOUT<- L LCY 8, :Z3; + +EM00746> ENDCYCLE: SINK<- CYRET, BUS, TASK; +EM00750> :EMCYCRET; + +EM00600> EMCYCRET: L_<-YCOUT, TASK, :LOADD; + +EM00601> RAMCYCRET: T<-PC, BUS, SWMODE, :TORAM; + +; Scan convert instruction for characters. Takes DWAX (Destination +; word address)-NWRDS in AC0, and a pointer to a .AL-format font +; in AC3. AC2+displacement contains a pointer to a two-word block +; containing NWRDS and DBA (Destination Bit Address). + +$XH $R10; +$DWAX $R35; +$MASK $R36; + +!1,2,HDLOOP,HDEXIT; +!1,2,MERGE,STORE; +!1,2,NFIN,FIN; +!17,2,DOBOTH,MOVELOOP; + +CONVERT: MAR_XREG+1; Got here via indirect mechanism which +; left first arg in SAD, its address in XREG. + T_17; + L_MD AND T; + + T_MAR_AC3; + AC1_L; AC1_DBA + L_MD+T, TASK; + AC3_L; AC3_Character descriptor block address(Char) + + MAR_AC3+1; + T_177400; + IR_L_MD AND T; IR_XH + XH_L LCY 8, :ODDCX; XH register temporarily contains HD +ODDCX: L_AC0, :HDENTER; + +HDLOOP: T_SAD; (really NWRDS) + L_DWAX+T; + +HDENTER: DWAX_L; DWAX _ AC0+HD*NWRDS + L_XH-1, BUS=0, TASK; + XH_L, :HDLOOP; + +HDEXIT: T_MASKTAB; + MAR_T_AC1+T; Fetch the mask. + L_DISP; + XH_L; XH register now contains XH + L_MD; + MASK_L, L_0+T+1, TASK; + AC1_L; ***X21. AC1 _ (DBA)+1 + + L_5; ***X21. Calling conventions changed. + IR_SAD, TASK; + CYRET_L, :MOVELOOP; CYRET_CALL5 + +MOVELOOP: L_T_XH-1, BUS=0; + MAR_AC3-T-1, :NFIN; Fetch next source word +NFIN: XH_L; + T_DISP; (really NWRDS) + L_DWAX+T; Update destination address + T_MD; + SINK_AC1, BUS; + DWAX_L, L_T, TASK, :L0; Call Cycle subroutine + +CONVCYCRET: MAR_DWAX; + T_MASK, BUS=0; + T_CYCOUT.T, :MERGE; Data for first word. If MASK=0 + ; then store the word rather than + ; merging, and do not disturb the + ; second word. +MERGE: L_XREG AND NOT T; Data for second word. + T_MD OR T; First word now merged, + XREG_L, L_T; + MTEMP_L; + MAR_DWAX; restore it. + SINK_XREG, BUS=0, TASK; + MD_MTEMP, :DOBOTH; XREG=0 means only one word + ; is involved. + +DOBOTH: MAR_DWAX+1; + T_XREG; + L_MD OR T; + MAR_DWAX+1; + XREG_L, TASK; ***X21. TASK added. +STORE: MD_XREG, :MOVELOOP; + +FIN: L_AC1-1; ***X21. Return AC1 to DBA. + AC1_L; *** ... bletch ... + IR_SH3CONST; + L_MD, TASK, :SH1; + +;RCLK - 61003 - Read the Real Time Clock into AC0,AC1 +EM00103> RCLK: MAR<- CLOCKLOC; +EM01035> L<- R37; +EM01036> AC1<- L, :LOADX; + +;SIO - 61004 - Put AC0 on the bus, issue STARTF to get device attention, +;Read Host address from Ethernet interface into AC0. +EM0104> SIO: L<- AC0, STARTF; +EM1037> T<- 77777; ***X21 sets AC0[0] to 0 +EM1040> L<- RSNF AND T; +EM1041> LTOAC0: AC0<- L, TASK, :TOSTART; + +;EngNumber is a constant returned by VERS that contains a discription +;of the Alto and it's Microcode. The composition of EngNumber is: +; bits 0-3 Alto engineering number +; bits 4-7 Alto build +; bits 8-15 Version number of Microcode +;Use of the Alto Build number has been abandoned. +;the engineering number (EngNumber) is in the MRT files because it +; it different for Altos with and without Extended memory. +EM00114> VERS: T<- EngNumber; ***V3 change +EM01042> L<- 3+T, :LTOAC0; ***V3 change + +;XMLDA - Extended Memory Load Accumulator. +; AC0 _ @AC1 in the alternate bank +XMLDA: XMAR_ AC1, :FINLOAD; ***V3 change + +;XMSTA - Extended Memory Store Accumulator +; @AC1 _ AC0 in the alternate bank +XMSTA: XMAR_ AC1, :XSTA1; ***V3 change + +;BLT - 61005 - Block Transfer +;BLKS - 61006 - Block Store +; Accepts in +; AC0/ BLT: Address of first word of source block-1 +; BLKS: Data to be stored +; AC1/ Address of last word of destination block +; AC3/ NEGATIVE word count +; Leaves +; AC0/ BLT: Address of last word of source block+1 +; BLKS: Unchanged +; AC1/ Unchanged +; AC2/ Unchanged +; AC3/ 0 +; These instructions are interruptable. If an interrupt occurs, +; the PC is decremented by one, and the ACs contain the intermediate +; so the instruction can be restarted when the interrupt is dismissed. + +!1,2,PERHAPS, NO; + +EM00105> BLT: L<- MAR<- AC0+1; +EM01043> AC0<- L; +EM01046> L<- MD, :BLKSA; + +EM00106> BLKS: L<- AC0; +EM01047> BLKSA: T<- AC3+1, BUS=0; +EM01050> MAR<- AC1+T, :MOREBLT; + +EM00606> MOREBLT: XREG<- L, L<- T; +EM01051> AC3<- L, TASK; +EM01052> MD<- XREG; STORE +EM01053> L<- NWW, BUS=0; CHECK FOR INTERRUPT +EM01054> SH<0, :PERHAPS, L<- PC-1; Prepare to back up PC. + +EM01045> NO: SINK<- DISP, SINK<- M7, BUS, :DISABLED; + +EM01044> PERHAPS: SINK<- DISP, SINK<- M7, BUS, :DOIT; + +EM00610> DOIT: PC<-L, :FINBLT; ***X21. Reset PC, terminate instruction. + +EM00611> DISABLED: :DIR; GOES TO BLT OR BLKS + +EM00607> FINBLT: T<-777; ***X21. PC in [177000-177777] means Ram return +EM01055> L<-PC+T+1; +EM01056> L<-PC AND T, TASK, ALUCY; +EM01057> TOSTART: XREG<-L, :START; + +RAMRET: T_XREG, BUS, SWMODE; +EM01060> TORAM: :NOVEM; + +;PARAMETERLESS INSTRUCTIONS FOR DIDDLING THE WCS. + +;JMPRAM - 61010 - JUMP TO THE RAM ADDRESS SPECIFIED BY AC1 +EM00110> JMPR: T<-AC1, BUS, SWMODE, :TORAM; + + +;RDRAM - 61011 - READ THE RAM WORD ADDRESSED BY AC1 INTO AC0 +EM00111> RDRM: T<- AC1, RDRAM; +EM01061> L_ ALLONES, TASK, :LOADD; + + +;WRTRAM - 61012 - WRITE AC0,AC3 INTO THE RAM LOCATION ADDRESSED BY AC1 +EM00112> WTRM: T<- AC1; +EM01062> L<- AC0, WRTRAM; +EM01063> L<- AC3, :FINBLT; + +;DOUBLE WORD INSTRUCTIONS + +;DREAD - 61015 +; AC0<- rv(AC3); AC1<- rv(AC3 xor 1) + +EM00115> DREAD: MAR<- AC3; START MEMORY CYCLE +EM01064> NOP; DELAY +EM01065> DREAD1: L<- MD; FIRST READ +EM01066> T<-MD; SECOND READ +EM01067> AC0<- L, L<-T, TASK; STORE MSW +EM01070> AC1<- L, :START; STORE LSW + + +;DWRITE - 61016 +; rv(AC3)<- AC0; rv(AC3 xor 1)<- AC1 + +EM00116> DWRITE: MAR<- AC3; START MEMORY CYCLE +EM01071> NOP; DELAY +EM01072> MD<- AC0, TASK; FIRST WRITE +EM01073> MD<- AC1, :START; SECOND WRITE + + +;DEXCH - 61017 +; t<- rv(AC3); rv(AC3)<- AC0; AC0<- t +; t<- rv(AC3 xor 1); rv(AC3 xor 1)<- AC1; AC1<- t + +EM00117> DEXCH: MAR<- AC3; START MEMORY CYCLE +EM01074> NOP; DELAY +EM01075> MD<- AC0; FIRST WRITE +EM01076> MD<- AC1,:DREAD1; SECOND WRITE, GO TO READ + + +;DIOGNOSE INSTRUCTIONS + +;DIOG1 - 61022 +; Hamming Code_ AC2 +; rv(AC3)_ AC0; rv(AC3 xor 1)_ AC1 + +EM00122> DIOG1: MAR<- ERRCTRL; START WRITE TO ERROR CONTROL +EM01077> NOP; DELAY +EM01100> MD<- AC2,:DWRITE; WRITE HAMMING CODE, GO TO DWRITE + +;DIOG2 - 61023 +; rv(AC3)_ AC0 +; rv(AC3)_ AC0 xor AC1 + +EM00123> DIOG2: MAR<- AC3; START MEMORY CYCLE +EM01101> T<- AC0; SETUP FOR XOR +EM01102> L<- AC1 XORT; DO XOR +EM01103> MD<- AC0; FIRST WRITE +EM01104> MAR<- AC3; START MEMORY CYCLE +EM01105> AC0<- L, TASK; STORE XOR WORD +EM01106> MD<- AC0, :START; SECOND WRITE + +;INTERRUPT SYSTEM. TIMING IS 0 CYCLES IF DISABLED, 18 CYCLES +;IF THE INTERRUPTING CHANEL IS INACTIVE, AND 36+6N CYCLES TO CAUSE +;AN INTERRUPT ON CHANNEL N + +EM00567> INTCODE:PC<- L, IR<- 0; +EM01107> T<- NWW; +EM01110> T<- MD OR T; +EM01111> L<- MD AND T; +EM01112> SAD<- L, L<- T, SH=0; SAD HAD POTENTIAL INTERRUPTS +EM01113> NWW<- L, L<- 0+1, :SOMEACTIVE; NWW HAS NEW WW + +EM00537> NOACTIVE: MAR<- WWLOC; RESTORE WW TO CORE +EM01114> L<- SAD; AND REPLACE IT WITH SAD IN NWW +EM01115> MD<- NWW, TASK; +EM01116> INTZ: NWW<- L, :START; + +EM00536> SOMEACTIVE: MAR<- PCLOC; STORE PC AND SET UP TO FIND HIGHEST PRIORITY REQUEST +EM01117> XREG<- L, L<- 0; +EM01120> MD<- PC, TASK; + +EM01121> ILPA: PC<- L; +EM01122> ILP: T<- SAD; +EM01123> L<- T<- XREG AND T; +EM01124> SH=0, L<- T, T<- PC; +EM01125> :IEXIT, XREG<- L LSH 1; + +EM00571> NIEXIT: L_ 0+T+1, TASK, :ILPA; +EM00570> IEXIT: MAR<- PCLOC+T+1; FETCH NEW PC. T HAS CHANNEL #, L HAS MASK + +EM01126> XREG<- L; +EM01127> T<- XREG; +EM01130> L<- NWW XOR T; TURN OFF BIT IN WW FOR INTERRUPT ABOUT TO HAPPEN +EM01131> T<- MD; +EM01132> NWW<- L, L<- T; +EM01133> PC<- L, L<- T<- 0+1, TASK; +EM01134> SAD<- L MRSH 1, :NOACTIVE; SAD_ 1B5 TO DISABLE INTERRUPTS + +; +; ************************ +; * BIT-BLT - 61024 * +; ************************ +; Modified September 1977 to support Alternate memory banks +; Last modified Sept 6, 1977 by Dan Ingalls +; +; /* NOVA REGS +; AC2 -> BLT DESCRIPTOR TABLE, AND IS PRESERVED +; AC1 CARRIES LINE COUNT FOR RESUMING AFTER AN +; INTERRUPT. MUST BE 0 AT INITIAL CALL +; AC0 AND AC3 ARE SMASHED TO SAVE S-REGS +; +; /* ALTO REGISTER USAGE +;DISP CARRIES: TOPLD(100), SOURCEBANK(40), DESTBANK(20), +; SOURCE(14), OP(3) +$MASK1 $R0; +$YMUL $R2; HAS TO BE AN R-REG FOR SHIFTS +$RETN $R2; +$SKEW $R3; +$TEMP $R5; +$WIDTH $R7; +$PLIER $R7; HAS TO BE AN R-REG FOR SHIFTS +$DESTY $R10; +$WORD2 $R10; +$STARTBITSM1 $R35; +$SWA $R36; +$DESTX $R36; +$LREG $R40; HAS TO BE R40 (COPY OF L-REG) +$NLINES $R41; +$RAST1 $R42; +$SRCX $R43; +$SKMSK $R43; +$SRCY $R44; +$RAST2 $R44; +$CONST $R45; +$TWICE $R45; +$HCNT $R46; +$VINC $R46; +$HINC $R47; +$NWORDS $R50; +$MASK2 $R51; WAS $R46; +; +$LASTMASKP1 $500; MASKTABLE+021 +$170000 $170000; +$CALL3 $3; SUBROUTINE CALL INDICES +$CALL4 $4; +$DWAOFF $2; BLT TABLE OFFSETS +$DXOFF $4; +$DWOFF $6; +$DHOFF $7; +$SWAOFF $10; +$SXOFF $12; +$GRAYOFF $14; GRAY IN WORDS 14-17 +$LASTMASK $477; MASKTABLE+020 **NOT IN EARLIER PROMS! + + +; BITBLT SETUP - CALCULATE RAM STATE FROM AC2'S TABLE +;---------------------------------------------------------- +; +; /* FETCH COORDINATES FROM TABLE + !1,2,FDDX,BLITX; + !1,2,FDBL,BBNORAM; + !17,20,FDBX,,,,FDX,,FDW,,,,FSX,,,,,; FDBL RETURNS (BASED ON OFFSET) +; (0) 4 6 12 +EM00124> BITBLT: L<- 0; +EM01135> SINK<-LREG, BUSODD; SINK<- -1 IFF NO RAM +EM01142> L<- T<- DWOFF, :FDBL; +EM01141> BBNORAM: TASK, :NPTRAP; TRAP IF NO RAM +; +EM01166> FDW: T<- MD; PICK UP WIDTH, HEIGHT +EM01143> WIDTH<- L, L<- T, TASK, :NZWID; +EM01144> NZWID: NLINES<- L; +EM01145> T<- AC1; +EM01146> L<- NLINES-T; +EM01147> NLINES<- L, SH<0, TASK; +EM01150> :FDDX; +; +EM01136> FDDX: L<- T<- DXOFF, :FDBL; PICK UP DEST X AND Y +EM01164> FDX: T<- MD; +EM01151> DESTX<- L, L<- T, TASK; +EM01152> DESTY<- L; +; +EM01153> L<- T<- SXOFF, :FDBL; PICK UP SOURCE X AND Y +EM01172> FSX: T<- MD; +EM01154> SRCX<- L, L<- T, TASK; +EM01155> SRCY<- L, :CSHI; +; +; /* FETCH DOUBLEWORD FROM TABLE (L<- T<- OFFSET, :FDBL) +EM01140> FDBL: MAR<- AC2+T; +EM01156> SINK<- LREG, BUS; +EM01160> FDBX: L<- MD, :FDBX; +; +; /* CALCULATE SKEW AND HINC + !1,2,LTOR,RTOL; +EM01157> CSHI: T<- DESTX; +EM01161> L<- SRCX-T-1; +EM01165> T<- LREG+1, SH<0; TEST HORIZONTAL DIRECTION +EM01167> L<- 17.T, :LTOR; SKEW <- (SRCX - DESTX) MOD 16 +EM01163> RTOL: SKEW<- L, L<- 0-1, :AH, TASK; HINC <- -1 +EM00162> LTOR: SKEW<- L, L<- 0+1, :AH, TASK; HINC <- +1 +EM01170> AH: HINC<- L; +; +; CALCULATE MASK1 AND MASK2 + !1,2,IFRTOL,LNWORDS; + !1,2,POSWID,NEGWID; +EM01171> CMASKS: T<- DESTX; +EM01173> T<- 17.T; +EM01200> MAR<- LASTMASKP1-T-1; +EM01201> L<- 17-T; STARTBITS <- 16 - (DESTX.17) +EM01202> STARTBITSM1<- L; +EM01203> L<- MD, TASK; +EM01204> MASK1<- L; MASK1 <- @(MASKLOC+STARTBITS) +EM01205> L<- WIDTH-1; +EM01206> T<- LREG-1, SH<0; +EM01207> T<- DESTX+T+1, :POSWID; +EM01176> POSWID: T<- 17.T; +EM01210> MAR<- LASTMASK-T-1; +EM01211> T<- ALLONES; MASK2 <- NOT +EM01212> L<- HINC-1; +EM01213> L<- MD XOR T, SH=0, TASK; @(MASKLOC+(15-((DESTX+WIDTH-1).17))) +EM01214> MASK2<- L, :IFRTOL; +; /* IF RIGHT TO LEFT, ADD WIDTH TO X'S AND EXCH MASK1, MASK2 +EM01174> IFRTOL: T<- WIDTH-1; WIDTH-1 +EM01215> L<- SRCX+T; +EM01216> SRCX<- L; SRCX <- SCRX + (WIDTH-1) +EM01217> L<- DESTX+T; +EM01220> DESTX<- L; DESTX <- DESTX + (WIDTH-1) +EM01221> T<- DESTX; +EM01222> L<- 17.T, TASK; +EM01223> STARTBITSM1<- L; STARTBITS <- (DESTX.17) + 1 +EM01224> T<- MASK1; +EM01225> L<- MASK2; +EM01226> MASK1<- L, L<- T,TASK; EXCHANGE MASK1 AND MASK2 +EM01227> MASK2<-L; +; +; /* CALCULATE NWORDS + !1,2,LNW1,THIN; +EM01175> LNWORDS:T<- STARTBITSM1+1; +EM01232> L<- WIDTH-T-1; +EM01233> T<- 177760, SH<0; +EM01234> T<- LREG.T, :LNW1; +EM01230> LNW1: L<- CALL4; NWORDS <- (WIDTH-STARTBITS)/16 +EM01235> CYRET<- L, L<- T, :R4, TASK; CYRET<-CALL4 +; **WIDTH REG NOW FREE** +EM00604> CYX4: L<- CYCOUT, :LNW2; +EM01231> THIN: T<- MASK1; SPECIAL CASE OF THIN SLICE +EM01236> L<-MASK2.T; +EM01237> MASK1<- L, L<- 0-1; MASK1 <- MASK1.MASK2, NWORDS <- -1 +EM01240> LNW2: NWORDS<- L; LOAD NWORDS +; **STARTBITSM1 REG NOW FREE** +; +; /* DETERMINE VERTICAL DIRECTION + !1,2,BTOT,TTOB; + T_ SRCY; +EM01244> L<- DESTY-T; +EM01245> T<- NLINES-1, SH<0; +EM01246> L<- 0, :BTOT; VINC _ 0 IFF TOP-TO-BOTTOM +EM01242> BTOT: L<- ALLONES; ELSE -1 +EM01247> BTOT1: VINC<- L; +EM01250> L<- SRCY+T; GOING BOTTOM TO TOP +EM01251> SRCY<- L; ADD NLINES TO STARTING Y'S +EM01252> L<- DESTY+T; +EM01253> DESTY<- L, L<- 0+1, TASK; +EM01254> TWICE<-L, :CWA; +; +EM01243> TTOB: T<- AC1, :BTOT1; TOP TO BOT, ADD NDONE TO STARTING Y'S +; **AC1 REG NOW FREE**; +; +; /* CALCULATE WORD ADDRESSES - DO ONCE FOR SWA, THEN FOR DWAX +EM01255> CWA: L<- SRCY; Y HAS TO GO INTO AN R-REG FOR SHIFTING +EM01256> YMUL<- L; +EM01257> T<- SWAOFF; FIRST TIME IS FOR SWA, SRCX +EM01260> L<- SRCX; +; **SRCX, SRCY REG NOW FREE** +EM01261> DOSWA: MAR<- AC2+T; FETCH BITMAP ADDR AND RASTER +EM01262> XREG<- L; +EM01263> L<-CALL3; +EM01264> CYRET<- L; CYRET<-CALL3 +EM01265> L<- MD; +EM01266> T<- MD; +EM01267> DWAX<- L, L<-T, TASK; +EM01270> RAST2<- L; +EM01271> T<- 177760; +EM01272> L<- T_ XREG.T, :R4, TASK; SWA <- SWA + SRCX/16 +EM00603> CYX3: T<- CYCOUT; +EM01273> L<- DWAX+T; +EM01274> DWAX<- L; +; + !1,2,NOADD,DOADD; + !1,2,MULLP,CDELT; SWA <- SWA + SRCY*RAST1 +EM01275> L<- RAST2; +EM01302> SINK<- YMUL, BUS=0, TASK; NO MULT IF STARTING Y=0 +EM01303> PLIER<- L, :MULLP; +EM01300> MULLP: L<- PLIER, BUSODD; MULTIPLY RASTER BY Y +EM01304> PLIER<- L RSH 1, :NOADD; +EM01276> NOADD: L<- YMUL, SH=0, TASK; TEST NO MORE MULTIPLIER BITS +EM01305> SHIFTB: YMUL<- L LSH 1, :MULLP; +EM01277> DOADD: T<- YMUL; +EM01306> L<- DWAX+T; +EM01307> DWAX<- L, L<-T, :SHIFTB, TASK; +; **PLIER, YMUL REG NOW FREE** +; + !1,2,HNEG,HPOS; + !1,2,VPOS,VNEG; + !1,1,CD1; CALCULATE DELTAS = +-(NWORDS+2)[HINC] +-RASTER[VINC] +EM01301> CDELT: L<- T<- HINC-1; (NOTE T<- -2 OR 0) +EM01314> L<- T<- NWORDS-T, SH=0; (L<-NWORDS+2 OR T<-NWORDS) +EM01315> CD1: SINK<- VINC, BUSODD, :HNEG; +EM01310> HNEG: T<- RAST2, :VPOS; +EM01311> HPOS: L<- -2-T, :CD1; (MAKES L<- -(NWORDS+2)) +EM01312> VPOS: L<- LREG+T, :GDELT, TASK; BY NOW, LREG = +-(NWORDS+2) +EM01313> VNEG: L<- LREG-T, :GDELT, TASK; AND T = RASTER +EM01316> GDELT: RAST2<- L; +; +; /* END WORD ADDR LOOP + !1,2,ONEMORE,CTOPL; +EM01317> L<- TWICE-1; +EM01322> TWICE<- L, SH<0; +EM01323> L<- RAST2, :ONEMORE; USE RAST2 2ND TIME THRU +EM01320> ONEMORE: RAST1<- L; +EM01324> L<- DESTY, TASK; USE DESTY 2ND TIME THRU +EM01325> YMUL<- L; +EM01326> L<- DWAX; USE DWAX 2ND TIME THRU +EM01327> T<- DESTX; CAREFUL - DESTX=SWA!! +EM01330> SWA<- L, L<- T; USE DESTX 2ND TIME THRU +EM01331> T<- DWAOFF, :DOSWA; AND DO IT AGAIN FOR DWAX, DESTX +; **TWICE, VINC REGS NOW FREE** +; +; /* CALCULATE TOPLD + !1,2,CTOP1,CSKEW; + !1,2,HM1,H1; + !1,2,NOTOPL,TOPL; +EM01321> CTOPL: L<- SKEW, BUS=0, TASK; IF SKEW=0 THEN 0, ELSE +EM01340> CTX: IR<- 0, :CTOP1; +EM01332> CTOP1: T<- SRCX; (SKEW GR SRCX.17) XOR (HINC EQ 0) +EM01341> L<- HINC-1; +EM01342> T<- 17.T, SH=0; TEST HINC +EM01343> L<- SKEW-T-1, :HM1; +EM01335> H1: T<- HINC, SH<0; +EM01344> L<- SWA+T, :NOTOPL; +EM01334> HM1: T<- LREG; IF HINC=-1, THEN FLIP +EM01345> L<- 0-T-1, :H1; THE POLARITY OF THE TEST +EM01336> NOTOPL: SINK<- HINC, BUSODD, TASK, :CTX; HINC FORCES BUSODD +EM01337> TOPL: SWA<- L, TASK; (DISP <- 100 FOR TOPLD) +EM01346> IR<- 100, :CSKEW; +; **HINC REG NOW FREE** +; +; /* CALCULATE SKEW MASK + !1,2,THINC,BCOM1; + !1,2,COMSK,NOCOM; +EM01333> CSKEW: T<- SKEW, BUS=0; IF SKEW=0, THEN COMP +EM01347> MAR<- LASTMASKP1-T-1, :THINC; +EM01350> THINC: L<-HINC-1; +EM01354> SH=0; IF HINC=-1, THEN COMP +EM01351> BCOM1: T<- ALLONES, :COMSK; +EM01352> COMSK: L<- MD XOR T, :GFN; +EM01353> NOCOM: L<- MD, :GFN; +; +; /* GET FUNCTION +EM01355> GFN: MAR<- AC2; +EM01356> SKMSK<- L; + +EM01357> T- MD; +EM01360> L<- DISP+T, TASK; +EM01361> IR<- LREG, :BENTR; DISP _<-DISP .OR. FUNCTION + +; BITBLT WORK - VERT AND HORIZ LOOPS WITH 4 SOURCES, 4 FUNCTIONS +;----------------------------------------------------------------------- +; +; /* VERTICAL LOOP: UPDATE SWA, DWAX + !1,2,DO0,VLOOP; +VLOOP: T_ SWA; + L_ RAST1+T; INC SWA BY DELTA + SWA_ L; + T_ DWAX; + L_ RAST2+T, TASK; INC DWAX BY DELTA + DWAX_ L; +; +; /* TEST FOR DONE, OR NEED GRAY + !1,2,MOREV,DONEV; + !1,2,BMAYBE,BNOINT; + !1,2,BDOINT,BDIS0; + !1,2,DOGRAY,NOGRAY; +EM01371> BENTR: L<- T<- NLINES-1; DECR NLINES AND CHECK IF DONE +EM01402> NLINES<- L, SH<0; +EM01403> L<- NWW, BUS=0, :MOREV; CHECK FOR INTERRUPTS +EM01372> MOREV: L<- 3.T, :BMAYBE, SH<0; CHECK DISABLED ***V3 change +EM01375> BNOINT: SINK<- DISP, SINK<- lgm10, BUS=0, :BDIS0, TASK; +EM01374> BMAYBE: SINK<- DISP, SINK<- lgm10, BUS=0, :BDOINT, TASK; TEST IF NEED GRAY(FUNC=8,12) +EM01377> BDIS0: CONST<- L, :DOGRAY; ***V3 change +; +; /* INTERRUPT SUSPENSION (POSSIBLY) + !1,1,DOI1; MAY GET AN OR-1 +EM01376> BDOINT: :DOI1; TASK HERE +EM01405> DOI1: T<- AC2; +EM01404> MAR<- DHOFF+T; NLINES DONE = HT-NLINES-1 +EM01406> T<- NLINES; +EM01407> L<- PC-1; BACK UP THE PC, SO WE GET RESTARTED +EM01410> PC<- L; +EM01411> L<- MD-T-1, :BLITX, TASK; ...WITH NO LINES DONE IN AC1 +; +; /* LOAD GRAY FOR THIS LINE (IF FUNCTION NEEDS IT) + !1,2,PRELD,NOPLD; +EM01400> DOGRAY: T<- CONST-1; +EM01414> T<- GRAYOFF+T+1; +EM01415> MAR<- AC2+T; +EM01416> NOP; UGH +EM01417> L<- MD; +EM01401> NOGRAY: SINK<- DISP, SINK<- lgm100, BUS=0, TASK; TEST TOPLD +EM01420> CONST<- L, :PRELD; +; +; /* NORMAL COMPLETION +EM01177> NEGWID: L<- 0, :BLITX, TASK; +EM01373> DONEV: L<- 0, :BLITX, TASK; MAY BE AN OR-1 HERE! +EM01137> BLITX: AC1<- L, :FINBLT; +; +; /* PRELOAD OF FIRST SOURCE WORD (DEPENDING ON ALIGNMENT) + !1,2,AB1,NB1; +EM01412> PRELD: SINK<- DISP, SINK<- lgm40, BUS=0; WHICH BANK +EM01421> T<- HINC, :AB1; +NB1: MAR_ SWA-T, :XB1; (NORMAL BANK) +EM01422> AB1: XMAR<- SWA-T, :XB1; (ALTERNATE BANK) +EM01424> XB1: NOP; +EM01425> L<- MD, TASK; +EM01426> WORD2<- L, :NOPLD; +; +; +; /* HORIZONTAL LOOP - 3 CALLS FOR 1ST, MIDDLE AND LAST WORDS + !1,2,FDISPA,LASTH; + %17,17,14,DON0,,DON2,DON3; CALLERS OF HORIZ LOOP +; NOTE THIS IGNORES 14-BITS, SO lgm14 WORKS LIKE L_0 FOR RETN + !14,1,LH1; IGNORE RESULTING BUS +EM01413> NOPLD: L_ 3, :FDISP; CALL #3 IS FIRST WORD +DON3: L_ NWORDS; + HCNT_ L, SH<0; HCNT COUNTS WHOLE WORDS +DON0: L_ HCNT-1, :DO0; IF NEG, THEN NO MIDDLE OR LAST +DO0: HCNT_ L, SH<0; CALL #0 (OR-14!) IS MIDDLE WORDS +; UGLY HACK SQUEEZES 2 INSTRS OUT OF INNER LOOP: + L_ DISP, SINK_ lgm14, BUS, TASK, :FDISPA; (WORKS LIKE L_0) +LASTH: :LH1; TASK AND BUS PENDING +LH1: L_ 2, :FDISP; CALL #2 IS LAST WORD +DON2: :VLOOP; +; +; +; /* HERE ARE THE SOURCE FUNCTIONS + !17,20,,,,F0,,,,F1,,,,F2,,,,F3; IGNORE OP BITS IN FUNCTION CODE + !17,20,,,,F0A,,,,F1A,,,,F2A,,,, ; SAME FOR WINDOW RETURNS + !3,4,OP0,OP1,OP2,OP3; + !1,2,AB2,NB2; +EM01433> FDISP: SINK_ DISP, SINK_lgm14, BUS, TASK; +FDISPA: RETN_ L, :F0; +F0: SINK_ DISP, SINK_ lgm40, BUS=0, :WIND; FUNC 0 - WINDOW +F1: SINK_ DISP, SINK_ lgm40, BUS=0, :WIND; FUNC 1 - NOT WINDOW +F1A: T_ CYCOUT; + L_ ALLONES XOR T, TASK, :F3A; +F2: SINK_ DISP, SINK_ lgm40, BUS=0, :WIND; FUNC 2 - WINDOW .AND. GRAY +F2A: T_ CYCOUT; + L_ ALLONES XOR T; + SINK_ DISP, SINK_ lgm20, BUS=0; WHICH BANK + TEMP_ L, :AB2; TEMP _ NOT WINDOW +NB2: MAR_ DWAX, :XB2; (NORMAL BANK) +AB2: XMAR_ DWAX, :XB2; (ALTERNATE BANK) +XB2: L_ CONST AND T; WINDOW .AND. GRAY + T_ TEMP; + T_ MD .T; DEST.AND.NOT WINDOW + L_ LREG OR T, TASK, :F3A; (TRANSPARENT) +F3: L_ CONST, TASK, :F3A; FUNC 3 - CONSTANT (COLOR) +; +; +; /* AFTER GETTING SOURCE, START MEMORY AND DISPATCH ON OP + !1,2,AB3,NB3; +F3A: CYCOUT_ L; (TASK HERE) +F0A: SINK_ DISP, SINK_ lgm20, BUS=0; WHICH BANK + SINK_ DISP, SINK_ lgm3, BUS, :AB3; DISPATCH ON OP +NB3: T_ MAR_ DWAX, :OP0; (NORMAL BANK) +AB3: T_ XMAR_ DWAX, :OP0; (ALTERNATE BANK) +; +; +; /* HERE ARE THE OPERATIONS - ENTER WITH SOURCE IN CYCOUT + %16,17,15,STFULL,STMSK; MASKED OR FULL STORE (LOOK AT 2-BIT) +; OP 0 - SOURCE +OP0: SINK_ RETN, BUS; TEST IF UNMASKED +OP0A: L_ HINC+T, :STFULL; ELSE :STMSK +OP1: T_ CYCOUT; OP 1 - SOURCE .OR. DEST + L_ MD OR T, :OPN; +OP2: T_ CYCOUT; OP 2 - SOURCE .XOR. DEST + L_ MD XOR T, :OPN; +OP3: T_ CYCOUT; OP 3 - (NOT SOURCE) .AND. DEST + L_ 0-T-1; + T_ LREG; + L_ MD AND T, :OPN; +OPN: SINK_ DISP, SINK_ lgm20, BUS=0, TASK; WHICH BANK + CYCOUT_ L, :AB3; +; +; +; /* STORE MASKED INTO DESTINATION + !1,2,STM2,STM1; + !1,2,AB4,NB4; +STMSK: L_ MD; + SINK_ RETN, BUSODD, TASK; DETERMINE MASK FROM CALL INDEX + TEMP_ L, :STM2; STACHE DEST WORD IN TEMP +STM1: T_MASK1, :STM3; +STM2: T_MASK2, :STM3; +STM3: L_ CYCOUT AND T; ***X24. Removed TASK clause. + CYCOUT_ L, L_ 0-T-1; AND INTO SOURCE + T_ LREG; T_ MASK COMPLEMENTED + T_ TEMP .T; AND INTO DEST + L_ CYCOUT OR T; OR TOGETHER THEN GO STORE + SINK_ DISP, SINK_ lgm20, BUS=0, TASK; WHICH BANK + CYCOUT_ L, :AB4; +NB4: T_ MAR_ DWAX, :OP0A; (NORMAL BANK) +AB4: T_ XMAR_ DWAX, :OP0A; (ALTERNATE BANK) +; +; /* STORE UNMASKED FROM CYCOUT (L=NEXT DWAX) +STFULL: MD_ CYCOUT; +STFUL1: SINK_ RETN, BUS, TASK; + DWAX_ L, :DON0; +; +; +; /* WINDOW SOURCE FUNCTION +; TASKS UPON RETURN, RESULT IN CYCOUT + !1,2,DOCY,NOCY; + !17,1,WIA; + !1,2,NZSK,ZESK; + !1,2,AB5,NB5; +WIND: L_ T_ SKMSK, :AB5; ENTER HERE (8 INST TO TASK) +NB5: MAR_ SWA, :XB5; (NORMAL BANK) +AB5: XMAR_ SWA, :XB5; (ALTERNATE BANK) +XB5: L_ WORD2.T, SH=0; + CYCOUT_ L, L_ 0-T-1, :NZSK; CYCOUT_ OLD WORD .AND. MSK +ZESK: L_ MD, TASK; ZERO SKEW BYPASSES LOTS + CYCOUT_ L, :NOCY; +NZSK: T_ MD; + L_ LREG.T; + TEMP_ L, L_T, TASK; TEMP_ NEW WORD .AND. NOTMSK + WORD2_ L; + T_ TEMP; + L_ T_ CYCOUT OR T; OR THEM TOGETHER + CYCOUT_ L, L_ 0+1, SH=0; DONT CYCLE A ZERO ***X21. + SINK_ SKEW, BUS, :DOCY; +DOCY: CYRET_ L LSH 1, L_ T, :L0; CYCLE BY SKEW ***X21. +NOCY: T_ SWA, :WIA; (MAY HAVE OR-17 FROM BUS) +CYX2: T_ SWA; +WIA: L_ HINC+T; + SINK_ DISP, SINK_ lgm14, BUS, TASK; DISPATCH TO CALLER + SWA_ L, :F0A; + +; THE DISK CONTROLLER + +; ITS REGISTERS: +$DCBR $R34; +$KNMAR $R33; +$CKSUMR $R32; +$KWDCT $R31; +$KNMARW $R33; +$CKSUMRW $R32; +$KWDCTW $R31; + +; ITS TASK SPECIFIC FUNCTIONS AND BUS SOURCES: +$KSTAT $L020012,014003,124100; DF1 = 12 (LHS) BS = 3 (RHS) +$RWC $L024011,000000,000000; NDF2 = 11 +$RECNO $L024012,000000,000000; NDF2 = 12 +$INIT $L024010,000000,000000; NDF2 = 10 +$CLRSTAT $L016014,000000,000000; NDF1 = 14 +$KCOMM $L020015,000000,124000; DF1 = 15 (LHS only) Requires bus def +$SWRNRDY $L024014,000000,000000; NDF2 = 14 +$KADR $L020016,000000,124000; DF1 = 16 (LHS only) Requires bus def +$KDATA $L020017,014004,124100; DF1 = 17 (LHS) BS = 4 (RHS) +$STROBE $L016011,000000,000000; NDF1 = 11 +$NFER $L024015,000000,000000; NDF2 = 15 +$STROBON $L024016,000000,000000; NDF2 = 16 +$XFRDAT $L024013,000000,000000; NDF2 = 13 +$INCRECNO $L016013,000000,000000; NDF1 = 13 + +; THE DISK CONTROLLER COMES IN TWO PARTS. THE SECTOR +; TASK HANDLES DEVICE CONTROL AND COMMAND UNDERSTANDING +; AND STATUS REPORTING AND THE LIKE. THE WORD TASK ONLY +; RUNS AFTER BEING ENABLED BY THE SECTOR TASK AND +; ACTUALLY MOVES DATA WORDS TO AND FRO. + +; THE SECTOR TASK + +; LABEL PREDEFINITIONS: +!1,2,COMM,NOCOMM; +!1,2,COMM2,IDLE1; +!1,2,BADCOMM,COMM3; +!1,2,COMM4,ILLSEC; +!1,2,COMM5,WHYNRDY; +!1,2,STROB,CKSECT; +!1,2,STALL,CKSECT1; +!1,2,KSFINI,CKSECT2; +!1,2,IDLE2,TRANSFER; +!1,2,STALL2,GASP; +!1,2,INVERT,NOINVERT; + +SE00004> KSEC: MAR<- KBLKADR2; +SE01574> KPOQ: CLRSTAT; RESET THE STORED DISK ADDRESS +SE01575> MD<-L<-ALLONES+1, :GCOM2; ALSO CLEAR DCB POINTER + +GETCOM: MAR_KBLKADR; GET FIRST DCB POINTER +GCOM1: NOP; + L_MD; +SE01601> GCOM2: DCBR<-L,TASK; +SE01602> KCOMM<-TOWTT; IDLE ALL DATA TRANSFERS + +SE01603> MAR<-KBLKADR3; GENERATE A SECTOR INTERRUPT +SE01604> T<-NWW; +SE01605> L<-MD OR T; + +SE01606> MAR<-KBLKADR+1; STORE THE STATUS +SE01607> NWW<-L, TASK; +SE01610> MD<-KSTAT; + +SE01611> MAR<-KBLKADR; WRITE THE CURRENT DCB POINTER +SE01612> KSTAT<-5; INITIAL STATUS IS INCOMPLETE +SE01613> L<-DCBR,TASK,BUS=0; +SE01614> MD<-DCBR, :COMM; + +; BUS=0 MAPS COMM TO NOCOMM + +SE01546> COMM: T<-2; GET THE DISK COMMAND +SE01615> MAR<-DCBR+T; +SE01616> T<-TOTUWC; +SE01617> L<-MD XOR T, TASK, STROBON; +SE01620> KWDCT<-L, :COMM2; + +; STROBON MAPS COMM2 TO IDLE1 + +SE01550> COMM2: T<-10; READ NEW DISK ADDRESS +SE01621> MAR<-DCBR+T+1; +SE01622> T<-KWDCT; +SE01623> L<-ONE AND T; +SE01624> L<- -400 AND T, SH=0; +SE01625> T<-MD, SH=0, :INVERT; + +; SH=0 MAPS INVERT TO NOINVERT + +SE01572> INVERT: L<-2 XOR T, TASK, :BADCOMM; +SE01573> NOINVERT: L<-T, TASK, :BADCOMM; + +; SH=0 MAPS BADCOMM TO COMM3 + +COMM3: KNMAR_L; + +SE01626> MAR<-KBLKADR2; WRITE THE NEW DISK ADDRESS +SE01627> T<-SECT2CM; CHECK FOR SECTOR > 13 +SE01630> L<-T<-KDATA<-KNMAR+T; NEW DISK ADDRESS TO HARDWARE +SE01631> KADR<-KWDCT,ALUCY; DISK COMMAND TO HARDWARE +SE01632> L<-MD XOR T,TASK, :COMM4; COMPARE OLD AND NEW DISK ADDRESSES + +; ALUCY MAPS COMM4 TO ILLSEC + +SE01554> COMM4: CKSUMR<-L; + +SE01633> MAR<-KBLKADR2; WRITE THE NEW DISK ADDRESS +SE01634> T<-CADM,SWRNRDY; SEE IF DISK IS READY +SE01635> L<-CKSUMR AND T, :COMM5; + +; SWRNRDY MAPS COMM5 TO WHYNRDY + +COMM5: MD_KNMAR; COMPLETE THE WRITE + SH=0,TASK; + :STROB; + +; SH=0 MAPS STROB TO CKSECT + +CKSECT: T_KNMAR,NFER; + L_KSTAT XOR T, :STALL; + +; NFER MAPS STALL TO CKSECT1 + +CKSECT1: CKSUMR_L,XFRDAT; + T_CKSUMR, :KSFINI; + +; XFRDAT MAPS KSFINI TO CKSECT2 + +CKSECT2: L_SECTMSK AND T; +KSLAST: BLOCK,SH=0; +GASP: TASK, :IDLE2; + +; SH=0 MAPS IDLE2 TO TRANSFER + +TRANSFER: KCOMM_TOTUWC; TURN ON THE TRANSFER + +!1,2,ERRFND,NOERRFND; +!1,2,EF1,NEF1; + +DMPSTAT: T_COMERR1; SEE IF STATUS REPRESENTS ERROR + L_KSTAT AND T; + MAR_DCBR+1; WRITE FINAL STATUS + KWDCT_L,TASK,SH=0; + MD_KSTAT,:ERRFND; + +; SH=0 MAPS ERRFND TO NOERRFND + +NOERRFND: T_6; PICK UP NO-ERROR INTERRUPT WORD + +INTCOM: MAR_DCBR+T; + T_NWW; + L_MD OR T; + SINK_KWDCT,BUS=0,TASK; + NWW_L,:EF1; + +; BUS=0 MAPS EF1 TO NEF1 + +NEF1: MAR_DCBR,:GCOM1; FETCH ADDRESS OF NEXT CONTROL BLOCK + +ERRFND: T_7,:INTCOM; PICK UP ERROR INTERRUPT WORD + +SE01646> EF1: :KSEC; + +NOCOMM: L_ALLONES,CLRSTAT,:KSLAST; + +IDLE1: L_ALLONES,:KSLAST; + +IDLE2: KSTAT_LOW14, :GETCOM; NO ACTIVITY THIS SECTOR + +SE01552> BADCOMM: KSTAT<-7; ILLEGAL COMMAND ONLY NOTED IN KBLK STAT +SE01661> BLOCK; +SE01662> TASK,:EF1; + +WHYNRDY: NFER; +STALL: BLOCK, :STALL2; + +; NFER MAPS STALL2 TO GASP + +STALL2: TASK; + :DMPSTAT; + +ILLSEC: KSTAT_7, :STALL; ILLEGAL SECTOR SPECIFIED + +STROB: CLRSTAT; + L_ALLONES,STROBE,:CKSECT1; + +KSFINI: KSTAT_4, :STALL; COMMAND FINISHED CORRECTLY + + +;DISK WORD TASK +;WORD TASK PREDEFINITIONS +!37,37,,,,RP0,INPREF1,CKP0,WP0,,PXFLP1,RDCK0,WRT0,REC1,,REC2,REC3,,,REC0RC,REC0W,R0,,CK0,W0,,R2,,W2,,REC0,,KWD; +!1,2,RW1,RW2; +!1,2,CK1,CK2; +!1,2,CK3,CK4; +!1,2,CKERR,CK5; +!1,2,PXFLP,PXF2; +!1,2,PREFDONE,INPREF; +!1,2,,CK6; +!1,2,CKSMERR,PXFLP0; + +KWD: BLOCK,:REC0; + +; SH<0 MAPS REC0 TO REC0 +; ANYTHING=INIT MAPS REC0 TO KWD + +REC0: L_2, TASK; LENGTH OF RECORD 0 (ALLOW RELEASE IF BLOCKED) + KNMARW_L; + + T_KNMARW, BLOCK, RWC; GET ADDR OF MEMORY BLOCK TO TRANSFER + MAR_DCBR+T+1, :REC0RC; + +; WRITE MAPS REC0RC TO REC0W +; INIT MAPS REC0RC TO KWD + +REC0RC: T_MFRRDL,BLOCK, :REC12A; FIRST RECORD READ DELAY +REC0W: T_MFR0BL,BLOCK, :REC12A; FIRST RECORD 0'S BLOCK LENGTH + +REC1: L_10, INCRECNO; LENGTH OF RECORD 1 + T_4, :REC12; +REC2: L_PAGE1, INCRECNO; LENGTH OF RECORD 2 + T_5, :REC12; +REC12: MAR_DCBR+T, RWC; MEM BLK ADDR FOR RECORD + KNMARW_L, :RDCK0; + +; RWC=WRITE MAPS RDCK0 INTO WRT0 +; RWC=INIT MAPS RDCK0 INTO KWD + +RDCK0: T_MIRRDL, :REC12A; +WRT0: T_MIR0BL, :REC12A; + +REC12A: L_MD; + KWDCTW_L, L_T; +COM1: KCOMM_ STUWC, :INPREF0; + +INPREF: L_CKSUMRW+1, INIT, BLOCK; +INPREF0: CKSUMRW_L, SH<0, TASK, :INPREF1; + +; INIT MAPS INPREF1 TO KWD + +INPREF1: KDATA_0, :PREFDONE; + +; SH<0 MAPS PREFDONE TO INPREF + +PREFDONE: T_KNMARW; COMPUTE TOP OF BLOCK TO TRANSFER +KW00016> KWDX: L<-KWDCTW+T,RWC; (ALSO USED FOR RESET) +KW01742> KNMARW<-L,BLOCK,:RP0; + +; RWC=CHECK MAPS RP0 TO CKP0 +; RWC=WRITE MAPS RP0 AND CKP0 TO WP0 +; RWC=INIT MAPS RP0, CKP0, AND WP0 TO KWD + +KW01704> RP0: KCOMM<-STRCWFS,:WP1; + +KW01706> CKP0: L<-KWDCTW-1; ADJUST FINISHING CONDITION BY 1 FOR CHECKING ONLY +KW01743> KWDCTW<-L,:RP0; + +WP0: KDATA_ONE; WRITE THE SYNC PATTERN +KW01744> WP1: L_KBLKADR,TASK,:RW1; INITIALIZE THE CHECKSUM AND ENTER XFER LOOP + + +KW01745> XFLP: T<-L<-KNMARW-1; BEGINNING OF MAIN XFER LOOP +KW01746> KNMARW<-L; +KW01747> MAR<-KNMARW,RWC; +KW01750> L<-KWDCTW-T,:R0; + +; RWC=CHECK MAPS R0 TO CK0 +; RWC=WRITE MAPS R0 AND CK0 TO W0 +; RWC=INIT MAPS R0, CK0, AND W0 TO KWD + +KW01724> R0: T<-CKSUMRW,SH=0,BLOCK; +KW01751> MD<-L<-KDATA XOR T,TASK,:RW1; + +; SH=0 MAPS RW1 TO RW2 + +KW01666> RW1: CKSUMRW<-L,:XFLP; + +W0: T_CKSUMRW,BLOCK; + KDATA_L_MD XOR T,SH=0; + TASK,:RW1; + +; AS ALREADY NOTED, SH=0 MAPS RW1 TO RW2 + +CK0: T_KDATA,BLOCK,SH=0; + L_MD XOR T,BUS=0,:CK1; + +; SH=0 MAPS CK1 TO CK2 + +CK1: L_CKSUMRW XOR T,SH=0,:CK3; + +; BUS=0 MAPS CK3 TO CK4 + +CK3: TASK,:CKERR; + +; SH=0 MAPS CKERR TO CK5 + +CK5: CKSUMRW_L,:XFLP; + +CK4: MAR_KNMARW, :CK6; + +; SH=0 MAPS CK6 TO CK6 + +CK6: CKSUMRW_L,L_0+T; + MTEMP_L,TASK; + MD_MTEMP,:XFLP; + +CK2: L_CKSUMRW-T,:R2; + +; BUS=0 MAPS R2 TO R2 + +RW2: CKSUMRW_L; + + T_KDATA_CKSUMRW,RWC; THIS CODE HANDLES THE FINAL CHECKSUM + L_KDATA-T,BLOCK,:R2; + +; RWC=CHECK NEVER GETS HERE +; RWC=WRITE MAPS R2 TO W2 +; RWC=INIT MAPS R2 AND W2 TO KWD + +R2: L_MRPAL, SH=0; SET READ POSTAMBLE LENGTH, CHECK CKSUM + KCOMM_TOTUWC, :CKSMERR; + +; SH=0 MAPS CKSMERR TO PXFLP0 + +W2: L_MWPAL, TASK; SET WRITE POSTAMBLE LENGTH + CKSUMRW_L, :PXFLP; + +CKSMERR: KSTAT_0,:PXFLP0; 0 MEANS CHECKSUM ERROR .. CONTINUE + +PXFLP: L_CKSUMRW+1, INIT, BLOCK; +PXFLP0: CKSUMRW_L, TASK, SH=0, :PXFLP1; + +; INIT MAPS PXFLP1 TO KWD + +PXFLP1: KDATA_0,:PXFLP; + +; SH=0 MAPS PXFLP TO PXF2 + +PXF2: RECNO, BLOCK; DISPATCH BASED ON RECORD NUMBER + :REC1; + +; RECNO=2 MAPS REC1 INTO REC2 +; RECNO=3 MAPS REC1 INTO REC3 +; RECNO=INIT MAPS REC1 INTO KWD + +REC3: KSTAT_4,:PXFLP; 4 MEANS SUCCESS!!! + +CKERR: KCOMM_TOTUWC; TURN OFF DATA TRANSFER + L_KSTAT_6, :PXFLP1; SHOW CHECK ERROR AND LOOP + +;The Parity Error Task +;Its label predefinition is way earlier +;It dumps the following interesting registers: +;614/ DCBR Disk control block +;615/ KNMAR Disk memory address +;616/ DWA Display memory address +;617/ CBA Display control block +;620/ PC Emulator program counter +;621/ SAD Emulator temporary register for indirection + +PA00015> PART: T<- 10; +PA01765> L<- ALLONES; TURN OFF MEMORY INTERRUPTS +PA01766> MAR<- ERRCTRL, :PX1; +PA00450> PR8: L<- SAD, :PX; +PA00447> PR7: L<- PC, :PX; +PA00446> PR6: L<- CBA, :PX; +PA00445> PR5: L<- DWA, :PX; +PA00444> PR4: L<- KNMAR, :PX; +PA00443> PR3: L<- DCBR, :PX; +PA00442> PR2: L<- NWW OR T, TASK; T CONTAINS 1 AT THIS POINT +PA00440> PR0: NWW<- L, :PART; + +PA01767> PX: MAR<- 612+T; +PA01770> PX1: MTEMP<- L, L<- T; +PA01771> MD<- MTEMP; +PA01772> CURDATA<- L; THIS CLOBBERS THE CURSOR FOR ONE +PA01773> T<- CURDATA-1, BUS; FRAME WHEN AN ERROR OCCURS +PA01774> :PR0; + +AltoIIMRT4K.mu: +; +; last modified December 1, 1977 1:14 AM +; +; This is the part of the Memory Refresh Task which +; is specific to Alto IIs WITHOUT Extended memory. +; +; Copyright Xerox Corporation 1979 +$EngNumber $20000; ALTO 2 WITHOUT EXTENDED MEMORY + +MRT: SINK_ MOUSE, BUS; MOUSE DATA IS ANDED WITH 17B +MRTA: L_ T_ -2, :TX0; DISPATCH ON MOUSE CHANGE +TX0: L_ T_ R37 AND NOT T; UPDATE REFRESH ADDRESS + T_ 3+T+1, SH=0; + L_ REFIIMSK ANDT, :DOTIMER; +NOTIMER:R37_ L; STORE UPDATED REFRESH ADDRESS +TIMERTN:L_ REFZERO AND T; + SH=0; TEST FOR CLOCK TICK + :NOCLK; +NOCLK: MAR_ R37; FIRST FEFRESH CYCLE + L_ CURX; + T_ 2, SH=0; + MAR_ R37 XORT, :DOCUR; SECOND REFRESH CYCLE +NOCUR: CURDATA_ L, TASK; +MRTLAST:CURDATA_ L, :MRT; + +DOTIMER:R37_ L; SAVE REFRESH ADDRESS + MAR_EIALOC; INTERVAL TIMER/EIA INTERFACE + L_2 AND T; + SH=0, L_T_REFZERO.T; ***V3 CHANGE (USED TO BE BIAS) + CURDATA_L, :SPCHK; CURDATA_CURRENT TIME WITHOUT CONTROL BITS + +SPCHK: SINK_MD, BUS=0, TASK; CHECK FOR EIA LINE SPACING +SPIA: :NOTIMERINT, CLOCKTEMP_L; + +NOSPCHK:L_MD; CHECK FOR TIME=NOW + MAR_TRAPDISP-1; CONTAINS TIME AT WHICH INTERRUPT SHOULD HAPPEN + MTEMP_L; IF INTERRUPT IS CAUSED, + L_ MD-T; LINE STATE WILL BE STORED + SH=0, TASK, L_MTEMP, :SPIA; + +TIMERINT:MAR_ ITQUAN; STORE THE THING IN CLOCKTEMP AT ITQUAN + L_ CURDATA; + R37_ L; + T_NWW; AND CAUSE AN INTERRUPT ON THE CHANNELS + MD_CLOCKTEMP; SPECIFIED BY ITQUAN+1 + L_MD OR T, TASK; + NWW_L; + +NOTIMERINT: T_R37, :TIMERTN; + +;The rest of MRT, starting at the label CLOCK is unchanged + +AltoIIMRT16K.mu: +; +; last modified December 1, 1977 1:13 AM +; +; This is the part of the Memory Refresh Task which +; is specific to Alto IIs with Extended memory. +; +; Copyright Xerox Corporation 1979 +$EngNumber $30000; ALTO II WITH EXTENDED MEMORY +; +; This version assumes MRTACT is cleared by BLOCK, not MAR_ R37 +; R37 [4-13] are the low bits of the TOD clock +; R37 [8-14] are the refresh address bits +; Each time MRT runs, four refresh addresses are generated, though +; R37 is incremented only once. Sprinkled throughout the execution +; of this code are the following operations having to do with refresh: +; MAR_ R37 +; R37_ R37 +4 NOTE THAT R37 [14] DOES NOT CHANGE +; MAR_ R37 XOR 2 TOGGLES BIT 14 +; MAR_ R37 XOR 200 TOGGLES BIT 8 +; MAR_ R37 XOR 202 TOGGLES BITS 8 AND 14 + +MRT: MAR_ R37; **FIRST REFRESH CYCLE** + SINK_ MOUSE, BUS; MOUSE DATA IS ANDED WITH 17B +MRTA: L_ T_ -2, :TX0; DISPATCH ON MOUSE CHANGE +TX0: L_ R37 AND NOT T, T_ R37;INCREMENT CLOCK + T_ 3+T+1, SH=0; IE. T_ T +4. IS INTV TIMER ON? + L_ REFIIMSK AND T, :DOTIMER; [DOTIMER,NOTIMER] ZERO HIGH 4 BITS +NOTIMER: R37_ L; STORE UPDATED CLOCK +NOTIMERINT: T_ 2; NO STATE AT THIS POINT IN PUBLIC REGS + MAR_ R37 XOR T,T_ R37; **SECOND REFRESH CYCLE** + L_ REFZERO AND T; ONLY THE CLOKCK BITS, PLEASE + SH=0, TASK; TEST FOR CLOCK OVERFLOW + :NOCLK; [NOCLK,CLOCK] +NOCLK: T _ 200; + MAR_ R37 XOR T; **THIRD FEFRESH CYCLE** + L_ CURX, BLOCK; CLEARS WAKEUP REQUEST FF + T_ 2 OR T, SH=0; NEED TO CHECK CURSOR? + MAR_ R37 XOR T, :DOCUR; **FOURTH REFRESH CYCLE** +NOCUR: CURDATA_ L, TASK; +MRTLAST:CURDATA_ L, :MRT; END OF MAIN LOOP + +DOTIMER:R37_ L; STORE UPDATED CLOCK + MAR_ EIALOC; INTERVAL TIMER/EIA INTERFACE + L_ 2 AND T; + SH=0, L_ T_ REFZERO.T; ***V3 CHANGE (USED TO BE BIAS) + CURDATA_L, :SPCHK; CURDATA_ CURRENT TIME WITHOUT CONTROL BITS + +SPCHK: SINK_ MD, BUS=0, TASK; CHECK FOR EIA LINE SPACING +SPIA: :NOTIMERINT, CLOCKTEMP_ L; + +NOSPCHK:L_MD; CHECK FOR TIME = NOW + MAR_TRAPDISP-1; CONTAINS TIME AT WHICH INTERRUPT SHOULD HAPPEN + MTEMP_L; IF INTERRUPT IS CAUSED, + L_ MD-T; LINE STATE WILL BE STORED + SH=0, TASK, L_MTEMP, :SPIA; + +TIMERINT:MAR_ ITQUAN; STORE THE THING IN CLOCKTEMP AT ITQUAN + L_ CURDATA; + R37_ L; + T_NWW; AND CAUSE AN INTERRUPT ON THE CHANNELS + MD_CLOCKTEMP; SPECIFIED BY ITQUAN+1 + L_MD OR T, TASK; + NWW_L,:NOTIMERINT; + +;The rest of MRT, starting at the label CLOCK is unchanged + diff --git a/Contralto/Disassembly/altocode24.mu b/Contralto/Disassembly/altocode24.mu new file mode 100644 index 0000000000000000000000000000000000000000..5802b67be26330ddad45ea5ec40b01a374c1901e GIT binary patch literal 53323 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