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.

InstructionDescription
RST 00CALL 0x0000: Reset
RST 08CALL 0x0008: Error Restart
RST 10CALL 0x0010: Print a Character
RST 18CALL 0x0018: Collect a Character
RST 20CALL 0x0020: Collect Next Character
RST 28CALL 0x0028: Floating Point Calculation
RST 30CALL 0x0030: Make BC Spaces
RST 38CALL 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:

ConditionDescription
Ccarry (C) is set
NCcarry is not set
Zzero (Z) is set
NZzero is not set
Msign (S) is set
Psign is not set
PEparity/overflow (P/V) is set
PEparity/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