Jumps
Once this lesson is done, we will be able to understand all of CopyTiles
!
So far, all the code we have seen was linear: it executes top to bottom. But this doesn’t scale: sometimes, we need to perform certain actions depending on the result of others (“if the crêpes start sticking, grease the pan again”), and sometimes, we need to perform actions repeatedly (“If there is some batter left, repeat from step 5”).
Both of these imply reading the recipe non-linearly. In assembly, this is achieved using jumps.
The CPU has a special-purpose register called “PC”, for Program Counter. It contains the address of the instruction currently being executed1, like how you’d keep in mind the number of the recipe step you’re currently doing. PC increases automatically as the CPU reads instructions, so “by default” they are read sequentially; however, jump instructions allow writing a different value to PC, effectively jumping to another piece of the program. Hence the name.
Okay, so, let’s talk about those jump instructions, shall we? There are four of them:
Instruction | Mnemonic | Effect |
---|---|---|
Jump | jp | Jump execution to a location |
Jump Relative | jr | Jump to a location close by |
Call | call | Call a subroutine |
Return | ret | Return from a subroutine |
We will focus on jp
for now.
jp
, such as the one line 5, simply sets PC to its argument, jumping execution there.
In other words, after executing jp EntryPoint
(line 5), the next instruction executed is the one below EntryPoint
(line 16).
🤔
You may be wondering what is the point of that specific jp
.
Don’t worry, we will see later why it’s required.
Conditional jumps
Now to the really interesting part. Let’s examine the loop responsible for copying tiles:
; Copy the tile data
ld de, Tiles
ld hl, $9000
ld bc, TilesEnd - Tiles
CopyTiles:
ld a, [de]
ld [hli], a
inc de
dec bc
ld a, b
or a, c
jp nz, CopyTiles
Don’t worry if you don’t quite get all the following, as we’ll see it live in action in the next lesson. If you’re having trouble, try going to the next lesson, watch the code execute step by step; then, coming back here, it should make more sense.
First, we copy Tiles
, the address of the first byte of tile data, into de
.
Then, we set hl
to $9000, which is the address where we will start copying the tile data to.
ld bc, TilesEnd - Tiles
sets bc
to the length of the tile data: TilesEnd
is the address of the first byte after the tile data, so subtracting Tiles
to that yields the length.
So, basically:
de
contains the address where data will be copied from;hl
contains the address where data will be copied to;bc
contains how many bytes we have to copy.
Then we arrive at the main loop.
We read one byte from the source (line 29), and write it to the destination (line 30).
We increment the destination (via the implicit inc hl
done by ld [hli], a
) and source pointers (line 31), so the following loop iteration processes the next byte.
Here’s the interesting part: since we’ve just copied one byte, that means we have one less to go, so we dec bc
.
(We have seen dec
two lessons ago; as a refresher, it simply decreases the value stored in bc
by one.)
Since bc
contains the amount of bytes that still need to be copied, it’s trivial to see that we should simply repeat the operation if bc
!= 0.
😓
dec
usually updates flags, but unfortunately dec bc
doesn’t, so we must check if bc
reached 0 manually.
ld a, b
and or a, c
“bitwise OR” b
and c
together; it’s enough to know for now that it leaves 0 in a
if and only if bc
== 0.
And or
updates the Z flag!
So, after line 34, the Z flag is set if and only if bc
== 0, that is, if we should exit the loop.
And this is where conditional jumps come into the picture! See, it’s possible to conditionally “take” a jump depending on the state of the flags.
There are four “conditions”:
Name | Mnemonic | Description |
---|---|---|
Zero | z | Z is set (last operation had a result of 0) |
Non-zero | nz | Z is not set (last operation had a non-zero result) |
Carry | c | C is set (last operation overflowed) |
No carry | nc | C is not set (last operation did not overflow) |
Thus, jp nz, CopyTiles
can be read as “if the Z flag is not set, then jump to CopyTiles
”.
Since we’re jumping backwards, we will repeat the instructions again: we have just created a loop!
Okay, we’ve been talking about the code a lot, and we have seen it run, but we haven’t really seen how it runs. Let’s watch the magic unfold in slow-motion in the next lesson!
Not exactly; instructions may be several bytes long, and PC increments after reading each byte. Notably, this means that when an instruction finishes executing, PC is pointing to the following instruction. Still, it’s pretty much “where the CPU is currently reading from”, but it’s better to keep it simple and avoid mentioning instruction encoding for now.