Machine Language Teaches the ZX80 to READ

See all articles from SYNC v1 n5

The Need for a READ

READ is a very useful statement. FORTRAN has it, and so do the Hewlett-Packard Basics. Microsoft, the company that wrote the Basics for most of the small American computers, has provided READ statements in its languages. However, the ZX80 does not have it. This is the one statement that I have missed more than any other in the Sinclair 4K Basic.

INPUT works fine if the program needs different data every time it is RUN. For example, a program to balance your checkbook would use INPUT to get the information about the check you wrote today. But many programs require large quantities of unchanging data in their arrays before they do their things. A lot of game programs fall into this category and so do some industrial control programs. This is normally the job of the READ statement.

In a game program, for instance, an array would be created by a DIMension statement, and then the necessary data would be READ into each element. In Basic it would look something like this:

10 DIM A(50)
20 FOR J=0 TO 50
30 READ A(J)
40 NEXT J
50 DATA 174,39,317,255,78,131,
286,228,224,152,158,186,247,241,
85,161,24,145,50,271,38,106,165,
95,313,206,95,261,80,58,259,296,
24,1,178,133,268,41,249,250,279,
294,66,323,179,115,81,66,93,200,
281

Listing 1. A READ statement as it might look on the Sinclair if it had one.

This part of the program would first open space in memory to store an array named “A” and having fifty-one storage locations (from A(0) through A(50) inclusive). It would then initialize A(0) with the value 174, A(1) with 39, A(2) with 317, and so on until it placed 281 into A(50).

Of course, there are ways to get around the READ deficiency. We could eliminate line 50 and change line 30 to:

30 INPUT A(J)

Then we would hand our friends a list of fifty-one numbers to type in each time that they wanted to play the game. Somehow, I think this would kill much of the fun! Furthermore, this way is sensitive to errors. Sometimes a program can be completely spoiled by a single error.

A better substitute for our missing READ statement would be:

20 LET A(0)=174
30 LET A(1)=39
40 LET A(2)=317
50 LET A(3)=255
60 LET A(4)=152
and so on until:
510 LET A(49)=200
520 LET A(50)=281

Listing 2. Initializing an array without a READ statement.

This would work! The data would be permanently in the program and the possibility of errors occurring each time you ran it would be eliminated. It has two disadvantages. It requires a lot of typing, and it ties up two-and-a-half to three times as much memory as would have been required if we had the READ statement to work with (as in Listing 1). The program of Listing 1 would require 221 bytes assuming that DATA and READ were one-byte keywords. On the other hand, the program of Listing 2 would require 644 bytes to get the same fifty-one numbers into the same fifty-one array locations.

So I decided to create a subroutine that would function a lot like the READ statement does in the native machine language of the Z-80 CPU (Central Processing Unit), and I could get at it with the USR function, when I needed it.

Using the USR

The ZX80 has a “USeR” function that tells the machine to do a machine language subroutine beginning at any place in memory that the programmer chooses. Suppose that you had a machine subprogram starting at memory address 14,336. Also suppose that this subroutine had to give us back some information. You might write the following:

210 ...
220 LET INFO=USR(14336)
230 ...

When the machine went to execute line 220, it would first run the USR subroutine and give back a number.

The Z-80 CPU has many internal locations in which it can store numbers that it is working on. We call these internal storage cells “registers.” When the computer comes back from running the subroutine, it would still have one more thing to do with line 220. It would have to copy the number in two of its registers into the variable named INFO. The new value of INFO would be equal to the value of the H and L registers (taken as a pair) at the instant that the machine language subroutine was completed.

This means that when I wrote the READ subroutine, I had to make sure that the number it was READing got into the HL register-pair before the Z-80 found the instruction to RETurn from the subroutine to the execution of the Basic line that called it. Keep this in mind if you plan to write subroutines of this type.

The programs in Listings 3 and 4 show what the USR function looks like from the programmer’s point of view.

 1 REM---------
10 POKE 16430,201
60 LET INFO=USR(16430)
70 PRINT "THE VALUE OF USR
=",INFO

Listing 3. USR returns the number 16430.

Line 1 is the REMark line where the machine code will be stored. The first thing that happens when you run this program is that line 10 POKEs 201 into a location in line 1. That number, 201, is the whole subroutine! It is the decimal form of the instruction telling the Z-80 to RETurn.

After line 10 puts the instruction into memory, line 60 tells the computer to go to the exact same spot and do whatever commands it finds there. But the Z-80 finds RET at the first location. So it makes no changes whatever; it simply comes back to the Basic program. Then it takes whatever number it finds in the HL register and puts a copy of it into the variable named INFO. When RUN, this program gives:

THE VALUE OF USR=        16430

This is the number that was in the HL register! It is also the number of the location where the subroutine starts. This tells me that the computer probably uses the HL register to help it get to the subroutine in the first place.

Now when I look at the program again, I notice that line 1 has changed. One of the dashes has changed to a question mark. That is because the POKE changed the contents of one of the memory locations being used to store line 1. Before we ran the program, that cell held number 220. When the computer looks up 220 in its table, it finds that it must display a dash. However, when it looks up the 201 that we put there instead, it finds that no display character has been assigned yet. So it jumps to an error routine and prints a question mark. There are twenty-five different numbers which can produce that question mark when POKEd into line 1. Typing a question mark is equivalent to POKEing the number 15. So if I want one of the others, I must use the POKE statement as I did here.

One of the most important things to understand about this method is that a number stored in that location can mean different things to the system at different times. That same 201 can be a low level instruction to the processor, or it can be a number that must be interpreted and transferred to the display file. Under different circumstances, it might even have been a statement in Basic that the machine would have to look up and execute. In each case the action is different.

Let me expand that program a little! I have not shown that the value of the HL register-pair becomes the value of USR. The following lines added to Listing 3 begin to do that.

 1 REM---?------
10 POKE 16430,33
20 POKE 16431,0
30 POKE 16432,0
40 POKE 16433,201
60 LET INFO=USR(16430)
70 PRINT "THE VALUE OF USR
=",INFO

Listing 4. USR will equal the value in 16431 plus 256 times the value in 16432, unless 16432 is more than 127 in which case USR will be negative.

The Z-80 recognizes the decimal number 33 (which line 10 will now place in memory) as the instruction to put the number that follows it in its L register, and the one after that in its H register. (When taken together, L is the lower part and H is the higher part of the pair.)

If you RUN it now, you will be told:

THE VALUE OF USR=        0

Zero is the number we told it to put in the HL pair! If you doubt it, change line 20 to POKE different numbers at that location. If you use values from 64 through 127, however, you stand a strong chance of confusing your computer so thoroughly that you might lose your program. For the moment, you will probably want to stick to the values from 0 through 63, and from 128 through 255.

I was fascinated by the changes in line 1 when I put different codes in it. The “Character and Keyword Appendix” of the user’s manual gives details of what each number stands for.

Supporting the Effort

By now, you can see that programming in machine code on the ZX80 is not the easiest thing in the world. Typing in all those POKE statements is difficult enough for a subroutine that occupies only four bytes, so imagine what it would be like for a program that filled 159 bytes as our READ subroutine does!

A Word about Hexidecimal

Another difficulty is that each variation of each instruction that the Z-80 has corresponds to one or more numbers. In order to know which number(s) I need, I have to look at a table. These tables are written in either a binary number code or a hexidecimal number code. I find the hex tables easier to use.

Since the ZX80 requires a decimal number code for its POKEs and returns a decimal number when it does a PEEK, it is up to us to do some converting.

The hexidecimal number system has sixteen possible numbers that can be put in any position:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, & F

Remember that A, B, C, D, E, and F are not letters here; they are numbers just like 8 and 9 are numbers.

For instance, the hexidecimal form of the instruction LD HL,NN, used in Listing 4 to get a number into HL, is 21. That is not twenty-one because it is not two tens and a one. It is two sixteens and a one, or the same as thirty-three in decimal.

The hexidecimal table lists the number for RETurn as C9. This means that there are C times sixteen plus nine times one. Since C equals 12, multiply 12 by 16 and add 9:

(12x16) + (9x1) = 201

In Listing 4 you will see that line 10 POKEs 33 and line 40 POKEs 201. These are exactly the values just computed.

Making It Automatic

When only three or four numbers are involved, this is not too demanding, but doing 159 numbers is another matter. Fortunately, a program can be written in Basic to eliminate the need for all those POKEs and conversions. Listing 5 gives such a program that will run on 1K. While it looks short, it runs into trouble if it tries to display all 159 bytes of the machine program. So, after you type in the 105th entry, it takes a moment to erase the screen so that you have room to finish the subroutine.

If you are running the program and want to stop, type S and then NEWLINE. To start again, type CONTinue and NEWLINE. When you do, the program will PEEK at all the locations that were on the screen before you stopped, convert them to hex, and display them again. So you will pick up exactly where you left off.

If, on the other hand, you find that you made a mistake five bytes back, you will first need to stop the program as above. Then you type in:

LET I=I-5

followed by NEWLINE. Then, when you start the program again, you will be five spaces back and ready to make the needed correction. The same is true if you want to look at the next twenty locations. Stop the program, add twenty to “I”, and then press CONTinue and NEWLINE.

When you are entering code with this program, you must type in both numbers. The ZX80 will not supply a missing zero. If you want 00, you must type 00 and not 0. A single zero will be entered as the hex number E5, and that is not the same at all.

Conclusion

In this article I have discussed what a READ statement does and why it is handy. The subroutine given above will duplicate the action of the READ statement, but it must be stored in a REMark to protect it and to allow us to SAVE it. The USR function is the means of retrieving the material in storage. Finally, a 1K program enables you to put material into the storage.

  1 REM -----------------------       ;160 minus signs reserve space for machine
-------------------------------- ; codes
-------------------------------- ; 23 in first line
-------------------------------- ; 32 in each of the next four lines
--------------------------------
--------- ; and 9 in the sixth line
2 DIM U(1) ;Machine subroutine uses U(0) for address
3 LET U(0)=16600 ; of next DATA and U(1) as a flag
5 LET R=16427

50 FOR I=R TO 16585 ;Start and end of machine code storage
60 IF I-R=105 THEN GO TO 160 ;Go make room in the display for the rest
70 INPUT C$ ; Enter machine code
80 LET C=CODE(C$)
90 IF C=56 THEN GO TO 150 ;If it was an "S" then go to "STOP"
100 IF C<28 OR C>43 THEN GO TO ;If not hexidecimal go back and try again
70
110 PRINT C$;" "; (2 sp.) ;Else display it with two spaces after it
120 POKE I,16*(C-28)+CODE(TL$(C ;Then convert and POKE it
$))-28
130 NEXT I
140 GO TO 999 ;Avoid running over

150 STOP
160 CLS ;Portion of routine displays codes that
170 LET S=R ; are actually stored in memory
180 IF I>R+104 THEN LET S=R+80 ;If too many locations will be displayed
190 FOR J=S TO I-1 ; delete first 80 and display the rest
200 LET K=PEEK(J) ;Look at the number in this location
210 LET L=K/16 ;Convert it and
220 PRINT CHR$(L+28);CHR$(K-L* ; convert some more and display it
16)+28;" "; (2 sp.) ; do not forget the two spaces
230 NEXT J
240 GO TO 70
999 STOP

Listing 5. Short (1K) version of Basic program accepts and converts hexidecimal machine code and stores it in its own line 1. (Enter “S” to STOP!) Hex codes between 40 and 7F can scramble the system when put in a REMark line, and must be avoided unless lines 5 and 50 are changed to store the codes where they will not be displayed.

To anticipate, Part 2 will present the READ subroutine and explain more about the “forbidden” codes, methods of getting along without them, and some of the features of the system.

For readers interested in more detailed study, I suggest the following works:

Barden, William, Z-80 Microcomputer Design Projects, Indianapolis: Howard Sams & Co., 1980. This book has me excited. It shows how to make a manual EPROM programmer and a small dedicated computer with software for several projects.

Barden, William. The Z-80 Microcomputer Handbook, Indianapolis: Howard W. Sams & Co., 1978. Good for its clarity and organization.

Nichols, Elizabeth A.; Nichols, Joseph C.; and Rony, Peter R. Z-80 Microprocessor Programming and Interfacing. 2 books. Indianapolis: Howard W. Sams & Co., 1979. Book 1 has the most complete tables. Both volumes fill certain other gaps. They give a number of experiments that apply to an Italian computer.

Zaks, Rodney, How to Program the Z-80. Berkley, CA: Sybex, 1979. This is a 624 page monster, but it seems exceedingly clear. It devotes an incredible 284 pages to the Z-80 instruction set.

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