Introducing the Instruction Set Part 3
This segment of the tutorial introduces branches, particularly conditional branches and function calls. This is Part 4 of a series. If you haven't yes, you may wish to review at least Part 1 and Part 2.
Contents
Unconditional Branches and Jumps
Unconditional branches are branches that are always taken. Jump instructions do essentially the same thing. The following table lists the instructions:
Mnemonic | Description | Cycles | Size |
---|---|---|---|
B | Branch to label | 9 | 2 words |
J | Jump to label | 12 | 3 words |
JD | Jump to label while disabling interrupts | 12 | 3 words |
JE | Jump to label while enabling interrupts | 12 | 3 words |
As you can see, the primary difference between branches and jumps is that branches are smaller and faster. Branches encode their "target address," the address being jumped to, as a relative displacement to the current address. Jumps, on the other hand, store the actual address of the target. In most cases, especially in a 16-bit ROM, there are few reasons to use a J instruction, although the combination instructions, JD and JE can be useful.
There is also a pseudo-instruction, JR, that allows "jumping to a location held in a register." It is really a pseudonym for "MOVR Rx, R7". Because it is a MOVR instruction, it will modify the Sign Flag and Zero Flag, which may be confusing if you're not expecting it. Otherwise, it is an efficient method for jumping to an address held in an register, such as when returning from a CALL.
Conditional Branches
The CP1610 has a rich set of conditional branch instructions. These branches work in concert with instructions that modify the CPU's flags in order to implement various constructs, such as if-then-else, loops, and so on. The following table summarizes the conditional branches.
Mnemonic | Name | Branch taken when... | Mnemonic | Name | Branch taken when... |
---|---|---|---|---|---|
BC | Branch on Carry | C = 1 | BNC | Branch on No Carry | C = 0 |
BOV | Branch on OVerflow | OV = 1 | BNOV | Branch on No OVerflow | OV = 0 |
BPL | Branch if PLus | S = 0 | BMI | Branch on MInus | S = 1 |
BEQ | Branch if EQual | Z = 1 | BNEQ | Branch on Not Equal | Z = 0 |
BZE | Branch on ZEro | BNZE | Branch on Not ZEro | ||
BLT | Branch if Less Than | S <> OV | BGE | Branch if Greater than or Equal | S = OV |
BNGE | Branch if Not Greater than or Equal | BNLT | Branch if Not Less Than | ||
BLE | Branch if Less than or Equal | Z = 1 OR S <> OV | BGT | Branch if Greater Than | Z = 0 AND S = OV |
BNGT | Branch if Not Greather Than | BNLE | Branch if Not Less than or Equal | ||
BUSC | Branch on Unequal Sign and Carry | S <> C | BESC | Branch on Equal Sign and Carry | S = C |
Conditional branches are most often used with numeric comparisons, or as the loop-closing branch. The following sections illustrate how numeric comparisons work in concert with branches.
Conditional branches can also be paired with other instructions that manipulate the flags. For instance, shift instructions, as described in Part 4 update sign, zero, carry and overflow flags depending the operation performed. This can lead to interesting and creative combinations of shifts and branches.
Another use of flags and branches is to pass status information in CPU flags (such as the Carry Flag) and then act on that information later. The SETC and CLRC instructions make it easy to manipulate the Carry Flag to pass this status information around.
Signed Comparisons
The following branches are particularly useful when comparing signed numbers:
Mnemonic | Branch taken when... | Mnemonic | Branch taken when... | ||
---|---|---|---|---|---|
BEQ | BZE | Z = 1 | BNEQ | BNZE | Z = 0 |
BLT | BNGE | S <> OV | BGE | BNLT | S = OV |
BLE | BNGT | Z = 1 OR S <> OV | BGT | BNLE | Z = 0 AND S = OV |
BOV | OV = 1 | BNOV | OV = 0 |
The compare instruction compares two numbers by subtracting them, and then setting the flags based on the result. This provides a lot of information about the relative values of the two numbers, as this table shows (ignoring overflow):
If this is true... | ...then this also must be true... | ...which implies the flags get set as follows (if you ignore overflow). | |
---|---|---|---|
x = y | x - y = 0 | S = 0 | Z = 1 |
x < y | x - y < 0 | S = 1 | Z = 0 |
x > y | x - y > 0 | S = 0 | Z = 0 |
That is, we can determine whether two numbers are equal or not by looking at the Z bit. We can determine if one's less than the other by looking at the sign bit. At least, that would be true if there was no such thing as overflow.
The machine has a limited word width though. If you try to subtract two numbers whose values are very far apart, such as, in the worst case 32767 - (-32768), you will trigger an overflow. Overflow causes the sign of the result to be the opposite of what you would get if no overflow had occurred. The branch hardware takes this into account and looks at the overflow bit in addition to the sign bit to decide whether one number is greater than or less than another. The following table illustrates the relationships both with and without overflow.
If this is true... | ...then the flags get set as follows... | ...which matches these branches. | ||
---|---|---|---|---|
x = y | S = 0 | Z = 1 | OV = 0 | BEQ, BGE, BLE |
S = 1 | Z = 1 | OV = 1 | ||
x < y | S = 1 | Z = 0 | OV = 0 | BNEQ, BLT, BLE |
S = 0 | Z = 0 | OV = 1 | ||
x > y | S = 0 | Z = 0 | OV = 0 | BNEQ, BGT, BGE |
S = 1 | Z = 0 | OV = 1 |
As you can see, the flag behavior dovetails nicely into the set of branches the CPU provides.
The syntax for the CP1610's compare instruction can confuse things slightly, since it does a "subtract from". Consider the following example:
MVII #1, R0 ; R0 = 1 MVII #2, R1 ; R1 = 2 CMPR R0, R1 ; Subtract R0 from R1 to set flags BLT label ; Is this taken?
This computes "R1 - R0", not "R0 - R1". It compares R0 to R1 by subtracting R0 from R1. In this example, that leaves S=0 and OV=0. R1 is not less than R0, so the branch is not taken, which matches our flags. BLT only gets taken if S=1 and OV=0 or vice versa.
Separate from all that: One pair of branches shown above—BOV and BNOV—are useful in this context primarily just for detecting overflow and little else. I included them here for completeness. These actually find more use paired up with shift instructions. Those are described Part 4 of this tutorial.
Unsigned Comparisons
These branches are useful when comparing unsigned numbers, including pointers to memory:
Mnemonic | Branch taken when... | Mnemonic | Branch taken when... | ||
---|---|---|---|---|---|
BC | C = 1 | BNC | C = 0 | ||
BEQ | BZE | Z = 1 | BNEQ | BNZE | Z = 0 |
Sign/Zero Comparisons
Common looping instructions, such as DECR and INCR only modify the sign and zero flags without updating the carry or overflow flags. These are best used with the following branches:
Mnemonic | Branch taken when... | Mnemonic | Branch taken when... | ||
---|---|---|---|---|---|
BPL | S = 0 | BMI | S = 1 | ||
BEQ | BZE | Z = 1 | BNEQ | BNZE | Z = 0 |
If-Then and If-Then-Else
Looping
Function Calls
Simple Call/Return
Nested Call/Return
Passing Arguments via Return Address
Indirect Branches and Jump Tables
"It was a 'Jump to Conclusions' mat. You see, it would be this mat that you would put on the floor... and would have different conclusions written on it that you could jump to." -- Tom Smykowski, Office Space
Indirect Branching: "Jump Vectors"
Simple Jump Tables
Adding to the Program Counter
Moving On
At this point, you may wish to move along to the last part, or review earlier parts of this tutorial:
- Introducing the Instruction Set Part 1: The CPU, Memory and Registers; Primary Instructions and Addressing Modes
- Introducing the Instruction Set Part 2: Single Register and Implied Operand Instructions
- Introducing the Instruction Set Part 4: Shift and Rotate Instructions
Or, you can return to the Programming Tutorials index.