← Back to Courses
module
4

Changing what happens next

Why this matters

Every program you have written so far runs in a straight line: first instruction to last, once, and then it stops. That was enough to print a word. It is not enough for anything else.

A game needs to do the same thing repeatedly - draw the screen, read the keys, move things, over and over. It needs to skip work that is not needed right now. Neither is possible while the processor only ever runs the next instruction along.

This section is the first tool for that: JP, which changes what happens next.

The Program Counter

You have already seen the thing JP works on.

At the end of every program, in the line of registers, there is a column marked PC:

 A  BC   DE   HL   PC   SZ-H-PNC
 5D 3D41 1EFF FB3E 0769 10010010

PC is the program counter - the address of the instruction the processor is about to carry out. It is how the machine keeps its place. After each instruction, the processor adds that instruction's length to PC, which is what makes a program run forwards through memory.

JP puts a different address into PC. That is the whole of it. The processor does not know it has jumped; it simply carries on from wherever PC now points.

Your first jump

Here is a program that needs one. Type it in as arrow.asm:

        ORG 256

        JP start        ; step over the data

arrow:  DEFB 93         ; right arrow - see Appendix I

start:  LD A,(arrow)    ; fetch that byte
        RST 8
        DEFB 158       ; ZOUTC
        RET

Run it and you get a right arrow on screen.

Two new things are happening.

arrow: and start: are labels. A label is a name for a place in your program. You put one in front of a line, ending in a colon, and afterwards you can refer to that place by name. The assembler works out what address it turned out to be.

LD A,(arrow) reads memory. The brackets mean "the contents of", so this loads the byte stored at arrow - the 93 - rather than the address itself. This is the distinction S3 mentioned and here it is doing real work.

And the jump is not decoration. Without it, the processor would reach DEFB 93 and try to run it. It has no way of knowing you meant that byte as data. Anything in the path of execution gets executed, so data that sits before the code that uses it has to be jumped over.

What the assembler did for you

Ask the assembler to show its working. From a terminal, in the folder with your file:

z80asm -l -o ARROW.COM arrow.asm

The -l prints a listing:

0100 c3 00 00           JP start
0103 5d         arrow:  DEFB 93
0104 3a 03 01   start:  LD A,(arrow)
0107 cf                 RST 8
0108 9e                 DEFB 158  ; ZOUTC
0109 c9                 RET
010a
 04 01

The left column is the address of each line. The next few bytes are what that line assembled to. Read it and several things fall out at once.

Instructions are different lengths. JP start is three bytes, RST 8 is one, LD A,(arrow) is three. This is why you cannot work out an address by counting lines - you have to count bytes, and the assembler is better at it than you are.

arrow: is at 0103, and start: is at 0104. Those are the addresses your labels turned into. Nothing about the names survives into the program; they exist only while assembling.

JP start shows c3 00 00 in the listing, but the finished program holds c3 04 01. start is defined further down than the jump that names it, so the address was not yet known when that line of the listing was printed. The 04 01 printed after the end of the file is the correction: the real target address, filled in once the assembler has seen the whole source. C3 is the instruction for jump; the two bytes after it are the address 0104 - and they are the wrong way round. The low byte comes first.

That last point is not a mistake in the listing. The Z80 stores every 16-bit value with its low byte first, an arrangement called little-endian. You will see it everywhere: addresses, counters, anything that does not fit in a single byte. Get used to reading 04 01 as 0104 now and it will stop surprising you later.

LD A,(arrow) shows the same thing: 3a 03 01 is the instruction 3A followed by the address 0103, low byte first, which is where arrow: is.

Rules for labels

There are only three, and the assembler enforces all of them.

The colon is required, and nothing may come between it and the name. loop: is a label. loop is not - the assembler will read it as an instruction it does not recognise. loop :, with a space before the colon, fails the same way.

No spaces inside the name. next item: is two words as far as the assembler is concerned, and it will reject the line.

Capitals matter. loop: and LOOP: are two different labels. Jumping to LOOP when you defined loop gives you error: unable to resolve reference, and the two can even coexist in one program as separate names - which is a good way to confuse yourself. Pick one style and keep to it.

That is the lot. Everything else is up to you:

counter:        DEC C           ; label and instruction on one line
        JR NZ,counter

verylongname:                   ; length is not limited
        NOP

spaced:                 NOP     ; spacing after the colon is free

Names can contain digits and underscores, and may even start with a digit. A label on its own line, with the instruction underneath, is the usual way to write a loop target - it makes the destination easy to spot when you are scanning for it.

The one thing worth doing that the assembler will not enforce: make the name say what the place is for. loop: and done: cost nothing to read. l1: and x: will mean nothing to you in a fortnight.

If you read Einstein code from elsewhere you may find much stricter label rules described - names of six characters or fewer, and exact spacing. Those belong to other assemblers, not this one. Appendix II covers the differences.

Jumping backwards

Nothing says a jump has to go forwards. Point one at a label above it and the program repeats:

        ORG 256

loop:   LD A,'*'
        RST 8
        DEFB 158       ; ZOUTC
        JP loop

Run it. Asterisks fill the screen and keep coming.

They keep coming because nothing stops them. JP loop is unconditional: it jumps every single time, so the program has no way out and never returns to the prompt. You will need to press Restart to get your machine back.

That is worth doing once deliberately, because it is going to happen to you by accident, and it is much less alarming when you recognise it.

This is also the limitation that shapes the next section. You can now repeat work, but only forever. To repeat something ten times, or to keep going until a key is pressed, the program has to be able to ask a question and jump only if the answer comes out a particular way.

Change one thing

  • Delete the JP start line from the arrow program and run it. Does the arrow still appear? Before you decide what that means, look at DE in the register line and compare it with what it read before. Something has changed. Working out what, and why, is the most useful thing in this section.
  • Put the DEFB 93 after the RET instead, and delete the jump. Does it work? Why does it not need a jump now?
  • In the arrow program, change LD A,(arrow) to LD A,arrow - no brackets. What gets printed, and what does that tell you about the difference between a value and an address?
  • In the loop, move loop: down one line so it sits on RST 8. What should happen now, and what does the screen fill with?

Exercises

4.1 - Three arrows. Codes 91, 93 and 94 are left, right and up arrows. Write a program that stores all three with DEFB, jumps over them, and prints them in order on one line.

4.2 - A pattern that never stops. Make a loop that prints two different characters each time round, so the screen fills with a repeating pattern rather than one character. Add a line break to the loop and see what changes.

4.3 - Read the map. Assemble one of your own programs with -l and find the address of every label in it. Then work out, by adding up the byte lengths in the listing, why each one landed where it did.

4.4 - Predict before you look. Write a short program with a label, and before assembling it, write down the address you think the label will get. Then check with -l. Repeat until you are right first time - it is a better test of whether you have understood this section than anything you can read.

When it goes wrong

What you see What it means
error: command or comment expected (was start LD A,1 ) A label without its colon. start: works, start does not.
error: unable to resolve reference: strat The label in your JP does not match the label you defined. Usually a spelling difference - or a difference in capitals, which counts.
The program never comes back and the screen fills A loop with no way out. Press Restart.
The program prints nothing and returns immediately Something is being executed that you meant as data, or a jump is landing past the part that prints. Check the listing for where your labels actually are.
The right output, but a register you never used has changed A data byte in the execution path was run as an instruction. It did no visible harm this time. It is still a bug.

Summary

  • PC, the program counter, holds the address of the next instruction. JP writes to it.
  • A label names a place in your program. It must end in a colon with no space before it, must not contain spaces, and is case-sensitive. The assembler turns it into an address.
  • JP <label> jumps forwards or backwards. A backward jump repeats, and an unconditional one repeats forever.
  • Anything in the path of execution is executed, including data. Jump over data, or put it after the last instruction that can be reached.
  • z80asm -l shows the address and bytes of every line. Instructions are different lengths, so count bytes and not lines.
  • 16-bit values are stored low byte first. c3 04 01 is a jump to 0104.

Next

S5. Jumping without saying where. There is a second jump instruction that is smaller than JP and does not name an address at all - which turns out to matter more than it sounds.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.