Exemple #1
0
 public ExecutionResult(InstructionPackage package, Flags flags, bool skipInterruptsAfterExecution = false)
 {
     InstructionAddress = package.InstructionAddress;
     Instruction        = package.Instruction;
     Data  = package.Data;
     Flags = flags;
     SkipInterruptAfterExecution = skipInterruptsAfterExecution;
 }
Exemple #2
0
        /// <summary>
        /// 获取下一条指令
        /// </summary>
        /// <returns>返回下一条指令</returns>
        public InstructionPackage GetNext()
        {
            InstructionPackage result = null;

            if (null != Now.Next)
            {
                result = Now.Next.GetInstruction();
                Now    = Now.Next;
                PC++;
            }
            return(result);
        }
Exemple #3
0
        private void InstructionCycle()
        {
            _clock = new Stopwatch();
            _clock.Start();

            while (_running)
            {
                InstructionPackage package = null;
                ushort             pc      = Registers.PC;

                if (!_halted)
                {
                    // decode next instruction
                    // (note that the decode cycle leaves the Program Counter at the byte *after* this instruction unless it's adjusted by the instruction code itself,
                    // this is how the program moves on to the next instruction)
                    package = DecodeInstructionAtProgramCounter();
                    if (package == null)
                    {
                        // only happens if we reach the end of memory mid-instruction, if so we bail out
                        Stop();
                        return;
                    }
                }
                else
                {
                    if (EndOnHalt)
                    {
                        Stop();
                        return;
                    }

                    // run a NOP - by not decoding anything we leave the Program Counter where it was when we HALTed and
                    // the PC will be advanced on resume from the HALT instruction when the next interrupt is handled

                    // we need to run the opcode fetch cycle for NOP anyway, so that the clock ticks and attached devices
                    // can generate interrupts to bring the CPU out of HALT; the following call does this...
                    FetchOpcodeByte();
                    package = new InstructionPackage(InstructionSet.NOP, new InstructionData(), Registers.PC);
                }

                // run the decoded instruction and deal with timing / ticks
                ExecutionResult result = Execute(package);
                _executingInstructionPackage = null;

                WaitForNextClockTick();

                HandleNonMaskableInterrupts();                                           // NMI has priority
                HandleMaskableInterrupts(result);                                        // if we came back from an NMI then we don't handle other interrupts until the next cycle

                Registers.R = (byte)(((Registers.R + 1) & 0x7F) | (Registers.R & 0x80)); // bits 0-6 of R are incremented as part of the memory refresh - bit 7 is preserved
            }
        }
Exemple #4
0
        private InstructionPackage DecodeIM0Interrupt()
        {
            // In IM0, when an interrupt is generated, the CPU will ask the device
            // to supply four bytes one at a time via a callback method, which are then decoded into an instruction to be executed.

            // To emulate this without heavily re-writing the decode loop (which expects the instruction bytes to be
            // in memory at the address pointed to by the program counter), we will temporarily copy
            // the last 4 bytes of RAM to an array, move the program counter and use those 4 bytes for the interrupt decode,
            // then restore them and fix up the program counter. Sneaky, eh?

            ushort address = (ushort)(Memory.SizeInBytes - 5);

            byte[] lastFour = Memory.Untimed.ReadBytesAt(address, 4);
            ushort pc       = Registers.PC;

            Registers.PC = address;

            _suspendMachineCycles = true;

            for (int i = 0; i < 4; i++)
            {
                // The callback will be called 4 times; it should return the opcode bytes of the instruction to run in sequence.
                // If there are fewer than 4 bytes in the opcode, return 0x00 for the 'extra' bytes
                byte value = _interruptCallback();
                IO.SetDataBusValue(value);
                Memory.Untimed.WriteByteAt((ushort)(address + i), value);
            }

            InstructionPackage package = DecodeInstructionAtProgramCounter();

            Registers.PC = pc;
            Memory.Untimed.WriteBytesAt(address, lastFour);

            _suspendMachineCycles = false;

            return(package);
        }
Exemple #5
0
        private void HandleMaskableInterrupts(ExecutionResult result)
        {
            if (!result.SkipInterruptAfterExecution && IO.INT && InterruptsEnabled)
            {
                if (_interruptCallback == null && InterruptMode == InterruptMode.IM0)
                {
                    throw new InterruptException("Interrupt mode is IM0 which requires a callback for reading data from the interrupting device. Callback was null.");
                }

                if (_halted)
                {
                    Resume();
                }

                switch (InterruptMode)
                {
                case InterruptMode.IM0:

                    // In this mode, we read up to 4 bytes that form an instruction which is then executed. Usually, this is an RST but it can in theory be
                    // any Z80 instruction. The instruction bytes are read from the data bus, whose value is set by a hardware device during 4 clock cycles.

                    // To emulate a device functioning via IM0, your device code must call RaiseInterrupt on the CPU when it is ready to interrupt the system, supplying a
                    // Func<byte> that returns the opcode bytes of the instruction to run one by one as it is called 4 times (it will *always* be called 4 times). Trailing
                    // 0x00s will be ignored, but you must return 4 bytes even if the instruction is shorter than that. See DecodeIM0Interrupt below for details of how this works.

                    // The decoded instruction is executed in the current execution context with registers and program counter where they were when the interrupt was triggered.
                    // The program counter is pushed on the stack before executing the instruction and restored afterwards (but *not* popped), so instructions like JR, JP and CALL will have no effect.

                    // NOTE: I have not been able to test this mode extensively based on real-world examples. It may well be buggy compared to the real Z80.
                    // TODO: verify the behaviour of the real Z80 and fix the code!

                    Timing.BeginInterruptRequestAcknowledgeCycle(InstructionTiming.IM0_INTERRUPT_ACKNOWLEDGE_TSTATES);
                    InstructionPackage package = DecodeIM0Interrupt();
                    ushort             pc      = Registers.PC;
                    Push(WordRegister.PC);
                    Execute(package);
                    Registers.PC = pc;
                    Registers.WZ = Registers.PC;
                    break;

                case InterruptMode.IM1:

                    // This mode is simple. When IM1 is set (this is the default mode) then a jump to 0x0038 is performed when
                    // the interrupt occurs.

                    Timing.BeginInterruptRequestAcknowledgeCycle(InstructionTiming.IM1_INTERRUPT_ACKNOWLEDGE_TSTATES);
                    Push(WordRegister.PC);
                    Registers.PC = 0x0038;
                    Registers.WZ = Registers.PC;
                    break;

                case InterruptMode.IM2:

                    // In this mode, we synthesize an address from the contents of register I as the high byte and the
                    // value on the data bus as the low byte. This address is a pointer into a table in RAM containing the *actual* interrupt
                    // routine addresses. We read the word at the address we calculated, and then jump to *that* address.

                    // When emulating a hardware device that uses IM2 to jump into its service routine/s, you must trigger the interrupt
                    // by calling RaiseInterrupt and supplying a Func<byte> that returns the data bus value to use when called.
                    // If the callback is null then the data bus value is assumed to be 0.

                    // It's actually quite common on some platforms (eg ZX Spectrum) to use IM2 this way to call a routine that needs to be synchronised
                    // with the hardware (on the Spectrum, an interrupt is raised by the system after each display refresh, and setting IM2 allows
                    // the programmer to divert that interrupt to a routine of their choice and then call down to the ROM routine [which handles the
                    // keyboard, sound etc] afterwards).

                    IO.SetDataBusValue(_interruptCallback?.Invoke() ?? 0);
                    Timing.BeginInterruptRequestAcknowledgeCycle(InstructionTiming.IM2_INTERRUPT_ACKNOWLEDGE_TSTATES);
                    Push(WordRegister.PC);
                    ushort address = (IO.DATA_BUS, Registers.I).ToWord();
                    Registers.PC = Memory.Timed.ReadWordAt(address);
                    Registers.WZ = Registers.PC;
                    break;
                }

                _interruptCallback = null;
                Timing.EndInterruptRequestAcknowledgeCycle();
            }
        }
Exemple #6
0
        private ExecutionResult Execute(InstructionPackage package)
        {
            BeforeExecute?.Invoke(this, package);
            _executingInstructionPackage = package;

            // check for breakpoints
            if (_breakpoints.Contains(package.InstructionAddress))
            {
                _onBreakpoint?.Invoke(this, package);
            }

            // set the internal WZ register to an initial value based on whether this is an indexed instruction or not;
            // the instruction that runs may alter/set WZ itself.
            // (the value in WZ [sometimes known as MEMPTR in Z80 enthusiast circles] is only ever used to control the behavior of the BIT instruction)
            ushort wz = package.Instruction switch
            {
                var i when i.Source.IsAddressFromIndexAndOffset() => Registers[i.Source.AsWordRegister()],
                var i when i.Target.IsAddressFromIndexAndOffset() => Registers[i.Target.AsWordRegister()],
                   _ => 0
            };

            wz           = (ushort)(wz + package.Data.Argument1);
            Registers.WZ = wz;

            ExecutionResult result = package.Instruction.Microcode.Execute(this, package);

            if (result.Flags != null)
            {
                Registers.F = result.Flags.Value;
            }
            result.WaitStatesAdded = _previousWaitCycles;
            AfterExecute?.Invoke(this, result);

            return(result);
        }

        // execute an instruction directly (without the processor loop running), for example for directly testing instructions
        ExecutionResult IDebugProcessor.ExecuteDirect(byte[] opcode)
        {
            Memory.Untimed.WriteBytesAt(Registers.PC, opcode);
            InstructionPackage package = DecodeInstructionAtProgramCounter();

            if (package == null)
            {
                throw new InstructionDecoderException("Supplied opcode sequence does not decode to a valid instruction.");
            }

            return(Execute(package));
        }

        // execute an instruction directly (specified by mnemonic, so no decoding necessary)
        ExecutionResult IDebugProcessor.ExecuteDirect(string mnemonic, byte?arg1, byte?arg2)
        {
            if (!InstructionSet.InstructionsByMnemonic.TryGetValue(mnemonic, out Instruction instruction))
            {
                throw new InstructionDecoderException("Supplied mnemonic does not correspond to a valid instruction");
            }

            InstructionData data = new InstructionData()
            {
                Argument1 = arg1 ?? 0,
                Argument2 = arg2 ?? 0
            };

            InstructionPackage package = new InstructionPackage(instruction, data, Registers.PC);

            Registers.PC += package.Instruction.SizeInBytes; // simulate the decode cycle effect on PC
            return(Execute(package));
        }