protected override void ExecuteSpecialFunction2(MicroInstruction instruction) { EmulatorF2 ef2 = (EmulatorF2)instruction.F2; switch (ef2) { case EmulatorF2.LoadIR: // Load IR from the bus _cpu._ir = _busData; // "IR<- also merges bus bits 0, 5, 6 and 7 into NEXT[6-9] which does a first level // instruction dispatch." _nextModifier |= (ushort)(((_busData & 0x8000) >> 12) | ((_busData & 0x0700) >> 8)); // "IR<- clears SKIP" _skip = 0; break; 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 ; dispatch selects register // 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 the msb...) // // NOTE: The above table is accurate and functions correctly; using the PROM is faster. // if ((_cpu._ir & 0x8000) != 0) { _nextModifier |= (ushort)(3 - ((_cpu._ir & 0xc0) >> 6)); } else { _nextModifier |= ControlROM.ACSourceROM[((_cpu._ir & 0x7f00) >> 8) + 0x80]; } break; 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 // // NOTE: The above table is accurate and functions correctly; using the PROM is faster. // if ((_cpu._ir & 0x8000) != 0) { // 3-IR[8-9] (shift field of arithmetic instruction) _nextModifier |= (ushort)(3 - ((_cpu._ir & 0xc0) >> 6)); } else { // Use the PROM. _nextModifier |= ControlROM.ACSourceROM[((_cpu._ir & 0x7f00) >> 8)]; } break; case EmulatorF2.ACDEST: // Handled in early handler, nothing to do here. break; case EmulatorF2.BUSODD: // "...merges BUS[15] into NEXT[9]." _nextModifier |= (ushort)(_busData & 0x1); break; case EmulatorF2.MAGIC: Shifter.SetModifier(ShifterModifier.Magic); break; case EmulatorF2.LoadDNS: // DNS<- does the following: // - modifies the normal shift operations to perform Nova-style shifts (done here) // - addresses R from 3-IR[3-4] (destination AC) (see Early LoadDNS handler) // - stores into R unless IR[12] is set (done here) // [NOTE: This overrides a LoadR BS field if present -- that is, if IR[12] is set and // BS=LoadR, no load into R will take place. Note also that the standard // microcode apparently always specifies a LoadR BS for LoadDNS microinstructions. Need to // look at the schematics more closely to see if this is required or just a convention // of the PARC microassembler.] // - calculates Nova-style CARRY bit (done here) // - sets the SKIP and CARRY flip-flops appropriately (see Late LoadDNS handler) int carry = 0; // Also indicates modifying CARRY _loadR = (_cpu._ir & 0x0008) == 0; // At this point the ALU has already done its operation but the shifter has not yet run. // We need to set the CARRY bit that will be passed through the shifter appropriately. // Select carry input value based on carry control switch ((_cpu._ir & 0x30) >> 4) { case 0x0: // Nothing; CARRY unaffected. carry = _carry; break; case 0x1: carry = 0; // Z break; case 0x2: carry = 1; // O break; case 0x3: carry = (~_carry) & 0x1; // C break; } // Now modify the result based on the current ALU result switch ((_cpu._ir & 0x700) >> 8) { case 0x0: case 0x2: case 0x7: // COM, MOV, AND - Carry unaffected break; case 0x1: case 0x3: case 0x4: case 0x5: case 0x6: // NEG, INC, ADC, SUB, ADD - invert the carry bit if (_cpu._aluC0 != 0) { carry = (~carry) & 0x1; } break; } // Tell the Shifter to do a Nova-style shift with the // given carry bit. Shifter.SetModifier(ShifterModifier.DNS); Shifter.DNSCarry = carry; break; default: throw new InvalidOperationException(String.Format("Unhandled emulator F2 {0}.", ef2)); } }
/// <summary> /// ExecuteInstruction causes the Task to execute the next instruction (the one /// _mpc is pointing to). The base implementation covers non-task specific logic, /// subclasses (specific task implementations) may provide their own implementations. /// </summary> /// <returns>An InstructionCompletion indicating whether this instruction calls for a task switch or not.</returns> protected virtual InstructionCompletion ExecuteInstruction(MicroInstruction instruction) { InstructionCompletion completion = InstructionCompletion.Normal; bool swMode = false; ushort aluData; ushort nextModifier; bool softReset = _softReset; _loadR = false; _loadS = false; _rSelect = 0; _busData = 0; _softReset = false; Shifter.Reset(); // // Wait for memory state machine if a memory operation is requested by this instruction and // the memory isn't ready yet. // if (instruction.MemoryAccess) { if (!_cpu._system.MemoryBus.Ready(instruction.MemoryOperation)) { // Suspend operation for this cycle. return(InstructionCompletion.MemoryWait); } } // If we have a modified next field from the last instruction, make sure it gets applied to this one. nextModifier = _nextModifier; _nextModifier = 0; _rSelect = instruction.RSELECT; // Give tasks the chance to modify parameters early on (like RSELECT) ExecuteSpecialFunction2Early(instruction); // Select BUS data. if (!instruction.ConstantAccess) { // Normal BUS data (not constant ROM access). switch (instruction.BS) { case BusSource.ReadR: _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" _loadR = true; break; case BusSource.None: _busData = 0xffff; // "Enables no source to the BUS, leaving it all ones" break; case BusSource.TaskSpecific1: case BusSource.TaskSpecific2: _busData = GetBusSource(instruction); // task specific -- call into specific implementation break; case BusSource.ReadMD: _busData = _cpu._system.MemoryBus.ReadMD(); break; case BusSource.ReadMouse: // "BUS[12-15]<-MOUSE; BUS[0-13]<- -1" // (Note -- BUS[0-13] appears to be a typo, and should likely be BUS[0-11]). _busData = (ushort)(_cpu._system.MouseAndKeyset.PollMouseBits() | 0xfff0); break; case BusSource.ReadDisp: // "The high-order bits of IR cannot be read directly, but the displacement field of IR (8 low order bits), // may be read with the <-DISP bus source. If the X field of the instruction is zero (i.e. it specifies page 0 // addressing) then the DISP field of the instruction is put on BUS[8-15] and BUS[0-7] is zeroed. If the X // field of the instruction is nonzero (i.e. it specifies PC-relative or base-register addressing) then the DISP // field is sign-extended and put on the bus." // NB: the "X" field of the NOVA instruction is IR[6-7] _busData = (ushort)(_cpu._ir & 0xff); if ((_cpu._ir & 0x300) != 0) { // sign extend if necessary if ((_cpu._ir & 0x80) != 0) { _busData |= (0xff00); } } break; default: throw new InvalidOperationException(String.Format("Unhandled bus source {0}.", instruction.BS)); } } else { // See also comments below. _busData = instruction.ConstantValue; } // Constant ROM access: // "The constant memory is gated to the bus by F1=7, F2=7, or BS>4. The constant memory is addressed by the // (8 bit) concatenation of RSELECT and BS. The intent in enabling constants with BS>4 is to provide a masking // 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." // This is precached by the MicroInstruction object. if (instruction.BS4) { _busData &= instruction.ConstantValue; } // // Let F2s that need to modify bus data before the ALU runs do their thing. // (This is used by the Trident KDTA special functions) // ExecuteSpecialFunction2PostBusSource(instruction); // // If there was a RDRAM operation last cycle, we AND in the uCode RAM data here. // if (_rdRam) { _busData &= UCodeMemory.ReadRAM(); _rdRam = false; } // // Let F1s that need to modify bus data before the ALU runs do their thing // (this is used by the emulator RSNF and Ethernet EILFCT) // ExecuteSpecialFunction1Early(instruction); // Do ALU operation. // Small optimization: if we're just taking bus data across the ALU, we // won't go through the ALU.Execute call; this is a decent performance gain for a bit // more ugly code... if (instruction.ALUF != AluFunction.Bus) { aluData = ALU.Execute(instruction.ALUF, _busData, _cpu._t, _skip); } else { aluData = _busData; ALU.Carry = 0; } // // If there was a WRTRAM operation last cycle, we write the uCode RAM here // using the results of this instruction's ALU operation and the M register // from the last instruction. // if (_wrtRam) { UCodeMemory.WriteRAM(aluData, _cpu._m); _wrtRam = false; } // // If there was an SWMODE operation last cycle, we set the flag to ensure it // takes effect at the end of this cycle. // if (_swMode) { _swMode = false; swMode = true; } // // Do Special Functions // switch (instruction.F1) { case SpecialFunction1.None: // Do nothing. Well, that was easy. break; case SpecialFunction1.LoadMAR: // Do MAR or XMAR reference based on whether F2 is MD<- (for Alto IIs), indicating an extended memory reference. _cpu._system.MemoryBus.LoadMAR( aluData, _taskType, _systemType == SystemType.AltoI ? false : instruction.F2 == SpecialFunction2.StoreMD); break; case SpecialFunction1.Task: // // If the first uOp executed after a task switch contains a TASK F1, it does not take effect. // This is observed on the real hardware, and does not appear to be documented. // It also doesn't appear to affect the execution of the standard Alto uCode in any significant // way, but is included here for correctness. // if (!_firstInstructionAfterSwitch) { // Yield to other more important tasks completion = InstructionCompletion.TaskSwitch; } break; case SpecialFunction1.Block: // Technically this is to be invoked by the hardware device associated with a task. // That logic would be circuituous and unless there's a good reason not to that is discovered // later, I'm just going to directly block the current task here. _cpu.BlockTask(this._taskType); break; case SpecialFunction1.LLSH1: Shifter.SetOperation(ShifterOp.ShiftLeft); break; case SpecialFunction1.LRSH1: Shifter.SetOperation(ShifterOp.ShiftRight); break; case SpecialFunction1.LLCY8: Shifter.SetOperation(ShifterOp.RotateLeft); 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. ExecuteSpecialFunction1(instruction); break; } switch (instruction.F2) { case SpecialFunction2.None: // Nothing! break; case SpecialFunction2.BusEq0: if (_busData == 0) { _nextModifier |= 1; } break; case SpecialFunction2.ShLt0: // Handled below, after the Shifter runs break; case SpecialFunction2.ShEq0: // Same as above. break; case SpecialFunction2.Bus: // Select bits 6-15 (bits 0-9 in modern parlance) of the bus _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; break; case SpecialFunction2.StoreMD: // Special case for XMAR on non-Alto I machines: if F1 is a LoadMAR we do nothing here; // the handler for LoadMAR will load the correct bank. if (_systemType == SystemType.AltoI) { _cpu._system.MemoryBus.LoadMD(_busData); } else if (instruction.F1 != SpecialFunction1.LoadMAR) { _cpu._system.MemoryBus.LoadMD(_busData); } 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. ExecuteSpecialFunction2(instruction); break; } // // Do the shifter operation if we're doing an operation that requires the shifter output (loading R, doing a LoadDNS, // modifying NEXT based on the shifter output.) // if (_loadR || instruction.NeedShifterOutput) { // A crude optimization: if there's no shifter operation, // we bypass the call to DoOperation and stuff L in Shifter.Output ourselves. if (Shifter.Op == ShifterOp.None) { Shifter.Output = _cpu._l; } else { Shifter.DoOperation(_cpu._l, _cpu._t); } } // // Handle NEXT modifiers that rely on the Shifter output. // switch (instruction.F2) { case SpecialFunction2.ShLt0: // // Note: // "the value of SHIFTER OUTPUT is determined by the value of L as the microinstruction // *begins* execution and the shifter function specified during the *current* microinstruction. // // Since we haven't modifed L yet, and we've calculated the shifter output above, we're good to go here. // if ((short)Shifter.Output < 0) { _nextModifier |= 1; } break; case SpecialFunction2.ShEq0: // See note above. if (Shifter.Output == 0) { _nextModifier |= 1; } break; } // // Write back to registers: // // Do writeback to selected R register from shifter output. // if (_loadR) { _cpu._r[_rSelect] = Shifter.Output; } // Do writeback to selected S register from M if (_loadS) { _cpu._s[_rb][instruction.RSELECT] = _cpu._m; } // Load T if (instruction.LoadT) { // Does this operation change the source for T? _cpu._t = instruction.LoadTFromALU ? aluData : _busData; // // Control RAM: "...the control RAM address is specified by the control RAM // address register... which is loaded from the ALU output whenver T is loaded // from its source." // UCodeMemory.LoadControlRAMAddress(aluData); } // Load L (and M) from ALU outputs. if (instruction.LoadL) { _cpu._l = aluData; // Only RAM-related tasks can modify M. if (_ramTask) { _cpu._m = aluData; } // Save ALUC0 for use in the next ALUCY special function. _cpu._aluC0 = (ushort)ALU.Carry; } // // Execute special functions that happen late in the cycle // ExecuteSpecialFunction2Late(instruction); // // Switch banks if the last instruction had an SWMODE F1; // this depends on the value of the NEXT field in this instruction. // (And apparently the modifier applied to NEXT in this instruction -- MADTEST expects this.) // if (swMode) { //Log.Write(LogType.Verbose, LogComponent.Microcode, "SWMODE: uPC {0}, next uPC {1} (NEXT is {2})", Conversion.ToOctal(_mpc), Conversion.ToOctal(instruction.NEXT | nextModifier), Conversion.ToOctal(instruction.NEXT)); UCodeMemory.SwitchMode((ushort)(instruction.NEXT | nextModifier), _taskType); } // // Do task-specific BLOCK behavior if the last instruction had a BLOCK F1. // if (instruction.F1 == SpecialFunction1.Block) { ExecuteBlock(); } // // Select next address, using the address modifier from the last instruction. // (Unless a soft reset occurred during this instruction) // if (!softReset) { _mpc = (ushort)(instruction.NEXT | nextModifier); } else { _cpu.SoftReset(); } _firstInstructionAfterSwitch = false; return(completion); }