/// <summary> /// "Silent Boot": (See Section 9.2.2) /// Used when a BOOT STARTF is invoked; resets task MPCs and sets /// the starting bank (RAM0 or ROM0) appropriately based on the contents /// of RMR. /// All other register contents are left as-is. /// </summary> public void SoftReset() { // Soft-Reset tasks. for (int i = 0; i < _tasks.Length; i++) { if (_tasks[i] != null) { _tasks[i].SoftReset(); } } Log.Write(LogComponent.CPU, "Silent Boot; microcode banks initialized to {0}", Conversion.ToOctal(_rmr)); UCodeMemory.LoadBanksFromRMR(_rmr); // Reset RMR after reset. _rmr = 0x0; // Start in Emulator _currentTask = _tasks[0]; // // TODO: // This is a hack of sorts, it ensures that the sector task initializes // itself as soon as the Emulator task yields after the reset. (CopyDisk is broken otherwise due to the // sector task stomping over the KBLK CopyDisk sets up after the reset. This is a race of sorts.) // Unsure if there is a deeper issue here or if there are other reset semantics // in play that are not understood. // WakeupTask(CPU.TaskType.DiskSector); }
/// <summary> /// Executes a single microinstruction. /// </summary> /// <returns>An InstructionCompletion indicating whether this instruction calls for a task switch or not.</returns> public InstructionCompletion ExecuteNext() { MicroInstruction instruction = UCodeMemory.GetInstruction(_mpc, _taskType); /* * if (_taskType == TaskType.Emulator && UCodeMemory.GetBank(_taskType) == MicrocodeBank.RAM0) * { * Console.WriteLine("{0}: {1}", Conversion.ToOctal(_mpc), UCodeDisassembler.DisassembleInstruction(instruction, _taskType)); * }*/ return(ExecuteInstruction(instruction)); }
/// <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); }
/// <summary> /// Executes a single microinstruction. /// </summary> /// <returns>An InstructionCompletion indicating whether this instruction calls for a task switch or not.</returns> public InstructionCompletion ExecuteNext() { MicroInstruction instruction = UCodeMemory.GetInstruction(_mpc, _taskType); return(ExecuteInstruction(instruction)); }