z80:Flags and Bit-Level Instructions
- 1 Existing Tutorials
- 2 Introduction
- 3 F Register
- 4 System Flags
- 5 Bitwise instructions
- 6 Bit masking
- 6.1 Bitwise AND
- 6.2 Bitwise OR
- 6.3 Bitwise XOR
- 6.4 Bitmasking Basics
- 6.5 Uses of Bitmasking
- 7 Review
- 8 Conclusion
A flag is a bit. That's all a flag is, and that's all a flag ever will be. If a flag is set (1), then the flag is raised. If the flag is reset (0), the flag is lowered/not raised. So how can one bit be of any use to us if it can only hold two values? Because there are things that can only be on or off. Why waste a byte to store something that only takes one bit? This way you can compress 8 flags into one byte, and save as much room as possible. There are two kinds of flags: the F register for flags, and a chunk of memory defined as Flags used by the OS.
The Flags register (F) is used specifically for flags. It can be used to test certain items concerning the instruction that was last used. Here is a breakdown of all the flags in the F register:
- 7th bit Signed flag (S). Determines whether the accumulator ended up positive (1), or negative (0). This flag assumes that the accumulator is signed.
- 6th bit Zero Flag (Z). Determines whether the accumulator is zero or not.
- 5th bit The 5th bit of the last 8-bit instruction that altered flags.
- 4th bit Half-carry (H). Is set when the least-significant nibble overflows.
- 3th bit The 3rd bit of the last 8-bit instruction that altered flags.
- 2nd bit Parity/Overflow (P/V). Either holds the results of parity or overflow. It's value depends on the instruction used. Is used for signed integers.
- 1st bit Add/Subtract (N). Determines what the last instruction used on the accumulator was. If it was add, the bit is reset (0). If it was subtract, the bit is set (1).
- 0th bit Carry (C). Determines if there was an overflow or not. Note that it checks for unsigned values. The carry flag is also set if a subtraction results in a negative number.
Remember in Control Structures how we used the CP instruction? This instruction subtracts the operand from the accumulator without changing the value of the accumulator. However, it did something else, also. It modified the flags by their definition. That way, when we checked if the flag was zero (exactly identical), we knew what the other value was.
The same applies to the jump instructions.
cp kUp jp z,mUp
However, in the case of jump instructions, instead of modifying the flags, we are checking its value. Then depending on whether it's set or reset, the action will be performed. You can do this with any of the flags, but they may not be useful because not all flags are modified by every instruction.
cp 5 jp n,isOk ;useless, cp doesn't modify the add/subtract flag
In addition to the flags register, there is a chunk of memory that is used by the OS specifically for various things. We can use these flags to our advantage since most ROM calls use the flags in some way or another. For a list of system flags you can use, see here.
To access system flags, use a IY offset.
set textInverse,(IY+TextFlags) res trigDeg,(IY+trigFlags) bit appAutoScroll,(IY+appFlags)
- SET: Sets the bit (bit=1).
- RES: Resets the bit (bit=0).
- BIT: Checks if a bit is one. Result is returned to the zero flag. If set, Z=0. If reset, z=1.
Bitwise Instructions Elsewhere
Possible the most powerful instructions in the z80 arsenal, bitwise instructions allow you not only to change flags, but you can also modify any byte.
set 0,a res 5,(hl)
So, which one is the zeroth bit or fifth bit? The least significant bit (LSB, or 0th bit) is always the bit furthest right and the most significant bit (MSB, or 7th bit) is the bit furthest to the left.
|7th bit (MSB)||6th bit||5th bit||4th bit||3rd bit||2nd bit||1st bit||0th bit (LSB)|
For a minute, we're going back to English class. Remember that School House Rock song "Conjunction junction, what's your function" ? Well, when they got to AND, they tell you that AND means that both statements are true. So, bitwise AND compares two bits and sets the resulting bit if both bits are set, and resets it if either one is reset. Here's a table to demonstrate the possible combinations two bits being compared and the results using bitwise AND.
|bit 1||bit 2||result|
Now, instead of only operating on 2 bits, the z80 bitwise AND instruction operates on the byte level. In essence, it does this operation to every corresponding bit between two bytes. So, let's practice bitwise ANDing bytes.
Bitwise OR isn't much different from bitwise AND. In the same School House Rock song, they describe OR as being a choice between two things, but you can only choose one. In programming, we tweak this a little bit and define bitwise OR as if either bit is set, then the resulting bit will be set. Again, here's a table of possible combinations:
|bit 1||bit 2||result|
|bit 1||bit 2||result|
So now that you know what the bitwise instructions are, let's learn what bitmasking is. Bitmasking is the process of altering the byte in such a way that the resulting byte will provide some useful information. In other words, you're hiding useless bits in that byte. So, let's get down to bitmasking.
Let's suppose that for some reason you want a particular bit in a byte to be set. How would you do this? Use the SET instruction, right? But what if you wanted many bits to be set? You could keep using the SET instruction, but there's a better way. To make sure that a bit will be set, just use Bitwise OR. This works because if either one of the corresponding bits is set, then the result is set. So, we use a "1" for our operand and it's set! But what about the bits we don't want to alter? What do we do about them? We would use a "0" for the bits we don't want to be changed, right? This is because if the other bit is "1", then the result will be "1", and if it is "0", then the result would be "0", meaning that no matter what you do, the bit will not change.
ld b,%11101111 ;we want everything except bit 4 to be set or b
Okay, now suppose you want a bit to be reset. We can use the bitwise AND instruction. If we want the bit to be reset, then put in a "0" for the corresponding bit and no matter what the resulting bit will be reset. For the bits you want to leave alone, you would use a "1".
ld b,%00101010 ;reset bits 0, 2, 4, 6, and 7 and b
So, what else can we do with the bits? The last option is to switch them, or have the opposite of the bit be the result. For this, we'll use the bitwise xor instruction. Hopefully you can see that if you input a "1", it will switch the bit and a "0" will leave the bit alone.
ld b,%00001111 ;switch the first 4 bits xor b
Uses of Bitmasking
So, now we know how to bitmask, how do we use it? There must be some use for bitmasking! Lo and behold, there are many uses for bitmasking. Here are a few common ones:
Unfortunately, there aren't any 16-bit comparison operations. So instead, we have to come up with our own method. We could load both bytes into the accumulator and check to see if they are both zero, but there is a better way. What you do is load one of the two values into the accumulator, and the bitwise OR the other byte with the accumulator. This way, if any bit was set, the resulting accumulator would not equal zero, so you would continue to decrease your counter.
Loop: DEC BC ; Decrease the counter LD A, B ; Load one byte of the counter into the accumulator OR C ; Bitwise OR with the other byte JR NZ, Loop ; If Z is reset then neither B or C was zero, so repeat
There are a few simple optimizations that you can do with the bitwise instructions. They are either smaller or faster than they "traditional" way of doing it.
Comparing if the accumulator is zero
A simple OR A will tell you if the accumulator is zero. It's both smaller and faster (1 byte smaller and 3 clock cycles faster). Note that it does effect P/V differently (CP 0 would alter P/V to detect overflow, but OR A would modify P/V to detect parity).
Setting the accumulator to zero
XOR A is a much faster and smaller instruction than LD A,0 (1 byte, 3 clock cycles). Hopefully you can see why XOR A does set the accumulator to zero. Note that the XOR does effect the flags, while LD preserves them.
This is an advanced topic that will be discussed later, but the basic idea is you want to draw a picture, but you don't want to erase/destroy what is already there. If you haven't noticed yet, XOR'ing a byte by the same byte twice will return the byte to its original state (inverse of an inverse is the original).
There are other uses of bitmasking, but they are usually code specific (mostly for optimizing).
Now that you read through all that stuff, time to see if you remember/understand it.
1. Explain why AND %11111111 doesn't do anything useful
3. Explain why there is no "XAND" (exclusive and).
Identify the Errors
Find the error(s), if any, in each piece of code. They can either be run-time errors or logic errors.
ld b,$FF ;let's reset all the bits and b
set OnInterrupt,(IY+OnFlags) ;reset the OnInterrupt flag
loop: ;... ;Operations that do something with z flag. We want to keep looping if z flag is set ;... xor a ;set the accumulator to zero jr z,loop
For answers, see here.
Bits are everything when programming on this low of a level. Use these instructions correctly, and it will enhance your programs beyond belief. Use them incorrectly, and you just might cause a RAM reset.