Chapter 4 — Bit Patterns
Chapters 2 and 3 treated each byte as one number. Hardware status registers, UART flags and packed record fields treat a byte as eight switches in one box. You set one switch without breaking the others with masks, and / or / xor and shifts.
This chapter works through a packed status byte: test a flag, set a flag, clear a flag, isolate one bit for a boolean result. Named op declarations from Book 1 Chapter 14 capture the repeated mask idioms. The companion program is examples/04_bit_flags.asm.
The problem: eight flags, one byte
A device reports ready, error and busy in a single status register at $8000. Your code must:
- Light an LED if ready was set at startup.
- Record an error without clearing ready.
- Clear busy after the operation finishes.
- Store whether the error bit is on as
$00or$01in a separate byte for a later test.
You could use eight bytes of RAM — wasteful on a small machine. One byte with named bit masks is the usual trade.
Bit masks as .equ names
Give each bit a name at assemble time:
FLAG_READY .equ $01 ; bit 0
FLAG_ERROR .equ $02 ; bit 1
FLAG_BUSY .equ $04 ; bit 2
FLAG_READY is not a memory address — it is the value $01 substituted wherever it appears. Combining flags at assembly time is or:
INITIAL .equ FLAG_READY | FLAG_BUSY ; $05
At run time you still load the live byte from (device_flags) into A.
AND, OR, XOR on A
| Instruction | Effect on bits |
|---|---|
or mask |
Sets every bit where mask is 1; leaves other bits unchanged |
and mask |
Clears every bit where mask is 0; keeps bits where mask is 1 |
xor mask |
Toggles bits where mask is 1 |
Set bit 1 (error):
ld a, (device_flags)
or FLAG_ERROR
ld (device_flags), a
Test bit 0 (ready) without changing the stored byte:
ld a, (device_flags)
and FLAG_READY
; Z set → ready bit was clear
Clear bit 2 (busy): you need and with the inverted mask. For FLAG_BUSY ($04), the clear mask is $FB:
ld a, (device_flags)
and $FB
When the mask is not a compile-time constant in A, invert through a scratch register:
ld b, a
ld a, FLAG_BUSY
cpl
and b
cpl complements A ($04 → $FB). Then and b clears only that bit in the saved value.
Toggle a bit: xor mask.
op for flag idioms
Book 1 Chapter 14: short sequences that repeat in one file are good op candidates — no call overhead, intent visible at the call site.
op bit_set(reg reg8, mask imm8)
or mask
end
op bit_clr(reg reg8, mask imm8)
ld b, reg
ld a, mask
cpl
and b
end
op bit_test(mask imm8)
and mask
end
Load the status byte into A first, then test:
ld a, (device_flags)
bit_test FLAG_READY
jr z, .not_ready
bit_test expands to a single and mask — A must already hold the byte under test. The Z80 has no ld a, a, so the op deliberately does not reload A.
bit_clr saves reg into B, complements the mask in A, then and b — the general pattern when you cannot write and $FB literally because the mask arrived in a register.
Shifts: move bits, watch carry
Logical shifts move bit positions for multiply/divide tricks and for isolation:
| Instruction | What moves |
|---|---|
rlca / rrca |
Rotate A through carry (8-bit, fast) |
rla / rra |
Rotate A through carry including previous carry |
sla r |
Shift left; bit 0 ← 0; high bit → carry |
srl r |
Shift right; high bit ← 0; low bit → carry |
Extract bit 1 into bit 0 after masking:
; extract_bit_u8: error bit as 0 or 1 in A
;! in A
;! out A
;! clobbers F
@extract_bit_u8:
and FLAG_ERROR
rr a
ret
and FLAG_ERROR clears all but bit 1 ($02). One rr a moves that bit into position 0. Result in error_bit should be $01 when the error flag is set.
For a general bit index n, loop n times with srl a or use the Z80 bit n, r instruction (sets Z if bit clear) when you only need a branch, not a 0/1 byte in A.
bit n, r for branches only
ld a, (device_flags)
bit 2, a
jr nz, .still_busy
bit does not change A; it only sets flags. Use it when you will branch immediately. Use and mask when you need a numeric 0/1 in A for storage.
Trace: flags from $05 to $03
Start: $05 = ready + busy ($01 | $04).
| Step | A | Action |
|---|---|---|
| test ready | $05 |
and $01 → NZ → ready_lit = 1 |
| set error | $07 |
or $02 |
| clear busy | $03 |
and $FB clears bit 2 |
| extract error | $01 |
and $02, rr a |
After halt, (device_flags) should be $03, (ready_lit) $01, (error_bit) $01.
Packed flags inside records (preview)
Chapter 5 stores structs as bytes. A status nibble and a type nibble can share one byte:
bit 7 6 5 4 3 2 1 0
[ type ][flags]
The same and / or / shift tools apply; offset and sizeof tell you which byte, not how to twiddle bits inside it.
main in the companion
.org $0000
main:
ld a, (device_flags)
bit_test FLAG_READY
...
ld a, (device_flags)
bit_set A, FLAG_ERROR
ld (device_flags), a
ld a, (device_flags)
bit_clr A, FLAG_BUSY
ld (device_flags), a
ld a, (device_flags)
call extract_bit_u8
ld (error_bit), a
halt
Examples
| File | What to verify |
|---|---|
examples/04_bit_flags.asm |
device_flags = $03, ready_lit = 1, error_bit = 1 |
azm examples/04_bit_flags.asm
AZM writes examples/04_bit_flags.lst by default. Open that listing to confirm bit_set expanded to or at the call site, not a subroutine call.
Summary
- A mask names which bits an instruction touches; define masks with
.equ. orsets,andclears or tests,xortoggles.- Clear one bit with
andand the inverted mask (cplon the mask byte when needed). opnames flag idioms when the same 2–4 instructions repeat in one file.- Shifts and
bit n, rmove or test bit positions; choose based on whether you need a branch or a stored 0/1. - Chapter 5 reuses these skills inside record layouts.
Exercises
- Start from
$05. Predict(device_flags)after onlybit_set A, FLAG_ERRORwithout clearing busy. - Add
FLAG_FAULT .equ $08. Writemainso a fault sets bit 3 and forces busy clear in one pass through A. - Implement
popcount_u8: count set bits in A with a loop (and 1,srl, increment counter). Return count in A. - Implement
parity_u8: return 1 if odd number of set bits, 0 if even. One compact approach is to toggle a workspace byte each time you find a set bit. - Replace
extract_bit_u8with eightbit n, a/jrbranches — when is the shift loop smaller? - Define an
oprot_right(reg reg8)that expands torrawith A loaded fromreg— use it in a 16-bit shift across A and a workspace byte.