Interrupts: im, ei, di and halt
The two interrupt lines of a real Z80, its three interrupt modes and the vector table mode 2 reads through the I register, and why halt is an idle loop on hardware. Then what this editor actually does, which is nothing.
The overview of this topic is in Assembly basics. The same topic in M68K, MIPS, RISC-V.
The keyboard and the mouse in the previous lecture were polled: the program went and looked, over and over, and burned the whole CPU doing it. The other way round is an interrupt, where the device says something and the program that was running is stopped in the middle to deal with it.
The Z80 has two wires for that, and no others. It has no exception mechanism at all: no illegal instruction trap, no division by zero (there is no division), no alignment fault, no privileged mode and no supervisor bit. Every undefined opcode does something, and nothing a program can do to itself takes the program counter away from it.
The two lines
- INT, the maskable interrupt. A device pulls it low, and the CPU takes notice at the end of the current instruction, but only if interrupts are enabled.
- NMI, the non-maskable interrupt. The same thing, except that no program can turn it off: the
CPU pushes the program counter and calls address
0x0066, always. It is for the things a machine cannot afford to miss, like the power supply saying it is about to fail.
Two flip-flops inside the CPU hold the state. IFF1 says whether INT is listened to, and IFF2 is a place to keep a copy of IFF1 while an NMI is being handled, so that the NMI handler can put it back.
diclears both: interrupts off.eisets both: interrupts on.
ei has a detail that catches everyone. It takes effect after the instruction that follows it,
not immediately, so ei / ret returns before any interrupt can arrive and the handler's own return
is never interrupted. That one instruction of delay is deliberate hardware behaviour.
The three modes
When INT is taken, what happens next depends on which of three modes the program selected with the
im instruction.
| written | what the CPU does when INT is taken |
|---|---|
im 0 | reads one instruction off the data bus and executes it, usually an rst n |
im 1 | calls address 0x0038, always, whatever the device is |
im 2 | reads a byte from the device, joins it to i, and calls the address stored there |
Mode 0 is the 8080's way: the device puts a whole instruction on the bus, and since rst n is one
byte and calls one of the eight low addresses, that is what a device usually supplies. Mode 0 is why
those eight addresses are reserved.
Mode 1 is what most home computers used, because it needs no hardware on the device's side: one
handler at 0x0038, and if there is more than one device it works out which by asking each of them.
Mode 2 goes through a table. The i register holds a byte, the device supplies another, and the
CPU reads a 16 bit address from the table at i times 256 plus the device's byte, then calls that
address. So i picks a 256 byte page of memory to be a table of handler addresses, and every device
gets its own handler with no asking around.
Type 9000 into the memory panel. The four vectors read 0C 80 four times, which is the address of
tick written little endian, and after the run counter holds 1 because the call ran the handler.
i is not in the registers panel, and ld a, i is how a program reads it back.
Returning from a handler
retireturns from a maskable interrupt. It pops the address the wayretdoes, and it also puts a pattern on the bus that lets a Zilog peripheral chip know its interrupt has been dealt with.retnreturns from an NMI, and copies IFF2 back into IFF1, so interrupts go back to whatever they were before the NMI arrived.
A handler runs between two instructions of a program that knows nothing about it, so it has to give
back every register it touches. Pushing four pairs and popping them again is over eighty clock cycles,
and this is the reason the shadow set from the registers lecture exists: ex af, af' and exx
together are eight cycles and hand the handler a whole private set.
a comes out at 44, bc at 1111, de at 2222 and hl at 3333, exactly as the program left
them, while af' reads 8800 and hl' reads 9999, which is what the handler was working on. Eight
clock cycles of saving, in two instructions.
halt
On a real Z80, halt does not stop the CPU. It executes nop over and over, keeping the memory
refresh going, until an interrupt arrives; the handler runs and the ret from it lands on the
instruction after the halt. So ei and then halt is the idle loop of a program waiting for a
device, and it is exactly how a machine sat there doing nothing while you were not typing.
di and then halt is the other case: no interrupt can arrive, so the CPU sits there until somebody
pulls the power. That is the closest a Z80 comes to an instruction that means "the program is over".
The r register is a side effect of that refresh. Its low seven bits count up on every instruction
fetch, and reading it is the classic cheap random number on this machine.
b comes out at 02 and c at 08, six fetches apart. r is not in the registers panel either,
and ld a, r is the only way to see it.
What this editor does
Nothing in this editor ever raises an interrupt. No key, no mouse button, no timer and no port pulls INT or NMI. So:
di,eiand all threeimforms assemble and execute, and change nothing you can observe.iandrare real registers here, written byld i, aand read byld a, i, and no interrupt ever reads the tableipoints at.retiandretnbehave exactly likeret: they pop an address off the stack and jump to it.haltends the run and the editor reports the program as terminated, whether interrupts are enabled or not, because a Z80 waiting for an interrupt that will never come is a program that has finished.
That was decided on purpose (ADR 0002): the peripherals here are polled, the port map is the whole I/O contract, and a run stays a plain sequence of instructions you can step through and undo. The other courses say the same thing in their own words, since none of the simulators in this editor delivers a device interrupt.
So the code on this page is the shape a real handler takes, and you can build it, step it and read
what it leaves behind, which is what the call in each program is standing in for.
Your turn
Set up interrupt mode 2 with its vector table in page 0x90: turn interrupts off, put 0x90 in the
i register, select the mode, turn interrupts back on, and finally read i back into b so the test
can see it.
Show solution
The second one hands you three handlers and a mode 2 vector table at 0x9000. The test starts a at
2, the byte a device would have put on the bus, and wants the handler at that index reached, so bc
comes back at 300. Scale the index, add the table's address, load the 16 bit address stored there and
jump to it.