Example #1
0
            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));
                }
            }
Example #2
0
            /// <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);
            }