Z80 CPU Bug - redcode/Z80 GitHub Wiki

In May 2022, I (Tony Brewer) discovered a Z80 bug that is present in the real CPU. It can occur if there is a special reset when the Z80 is halted. The bug was first found in 2014 without fully realising it is a bug. If you are unfamiliar with the special reset, I suggest reading the Z80 Special Reset page.

Usually the program counter (PC) is incremented during cycle M1T1 of the opcode fetch. $\overline{\texttt{HALT}}$ goes low on the falling edge of CLK in M1T4 of the HALT instruction, which stops PC from incrementing so that the same opcode after HALT is fetched and ignored again and again until an exit condition occurs (reset or interrupt accepted).

If a special reset occurs when halted the HALT state is exited immediately and the opcode of the instruction after HALT is fetched and executed with no delay. However, PC does not increment during this opcode fetch and is one less than it should be for the remainder of the instruction. $\overline{\texttt{RESET}}$ going low causes $\overline{\texttt{HALT}}$ to go high immediately and PC increments normally for each subsequent opcode or immediate fetch.

Thus for multi-byte instructions after HALT the first opcode is read twice and it replaces either the correct second opcode or the first immediate byte if there is no prefix. This is the bug and it is unavoidable, as a special reset is detected after the T-state when PC is incremented.

Instructions such as LD r,n load register r with the LD r,n opcode instead of n.

Instructions such as LD rr,nn load the low reg with the LD rr,nn opcode and the high reg with the low immediate byte.

All prefix instructions read the first prefix twice. For DD/FD this simply adds 4T to the instruction which otherwise executes correctly.

All CB XX instructions are executed as CB CB = SET 1,E instead of the intended instruction thus corrupting E.

All ED XX instructions are executed as ED ED, a harmless 8T NOP.

Single-byte instructions after HALT are not affected by this bug and execute normally if non-branching.

Jumps, calls and returns during which special reset occurs do not branch. CALL/RST push an incorrect return address that is one too low and CALL/DJNZ/JR/JP nn load the wrong branch address into WZ, which is preserved after the special reset takes place as are all registers except PC.

The bug can be studied most easily perhaps by using Visual Z80 Remix.