.png)
JP works. It has two costs, though, and both matter in a game.
It takes three bytes every time you use one, and a game loop is nothing but jumps. And it names an exact address, which means the program only works if it is sitting exactly where the assembler thought it would be.
JR avoids both. For most of the jumps you will ever write, it is the one to
reach for.
JR stands for jump relative. Rather than naming the address to go to, it
says how far to move:
JR done
You write it exactly like JP, with a label, and the assembler works out the
distance. The difference is in what ends up in your program.
Here is the section's example. Type it in as jr.asm:
ORG 256
LD A,'J'
RST 8
DEFB 158 ; ZOUTC
JR done ; step over the data
DEFB 118 ; skipped
done: LD A,'R'
RST 8
DEFB 158 ; ZOUTC
RET
What you should see
>JR
A BC DE HL PC SZ-H-PNC
52 3D41 1EFF FB3E 0769 10010010
Now look at what it assembled to:
0100 3e 4a LD A,'J'
0102 cf RST 8
0103 9e DEFB 158 ; ZOUTC
0104 18 00 JR done
0106 76 DEFB 118
0107 3e 52 done: LD A,'R'
0109 cf RST 8
010a 9e DEFB 158 ; ZOUTC
010b c9 RET
010c
01
JR done is two bytes: 18 then a one-byte distance. The listing shows
18 00 for the same reason a forward JP shows c3 00 00 - the distance was
not yet known when that line was printed. The correction, 01, appears after
the end of the file, and that is the byte the finished program actually holds:
18 01.
The 01 is the distance. It is counted from 0106, the address of the
instruction after the jump, so 0106 + 1 = 0107, which is done:. That is
worth remembering when you read a listing: the offset is measured from where
the processor will be once it has finished reading the jump, not from the jump
itself.
Jumping backwards gives a negative distance, stored the way the Z80 stores
negative numbers - so a jump back four bytes assembles as 18 fc. You do not
need to work those out. The assembler does, and it is the whole reason to use
labels.
It would be easy to conclude that labels are what JR buys you. They are not -
JP done takes a label just as happily, and you used one in the last section.
The two real reasons are worth being clear about.
Two bytes against three. That sounds trivial, and for one jump it is. But you have 64 kilobytes for everything - code, graphics, sound, level data - and a game is full of short jumps. Replacing a hundred of them saves a hundred bytes, which is a sprite. Habits at this scale are what make a program fit.
This is the more interesting one, and it is easiest to see by breaking something.
.COM programs load at address 256, which is why every program so far has
started with ORG 256. Change that line to ORG 512 - lie to the assembler
about where the program will be - and run it again.
The version above, with JR, still prints JR and returns perfectly.
Now change the JR done to JP done and run that. You get:
>J
A BC DE HL PC SZ-H-PNC
41 3D41 1EFF FB3E 0208 10010010
Only the J. The R never printed, and PC reads 0208.
That number is the whole story. done: really sits at 0108. The assembler,
told the program would be at 512, worked out 0208 instead - 256 too high,
exactly the size of the lie. JP went where it was told, which was into
memory that has nothing to do with your program.
The JR version did not care, because "move forward one byte" is true no
matter where the program is loaded.
You will not often lie about ORG on purpose. But code does get moved,
copied to a different address, or reused somewhere it was not written for, and
relative jumps carry on working when absolute ones quietly do not.
A relative jump has to fit its distance into a single byte, and that byte is signed - so it can reach 127 bytes forward or 128 back, and no further.
Go beyond that and the assembler stops you:
JR far
DEFS 200 ; reserve 200 bytes
far: RET
error: relative jump out of range (200)
DEFS 200 reserves 200 bytes of space, which is a useful thing in its own
right and here is a convenient way to push a label out of reach. The error
names the distance it could not encode, so you always know how far over you
are.
When that happens, use JP. That is what it is for: the long jumps, and the
rare occasions when you genuinely mean one specific address.
In practice: reach for JR first. If the assembler complains, change that
one to JP. You will find almost every jump in a loop is comfortably in
range, because loops are small.
ORG 256 to ORG 512 in the JR version. It still works. Now
change the JR to a JP and check PC in the register line. Where did it
go, and can you predict that number before you run it?JR done to JR 999. What does the assembler say?DEFS 150 between the JR and done:. Read the error, then change the
JR to a JP and watch the same program assemble.JP loop to
JR loop. Does anything about the behaviour change? Should it?5.1 - Every jump relative. Rewrite exercise 4.1, the three arrows, using
JR for every jump. Assemble with -l and check that each one came out as
two bytes.
5.2 - Find the boundary. Using DEFS, find the largest gap a forward JR
will cross before the assembler refuses. Then do the same backwards. Do the
two numbers agree with the limit given above?
5.3 - Read the offsets. Write a program with three JRs - one forwards,
one backwards, one to the very next instruction. Work out each offset by hand
from the listing addresses, then check them against the bytes the assembler
produced.
5.4 - Move it. Write a short program with one JR and one JP, both
jumping to labels. Assemble it at ORG 256 and confirm it works. Then change
only the ORG and predict which of the two jumps breaks and what PC will
read when it does.
| What you see | What it means |
|---|---|
error: relative jump out of range (200) |
The target is further than a JR can reach. Use JP for that one. The number is the distance it could not encode. |
error: unable to resolve reference: done |
The label in the jump does not match the label you defined. |
| Part of the output appears, then the program stops early | A jump landed somewhere unintended. Check PC in the register line - it is the address it stopped at, and comparing it with the listing usually names the problem instantly. |
The program works, but PC reads an address nowhere near your program |
An absolute jump built for the wrong ORG. Check the top line of your source. |
JR jumps by a distance rather than to an address. Two bytes against three.JR reaches 127 bytes forward and 128 back. Beyond that the assembler
refuses and names the distance, and you use JP instead.JR first, and let the assembler tell you when it will not fit.S6. Asking questions. Every loop you can write so far runs forever. Next the program compares two values and jumps only if the answer comes out a particular way, which turns a loop into something that finishes - and finally explains that last column of the register line.