An Introduction to Machine Code

See all articles from SYNC v1 n6

The managing editor and I are frequently asked about how one starts to use machine code on a ZX80/1. So this article is an attempt to reply to these questions, and I trust that you will find that machine code is not only for the expert.

An Outline View

The ZX80/1 microcomputer system as supplied by Sinclair Research is capable of being programmed in two different languages, i.e., Basic and Machine Code.

Basic is a very easy language to use for the beginner and, as long as one’s programs are simple, the language is almost ‘ideal.’ However, Basic is a rather ‘slow’ language and limited in its commands.

Machine code, however, is a much more difficult language to use. The resultant programs are executed by the Z80 microprocessor at a fantastic speed and the complexity of the programs is limited more by the knowledge of the programmer than by the actual microcomputer.

It is always difficult to explain to the ‘beginner’ just how to write a machine code program, but in this article we will begin by drawing upon the similarities between Basic and machine code.

Program Structure

A Basic program is made up of a set of Basic lines. In the ZX80/1 system these lines are kept in an area of the RAM (random access memory) that is termed by Sinclair as the PROGRAM AREA. When the user first turns on the machine, this PROGRAM AREA is empty, and the user will then proceed to enter a program into this area. The program can be as short as a single line, e.g., 10 PRINT or can be several hundred Basic lines. The user will then RUN the program, and this will result in the system interpreting line after line of the program, as has been determined by the programmer, until the ‘last’ line has been reached.

A machine code program is in many ways dealt with in a similar manner. First, the programmer must decide just what part of the RAM he is going to designate as his ‘machine code area.’ It is possible in the ZX80/1 systems to choose an area from several different parts of the RAM but my favorite technique is to reserve part of the PROGRAM AREA by using a REM statement. The next task is to actually enter the machine code into the RAM and this has to be done by using POKE commands. An actual machine code program entered in this fashion can be made up of just a single instruction or many thousands of instructions. This program is then ‘run’ by using a USR command which is either a single line Basic program, e.g.,

10 LET A=USR(16427)

or a USR command occurring in a longer Basic program, in which case the machine code program becomes a ‘machine code subroutine’ of the Basic program. Note how the USR command has to be followed by a number. This number is the address of the location within the machine code area where the machine code program begins.

Instruction Format

All Basic lines can be described as containing an obligatory ‘operator’—the command—and an optional ‘operand.’ The line

10 PRINT

contains only the ‘operator’ PRINT whereas the line

20 PRINT A

contains the ‘operator’ PRINT and the part that is to be printed, the ‘operand’ A. Note how the Basic line has the ‘operator’ coming before the ‘operand.’

This division of a line into an ‘operator’ and an ‘operand’ is an essential part of Basic syntax and the ZX80/1 systems with their ‘syntax checking’ facility ensure that the user has no difficulty remembering to place his ‘operators’ before his ‘operands.’

Just as it is in Basic so it is in machine code, but there are hundreds of different ‘operators,’ as opposed to the 20 or so in Basic.

Whereas a Basic program is made up of ‘decimal numbers and letters,’ a machine code program consists of only a set of numbers. These numbers can be considered to be in binary, decimal or hexadecimal arithmetic, but for users of the ZX80/1 systems the use of the decimal values is the easiest method, although the ‘expert’ will usually only think in hexadecimal arithmetic.

So what are the ‘operators’ in machine code? Well, they are the decimal numbers 0-255, (hex. OO-FF), but since more than 256 ‘operators’ are required, the numbers 203, 221, 237, and 253 (hex. CB, DD, ED, and FD) introduce a second decimal number into the ‘operators.’

In Basic the ‘operators’ are commonly called the ‘commands’ and in machine code the ‘operators’ are called the ‘instructions.’ Fortunately, one does not have to memorize all the different numbers as each instruction has been given a descriptive ‘mnemonic’ and most programmers only ‘look-up’ the numbers when they need them.

The ‘operands’ in machine code are also numbers in the range decimal 0-255, (hex. OO-FF), and these ‘operands’ are placed after the instructions proper when they are needed.

A machine code program may also contain ‘data.’ Once again this will be in the form of locations holding decimal numbers in the range 0-255.

All this is better illustrated by the example in Figures 1 and 2.

A Simple Basic Subroutine     Comment

10 LET Z=1                    'operator' is 'LET Z', 'operand' is '1'
20 LET Z=Z+6                  'operator' is 'LET Z', 'operand' is 'Z+6'
30 RETURN                     'operator' is 'RETURN'.

[Figure: Figure 1.]

The Machine Code Subroutine           Comment

mnemonic    | Decimal | Hex.
            |         |
LD  A,+1    |   62    | 3E      Load the 'A' register with
            |    1    | 01      the constant 1.
------------+---------+------------------------------------
ADD  A,+6   |  198    | C6      Add the constant 6 to the
            |    6    | 06      value in the 'A' register.
------------+---------+------------------------------------
RET         |  201    | C9      Return to the calling routine.

[Figure: Figure 2.]

Note that the machine code subroutine would occupy 5 locations and would be entered by:

50 POKE 16427,62
52 POKE 16428,1
54 POKE 16429,198
56 POKE 16430,6
58 POKE 16431,201

and ‘run’ by using:

60 LET A=USR(16427)

Note: Reserve 16427-16431 first (16514-16518 for 8K).

Variables v. Registers

In a Basic program there are two different ways of handling variables. The first is to use ‘named variables,’ e.g., A,B,COUNTER, and this is very much the standard method. However, there is an alternate method that involves the use of ordinary memory locations to which the user will assign values as necessary. This second technique is commonly used in games that use the display file. E.g., if location 16800 is a ‘certain point’ on the screen, then ‘POKE 16800,…’ will assign the required value and ‘PEEK 16800’ will collect the value of the variable.

A machine code program normally uses this second method. That is, the programmer first selects certain locations that he wishes to be filled with ‘named variables’; however, these ‘names’ are only known to the programmer and not the ‘computer.’

It is possible though to take the general concept of the ‘Basic named variables’ a little further and draw a useful analogy between the use of certain Basic variables and the internal registers of the Z80 microprocessor.

In a Z80 microprocessor as used in the ZX80/1 there are many ‘registers.’ These registers can be considered as ‘named variables’ in an internal ‘variable area.’ Each is equivalent to an ordinary memory location in that it can hold a number which has the decimal range 0-255 (hex.OO-FF). The simple registers are the A, H, L, B, C, D, and E registers. The full set of registers is shown in Figure 2.

Although the registers are equivalent to ‘one memory location,’ there are many times when it is desirable to use a pair of registers that would thereby have the equivalent of ‘two locations in memory.’ The simplest register pairings are those of the H and L registers, the B and C registers and the D and E registers. Usually these are written as HL,DE, and BC. Such register pairs can be considered to be able to hold numbers in the range decimal 0-65535 (hex.OOOO-FFFF).

            The Registers of the Z80 Microprocessor

 [ A ]                       [   1R   ]   [ B'C' ]

 [ H ]   [ L ]               [   1X   ]   [ D'E' ]

 [ B ]   [ C ]               [   1Y   ]   [  SP  ]

 [ D ]   [ E ]               [  A'F'  ]

                             [  H'L'  ]
                                    [ F ]

 The simple registers.       The more complicated registers.

[Figure: Figure 3.]

The registers of the Z80 can therefore be considered as follows:

The A register is a variable named ‘A’.

The H register is a variable named ‘H’

and so on for all the simple registers named above. The register pairs can be considered as:

The HL register pair is a variable named ‘HL’.

The BC register pair is a variable named ‘BC’.

The DE register pair is a variable named ‘DE’.

Actual Machine Code Instructions

Now that the analogy has been made, it is possible to use the variables A, H, L, B, C, D, E, HL, DE, and BC to explain the more simple of the 600+ instructions of the Z80 machine code language.

1) Loading Constants.

The simplest instructions are those that are used to load a register or a register pair with a ‘constant.’ For example, in the instruction ‘LD A,+dd’, the actual code would be two bytes. The first is a decimal 62, (hex.3E), and the second, the value of the constant itself. This instruction can be considered to have the same result as a Basic line:

LET A+ …… a constant,

when the variable A is located in the microprocessor. In the instruction ‘LD HL,+dddd’ the code is three bytes. The first is decimal 33 (hex.21) and the following two are the constant. Note that the constant always appears with the ‘remainder’ coming before the integer of the ‘constant/256.’ This instruction would be equivalent to:

LET HL = … a constant.

or more precisely:

LET L = ‘remainder’ and
LET H = ‘constant/256’

2) Loading Registers from memory Locations

There are only two simple instructions in this group. The first instruction is ‘LD A,(addr.)’ which is a three byte instruction. The first number is a decimal 58 (hex.3A) and whose other two bytes are the ‘address in memory’ of the location that is to be copied. Note that the address is once again to be entered as the ‘remainder’ followed by the ‘address/256.’

The Basic equivalent of this instruction is:

LET A=PEEK(1st+2nd,*256)

The other instruction is for loading the HL register pair, and the mnemonic is ‘LD HL,( addr.).’ Again, this is a three byte instruction. The first byte is decimal 42 (hex.2A), and the other two bytes are the address again.

The Basic equivalent is:

LET HL=PEEK(1st+2nd*256)+256*
PEEK(1st+2nd*256)

or more simply:

LET L= PEEK(addr.)
and LET H=PEEK (addr.+1)

3) Three Further Instructions

It is beyond the scope of this article to detail more than just a few of the instructions in the Z80 machine code instruction set, but the following instructions will be used in the game below.

a) The contents of most registers can be copied into another register by using the appropriate instruction.

E.g., the instruction ‘LD E,A’ copies the contents of the A register into the E register. The instruction code is decimal 95 (hex.5F).

The Basic equivalent would be:

LET E=A

b) The contents of the DE register pair can be added to the contents of the HL register pair by using the instruction ‘ADD HL,DE.’ This instruction has the code decimal 25 (hex.19).

The Basic equivalent would be:

LET HL=HL+DE

or if preferred:

LET L=L+E
and LET H=H+D+ carry if present.

c) The last instruction of any machine code program must always act as a ‘RETURN.’ It is easy to understand that this can be performed by the straightforward instruction ‘RET’ whose code is decimal 201 (hex.C9), but it is often found that the ‘return’ is made by using a ‘stack-handling’ instruction instead.

The Basic equivalent of the ‘RET’ instruction is simply:

RETURN

Once the reader has understood just how instructions are used, it is fairly easy to gradually use the more complex instructions. Suitable lists and tables of all the instructions are to be found in all books on machine code programming, or if the reader prefers he can just take notes on ‘new’ instructions as he finds them in different programs.

The Demonstration Game

The following Basic program includes many features that can be easily ‘machine coded.’ However, as with many similar programs, there is no genuine advantage to replacing Basic lines with machine code subroutines, except from the point of interest.

If the reader wishes to try writing the whole of a ‘Basic program’ in machine code, then I very much suggest that he use a ZX80 with the 8K ROM and a ‘slow converter,’ or a ZX81, as machine code programming in ‘slow’ mode is the easiest for larger programs.

The Tower Game

There are two towers of ‘bricks’ and a single brick is taken from one tower and placed on the other tower. The choice of the ‘declining’ tower is made at random. The game is over when only a single tower remains.

The larger usable RAM and the compact integer handling of the ZX80 with a 4K ROM allows for a ‘more complicated’ game than on a ZX80 with the 8K ROM or a ZX81. Therefore, the 8K version is slightly different overall but does use the identical central algorithm.

The Tower Game (8K ROM; 1K RAM)

                                      Comment.
20 RAND Different each time.
30 DIM L(2) The two towers.
40 LET L(1)=62 The pointers to the tops of the
50 LET L(2)=69 towers in the Display File.
60 FOR A=1 TO 4
70 PRINT 9-A;"############" 12 spaces.
80 NEXT A
90 FOR A=1 TO 4
100 PRINT 5-A;"####[gA]######[gA]" 4sp., graphic A, 6 sp., grap. A.
110 NEXT A
120 PRINT "###[g7][g7][g7][g7][g7]##[g7][g7][g7][g7][g7]"
3 sp., 5 grap. "7", 2 sp., 5 grap."7"
130 LET C=0 Initialise the move counter.
140 PAUSE 50 | FAST or 140 FOR D=1 TO 20 | SLOW
150 POKE 16437,255 | 150 NEXT D |
160 LET R=INT (RND+1.5) Randomly choose a tower.
170 LET P=1+(R=1) If R=1 then P=2, and vice versa.
180 LET A=PEEK 16396+PEEK 16397 Pick up the start of the D-File.
*256
190 POKE A+L(R),0 Remove a brick.
200 LET L(R)=L(R)+14 Point to "new" top of tower.
210 LET L(P)=L(P)-14 Point to space above the other tower.
220 POKE A+L(P),8 Put the brick into place.
230 LET C=C+1 Count the "move."
240 IF L(R)<118 THEN GOTO 140 If two towers exist, then go back.
250 PRINT "DID YOU GUESS#";C;"# Did you do well?
MOVES?"

The Tower Game (4K ROM; 1K RAM)

                                      Commment.
20 RANDOMISE Different each time.
30 DIM L(2) The two towers.
40 LET L(1)=62 The pointers to the tops of the
50 LET L(2)=69 towers in the Display File.
60 FOR A=1 TO 4
70 PRINT 9-A;"############" 12 spaces.
80 NEXT A
90 FOR A=1 TO 4 The towers.
100 PRINT 5-A;"####[gA]######[gA]" 4 spaces,shifted "A",6 spaces,shifted "A"
110 NEXT A
120 PRINT "###[gG][gG][gG][gG][gG]##[gG][gG][gG][gG][gG]"
3 spaces,5 shifted "G".2 spaces,5
130 PRINT shifted "G" forms a base to the towers.
140 FOR G=RND(23)+4 The computers guess.
150 PRINT "YOUR GUESS(4-27)"; Make a guess for the number of
160 INPUT Y moves to eliminate a tower.
170 IF Y<4 OR Y>27 THEN GO TO 1 A suitable range. But not the
60 actual range.
180 PRINT "-#";Y;",#MINE-#";G," Watch the spacing!
PRESS N/L"
190 LET C=0 Initialise the move counter
200 INPUT A$ Wait for a N/L, before each "move".
210 LET R=RND(2) Randomly choose a tower.
220 LET P=1-(R=1) If R=1 then P=2, and vice versa.
230 LET A=PEEK(16396)+PEEK(1639 Pick up the start of the D-file.
7)*256
240 POKE A+L(R),0 Remove a brick.
250 LET L(R)=L(R)+14 Point to "new" top of tower.
260 LET L(P)=L(P)-14 Point to space above the other tower.
270 POKE A+L(P),9 Put the brick into place.
280 LET C=C+1 Count the "move."
290 IF L(R)<118 THEN GO TO 200 If two towers exist, then go back.
300 PRINT "IT WAS#";C;"#MOVES A An end message.
ND#";
310 IF ABS(G-C)<ABS(Y-C) THEN G Whose guess was the better?
0 TO 340
320 PRINT "YOU WON" Well done.
330 STOP
340 PRINT "I WON" Oh dear. Out classed by the ZX80.

A First Machine Code Subroutine

The easiest Basic line to convert to machine code is the line:

4K: 230 LET A=PEEK(16396)+PEEK
(16397*256
8K: 180 LET A=PEEK 16396+PEEK
16397*256

which picks up the address of the start of the Display File.

In the 4K program one proceeds as follows:

1) Replace line 230 by:

230 LET A=USR(16427)

2) Enter a line 10

10 REM 1234

that reserves 4 locations for a machine code routine. The address of the first of these locations being 16427.

3) Remove line 10 from the listing by using, for example:

LIST 250

and from now on do not use HOME or LIST as this will cause problems. (The problems can be solved by using after a HOME, etc., Cursor down, cursor down & POKE 16403,20 if needed)

4) Enter:

POKE 16427,42    (hex. 2A)
POKE 16428,12 (hex. 0C)
POKE 16429,64 (hex. 40)
POKE 16430,201 (hex. C9)

which will enter a 4-byte machine code routine into the 4 reserved bytes of line 10.

The ‘mnemonics’ for this program are:

LD HL,(D-File)
RET

where the address of D-File is 16396 in decimal, which splits into a remainder of 12, and 64*256.

5) Run the program. The reason for this routine working is that the expression:

USR(16427)

returns to the Basic program the current value of the HL register pair.

In the 8K program one proceeds in a similar manner.

1) Replace line 180 by:

180 LET A=USR 16514

2) Enter a line 10:

10 REM 123456

that reserves 6 locations for the machine code. The starting address being 16514.

3) Enter:

POKE 16514,42    (hex. 2A)
POKE 16515,12 (hex. 0C)
POKE 16516,64 (hex. 40)
POKE 16517,68 (hex. 44)
POKE 16518,77 (hex. 4D)
POKE 16519,201 (hex. C9)

which will enter a 6-byte machine code routine into line 10.

4) RUN the program.

The ‘mnemonics’ for this program are:

LD HL,(D-File)
LD B,H
LD C,L
RET

where as before the address of D-File has to be split into ’12’ and ’64’.

The two new instructions ‘LD B,H and LD C,L’ are needed as the 8K ROM returns the value of the BC register pair rather than the HL register pair.

A Second Machine Code Routine

If you have followed the article so far, you might now like to try a longer machine code routine. Several new instructions will be introduced.

In the Tower Game the start of the Display File is used as a base address to which the variables L(R) and L(P) are added in turn. The resultant address then points to the location that is to be filled with a specific value. All of this procedure can be easily performed in machine code.

In the 4K program one proceeds as follows:

1) Replace lines 230 and 240 by:

230 POKE 16431,L(R)

which transfers the current value of L(R) to a suitable location for the machine code routine and

240 LET A=USR(16427)

which runs the machine code routine. Note how the variable A is just a dummy variable, i.e., not used later.

2) Replace line 270 by:

270 POKE 16441,L(P) and
275 LET A=USR(16437)

3) Replace line 10 by:

10 REM 12345678901234567890

4) And fill line 10 by using a loader such as:

500 FOR A=16427 TO 16446
510 INPUT B
520 POKE A,B
530 NEXT A
RUN 500

and enter the code:

42,12,64,17,0,0,25,54,0,201,42,12,64,17,0,
0,25,54,9,201

5) Now delete the loader in lines 500-530 and RUN the whole program.

The machine code routine is given in Figure 4.

16427 LD   HL,(D-File)  Pick-up D-File.
LD DE,dddd The offset.
ADD HL,DE Form new
address.
LD (HL),+00 Blank out this
location.
RET Finished.
16437 LD HL,(D-File) Pick-Up D-File.
LD DE,dddd The offset.
ADD HL,DE Form new
address.
LD (HL),+09 Put a 'brick' in
this location.
RET Finished.

[Figure: Figure 4.]

The instruction ‘LD DE,+dddd’ loads a 2-byte constant into the DE register pair. In the routine the first byte is altered as required whereas the second byte always stays as zero. The instruction ‘LD (HL),+dd’ is used to load a constant into the location whose address is the current value of the HL register pair.

In the 8K program one proceeds in a similar manner.

1) Replace lines 10,180,190 and 220 by:

10 REM 12345678901234567890
180 POKE 16518,L(R)
190 LET A=USR 16514 or
190 RAND USR 16514 (which looks nice)
220 POKE 16528,L(P)
225 LET A=USR 16524 or
225 RAND USR 16524

2) Load line 10 by using:

500 FOR A=16514 TO 16533
510 INPUT B
520 POKE A,B
530 NEXT A
RUN 500

and enter:

42,12,64,17,0,0,25,54,0,201,42,12,64,17,0,0,
25,54,8,201.

which is the same routine as given for the 4K version but with ‘graphic 8’ instead of ‘graphic 9.’

3) Delete lines 500-530 and RUN the program.

The reader is now encouraged to try his own hand. For example, the variable C can be replaced entirely. This will, however, probably require the use the instructions in Figure 5.

                     dec.  hex.
LD    A,+dd          62    3E     = LET A= ....
LD    A,(addr.)      58    3A     = LET A=PEEK.....
LD    (addr.),A      50    32     = POKE ... , A
INC   A              60    3C     = LET A=A+1

[Figure: Figure 5.]

A Bibliography

For those readers who wish to delve further into machine code, the following books are available (at least from U.K. suppliers).

Understanding Your ZX81 ROM by Ian Logan. £8.95. The Essential Software Company (Visconti Ltd.), 47, Brunswick Centre, London W1 CN 1AF, and other Melbourne House outlets. Need I say anything more than that this book deals extensively with the use of machine code in the ZX80/81 systems.

Mastering Machine Code on Your ZX81 or ZX80 by Tony Baker. £5.95. Interface, 44, Earls Court Road, London W8 6EJ. “Speak kindly of one’s rivals and they will be kind to you.”

Machine Language Programming for Your ZX80 & ZX81 £8.95. Melbourne House Publishers, 131, Trafalgar Rd., London SE 10.

The currently available books about the ZX81 are:

The ZX81 Campanion by Bob Maunder. £7.95 Linsac, 68, Barker Road, Linthorpe, Middlesborough Co. Cleveland TS5 5ES. A very good book. Deals more with ‘computing theory’ and less with the monitor than its predecessor.

The ZX81 Pocket Book by Trevor Toms. £4.95. Phipps Associates, 3, Downs Avenue, Epsom, Surrey KT 18 5HQ.

From Interface:

Getting Acquainted with your ZX81 by Tim Hartnell. £4.95.

30 Amazing Games for the ZX81 by Alistair Gourlay. £3.95.

50 Rip-Roaring Games for the ZX80 and ZX81. £4.95.

Hints & Tips for the ZX81. £4.25. Hewson Consultants, 7, Grahame Close, Blewbury, Oxon. OX 11 9QE.

Not Only 30 Programs for the Sinclair ZX81. £6.95. Essential Software Company (Visconti Ltd.), 47, Brunswick Centre, London W1 CN 1AF, and other Melbourne House outlets.

Again I would welcome seeing any programs written as a consequence of this article.

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