Z8 Introduction

        .cr     z8           To load this cross overlay

Don't be fooled by its name thinking the Z8 is a little brother of the Z80 family! To my humble opinion quite the opposite is true!
The designers at Zilog didn't make the Z8 compatible with any of the existing mircro processors. They've got rid of the traditional Accumulator, which is available in all other designs.
Every byte in the internal RAM memory can be used as Accumulator, giving the standard device up to 128 Accumulators! Even I/O ports can be used as Accumulators!
Other devices usually have to get along with one or two 16-bit data pointers. On the Z8 however you can group any two adjacent registers together to form a 16-bit data pointer. Therefore the standard version of the Z8 has a total of 64 data pointers!
And as if this isn't enough already it is very easy to switch context, e.g. during interrupt routines. Registers are grouped in banks of sixteen 8-bit registers each. You only have to change the RP register to switch to a completely different register bank, effectively saving all contents of the originally selected register bank.
The standard device is equipped with a total of 128 bytes of internal RAM. Apart from that the Z8 can address up to 64k of program memory and a separate 64k of data memory. Program memory can also be written to, as opposed to program memory on an 8051.

There are too many benefits to the Z8 to be mentioned here. The most important ones are mentioned above. Comparing the Z8 with the ever so popular Intel 8051 makes you wonder why Intel won the race! The 8051 doesn't even come close!

Why is the absence of a dedicated Accumulator so efficient? Because it reduces the need to move data in and out of the Accumulator to be able to perform an operation on the data. Let me give you an example:

First we take a look at how an 8051 shifts a 16-bit number 1 bit to the left.

        CLR    C
        MOV    A,R3
        RLC    A
        MOV    R3,A
        MOV    A,R2
        RLC    A
        MOV    R2,A

And now the way the Z8 would do the same thing.

        RLC    R3
        RLC    R2

Does this require any explanation?! The 8051 needs 7 instructions, where the Z8 is satisfied with only 3! If you don't count the clearing of the carry flag, it is a 3:1 ratio, simply because the Z8 doesn't have to move the data in and out of the Accumulator.
Not convinced yet? Then try to shift a 16-bit number 3 bits to the left then.

        CLR    C
        MOV    A,R3        Shift once
        RLC    A
        MOV    R3,A
        MOV    A,R2
        RLC    A
        MOV    R2,A

        CLR    C
        MOV    A,R3        Shift again
        RLC    A
        MOV    R3,A
        MOV    A,R2
        RLC    A
        MOV    R2,A

        CLR    C
        MOV    A,R3        And again
        RLC    A
        MOV    R3,A
        MOV    A,R2
        RLC    A
        MOV    R2,A

And now the Z8 code.

        RLC    R3          Shift once
        RLC    R2

        RLC    R3          Shift again
        RLC    R2

        RLC    R3          And again
        RLC    R2

I'm sorry, maybe I've got a bit carried away by the beauty of this processor. Does it show that I'm a little biased?

Programming Model

The programming model below shows the unusual structure of the Z8 processor. I only include a little summary about the features of the Z8's programming model here. It is not my intention to make the original documentation obsolete, so please refer to the original documentation for further details.

Z8 programming model

The first thing we should notice is the absence of a dedicated Accumulator because every location of the internal RAM may be used as Accumulator. The internal memory is divided into 16 banks of 16 registers each. All the internal RAM memory in the center column can be accessed directly, except bank $Ex.

One of the 16 banks can be selected as the working register bank, which enables short addressing of its 16 individual registers. This is shown in the left column, where register bank $4x is selected. Only working registers can be used as indirect/indexed addressing pointers, pointing to internal or external memory. Two adjacent working registers can be concatenated to form a 16-bit pointer register. The most significant byte of the target address is always located in the even numbered working register, while the least significant byte is located in the next odd numbered working register.
Please note that this does comply with the big endian model.
Working registers are numbered R0 ... R15, while register pairs are numbered from RR0 ... RR14 (only the even numbers).

Not all register banks are always available on all Z8 devices. And some devices may even have more memory, which will be bank switched to maintain addressing compatibility. Please refer to the proper manual to see what memory is available.

There are three special banks. The lowest 4 bytes of bank $0x are assigned to the 4 standard I/O ports of the Z8 and therefore not to real RAM. This doesn't mean however that they can't be used as Accumulator!
Bank $Ex can be physically equipped with memory, but you can't access this memory directly. It can only be accessed using indirect addressing mode.
Bank $Fx is assigned to the system control registers, like stack pointer, flag register and I/O control registers. They can be used like any other registers although they serve special purposes. The right hand column shows the standard functions of these registers. The register names are not pre-defined in the SB-Assembler, but you can name them yourself with normal labels.

Serial Input Output register

This register is used to pass data to and from the serial port. Reading this register reads the data that was serially clocked in. Writing to it sets the data to be shifted out of the serial port.
Please refer to the user manual for all the details on serial I/O on the Z8.

Timer control registers TMR ($F1), T1 ($F2), PRE1 ($F3), T0 ($F4) and PRE0 ($F5)

These registers are used to control the 2 basic timers on the Z8. A description of the timers is beyond the scope of this brief description though. Please refer to the user manual for all the details.

Port configuration registers P2M ($F6), P3M ($F7) and P01M ($F8)

These three registers are used to select the input/output configuration of the 4 basic I/O ports on a Z8 controller. The bits on port 2 and 3 can be individually set to be an input or an output, dictated by the P2M and P3M registers.
The data direction and special features of ports 0 and 1 are controlled by the P01M register, which also has some other functions like selecting between internal or external stack memory.

Interrupt control registers IPR ($F9), IRQ ($FA) and IMR ($FB)

The Z8 has a powerful interrupt system. Interrupts can be serviced automatically via the interrupt vectors, or they can be polled.

The Interrupt Mask Register (IMR) determines which interrupts are enabled. A vectored interrupt service routine is only possible when its associated mask bit is set.
The Interrupt Request Register (IRQ) can be polled to find out what interrupts are pending. These bits are set whenever an interrupt is triggered, even when that particular interrupt is disabled by the IMR register. Please refer to the manual to know whether IRQ bits are cleared automatically or have to be cleared manually.
The Interrupt Priority Register (IPR) is used to set interrupt priorities.

Describing all interrupt capabilities of the Z8 is beyond the scope of this brief description, so please refer to the appropriate manuals for details.

The FLAGS register (Address $FC)

The FLAGS register contains 8 system flags:

Bit 7CCarry flag
Bit 6ZZero Flag
Bit 5SSign Flag
Bit 4VOverflow Flag
Bit 3DDecimal Flag
Bit 2HHalf carry Flag
Bit 1F2User definable Flag
Bit 0F1User definable Flag

Register set Pointer RP (Address $FD)

The 4 most significant bits determine which register bank is to be used. After a hardware reset the RP register is cleared, effectively activating working register set $0x.
Only the 4 most significant bits are used as register pointer. The other 4 bits are not used on the basic Z8 device. On more advanced Z8 devices these bits are used to bank switch more memory or I/O control registers.

Working registers are addressed with only 4 bits, whereas all other internal memory is addressed using 8 bits. This makes the working registers faster. Apart from being faster the working registers have other privileges. They can be joined together to form 16-bit registers and they can be used as pointers.

The SB-Assembler can automatically translate label names to working registers. To be able to do this the SB-Assembler needs to know what the currently expected working register bank is. This is done with the .RP directive, which is added by the Z8 Cross overlay.
An internal RAM address is translated to a working register, or register pair, if the instruction allows it and if the expression didn't contain a forward referenced label.

Stack pointer SPL and SPH (Addresses $FF and $FE)

The stack on a Z8 can be located in internal or in external RAM memory. In case the stack is located in external RAM both stack pointer registers are concatenated to form a 16-bit pointer. When the stack is located in internal RAM memory only SPL is used as stack pointer. This frees the SPH register, which can then be used like any other internal RAM memory byte.
It goes without saying that only real RAM memory can be used as stack memory.

During a Push operation the stack pointer is decremented first and then the data is stored at the address pointed to by the stack pointer. This means that the stack pointer always points to the last byte pushed on to the stack. A Pull operation loads the data first, then increments the stack pointer.
Please note that the stack grows down in memory on the Z8.

CALL instructions store the address of the next instruction to be executed after a return on the stack, pushing its LSB first. Interrupts also push the Flags register on the stack, after pushing the return address.

The Program Counter

The program counter PC is normally incremented after fetching each instruction or operand byte during program execution. The only way you can change this behaviour is with the jump, call and return instructions. Also interrupts can change the program counter's value.
The PC is the only register that is not implemented in the register file.

There is one odd thing to mention about the Z8. When the device comes out of reset it will start execution of the program from address $000C, instead of $0000, which you might have expected.
The first 12 bytes of program ROM are occupied by 6 interrupt vectors.


In Version 3 of the SB-Assembler you can display the instruction times of each instruction by turning on the list option TON. The shown values are the number of clock cycles each instruction takes.

Reserved Words

The SB-Assembler Z8 cross overlay has some reserved words. Reserved words are all register names and condition codes. You better avoid these reserved words when you assign your own labels. E.g. don't call your labels R0 or RR12.
If you do use the reserved words as label names you may expect unpredictable behaviour of the assembler sooner or later. Please note that the assembler will not warn you if you try to assign a label with a reserved name!
Reserved names can not be used in expressions, like label names can. An Undefined label error will be reported if you do try to use a reserved word in an expression because it is treated as a normal label in this case.

Here's the list of all reserved words:

R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15,
RR0, RR2, RR4, RR6, RR8, RR10, RR12, RR14,

Special Features

Register addressing

Internal memory of the Z8 consists of 16 banks of 16 registers each. These registers are simply a small portion of internal memory that can be addressed with only 4 bits, instead of 8. Many instructions which use registers are only 1 byte long, resulting in compact code and fast execution times.

Selecting one of the 16 banks is done by writing the desired register bank number into the RP register (upper 4 bits). Registers are called R0 to R15 by default. The SB-Assembler allows you to give your registers more meaningful names by assigning labels to the appropriate RAM addresses. The SB-Assembler will automatically use register addressing whenever a label is used where register addressing is available.
This is also true for register pairs RR0 to RR14.

The assembler will use register or register pair mode whenever the addressing mode is available and the effective address is within the currently selected register bank. The address must be resolved completely to know for sure that it will be a register addressable memory location, therefore the expression may not contain any forward referenced labels. The worst case is expected when a forward referenced label is used, which means that the assembler will use an 8-bit address for internal memory.
You must tell the assembler what the expected register bank is by means of the .RP directive.

There is one situation where the assembler is unable to translate the address to a register (pair). Both instructions LDCI and LDCE need two parameters, an indirect register and an indirect register pair. The problem is though that the two operands can be reversed depending on which one is the source or destination.

The example below explains the problem:

        LDCI   @R1,@RR2         It is perfectly clear who is who
        LDCI   @RR2,@R1         This is clear too
        LDCI   @POINTER,@RR2    Even this is clear
        LDCI   @RR2,@POINTER    No problem with this either
        LDCI   @PNTR1,@PNTR2    But who is who here?

Only with these two rarely used instructions you may not use a label name (or expression) to refer to the first operand if it must be a register pair. So if the first operand isn't an explicit register pair, e.g. RR2, then the assembler will assume that the 1st operand is a register and the 2nd operand is a register pair.
According to this rule PNTR2 in the last line of the example is assumed to be the register pair.

CMOS instructions

Some Z8 versions have a few extra instructions to limit power consumption during inactive times. These instructions are HALT, STOP and WDT. The SB-Assembler Z8 cross knows these instructions too. But you shouldn't use these instructions unless you are absolutely certain that the processor you're programming really understands them.
Version 3 of the SB-Assembler knows an extra CMOS instruction WDH which was not defined when Version 2 was created.

Differences between Version 2 and Version 3

Version 3 of the Z8 cross has a few additional differences between Version 2.

  • Register addresses from $E0 to $EF are not directly accessible. Therefore Version 3 will now initiate an Out of range error if one of these values is used as direct address.
  • For the same reason the .RP directive will not accept values between $E0 and $EF in Version 3.
  • The official syntax for the JR never and JP never instructions is JR F,destination or JP F,destination. Version 3 will follow this official syntax, whereas Version 2 accepts a JR or JP instruction without operand.
    I doubt if you'll ever notice the difference because the jump never instruction is quite useless by nature.
  • The SRP instruction will now accept any value between $00 and $FF (except values between $E0 and $EF). Version 2 only accepted values with the LSD being 0. However later models of the Z8 used the LSD as memory bank switch, making the LSD significant too.
  • The address confusion for the LDCI and LDEI instructions is a bit reduced in Version 3. See above for a description of the confusion which may arise when the assembler can't distinguish both operands from each other. I have made the parser a bit more clever in Version 3. An operand is considered an indirect register if it's value is odd, because it can't be an indirect register pair if is not even.
    Thus if operand2 is an odd value in LDCI @operand1,@operand2, then operand 1 will be assumed to be an indirect register pair.

Overlay Initialization

Three things are set while initializing the Z8 overlay every time it is loaded by the .CR directive.

  • The register pointer (the one that's changed by the .RP directive) is set to 0.
  • Big endian model is selected for 16-bit addresses and for the .DA and .DL directives. This means that words or long words are stored with their high byte first.
  • The maximum program counter value is set to $FFFF.

.RP     Register bank Pointer


        .RP  expression


The .RP directive is used to tell the SB-Assembler what the currently selected register bank is. Knowing this enables the SB-Assembler to select between register or direct addressing mode automatically. The SB-Assembler will use register addressing mode whenever the address and the instruction both allow it.


One of the features of the Z8 SB-Assembler is the ability to select the most economical addressing mode for internal RAM. You can assign any name to an internal RAM location and the SB-Assembler will use register addressing mode whenever it can. In order to do this the SB-Assembler must know which one of the 16 possible register banks is selected at the moment. You can use the .RP directive to tell the SB-Assembler what register bank is selected.
Please note that you only tell the SB-Assembler what register bank is supposed to be selected. It is the programmer's responsibility to effectively set the RP register of the processor correctly to make it really happen! The SB-Assembler has no way of checking this and is therefore unable to warn you about mistakes. So the .RP directive is in no way a substitute for the loading of the RP register which is required to select the proper register bank on the processor!

The expression must evaluate to a value from $00 to $F0, in increments of $10 and may not contain forward referenced labels. Other values will result in an Out of range error.
The increments of $10 behaviour is done to make the .RP directive behave the same as the SRP instruction.
Per default (after loading or reloading the Z8 cross overlay) the selected register bank is 0. This is in sync with the Z8 itself, because there register bank 0 is also the default selection after reset.

Please note that you can use a complete expression to indicate a direct address, even if it is translated into a register automatically. But it is not possible to use a register name (like R3) in any expression.
The SB-Assembler will use the direct addressing mode instead of register mode if the address expression contains a forward referenced label.

The Z8 assembler does not allow you to force register or direct addressing modes with < and > symbols. But you can always use normal register names to force register addressing mode, e.g. R3 or RR8.


0014-         LABEL    .EQ  $14            Just a location
0000-                  .RP  $00            Assume register bank $00
0000-31 00             SRP  #$00           Select bank $00 on Z8
0002-2C 12             LD   R2,#$12        Load R2 with $12
0004-2C 12             LD   $02,#$12       Does the same
0006-E6 14 12          LD   $14,#$12       Load RAM address $14 with $12
0009-                  .RP  $10            Now assume register bank $10
0009-31 10             SRP  #$10           Select bank $10 on Z8
000B-2C 12             LD   R2,#$12        Load R2 with $12
000D-E6 02 12          LD   $02,#$12       This is not a register anymore!
0010-4C 12             LD   $14,#$12       Load register R4 with $12
0012-4C 12             LD   LABEL,#$12     Works also with expressions
0014-E6 04 12          LD   FORWARD,#$12   But not with forward referenced!
0014-         FORWARD  .EQ  $14

Please note that the assembler would still generate the same code when the SRP $10 instruction at address $0009 was omitted. But in real life R4 would point to RAM address $04 in the instruction at address $0010 and not the intended address $14!

Differences Between Other Assemblers

There are some differences between the SB-Assembler and other assemblers for the Z8 processor. These differences require you to adapt existing source files before they can be assembled by the SB-Assembler. This is not too difficult though, and is the (small) price you have to pay for having a very universal cross assembler.

  • Normal Zilog Z8 hexadecimal numbers are preceded by the % symbol. The SB-Assembler will interpret the % symbol as a binary prefix though. The hexadecimal prefix is as usual $ or 0x. Or you can use the H postfix to indicate hex values.
  • The Z8 uses the @ prefix to indicate indirect addressing mode. This may interfere with SB-Assembler's octal number notation. In the case of the Z8 cross the SB-Assembler will interpret the @ symbol as an indirect prefix if there is any doubt.
    So the use of octal numbers is somewhat restricted when using the Z8 cross. This is not such a big problem because octal numbers are a bit out of fashion anyway.
    You can always precede the octal symbol by a + sign to avoid confusion with the indirect symbol. Or you can use the 0123Q octal notation instead.
  • Automatic selection of the shortest addressing mode when internal memory address is within the currently selected register bank is not supported by many other assemblers.
  • The obvious differences in notation of directives and radixes common to all SB-Assembler crosses.
  • Don't forget that the SB-Assembler does not allow spaces in or between operands. Only Version 3 will allow one space after each comma separating operands in the operand field.