The Z80, like nearly all 8-bit microprocessors of it’s generation has a fairly limited instruction set. As a result it would be fair to say that it’s capability for maths has been pruned down to the bare minimum.
- Add and subtract bytes and words (ADD, ADC, SUB, SBC, INC, DEC)
- Compare bytes (CP)
- Perform operations on individual bits in bytes (BIT, RES, SET)
- Perform boolean algebra (AND, OR, XOR, CPL)
- Shift and rotate bytes (RL, RR, SLA, SRA, etc)
Typically these operations will be performed on registers within the Z80, more of which later. There is no support for floating point; it’s all binary and integer, though there is an implied understanding of negative numbers.
From these instructions it is possible to write routines to perform all higher level maths functions, such as multiply, divide, square root and handling floating point numbers. The Spectrum ROM contains such routines to support it’s BASIC interpreter.
Addition, Subtraction and Compare
There is quite a lot of binary voodoo that happens when you add or subtract numbers in binary. Fortunately for you, if you ask a Z80 to add 2+2 you will still get 4. If you are working in bytes and add 123 to 200, then you are clearly going to get some issues as 323 cannot be stored in a byte, but fortunately the Z80 can “carry the one”, and will set a flag to indicate that the result does not fit into the register.
Operations on individual bits
There are instructions in Z80 to SET (to 1) or RES (to 0) individual bits in a register. In a byte, the bits can be numbered from 0 to 7, where 0 is the least significant bit (far right, as you read it, with the “1” heading) and 7 is the most significant bit (far left, as you read it, with the “128” heading). You can also check the state of a bit with the BIT instruction.
This actually sounds more complicated than it is. To start off with, here is a table of two bits, a and b. The first two columns represents all possible combinations a and b can be, so 00, 01, 10 and 11. The last three columns represent the results if you perform the specified boolean operation on a and b. Looks complicated? It’s not really…
|a||b||a AND b||a OR b||a XOR b|
Right. AND is easy; the result bit is 1 only if a AND b are 1. How hard was that? Similarly, the result bit for OR is 1 if a OR b are 1.
XOR is a little bit more complicated; the result is 1 if either a or b are 1, but not if both are 1. XOR is short for eXclusive OR.
Well done if you’ve followed this so far. Remember, don’t worry if it doesn’t make much sense here; it will make much more sense in the context of an example!
As a bit of light relief, the NOT operation is dead easy:
The Z80 typically performs these operations on bytes. Here is an example of boolean algebra with two bytes:
A = %10110000 B = %00100100 A OR B = %10110100 A AND B = %00100000 A XOR B = %10010100 NOT A = %01001111
When a complete byte is involved, you can see that the operation works on each column of bits in turn, so for a OR b, the first bit of a and the first bit of b are OR‘d together to make the first bit of the answer, the second bit of a and the second bit of b are OR‘d together to form the second bit of the answer, and so on.
Shifting and rotating
The Z80 provides a whole load of different shift instructions but in essence what they do is shift the bits in a byte left or right.
LD A,%00110001 SLA A ;A is now %01100010 - all the bits have shifted to the left
Some instructions rotate, that is the bit that falls off the end becomes the new bit that is shifted in. Others simply shift 0’s in.
Something almost magical happens with shifting in binary though.
If we take the above example, 00110001 converts to 49 in decimal. If I shift it to the left it becomes 01100010 which converts to 98 in decimal. 98 actually equals 49 x 2! So a shift to the left (where a 0 is shifted in) is like a multiply by 2. Similarly, a shift to the right is like a divide by 2.
Why is this? Well, think back to the numbers primer. If I work in decimal and take 123 and shift each digit into the column on the left and put a 0 in the units column, then I get 1230; effectively multiplying the number by 10. Similarly, if I take 1230 and shift each digit into the column on the right, losing the 0 that falls off the end, then I get 123; effectively dividing the number by 10.
It’s the same with binary, though as the column headings are now powers of 2, not 10, then the multiply and divide is by 2, not 10.
By combining shifts, addition, subtraction and a loop you can do multiplication and division by any number.