In this, the final instalment of the series, we're going to dig deep into the Spectrum innards, to see how we can make use of what Sinclair has already given us.
Until now, every machine code program we've written has done everything for itself. If we've wanted to put a character onto the screen, we've written a piece of code which loads the first byte into the initial location, then moves on to process the second byte, and so on.
However, have you ever stopped to wonder how the Spectrum normally places characters onto the screen? It can't do it directly from Basic, and so the Basic is interpreted, and a machine code routine does all the work. If we know where that machine code routine is, and how it works, there is no reason why we shouldn't CALL it from our own programs.
The Spectrum operating system, Basic interpreter and standard character set, occupies the lowest 16K of the Spectrum memory. That is all in ROM, as opposed to the subsequent 48K of memory which is addressable RAM. Any program which can be written in Basic can also be written in machine code, with CALLs to this ROM area to decrease the size of the user-written code. For instance, any printable character can be printed using the two statements:
LD A,(ascii code) RST 10H
That is one of the 'Restart' routines at the very beginning of the ROM area.
As an example of how useful these routines can be, have a look at this month's program, the assembly code of which is shown in figure one. Its function is to perform a Block Delete - in other words, it will allow you to delete a range of lines in a Basic program.
The first CALL (location 1C79 hex) is to a routine which will evaluate two expressions, separated by a comma, and place the resulting two numbers onto the calculator stack, with the last at the top. The routine expects to find those expressions immediately after the last executed command, entered either directly or from Basic. This location is referenced by the system variable CH_ADD - locations 23645 and 23646. Using this routine gives us yet another way of passing values to machine code routines. We've already seen how to pass them by Poking into locations, then loading these into registers, and through the use of a function. We can now specify the range of lines to delete as part of the machine code call from Basic, in the form:
PRINT USR location, first line, last line
and the CALL will automatically place the values of 'first line' and 'last line' onto the calculator stack.
The next problem is to retrieve the values from the calculator stack. We've seen how easily that can be done before - Sinclair User October - by making a CALL to location 2DA2 hex, which places the top value into the BC register pair. A couple of points we didn't mention are that the routine will also set the Carry flag if the top value is greater than 65535 - the maximum value holdable in two bytes - and that it also places a copy of the low byte into the Accumulator. This is why the two conditions following the CALL work correctly.
Once we've got the line number from the stack, and checked it, we need to convert it into an address in memory. One way to do this, assuming we know how the Spectrum holds a Basic program, is to search through memory for the required sequence of bytes. Here again, however, by transferring the number from BC to the HL register pair, we can simply CALL location 196E hex, and the Spectrum operating system will do the work for us.
That line-to-address conversion routine will place the starting address of the line held in HL, or the first line after this, into the HL register pair, and places the start of the previous line into the DE register pair. The first time we CALL this routine in our program, we initially increment HL. That is just so that we can make use of the 'Reclaim' routine (19E5 hex), further on in the code. The routine will close up the program area, starting at the address in the DE register pair, and finishing with the address in the HL register pair as the first unchanged location. This routine will also carry out the messy work of amending all the necessary system variable pointers for us.
To see how the routine works, just type in the Basic loader program in figure two. This will load the code into memory, then delete the block of REM statements near the beginning of the program, before Listing itself.
This sort of utility program can prove very useful. For instance, if you want to use a Basic loader to enter machine code into a REM statement at the start of the program, as explained in January's article, then you'll need to delete all the lines following the initial REM, before rewriting the program. A utility such as this Block Delete will do that in a fraction of the normal time. You'll also notice that the routine is completely relocatable, so you can place it anywhere in memory.
There are a wide variety of Spectrum ROM routines which may prove useful. October's article showed how to use the 'RST 28H' instruction to make use of the ROM calculating routines, and figure three shows a few other generally useful CALLs. For anybody wanting to dig further into the Spectrum ROM, The Complete Spectrum ROM Disassembly, by Dr Ian Logan and Dr Frank O'Hara is recommended.
Previous article in series (issue 46)