Logic Operations

Logic operations manipulate individual bits within bytes and words, enabling hardware control, data packing, flag testing, and arithmetic on binary representations.

Why This Matters

Computers are built from logic gates — AND, OR, NOT, XOR. These same operations are available as instructions to the programmer. They are not abstract mathematics; they are the most direct way to communicate with hardware registers, pack multiple values into a single byte, implement protocol encoders and decoders, and perform operations that arithmetic alone cannot express.

For rebuilders working at the hardware interface level, logical operations appear in every program that reads from or writes to hardware. A status register returns 8 bits, each meaning something different. You must isolate the specific bit you care about. A control register has fields packed into subsets of its bits. You must modify one field without disturbing the others. These tasks require AND, OR, and XOR.

Understanding logical operations at both the conceptual level (what they do to bits) and the practical level (the specific patterns for masking, setting, clearing, and toggling) makes hardware programming straightforward.

The Four Basic Operations

AND: Both input bits must be 1 for the output to be 1. If either is 0, the output is 0.

  1011 0110
AND
  1111 0000
= 1011 0000

AND with 0 forces bits to 0 (masking off bits). AND with 1 preserves bits. AND is used to isolate (mask) specific bits from a larger value.

OR: At least one input bit must be 1 for the output to be 1. Only if both are 0 is the output 0.

  1011 0110
OR
  0000 0001
= 1011 0111

OR with 1 forces bits to 1 (setting bits). OR with 0 preserves bits. OR is used to set specific bits without affecting others.

XOR (exclusive OR): The output is 1 if the inputs differ, 0 if they are the same.

  1011 0110
XOR
  1111 0000
= 0100 0110

XOR with 1 flips bits (toggling). XOR with 0 preserves bits. XOR a value with itself produces 0 — a fast way to zero a register in assembly (XOR A on Z80 sets A to zero and clears flags). XOR is also used in checksums (XOR all bytes in a block to produce a simple error-detection value) and simple encryption.

NOT (complement, bitwise invert): Flips every bit. 1 becomes 0, 0 becomes 1.

NOT 1011 0110 = 0100 1001

NOT is used less often standalone and more often as part of compound operations — particularly “AND NOT” to clear specific bits.

Practical Patterns

These four patterns appear constantly in hardware programming:

Testing a bit: Is bit N set in register R?

AND value, mask    ; mask has a 1 only in bit N
JP Z, NOT_SET      ; jump if result is zero (bit N was 0)
; bit N was set

Example: test bit 3 (value 0x08) of a status byte:

LD A, (STATUS)
AND 0x08
JP NZ, READY      ; jump if bit 3 is set

Setting a bit: Force bit N to 1 without affecting other bits.

OR value, mask    ; mask has a 1 only in bit N

Example: set bit 5 (0x20) of a control register:

LD A, (CTRL_REG)
OR 0x20
LD (CTRL_REG), A

Clearing a bit: Force bit N to 0 without affecting other bits.

AND value, NOT(mask)    ; mask inverted has 0 only in bit N

Example: clear bit 5 (0x20) of a control register. NOT(0x20) = 0xDF:

LD A, (CTRL_REG)
AND 0xDF           ; 0xDF = 1101 1111 — clears bit 5 only
LD (CTRL_REG), A

Toggling a bit: Flip bit N regardless of its current state.

XOR value, mask    ; mask has a 1 only in bit N

Example: toggle the LED on bit 2 (0x04):

LD A, (LED_PORT)
XOR 0x04
LD (LED_PORT), A

Extracting Bit Fields

A byte or word often contains multiple packed fields. To extract a field, mask off the bits you do not want, then shift the result to the right to position the field at bit 0.

Example: a status byte where bits 4-6 encode a 3-bit error code (values 0-7):

; Extract error code from bits 4-6
LD A, (STATUS)
AND 0x70          ; 0x70 = 0111 0000 — keep bits 4-6 only
; now shift right 4 places
RRCA              ; rotate right
RRCA
RRCA
RRCA
; A now contains the error code in bits 0-2

Or using the Z80’s extended shift instructions:

LD A, (STATUS)
AND 0x70
SRL A             ; shift right logical 1 bit
SRL A
SRL A
SRL A
; A = error code

Inserting Bit Fields

To insert a new value into a bit field without disturbing other bits:

  1. Clear the target field in the register
  2. Shift the new value into the correct bit position
  3. OR the new value into the register
; Set bits 4-6 to value in B (B must be 0-7)
LD A, (STATUS)
AND 0x8F          ; 0x8F = 1000 1111 — clear bits 4-6
LD C, B
SLA C             ; shift B left 4 places
SLA C
SLA C
SLA C
OR C              ; insert into bits 4-6
LD (STATUS), A

Logical Operations in Conditions

Boolean conditions in high-level code — IF (A > 0) AND (B < 10) — compile to conditional jumps. But the individual comparisons’ results (true/false) are 1-bit values that can be combined with logical operations.

In assembly, there is no direct AND of two boolean results. Instead, you structure the jumps to implement the desired logic:

AND: Both must be true — exit the conditional on any false:

; if (A > 0) AND (B < 10)
  ; test A > 0
  CP 0
  JP Z, FAIL      ; A = 0, fail
  JP M, FAIL      ; A < 0, fail
  ; test B < 10
  LD A, (B)
  CP 10
  JP P, FAIL      ; B >= 10, fail
  ; both conditions met
  ...
FAIL:

OR: Either may be true — proceed on any success:

; if (A = 0) OR (B = 0)
  CP 0
  JP Z, SUCCESS   ; A = 0, take branch
  LD A, (B)
  CP 0
  JP Z, SUCCESS   ; B = 0, take branch
  ; neither zero
  JP CONTINUE
SUCCESS:
  ...
CONTINUE:

De Morgan’s Laws

De Morgan’s laws let you rewrite complex logical conditions in equivalent forms that may be easier to implement:

  • NOT (A AND B) = (NOT A) OR (NOT B)
  • NOT (A OR B) = (NOT A) AND (NOT B)

Practical use: “jump if NOT (X > 0 AND Y > 0)” is equivalent to “jump if (X 0) OR (Y 0)“. The second form is directly testable with two sequential conditional jumps.

When a complex condition requires many instructions to test, De Morgan transformations sometimes yield a shorter or clearer implementation. When the condition for jumping to the else branch, rather than the then branch, is simpler, use that.

Shift and Rotate for Arithmetic

Shift operations are logical operations with arithmetic significance:

  • Left shift by 1: multiply by 2
  • Left shift by N: multiply by 2
  • Right shift by 1 (logical, unsigned): divide by 2
  • Right shift by N (logical, unsigned): divide by 2

Since 8-bit CPUs typically have no multiply or divide instruction, shifts are how you multiply and divide by powers of 2. For arbitrary multiplication: express the multiplier as a sum of powers of 2, shift for each power, and add. This is the principle behind all multiplication algorithms.

Practical Notes for Rebuilders

Memorize the four operations and their three practical uses: AND for masking/testing, OR for setting, XOR for toggling. These patterns are so common that they become reflexive with practice.

Always comment bitwise operations with what they accomplish, not just what they do. AND 0x0F ; extract low nibble (4-bit channel number) is far more useful than just the instruction.

When working with hardware registers, create named constants for all bit masks and field positions. AND CHANNEL_MASK followed by SRL A four times, where CHANNEL_MASK EQU 0xF0 and the shift count is documented, is readable. The same code with raw numbers is not.