Test, Then Jump
There's no IF on the Z80. Learn how the machine makes decisions instead — compare a value, which sets a flag, then jump on that flag. Build your own IF, and write your first labels.
You've met everything the machine is. Now: how does it decide?
In BASIC you wrote IF x = 5 THEN …. The Z80 has no IF. It does something smaller and stranger, in two steps:
- Compare.
cp 5subtracts 5 fromA, throws the answer away, and keeps only the flags — little yes/no bits the last operation leaves behind. IfAwas exactly 5, the result was zero, so the zero flag is set. - Jump.
jr z, somewheremeans "jump to somewhere if the zero flag is set." If it isn't, the machine carries straight on.
Test, then jump. You assemble the IF yourself out of those two pieces — and it turns out that's all IF ever was.
What you'll see by the end
A green border — because the program decided on green. We put 5 in A, compared it with 5, and because they matched, the program jumped to the branch that sets green. Put a different number in and it takes the other road, to red. The colour is the program's answer to a question.
Building the IF
; ============================================================================
; PRIMER — Beat 7: Test, Then Jump
; ============================================================================
; There is no IF on the Z80. Instead the CPU does two smaller things:
;
; cp 5 -- COMPARE A with 5. (It subtracts 5 from A, throws the
; answer away, and just keeps the FLAGS. If A was 5, the
; result was 0, so the ZERO FLAG is set.)
; jr z, .equal -- JUMP if the zero flag is set. Otherwise carry straight on.
;
; You build the IF yourself: test, then jump. Below, A is shown on the border
; as green if it equals 5, red if it doesn't. Change the value and the path
; the program takes changes with it.
;
; .equal and .hold are labels WE name. The leading dot makes them "local" to
; the routine above (start) -- that, and the rule that label spelling is
; case-sensitive, are conventions of the ASSEMBLER (pasmonext), not the Z80.
; See the unit text.
; ============================================================================
org 32768
start:
ld a, 5 ; the value we're testing (change me, re-run)
cp 5 ; compare A with 5 -> sets the zero flag if equal
jr z, .equal ; zero flag set? (A was 5) jump to .equal
ld a, 2 ; A was NOT 5 -> red
out ($FE), a
jr .hold
.equal:
ld a, 4 ; A was 5 -> green
out ($FE), a
.hold:
halt
jr .hold
end start
Read it as a fork in the road. cp 5 asks the question; jr z, .equal takes the green road if the answer is "equal"; otherwise the code falls through to the red road, then jr .hold skips over the green branch so it isn't run by accident. jr .hold with no condition is an unconditional jump — go there, always. (That's the same jr you've used to loop since Beat 1, now doing a different job.)
Labels — and here your assembler starts to matter
.equal and .hold are labels you named — the first you've written; until now you were only given .loop. Labels are how you point a jump at a place in the code. And they come with two rules that are worth meeting head-on, because this is the first place they can bite:
- They're case-sensitive.
.equaland.Equalare two different labels. Jump to one having defined the other and the assembler stops with "symbol undefined." Pick a spelling and keep it. - That leading dot means "local."
.equalbelongs to the routine above it (start). It's why we've been writing.loopwith a dot all along — now you know what the dot was for.
Here's the important part, though. Everything you've learned about the machine — registers, the screen, the flags — is universal: it's true of any Z80, assembled by any tool. But how you write labels is a rule of the assembler — the program that turns your text into bytes — not of the chip. We use pasmonext; a different assembler might treat label case differently, or mark local labels with @ or $$ instead of a dot. This is the first time the tool has a say, not just the machine. From here on, when a rule belongs to the assembler and not the Z80, we'll say so.
Assemble and run
pasmonext --sna test-and-jump.asm primer.sna
A is 5, so the border comes up green — the program took the equal road.
Try this: change the answer
Change ld a, 5 to ld a, 3, assemble and run: red, because 3 isn't 5 and the program takes the other road. Put it back to 5: green again. You're not changing the question (cp 5), only the value being asked about — and watching the decision flip.
Try this: branch three ways
One compare can sort a value into less, equal or greater with a second jump — the two-way fork grown a third road:
| 1 | 1 | ; ============================================================================ | |
| 2 | - | ; PRIMER — Beat 7: Test, Then Jump | |
| 2 | + | ; PRIMER — Beat 7, "Try this: branch three ways" | |
| 3 | 3 | ; ============================================================================ | |
| 4 | - | ; There is no IF on the Z80. Instead the CPU does two smaller things: | |
| 5 | - | ; | |
| 6 | - | ; cp 5 -- COMPARE A with 5. (It subtracts 5 from A, throws the | |
| 7 | - | ; answer away, and just keeps the FLAGS. If A was 5, the | |
| 8 | - | ; result was 0, so the ZERO FLAG is set.) | |
| 9 | - | ; jr z, .equal -- JUMP if the zero flag is set. Otherwise carry straight on. | |
| 10 | - | ; | |
| 11 | - | ; You build the IF yourself: test, then jump. Below, A is shown on the border | |
| 12 | - | ; as green if it equals 5, red if it doesn't. Change the value and the path | |
| 13 | - | ; the program takes changes with it. | |
| 14 | - | ; | |
| 15 | - | ; .equal and .hold are labels WE name. The leading dot makes them "local" to | |
| 16 | - | ; the routine above (start) -- that, and the rule that label spelling is | |
| 17 | - | ; case-sensitive, are conventions of the ASSEMBLER (pasmonext), not the Z80. | |
| 18 | - | ; See the unit text. | |
| 4 | + | ; One compare can answer three questions at once: less, equal, or greater. | |
| 5 | + | ; jr z jumps when A == n (zero flag) | |
| 6 | + | ; jr c jumps when A < n (the CARRY flag -- set when the subtract borrowed; | |
| 7 | + | ; we'll meet carry properly later, just use it here) | |
| 8 | + | ; Anything that falls past both is A > n. | |
| 9 | + | ; Change the value: 3 (less -> blue), 5 (equal -> green), 9 (greater -> red). | |
| 19 | 10 | ; ============================================================================ | |
| 20 | 11 | | |
| ... | |||
| 22 | 13 | | |
| 23 | 14 | start: | |
| 24 | - | ld a, 5 ; the value we're testing (change me, re-run) | |
| 25 | - | cp 5 ; compare A with 5 -> sets the zero flag if equal | |
| 26 | - | jr z, .equal ; zero flag set? (A was 5) jump to .equal | |
| 15 | + | ld a, 7 ; change me: 3, 5, or 9 | |
| 16 | + | cp 5 ; compare A with 5 | |
| 27 | 17 | | |
| 28 | - | ld a, 2 ; A was NOT 5 -> red | |
| 18 | + | jr z, .equal ; A == 5 | |
| 19 | + | jr c, .less ; A < 5 | |
| 20 | + | | |
| 21 | + | ld a, 2 ; A > 5 -> red | |
| 22 | + | out ($FE), a | |
| 23 | + | jr .hold | |
| 24 | + | | |
| 25 | + | .less: | |
| 26 | + | ld a, 1 ; A < 5 -> blue | |
| 29 | 27 | out ($FE), a | |
| 30 | 28 | jr .hold | |
| 31 | 29 | | |
| 32 | 30 | .equal: | |
| 33 | - | ld a, 4 ; A was 5 -> green | |
| 31 | + | ld a, 4 ; A == 5 -> green | |
| 34 | 32 | out ($FE), a | |
| 35 | 33 | |
jr c jumps when A was less than the value — that's the carry flag, the other half of a comparison. (We'll meet carry properly later; here, just use it.) Change the tested value to 3, 5 and 9 and watch the border go blue, green, red.
When it's wrong, see why
pasmonextsays a symbol is undefined. A label's spelling doesn't match between where you defined it and where you jump to it — most often a capital letter. Labels are case-sensitive:.equal≠.Equal.- The border is always the same colour whatever you load. The conditional jump's flag is wrong, or the
jr .holdthat skips the green branch is missing, so both branches run. Compare against the listing. - The program runs off and the screen garbles. A label is misplaced so a jump lands in the wrong spot. Check
.equaland.holdsit exactly where shown.
What you've learnt
There's no IF — you cp to compare (which sets flags), then jr z to jump on the result or carry straight on; every decision the machine makes is built from test-then-jump.
What's next
Decisions are far more useful when they're about the player. Next — The Machine Can Hear You — we read the keyboard: one key, one bit, and a jr that branches on whether it's held. The first time the machine reacts to you.