Z80 Programming
This article describes how to write machine code programs for the Z80.
Requirements
For writing Z80 machine code programs you will typically need an assembler (to convert Z80 assembly listings into binaries) and probably also an emulator for testing and single-stepping your code. Some emulators include assemblers although the level of usability is somewhat variable. Some popular Z80 assemblers are:
- z80asm (part of z88dk)
- binutils-z80 (GNU binary utilities, for the z80-unknown-coff target)
Z80 machine code reference
Registers
The Z80 has a collection of 16-bit registers, some of which can also be accessed as pairs of 8-bit registers. (Or is it the other way round?)
The following table gives details; one-character names imply 8 bits, while two-character names imply 16 bits. (I've done this from memory, so someone should check it, particularly the Access column. Also a proper chart of all this would be good, with things like "load from [nn]" and "16-bit add to"; the current table is incomplete and changing that would clutter it enormously)
Name | Description | Access |
---|---|---|
PC | Program Counter | Write with JP/JR/CALL/RET |
A | Accumulator | All LD, 8-bit arithmetic & logic ops, IN, OUT, CPI[R]/CPD[R], DAA. As AF with PUSH/POP and EX AF,AF' |
F | Flags register | Written indirectly by most ops, directly by CCF/SCF; read with conditional ops (JP/JR/CALL/RET cc). As AF with PUSH/POP/EX |
BC | User register | Most LD, PUSH/POP, 16-bit arithmetic, block instructions, IN, OUT, EXX. As B,C with LD, 8-bit arithmetic & logic, DJNZ. |
DE | User register | Most LD, PUSH/POP, 16-bit arithmetic, block instructions, EXX, EX DE,HL. As D,E with LD, 8-bit arithmetic & logic. |
HL | User register | All LD, PUSH/POP, 16-bit arithmetic, block instructions, EXX, EX DE/SP,HL. As H,L with LD, 8-bit arithmetic & logic. |
SP | Stack pointer | Some LD, 16-bit arithmetic, EX SP,HL, write indirectly with CALL/RET/PUSH/POP. |
I | Interrupt vector | LD A,I; LD I,A |
R | DRAM Refresh register | LD A,R; LD R,A |
IX, IY | Index registers | All LD, PUSH/POP, 16-bit arithmetic, block instructions, EXX. As IXH,IXL/IYH,IHL with LD, 8-bit arithmetic & logic. |
AF' | Shadow register | EX AF,AF' |
BC', DE', HL' | Shadow registers | EXX |
Instruction set
Anything enclosed in (brackets) refers to the contents of the memory location pointed to. So LD HL,(0x4000) will store the first two bytes of screen RAM in L and H (the order is backwards because the Z80 is little-endian). Whenever IX or IY is used as a pointer, the instruction contains a literal 'displacement' byte, which is signed. This is typically used for tables and structs. When the action of an opcode is given in C-style notation, a prefixed asterisk has the same meaning. So the action of LD HL,(0x4000) is HL=*0x4000; (although in C you'd probably need to cast that, but never mind).
LD (Load) - many variants, all essentially of the form LD dest,src. Reads a byte or word from src and writes it to dest. Mostly take 4 T-states plus 3 for each byte of literal plus 4 if using IX/IY plus 3 if reading/writing memory plus 8 if using IX+d/IY+d.
LD r,s Register Direct Load 8-bit - r and s are 8-bit registers; if either is I or R then the other must be A LD r,n Register Load Literal 8-bit - r is an 8-bit register, n is an 8-bit literal LD r,(hh) Register Load Indirect 8-bit - r is an 8-bit register, hh is HL, IX+d or IY+d LD (hh),r As above LD (hh),n Load Indirect Literal 8-bit - hh is HL, IX+d or IY+d, n is an 8-bit literal LD SP,hh Register Direct Load 16-bit - hh is HL, IX or IY LD rr,nn Register Load Literal 16-bit - rr is a 16-bit register (BC, DE, HL, SP, IX or IY), nn is a 16-bit literal LD rr,(nn) Register Load Indirect 16-bit - rr is a 16-bit register (BC, DE, HL, SP, IX+d or IY+d), nn is a 16-bit literal. Value is read LSB first. LD (nn),rr As above LD A,(qq) Accumulator Load Indirect - qq is BC or DE, or a 16-bit literal LD (qq,A) As above
PUSH - store a value on the stack. Action is *--SP=rrh;*--SP=rrl; (in C notation). 11 T-states, plus 4 if using IX/IY.
PUSH rr Push 16-bit value to stack - rr is a 16-bit register (BC, DE, HL, SP, IX or IY)
POP - fetch a value from the stack. Action is *SP++=rrl;*SP++=rrh; (in C notation). 10 T-states, plus 4 if using IX/IY.
POP rr Pop 16-bit value from stack - rr is a 16-bit register (BC, DE, HL, SP, IX or IY)
EX (Exchange) family - generally of the form EX dest,src. Swaps the values in dest and src. 4 T-states, plus 15 if writing through (SP), plus 4 if using IX/IY.
EX DE,HL Exchange DE with HL - note that FD or DD prefixes don't make this use IX/IY, it still uses HL EX AF,AF' Exchange AF with shadow EXX Exchange BC,DE,HL with shadows EX (SP),hh Exchange word at SP with hh - hh is HL, IX or IY
Block loads - take 16 T-states, or 21 if they Repeat and BC!=0
LDI Load and Increment - *DE++=*HL++; BC--; LDIR Load, Increment and Repeat - *DE++=*HL++; BC--; if(BC) PC-=2; LDD Load and Decrement - *DE--=*HL--; BC--; LDDR Load, Decrement and Repeat - *DE--=*HL--; BC--; if(BC) PC-=2;
Block compares - take 16 T-states, or 21 if they Repeat, BC!=0 and A!=*HL
CPI Compare and Increment - compare(A-*HL); BC--; CPIR Compare, Increment and Repeat - compare(A-*HL); BC--; if(BC && !ZF) PC-=2; // ZF is Z flag, F&0x40, set by the compare() CPD Compare and Decrement - compare(A-*HL); BC--; CPDR Compare, Decrement and Repeat - compare(A-*HL); BC--; if(BC && !ZF) PC-=2; // ZF is Z flag, F&0x40, set by the compare()
(Not finished, obviously)