The 68000 is a 16-bit CPU that can do operations on 32-bit numbers. That usually should be enough, but in rare cases (e.g. a game with ultra-inflated scoring) you may want even larger and use 64-bit numbers. Luckily, the 68000 has some provisions to work with numbers of arbitrary sizes.
Yes, this is a 16-bit CPU with 32-bit instructions working with 64-bit numbers, because why not?
- The Extend flag
- Negation (flip sign)
- Boolean operations
- Extend 32-bit to 64-bit
- Even larger values
The Extend flag
Most 68000 instructions set the condition flags, which are used by branch instructions and such. One of those flags however is to allow working with large numbers: the Extend flag holds the carry or borrow of the last operation (note that this is not the same thing as the Carry or Overflow flags).
There are also a few "extended" instructions to go with it:
subx, etc. The way it works is that you start
the operation with the "normal" instruction (e.g.
the lower bits, then execute the "extended" instructions (e.g.
addx) on the higher bits. The Extend flag is used to pass carry
from the lower to the higher bits. You can perform operations on numbers
with arbitrary sizes this way.
Note that the Extend and Carry flags are not the same, and a
few instructions will affect them differently (e.g.
will clear Carry but not touch Extend). Extend is used for working
with large numbers, Carry is used for comparisons.
To add two 64-bit numbers, you need two instructions: first do
add.l on the low halves of both operands, then
addx.l on the high halves. The
addx instruction resumes the last
addition (in particular moving the carry from the low half to the high
half). Flags will be set accordingly after both instructions are
Since registers are only 32-bit, we need to use two registers to hold a
single 64-bit value. In this case,
hold the upper and lower halves of the destination operand, while
d3 hold the source operand:
add.l d3, d1 addx.l d2, d0
Most of the time you'll do the above.
You can also use
addx when both operands are in memory, in
this case two address registers hold pointers to the end of the
numbers (i.e. first byte after each of them). Numbers must be stored big
endian for this to work. See the example below — note that we need to
reset the flags manually since the
add instruction doesn't
support an operand combination like this:
lea 8(a0), a0 lea 8(a1), a1 and.b #$00, ccr addx.l -(a1), -(a0) addx.l -(a1), -(a0)
One big problem is that there's no immediate form for the
addx instruction, so if you have to add a fixed value you'll
need to store it in registers or memory. Note that the
moveq instructions don't change the Extend flag, so it's
safe to use between the two additions:
; Add 100 to 64-bit value in d1:d0 ; Assumes that d2 is free to use moveq #100, d2 add.l d2, d1 moveq #0, d2 addx.l d2, d0
Substraction is similar to addition, but using
sub.l d3, d1 subx.l d2, d0
Same limitations as
Negation (flip sign)
Negation is also similar to addition, but using
neg.l d1 negx.l d0
For the record, unlike
can actually use
negx with just about every valid
Here we run into a problem: there is not a
instruction, so the obvious approach doesn't work. If you want to do a
comparison, you'll need to waste a couple of registers (or 8 bytes in
memory) to do a substraction and ignore its result:
; assume d4-d5 are free move.l d1, d5 move.l d0, d4 sub.l d3, d5 subx.l d2, d4 ; flags will be set here
If you can discard at least one of the operands then you could instead store the result of the substraction there and avoid wasting anything else.
Another thing you can do if you need to jump away when both values are different, you may be able to get away by checking if either pair of halves is different:
cmp.l d3, d1 bne Different cmp.l d2, d0 bne Different
You can't do the reverse to check if they're identical (because one pair could be but not the other), but you could instead check if they're different to hop over the actual jump:
cmp.l d3, d1 bne.s Different cmp.l d2, d0 bne.s Different bra Identical Different:
If you can get away with just checking if a 64-bit number is positive or negative, you can test the sign of the upper half:
tst.l d0 bpl Positive
tst.l d0 bmi Negative
Boolean operations (
not) are not affected across bits, so you can apply them
directly on the individual halves. Just bear into mind that the flags
will only reflect the result of the last half you touch, so you may need
to compare the values manually if that's
and.l d3, d1 and.l d2, d0
or.l d3, d1 or.l d2, d0
eor.l d3, d1 eor.l d2, d0
not.l d1 not.l d0
Extend 32-bit to 64-bit
Even when working with 64-bit values, it's likely that most of the time
you'll be dealing with 32-bit ones. This means that sooner or later
you'll find yourself wanting to use a 32-bit number in a 64-bit operation
which means you'll need to convert it to 64-bit. These examples assume
that the value is in
d1 and will be extended to the
For unsigned numbers the upper half will be always 0, so:
moveq #0, d0
For signed numbers it's trickier: you need to make the upper half 0 if
it's positive or -1 if it's negative. One way to do this would be to
check the sign flag, then use the
smi instruction (which
sets a register to 0 when sign is positive or -1 when negative), then
extend the value into a longword.
tst.l d1 smi.b d0 ext.w d0 ext.l d0
To extend from 8-bit or 16-bit to 64-bit, first extend them to 32-bit as usual then do one of the above.
Even larger values
Do you need even larger values? In theory, you can use these instructions to work with arbitrarily large numbers (though the number of registers may be a problem, so you may need to work off memory).
The following adds two 128-bit numbers (one in
d0-d3 and the
add.l d7, d3 addx.l d6, d2 addx.l d5, d1 addx.l d4, d0
The following adds two 256-bit numbers in memory (pointed by
a1, respectively), since there's no way to
fit both of them in registers. Note that we need to clear the flags
manually because the
add instruction doesn't take this
lea 32(a0), a0 lea 32(a1), a1 and.b #$00, ccr addx.l -(a1), -(a0) addx.l -(a1), -(a0) addx.l -(a1), -(a0) addx.l -(a1), -(a0) addx.l -(a1), -(a0) addx.l -(a1), -(a0) addx.l -(a1), -(a0) addx.l -(a1), -(a0)
Finally, remember that these "extended" instructions come in byte, word and long variants, so you aren't restricted to sizes multiple of 32-bit.