I've found that the author of "Z80 Explorer", Goran Devic, has written a Z80 module for FPGA, presumably based on the extensive analysis of the die that he made. This file deals with interrupts.
I haven't checked much of the rest of files but I think it's safe to assume the following, specifically related to the NMI:
nreset
is the /RESET line.
nmi
is the negation of the /NMI line.
setM1
is a flag that indicates that this is the last cycle in the instruction (as it has to, well, set M1 on the next cycle).
ctl_no_ints
is a signal that tells whether interrupts are disabled for this opcode. Interrupts are disabled for CB, DD, ED or FD prefixes, even if M1 will be active in the next byte (*)
Now, here's what I gather from it. Ignoring the /RESET line, which just resets both registers:
nmi_armed
is clocked by the /NMI line (and by the line that resets it), not by the system clock. It's set with an /NMI falling edge, and reset when the flag that tracks whether the interrupt needs servicing is set. So it seems to be a sort of async-to-sync bridge that holds the fact that NMI was requested until the CPU is ready to read it.
in_nmi_ALTERA_SYNTHESIZED
seems to be the register that tracks whether the NMI needs servicing. It's clocked by the system clock and copied from nmi_armed
on the last clock of an instruction, provided interrupts are possible. This means that it will be reset on the last cycle of the 11-cycle NMI processing, which sets setM1 again.
Here are the parts of the code that are relevant to NMI, with the nreset signal eliminated for clarity, simplified to get rid of the confusingly named wires:
input wire setM1;
input wire ctl_no_ints;
input wire nmi;
output wire in_nmi;
reg nmi_armed;
reg in_nmi_ALTERA_SYNTHESIZED;
always @(posedge nmi or posedge in_nmi_ALTERA_SYNTHESIZED)
begin
nmi_armed <= ~in_nmi_ALTERA_SYNTHESIZED;
end
always @(posedge clk)
begin
if (setM1 & ~ctl_no_ints)
begin
in_nmi_ALTERA_SYNTHESIZED <= nmi_armed;
end
end
assign in_nmi = in_nmi_ALTERA_SYNTHESIZED;
(*) According to the code, DI
and EI
also set ctl_no_ints
, which would mean they also prevent an NMI from executing in the next instruction. I don't think this is right - in the die simulation, EI
seems to disable IFF1 until the falling edge of M1T2 of the next instruction, in which it is set again, and that seems to be the way it prevents a maskable interrupt after EI
, yet NMI can happen right after either DI
or EI
.
Update:
In the "Z80 Remix" die-level simulation, nmi_armed
is node #57 while in_nmi
appears to be node #78 or #1070, not sure which as I haven't seen them differ and I can't read dies. #57 is about halfway between the _nmi and the _halt pins (easy to find by ticking and unticking "NMI" without stepping the clock, and checking what's changed), while #78 and #1070 are both immediately below the _halt pin.
There's an inconsistency between the die version and the Verilog version, in that in the die version, nmi_armed
is cleared 2 half-clocks after node #78 goes on, while in the Verilog version it should be cleared pretty much immediately. I don't know the reason for this discrepancy, but it doesn't affect the outcome much.
As mentioned above, there's another inconsistency in that in the die simulation, an NMI can occur right after DI
or EI
, while in the Verilog version can't.
Yet another inconsistency is that in the Verilog version, there's a synchronous int_armed
register that holds the negation of the /INT state until the CPU acknowledges it, while in the die simulation, activating /INT at any clock other than the last will skip the interrupt.
I don't know the reason for these discrepancies, but it would appear that the Verilog version is not too precise in these details. These inconsistencies diminish the confidence in the Verilog version and thus in the accuracy of this answer, although the die simulation seems to support the general findings.
um00800.pdf
starts with "Nonmaskable Interrupt (input, negative edge-triggered)".