private void HandleNonMaskableInterrupts() { if (IO.NMI) { if (_halted) { Resume(); } _iff2 = _iff1; // save IFF1 state ready for RETN _iff1 = false; // disable maskable interrupts until RETN Timing.BeginInterruptRequestAcknowledgeCycle(InstructionTiming.NMI_INTERRUPT_ACKNOWLEDGE_TSTATES); Push(WordRegister.PC); Registers.PC = 0x0066; Registers.WZ = Registers.PC; Timing.EndInterruptRequestAcknowledgeCycle(); } IO.EndNMIState(); }
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(); } }