Arithmetic is essential to any programming language;
almost every application requires the use of simple arithmetic functions.
All of the Z80’s 8-bit arithmetic instructions operate using the A register.
These instructions are: add (add), adc (add with carry),
sub (subtract), sbc (subtract with carry), AND (logical
and), OR (logical or), XOR (exclusive or).
Add will add the instruction operand to the A register,
storing the result in A. <add a,b>, <add a,(hl)>, <add a,$10>.
The flags affected are the carry (set if the addition results in a number
larger than 255, otherwise reset); the zero (set if the result is zero,
otherwise reset); the sign (set if the resulting bit 7 is set, otherwise
reset); parity / overflow (set if overflow -- not important).
Subtract is similar except that A isn’t written
explicitly as the operator. <sub 32>, <sub c>, <sub (ix)>.
Each of these subtract the operand from the A register. The carry
is set if a borrow is needed (if the operand is greater than A), otherwise
reset (if the operand is less than or equal to A). The zero flag
is set if the resulting value is zero (operand and A are equal), otherwise
reset (not equal). The sign flag is set and reset under the same
circumstances as the carry flag (result positive or negative).
The idea of adding and subtracting with a carry
is useful in situations where you are combining registers unconventionally,
forming 24-bit and 32-bit register aggregations. Part of these instructions
input is the carry flag: if the carry is set, then <adc a,7> will add
one to whatever the result is; <add hl,$f000 / adc a,0> will handle
ahl as a 24-bit register troika (not very uncommon, as you will see).
Add with carry adds the operand and the carry flag
to A. Flags are set identically to the add instruction in any condition.
Subtract with carry (sbc) subtracts the operand
and the carry flag from A. Flags are set identically to the sub instruction
in any condition.
ANDing, ORing and XORing must be done on the binary
level. A logical AND takes two binary numbers and sets the resulting
bit iff the same bit is set in both the operand and the operator.
For instance, <and c> will the have the following result with %11010110
in A and %01000101 in C.
|
|
|
|
AND is used most often as a bitmask.
If you only care about, say, the bottom four bits of (hl), then <ld
a,%1111 / and (hl)>. It will and A with (HL), storing the resulting
number to A. If (HL) is %10110010, then:
|
|
|
|
If you want the result to be remembered in (HL),
then you need an additional <ld (hl),a>.
OR is similar, save that it only sets the resulting
bit iff the same bit is set in either the operand or the operator.
<or $30> will have the following result when A is %10110001:
|
|
|
|
Exclusive OR is the same as OR, except that
if both bits are set, then the resulting bit will be reset.
XOR is useful in toggling between bits or bytes. If you have a bit
in a number that unknown, then you can use XOR to complement the bit.
<ld a,%1000 / xor (iy+5) / ld (iy+5),a> will complement bit 3 of (iy+5).
Bit 3 is set in a and XORed with (iy+5), loading the result back into (iy+5).
These three instructions will toggle the text inverse flag:
%11101011 |
%00001000 |
xor: |
%11100011 |
Bit toggling can be accomplished in this way and
byte toggling can be done similarly. Suppose that it’s necessary
to switch between two numbers each time a certain portion of code is executed.
There will always be some value that can be XORed against one of the numbers,
which will derive the other. For instance, if you wanted to switch
between $3c and $0a, you could use %110110 as the toggle byte:
%00111100 ($3c) |
%00110110 (toggle byte) |
xor: |
%00001010 ($0a) |
%00001010 ($0a) |
%00110110 (toggle byte) |
xor: |
%00111100 ($3c) |
AND, OR and XOR will affect the flags in the following manner: the zero flag is set iff the result of the operation is zero; the sign flag is set if the bit 7 of the result is set; the carry flag is always reset; the parity / overflow flag is set iff the parity is even.
The Z80 also has arithmetic instructions that handle
16-bit numbers. These instructions are add, adc and
sbc and require HL as the operator: <add hl,de>, <adc
hl,$fc00>, <sbc hl, bc>.
You can never use IX or IY in the same instruction
as HL (because the opcodes add $dd or $fd, if you recollect). But
you can, however, change any instruction that uses HL to use IX and IY:
<add ix,de>, <sbc iy,6> are permissible, but you cannot do
<adc ix,hl>. Also, SP is a valid operand for all 16-bit arithmetic
instructions: <add ix,sp>, <add hl,sp>. You can load a register
pair with SP in this way: <ld ix,0 / add ix,sp>. Also these three
instructions do exactly what you would expect with the flags, except add,
which will set the carry iff there is a carry, but affecting no other flags.
One last thing you may notice is that there is no 16-bit sub
instruction. Since AND and OR reset the carry, though, you can do:
<or a / sbc hl,de>. ANDing or ORing A with itself has no affect
on the A register, but always resets the carry.
Here's an example of a call that will determine whether
the square on a chess / checker board (numbered 0-63, starting at the top
left) is white or black, while at the same time returning the row and column
number of that square in registers B and C:
return_info:
and %111111 ;interest in only bottom six bits of A
(0-63); modulus 63
ld b,a ;load a into b, a and b are now both
the same
srl b ;divide b by 2
srl b ;divide b by 2 again
srl b ;and again for a result of b = original
b / 8
;the original number between 0 and 63, or %0 and %111111 is now a number
between 0 and 7.
;Because of the special way binary works, this number is the row number
of the number we
;started with (still in A). The third bit of A will be incremented
every 8th increment, making the
;third bit and the two bits above it representive of the row and bits
0,1 and 2 representive of the
;column. By dividing by 8, we put bits 3,4 and 5 into where bits
0,1 and 2 were (every division by
;two shifts all bits one to the right).
and %111
;the original number in A is now only the bottom three bits of that number, or the column
ld c,a ;load the column into c; we now have
a & c = col, b = row
xor b
;XOR A with B, column with row. B is still the row, C is still
the column. The effect of this XOR
;is that bit 0 of A is set iff the square is black. Bit 0 of
the row and bit 0 of the column tell
;whether the row / col is odd or even, or if the first obtained value
for row is white or black in the
;fist column of rows and vice versa. Looking at a chess board,
you will see that when a column in
;the first row is white and when a row in the first column is white,
the square that the row and
;column have in common will be white, in other words, the square represented
by that row and
;column number will be white if both the row and column numbers are
even. If either the row or
;the column is black (odd), then the square will be black, but if both
the row and the column are
;black, then the square will be white. So, if either operand
is set, the result will be set, if both are
;set or if neither are set, the result will be reset: XOR! So,
bit 0 of A is now set if the square is
;black, reset if the square if white.
and %1
ret
;get rid of all bits but bit 0, another affect of the AND instruction
is that if the resulting number in
;a is zero, then the zero flag will be set, and reset if it isn't zero
(the only other option is one). So,
;to check whether the square is white or black after returning you
could either check the zero flag
;or check to see if A is 1 or 0. The column is returned in C
and the row is returned in B, as well.
Don't worry if you didn't understand this example
-- it requires an excellent understanding of binary. You should at
the very least see how useful ANDing bitmasks can be in a program.