Last unit you walked the finger by hand — write, step, write, step — and you could feel the repetition piling up. Three cells was fine; thirty-two would be absurd. So you don’t write it thirty-two times. You tell the machine to.
That’s a loop, and on the Z80 the everyday one is DJNZ:
ld b, 32—Bis the counter. Load it with how many times you want.djnz .fill— Decrement B, Jump if Not Zero. In one instruction it subtracts 1 fromBand, ifBisn’t yet zero, jumps back to the label.
Put a body between the count and the djnz, and that body runs exactly B times. It’s BASIC’s FOR i=1 TO 32 … NEXT, assembled from a counter and a jump you already know.
What you’ll see by the end

A red bar clear across the top — the whole top row of 32 colour cells, painted by a loop that ran the same two-line body thirty-two times. You wrote the body once.
The loop
; ============================================================================
; PRIMER — Beat 10: Counting Toward Zero
; ============================================================================
; In Beat 9 you stepped the finger by hand: write, inc, write, inc... A loop
; hands that repetition to the machine.
;
; ld b, 32 -- B is the dedicated COUNTER. Load it with how many times.
; .fill: ... -- the body: do the thing once.
; djnz .fill -- DJNZ = "Decrement B, Jump if Not Zero". One instruction
; subtracts 1 from B and loops back until B reaches 0.
;
; So the body runs exactly B times. Here it colours one cell and steps the
; finger -- 32 times -- which paints the whole top row of the screen red.
; That's BASIC's `FOR i = 1 TO 32 ... NEXT`, built from a counter and a jump.
;
; The pointer walk (ld hl / ld (hl) / inc hl) is exactly Beat 9; the only new
; thing is letting the machine do the counting.
; ============================================================================
org 32768
start:
ld hl, $5800 ; finger on the first colour cell
ld b, 32 ; count: 32 cells = one full row
.fill:
ld (hl), $17 ; colour the cell (PAPER red, INK white)
inc hl ; step to the next
djnz .fill ; B = B - 1; not zero? back to .fill
.loop:
halt
jr .loop
end startThe inside of the loop is exactly Beat 9 — colour the cell, step the finger. The only new thing is the wrapper: ld b, 32 sets the count, and djnz .fill does the counting and the jumping in one go. The finger walks all 32 cells; the bar appears.
Why count toward zero? Because that test is built into the instruction. DJNZ takes 1 off B and looks at B itself: not zero yet, jump back; zero, carry on. It doesn’t use the zero flag from Beat 7 at all. DJNZ changes no flags, so whatever the last cp or bit left in them is still there afterwards. Counting up would need a compare against the limit and a conditional jump every time round. FOR counts up for human comfort; the CPU counts down because the zero test comes free.
B is the counter DJNZ is wired to — that’s its job in this idiom. (It’s why we’ve kept B free in the loops you’ve seen.)
Assemble and run
asm198x --dialect pasmo --cpu z80 --tapbas counting-toward-zero.asm -o primer.tapOne loop, a full red row.
Try this: change the count
Set ld b, 16 for half a row, ld b, 10 for ten cells. ld b, N is your FOR i=1 TO N — the count is the loop’s whole story. (Avoid ld b, 0: DJNZ decrements first, so a count of 0 wraps to 256 and runs far longer than you meant — a classic first-loop surprise.)
Try this: the dashed line, properly
Remember Beat 5’s dashed line — eight near-identical pokes? Here it is as a loop, all 32 cells across — the same djnz body, pointed at the pixel map and writing the dotted byte:
; ============================================================================
Removed: ; PRIMER — Beat 10: Counting Toward Zero
Added: ; PRIMER — Beat 10, "Try this: the dashed line, properly"
; ============================================================================
Removed: ; In Beat 9 you stepped the finger by hand: write, inc, write, inc... A loop
Removed: ; hands that repetition to the machine.
Removed: ;
Removed: ; ld b, 32 -- B is the dedicated COUNTER. Load it with how many times.
Removed: ; .fill: ... -- the body: do the thing once.
Removed: ; djnz .fill -- DJNZ = "Decrement B, Jump if Not Zero". One instruction
Removed: ; subtracts 1 from B and loops back until B reaches 0.
Removed: ;
Removed: ; So the body runs exactly B times. Here it colours one cell and steps the
Removed: ; finger -- 32 times -- which paints the whole top row of the screen red.
Removed: ; That's BASIC's `FOR i = 1 TO 32 ... NEXT`, built from a counter and a jump.
Removed: ;
Removed: ; The pointer walk (ld hl / ld (hl) / inc hl) is exactly Beat 9; the only new
Removed: ; thing is letting the machine do the counting.
Added: ; Beat 5's dashed line took eight near-identical pokes. Here it is as a loop:
Added: ; point at the screen, write the dotted byte, step, repeat 32 times -- a full
Added: ; dashed line across the very top, and not a line of source repeated.
; ============================================================================
…
start:
Removed: ld hl, $5800 ; finger on the first colour cell
Removed: ld b, 32 ; count: 32 cells = one full row
Added: ld hl, $4000 ; finger on the top-left pixel cell
Added: ld b, 32 ; 32 cells across the top
.fill:
Removed: ld (hl), $17 ; colour the cell (PAPER red, INK white)
Removed: inc hl ; step to the next
Removed: djnz .fill ; B = B - 1; not zero? back to .fill
Added: ld (hl), %10101010 ; the dotted byte from Beat 5
Added: inc hl
Added: djnz .fill
.loop:
Point at the screen instead of the colour map, write the dotted byte, step, repeat. The repetition you typed out by hand in Beat 5 is now four lines that do the lot.
When it’s wrong, see why
- Only one cell is coloured. The
inc hlis missing or sits outside the loop, so every pass writes the same box. Both the write and the step belong inside, beforedjnz. - The loop runs far too long / the screen fills up. You loaded
ld b, 0.DJNZdecrements first, so 0 becomes 256. Use the count you mean. - The assembler can’t find
.fill. The label and thedjnz .fillmust match exactly, case and all — labels are the assembler’s rules, from Beat 7.
What you’ve learnt
ld b, N then djnz label runs the loop body exactly N times — a counter and a jump that count toward zero — so one loop does what would be N lines by hand.
What’s next
Your programs are growing, and chunks of them want names. Next — Call, Return, and a Stack You Can See — we package a job into a subroutine you can call from anywhere and ret from, and watch where the machine remembers its way back: the stack, in plain memory.
Sources
Zilog, Z80 CPU User Manual, UM008011-0816, pp. 278–279: DJNZ decrements B and jumps while B is not zero, with no condition bits affected; LD (HL), n and INC ss. Sinclair Research, ZX Spectrum BASIC Programming, chapter 16, colours: one attribute per character position.
Run it here
This unit's program, built for you.
- Program
- primer.tap
- Machine
- ZX Spectrum 48K (PAL)