A Spectrum runs at 3.5 MHz, a television frame lasts 20 milliseconds, and so a game has about 70,000 T-states (clock cycles) to do everything it does between one frame and the next. Every choice on this page is spent from that budget. XOR A clears A in 4 T-states where LD A,0 takes 7; LD r,(HL) reads memory in 7 where LD r,(IX+d) takes 19; an LDIR copy costs 21 T-states a byte, so filling the 6,144-byte Spectrum screen with it takes about 129,000, nearly two whole frames. Programmers learnt these numbers by heart because they decided what a game could fit in.
This page covers the instructions Z80 programs use most: 8- and 16-bit loads, arithmetic, logic, jumps, calls, the stack, I/O, block moves, shifts, rotates and bit operations, then the interrupt system and how the Spectrum uses it. Each table gives the byte count, the T-states and the flag effects as Zilog’s Z80 CPU User Manual gives them, and the same figures hold on any Z80 machine, from MSX and the Amstrad CPC to CP/M computers. Where the manual is wrong, the page says so. For the complete instruction set, see the Zilog manual. For the undocumented opcodes and the places where the hardware disagrees with the manual, see Sean Young’s The Undocumented Z80 Documented.
The T-states are the processor’s own. A machine can make an instruction take longer. On the Spectrum, the ULA draws the picture from the same 16K of RAM that holds the screen, and Chris Smith’s study of the chip explains that when it “detects a CPU address between 16384 and 32767 while it is reading video bytes, it immediately holds the Z80 clock high” until its own fetch is done. Code and data in that region, $4000 to $7FFF, run slower while the screen is being drawn. Timing-critical Spectrum code therefore lives above $8000.
Reading This Reference
Notation
- n = 8-bit immediate value (0-255)
- nn = 16-bit immediate value (0-65535)
- d = 8-bit signed displacement (-128 to +127)
- r = 8-bit register (A, B, C, D, E, H, L)
- rr = 16-bit register pair (BC, DE, HL, SP)
- (addr) = memory at address
- $ = current address (in expressions)
Flag Notation
- S = Sign flag (bit 7 of result)
- Z = Zero flag (result is zero)
- H = Half-carry flag (carry from bit 3 to 4)
- P/V = Parity/Overflow flag
- N = Add/Subtract flag (for BCD)
- C = Carry flag
- ● = Flag set or reset according to the result
- 1 = Flag always set
- 0 = Flag always reset
- — = Flag unchanged
Where the Zilog manual gives two T-state figures for a conditional instruction, the table shows them as taken/not taken.
8-Bit Load Instructions
LD r,n - Load Immediate
Load 8-bit value into register.
| Instruction | Bytes | T-states | S | Z | H | P/V | N | C |
|---|---|---|---|---|---|---|---|---|
| LD A,n | 2 | 7 | — | — | — | — | — | — |
| LD B,n | 2 | 7 | — | — | — | — | — | — |
| LD C,n | 2 | 7 | — | — | — | — | — | — |
| LD D,n | 2 | 7 | — | — | — | — | — | — |
| LD E,n | 2 | 7 | — | — | — | — | — | — |
| LD H,n | 2 | 7 | — | — | — | — | — | — |
| LD L,n | 2 | 7 | — | — | — | — | — | — |
Example:
LD A,42 ; A = 42
LD B,0 ; B = 0
LD H,$40 ; H = $40 (64 decimal)Note: LD instructions do not affect flags, with two exceptions: LD A,I and LD A,R (2 bytes, 9 T-states) set S and Z from the value loaded, reset H and N, and copy the interrupt flip-flop IFF2 into P/V. The manual adds a warning: “If an interrupt occurs during execution of this instruction, the Parity flag contains a 0.”
LD r,r’ - Load Register to Register
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| LD A,B | 1 | 4 | None affected |
| LD B,C | 1 | 4 | None affected |
| LD H,L | 1 | 4 | None affected |
All 49 combinations: A,B,C,D,E,H,L can be copied to/from each other.
LD r,(HL) - Load from Memory
Load register from memory address in HL.
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| LD A,(HL) | 1 | 7 | None affected |
| LD B,(HL) | 1 | 7 | None affected |
| LD C,(HL) | 1 | 7 | None affected |
LD (HL),r - Store to Memory
Store register to memory address in HL.
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| LD (HL),A | 1 | 7 | None affected |
| LD (HL),B | 1 | 7 | None affected |
| LD (HL),n | 2 | 10 | None affected |
16-Bit Load Instructions
LD rr,nn - Load 16-bit Immediate
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| LD BC,nn | 3 | 10 | None affected |
| LD DE,nn | 3 | 10 | None affected |
| LD HL,nn | 3 | 10 | None affected |
| LD SP,nn | 3 | 10 | None affected |
| LD IX,nn | 4 | 14 | None affected |
| LD IY,nn | 4 | 14 | None affected |
Arithmetic Instructions
ADD A,r / ADD A,n - Add
| Instruction | Bytes | T-states | S | Z | H | P/V | N | C |
|---|---|---|---|---|---|---|---|---|
| ADD A,B | 1 | 4 | ● | ● | ● | ● | 0 | ● |
| ADD A,n | 2 | 7 | ● | ● | ● | ● | 0 | ● |
| ADD A,(HL) | 1 | 7 | ● | ● | ● | ● | 0 | ● |
SUB r / SUB n - Subtract
| Instruction | Bytes | T-states | S | Z | H | P/V | N | C |
|---|---|---|---|---|---|---|---|---|
| SUB B | 1 | 4 | ● | ● | ● | ● | 1 | ● |
| SUB n | 2 | 7 | ● | ● | ● | ● | 1 | ● |
INC r / DEC r - Increment/Decrement
| Instruction | Bytes | T-states | S | Z | H | P/V | N | C |
|---|---|---|---|---|---|---|---|---|
| INC A | 1 | 4 | ● | ● | ● | ● | 0 | — |
| DEC A | 1 | 4 | ● | ● | ● | ● | 1 | — |
Note: The carry flag is not affected by 8-bit INC/DEC, so a loop counter can be decremented in the middle of a multi-byte add without losing the carry.
16-Bit Arithmetic
ADD HL,rr - 16-bit Add
| Instruction | Bytes | T-states | S | Z | H | P/V | N | C |
|---|---|---|---|---|---|---|---|---|
| ADD HL,BC | 1 | 11 | — | — | ● | — | 0 | ● |
| ADD HL,DE | 1 | 11 | — | — | ● | — | 0 | ● |
| ADD HL,HL | 1 | 11 | — | — | ● | — | 0 | ● |
| ADD HL,SP | 1 | 11 | — | — | ● | — | 0 | ● |
ADD HL,rr leaves S, Z and P/V alone (its H is the carry out of bit 11), so it cannot be followed directly by a JP Z or JP P test on the sum. The index registers have their own forms, ADD IX,pp and ADD IY,rr (2 bytes, 15 T-states). The ED-prefixed ADC HL,rr and SBC HL,rr (2 bytes, 15 T-states) do set S, Z and P/V. There is no 16-bit SUB, so a 16-bit compare clears the carry first and uses SBC: OR A / SBC HL,DE (see Common instruction patterns).
INC rr / DEC rr - 16-bit Increment/Decrement
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| INC BC | 1 | 6 | None affected |
| DEC HL | 1 | 6 | None affected |
Important: 16-bit INC/DEC do not affect any flags, so DEC BC / JR NZ,loop does not work — test the pair with LD A,B / OR C first.
Logical Instructions
AND r / AND n - Logical AND
| Instruction | Bytes | T-states | S | Z | H | P/V | N | C |
|---|---|---|---|---|---|---|---|---|
| AND B | 1 | 4 | ● | ● | 1 | ● | 0 | 0 |
| AND n | 2 | 7 | ● | ● | 1 | ● | 0 | 0 |
AND sets H; OR and XOR reset it. P/V holds the parity of the result for all three: set when the result has an even number of 1 bits. Zilog’s current manual gets this wrong on two of its pages. Its AND page says “P/V is reset if overflow” and its OR page “P/V is set if overflow”, but its general description of the flag says it is “used with logical operations and rotate instructions to indicate the resulting parity is even”, its XOR page says parity, and Young’s tables give parity for all three.
OR r / OR n - Logical OR
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| OR B | 1 | 4 | S,Z,P/V affected; H,N,C reset |
| OR n | 2 | 7 | S,Z,P/V affected; H,N,C reset |
OR A idiom: Sets Z from A and clears carry, without changing A.
XOR r / XOR n - Logical XOR
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| XOR B | 1 | 4 | S,Z,P/V affected; H,N,C reset |
| XOR n | 2 | 7 | S,Z,P/V affected; H,N,C reset |
XOR A idiom: Clears A in one byte and 4 T-states, against two bytes and 7 for LD A,0. Unlike LD, it also sets Z and clears carry, so use LD A,0 when the flags must survive.
CP r / CP n - Compare
Like SUB but doesn’t store result (only sets flags).
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| CP B | 1 | 4 | S,Z,H,P/V,C affected; N set |
| CP n | 2 | 7 | S,Z,H,P/V,C affected; N set |
Jump Instructions
JP nn - Unconditional Jump
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| JP nn | 3 | 10 | None affected |
| JP (HL) | 1 | 4 | None affected |
JP cc,nn - Conditional Jump
| Condition | Meaning | Instruction | Bytes | T-states |
|---|---|---|---|---|
| Z | Zero | JP Z,nn | 3 | 10 |
| NZ | Not zero | JP NZ,nn | 3 | 10 |
| C | Carry | JP C,nn | 3 | 10 |
| NC | No carry | JP NC,nn | 3 | 10 |
JR d - Relative Jump
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| JR d | 2 | 12 | None affected |
| JR Z,d | 2 | 12/7 | None affected |
| JR NZ,d | 2 | 12/7 | None affected |
Range: the displacement byte is −128 to +127 counted from the address after the two-byte instruction, which Zilog states as “a total displacement of +129 to –126 from the jump relative op code address”. The assembler does the adjustment. JR has only four conditions — Z, NZ, C, NC — where JP also has PE, PO, P and M.
DJNZ d - Decrement and Jump if Not Zero
Special loop instruction. Decrements B and jumps if B ≠ 0.
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| DJNZ d | 2 | 13/8 | None affected |
Same range as JR. DJNZ does not touch the flags, so a carry from the loop body survives to the next iteration.
Call and Return Instructions
CALL nn - Unconditional Call
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| CALL nn | 3 | 17 | None affected |
| CALL cc,nn | 3 | 17/10 | None affected |
RET - Return from Subroutine
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| RET | 1 | 10 | None affected |
| RET Z | 1 | 11/5 | None affected |
| RET NZ | 1 | 11/5 | None affected |
Stack Instructions
PUSH rr - Push to Stack
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| PUSH BC | 1 | 11 | None affected |
| PUSH DE | 1 | 11 | None affected |
| PUSH HL | 1 | 11 | None affected |
| PUSH AF | 1 | 11 | None affected |
| PUSH IX | 2 | 15 | None affected |
POP rr - Pop from Stack
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| POP BC | 1 | 10 | None affected |
| POP AF | 1 | 10 | All flags loaded from stack |
| POP IX | 2 | 14 | None affected |
I/O Instructions
IN A,(n) - Input from Port
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| IN A,(n) | 2 | 11 | None affected |
A goes to the top half of the address bus, n to the bottom half — so IN A,($FE) on a Spectrum reads the keyboard half-row selected by whatever A held.
OUT (n),A - Output to Port
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| OUT (n),A | 2 | 11 | None affected |
IN r,(C) - Input using BC
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| IN A,(C) | 2 | 12 | S,Z,P/V affected; H,N reset |
| OUT (C),r | 2 | 12 | None affected |
The whole of BC goes onto the address bus, which is how Spectrum code addresses a specific half-row (LD BC,$DFFE / IN A,(C)) or the 128K’s paging and sound ports. See ZX Spectrum Hardware Ports.
Rotate and Shift Instructions
The “fast” rotates act on A only and are 1-byte / 4 T-states. The general rotates and shifts (CB-prefix) work on any 8-bit register or (HL) and are 2-byte / 8 T-states (for register) or 2-byte / 15 T-states (for (HL)). All set the carry from the bit shifted out.
Fast (A-only) rotates
| Instruction | Bytes | T-states | Description |
|---|---|---|---|
| RLCA | 1 | 4 | Rotate A left circular (bit 7 → C and → bit 0) |
| RLA | 1 | 4 | Rotate A left through carry |
| RRCA | 1 | 4 | Rotate A right circular |
| RRA | 1 | 4 | Rotate A right through carry |
The A-only forms leave S, Z and P/V alone; the CB-prefix forms set them from the result.
General rotates and shifts (CB-prefix)
| Instruction | Bytes | T-states (reg / (HL)) | Description |
|---|---|---|---|
| RLC r | 2 | 8 / 15 | Rotate left circular |
| RL r | 2 | 8 / 15 | Rotate left through carry |
| RRC r | 2 | 8 / 15 | Rotate right circular |
| RR r | 2 | 8 / 15 | Rotate right through carry |
| SLA r | 2 | 8 / 15 | Shift left arithmetic (bit 0 ← 0) |
| SRA r | 2 | 8 / 15 | Shift right arithmetic (bit 7 preserved — sign extend) |
| SRL r | 2 | 8 / 15 | Shift right logical (bit 7 ← 0) |
Note: The CB-prefix slot between SRA and SRL is the undocumented SLL (sometimes written SL1). Young: “It works like SLA, for one exception: it sets bit 0 (SLA resets it).” It is not in the Zilog manual, and not every assembler accepts the mnemonic.
Bit Operations (CB-prefix)
| Instruction | Bytes | T-states (reg / (HL)) | Description |
|---|---|---|---|
| BIT b,r | 2 | 8 / 12 | Test bit b of register; sets Z if bit clear |
| SET b,r | 2 | 8 / 15 | Set bit b of register to 1 |
| RES b,r | 2 | 8 / 15 | Reset bit b of register to 0 |
BIT is a non-destructive test — flags update but the register is unchanged. SET and RES modify in place.
Exchange Instructions
The Z80 carries a second set of registers — AF', BC', DE' and HL' — and, in Zilog’s words, “the contents of main and alternate registers can be completely exchanged by executing only two instructions, EX and EXX”.
| Instruction | Bytes | T-states | Description |
|---|---|---|---|
| EX DE,HL | 1 | 4 | Swap DE ↔ HL |
| EX AF,AF’ | 1 | 4 | Swap AF ↔ AF’ (alternate flags) |
| EXX | 1 | 4 | Swap BC/DE/HL ↔ BC’/DE’/HL’ all at once |
| EX (SP),HL | 1 | 19 | Swap HL with the word at top of stack |
Interrupt handler idiom: EX AF,AF' + EXX puts the whole main set out of harm’s way in 8 T-states and the same pair brings it back. PUSH AF / PUSH BC / PUSH DE / PUSH HL costs 44 T-states and the matching POPs 40. The saving only holds if nothing else in the program uses the alternate set. On the Spectrum, the ROM does: its printing routine wraps a character in EXX to save HL and BC, and Ian Logan’s Spectrum Microdrive Book warns that H’L’ holds “the return address to BASIC”. A program that calls the ROM, or returns to BASIC, cannot let an interrupt routine change the alternate registers.
Block Instructions
LDIR - Load, Increment, Repeat
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| LDIR | 2 | 21/16 | H,N reset; P/V reset on completion; S,Z,C unchanged |
Operation: Copy byte from (HL) to (DE), increment HL and DE, decrement BC. Repeat until BC = 0. 21 T-states per byte while repeating, 16 for the final byte. LDDR does the same downwards; LDI/LDD do one byte and stop.
Example:
; Copy 6144 bytes from $8000 to $4000
LD HL,$8000 ; Source
LD DE,$4000 ; Destination
LD BC,6144 ; Count
LDIR ; Copy all bytesMiscellaneous Instructions
NOP - No Operation
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| NOP | 1 | 4 | None affected |
HALT - Halt CPU
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| HALT | 1 | 4 | None affected |
“When a software HALT instruction is executed, the CPU executes NOPs until an interrupt is received” — either an NMI or, with interrupts enabled, a maskable one. On a Spectrum with interrupts enabled, HALT is the one-byte way to wait for the next frame.
DI / EI - Disable/Enable Interrupts
| Instruction | Bytes | T-states | Flags |
|---|---|---|---|
| DI | 1 | 4 | None affected |
| EI | 1 | 4 | None affected |
EI takes effect one instruction late: “any pending interrupt request is not accepted until after the instruction following EI is executed. This single instruction delay is necessary when the next instruction is a return instruction.” So EI / RET (or EI / RETI) returns before the next interrupt can arrive. NMI is unmaskable — DI does not block it.
Interrupt Modes
The Z80 supports three interrupt modes selected by the IM instruction. After a reset the CPU is in mode 0.
| Mode | Sets via | Behaviour | T-states (interrupt entry) |
|---|---|---|---|
| IM 0 | IM 0 (ED 46) |
The interrupting device puts an instruction on the bus during the acknowledge cycle and the CPU executes it — “often this response is a restart instruction”. | 13 for an RST |
| IM 1 | IM 1 (ED 56) |
CPU pushes PC and restarts at $0038, whatever is on the bus. The Spectrum ROM selects this. |
13 |
| IM 2 | IM 2 (ED 5E) |
CPU forms an address from I (high byte) and the byte on the data bus (low byte), reads a 16-bit address from there, and calls it. | 19 |
Zilog says the low byte’s bottom bit “must be a 0”, giving a table of 128 two-byte entries. Young tested it and found the byte is used as-is: the address is I × 256 + bus byte, unmasked.
| Instruction | Bytes | T-states | Effect |
|---|---|---|---|
| IM 0 / IM 1 / IM 2 | 2 | 8 | Select interrupt mode |
| RETI | 2 | 14 | Return from maskable interrupt; signals Z80-family peripherals that the routine is done |
| RETN | 2 | 14 | Return from NMI; copies IFF2 back into IFF1 |
NMI (non-maskable interrupt) “is always recognized at the end of the current instruction, independent of the status of the interrupt enable flip-flop, and automatically forces the CPU to restart at location 0066h.” Nothing in the 48K Spectrum drives the NMI line. Its ROM does hold a routine at $0066, which Ian Logan and Frank O’Hara describe as “not used in the standard Spectrum”, though it “allows for a system reset to occur following activation of the NMI line”. It reads the system variable they call NMIADD, at $5CB0, and jumps through it, but its test is the wrong way round: it jumps only when NMIADD holds zero, which resets the machine, and returns otherwise. Their margin note on the JR NZ reads “This should have been ‘JR Z’!” As written, a program cannot put the address of its own NMI routine in NMIADD. Amstrad’s +3 fixed it: its manual says the variable “is now used on the +3 as an NMI jump vector”, with zero meaning no action and any other value a jump. Hardware that drives the line on the older models, such as the Multiface, has to bring its own handler with it.
Interrupts on the Spectrum
The ROM’s initialisation sets I to $3F and selects IM 1. Every 20 ms an interrupt arrives, and the ROM’s MASK-INT routine at $0038 pushes AF and HL (and BC and DE around the keyboard call), increments the FRAMES counter, scans the keyboard, and returns with EI / RET. A program that lives with that gets a free 50 Hz clock and keyboard scan. The price is the IY register. The ROM keeps it pointing into its system variables, which the keyboard routine updates; Logan and O’Hara’s note on the start-up code reads “IY holds +ERR-NR always”. Amstrad’s manuals for the +2 and +3 put it plainly: “You should not use the IY and I registers in a machine code routine that expects to use the BASIC interrupt mechanism.” A program that needs IY, or its own 50 Hz routine, disables interrupts or switches to IM 2.
IM 2 on the Spectrum has one complication: nothing in the machine puts a chosen byte on the bus during the acknowledge cycle. Young notes that on systems like this “this value ends up being FFh”, but nothing guarantees it, and an attached peripheral can change it. Since the byte is not under the program’s control, and is not masked to even values, the vector table must give the same result from any of the 256 possible offsets. That is why the table is 257 bytes of one value, so that every pair of neighbouring bytes reads as the same address. The +3 manual adds where the table may safely go: keep I out of $40-$7F on the 48K to +2 (the video RAM region), and out of $C0-$FF when a contended bank is paged at $C000; “you should only vector IM 2 interrupts to between 8000h and BFFFh”.
Common Instruction Patterns
Clear A Register (fastest)
XOR A ; 4 T-states (vs LD A,0 = 7); also sets Z, clears CTest if A is Zero
OR A ; Sets Z flag without changing A
JR Z,IsZeroDouble A Register
ADD A,A ; Shift left one bit, carry gets bit 7Delay Loop
LD B,100
Delay: DJNZ Delay ; 13 × 99 + 8 = 1295 T-states16-bit compare
OR A ; clear carry
SBC HL,DE ; HL - DE, sets Z and C
JR C,HLless ; carry set: HL < DE (unsigned)SBC leaves the difference in HL. If HL is still needed, put ADD HL,DE straight after it. That restores HL and leaves Z alone, and its carry out is set exactly when the subtraction borrowed, so Z and C still hold the result of the compare.
Performance Notes
Fastest instructions:
- Register operations: 4 T-states
- XOR A (clear A): 4 T-states
- INC/DEC register: 4 T-states
Optimisation tips:
- Use register operations over memory.
- Use HL instead of IX/IY for indexed access —
LD r,(HL)is 7 T-states,LD r,(IX+d)is 19. - Prefer
JRoverJPfor size, not speed —JR eis 12T (slower thanJP nnat 10T) but only 2 bytes (vs 3). ConditionalJR cc,eis 12T taken / 7T not taken;JP cc,nnis always 10T. So in tight code where the not-taken path dominates,JR ccis faster and smaller. - Use
DJNZfor counted loops — 13/8 T-states with no separate decrement and test. - Use
EX AF,AF'+EXXfor register backup in interrupt handlers (8T total against 44T + 40T for four pushes and four pops), if nothing else uses the alternate set. OR A(1 byte) clears carry and tests A for zero without changing it.CP 0gives the same Z and C but takes 2 bytes and sets N.- Unroll loops in speed-critical code (raster effects, audio mixing, sprite blitting).
Sources
- Zilog, Z80 CPU User Manual, UM008011-0816 (2016): the per-instruction pages for every byte count, T-state figure and flag effect in the tables; p. 67, the Parity/Overflow flag and its use for parity after logical operations; the
ANDandORpages’ “overflow” wording;LD A,IandLD A,Rand the parity warning;JRandDJNZranges;HALT;EI; IFF1, IFF2,RETN; the three interrupt modes, reset into mode 0, and the NMI at0066h; the alternate register set andEXX. - Sean Young, The Undocumented Z80 Documented, version 0.91, 18 September 2005:
SLL; the bus value “FFh”; interrupt acceptance times of 13, 13 and 19 T-states; theIM 2address unmasked, tested against Zilog’s figure; parity forAND,ORandXORin the flag tables; NMI handling. - Ian Logan and Frank O’Hara, The Complete Spectrum ROM Disassembly (Melbourne House, 1983): MASK-INT at
$0038; the NMI routine at$0066and the “JR Z” note; I set to+3F,IM 1and “IY holds +ERR-NR always” in the start-up code;EXXin PO-SAVE at$0C3B. - Ian Logan, Spectrum Microdrive Book (Melbourne House, 1983), hook codes: “preserve H’L’, the return address to BASIC”.
- Amstrad, ZX Spectrum +3 manual (1987), chapter 8, part 26, “Using machine code”: IY and I with the BASIC interrupt mechanism, where
IM 2may be vectored, and NMIADD as the +3’s NMI jump vector. Amstrad, Sinclair ZX Spectrum +2 manual (1986), part 26, p. 154: the same IY and I warning. - Chris Smith, The ZX Spectrum ULA: How to Design a Microcomputer (ZX Design and Media, 2010), chapter 18, p. 190: the ULA holding the Z80 clock during video fetches from addresses 16384 to 32767; the 3.5 MHz CPU clock.