Branching and looping is fundamental to computing, and the Z80 has a comprehensive set of instructions to support this.
You can:
- Perform relative and absolute jumps.
- Perform conditional jumps.
- Jump to a direct address, or to the contents of a register.
- Call and return from subroutines.
Relative and Absolute Jumps
There are two types of jumps in Z80; JR (Jump Relative) and JP (Jump) instructions.
A relative jump is encoded in two bytes, the second byte being a two’s complement number (between 127 and -128), and jumps relative to the PC at the point of the jump, so can only jump to a point within the instruction’s 128-byte vicinity.
An absolute jump is encoded in three bytes, but can jump to any 16 bit address, and is slightly quicker to execute.
Subroutines
The instruction set CALL is similar to JP, but the address of the next instruction (PC) is pushed onto the stack before the jump is performed.
The instruction set RET pops the word off the top of the stack into the program counter (PC), and uses it as the address of the next instruction to be executed.
These are similar to the functions GO SUB and RETURN in BASIC.
There are also 8 special RST instructions; these are 1-byte call routines that call a specific address in the first page of memory (from address 0x0000). Z80-based Sinclair machines have ROM in that location, so these call ROM routines.
Instruction | Description |
RST 00 | CALL 0x0000: Reset |
RST 08 | CALL 0x0008: Error Restart |
RST 10 | CALL 0x0010: Print a Character |
RST 18 | CALL 0x0018: Collect a Character |
RST 20 | CALL 0x0020: Collect Next Character |
RST 28 | CALL 0x0028: Floating Point Calculation |
RST 30 | CALL 0x0030: Make BC Spaces |
RST 38 | CALL 0x0038: NMI (Non-Maskable Interrupt) |
For more information on these ROM routines, I suggest reading The Complete Spectrum ROM Disassembly by Dr Ian Logan and Dr Frank O’Hara.
Conditions
The instructions JR, JP, CALL and RET support conditional jumps. If the condition is not fulfilled then program execution carries onto the next instruction.
The conditions can be one of the following:
Condition | Description |
C | carry (C) is set |
NC | carry is not set |
Z | zero (Z) is set |
NZ | zero is not set |
M | sign (S) is set |
P | sign is not set |
PE | parity/overflow (P/V) is set |
PE | parity/overflow is not set |
Note:
- The instruction JR only supports the conditions Z, NZ, C and NC.
- The instructions JP (HL), JP (IX) and JP (IY) do not support conditionals, though it is possible to get around this with either self-modifying code, using the stack, or using another branch instruction before the jump (see code examples at bottom).
- In all instances, the flags are preserved.
Looping
There is a special loop instruction DJNZ. This decreases the register B, and performs a JR if the result is not zero. The flags are preserved, and since this is a relative jump can only jump within the instruction’s 128-byte vicinity.
In Practice
Most of the maths operations detailed in the previous chapter will set or clear some or all of the condition flags, so it’s easy to start combining those with branching operations to create simple programs.
Here is a sample code snippet that counts how many bits are set to 1 in the accumulator (register A). It returns the result in register C, with register A containing it’s original value.
LD A,%00110001
LD B,8 ; Number of times to loop (8 bits)
LD C,0 ; Store the answer in C
LOOP: RRCA ; Rotate A right, with bit 0 moved to bit 7 and the carry flag
JR NC, SKIP ; If the carry flag is not set, that bit was 0, so we skip the next bit
INC C ; Increment the C register
SKIP: DJNZ LOOP ; Decrease B, and loop 8 times
This code snippet does the equivalent of JP NZ (HL):
JR Z, SKIP
JP (HL) ; This instruction will only be called if the Z flag is NZ
SKIP: ... ; The rest of your code here
An alternative to the above, using self-modifying code:
LD (LABEL+1),HL ; Store the contents in HL directly in the jump instruction
LABEL: JP NZ, 0x0000 ; The jump instruction is encoded as C2 HH LL, where HH and LL are the H and L address bytes
And finally, a neat solution using the stack:
PUSH HL ; Push the address onto the stack
RET NZ ; Pop it back off, and start executing at that address