So far, all programs we have seen executed from top to bottom, and there weren't any conditions. It's time to change that!
Let's quickly talk about how the CPU processes instructions. The CPU has another 16-bit register I didn't talk about yet, which is called
pc, the program counter. It's simply the memory address where the CPU is reading instructions from. When the CPU executes an instruction, the following happens:
- The byte
[pc]is read (this means that if PC = $1234, the byte at $1234 is read)
- That byte is decoded so the CPU knows which instruction it should execute
- If the instruction has operand byte(s), it is/they are read as well, one by one, and
pcis incremented after each read
- The instruction is executed proper
So far, execution has been linear - from $0000 to $FFFF. However, some instructions modify
pc! There are five such instructions:
ret. The latter three make use of the stack, which is a concept we will see later. For now, we will focus on
A CPU on a pogo stick
jp lets execution "jump" (that's how it's called) to another place in memory.
jp $1234 simply sets
pc to $1234 -- if you want, it's
ld pc, $1234, although that syntax is invalid :3
This essentially enables execution to flow in any order, which resolves the linearity problem. But not the conditional problem. Fear not! For
jp is an instruction that can be executed conditionally! Depending on what, you may ask? Depending on our trusty flags.
There are four conditions available, which act depending on two flags: C and Z.
|No carry||nc||Carry reset|
Remember that labels are references to memory locations, which make them very common operands for
jp. Here's a small code snippet that uses several things we've seen so far:
ld a, [wPlayerHP] and a ; Is it 0? jp z, GameOver ; If HP hit 0, play Game Over animation cp 10 ; Is it under 10 HP? jr nc, .noLowHealthBeep ; If above 10 HP, don't beep ; We're going to see just below that jr is essentially the same as jp ... .noLowHealthBeep
Short, but fast
Now, we saw how
jp works. But what about
jr? It also performs jumps, but they're relative. Relative to what? To
pc! Instead of specifying the destination address,
jr takes one byte as its operand, the offset, which is added to
pc to determine the target destination.
Pro: smaller and faster
Con: can only go 128 bytes backwards or 127 bytes forwards
As for the syntax... it's the same as
jp! The nice part about the assembler is that it computes the offset for you, based on where the instruction and the target will be located. If the offset cannot be encoded as a
jr, then it will simply complain and error out. I recommend using
jr by default, and
jp when it can't be used.
And by the way,
jr supports the same conditions as
Does it all make sense so far? We are very close to making our Hello World, don't worry. All that's left for us to figure out is how to draw things to the screen...