z80:Flags and Bit-Level Instructions

From Learn @ Cemetech
Revision as of 07:02, 3 February 2016 by KermMartian (talk | contribs) (Initial content)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

Existing Tutorials

Introduction

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.

F Register

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


System Flags

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)


Bitwise instructions

Now that we know what flags are, how do we use them? As seen in the code above, there are 3 bitwise instructions that can be used on flags: SET, RES, and BIT.

  • 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) ||

Bit masking

In addition to these three instructions there are 3 more instructions that mess with bits at the byte level. They are bitwise AND, bitwise OR, and bitwise XOR.

Bitwise AND

For a minute, we're going back to english class. Remember that School House Roc 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.

Bitwise AND Table
! bit 1 bit 2 result
0 0 0
0 1 0
1 0 0
1 1 1

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.

Byte1: %11001111
Byte2: %01011101
Result: %01001101

Bitwise OR

Bitwise OR isn't much different from bitwise AND. In the same School House Roc 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:

Bitwise OR Table
! bit 1 bit 2 result
0 0 0
0 1 1
1 0 1
1 1 1

Bitwise XOR

So what is bitwise XOR then? the "X" in XOR stands for exclusive. This means that only one can be true, or else the whole statement is false. In other words, two trues = false.

! bit 1 bit 2 result
0 0 0
0 1 1
1 0 1
1 1 0

Bitmasking Basics

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.

Setting bits

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


Resetting bits

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


Switching bits

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:

16-bit counters

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


Simple Optimizations

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.

XOR'd Sprites

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).

Review

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

2. Create a simple program that will set and reset the z flag using only the bitwise instructions (SET, RES, BIT, AND, OR, XOR).

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.

4.

   ld b,$FF	;let's reset all the bits
   and b


5.

   set OnInterrupt,(IY+OnFlags)		;reset the OnInterrupt flag


6.

   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.

Conclusion

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.