Day 8: Bit-Level Instructions

The Z80’s bit-level instructions enable you to manipulate the individual bits of registers and bytes in memory. You must be familiar with binary notation to continue. If you need some review, look back to Day 3.

Bit Logic Instructions

There are four main logical (a.k.a boolean) operations, called AND, OR, XOR, and NOT. All except NOT are dyadic (they want two operands). To figure out what the result of a dyadic logical operation is, the values of the two operands must be worked on in binary. The operator is applied to a bit in one value and the corresponding bit in the other value; this is done for each bit. The result of each operator can be found by consulting this table:

bit 1 bit 2 AND OR XOR NOT
0 0 0 0 0 1
0 1 0 1 1
1 0 0 1 1 0
1 1 1 1 0

Examples:

$$ \begin{array}{ll} & 11110000_2 \\ \textrm{AND} & 01010101_2 \\ \hline & 01010000_2 \end{array} $$ $$ \begin{array}{ll} & 11110000_2 \\ \textrm{OR} & 01010101_2 \\ \hline & 11110101_2 \end{array} $$ $$ \begin{array}{ll} & 11110000_2 \\ \textrm{XOR} & 01010101_2 \\ \hline & 10100101_2 \end{array} $$ $$ \begin{array}{ll} \textrm{NOT} & 11110000_2 \\ \hline & 00001111_2 \end{array} $$

Naturally, the logical operations are available as Z80 instructions.

AND {reg8 | imm8 | (HL) }: Bitwise AND on the accumulator.
S
affected
Z
affected
P/V
detects parity
C
reset
OR {reg8 | imm8 | (HL) }: Bitwise OR on the accumulator.
S
affected
Z
affected
P/V
detects parity
C
reset
XOR {reg8 | imm8 | (HL) }: Bitwise XOR on the accumulator.
S
affected
Z
affected
P/V
detects parity
C
reset
CPL: Bitwise complement (NOT) of the accumulator.

Bit Masking

The boolean operations are mainly used in a technique called “bit masking”, which allows you to set or reset specific bits.

Setting Bits

To set bits, use OR. For the mask, use 1 for each bit you want forced to be set, and 0 for the bits you want to preserve. Typically, you would use an OR bitmask to integrate some pattern into a byte without disturbing the other bits.

E.g. Use OR to force bits 1, 4, 5, and 7 to be set.

$$ \begin{array}{lll} \begin{array}{ll} & 00000000_2 \\ \textrm{OR} & 10110010_2 \\ \hline & 10110010_2 \end{array} & \begin{array}{ll} & 01100101_2 \\ \textrm{OR} & 10110010_2 \\ \hline & 11110111_2 \end{array} & \begin{array}{ll} & 10001111_2 \\ \textrm{OR} & 10110010_2 \\ \hline & 10111111_2 \end{array} \end{array} $$

Resetting Bits

To reset bits, use AND. For the mask, use 0 for each bit to reset, and 1 for each bit to ignore. Use this AND bitmask to remove bits that are considered “garbage” from a piece of data.

E.g. Use AND to force bits 0, 1, 2, and 6 to be reset.

$$ \begin{array}{lll} & \begin{array}{ll} & 11111111_2 \\ \textrm{AND} & 10111000_2 \\ \hline & 10111000_2 \end{array} & \begin{array}{ll} & 00111001_2 \\ \textrm{AND} & 10111000_2 \\ \hline & 00111000_2 \end{array} & \begin{array}{ll} & 01101101_2 \\ \textrm{AND} & 10111000_2 \\ \hline & 00101000_2 \end{array} \end{array} $$

Toggling Bits

To flip the state of a bit, use XOR. For the mask, use 1 for each bit to flip, and 0 for each bit to ignore.

E.g. Use XOR to flip bits 7 to 4.

$$ \begin{array}{lll} & \begin{array}{ll} & 10101010_2 \\ \textrm{XOR} & 11110000_2 \\ \hline & 01011010_2 \end{array} & \begin{array}{ll} & 11001100_2 \\ \textrm{XOR} & 11110000_2 \\ \hline & 00111100_2 \end{array} & \begin{array}{ll} & 01011110_2 \\ \textrm{XOR} & 11110000_2 \\ \hline & 10101110_2 \end{array} \end{array} $$

Uses For Bitmasking

16-Bit Counters

Because the only way to get a result of zero with an OR operation is when both operands are zero, it can be used when you want a 16-bit counter.

Loop:
    DEC    BC         ; Update the counter
    LD     A, B        ; Load one half of the counter
    OR     C          ; Bitmask with the other half of the counter
    JR     NZ, Loop    ; If Z is reset then neither B or C is zero, so repeat

Calculating Remainders

AND can be used to perform modulo with a power of two.

    AND    %00011111     ; A = A mod 32
    AND    %00000111     ; A = A mod 8

Modulo-N Counters

This feature of AND can be used to implement a modulo-n counter. The premise is that a value is incremented until it hits 2n, at which point it resets to zero. Think of it as a clock with 2n digits. The format for such a counter is:

Loop:
    LD     A, (count)
    INC    A
    AND    2^n-1
    LD     (count), A
    JP     Z, Fishkill
    JR     Loop

Optimizing

Some cheap optimization tricks with boolean operations.

Instruction Bytes Cycles Replacement Bytes Cycles Downside
CP 0 2 7 OR A / AND A 1 4 P/V flag is affected differently
LD A, 0 2 7 XOR A 1 4 Flags are affected.
SUB A will have the same effect, btw

Signed Comparisons

If you remember, when subtracting one signed number from another, we can tell how the two numbers compare based on the sign and overflow flags:

Sign Overflow Meaning
SET SET op1 >= op2
SET CLEAR op1 < op2
CLEAR SET op1 < op2
CLEAR CLEAR op1 >= op2

Interestingly, this looks just like the operation of XOR. So one could surmise that XORing the sign and overflow flags together after a comparison would yield their relationship.

Now, while it is impossible to do an operation on two flag bits, we can take advantage of the fact that the sign bit is a copy of the seventh bit of the result, then use an XOR bitmask if it’s warranted. Although, this means that you have to use SUB instead of CP to do the comparison.

    SUB    -5
    JP    PO, $+5    ; P/V reset, and XORing with zero does nothing
    XOR   $80
; Can now use M for <, or P for >=

Be aware that this method does not leave the Z flag in any meaningful state.

Single-Bit Instructions

SET n, {reg8 | (HL) }: Sets bit n (0-7) of the operand.
RES n, {reg8 | (HL) }: Resets bit n (0-7) of the operand.
BIT n, {reg8 | (HL) }: Checks bit n (0-7) of the operand.
S
not affected
Z
affected
P/V
not affected
C
not affected

These instructions can be useful, because they allow you to do bitmasking without having to involve the accumulator.

There is a feature of the TI-OS called the system flags that you can take advantage of with these instructions. For starters, look at the Mode or Format menu. You’ll see a bunch of options that affect graphing, trigonometry, numbers, etc. Each of these options is controlled by a system flag.

Register IY

IX has a sister register called IY that is entirely interchangeable with its döppelganger, but the OS has called dibs on it.

All the system flags are in an array with a base at $89F0, and the calculator has this address stored in IY. We can modify a system flag with the SET/RES instructions, and check them with the BIT instruction.

Example: This will check the trigonometric mode. If this bit is 1, then degree mode is set. If it’s 0, radian mode is set.

    BIT    2, (IY + 0)    ; Checks bit 2 of byte $89F0

What relates 2 and 0 to with trigonometry? Not much, the fact that this bit and byte combination record the trig setting was a completely arbitrary decision on TI’s part. That’s why all the bytes and offsets of the system flags are equated in the INC file:

    BIT    TrigDeg, (IY + TrigFlags)

TrigDeg is equal to 2, and TrigFlags is equal to 0. This will do the same thing as above, but it’s a lot easier to understand.

In the reference section there is a table of system flags listing some of the most important flags.