Skip to content

Counting Toward Zero

Hand the repetition to the machine. Load a count into B, and DJNZ runs the body that many times — one loop fills a whole row where last unit you stepped by hand.

  • Sinclair ZX Spectrum
  • Assembly

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 — B is the counter. Load it with how many times you want.
  • djnz .fill — Decrement B, Jump if Not Zero. In one instruction it subtracts 1 from B and, if B isn’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 solid red bar running the full width of the screen, near the top.
The whole top row — 32 colour cells — painted by a two-line body that ran 32 times. You wrote the body once; DJNZ did the counting.

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

counting-toward-zero.asm
; ============================================================================
; 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     start

The 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

Code
asm198x --dialect pasmo --cpu z80 --tapbas counting-toward-zero.asm -o primer.tap

One 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:

The same loop pointed at $4000, writing the dotted byte+9 added−20 removed
; ============================================================================
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 hl is missing or sits outside the loop, so every pass writes the same box. Both the write and the step belong inside, before djnz.
  • The loop runs far too long / the screen fills up. You loaded ld b, 0. DJNZ decrements first, so 0 becomes 256. Use the count you mean.
  • The assembler can’t find .fill. The label and the djnz .fill must 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)

Download primer.tap