“Hello, Z80 Calling…”

Authors

Harry Doakes

Publication

Publication Details

Volume: 3 Issue: 6

Date

November/December 1983

Pages

64-70
See all articles from SYNC v3 n6

In the last installment of this series on Z80 machine code programming, we translated a program that took more than 200 bytes of machine code. It was so big that it could not quite fit into a computer with less than 2K RAM.

This time, we will look at ‘ROM calls’ the machine code subroutines stored in the ROM of your computer. They can help keep your Z80 programs shorter, and save programming time and effort.

But before we use ROM subroutines, let’s take a look at machine code subroutines in general, and, to understand them, we will first look at a feature the Z80 microprocessor has that Basic does not share โ€” the stack.

Let’s start with an example in Basic. Suppose you say

LET X=5

Then the variable X will have a value of 5. You could also say

LET X=5
LET X=62
LET X=297
LET X=5

but when you are finished with this string of LET statements, X will once again equal 5, not 62 or 297. Every time you LET X equal a new number, it completely forgets everything else it ever knew.

But two special types of machine code instructions for the Z80 microprocessor can help solve that forgetfulness problem. They are the PUSH and POP instructions, and they have to do with the stack.

The stack is like a listโ€” a list of numbers, stored somewhere in memory. The instruction PUSH adds another number to the list. POP, on the other hand, retrieves the last number that was added, and crosses it off the list.

Suppose you want to save the number that is in the HL register pair, but you do not want to put the number into a variable. You can use the instruction PUSH HL and the number is added to the list. It is safe until you POP HL when it drops off the list and back into the HL register pair.

In the meantime, you can also PUSH and POP other numbers. They will all be safe on the listโ€” in exactly the same order that you pushed them. For example, if you

PUSH HL
PUSH DE
PUSH BC

all three numbers go on the list, in order. You can then use those registers for something else, if you like, until it is time to get the numbers back again. Then you just reverse the process:

POP BC
POP DE
POP HL

and the original numbers you PUSHed are back again, safe and sound.

Safety First

What good is all this PUSHing and POPping? Well, remember that there are only a handful of registers in the Z80 processor. Sometimes you need to save a number, but it is just not convenient to make a machine code variable. The stack is a quick and convenient way of saving that number.

There is a disadvantage, though: you must remember what order you put numbers on the stack. If you forget, and POP the numbers in a different order than you PUSHed them, you will end up with numbers in the wrong registers.
For example, with:

PUSH DE
PUSH HL
POP DE
POP HL

the number that started in register pair DE ends up in HL, and what started in HL ends in DE.

Of course, any disadvantage can be an advantage, too. If you PUSH and then POP numbers in the “wrong” order, you have an easy way to switch numbers between register pairs.

Mechanics Illustrated

How does it work? It is really pretty simple.

The Z80 processor has a special double-sized register, the stack pointer, called register SP for short. Remember how a register can be used as a pointer? It is like PEEK in Basic: the register points to a specific location in memory. For example, if register pair HL contains the number 75, then LD A,(HL) will get whatever number is in memory location 75, and put it in register A. In Basic, you could say:

LET A = PEEK(HL)

It also works the other way:

LD (HL),A

is very much like Basic’s

POKE HL,A

Now remember: the stack is a list, and register SP always points to the last number you added to the list.

The program counter keeps track of where the computer is in your machine code program and always points to the beginning of the next instruction.

When you PUSH a register pair, e.g., register pair BC, this is what happens:

  1. First, the stack pointer, register SP, is decremented, that is, it is reduced by 1.
  2. Then the number in register B is POKEd into the memory location that SP points to.
  3. Then register SP is decremented again,and this time it is register C that is copied into the location SP points to. (Remember, registers B and C do not change while all this is happening; only the memory locations that SP points to will change.)

Figure 1 shows what the process would look like if you had to do it step-by-step — first in Basic, then in Z80 machine code instructions:

BASIC              Machine Code
LET SP=SP-1        DEC SP     
POKE SP,B          LD (SP),B
LET SP=SP-1        DEC SP
POKE SP,C          LD (SP),C

As you have probably guessed, POP is exactly the reverse of PUSH. Figure 2 shows how POP BC would look, step by step:

BASIC              Machine Code
LET C=PEEK(SP)     LD C, (SP)
LET SP=SP+1        INC SP
LET B=PEEK(SP)     LD B, (SP)
LET SP=SP+1        INC SP

You cannot add a number in the middle of this list of numbers, but only at the bottom. Nor can you “cross out” a number if it is in the middle of the list. Whether you are adding or removing a number, you always have to work from the bottom.

Handle with Care

The stack is a great place to keep things safe, but do not get too enthusiastic about PUSHing things onto it. Here is why: the more times you PUSH without POPing, the longer your list will get. Register SP will point to lower and lower memory locations, and eventually, if you are not careful, it will point to other important things in memory, such as a machine code program, Basic variables, or the display file, and wipe them out.

To avoid that problem, the ZX80, ZX81, and TS 1000 all start the stack pointer off just about as high as possible. That means the stack starts out very near the top of your RAM memory, so there is usually lots of room for the list to get longer.

That brings up something else to beware of: be careful not to POKE holes in the list. Suppose you POKE a number into a memory location that is part of the stack. What happens? Well, you will change the value that was already there. Then, somewhere along the line, a number will POP, but it will not be the value that was originally PUSHed. That can mean problems.

Always be careful about POKEing around in high memory locations. Remember, the stack is a safe place as long as you help keep it safe.

You can use the PUSH and POP instructions with any of the three register pairsโ€” BC, DE, and HL. You can also PUSH and POP register pair AF. That is register A, along with the flags (the zero flag, the carry flag, and all the others) that are sent up or down at the end of each instruction. (None of the stack instructions affect any of the flags.)

Pathfinder

Another register, called the program counter (register PC for short), can be PUSHed on the stack. Like register SP, it is a special double-sized register that can hold any number from to 65535. Like SP, register PC always points to a memory location. The program counter keeps track of where in your machine code program the computer is.

For example, when you first turn your computer on, the program counter is 0, and that is where the Z80 processor goes to look for its first instruction, memory location 0. After getting the instruction that is stored in memory, the first thing the Z80 does is add 1 (or 2 or 3 or 4, depending on how many bytes long the instruction is) to the number in register PC. That way, PC always points to the beginning of the next instruction. For example, after

LD A,B

it adds 1 to the number in PC because that instruction takes up just one memory location. In the case of

LD HL,6723

the program counter would go up by three since this instruction takes three bytes of memory.

Maybe you remember when we first encountered relative jumpsโ€” the instructions that make the processor jump forward or backward only a certain number of bytes. When the program says

JR 6

it really means “add 6 to register PC” and

JR -12

means “subtract 12 from register PC.” (Think about that a moment. It is tricky, but it makes sense.)

Of course, a regular jump, such as JP 17430 just loads the number 17430 into register PC. Then the program counter points to location 17430, and that is where the Z80 processor looks to get its next instruction.

Obviously, this makes PC an important register. If it accidentally gets fouled up, there is no telling where the processor might go looking for instructions. Fortunately, it is pretty difficult to make that kind of mistake with Z80 instructions, except for one way, which we will see as we look next at machine code subroutines and how they work.

CALLing All Subroutinesโ€ฆ

Chances are, after you read through your manual the first time, you understood how a subroutine works. It is a sort of miniature program inside your program.

When your Basic program hits the command

GOSUB 1000

it skips to line 1000, and begins working there. It follows through until it hits the command RETURN. Then it jumps back to the program line immediately following the GOSUB command and continues from there.

GOSUB is a Basic command for Basic subroutines. To use a machine code subroutine from your machine code program, you need the Z80 instruction CALL. Like the machine code “jump” instruction, JP, it tells the processor to go to a memory location (there are no line numbers in a Z80 program). But just as GOSUB is a little different from GOTO in Basic, CALL and JP work in slightly different ways.

Let’s take a look, step by step, at what happens when the Z80 meets an instruction such as

CALL 16984

First, the processor adds 3 to the program counter, register PC (CALL is a three-byte instruction). As a result, PC points to the first instruction following the CALL instruction.

Then, before it jumps to the subroutine, it PUSHes PC onto the stack.

Finally, it makes the jump by sticking the number 16984 into register PC. Now the program counter points to memory location 16984, and that is where the Z80 goes looking for its next instruction.

In other words, CALL is just like JP except that, after a CALL instruction, something has been added to the stack.

Maybe you have already guessed what the machine code return instruction RET does. It POPs a number off the stack and into register PC. If everything has worked right, that number makes the PC point to the instruction immediately following the CALL instruction, and the Z80 continues from there.

Make sure you have the same number of POP instructions as PUSH instructions in any MC subroutine.

Think about that a minute. It is important. Suppose things have not gone right, say, something else has been PUSHed on the stack. Then RET will POP the wrong number into the program counter, and the Z80 will go looking in the wrong place for its next instruction.

Or suppose the original number has been POPed off the stack already. Once again, the Z80 will get lost, and chances are the computer will lock up or destroy your program. If that happens, you have to unplug it and start all over again. When register PC gets fouled up, all sorts of things can go wrong.

It is worth repeating: be careful when you use the stack. Always make sure that, if you PUSH something, it eventually gets POPped. Make sure you have the same number of POP instructions as PUSH instructions in any machine code subroutine. That way, you will never lose your place in the stack, and your machine code program will stay on track.

One last note on using CALL instructions: the numeric version (the one the computer understands) is always three bytes. First is 205 (CDh); then comes the memory location where the subroutine begins, a number from to 65535. As usual, you should divide it by 256, and make the remainder the second byte of your CALL instruction, and the quotient the third byte.

The Mysterious “ROM Calls”

One of the advantages to using machine code on a TS/ZX computer is that you do not always have to do everything yourself. That is because the Basic language interpreter program (the one that is stored in ROM) has to do a lot of very common things that other machine code programs also have to do such as get information from the keyboard and print things on the screen. It usually uses subroutines to do these things.

Some of the ROM subroutines can get rather complicated to use. The routines that handle floating-point arithmetic in the 8K ROM, e.g., require all sorts of special preparation. But others are relatively simple, and the best way to get a good feel for how to use subroutines is to try a few of them out.

A word of warning: all of the subroutines I will refer to in this section are in the 8K ROM, and the information applies only to this ROM. If you have a 4K ROM, or any other ROM, this information probably will not be much help. Sometimes there are “monitor listings” available for different ROMs; from these you may be able to figure out where useful machine code subroutines appear in the ROM, and how to make use of them. But there is no standard place to put the “print a character” routine, e.g., so, for now at least, I will just cover it for the 8K ROM.

One other reminder: all ROM routines use some of the Z80 registers. If you have a number in, say, register pair BC that you do not want to lose, be sure either to save the number in a machine code variable, or PUSH BC onto the stack before you CALL the subroutine (and, of course, POP BC after the subroutine is finished). Otherwise the subroutine may use register pair BC, and you may lose your number.

PRINT

The first routine is the “print a character” routine, located at memory location 16 (0010h). To use it, you put the number of the character you would like to print in register A, then call the subroutine.

For example, the letter “Q” is character number 54.

PRINT: LD A,54        ;"Q" in register A 
       CALL 16        ; print it

This works just like the PRINT function in Basic, except that it prints just one character at a time. You can use it for any character in the regular or reversed character set, i.e., character numbers through 63 and 128 through 191. You can also use it with character number 118, the ENTER character that starts a new line on the screen.

INPUT

The INPUT routine is a little more complicated. The computer uses two different ROM calls to find out what key on the keyboard is being pressed.

First, the routine at 699 (02BBh) scans the keyboard. When it is done, there is a pair of numbers in registers H and L indicating what key has been pressed. If register H is 255 (FFh), no key has been pressed; if it is 254 (FEh), only the SHIFT key was pressed. Anything else means that a regular key was pressed.

Next, if there is a regular key pressed, the number in HL has to be put into register pair BC. Finally, the routine at 1981 (07BDh) goes to work; when it is done, register pair HL points to the correct character.

The description may make it sound difficult, but the code below shows the routine. It is easy to use, and works like the INPUT function in Basic: it waits until you press a key before continuing with the program. However, it only checks the keyboard for one key.

INPUT:    CALL 699      ;scan the keyboard
          LD A, 253     ;if H>253, scan again
          CP H
          JR C, INPUT 
          LD B,H        ;put HT in BC
          LD C,L 
          CALL 1981     ;find the character
          LD A, (HL)    ;put the character in A

INKEY$

Figure 5 shows how to modify the INPUT routine slightly so it works like the INKEY$ in Basic.

This time, if no key (or just the “shift” key) has been pressed, register A contains 255; otherwise it contains the character code for the key pressed.

Be careful using the INPUT (or INKEYS) and PRINT routines together. Some of the character codes you can get from the keyboard, such as LPRINT (code 225) or THEN (code 222), cannot be printed by the PRINT routine. It only works with individual characters or their inverses (code numbers 0-63 and 128-191) and the ENTER code, 118.

INKEY$:   CALL 699      ;scan the keyboard
          LD A,253      ;if h>253, skip it
          CP H
          LD A,255      ;if no key, A=255
          JR C,NEXT
          LD B,H
          LD C,L
          CALL 1981     ;find the character
          LD A,(HL)     ;put the character in A
NEXT:     (whatever comes next)

SCROLL

This one is fast and easy to use. It works just as in Basic. When you use the subroutine at 3086 (0C0Eh), the display moves up a line, and the cursor drops to the bottom line of the display.

SCROLL: CALL 3086

FAST and SLOW

I mentioned before that there is no standard place to put machine code routines in ROM. That is why the routines are in different places in the 4K and 8K ROMs.

In fact, there are two different versions of the 8K ROM itself. That means you will have to do a little bit of testing to make sure of which version you have. In SLOW mode, type in:

LET A=USR 3872 

One of two things should happen: either your screen shows “0/0” in the lower lefthand corner and you are in FAST mode now, or it shows “8/0” and you are still in SLOW mode.

If it shows “0/0”, use these ROM calls:

FAST: CALL 3872
SLOW: CALL 3880

If it shows “8/0”, you should use these:

FAST: CALL 3875
SLOW: CALL 3883

PLOT and UNPLOT

For each of these commands, you will need a horizontal coordinate between and 63, and a vertical coordinate between and 43. That’s right. It is just like PLOT and UNPLOT in Basic. The horizontal X-coordinate goes in register C, with the vertical Y-coordinate in register B. Both PLOT and UNPLOT use the ROM subroutine at 2994 (0BB2h); the only difference is that PLOT POKEs the number 128 into memory location 16432 (4030h) before calling the ROM subroutine, while UNPLOT POKEs the number into that location. Figure 6 shows the routines.

PLOT:    LD B,Y         ;Y is the vertical
         LD C,X         ;X is the horizontal
         LD A, 128      ;to PLOT
         LD (16432), A 
         CALL 2994      ;plot it
UNPLOT:  LD B,Y         ;Y is the vertical
         LD C,X         ;X is the horizontal
         LD A, 0        ;to PLOT
         LD (16432), A 
         CALL 2994      ;unplot it

Figure 7 is a program in both Basic and machine code that uses both ROM calls and the PUSH and POP instructions. First type in the program in Basic, RUN it in SLOW mode, and use the arrow keys to draw lines on the screen. (To use the arrow keys, hold down the SHIFT key while you press 5, 6, 7, or 8.) Then use the loader program in Figure 8 to put in the machine code version and see how much smoother and faster the program becomes.

OE 20              LD C,32          10 LET X=32
06 16              LD B,22          12 LET Y=22
C5          PLOT:  PUSH BC          20 PLOT X,Y
3E 80              LD A, 128
32 30 40           LD (16432),A
CD B2 OB           CALL 2994
CD BB 02  INKEY$:  CALL 699         30 LET A$=INKEY$
3E FD              LD A,253         32 IF A$="" THEN GO TO 30
BC                 CP H
3E FF              LD A,255
38 06              JR C,UP
44                 LD B,H
4D                 LD C,L
CD BD 07           CALL 1981
7E                 LD A,(HL)
C1            UP:  POP BC
FE 70              CP 112           40 IF A$<>CHR$ 112 THEN GO TO 50
20 08              JR NZ,DOWN
3E 2B              LD A,43          42 IF Y<>43 THEN LET Y=Y+1
B8                 CP B
28 01              JR Z,+1
04                 INC B
18 DA              JR PLOT          44 GO TO 20
FE 71       DOWN:  CP 113           50 IF A$<>CHR$ 113 THEN GO TO 60
20 08              JR NZ,LEFT
3E 00              LD A, 0          52 IF Y<>0 THEN LET Y=Y+1
B9                 CP C
28 01              JR Z,+1
05                 DEC B
18 CE              JR PLOT          54 GO TO 20
FE 72       LEFT:  CP 114           60 IF A$<>CHR$ 114 THEN GO TO 70
20 08              JR NZ, RIGHT
3E 00              LD A,0           62 IF X<>0 THEN LET X=X+1
B9                 CP C
28 01              JR Z,+1
0D                 DEC C
18 C2              JR PLOT          62 GOTO 20
FE 73      RIGHT:  CP 115           70 IF A$<>CHR$ 115 THEN GO TO 30
20 08              JR NZ, BREAK
3E 3F              LD A,63          72 IF A<>63 THEN LET X=X+1
B9                 CP C
28 01              JR Z,+1
0C                 INC C
18 B6              JR PLOT          74 GO TO 20
FE 00      BREAK:  CP 0
20 B2              JR NZ,PLOT
C9                 RETURN 

Letters, We Get Lettersโ€ฆ

Wanda Dietrich, of Blanca, Colorado, writes to suggest, “Please, at the end of each article list all the instructions and what they stand for. Not what they mean โ€” you have already explained that โ€” just what they stand for in English.” The list of instructions keeps getting longer, but Figure 9 should help.

You can get a free Z80 reference guide from Zilog, the company that designed the Z80 microprocessor, listing all the Z80 instructions and what they do, including the numerical codes (in hexadecimal) and more technical information than you are ever likely to need.

Next time, we will take a look at how to use that free reference guide. We will also look at a wide-ranging collection of Z80 instructions that can work on numbers just one bit at a time. They are not used as often as LOAD or JUMP instructions, but they can still come in handy for all kinds of programs.

Scroll to Top