Difference between revisions of "Add 8-bit to 16-bit"
m (→Signed addition: remove something added in a draft that turned out to be a duplicate) |
(→With 16-bit operations: unsigned addition was miscounted) |
||
(7 intermediate revisions by 2 users not shown) | |||
Line 11: | Line 11: | ||
== Unsigned addition == | == Unsigned addition == | ||
− | Adding A to HL is kind of tricky (you'll probably understand it better from the comments in the code snippet below). We first add the low byte. Then we add the high byte with carry and | + | Adding A to HL is kind of tricky (you'll probably understand it better from the comments in the code snippet below). We first add the low byte. Then we add the high byte with carry and subtract the low byte to get the high byte's result (which is high byte + carry) in 5 bytes and 5 cycles. |
We'll build on the code below for the other methods. | We'll build on the code below for the other methods. | ||
Line 23: | Line 23: | ||
== Signed addition == | == Signed addition == | ||
− | Signed addition is similar, except we "extend" A to 16-bit. The "high byte" of A is always either 0 (if positive) or -1 (if negative), so we check A's sign and adjust H accordingly. After this we add the lower byte as with unsigned addition. | + | Signed addition is similar, except we "extend" A to 16-bit. The "high byte" of A is always either 0 (if positive) or -1 (if negative), so we check A's sign and adjust H accordingly. After this we add the lower byte as with unsigned addition, with a result in 10 bytes and 10 cycles. |
; If A is negative, we need to subtract $100 from HL | ; If A is negative, we need to subtract $100 from HL | ||
Line 39: | Line 39: | ||
sub l | sub l | ||
ld h, a | ld h, a | ||
+ | |||
+ | If you have an 8-bit register to spare, you can use this smaller (-2 bytes) and faster (-2 cycles) version, where R is the "temporary" register: | ||
+ | |||
+ | ld R, a | ||
+ | ; Begin addition as usual... | ||
+ | add a, l | ||
+ | ld l, a | ||
+ | adc a, h | ||
+ | ; ...but check the sign now, and subtract 1 if the sign was negative | ||
+ | rl R | ||
+ | sbc l | ||
+ | ld h, a | ||
+ | (If the value added to the 16-bit register was already in a 8-bit register other than A, you can skip the initial "ld R, a" and save 1 extra byte and cycle.) | ||
== Unsigned subtraction == | == Unsigned subtraction == | ||
Line 44: | Line 57: | ||
The idea behind this is that HL - A is the same thing as HL + (-A), so we flip A's sign then perform an addition. The catch is that A's "upper byte" becomes -1, so we need to subtract 1 from HL's upper byte to account for this. Think of this as a specialized variant of signed addition. | The idea behind this is that HL - A is the same thing as HL + (-A), so we flip A's sign then perform an addition. The catch is that A's "upper byte" becomes -1, so we need to subtract 1 from HL's upper byte to account for this. Think of this as a specialized variant of signed addition. | ||
− | The | + | The CPL instruction calculates $FF - A, and SCF ADC to add 1 afterward makes it $100 - A. (This is different from INC A ADD in the way the carry is generated) |
− | + | ; Add 256 - A to HL | |
− | + | ||
− | + | ||
cpl | cpl | ||
− | + | scf | |
− | + | adc a, l | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
ld l, a | ld l, a | ||
+ | ld a, -1 ; And subtract 256 here | ||
adc a, h | adc a, h | ||
− | |||
ld h, a | ld h, a | ||
− | |||
== Signed subtraction == | == Signed subtraction == | ||
− | Signed subtraction is trivial to build on top of signed addition: flip A's sign then do the addition. The only catch is that we need to handle that -$80 should become +$80. and the obvious way to do that on 8080 uses the overflow flag, which isn't present on the SM83. | + | Signed subtraction is trivial to build on top of signed addition: flip A's sign then do the addition in 12 bytes and 12 cycles (minus 1 cycle if positive). The only catch is that we need to handle that -$80 should become +$80. and the obvious way to do that on 8080 uses the overflow flag, which isn't present on the SM83. |
; Values $00-$7F mean subtract $0000-$007F or add $0000-$FF81 | ; Values $00-$7F mean subtract $0000-$007F or add $0000-$FF81 | ||
Line 103: | Line 105: | ||
* 8080 and SM83 lack 16-bit subtraction, having only addition. (Even though Z80 adds <code>sbc hl</code>, there is no dedicated opcode for <code>sub hl</code>.) | * 8080 and SM83 lack 16-bit subtraction, having only addition. (Even though Z80 adds <code>sbc hl</code>, there is no dedicated opcode for <code>sub hl</code>.) | ||
− | Unsigned addition: | + | Unsigned addition (4 bytes, 5 cycles, DE clobbered): |
ld e, a ; DE = A | ld e, a ; DE = A | ||
Line 109: | Line 111: | ||
add hl, de ; HL = HL+DE | add hl, de ; HL = HL+DE | ||
− | Signed addition: | + | Signed addition (5 bytes, 6 cycles, DE clobbered): |
ld e, a ; DE = A | ld e, a ; DE = A |
Latest revision as of 20:17, 31 March 2022
Sometimes you need to add an 8-bit value (in A) to a 16-bit one (in either HL, BC or DE). You can extend the value to 16-bit, but it can get cumbersome as you may need to clobber registers you're using and possibly move values around (since only HL can be a destination).
Here we show how to do this without resorting to using 16-bit operations (which can avoid all those issues). Note that here "unsigned" and "signed" refers to the 8-bit value in A.
Some notes:
- In all these cases, only A gets clobbered.
- In all these cases, you can replace HL with BC or DE.
- You really should consider wrapping these into macros.
Contents
Unsigned addition
Adding A to HL is kind of tricky (you'll probably understand it better from the comments in the code snippet below). We first add the low byte. Then we add the high byte with carry and subtract the low byte to get the high byte's result (which is high byte + carry) in 5 bytes and 5 cycles.
We'll build on the code below for the other methods.
add a, l ; A = A+L ld l, a ; L = A+L adc a, h ; A = A+L+H+carry sub l ; A = H+carry ld h, a ; H = H+carry
Signed addition
Signed addition is similar, except we "extend" A to 16-bit. The "high byte" of A is always either 0 (if positive) or -1 (if negative), so we check A's sign and adjust H accordingly. After this we add the lower byte as with unsigned addition, with a result in 10 bytes and 10 cycles.
; If A is negative, we need to subtract $100 from HL ; (since A's "upper byte" is $FF00) cp $80 jr c, .positive dec h .positive: ; Then do addition as usual ; (to handle the "lower byte") add a, l ld l, a adc a, h sub l ld h, a
If you have an 8-bit register to spare, you can use this smaller (-2 bytes) and faster (-2 cycles) version, where R is the "temporary" register:
ld R, a ; Begin addition as usual... add a, l ld l, a adc a, h ; ...but check the sign now, and subtract 1 if the sign was negative rl R sbc l ld h, a
(If the value added to the 16-bit register was already in a 8-bit register other than A, you can skip the initial "ld R, a" and save 1 extra byte and cycle.)
Unsigned subtraction
The idea behind this is that HL - A is the same thing as HL + (-A), so we flip A's sign then perform an addition. The catch is that A's "upper byte" becomes -1, so we need to subtract 1 from HL's upper byte to account for this. Think of this as a specialized variant of signed addition.
The CPL instruction calculates $FF - A, and SCF ADC to add 1 afterward makes it $100 - A. (This is different from INC A ADD in the way the carry is generated)
; Add 256 - A to HL cpl scf adc a, l ld l, a ld a, -1 ; And subtract 256 here adc a, h ld h, a
Signed subtraction
Signed subtraction is trivial to build on top of signed addition: flip A's sign then do the addition in 12 bytes and 12 cycles (minus 1 cycle if positive). The only catch is that we need to handle that -$80 should become +$80. and the obvious way to do that on 8080 uses the overflow flag, which isn't present on the SM83.
; Values $00-$7F mean subtract $0000-$007F or add $0000-$FF81 ; Values $80-$FF mean subtract $FF80-$FFFF or add $0080-$0001 ; First begin to negate A (since HL - A is the same as HL + -A) ; A will be incremented later. cpl ; Values $00-$7F mean add $0001-$0080 ; Values $80-$FF mean add $FF81-$0000 ; So if value at least $80, add $FF00 cp $80 jr c, .positive dec h scf .positive: ; Now add A + 1 to HL adc a, l ld l, a adc a, h sub l ld h, a
With 16-bit operations
For the sake of completeness, since if the stars align right somehow (i.e. values happen to be in the right registers) these may be more useful. Also they're more compact, if space usage matters more than speed in a particular case. The additions work by sign-extending an 8-bit value to 16-bit and then adding it to HL.
Sik finds these not ideal for several reasons:
- Destination must be HL, which is more important on 8080/SM83 than on Z80 because of its use to step through an array.
- You must clobber BC or DE.
- 8080 and SM83 lack 16-bit subtraction, having only addition. (Even though Z80 adds
sbc hl
, there is no dedicated opcode forsub hl
.)
Unsigned addition (4 bytes, 5 cycles, DE clobbered):
ld e, a ; DE = A ld d, 0 add hl, de ; HL = HL+DE
Signed addition (5 bytes, 6 cycles, DE clobbered):
ld e, a ; DE = A add a, a ; Bit 7 into carry sbc a, a ; $00 if no carry, $FF if carry ld d, a add hl, de ; HL = HL+DE
Unsigned subtraction: To be written
Signed subtraction: To be written
External links
- Adapted from Z80 Adding 8-bit to 16-bit by Sik, with permission