← Back to Courses
module
6

Loops that stop

Why this matters

Every loop you can write so far runs forever.

That is one instruction short of being useful. A game needs to draw twenty sprites, print a five-digit score, step through a level one tile at a time - all of which mean doing something a particular number of times and then carrying on.

The missing piece is a jump that only sometimes jumps.

The flags, at last

You have been looking at the answer since S3. On the end of every register line there is a column you have had no use for:

 A  BC   DE   HL   PC   SZ-H-PNC
 41 3D00 1EFF FB3E 0769 01000010

SZ-H-PNC is a heading for eight single bits, and together they are the flag register. The processor sets them automatically after most instructions, as a record of how the last result turned out. They are how a program knows anything about what it just did.

Flag Name Means
S Sign The result was negative.
Z Zero The result was zero.
H Half-carry A carry happened inside the byte. Specialised; ignore it for now.
P Parity / overflow Depends on the instruction. Later.
N Subtract The last operation was a subtraction rather than an addition.
C Carry An addition carried out of the byte, or a subtraction borrowed.

The two gaps in the heading are positions the Z80 does not use.

This section needs exactly one of them.

A word of warning about names. The C at the end of that heading is the carry flag. The C in BC is the C register. They have nothing to do with each other, they are used in completely different places, and confusing them will cost you an hour one day. The instruction DEC C decrements the register. The condition NC means "no carry" and does not mention the register at all.

The zero flag

Z is set when the last result came out as zero, and clear when it did not. That is the whole rule.

You can watch it happen. Two programs, differing in one digit:

        ORG 256
        LD A,1
        DEC A           ; 1 - 1 = 0
        RET
 A  BC   DE   HL   PC   SZ-H-PNC
 00 3D41 1EFF FB3E 0769 01000010
        ORG 256
        LD A,2
        DEC A           ; 2 - 1 = 1
        RET
 A  BC   DE   HL   PC   SZ-H-PNC
 01 3D41 1EFF FB3E 0769 00000010

Line the two flag bytes up against the heading. Every bit is the same except the second - Z - which reads 1 where the answer was zero and 0 where it was not.

Run both. It is the fastest way to make the flags stop feeling abstract.

DEC

DEC subtracts one:

        DEC A
        DEC C

It works on A, B, C, D, E, H and L, and it sets the flags according to the result - which is the part that matters here. DEC a register holding 1 and Z comes up set. DEC a register holding anything else and it does not.

There is an INC that adds one, and it sets the flags the same way.

JR NZ - the conditional jump

Now the piece that has been missing:

        JR NZ,loop

Read it as three parts. JR is the relative jump from S5. NZ is the condition - not zero, meaning "only if Z is clear". loop is where to go.

In plain English: jump back to loop if the last result was not zero.

If the last result was zero, the jump does not happen and the processor carries straight on to the next instruction. That is the way out of the loop.

A loop that counts

Put those together and you can repeat something a fixed number of times:

        ORG 256

        LD C,10         ; how many times
        LD A,'A'        ; what to print

loop:   RST 8
        DEFB 158       ; ZOUTC
        DEC C           ; one fewer to go
        JR NZ,loop      ; not finished? go round again

        RET

What you should see

>AAAAAAAAAA
 A  BC   DE   HL   PC   SZ-H-PNC
 41 3D00 1EFF FB3E 0769 01000010

Ten A's - count them. And the register line tells you the rest of the story: C reads 00, so the counter ran out, and Z reads 1, which is exactly what stopped the jump being taken.

Follow one lap. C is 10. Print an A. DEC C makes it 9, which is not zero, so Z is clear, so JR NZ jumps. Round again. On the tenth lap DEC C makes it 0, Z is set, the jump is not taken, and the RET underneath finally runs.

Why the counter goes in C

Because the print call leaves C alone.

That is not a style preference. RST 8 / DEFB 158 is somebody else's code, and you have no say in what it does with the processor while it runs. It happens to give you back A - which is why A still holds your character on the next lap - and it gives you back C. So C is a safe place to keep a count across a print, and the loop above works.

A could not be the counter here anyway: it is carrying the character. Any loop that both prints and counts needs the count somewhere else, and C is the register known to survive.

Counting the character too

Nothing stops you changing A as well as counting. Here is the alphabet backwards:

        ORG 256

        LD C,26         ; 26 letters
        LD A,'Z'        ; starting from the end

loop:   RST 8
        DEFB 158       ; ZOUTC
        DEC A           ; previous letter
        DEC C           ; one fewer to go
        JR NZ,loop

        RET

What you should see

>ZYXWVUTSRQPONMLKJIHGFEDCBA
 A  BC   DE   HL   PC   SZ-H-PNC
 40 3D00 1EFF FB3E 0769 01000010

There is one thing in here worth being careful about. DEC A and DEC C both set the flags, and JR NZ only ever looks at the most recent result. DEC C is the last one before the jump, so the loop counts laps and not letters. Swap those two lines and the loop would end when a letter hit zero, which is not what you want and is a mistake that reads perfectly innocently.

The rule: put the thing you are testing immediately before the jump.

A finishing at 40 is the proof it ran the right number of times - one below 'A', which is 65, because the final DEC A happened after the last letter printed.

Change one thing

  • Change LD C,10 to LD C,1. How many characters, and why not none?
  • Change LD C,10 to LD C,0 and predict what happens before you run it. This one is worth thinking through: what does DEC C do to a register holding zero, and how many laps will it take to come back to zero?
  • In the alphabet program, swap DEC A and DEC C. Predict the output, then run it.
  • Change JR NZ,loop to JR Z,loop - jump if zero. What should happen on the very first lap?
  • Take the flag demonstration and replace DEC A with INC A, starting from
  • Does Z come up set? What does that tell you about what Z actually measures?

Exercises

6.1 - Count the alphabet forwards. Print A to Z in order. Use C for the count and INC A for the letter.

6.2 - A row of anything. Write a program that prints a chosen character a chosen number of times, where changing the character and the count means editing exactly one number each. Use it to draw a line of 40 dashes.

6.3 - Two loops. Print five rows of ten asterisks, with a line break between rows. You will need a count for the row and a count for the columns - which means finding a second register that survives a print. Try B, check the register line to see whether it did, and if it did not, work out another way.

6.4 - Read the flags. Write three short programs that end with Z set, Z clear, and C set. Check each against the register line. For the carry one you will need to look up an instruction this section has not covered - that is the exercise.

When it goes wrong

What you see What it means
One character instead of many The counter was already zero, or the register you counted in was not the one you loaded.
The screen fills and the program never returns The jump is always taken. Usually the flags are being set by something after the DEC, so the jump never sees the zero. Put the DEC immediately before the jump.
A screenful of characters when you asked for none DEC on a register holding zero wraps round to 255 rather than stopping, so the loop runs a full cycle before Z comes up again.
The right number of laps but the wrong characters Two DECs, and the jump is testing the wrong one. Check which is last.
error: unable to resolve reference: loop The label and the jump do not match, or the colon is missing from the label.

Summary

  • The flag register records how the last result turned out. It is the SZ-H-PNC column you have been seeing since S3.
  • Z is set when the last result was zero.
  • DEC subtracts one and sets the flags. INC adds one and does the same.
  • JR NZ,label jumps only if the last result was not zero, so a loop can end.
  • Count in C. The print call is known to give A and C back, and A is usually busy carrying the character.
  • Only the most recent result sets the flags the jump will test, so put the DEC immediately before the JR.

Next

S7. Doing the same job from several places. The two lines that print a character have appeared in every program you have written. Next you write them once and call them from anywhere - which introduces the stack, and explains the RET you have been typing since S3.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.