Z80 Assembler Reference Manual
- 1 Introduction
- 2 Usage
- 3 The Assembly Language
- 3.1 Symbols
- 3.2 Constants
- 3.3 Expressions
- 3.4 Pseudo-ops
- 3.5 IRP and IRPC
- 3.6 Extended Condition Codes
- 4 Assembler Directives
- 5 Diagnostics
- 6 Z80/Z180/64180 Instruction Set
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.
An_identifier an_identifier an_identifier1 $$$ ?$_123455
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.
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:
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:.
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.
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.
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:
|.gt.||Signed greater than|
|.high.||Hi byte of operand|
|.low.||Low byte of operand|
|.lt.||Signed less than|
|.ult.||Unsigned less than|
|.ugt.||Unsigned greater than|
|<||Signed less than|
|>||Signed greater than|
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 alabel: ld hl,astring call putit ld hl,anotherstring psect data, global astring: defm 'A string of chars' defb 0 anotherstring: defm 'Another string' defb 0 psect text putit: 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:
COND, IF, ELSE, ENDC
- 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:
IF CPM call 5 ELSE call os_func ENDC
- See COND.
- See COND.
- See MACRO.
- 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:
- 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 ENDM
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 ENDM
MACRO bdos,func,arg ld de,arg ld c,func call 5 ENDM
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 ldir pop hl pop de nocopy: pop bc pop af ENDM
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) ldir 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:
REPT 3 ld (hl),0 inc hl ENDM
will expand to
ld (hl),0 inc hl ld (hl),0 inc hl ld (hl),0 inc hl
IRP and IRPC
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 ENDM
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 ENDM
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:
|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|
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.