Z80 Assembler Reference Manual

From HI-TECH C for CP/M Fan WIKI(EN)
Jump to: navigation, search


The assembler incorporated in the HI-TECH C compiler system is a full-featured relocating macro assembler accepting Zilog mnemonics. These mnemonics and the syntax of the Z80 assembly language are described in the "Z80 Assembly Language Handbook" published by Zilog and are included at the end of this manual as a reference. The assembler implements certain extensions to the operands allowed, and certain additional pseudo-ops, which are described here. The assembler also accepts the additional opcodes for the Hitachi 64180 and Z180 processors.


The assembler is named zas, and is invoked as follows:

ZAS options files ...

The files are one or more assembler source files which will be assembled, but note that all the files are assembled as one, not as separate files. To assemble separate files, the assembler must be invoked on each file separately. The options are zero or more options from the following list:

Ignore arithmetic overflow in expressions. The -N option suppresses the normal check for arithmetic overflow. The assembler follows the "Z80 Assembly Language Handbook" in its treatment of overflow, and in certain instances this can lead to an error where in fact the expression does evaluate to what the user intended. This option may be used to override the overflow checking.
Attempt to optimize jumps to branches. The -J option will request the assembler to attempt to assemble jumps and conditional jumps as relative branches where possible. Only those conditional jumps with branch equivalents will be optimized, and jumps will only be optimized to branches where the target is in branch range. Note that the use of this option slows the assembly down, due to the necessity for the assembler to make an additional pass over the input code.
Treat undefined symbols as external. The -U option will suppress error messages relating to undefined symbols. Such symbols are treated as externals in any case. The use of this option will not alter the object code generated, but merely serves to suppress the error messages.

Place the object code in file. The default object file name is constructed from the name of the first source file. Any suffix or file type (i.e. anything following the rightmost dot ('.') in the name is stripped, and the suffix .obj appended. Thus the command
ZAS file1.as file2.z80
will produce an object file called file1.obj. The use of the -O option will override this default convention, allowing the object file to be arbitrarily named. For example:
ZAS -ox.obj file1.obj
will place the object code in x.obj.
Place an assembly listing in the file list, or on standard output if list is null A listfile may be produced with the -L option. If a file name is supplied to the option, the list file will be created with that name, otherwise the listing will be written to standard output (i.e. the console). List file names such as CON: and LST: are acceptable.
The listing is to be formatted for a printer of given width The -W option specifies the width to which the listing is to be formatted. E.g.
ZAS -Llst: -W80 x.as
will output a listing formatted for an 80 column printer to the list device.
This options requests ZAS to produce cross reference information in a file. The file will be called xxx.crf where xxx is the base part of the first source file name. It will then be necessary to run the CREF utility to turn this information into a formatted listing.

The Assembly Language

As mentioned above, the assembly language accepted by zas is based on the Zilog mnemonics. You should have some reference book such as the "Z80 Assembly Language Handbook". Described below are those areas in which zas differs, or has extensions, compared to the standard Zilog assembly language.


The symbols (labels) accepted by the assembler may be of any length, and all characters are significant. The characters used to form a symbol may be chosen from the upper and lower case alphabetics, the digits 0-9, and the special symbols underscore ('_'), dollar ('$') and question mark ('?'). The first character may not be numeric. Upper and lower case are distinct. The following are all legal and distinct symbols.


Note that the symbol $ is special (representing the current location) and may not be used as a label. Nor may any opcode or pseudo-op mnemonic, register name or condition code name. You should note the additional condition code names described later.

Temporary Labels

The assembler implements a system of temporary labels, useful for use within a localized section of code. These help eliminate the need to generate names for labels which are referenced only in the immediate vicinity of their definition, for example where a loop is implemented.

A temporary label takes the form of a digit string. A reference to such a label requires the same digit string, plus an appended b or f to signify a backward or forward reference respectively. Here is an example of the use of such labels.

entry_point:   ;This is referenced from far away
    ld   b,10
1:  dec  c
    jr   nz,2f ;if zero, branch forward to 2:
    ld   c,8
    djnz 1b    ;decrement and branch back to 1:
    jr   1f    ;this does not branch to the
               ;same label as the djnz
2:  call fred  ;get here from the jr nz,2f
1:  ret        ;get here from the jr 1f

The digit string may be any positive decimal number 0 to 65535. A temporary label value may be re-used any number of times. Where a reference to e.g. 1b is made, this will reference the closest label 1: found by looking backwards from the current point in the file. Similarly 23f will reference the first label 23: found by looking forwards from the current point in the file.


Constants may be entered in one of the radices 2, 8, 10 or 16. The default is 10. Constants in the other radices may be denoted by a trailing character drawn from the following set:

Character Radix Name
B 2 binary
O 8 octal
Q 8 octal
o 8 octal
q 8 octal
H 16 hexadecimal
h 16 hexadecimal

Hexadecimal constants may also be specified in C style, for example LD A,0x21. Note that a lower case b may not be used to indicate a binary number, since 1b is a backward reference to a temporary label 1:.

Character Constants

A character constant is a single character enclosed in single quotes ('). Multi character constants may be used only as an operand to a DEFM pseudo-op.

Floating Constants

A floating constant in the usual notation (e.g. 1.234 or 1234e-3) may be used as the operand to a DEFF pseudo-op.

Opcode Constants

Any z80 opcode may be used as a constant in an expres- sion. The value of the opcode in this context will be the byte that the opcode would have assembled to if used in the normal way. If the opcode is a 2-byte opcode (CB or ED prefix byte) only the second byte of the opcode will be used. This is particularly useful when setting up jump vectors. For example:

ld   a,jp         ;a jump instruction
ld   (0),a        ;0 is jump to warm boot
ld   hl,boot      ;done here
ld   (1),hl


Expressions are constructed largely as described in the "Z80 Assembly Language Handbook".


The following operators may be used in expressions:

Operator Meaning
& Bitwise AND
* Multiplication
+ Addition
- Subtraction
.and. Bitwise AND
.eq. Equality test
.gt. Signed greater than
.high. Hi byte of operand
.low. Low byte of operand
.lt. Signed less than
.mod. Modulus
.not. Bitwise complement
.or. Bitwise or
.shl. Shift left
.shr. Shift right
.ult. Unsigned less than
.ugt. Unsigned greater than
.xor. Exclusive or
/ Divison
< Signed less than
= Equality
> Signed greater than
^ Bitwise or

Operators starting with a dot "." should be delimited by spaces, thus label .and. 1 is valid but label.and.1 is not.


Zas produces object code which is relocatable; this means that it is not necessary to specify assembly time where the code is to be located in memory. It is possible to do so, by use of the ORG pseudo-op, however the preferred approach is to use program sections or psects. A psect is a named section of the program, in which code or data may be defined at assembly time. All parts of a psect will be loaded contiguously into memory, even if they were defined in separate files, or in the same file but separated by code for another psect. For example, the following code will load some executable instructions into the psect named text, and some data bytes into the data psect.

    psect text, global

    ld    hl,astring
    call  putit
    ld    hl,anotherstring

    psect data, global
    defm  'A string of chars'
    defb  0
    defm  'Another string'
    defb  0

    psect text

    ld    a,(hl)
    or    a
    ret   z
    call  outchar
    inc   hl
    jr    putit

Note that even though the two blocks of code in the text psect are separated by a block in the data psect, the two text psect blocks will be contiguous when loaded by the linker. The instruction "ld hl,anotherstring" will fall through to the label "putit:" during execution. The actual location in memory of the two psects will be determined by the linker. See the linker manual for information on how psect addresses are determined.

A label defined in a psect is said to be relocatable, that is, its actual memory address is not determined at assembly time. Note that this does not apply if the label is in the default (unnamed) psect, or in a psect declared abso- lute (see the PSECT pseudo-op description below). Any labels declared in an absolute psect will be absolute, that is their address will be determined by the assembler.

With the version of ZAS supplied with version 7 or later of HI-TECH C, relocatable expressions may be combined freely in expressions. Older versions of ZAS allowed only limited arithmetic on relocatable expressions.


The pseudo-ops are based on those described in the "Z80 Assembly Language Handbook", with some additions.


This pseudo-op should be followed by a comma-separated list of expressions, which will be assembled into sequential byte locations. Each expression must have a value between -128 and 255 inclusive. DB can be used as a synonym for DEFB. Example:
DEFB  10, 20, 'a', 0FFH
DB    'hello world',13,10,0


This pseudo-op assembles floating point constants into 32 bit HI-TECH C format floating point constants. For example:
pi: DEFF  3.14159


This operates in a similar fashion to DEFB, except that it assembles expressions into words, without the value restriction. Example:
DEFW  -1, 3664H, 'A', 3777Q


Defs reserves memory locations without initializing them. Its operand is an absolute expression, representing the number of bytes to be reserved. This expression is added to the current location counter. Note however that locations reserved by DEFS may be initialized to zero by the linker if the reserved locations are in the middle of the program. Example:
DEFS  20h   ;reserve 32 bytes of memory


Equ sets the value of a symbol on the left of EQU to the expression on the right. It is illegal to set the value of a symbol which is already defined. Example:
SIZE      equ   46


This is identical to EQU except that it may redefine existing symbols. Example:

SIZE      defl  48


Defm should be followed by a string of characters, enclosed in single quotes. The ASCII values of these characters are assembled into successive memory locations. Example:
DEFM  'A string of funny *@$ characters'


The end of an assembly is signified by the end of the source file, or the END pseudo-op. The END pseudo-op may optionally be followed by an expression which will define the start address of the program. This is not actually useful for CP/M. Only one start address may be defined per program, and the linker will complain if there are more. Example:
END   somelabel


Conditional assembly is introduced by the COND pseudo-op. The operand to COND must be an absolute expression. If its value is false (zero) the code following the COND up to the corresponding ENDC pseudo-op will not be assembled. COND/ENDC pairs may be nested. IF may be used as a synonym for COND. The ELSE pseudo operation may be included within a COND/ENDC block, for example:
call  5
call  os_func








This pseudo-op allows specification of relocatable program sections. Its arguments are a psect name, optionally followed by a list of psect flags. The psect name is a symbol constructed according to the same rules as for labels, however a psect may have the same name as a label without conflict. Psect names are recognized only after a PSECT pseudo-op. The psect flags are as follows:
Psect is absolute
Psect is global
Psect is not global
Psect is to be overlapped by linker
Psect is to be read-only
If a psect is global, the linker will merge it with any other global psects of the same name from other modules. Local psects will be treated as distinct from any other psect from another module. Psects are global by default.
By default the linker concatenates code within a psect from various modules. If a psect is specified as OVRLD, the linker will overlap each module's contribution to that psect. This is particularly useful when linking modules which initialize e.g. interrupt vectors.
The PURE flag instructs the linker that the psect is to be made read-only at run time. The usefulness of this flag depends on the ability of the linker to enforce the requirement. CP/M fails miserably in this regard.
The ABS flag makes a psect absolute. The psect will be loaded at zero. This is useful for statically initializing interrupt vectors and jump tables. Examples:

PSECT     text, global, pure
PSECT     data, global
PSECT     vectors, ovrld


Global should be followed by one more symbols (comma separated) which will be treated by the assembler as global symbols, either internal or external depending on whether they are defined within the current module or not. Example:

GLOBAL      label1, putchar, _printf


An ORG pseudo-op sets the current psect to the default (absolute) psect, and the location counter to its operand, which must be an absolute expression. Example:
ORG   100H


This pseudo-op defines a macro. It should be either preceded or followed by the macro name, then optionally followed by a comma-separated list of formal parameters. The lines of code following the MACRO pseudo-op up to the next ENDM pseudo-op will be stored as the body of the macro. The macro name may subsequently be used in the opcode part of an assembler statement, followed by actual parameters. The text of the body of the macro will be substituted at that point, with any use of the formal parameters substituted with the corresponding actual parameter. For example:
print     MACRO string
    psect data
999:      db    string,'$'
    psect text
    ld    de,999b
    ld    c,9
    call  5

When used, this macro will expand to the 3 instructions in the body of the macro, with the actual parameters substituted for func and arg. Thus print 'hello world' expands to

    psect data
999:      db    'hello world','$'
    psect text
    ld    de,999b
    ld    c,9
    call  5

Macro arguments can be enclosed in angle brackets ('<' and '>') to pass arbitrary text including delimiter characters like commas as a single argument. For example, suppose you wanted to use the print macro defined above to print a string which includes the carriage return and linefeed characters. The macro invocation:

print 'hello world',13,10

would fail because 13 and 10 are treated as extra arguments and ignored. In order to pass a string which includes commas as a single argument, you could write:

print <'hello world',13,10>

which would cause the text 'hello world',13,10 to be passed through as a single argument. This would expand to the following code:

    psect data
999:      db    'hello world',13,10,'$'
    psect text
    ld    de,999b
    ld    c,9
    call  5

ZAS supports two forms of macro declaration for compatibility with older versions of ZAS and other Z80 assemblers. The macro name may be declared either in the label field before the MACRO pseudo-op, or in the operand field after the MACRO pseudo-op. Thus these two MACRO declarations are equivalent:

bdos      MACRO func,arg
ld    de,arg
ld    c,func
call  5


MACRO bdos,func,arg
ld    de,arg
ld    c,func
call  5


The LOCAL pseudo-op allows unique labels to be defined for each expansion of a macro. Any symbols listed after the LOCAL directive will have a unique assembler-generated symbol substituted for them when the macro is expanded. For example:

copy      MACRO source,dest,count
    LOCAL nocopy
    push  af
    push  bc
    ld    bc,source
    ld    a,b
    or    c
    jr    z,nocopy
    push  de
    push  hl
    ld    de,dest
    ld    hl,source
    pop   hl
    pop   de
nocopy:   pop   bc
    pop   af

when expanded will include a unique assembler generated label in place of nocopy. For example, copy (recptr),buf,(recsize) will expand to:

    push  af
    push  bc
    ld    bc,(recsize)
    ld    a,b
    or    c
    jr    z,??0001
    push  de
    push  hl
    ld    de,buf
    ld    hl,(recptr)
    pop   hl
    pop   de
??0001:   pop   bc
    pop   af

if invoked a second time, the label nocopy would expand to ??0002.


The REPT pseudo-op defines a temporary macro which is then expanded a number of times, as determined by its argument. For example:

ld    (hl),0
inc   hl

will expand to

ld    (hl),0
inc   hl
ld    (hl),0
inc   hl
ld    (hl),0
inc   hl


The IRP and IRPC directives are similar to REPT, however instead of repeating the block a fixed number of times it is repeated once for each member of an argument list. In the case of IRP the list is a conventional macro argument list, in the case of IRPC it is successive characters from a string. For example:

    IRP   string,<'hello world',13,10>,'arg2'
    LOCAL str
    psect data
str:      db    string,'$'
    psect text
    ld    c,9
    ld    de,str
    call  5

would expand to

    psect data
??0001:   db    'hello world',13,10,'$'
    psect text
    ld    c,9
    ld    de,??0001
    call  5
    psect data
??0002:   db    'arg2','$'
    psect text
    ld    c,9
    ld    de,??0002
    call  5

Note the use of LOCAL labels and angle brackets in the same manner as with conventional macros.

IRPC is best demonstrated using the following example:

IRPC  char,ABC
ld    c,2
ld    e,'char'
call  5

will expand to:

ld    c,2
ld    e,'A'
call  5
ld    c,2
ld    e,'B'
call  5
ld    c,2
ld    e,'C'
call  5

Extended Condition Codes

The assembler recognizes several additional condition codes. These are:

Code Equivalent Meaning
alt m Arithmetic less than
llt c Logical less than
age p Arithmetic greater or equal
lge nc Logical greater or equal
di Use after ld a,i for test-
ei ing state of interrupt
enable flag - enabled or
disabled respectively.

Assembler Directives

An assembler directive is a line in the source file which produces no code, but rather which modifies the behaviour of the assembler. Each directive is recognized by the presence of an asterisk in the first column of the line, followed immediately by a word, only the first character of which is looked at. the line containing the directive itself is never listed. The directives are:

Use the text following the directive as a title for the listing.
Use the text following the directive as a subtitle for the listing; also causes an *Eject.
May be followed by ON or OFF to turn listing on or off respectively. Note that this directive may be used inside a macro or include file to control listing of that macro or include file. The previous listing state will be restored on exit from the macro or include file.

The file named following the directive will be included in the assembly at that point.
A new page will be started in the listing at that point. A form feed character in the source will have the same effect.

Some examples of the use of these directives:

*Title Widget Control Program
*Heading Initialization Phase

*Include widget.i


An error message will be written on the standard error stream for each error encountered in the assembly. This message identifies the file name and line number and describes the error. In addition the line in the listing where the error occurred will be flagged with a single character to indicate the error. The characters and the corresponding messages are:

Absolute expression required
Bad arg to *L
Bad arg to IM
Bad bit number
Bad character constant
Bad jump condition
Directive not recognized
Digit out of range
EOF inside conditional
Expression error
Garbage after operands
Garbage on end of line
Index offset too large
Jump target out of range
Lexical error
Multiply defined symbol
Operand error
Phase error
Psect may not be local and global
Relocation error
Size error
Syntax error
Undefined symbol
Undefined temporary label
Unterminated string

Z80/Z180/64180 Instruction Set

The remainder of this chapter is devoted to a complete instruction set listing for the Z80, Z180, 64180 and NSC800 processors. The Z180 and 64180 will execute all Z80 instructions, although the timing is different.