Zeus Z80 assembler extras

This page collates files I have made in the course of customising Simon Brattel and Neil Mottershead's Zeus assembler for the ZX Spectrum Next and experimenting with it. The examples listed later on this page can be typed into any Z80 assembler, not just Zeus, though the code downloads are in Zeus's tokenised compressed binary .god format.

Simon N Goodwin Zeus Sinclair package

Quick index
Next header files for Zeus
Improved Zeus for Next
A minimal dot command
INKEY$ from Z80, via the FPC

Next header files for Zeus

November 2020: There are a lot of 'magic numbers' associated with the Sinclair ZX range of computers, including the standard Z80 op-codes which are generated by Zeus from corresponding mnemonics. Four new files, available here as plain text and in Zeus's space-saving tokenised .god file format, make it easy to use symbolic names for Next hardware registers: nextreg.god, special Z80n instructions, ZX floating-point calculator codes and the full set of NextBASIC System Variables documented in the Next manual: sysvars.god. As some of the earlier Sinclair names include underscores I've applied a one-byte patch to Zeus to allow those to be used in assembly source. The new version linked here must be used so that such names are accepted.

Corresponding text files are nextreg.txt for Next register names, special Z80n instructions in nextras.txt, ZX floating-point calculator codes in fpcodes.txt and the full set of NextBASIC System Variables documented in the Next manual: sysvars.txt.

The register names are taken from a set worked out and shared by Matt Davies in the Spectrum Next Development group on Facebook in September 2020. It currently only includes registers accessible to the Copper.

Floating Point language equates

Next's ROM, like the ZX-81, Spectrum and Sam before it, implements and uses a concise bytecode mini-language to express floating-point arithmetic algorithms. This can be used in other programs outside the ROM, including the example later on this page. The Zeus tokenised source file fpcodes.god includes all the FPC instructions documented in Ian Logan's books, and uses similar mnemonics to those in his ROM disassembly and output by Nextramon, the symbolic disassembler I have contributed to the tools/dev folder on the official Next distribution SD card.

A couple of FPC mnemonics have had to be changed because they have the same names as Z80 mnemonics, so OR and NOT must be entered as FP_NOT and FP_OR respectively, so the assembled code uses the correct binary instruction-set. defb should be used to insert floating-point constants into the bytecode stream.

The FPC instruction set is listed in the documentation for Nextramon. This hypertext file includes links to the books by Ian Logan which explain how the FPC works and show how it can be used. Use the .GUIDE command on the Next itself to read it.

Support for extra Z80n instructions

Zeus supports the full set of Zilog Z80 machine-code mnemonics, but the tokenising scheme does not allow for the 20 extra opcodes, all preceded by the $ED prefix, which are unique to Next's Z80n processor. It's easy to use those, in the rare cases where they're needed, in any standard assembler by entering the corresponding byte codes e.g. DEFB 237,48 for MUL D,E, but easier and less error-prone to use symbolic names for the new opcodes, e.g.


In this example the token NEXTX is equated to 237, for greater clarity. These magic numbers are defined in the nextras.god source file. image of Zeus implementing Z80n instructions

This technique does not accommodate the extra register names shown in examples of the Z80n instruction set, as it does not need to, e.g. MUL D,E is written simply MUL, MIRROR A is MIRROR and the BRLC DE,B opcode can be unambiguously written DEFB NEXTX,BRLC - the suffixes serve no semantic purpose as those instructions all have unique mnemonics and are hard-wired to work on specific registers - there is no BRLC HL,A for example, and all the barrel shifter instructions shift DE according to a count in register B. The Next opcode jp (c) only uses register C; use DEFB JP_BC in Zeus. JP_C could too easily be confused with JP C, ... Read up on the semantics before trying this rather eccentric one! To add the 8-bit accumulator value to a 16-bit register pair, use ADDHL_A or similar.

Four of the Nextras expect to be followed by 16-bit constant operands, written NN in Appendix A of the Next manual, e.g. ADD HL,NN. Since ADD HL already prefixes several instructions, the symbols for Zeus on Next are single words with _W to denote the need for a 16-bit constant after the opcode, e.g. ADDHL_W, ADDDE_W and ADDBC_W.

To add 48K to (or equivalently subtract 16K from) HL, enter:

    DEFW 49152

Similarly, PUSH_W pushes the following 16 bit-word onto the stack without using intermediate registers. In fact the Z80n does this in a way so alien to the original chip that the bytes are the opposite way round from every other 16-bit value. This shocking wart does make the FPGA simpler, and the Copper co-processor also includes operands backwards from the Intel and Zilog convention.

That approach, of putting bytes in ascending order of significance at ascending addresses, has the functional advantage that it means the carry from adding the first pair is ready for the next and as many other bytes as are required for multi-byte addition or subtraction, but it means the values are written in memory the opposite way round from that in which they're read - Arabic numerals, remember - to add two numbers in decimal we must move from right to left. It depends on what you're used to, but swapping byte order is serious stuff. In a reference to the Holy War about how to eat a boiled egg in Jonathan Swift's satire Gullivers Travels, the DEC/Intel/Zilog approach of 8-bit micros, PCs and phones is known as 'little-endian', while the Motorola/IBM approach (Amiga, ST, Xbox 360, PS3, Wii etc) is 'Big-endian'.

In fact ZX BASIC employed big-endian words even before the Spectrum, in the storage of line numbers, to make detection of the end of the program a little faster.

That said, to push the 16-bit hexadecimal value #ABCD onto the stack, write:

    DEFB #AB,#CD

Notice how the most significant byte is placed first, the opposite of the Intel convention. This is easier to see when the values are written in hexadecimal.

Then consider TEST n, the new instruction to test the accumulator by masking its bits with the following operand byte - this is similar to AND n except that it does not alter A; it's a counterpart instruction, much as CP n is to SUB n. Unlike those, which were inherited by Zilog from the Intel 8080 (also created by Faggin and Shima) TEST is a three byte instruction. This is because of the need for a NEXTX prefix byte to sift out the Next extension - and many original Zilog opcodes too, as Zilog used the same prefix to add many of the Z80 instructions the 8080 lacked. This time any byte mask (126, BIN 01111110 here) can follow to complete the instruction on the same line:


Finally (until Allan adds more) consider the pair of Z80n opcodes to write Next registers, documented as NEXTREG r,N to set register number r to byte value N, and NEXTREG r,a which sets register number r (not to be confused with register R, or any CPU register!) to the value in the accumulator. To use those in Zeus, the equates are NEXTREG and NEXTREGA; append the register number after the NEXTREGA with DEFB, or both the register and byte value for NEXTREG, e.g.

    XOR A

The first instruction sets the CPU speed to 28 MHz. The last one sets it to 3.5 MHz, as XOR A clears the value in the accumulator.

Merging sources

The mnemonic equates are supplied in four separate short .god files so you don't need to include more of them in your own program source than it requires. The files can easily be merged into a single .god file by loading them to successive addresses in memory, over-writing the last two bytes of all but the final file with the start of the one to follow it in the combined .god file. Then enter Zeus with GO TO 70, type O to recover the text, and R to renumber the lines if you wish, before saving in the usual way. The overlap is needed because Zeus puts two $FF bytes to mark the end of source in memory, and each saved .god file ends with them. By storing the next file over the top of them, a single long file is created:

    LOAD "nextras.god" CODE 32768
    LOAD "nextreg.god" CODE 32768+952
    SAVE "nextset.god" CODE 32768,1566+952

The value 952 is the length of the data in the nextras.god file after ignoring the 128 byte header added by NextZXOS and the two bytes at the end: 952 = 1082 (from .ls -l) - 128 - 2. Likewise 1566 is the length of nextreg.god, without the header (so 1694-128) but including the last two bytes. Substitute corresponding values to merge any pair of .god files, or longer sequences.

Latest and greatest

Here is the latest version of ZEUS.bin which includes all the patches in Zeusx noted below, and an extra tweak to allow the entry and assembly of labels containing underscore characters. A side-effect of this is that you are also now able to enter labels with a pound sign "£" in their names - a consequence of Sinclair's Anglicised interpretation of the ASCII character-set.

Here is the corresponding loader ZEUS.bas which automates loading and saving of tokenised source .god files and corresponding .bin assembled machine-code. Keep reading for more information about new features.

Improved Zeus for Next

August 2020: I've been delving again into Zeus, the Spectrum assembler formerly sold by Sinclair Research in 1983 and now free in tools/dev on your Next SD. I've fixed the bug which stopped it working in 128 BASIC (previously requiring extra memory and a code patch on Next) and made a minimalist NextBASIC 'IDE' to save remembering addresses and typing LOAD and SAVE commands in BASIC to transfer assembler source or assembled code to SD, ZX Net or wherever.

Here is the Patched version of Zeus, with better 128 compatibility and stability on Next (patches by Simon N Goodwin, original courtesy of Simon Brattel). My 'mini IDE', ZEUSx.bas gets around the regression of 128 BASIC wiping the screen of T command output. It also allows us to load source and save source and binary without any risk of mistyping CODE arguments. It just fits in the same space as the original patch loader! It uses ZEUSx.bin from the patcher, as linked above.

After loading Zeus and adapting input to the CPU speed it prompts for a project name, loads the source file e.g. project.god and enters the assembler. After assembling when you return to BASIC line 80 reads the start address of the source, while other DPEEKs find the end, and binary if any was assembled, saves that as project.bin, and re-saves the updated .god tokenised source file. Eat your heart out Xcode, VS, CodeWarrior etc.

ZEUSx.bas makes assumptions, but it is written in BASIC so easy to edit if those are invalid. Specifically it assumes the source is at the default address 32768 (but if that's changed it spots and honours the change later) and that the first line of assembled code or data to be saved has its address labelled with ENT.

Both are good defaults but not always the right choice when working with big programs. We should have room for an extra 50 lines or so now the printer buffer no longer needs saving. But actually the patch may be more significant than that, because I found the PAGE over-write was also affecting return to BASIC and even the resolution of some forward jumps.

Here is the new Zeus loader with automatic project LOAD and SAVE for source and binary in standard ZX CODE files. It replaces the original loader and patcher Zeus.bas.

The patcher ZEUSy.bas loads ZEUS.BIN from your SD, sets a safe keyboard rate, flags a cold start for the source default address, fixes the off by 1 error in CLS that broke Spectrum 128 paging, and sets some other defaults which make automatic saving easier later... ZEUSx.bin is the result, same size as the 1983 version but 128-friendly and with room for even more lines of yummy Z80 assembler.

image of Zeus patching BASIC code

The POKEs ensure a known state so a BASIC framework can load and autosave source and binary. It infers the object code addresses from ENT and asmPC, the next codegen address (cheers Simon Brattel for the disassembly!) so I init those to 0 - in the distro image asmPC is 30017 IIRC so someone did the manual example before it was saved. Also the magic startup flag is 42 (indicating already initialised) so the distro copy never coldstarts, though that seems benign. And then I set the source pointer to 32767 so the T command reports 0 bytes not 53000-odd if nothing's loaded.

Zeus patcher - this loads Zeus.bin from Tools/Dev in the Spectrum Next distro and saves an improved version Zeusx.bin, which you can download from the link above. This file is not needed unless you're interested in how the patched version differs from the 1983 release or want to add further patches.

A minimal DOT command

Interested in writing dot commands but not sure where to start? This tiny but complete example shows how to read the command arguments and write messages to any channel from a dot command assembled to the required offset by Zeus. Put whatever you like in the middle, but read the Next system documentation before you make any assumptions about memory paging!

Zeus source for a minimal but useful dot command, ECHO - this file is in tokenised .god format which loads directly into Zeus. It shows how a dot command can be implemented in just 336 bytes of source - less than a page: ECHO source code in Zeus

The assembled ECHO command, just 17 bytes of assembled Z80 code. Compare that with the equivalent directly-executable native code command file size in 'modern' C-based systems... /bin/echo is 18,128 bytes on my macbook, but does have the extra -n option - you should be able to add that on Next in fewer than 18,111 bytes, if you need it!

Notice that A is 0 at the end and the Z flag is always set on return to signal 'no error' - see the docs for how to return an error code or message.

Here's the Dot-command converter to take the CODE saved after assembly and make a raw file which can be placed in C:/DOT directory and run by typing its name, e.g.


The key part of this is the loop at the end that writes a binary file with no header. ZX BASIC adds PLUS3DOS headers to files it saves, but those stop dot commands loading and working as expected. You'll need to do this for any dot command written on Next itself and saved as CODE from BASIC.

The first few lines sets up and run Zeus.bin from the March 2020 distro. Tweak this to suit yourself, e.g. to use the patched Zeus start at 57344 rather than 57062, and replace ECHO.ASM with ECHO.GOD to use the source version downloadable from the link above. The Next system browser was confused by .ASM used here, so the Zeus IDE now uses the .god extension for Zeus tokenised source files.

Reading CODE INKEY$ with the Floating Point Calculator

The BASIC command INKEY$ is a useful way to detect when a single key is currently pressed, and know which key it is. CODE INKEY$ returns the ASCII code of the character, e.g. 65 for "A", 49 for "1", etc. But it's difficult to call the corresponding Z80 routine in the ROM from assembly language, because the subroutine returns not a character code in a register but a five-byte entry on the BASIC calculator stack.

As well as BASIC and Z80 machine language, every Spectrum (and ZX-81) supports an internal language called Floating Point Calculator code, which is embedded in Z80 programs between RST 40 instructions and 'end-calc' FPC instructions that switch from the stack-based Forth-like FPC language to the hardware-level Z80 instruction stream.

FPC code is slower than Z80 assemble language but it can manipulate strings and five-byte decimal values accurate to nine decimal places, as well as the simple bytes and 16-bit integers natively supported by the Z80n. It is much more concise than Z80 code for this purpose, and extensively used in the ZX ROM to plot curves and implement complex mathematical functions like ATAN, LN and the ^ operator. ZX BASIC would be a lot less capable without it.

This example was written to test out the FPC disassembler in my Nextramon utility (shipped in the Next distro, alongside Zeus in Tools/Dev) and draws on examples from books by Ian Logan. It demonstrates a very general way of calling ROM routines even when they do not have a convenient entry point or ideal input and output expectations for Z80 programmers. It also enabled me to fix a bug - RST 40 was being incorrectly disassembled as RST 56. This is fixed in the version here and will be corrected in the next distro release.

Here is the code-inkey binary ready to load with LOAD "code-inkey.bin" CODE - it is fully relocatable and can run from whatever address you load it to.

Here is the code-inkey source code, in Zeus assembler format ready to load and assemble with ZEUSx.bas as documented above. Zeus does not know about the FPC calculator tokens so most of those instructions are byte values prefixed with DEFB. Unlike other dissassemblers, Nextramon detects and decodes FPC instructions automatically as it finds them in a Z80n program. Here is the relatively easy-to-follow dissassembly, with comments noting the token and call labels.

65500 3EA6     LD   A,166      ; INKEY$ 
65502 CD282D   CALL 11560      ; Stack-A 
65505 EF       RST   40        ; FPCALC 
65506 2F       CHR$ 
65507 18       VAL$ 
65508 1C       CODE 
65509 38       End-calc 
65510 C3A22D   JP   11682      ; Fp-to-BC

Two ROM routines are used to get integer values into and out of the floating point calculator. The routine at address 11560 is known as 'stack-A' and puts the value in the a register onto the 'calculator stack' where it does arithmetic. In this case the value of A is 166, the token of the INKEY$ function in ZX BASIC. The second ROM routine serves the opposite purpose. The routine at address 11682 reads the last value from the calculator stack and stores it as 16 bit integer in the BC register pair. This is uncoincidentally where USR expects the value it is to return to BASIC, so a USR call to address 65500 returns to BASIC the result of the preceding sequence of FPC instructions.

The ingenious bit is the four-byte sequence after the RST 40 instruction switches the Spectrum into FPC language. The CHR$ function, FPC token 47 ($2F) works as in BASIC, converting a byte value 0..255 into a single-character string. Now we have a string "INKEY$" on the stack, Logan showed how the much-maligned and misunderstood VAL$ function can be called to evaluate this 'string expression'. Token 24 ($18) calls the ROM string expression evaluator, which is not otherwise easily accessible, to find the string value of VAL$ CHR$ 166.

As in BASIC, VAL$ CHR$ "INKEY$" (where the string contains the INKEY$ function token byte 166, rather than the six-character name) returns the same thing as INKEY$ but by a more devious route. In this case the deviation is necessary to call the INKEY$ ROM routine in the context it expects. The next FPC instruction, byte value 28 ($1C), calls the ROM CODE function. This takes a string and converts it into an integer in the same place on the FPC stack. Token 56 ($38) stands for 'End-calc' and returns from FPC sub-language to Z80 code. The final JP to 11682 (Fp-to-BC) gets the stacked value into the BC register so it can be returned by USR. As we want to return after this, we use JP rather than CALL, saving a byte of code and five avoidable memory transfers.

The FPC instruction set is listed in the NextGuide-format documentation for Nextramon, downloadable from here. This includes links to the books by Ian Logan which explain the FPC works and show how it can be used. Use the .GUIDE command on the Next itself to read this hypertext file.

The following BASIC example program POKEs the code-inkey routine into any specified address and calls it repeatedly in a loop until the space key (ASCII code 32) is pressed. The PAUSE and BORDER statements in the REPEAT loop change the border from blue to red while the USR call is being interpreted, showing how long it takes to run. It's much slower than using IN to read any single group of five keys, but does a lot more work, scalling all the keys and converting the result to ASCII as well as doing the necessary string and numeric conversions required by the ROM routines.

490 POKE 65500,62,166,205,40,45,239,47,24,28,56,195,162,45
495 PAPER 6: CLS
510   PAUSE 1: BORDER 2:%k=% USR 65500: BORDER 1
520 REPEAT UNTIL %k=32
530 STOP

Just pressing the SHIFT key is enough to alter the timing of the routine, moving the border stripes down. Switch the CPU speed with RUN AT or the NMI menu to see how the border stripes shrink at higher speeds. If the border tumbles and flickers this means the loop is taking more than one PAUSE frame - around a 50th of a second - to complete. At speeds above 3.5 MHz this should not be a problem until you put a lot more code into the REPEAT loop.

This little program can be downloaded from here. Add

515   PRINT AT 0,0;%k;" "; : BORDER 0

or similar to see the values being read back - and how slow BASIC PRINT commands are. Now the blue area of the border shows time spent on line 515 and any spare time in the frame appears as a black area.

Enjoy! Simon N Goodwin, 2nd November 2020

Link to Simon's NextBASIC demos and download page, for Nextramon and other goodies.

Link to Simon N Goodwin's personal home-page