Looping the Loop

See all articles from SYNC v3 n2

Maybe you have noticed that very short machine code routines do not seem to work much faster than Basic. There is a reason for that—if it is short and simple. Basic can often do it almost as fast as machine code. Since there is a lot of extra work involved in writing a machine code routine, it hardly seems worth the effort.

But the longer and more complicated the job is, the longer it takes in Basic—and the more you can see the speed advantage of machine code. The question is, how can you do long, complicated jobs using machines code—without having to write long, complicated programs in machine code?

The answer, in a word: loops.

How’s That Again?

The real power of a computer comes into play when you want to do something over and over. For example, it is not much work to multiply 7 times 5. It probably takes most people longer to type

PRINT 7*5

than it does to figure out that 35 is the answer.

But suppose you want to multiply 7 by each number from, say, 1 to 30. One way to do it is to write a long program that does one multiplication in each line—something like this:

10 PRINT 7*1
20 PRINT 7*2
30 PRINT 7*3
40 PRINT 7*4

…and so on. You could multiply numbers by 7 as high as you wanted to go, taking one line for each multiplication.

But you can get the same results in Basic with a program that is just 4 lines long, no matter how many times you want to multiply. The trick is getting the computer to do the same thing again and again:

10 LET A=1
20 PRINT 7*A
30 LET A=A+1
40 GO TO 20

If you enter and run this program, you will get more than 20 answers—without a lot of typing.

Making the Big Jump

The GO TO command in Basic is used to form a loop. That means the computer repeats a group of lines more than once, with only slight differences each time through.

As you might guess, there is a Z80 machine code instruction that works exactly the same way GO TO does. It is called the jump instruction, abreviated “JP”. Just as the Basic GO TO causes the program to go to a particular line, the jump instruction makes the Z80 processor go immediately to a particular part of the machine code program. It is not a line number, though (remember, machine code programs do not have line numbers), but a particular byte of memory.

In other words, you can translate a Basic GO TO directly into a machine code jump—if you know the memory location you want it to jump to. For example,

10 LET A=0
20 LET A=A+1
30 GO TO 20

becomes

       LD A,0
LOOP: ADD A,1
JP LOOP

“Loop,” of course, is just a “dummy” name. When you convert the machine code routine into the numbers the computer will understand, you have to know exactly what memory location the “ADD A,1” instruction begins at.

Maybe that sounds complicated. It is not really; after all, eventually you will have to figure out where the machine code routine will go in memory anyway. Suppose our simple loop routine starts at location 17000. Then it sets up something like this:

17000     LD A,0       62,0,
17002 ADD A,1 198,1,
17004 JP 17002 195,106,66

The numbers in the right-hand column are the numeric codes that will actually go into the computer. You can check each of them by looking in the back of your ZX81 or T/S 1000 manual, in the appendix titled “The Character Set.” “LD A,N” is 62 (and the number to load into register A is 0). “ADD A,N” is 198, and what you are adding is 1. Finally, “JP” is 195. The two numbers that follow are the memory location 17002.

Remember, a byte of memory can only hold a number between 0 and 255. When you make a larger number—such as a memory location—part of a program, you have to fit it into two bytes. First, divide the number by 256; then the remainder goes in the first byte, and the quotient goes in the second. 17002 divided by 256 is 66 with 106 remainder, so “JP 17002” becomes the three numbers 195,106,66.)

One Small Step Backwards

There is also another kind of jump instruction—and there is nothing in Basic quite like it. It is called a relative jump, but you might think of it as a “jump forward or backward” instruction.

Suppose you had a Basic command that would let you “go back” a few lines. You would not have to know the number of the line to GO TO in our simple loop program. You would simply tell the program to “go back one line”:

10 LET A=0
20 LET A=A+1
30 GO BACK 1

It does not exist in Basic—but in machine code, it does. The jump relative instruction is abbreviated “JR”, and the best way to see how it works is to rewrite our simple loop using it:

17000     LD A,0       62,0,
17002 ADD A,1 198,1,
17004 JR-4 24,-4

See the difference? With “JR” you do not have to worry about the remainder and the quotient and dividing by 256. You just tell the processor to “go back 4 bytes.” No matter where you put this routine in memory, it will always work properly. Unlike the “jp” version, it doesn’t have to start at 17000; it can be moved anywhere in memory without being changed.

Why is the instruction “go back 4 bytes” instead of, say, “go back 2 bytes”? Well, the Z80 processor reads the whole instruction first, then does what it is told. When it get to the “JP -4″ instruction, first it reads the whole instruction—”24,-4”. Then it goes back 4 bytes. Going backwards, the first byte is “-4”. Then comes “24”, then “1”, then finally it gets to “198”, which is where it starts to work again—the beginning of the “ADD A,1” instruction.

If it were just “JR -2”, the processor would go back to the beginning of the jump instruction—something like

40 GO TO 40

in Basic. It would just keeping coming back to itself. If the instruction were “JR 0” then the processor would not go back at all—it would just continue with the next instruction.

There is a disadvantage to the “JR” instruction—you cannot use it to go back (or forward) more than 127 bytes in your program. But for most machine code routines, that is plenty of room to work with—and if you have to take a bigger step than that, you can always use a “JP” instruction.

Around and Around and…

There is another problem with using a jump instruction to make a loop. If you enter and run the Basic “multiply by 7” loop, you will notice that the program stops only when the screen fills up and you get an error message. There are not any error messages in machine code—so, given a chance, the machine code routines we have just described will keep running forever, or at least until you pull the plug of the computer. That is not very helpful.

The solution: put a limit on the loop by choosing a point at which you want it to stop. For example, you can change the Basic version of the program to look like this:

10 LET A=1
20 PRINT 7*A
30 LET A=A+1
40 IF NOT (A=16) THEN GO TO 20

Each time the program goes through the loop, variable A gets higher. When the program hits line 40, it will GO TO line 20—as long as A is less than 16. Finally, after printing 15 numbers, A equals 16, and the computer will not GO TO 20—instead, it drops out of the loop, and the program ends. There is no more endless loop—and you do not need an error message to stop it any more.

Can we do it in machine code? Yes, but it is just a little tricky. In Z80 machine code, there is no single instruction exactly like the IF…THEN in Basic. The Basic command

40 IF NOT (A=16) THEN GO TO 20

becomes two machine code instructions: “compare what is in register A to 16, and, if it is not the same, then jump to the beginning of the loop.” That is abbreviated as

CP 16
JP NZ,LOOP

It is two instructions, one right after the other.

How can the computer make two separate instructions work together this way? It keeps track of what is going on by using flags.

Six Flags over Z80

Almost every time the computer performs a machine code instruction, it takes notes on what has happened. For example, after an “ADD A,N” or “SUB A,N” instruction, the processor notices whether the result was zero, or less than zero, or greater than 255, among other things. (It ignores a few kinds of instructions—for example, it does not take notes on the “load” instructions at all.)

The computer keeps track of all these things in a special “scratch-pad” register called the flag register. There are six flags that go up or down, depending on the result of the instruction. For example, when a “SUB A,B” instruction leaves a zero in register A, the “zero” flag goes up; if the result is not a zero, the zero flag comes down.

You can make a jump instruction depend on whether a particular flag is up or down.

To send the flag up or down in our loop, we will test the value in register A with a “compare” instruction. “Compare” is abbreviated “CP”; it compares what is in register A to some other number. For example,

CP B

will compare the value in register A with the value in register B.

CP 23

compares the value in register A with the number 23.

How does the processor compare them? It subtracts the number from the value in register A—but it does not actually change register A. In other words, it figures what the result would be—but it does not actually put the answer in register A, as a subtract instruction would.

Maybe you are wondering what good “CP” is—after all, you are not going to get an answer from it. But with a compare instruction, though you never see the result, the processor does take notes—it sets the flags, depending on what the answer would have been.

Get the idea? Once the flags are set, we can use a jump instruction that works only if the flags are the way we want them.

For example, suppose register A holds the number 42, and register B also contains the number 42. After a “CP B” instruction, since 42 minus 42 would be zero, the zero flag is up.

JP Z,location
JR Z,howfar

(“Location” and “howfar” are just dummies; in a real program you would have to replace them with an actual memory location or distance.)

Suppose registers A and B held two different numbers. Then ‘A minus B” would not be zero. These jump instructions will jump only if the zero flag is down:

JP NZ,location
JR NZ,howfar

These two jump only if the result is less than zero:

JP C,location
JR C,howfar

(The “C” stands for “carry.” That is because it is the same flag that goes up when you add two numbers together and the result is larger than a register can hold. It lets you know that you have to “carry” part of the result into another register or memory location. Do not get confused—it is completely different from register C.)

These instructions jump only when the result is greater than or equal to zero:

JP NC,location
JR NC,howfar

Here are a few examples to show how IF…THEN in Basic can translate into the compare-and-jump sequence:

IF NOT (A=45) THEN GO TO...          CP 45
(or IF A< >45 THEN GO TO....)        JP NZ,location
IF A=7 THEN GO TO...                 CP 7
                                     JP Z,location
IF A<B THEN GO TO...                 CP B
                                     JP C,location
IF A>=C THEN GO TO....               CP C
(or IF NOT (A<C) THEN GO TO...)      JP NC,location

Take Ten

Has this got you running in circles? Take a short breather. While you do, take a look at the brief machine code routine in Listing 1; it adds up the numbers from 1 to 10. See if you can follow exactly how it does it.

Listing 1.

       LD A,1         62,1,
LD HL,0 33,0,0,
LD B,0 6,0,
LD C,A 79,
LOOP: ADD HL,BC 9,
ADD A,1 198,1,
CP 11 254,11
JR NZ,-8 32,-8,
LD B,H 68,
LD C,1 77,
RET 201

You can put this 18-byte routine in memory by using the Basic program in Figure 1. Remember to save some space at the top of memory before you load it in.

First, reserve space at the top of memory like this:

4K ROM:

Type PRINT USR(620). Wait until the screen clears, then type PRINT USR(620) again.

8K ROM:

Run this program:

10 POKE 16388,-20
20 POKE 16389,PEEK 16389-1
30 NEW

Now, for either ROM, run the following program. Enter the numbers in the right-hand column of the machine code program listing, in exactly the order they appear. If the program is shorter than 18 bytes, fill each extra space by typing zeros.

Figure 1.

 10 LET Q=17390
20 FOR A=0 TO 17
30 PRINT A;"########";
40 INPUT B
50 POKE A+Q,B
60 PRINT PEEK (A+Q),
70 NEXT A
80 LET B=USR (Q)
90 CLS
100 PRINT B

This program is designed for a ZX80 or ZX81 with 1K RAM. For a 2K Timex/Sinclair 1000, change line 10 to read

10 LET Q=18414

If you are using a 16K RAM module, change it to

10 LET Q=32750

Multiplication Time

There is no machine code instruction to multiply two numbers on the Z80 microprocessor—the process is just a bit too complicated for its tiny electronic brain. Fortunately, it is not hard to fake multiplication by using addition and a loop.

For example, to multiply 17 by 23 in Basic—without actually multiplying—type in and run this program:

 10 LET A=17
20 LET B=23
30 LET H=0
40 LET H=H+B
50 LET A=A-1
600 IF NOT (A=0) THEN GO TO 40
70 PRINT H

See how it works? Starting with zero in H, you add 23 to it 17 times. The answer, naturally enough, will be 17 times 23.

You can translate this directly into the machine code. The translation is in Listing 2.

Listing 2.

       LD A,17        62,17,
LD BC,23 1,23,0,
LD HL,0 33,0,0,
LOOP: ADD HL,BC 9,
SUB A,1 214,1
CP 0 254,0,
JR NZ,-7 32,-7,
LD B,H 68,
LD C,L 77,
RET 201

Enter and run this routine using the program in Figure 1. Once you have tried it, you can replace 17 and 23 with other numbers. If you are using the 4K ROM, you will notice that sometimes the result will be negative. That happens when the product is larger than 32767, the highest number your computer can display. To get the correct answer, add 65536 to the negative number.

Optimize!

What is optimizing? It just means making a program more efficient. For example, take another look at the loop in our multiplication program. We can make the routine a little shorter—and a little faster—by using some special Z80 instructions that do not have any equivalents in Basic.

For example, there is the Z80 instruction increment, abbreviated “INC.” “Increment” means “add one.” In other words,

INC A

is almost exactly the same as

ADD A,1

(The difference is that “INC” does not send up all the same flags as “ADD.”)

The instruction decrement, or “DEC” for short, is exactly the opposite; it subtracts one:

DEC A

is very much like

SUB A,1

You can increment or decrement any of the regular registers—A, B, C, D, E, H, or L. And instead of taking two bytes of machine code, like “ADD” and “SUB” do, “INC” and “DEC” only take one byte.

But wait, there is more.

All four of these instrucions—”ADD”, “SUB”, “INC”, and “DEC”—will send up the zero flag if the result is zero. That means we can also get rid of the compare instruction that comes before the jump in our multiplication routine.

Here is how it works: each time it goes through the loop, the number in register A drops by 1. when it gets down to zero, the processor sends up the zero flag. We do not actually have to test register A with “CP O”—the flag has already been set. Now we can replace three instructions with two—and shorten the routine by three bytes in the process.

The new version of the routine is in Listing 3. Notice that there is a change in the jump instruction, too—now that we have shortened the loop by 3 bytes, the processor only has to jump back 4, instead of the original 7.

Listing 3.

       LD A,17        62,17,
LD BC,23 1,23,0,
LD HL,0 33,0,0,
LOOP: ADD HL,BC 9,
DEC A 61,
JR NZ,-4 32,-4
LD B,H 68,
LD C,L 77,
RET 201

Coming Attractions

…So now we can multiply—which is something much easier to do in Basic. Why multiply using a machine code loop?

The answer is simple: a loop is a loop. Once you have the hang of using it for something easy, like multiplying, you can start applying it to more interesting problem Next time we will use it to put something on your television screen, and you will see how fast your computer can be—even in “SLOW” mode. When a machine code routine prints something, it leaves Basic behind in a cloud of dust.

If you have comments or questions about machine code programming, or something is not quite clear, let me hear from you. Be sure to send along a stamped, self-addressed envelope if you need a reply.

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.