SPECTRAMON by Simon Goodwin from ZX Computing Apr/May&Jun/Jul.1983 Simon Goodwin of Hereford unveils an excellent program for the 48K Spectrum. If you've ever wondered how your ZX Spectrum works, Spectramon (the Spectrum monitor) will make it easy for you to find out. This program will print or display the contents of ROM or RAM in numeric, character or assembly language form. Addresses may be entered in decimal or hexadecimal, and the user may select the base used for output. Spectramon will run on a 48K Spectrum with or without a printer. The disassembler option has been written with the failings of ZX80 and ZX81 programs in mind - unlike other published listings it will handle all 694 standard Z80 instruction codes, using the standard mnemonic names and formats devised by Zilog, the firm which designed the Z80 processor used in the Spectrum. Z80 instructions The Z80 instruction set is the most complicated of any 8-bit microcomputer. The Z80 processor was designed by a group of people who left Intel, the firm which makes the 8080 processor, to set up Zilog. The Z80 will execute any of the instructions of an 8080, plus a large number of extra ones 'tacked on' by Zilog. This approach meant that programs written to run on an 8080 would also run on a Z80 without changes. New programs could then be written using the added facilities of the Z80. That was how many early Z80 programs were produced. The BASIC interpreter used on the TRS-80, for instance, is substantially an 8080 program even though the TRS-80 has a Z80 processor. Only the display and keyboard routines contain Z80 instructions since they were the last to be written. Sinclair BASIC is written using the full features of the Z80 processor. Zilog added instructions to handle fast moving and searching of tables in memory, extra registers (internal storage) and instructions to increase the number of things that could be done with the original 8080 registers. They wanted to more than double the number of possible instructions, but there was a problem - Intel had decided to use a single byte (8 bits) to store the instruction numbers for the 8080, and most of the 256 possible numbers were already in use. Zilog got around this by giving four instruction numbers special meanings - instructions with one of those numbers would carry out a certain 'class' of operation, and the next byte would explain the operation required in detail. In theory, that gave Zilog plenty of possible numbers - 252 (using the remaining one-byte values) plus 1,024 (4 * 256) if they were to use all of the possible two-byte instructions. In practice, they only used 694 of the 1,276 possibilities, but that's still a very large number of instructions for an 8-bit computer! If you consult [Appendix A of the Sinclair Spectrum manual, you will see the standard Z80 mnemonics listed. The prefix byte 203 is used to generate add-on instructions for 'bitwise' operations - instructions which manipulate or test binary digits. The prefix byte 221, indicates that the next instruction is an 8080 one, which would use register pair HL but must now use register IX instead. Likewise, the prefix 253, indicates that IY should replace HL in the next instruction. If HL was in brackets in the old 8080 instruction (as in LD a, (HL)) then the Z80 version allows an offset to be applied to IX or IY before use - this is specified in an extra byte after the end of the 8080 instruction. Finally, the prefix 237 is used to indicate that the instruction following is one of a group of miscellaneous Z80 add-ons. Monitoring the situation If all this sounds very complicated you've probably realised why a monitor is a useful program - Spectramon will automatically convert sequences like 'EDH 7BH 3DH 5CH' into the mnemonic: LD SP,(23613). The EDH told Spectramon that it was a miscellaneous Z80 add-on instruction (EDH is 237 decimal). The 7BH corresponds to LD SP,(some address), and the 3DH 5CH corresponds to the value 23613. To check that, convert 3DH and 5CH to decimal then add the first result to the second (multiplied by 256). It's an awful lot simpler to let the computer puzzle that out than it is to work it out for every instruction by hand. Of course, you may think that LD SP,(23613), is just as baffling as EDH 7BH 3DH 5CH - in which case, you'll have to learn a little about Z80 machine code before Spectramon becomes useful to you. Before you can investigate the ROM of a computer, you do need to understand the computer language in which it was written - assembler, in most cases. LD SP,(23613) is an assembler (or "assembly language" or "machine code') command. If you don't understand assembler, please don't throw this article away! It will take you no longer to learn assembler than it did to learn BASIC (it should be just as much fun too) and you can come back to Spectramon when you know more. In fact, the instruction LD SP,(23613) has a very simple purpose - it tells the computer to put the number in address 23613 into the register called 'SP'. If you consult the Spectrum manual you will find out that 23613 is the "address of item on machine stack to be used as error return", which tells you that the instruction is part of the ROM error-handler. Using a disassembled listing and the table of 'System variables' in chapter 25 of the manual, you can trace your way through the ROM, finding out what each section does. Using the program Spectramon takes about 15 seconds to set itself up when first RUN. During this time, it is building a table of instruction codes for the disassembler, and once that is complete, the menu of commands will appear. To quit from the monitor, type 'Q' followed by Enter. This returns you to ZX BASIC. If you wish to disassemble a program in RAM or ROM, then you should type 'D' followed immediately by the address at which you want to start. Addresses may be entered to Spectramon in decimal or Hex - if you want to disassemble from address 126 (decimal), you could type Dl26 or D007EH or D7EH - leading zeros are optional - and if you enter more than four Hex digits, only the last four will be considered. If a meaningless address is typed (such as D, DFF, D123456 or D-1) then the command will be ignored. The disassembler displays the contents of memory one screenful (21 lines) at a time. The left-hand column shows the address of the instruction. It is followed on the same line by a hexadecimal representation of the instruction, and then the assembly language text. After 21 lines have been displayed, the prompt "More? (Enter = No)" will appear. Press any alphabetic or numeric key and the listing will continue on a new screen. Press the Enter key to return to the menu. After each line is displayed, the program checks to see whether or not a key has been pressed. The Space key pauses the display, which will continue when any alphanumeric key is pressed. The Enter key causes disassembly to cease and the menu is displayed. Magic numbers? The third option allows display of the numeric contents of memory. Although the disassembler does this, it only lists between one and four bytes per line (depending upon the instruction). The N command allows eight bytes to be listed on each line of the display. A start address may be specified in Hex or decimal, just as for the D command. The N command is useful for displaying the contents of tables used by a program or the ROM. Type 'N150' to see the Spectrum reserved-word table. That is where ZX BASIC stores the spellings of words such as PRINT and RETURN. The words are stored in a modified version of ASCII code - the last letter of each word has 128 (80H) added to it, to make it easy for the ROM routine which displays words to find where each one starts and ends. If you found the numeric representation of the BASIC words rather hard to follow, you can use the command A150 to display the reserved word table in character form. The command uses 7-bit ASCII values, so that letters with 128 added to their code still print out correctly. To avoid changing colours or moving the cursor unexpectedly, the ASCII output routine displays control characters (those with a code less than 32) as full stops. You can use the Space and Enter keys to control listings output by commands N and A, just as you would for a disassembly. Every 21 lines the "More? (Enter=No)" message will appear before a new screen is started. The final two commands don't output anything themselves, but they do change the output which the others generate. When you first RUN Spectramon, the message "P Printer option (Now OFF)" appears. Type the command 'P' followed by Enter and the message will become "P Printer option (Now ON)". If you then display memory contents (using A, N or D) the information will be sent to the printer as well as the television. Once you've finished printing, press Enter to stop the display and then use the command P to switch the printer option off again. Notice that the printer routine does not output any lines until an entire screen-full has been generated. In fact, it deliberately avoids using the LPRINT statement to send each line to the printer. Instead it uses COPY, the ZX BASIC command which sends all of the text on the screen to the printer. That's because it is almost twice as fast to build up a full screenfull of data and then print it using COPY than it is to use LPRINT for each line as it is generated. The printer can't stop and start very quickly and consequently the LPRINT statement is much slower than COPY - the printer must rev up and slow down 21 times (once for each line) instead of just once. In fact, the printer always outputs the last line of a group at half speed, to make sure that everything falls in the correct place when it stops. As far as it is concerned each LPRINT is the last line of a group (when there's less than 33 characters being printed). Base choice The final option allows the user to select the base in which numbers are output by the program. Sometimes it is useful to have numbers printed in decimal (for example, when referring to addresses mentioned in the Spectrum manual) and sometimes hexadecimal is more convenient (when displaying address tables or working out jump offsets). Type the command 'B' to change the output base. When you first run Spectramon it will be Hex (hence the display "B Base selection (Now = HEX)") but you can switch it to decimal with the B command. If you wish to re-select Hex output later you can 'toggle' back by typing 'B' again. If an unknown command is entered, Spectramon will ignore it. If it is called upon to show the contents of non-existent memory (past address 65535) it will display the message "End of Memory". If the end of memory is encountered while the program is half-way through processing a line of numbers or ASCII characters, it will fill the rest of the line with zeros or spaces. If you have to stop the monitor for any reason by typing Break (perhaps because your desk has melted from under the computer or the ZX printer is strangling itself) you can re-start Spectramon by entering GO TO 200 after the panic is over. So long as you've not typed LOAD, CLEAR or NEW in the meantime, the menu will appear immediately (without the 15 second wait for table set-up) and the current base (Hex or Decimal) will be preserved. The next byte The second part of this article, complete with program listing for Spectramon, will be published in the June/July issue of ZX Computing. In the meantime, if you can't wait to try out Spectramon for yourself, a tape of the program is now available from ASP Software priced at #5.99. For more details, check out the advertisement elsewhere in this issue. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Presenting the second part of this feature article, including the full listing of Simon Goodwin's incredible Spectrum monitor program. Spectramon is written in ZX BASIC but it should be quite easy to convert for other computers. Obviously, it will only be useful on machines which use the Z-80 processor! The Spectrum CODE function corresponds to ASC on other computers - brackets around its argument are optional in ZX BASIC. String arrays are handled rather oddly by Sinclair BASIC - the variable Z$ is set up by line 40 as having a fixed length of 32. Unused character-positions contain spaces - so that Z$ is simply used as an array of space characters by the instruction formatting routine. The array O$ contains 608 strings (numbered from one, not zero) and each string has a fixed length of nine characters (line 130). The other string variables are normal 'Microsoft' strings - they vary in length to accommodate whatever is stored in them. ZX BASIC allows sub-strings to be extracted from a string using the 'TO' instruction - A$(1 TO 1) returns the first character of a string, corresponding to LEFT$(A$,1) in Microsoft BASIC. If A$ is set up as 'SPECTRAMON' then A$,6,3). [sic] In short, the 'TO' instruction extracts all the characters from one position TO another, inclusive. Spectrum BASIC allows long variable names to be specified, and (unlike Microsoft BASIC) all the characters of a name are significant. On the Spectrum, INDEX and INDIRECT are two different, valid variables - in Microsoft BASIC they will have to be renamed, otherwise they would be treated as the same variable because they have the same first two characters. In some versions of Microsoft BASIC, neither variable name would be allowed since they both contain the key-word 'IN'. Sinclair BASIC is also unusual in that it allows spaces to occur in variable names. Table 1 shows all the variable names used in Spectramon and documents their usage. Other systems can ignore the lines using COPY to send out a listing and simply LPRINT L$ if LP=1, printing out lines one at a time rather than en masse. A user defined function is set up in line 50, but it is fairly easy to code around this if your computer doesn't support that feature. FN H(H$) simply returns the decimal value of the first character in H$ - 1 for '1', 10 for 'A', 11 for 'B' and so on. Spectramon uses a few PEEKs and POKEs which will not be required on other systems. POKE 23658,8 is a useful command which forces the Spectrum into capitals-lock (selecting a flashing 'C' as a cursor rather than a flashing 'L'). This ensures that commands are entered in capitals (unless the user purposely switches to lower-case in the course of entering a command). The location 23689 contains the number of empty lines on the Spectrum screen - when PEEK 23689 is three or less the screen is assumed to be full since the bottom two lines aren't normally used for text and a line is needed for the "More? ..." message. Location 23560 contains the ASCII code of the key most recently pressed. It is set to 32 when the space bar has been pressed (or is being simulated) and 13 when Enter has been typed. The last word ... When I received my Spectrum I was convinced that I'd never get used to the keyboard. After writing, editing and typing in Spectramon I was well practiced! Hopefully, the program also illustrates a few useful quirks of ZX BASIC, both from the BASIC and the assembler programmer's point of view. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Table 1. Variables used in Spectramon Z$ - Fixed length string of 32 spaces, used in formatting. H$ - Hex characters '0'-'F' - also a local variable used in the Hex-Decimal conversion function, FN H (line 50). LP - 'Flag' set to 1 if printout is required. DEC - 'Flag' set to 1 if numbers must be output in decimal. CheckIndex - Line number of the routine which checks to see whether an operation could involve IX or IY. GetInstruction - Line number of the routine which formats a complete line of disassembler output. MakeText - Line number of the routine which formats a complete line of disassembler output. ByteValue - Line number of a routine which expresses the contents of C (0-255) in C$, using the current base. WordValue - Line number of a routine which sets up C$ with a string copy of C (0-65535) in the current base. F$ - String containing register names. O$ - String array containing the opcode text. I,K,T - Loop counters and temporary values. A$ - The command typed in by the user. C$ - The first character of the command. SUB - Line number of the chosen monitor subroutine. LOC - The location being examined by the monitor. L$ - The line of text to be output by the monitor. I0,I1,I2 - The instruction code and its operands. N$ - The name of the current index register. S$ - The name of the current indirect register (N$). M$ - The mnemonic form of the instruction. NBYTES - Length of instruction, in bytes. INDEX - Set to 1 if IX or IY are to replace HL. INDIRECT - Set to 1 if (IX) or (IY) are to replace (HL). R$ - Character within instruction mnemonic. MODE - Addressing mode 0-9; declares number and format of operands. C - Number for conversion into a decimal or Hex string. C$ - Number after conversion into a string. D$ - Part of disassembler output line. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = [I've made the following corrections to the listing. JimG.] Line 6090 should just have eight "9" elements in the DATA statement, not nine. Line 1320 should be LET i0=i0+416 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = [Letter from ZX Computing Aug/Sep.1983. ] [The corrections given here have also been included. JimG.] BYTING BUGS Dear ZX Computing, I'm afraid I have found a couple of minor errors in my Spectramon program. However, I have two solutions to these problems: 1) When the Spectrum tries to disassemble close to the top limit of memory, because of the way in which the Z80 instruction set is constructed we may have a 'look ahead' by up to four bytes. If you are at location 65533 and this 'look ahead' occurs it will try to PEEK beyond the range of memory. This causes an "out of range" error. There is no true solution without major alteration of the program. However, a simple 'fix' can be achieved by changing line 605 to read: 605 IF loc>65532 THEN PRINT "End of memory".": POKE 23560,32: GO TO 610: REM Pretend SPACE was typed 2) The other problem in the program is far more subtle and occurs when the Spectrum tries to wrap around its memory map going from 65535 back to zero. This shows up as a subscript error when using the hexadecimal conversion routine. This can easily be cured with the addition of the line 3435: 3435 IF c>65535 THEN LET c=c-65536 Hopefully these two solutions will end all your worries with my Spectramon program. Yours faithfully, Simon Goodwin, Hereford -- Another Fine Product transcribed by: Jim Grimwood (jimg@globalnet.co.uk), Weardale, England --