jp, jr and the conditions

An if statement flattened into a compare and a conditional jump, the two jump instructions and the 128 bytes that separate them, and the four conditions jr is allowed to test.

The F register lecture showed cp writing the flags and one jump reading them. Let's now write the control flow of a real program out of those two, starting from the C.

An if, flattened

Say we want this:

int x = 50;
if (x >= 10) {
    x = 100;
} else {
    x = 200;
}

Assembly runs top to bottom and jumps, so the first step is to write the if as a goto, the way the general course did. Flip the condition and jump over the true branch:

    int x = 50;
    if (x < 10) goto smaller;
    x = 100;
    goto done;
smaller:
    x = 200;
done:

Every line of that has an instruction. x lives in a, the if is a cp and a conditional jump, and the two gotos are unconditional jumps.

a comes out at 64, which is 100. cp 10 computes a - 10 and throws the answer away, and C is set when that subtraction borrowed, which is when a was the smaller of the two. So jr c is "jump if a was less than 10", reading them as unsigned numbers.

Try changing ld a, 50 to ld a, 5 and running again: the jump is taken and a comes out at C8, which is 200.

smaller and done are labels, which is to say addresses, and jr done on the fifth line exists for the same reason the goto done does in the C: without it the program would fall into the else branch and run both.

Two jumps

The Z80 has two unconditional jumps and they go to the same place by different means.

writtenbytesreacheshow
jp label3anywhere in the 64 KBthe address is in the instruction
jr label2128 back, 127 forwarda signed byte added to pc

Build it and read the memory panel: 18 00 C3 05 80 76. The jr carries the number 0, which is how far to jump from the instruction after it, and the jp carries 05 80, which is 0x8005 written little endian.

The assembler works the displacement out from the label, so you write the same thing either way. What it cannot do is stretch it: a jr to a label more than 127 bytes ahead fails the build with "destination is too far by 73 bytes for relative jump; use jp", and the fix is in the message.

Which to write: jr inside a loop or an if, where the target is a few instructions away, and jp for anything that leaves the neighbourhood. On a real Z80 jr is one byte shorter and slower when taken, so the choice was never obvious; here it is a matter of range.

The conditions

jp takes all eight conditions from the F register lecture. jr takes only four, because a two byte instruction had no room for more:

conditionjpjrjumps when
nzyesyesZ is 0
zyesyesZ is 1
ncyesyesC is 0
cyesyesC is 1
poyesnoP/V is 0, no overflow
peyesnoP/V is 1, overflow
pyesnoS is 0, the result was not negative
myesnoS is 1, the result was negative

So a branch on the sign or on an overflow is a jp, whatever the distance.

cp writes Z and C in one go as well, so a less, equal, greater decision is two conditional jumps and no second comparison. This one asks all three questions in a row.

b comes out at 02, c at 02 and d at 03. Neither z on the first test nor pe on the second has a jr form here, so both of those jumps had to be jp; the three way comparison at the end tests Z and C, which jr can do.

Try changing ld a, 5 to ld a, 3 and then to ld a, 9, and watch d come out at 2 and 1. The order matters: jr z has to come first, because a cp of two equal numbers leaves C at 0, so with the two jumps the other way round the equal case would fall through into the "larger" branch.

Branching on one bit

bit n, r tests one bit and sets Z from it, and the sense is backwards from what you would guess: Z is 1 when the bit is 0. So jr z after a bit means "the bit was clear" and jr nz means "the bit was set".

b comes out at 01. Try changing bit 0, a to bit 1, a: bit 1 of 0b101 is 0, Z goes to 1, the jump is taken and b stays 0.

or a is the same idea for the whole register. It leaves a alone and sets Z from it, so or a and jr z is how a program asks "is a zero", and it is one byte where cp 0 is two.

Jumping to an address in a register

jp (hl) sets the program counter to whatever hl holds. The parentheses are a lie inherited from Zilog's own syntax: nothing is read from memory, the jump goes to the address in hl, which is why some assemblers spell it jp hl.

a comes out at 07. jp (ix) and jp (iy) do the same with the index registers, and there is no conditional form of any of the three.

That is a function pointer in C, f() where f is a variable, and it is also how a switch is written when the cases are dense: put the addresses in a table with .dw, index into it, load the address into hl and jp (hl). The jump-table Example of this course does exactly that.

Your turn

The test starts a at 200. Leave 1 in b if a is 100 or more, and 2 if it is less, reading a as an unsigned number. One cp and one conditional jump.

Show solution

The second one starts a at 0b00010000. Leave 1 in c if bit 4 of a is set and 0 if it is not, without changing a.

Show solution