예제 #1
0
        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();
        }
예제 #2
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();
            }
        }