Welcome to the fourth lessonf of the Ash School! The part on PUSH and POP
which I promised in the last section, will come in the next lesson.
Contents
Introduction to bit manipulating instructions
All these instructions (they're quite many) changes the bits in a byte in someway. You don't add or
subtract anything, you just change the bits! You can either shift them left or right in different ways, or
rotate them, set/reset/test them individually and last you can make some logical operations on them
(OR, AND, XOR).
Why should you use those instructions? Well, when you want fast graphics for example,
because the pixel on the LCD is stored in bits (since it's only two colors, only one bit/pixel is needed)
which means that 1 byte contains 8 pixels. To put a pixel, you then have to OR it, or maybe use XOR
and AND to remove it or if you want something to scroll smootly to the left or right, a rotating instruction
may give the best result. Also, if you want to multiply a register with 2, 4, 8... shifting is the absolute
fastet way to do that.
Just a simple reminder so you know how a binary number looks like :-) Remember also that bit 0 is the
LSB (Least Significant Bit, the bit to the far right - a zero in this case) and bit 7 the MSB. You must
know that else you may have a hard time understanding the instructions below.
Logical operators
All three logical operations uses two operands: the A registers and another registers or
immediate data. The result is always stored in the A register.
OR
This instruction makes a logically 'or' with the A reg (also called accumulator) and the specified
operand. With this I mean, that if at least one bit is set in a bitposition (bit 0, bit 1... bit 7) that bit
is also set in the result (if bit 0 is set in the accumulator OR bit 0 is set in the specified operand then
bit 0 is set in the answer. Then if bit 1 is set... and so on). Example:
Input: A - 186
E - 92
Instr: OR E
Output: A - 254
186 = 10111010
92 = 01011100
-----------------
11111110 = 254
Note that you don't write OR A,E - just OR E. OR A wouldn't change the accumulator (that's very obvious).
How will this affect the flags? The Carry Flag is ALWAYS reset on ALL logical operators so that's easy. The zero
flag is also set if the result is zero, else it will be reset.
One common use for the OR instruction is OR A. Why? It doesn't change any registers!? No, but it changes the
flags. If A=0 the zero flag will be set and if A<>0 then it will be reset. That tells you "aha, then I can use
OR A instead of CP 0 when I wan't to check if the accumulator is zero!". Yup! And it's smarter too! OR A just takes
one byte, and CP 0 takes two bytes! So you save one byte! In a big program (>2000 rows) you can save about 50
bytes with this. When your programs are finished, search for CP 0 in it and you may find some things to change.
AND
AND is very easy to explain when you know OR. Instead of 'or'ing the bits, you 'and' the bits :-) In other words,
both bits must be set if the result bit should be set. Example:
Input: A - 186
E - 92
Instr: AND E
Output: A - 24
186 = 10111010
92 = 01011100
-----------------
00011000 = 24
AND is very useful when you want to mask away some bits. For example, you're only intersted in the lower four
bits in the accumulator, and the upper four bits are just trash. How do I remove them? The answer is AND 15, since
15d = 00001111b. Now, the four upper bits are all reset since both bits would be needed to be set if the bit should
hang on to the result. Since the mask (in this case, 15) have those bits reset they will be spoiled. The
lower four bits are in the mask set, which means that the lower four bits in the accumulator will remain.
I really hope you can see this, because this is important. You should also write the masks in hexadecimal or
even better in binary in your source, because it makes the code easier to read (in this case, $0F or %00001111).
XOR
XOR is an exclusive OR. The difference between OR is that if both bits are set, the answer bit will be reset! Example:
Input: A - 186
E - 92
Instr: XOR E
Output: A - 230
186 = 10111010
92 = 01011100
-----------------
11100110 = 230
In other words, if the specified operands bit is set, then you change that bit in the accumulator! Since you only change the
bits, you doesn't really lose the value! For example, if you make two XOR E in a row, the accumulator will have the original value
again. This may be good for encoding for example, because can always know which value you had before the XOR if you
know the specified operand.
This can also be used for removing bits. Since OR is used to set a bit, XOR can be used to remove bits. To do that you must
first OR the accumulator then XOR it like this:
Input: A - 186
E - 92
Instr: OR E followed by XOR E
Output: A - 162
186 = 10111010
92 = 01011100
----------------- (first the OR)
11111110 = 254
01011100
----------------- (then the XOR)
10100010 = 162
This could also be done with a combination of AND and XOR but then the mask must
be in the A reg, because first you should XOR the mask with $FF to change all bits and then
you XOR that with the operand you want to remove bits from. This takes one byte more though,
but may be more useful if you have the mask in the accumulator.
Instead of using XOR $FF you can use the instruction CPL, which does the same thing and it is
one byte smaller.
All shift instructions
There are three shift instructions, SLA, SRA and SRL. They all have one operand (a 8 bit registers)
which the result also is stored in and they all shift the bits to either the left (SLA) or the right (SRA and SRL)
and the bit that disappear is stored in the Carry Flag. SLA and SRL will put a 0 at the empty position while SRA
will not change the value in bit 7. To make it easier to understand here are some "pictures":
SLA (Shift left arithmeticly)
C <- | 7 <---- 0 | <- 0
the operand
This is a common way to multiply something with 2, 4, 8 and so on. Note: you should NEVER use SLA A
since the same thing could be done with ADD A,A and the latter is one byte shorter.
SRA (Shift right arithmeticly)
| 7 ----> 0 | -> C
the operand
This shift is good when you know that the operand may be a negative number, since that is
indicated by bit 7. This shift won't change bit 7 which may be good. For example, 10100011 will
become 11010001 and with the Carry Flag set after a SRA.
SRL (Shift right logically)
0 -> | 7 ----> 0 | -> C
the operand
And this shift divides the operand with two and stores the remainder in the Carry Flag.
All rotate instructions
There are 10 different rotating instructions, although I won't explain two of them (RLD and
RRD) since they are almost only good when dealing with BCD numbers, which I may explain
in a later lesson. I've never used them, and the reason for it is that it's slow (BCD is a way
to store big numbers and a way to handle floating point operations) and not really necessary
in game programming.
Anyway, what's the difference between rotating and shifting? In the shifting routines you saw
that a 0 was being put at the end/beginning (except SRA). That's shifting. Rotating will put the
Carry Flag in that position instead! Now, why so many more instructions then? Well, some instructions
are almost the same and there are both 8 bit rotating and 9 bit rotating (through the carry). Here
are the instructions one by one:
RL and RLA (Rotate left through carry)
These instructions do the same, except that with RL you specify an operand and with RLA the operand
is A. So, RL A and RLA are the same thing? Yes, except for the flags. RL changes the Zero flag according
to the result (set if operand becomes 0, else reset) but RLA doesn't touch the Zero flag. That isn't so important
because when rotating you often don't care about the flags. Also, RLA saves one byte and is one cycle faster.
According to "Programming the Z80" RLA is provided for compatibility with another CPU (8080).
Another nice ASCII picture follows...
C <- | 7 <---- 0 | <- C
the operand
Both C:s are stands for the Carry Flag, and they're not overwritten by each other in some way. This is 9 bit rotation
also as you may see.
RLC and RLCA (Rotate left with branch carry)
Same thing here, the only difference is that RLCA doesn't change the Zero flag and it's a byte shorter. This
is just a 8 bit rotation though as you see of the picture below:
C <- | 7 <---- 0 | <- b7
the operand
b7 is the seventh bit of the operand, it's just hard to draw nice small ASCII pictures :-)
RR and RRA (Rotate right through carry)
Works like RL and RLA except for a direction change. No more explanation needed I guess.
C -> | 7 ----> 0 | -> C
the operand
RRC and RRCA (Rotate right with branch carry)
And this is an 8 bit rotation to the right and it stores the bit that was rotated around in the carry just like
RLC and RLCA...
b0 -> | 7 ----> 0 | -> C
the operand
Shifting and rotating 16 bit registers
Since all shift and rotate instructions only work with 8 bit register, you have to combine them
to get it to work with 16 bit registers. I will just give a few examples since you probably can figure
out how to do it by your own when you've seen the technique.
Problem
| Solution
| Comment
|
Shifting HL left |
ADD HL,HL |
Since multiplying with two is the same as adding something with itself, so why not? Just one byte long also.
This only works with HL since you're not allowed to have any other 16 bit register as the first operand. |
Shifting DE left |
SLA E |
RL D |
First we shift the LSB left one step and put b7 in the carry, which will become b0 in the D register with
RL. If you want to redo this two more times (multiply by 8) there is a smarter way which will be explained in
another lesson (it's really simple though). |
Shifting BC right |
SRL B |
RR C |
Same thing as the above except that you first start with the MSB since it's a right shift |
Rotate HL right through carry |
RR H |
RR L |
A 17 bit rotation is easy as you see. Works like C -> H -> L -> C. |
It's more complicated to make a 16 bit rotation since then you must first make a shift, then a rotate and then
check if the Carry is set and if so change b0 (or b7 depending on which direction you're rotating) in the register
you first shifted. Since you don't know how to change individual bytes yet (yeah, you can OR but then only
with the A register) we better learn that...
All single bit manipulating instructions
With individual bits you can either set them (SET), reset them (RES) or test them (BIT). This is useful when you only
want to change/test one bit in a register that isn't the accumulator. All these instructions have two operands: the bit you
will change/test (0-7) and the register. Very sadly, you MUST know which bit you want to test, you can't for example
write something like SET A,B which could mean "set bit A in B" but that isn't allowed.
I don't know if I have to draw some ugly pictures really, since it's pretty obvious how it works. SET sets a bit,
RES resets a bit and BIT tests a bit. You must know that BIT copies the bit into the Zero flag, so if the bit is 1 (set) the
Zero flag will be set (Zero = flag set = 1) even though the bit is 1...
Now we can write a short routine that rotates a 16 bit register:
RRHL: ; Rotate HL right (16 bit rotation)
srl h
rr l
ret nc
set 7,h
ret
Don't know if I've said it before, but you can use a condition after RET also as you see in the example. This one exits
the routine (which is called with CALL RRHL) if the carry flag isn't set, because if it isn't no bit is supposed to be set.
Small, useful instructions you should know
Here are some small instructions that I've skipped that you might want to know. All instruction takes one byte also.
Instruction |
Comment |
SCF |
This instruction sets the carry flag. May be useful to use before a rotate or to indicate that something went wrong
before you make a return from a subroutine or something. To clear the carry flag, use one of the logical operators. OR A
is often used. |
CCF |
This instruction "complements the carry flag" which means that the Carry flag is inverted, ie if it
was set it becomes reset and vice versa. Can't find any real use for it anyway. |
CPL |
Complement accumulator. Changes all bits in the accumulator (same as XOR 255), but doesn't change the C and Z flags. |
NEG |
Negatates the accumulator. If A=5 it becomes -5 ($FB). Good to use when subtracting sometimes because then
you can change the orders of the registers (B-A can be coded as A-B followed by a NEG to save one byte). |