Andrew Hewson delves into the heart of the Z80 to unearth some useful routines.
SINCLAIR Research have manufactured three machines all based on the Z80 microprocessor: the ZX-80, the ZX-81 and the ZX Spectrum, and it is interesting to observe the development from one machine to the next.
A consequence of that understandable policy of developing one ROM from its predecessors is that some features which were necessary or desirable in the earlier version may be retained in the later version because the relevant code is known to work even though they impose constraints on the new design. This is, indirectly, the answer to the following question from John Blackwood who asks "Why is 9999 the largest line number permitted on the ZX-81?"
At first sight the limitation to 9999 seems quite illogical because 9999 is not a 'round number' as far as the Z80 microprocessor is concerned. A more logical upper limit might be 255 because that is the maximum integer which the machine can store in a single memory location - or byte. However, many users could be expected to write programs containing more than 255 lines and so a greater limit is desirable. The next highest 'round number' is 65535 which is the largest integer which the computer can store in two consecutive bytes. So why limit line numbers to 9999 when 65535 could be used just as easily?
The reason appears to be that by limiting line numbers in that way and by manipulating the numeric codes for variables the ZX-81 has a device for distinguishing lines in the program area from variables in the variables area.
To understand the mechanism at work, consider the binary representation of 9999. Line numbers are held with their most significant byte first. That is contrary to the usual Z80 convention so we can assume that the manufacturers had some special motive in choosing that arrangement. Hence line number 9999 is held as a byte containing 39 followed by a byte containing 15 because
39 * 256 + 15 = 9999
The bit pattern of the byte, obtained by converting 39 to binary, is 00100111. Notice that the three most significant bits - bit numbers 7, 6 and 5 are set to 0, 0 and 1 for this, the largest permitted line number. Hence bit numbers 7, 6 and 5 of the first byte of all permitted line numbers will be set to 0, 0 and 1, or in the case of line numbers less than 8192, they will be set to 0, 0, 0.
Now look at pages 172 to 174 of the ZX-81 Basic Programming manual and you will see illustrations of the different types of variables as they are represented in the variables area. In each case the first byte contains a numeric code related to the code of the letter which identifies the variable - or the code of the first letter of the variable name in the case of a number whose name is longer than one letter. The largest possible letter code is 63, the code for Z, which is still 00111111 in binary, and the smallest is 38, the code for A, which is 00100110 in binary.
Hence bits 7 and 6 are not needed when distinguishing between letter codes and as bit 5 is always set to one, the ZX-81 can use these bits to distinguish between the different types of variable. Three bits can be set in
different ways. Table 1 lists the eight ways and their interpretation.
It is strange that Sinclair should take such elaborate precautions to distinguish a line number from a variable because the same purpose could be served by comparing the address of the byte in question to the D-FILE or VARS system variables. It allows the ZX-81 to use the same routine, at 2546 to 2576, to step through memory to the 'next' line or the 'next' variable but that seems a small advantage.
It is certainly one of the features which has been carried forward from the ZX-80 to the ZX-81 and then to the Spectrum.
We shall return to discussing Basic variables later but first a small but relevant digression is prompted by the following question from Patrick Higham. He asks: "Is there a simple method of printing characters on the Spectrum screen from a machine code routine?"
Printing from machine code is very straightforward because the manufacturers have thoughtfully provided a routine in ROM to do all the hard work. The routine is called at address 16 decimal - 10 in hexadecimal - and should be accessed using the special Z80 machine code instruction
That instruction, for some reason which has never been adequately explained, is called a 'restart' - hence the RST abbreviation - and is one of eight such special instructions. As far as the user is concerned it has the same effect as a CALL instruction except that only one byte instead of three is required to hold it.
The routine is entered with the A register set to the code of the character to be PRINTed and the appropriate character appears on the screen at the current PRINT position. All registers are preserved by the routine except the AF register pair and so in some circumstances it may be necessary to PUSH and POP AF before and after the RST instruction respectively.
The routine listed in table 2 demonstrates the use of RST 16 by using it to PRINT all characters with codes lying between 32 and 255 inclusive. Note that includes all the tokens so the routine demonstrates that command words like POKE, READ and DRAW can be PRINTed using RST 16 if required. The decimal codes for the routine can be loaded into the printer buffer using the decimal loader listed in table 3.
The RST 16 facility can also be used to control the screen format and layout character codes but a little care must be taken not to follow the INK, PAPER and other control codes by invalid numbers because otherwise error code K results. Some of those layout characters are extremely useful, for example
LD A, 13 RST 16
will PRINT an 'ENTER' character so that the current PRINT position will move to the start of the next line.
Of course, the PRINT routine at address 16 was not provided by the manufacturers solely for the benefit of users of the finished machine. The Spectrum ROM itself makes extensive use of the facility and so it is littered with RST 16 instructions. That goes some way to explaining the power of RST instructions. Every time one is used two bytes of memory are saved - the difference between the length of a CALL and a RST instruction - and more importantly the Z80 does not waste time calculating the address which is being called because it is implicit in the instruction. Hence RST is very useful for calling routines which are used frequently.
The call to RST 16 is an important part of the routine which is listed in table 4 in response to the following letter from Alan Procter: "Have you a routine to identify the variables existing in memory, identifying them as numerics, string simple or array?"
The routine is rather longer than the ones I usually include in this column and so I recommend that an assembler program is used to load it into memory. Please note that the routine is not relocatable ie if the decimal codes are used it can only be loaded into the printer buffer starting at 23296.
The routine contains seven subroutines which I have called setb, prtvar, prtdol, addlen, lonvar, addsix and prtype. Those perform the following functions: