Ever thought about learning to program in machine code?
Maybe you have. Maybe you even got far enough to open one of those thick books full of jargon and technical details. Maybe you got far enough into it to be scared away.
But chances are, you just could not make any sense of it. All the talk about hexadecimal and bus structures and pseudo-ops and gobbledygook….
Let us try a different approach. Machine code programming is, first of all, programming. You already know how to program in Basic; that means you are already on the way to programming in machine language.
What Is in a Name?
Every language has a name. Some languages have more names than they really need, and that is the case with machine code programming.
You may hear people talk about mnemonics, or source code, or assembler code, or assembly language. They will also refer to object code, or numerical code, or hex code.
Do not panic—it is all the same thing.
Take a look at Figure 1. On the left is a string of numbers, while on the right we see something that looks like a regular sort of programming language. They both represent the same program. The numerical version is for the Z80 microprocessor, the “brain” of your computer—it only understands numbers. Since it is a little tough for human beings to remember what all those numbers mean, the version on the right translates the program into words. But—line for line—both versions mean exactly the same thing.
It is easier for us to use a word like “ADD” than to remember what function 198 does, so we write machine code programs in this more human kind of language. Then it is a simple matter to convert each line into the numbers the computer understands. This way, no one has to remember the numbers; for machine code programming, you only need to learn a new programming language that uses words you can recognize.
Figure 1. Machine Code Program.
42, 12, 64 LD HL, (16396)
22, 22 LD D, 22
1, 254, 0 LD BC, 254
35 LOOP: INC HL
126 LD A, (HL)
254, 118 CP 118
40, 10 JR Z, NEXT
198, 128 ADD A, 128
119 LD (HL), a
237, 120 IN A, (C)
254, 63 CP 63
192 RET NZ
24, -16 JR LOOP
21 NEXT: DEC D
32, -19 JR NZ, LOOP
201 RET
Why Me?
Maybe you are thinking one taste of this is enough. Why should anybody have to go through all the trouble of figuring out this arcane language; except for a few things—such as “loop” and “next”—this is not even English. Maybe it is a better idea to stick to Basic.
Well … remember the first time you saw a computer program? Even if it was in Basic, you probably could not figure out what it meant. That is because Basic is another language; though it is similar to English, and uses English words, you had to learn a whole new set of rules for using the words.
Although it may not look much like it, that “human” version in Figure 1 is full of English words, too. You probably do not recognize them because they are all abbreviations. Some are for familiar words that mean just what they sound like: “RET” means “return,” which is the same as in Basic—it is for returning from a subroutine. “CP” is “compare,” for comparing one number to another.
Why should you learn a whole new language when you already know Basic? The answer is simple. Machine code is compact. Machine code is powerful. Most of all, machine code is fast.
How fast? Well, let’s try this program and see.
A Day at the Races
If your Sinclair computer cannot operate in SLOW mode, this demonstration might not be very impressive. But follow along anyway; it works with either the 4K or 8K ROM.
First, we cannot enter machine code directly into the computer. It will accept commands only in Basic. So we must make some space for the routine. With the 4K ROM you can do that by typing
PRINT USR (620)
That will reserve 12 bytes of memory at the top of RAM. This program is longer than that, so you will need to do it twice more. Be sure you type it in each time. You cannot just make a program out of it, because it works like a NEW command—it wipes out the whole program each time it runs.
With the 8K ROM, run this short program:
10 POKE 16388,-30
20 POKE 16389, PEEK 16389-1
30 NEW
That will give you 30 bytes of space at the top. You can change the amount of space you have by changing the POKE in line 10.
We will use the Basic program in Listing 1 to get our machine code routine working. Type it in—but do not run it yet! (If you have the 4K ROM, remember to use parentheses with all the functions.)
Listing 1. Loader Program for Machine Code Program.
10 LET Q=17380
20 FOR A=O TO 27
30 PRINT A; "########";
40 INPUT B
50 POKE A+Q,B
60 PRINT PEEK (A+Q),
70 NEXT A
80 FOR A=1 TO 100
90 NEXT A
100 LET B=USR Q
110 STOP
The program in Listing 1 is designed for 1K RAM. That is what comes on a ZX80 or ZX81. If you have a Timex/Sinclair 1000, you have 2K RAM, and you should change line 10 to read
10 LET Q=18404
If you are using a 16K RAM module, change it to
10 LET Q=32740
Now run the program, and enter the numbers in the left column of Figure 1. When you get to the last number, the screen should look like this:
0 42 1 12 2 64 3 22 4 22 5 1 6 254 7 0 8 35 9 126 10 254 11 118 12 40 13 10 14 198 15 128 16 119 17 237 18 120 19 254 20 63 21 192 22 24 23 240 24 21 25 32 26 237 27 201
If you have typed a wrong number, hit BREAK and start again.
Now hit ENTER (or NEW LINE) again, and watch what happens.
What did happen? Suddenly—very suddenly—the whole screen was reversed.
That is the speed of machine code. It is the fastest that the computer can work.
There is even a way to test how fast it really is. You can stop a program in Basic by hitting the BREAK key. This machine code routine can be stopped by hitting any key while it is running.
So run the program again—and this time, as soon as you see it starting to reverse the screen, hit a key to stop it. Good luck trying to catch it, though—it runs very, very fast.
Riding the Fast Track
What exactly is going on here? One way to get an idea is to look at a Basic version of the machine code routine. It is in Listing 2.
Listing 2. Basic Version of Figure 1.
200 LET D=22
210 LET H=PEEK 1696+256*PEEK 16397
220 LET H=H+1
230 LET A=PEEK H
240 IF A=118 THEN GOTO 280
250 LET A=A+128
260 POKE H, A
270 GOTO 220
280 LET D=D-1
290 IF D>O THEN GOTO 220
300 RETURN
The subroutine works by using one of the Basic system variables, a number called D-FILE. This tells the computer where, in memory, what is on the TV screen is kept. Then the program runs through all 22 lines of the display (that is why D equals 22 to begin with). It adds 128 to the value of each character (that is, reverses it) except for the special, invisible NEWLINE character at the end of each line (that is the IF in line 240).
Even if you do not quite follow exactly how it works, you can type it in along with the program you already have. Replace lines 40 and 100 with
40 GOTO 60
100 GOSUB 200
You will see the difference. In Basic, it is slow. In machine code, it is fast.
You know what that means. The trouble with most programs is they are not fast enough. That is especially true with games; it seems to take forever for things to move around on the screen in Basic.
You have already seen how fast they can move in machine code.
Ready to ride the fast track? I will try to keep the gobbledygook to a minimum, and explain things as we go along. With some practice, you will not only be able to run machine code programs, but you will also understand what is going on—and even start writing machine code routines yourself.
As Easy As 2 + 2
Let’s start out with something a little simpler than that fancy screen-blitzing program: adding 2 and 2. In Basic, it is pretty simple:
LET A=2+2
will do the trick. With machine code, it is only a little more complicated:
LD A,2
ADD A,2
Yes, ADD means just what you think it does—but first things first.
LD is short for “load.” In Basic you LOAD a program (from your cassette recorder). In machine code, you “load” a number. As you might suspect, “load” is almost exactly like LET in Basic. So
LD A,2
means the same thing as
LET A=2
—both lines put the number 2 into the variable A.
… Except that it is not exactly a variable in the machine code version. The A is a register—a special memory location that is located right inside the Z80 microprocessor that runs your computer. Unlike a Basic variable, a register can only hold a number from 0 to 255. However, being inside the brain of the computer, a register can move numbers around much faster than a Basic variable can.
The Z80 has more than 20 registers. Some of them we cannot use without crashing the system of the Sinclair computer, because they are used almost constantly in the SLOW mode. Let’s start out by looking at another half dozen registers that we can use.
Their names are registers B, C, D, E, H, and L. (These one-letter names are really just for the benefit of programmers. To the computer, they are all just numbers.)
The registers work like Basic variables. Just as you can
LET B=C
in Basic, you can
LD B,C
in machine code. You can load any of these registers into any other register. You can also load any number from 0 to 255 into any of the 7 registers—for example,
LD E,67
LD H,212
Bigger and Better
Better still, you can load in even bigger numbers—up to 65535—by using two registers at once. For example,
LD BC, 32000
or
LD HL,999
Maybe you are wondering how this works. After all, you are not just adding these two registers together. 65535 is hundreds of times higher than 255.
It works something like regular decimal numbers. Suppose you write a number with just one digit. The largest number you can write is 9.
Now write a number with two digits. Suddenly the highest number you can reach is 99. The new digit—the one on the left—is worth 10 times as much as the one on the right. That is why the biggest number you can get with two digits is more than 10 times what you can reach with one digit.
When you use two registers, the second register is worth 256 times what it usually is.
But you cannot use just any two registers together this way. They work together in pairs; the pairs are always B and C, D and E, or H and L. They are sometimes called the BC register pair, the DE register pair, and the HL register pair, and it is always the first register—B, D, or H—that is worth 256 times its usual value.
So, for example, if you
LD BC,32000
the computer puts 32000/256 (that is 125) into register B, and the remainder (in this case, 0) into register C. It has the same effect as
LD B,125
LD C,0
except it is only one instruction instead of two. Similarly,
LD HL,999
has the same effect as
LD H,3
LD L,231
Maybe you have noticed that register A is not part of a register pair. It has not really been left out: register A is special. It can do lots of things that the other registers cannot.
Like adding, for instance.
Add Another Instruction
In Basic you can add to any variable. In machine code, though, you can only add to one of the registers, register A. You can also add to just one of the register pairs—the HL register pair.
In other words, you can say
ADD A,B
which works like
LET A=A+B
in Basic. Likewise, you can say
ADD HL,BC
which in Basic might be
LET HL=HL+BC
Do not get confused, though. You can only add a single register, or a number from 0 to 255, to register A, and you can only add a register pair. You cannot mix the two kinds—for example, you cannot add register pair A directly to register pair HL.
What if you wanted to add register A to register pair HL? You might do it like this:
LD B,0
LD C,A
ADD HL,BC
Once you have copied what is in register A into the BC register pair, you can add that to HL with no trouble.
What if you wanted to add register A to register pair HL? You might do it like this:
LD B,O
LD C,A
ADD HL,BC
Once you have copied what is in register A into the BC register pair, you can add that to HL with no trouble.
USR Friendly
Maybe you are feeling a little overloaded with machine code just now. Well, take a deep breath and relax for a moment, while we talk about something in Basic: the USR function.
USR is how we can run a machine code subroutine in the middle of a Basic program. When the computer hits a line like
PRINT USR 17380
it stops what it is doing and goes to work on the machine code routine beginning at memory location 17380.
It keeps working on it until it hits a RET instruction—the machine code “return.” Then it returns to Basic, and picks up where it left off. This is a lot like GOSUB—only the subroutine is machine code, not Basic.
But wait—that line has a PRINT in it. What gets PRINTed on the display?
That depends. If you have the 8K ROM, the computer PRINTs the number that was in the BC register pair. If you are using the 4K ROM, it PRINTs the HL register pair.
You can use the USR function just like any other number. For example, you might say
LET A=USR 17380
or even
PRINT CHR$ (USR 17380)
This is very handy because it means that we can solve a problem in a machine code subroutine and then send the answer back to our main Basic program by loading the answer into HL or BC before we return to Basic.
It All Adds Up
Ready to add 2 and 2 now? Here is how we will do it—first, the addition:
LD A,2
ADD A,2
Then the result goes in register pair BC for the return to Basic:
LD B,O
LD C,A
For the benefit of 4K ROM users, we will put it in HL, too:
LD H,B
LD L,C
Finally, we return to Basic:
RET
Figure 2 shows this program, along with the numerical version the computer understands. (You can check the numbers in the appendix section of your ZX81 or T/S 1000 manual, under the heading “The Character Set.”)
Figure 2. Machine Code Program for Adding 2 and 2.
62, 2 LD A,2
198, 2 ADD A,2
6,0 LD B,O
79 LD C,A
96 LD H,B
105 LD L,C
201 RET
Now you are ready to run it.
First, reserve space at the top of memory, as you did for the previous program. Then carefully type in the program in Listing 3. If you are using a system with more than 1K RAM, remember to change line 10 accordingly.
Listing 3. Loader Program for Figure 2.
10 LET Q=17380
20 FOR A=0 TO 9
30 PRINT A,
40 INPUT B
50 POKE A+Q,B
60 PRINT PEEK (A+Q)
70 NEXT A
Now run the program and type in the ten numbers in Figure 2. When you are finished, check carefully to make sure you have everything in the right order. It should look like this:
0 62 1 2 2 198 3 2 4 6 5 0 6 79 7 96 8 105 9 201
Now you are ready. Type
PRINT USR Q
and the answer on your screen should be—surprise!—4.
Care to experiment? You can add two different numbers together. Just change the 2’s to other values. To add 7 and 3, for example, run the program in Listing 3 again. This time, the first four numbers you type should be
0 62 1 7 2 198 3 3
The rest of the numbers are as before. Type
PRINT USR Q
again to get the new result.
Try your own numbers. Remember, they should be between 0 and 255. If you use large numbers, notice what happens when the sum is larger than 255.
Coming Attractions
Adding 2 and 2 may not seem like a big deal—but it is just the beginning. Next time we will take a look at loops, and see how fast machine code can really run.
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.