public ExecutionResult(InstructionPackage package, Flags flags, bool skipInterruptsAfterExecution = false) { InstructionAddress = package.InstructionAddress; Instruction = package.Instruction; Data = package.Data; Flags = flags; SkipInterruptAfterExecution = skipInterruptsAfterExecution; }
/// <summary> /// 获取下一条指令 /// </summary> /// <returns>返回下一条指令</returns> public InstructionPackage GetNext() { InstructionPackage result = null; if (null != Now.Next) { result = Now.Next.GetInstruction(); Now = Now.Next; PC++; } return(result); }
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 } }
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); }
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(); } }
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)); }