Assembler for Dummies (Z80)
This page was last modified 00:58, 11 December 2018 by Gdx. Based on work by NYYRIKKI and Grauw.

Contents

The basics

This is a brief description whose purpose is to understand the basics.

The assembler is a very basic language whose purpose is to simplify the creation of a program in machine language. The statements are therefore mainly transcribing CPU statements plus a few others to simplify the development of a program.
Machine statements are called opcodes. The others are usually functions that vary from one assembler to another.

The registers

It is important to understand what a register is before going further.

Register is a specialized internal memory of 8bit. Internally always two of these registers form a pair that is called register pair. During programming the registers can be used individually (8bit) or as a register pair (16bit) depending of what instruction is used. Regardless of the method they point to same physical memory. Changing ie. register D will affect register pair DE and other way around. Please note that when the register pairs are loaded or stored to memory the later member of pair will be always stored to the address pointed and the first member will be stored to address +1. This means that storing ie. DE will become E,D in RAM.

The registers can not be thought as a general memory since not all of the Z80 commands can accept all registers as parameters and this may feel a bit hard at start. Each register pair have anyway their own characteristics of what they are good for. Here we do not try to describe all that you can do with them as there are manuals for that, but just these kind of typical characteristics.

The most important register pairs to know are:

A and F that form register pair AF. A is also called accumulator on some documentations. This register is the most important register since all 8bit calculations are done against this register. This is also why it practically can not be used to store information as you will end up loading and saving this register very often. F is also very important register, but it can not be used directly. It is divided to individual status bits called flags that change each time the CPU executes a command that needs to return a status. The individual bits of this register have different names such as carry flag (C), Zero flag (Z) and so on. Please see flags for more information. AF as a register pair can not be used together to other than storing the accumulator together with the status flags to memory or restoring that.

B and C that form register pair BC. These registers can be used as temporary CPU internal storage. B is usually good choice as a 8bit loop counter. C is usually used when you need to point I/O port. Together register pair BC is usually used for storing 16bit "length" or other 16bit value needed for calculation.

D and E that form register pair DE. As individual registers they don't have any "special gifts" other than being good for CPU internal temporary storage, but when they are used together as a pair they are usually used to point a "destination" in memory. DE is also good choice when 16bit value is needed for calculation.

H and L that form register pair HL. As individual registers these also don't have any "special gifts" but when they are used together they form the most important 16bit pair as typically all 16bit calculations are done against HL. It is often used also as a "source" when pointing memory.

Register pair SP. This is the 16bit pointer that defines where commands like CALL or PUSH store their information. Beginner programmer very rarely needs to manipulate this register directly, but it is good to know. It is also important to know that this points to END of storage, so any new values are stored before the address this is pointing to and the content of SP is decreased by 2. 8bit values that form the register pair are not ever used individually, so they don't even have 8bit names.

If you have read this far about registers, you can skip rest of this chapter and come back later to read more. With these already mentioned registers you can already do practically everything that the MSX can do. Naturally knowing about rest of the registers is also important when you get to speed, but I suggest that you stick to these in your first programs.


Rest of the registers:

Register pair PC also known as Program Counter. This always points to where the Z80 is reading program from and although it is very important, programmer very rarely need to think it as a register pair. 8bit values that form the register pair are not ever used individually, so they don't even have 8bit names. PC is also not ever pointed directly, but it is manipulated with commands like JP xxxx (jump) that can be thought as LD PC,xxxx or RET that can be thought as POP PC.

IXh and IXl that form register pair IX. As individual registers these are quite good as temporary storage. They are slower than previously mentioned alternatives, but still faster than using CPU external RAM. Using them as individual registers is anyway officially undocumented. Practically these are safe to use, but you may find an assembler that does not compile the code if you use the names like IXl or IXh. As a pair they behave very similar than HL only slower. The special feature is that many times you may use extra offset value when you are using IX as a pointer.

IYh and IYl that form register pair IY. These registers individually as well as a pair work in all cases exactly like IX does.

I and R that form register pair IR. These are very special registers that rarely have any practical use. I is related to interrupt handling, but has very few use cases in MSX environment. Part of R is used as Z80 internal counter that is sometimes used as a source of random seed. These are newer used as register pair although IR may pop up in some documents.

BC', DE', HL' and AF'. These are called "shadow registers" and are just alternative internal storage places for the BC, DE, HL and AF register pairs. You can swap the storage place with commands EXX and EX AF,AF' but this is all you can do.

Flags

Flags are the things that live in F-register. They allow you to act based on comparisons and do ie. conditional decisions. If you are a beginner programmer, pay special attention to working of CF and ZF since those two are the most important flags out there. Almost everything is usually done by using only those two.

As a general rule everything that involves calculation of something affect also flags. 16bit INC and DEC commands are exception to this rule. Generally flags are NOT affected by jump instructions, CPU or interrupt control instructions or load instructions. Load from I or R register is exception to this second rule.

Here is list of flags in F-register:

bit 7, SF, Sign flag. This is copy of the results most significant bit. If the bit is set (= 1 = "M") "Minus" the 2-complement value is negative other ways the result is positive (= 0 = "P") "Plus". Note that this flag can be used only with conditional JP-instruction.

bit 6, ZF, Zero flag. If the result of mathematical operation is zero the bit is set (= 1 = "Z") other ways the bit is reset (= 0 = "NZ") "Not zero"

Bit 5, YF, copy of the results 5th bit.

Bit 4, HF, "Half-carry" from bit 3 to 4. Z80 uses this internally for BCD correction.

Bit 3, XF, copy of the results 3rd bit.

Bit 2, PF/VF, Parity flag. This is copy of the results least significant bit. If the bit is set (= 1 = "PO") the parity is odd otherways the result is even. (= 0 = "PE") On some cases this bit might be used to indicate 2-compliment signed overflow.(VF) Note that this flag can be used only with conditional JP-instruction.

Bit 1, NF, this bit is used by Z80 internally to indicate if last operation was addition or subtraction (needed for BCD correction)

Bit 0, CF, Carry flag = Overflow bit. This bit is the most high bit that did not fit to the result. In addition to large calculations and bit transfers it is mostly used in comparisons to see if the subtraction result was smaller or greater than zero. If the carry flag was set (= 1 = "C") the result did overflow. Other ways the flag is reset (= 0 = "NC") "No carry". Please note that 8bit INC/DEC commands do not update this flag.

In addition to these flags there are two more Z80 internal flags that user rarely needs to know about, but they are mentioned for completeness sake:

IFF1 This is used by Z80 internally to indicate if there is pending interrupt
IFF2 this is backup copy of IFF1 so that the state can be restored.

The Stack

The stack is a memory location specified by the SP register used to store the back addresses of subroutines called by CALL or RST. CALL or RST statement stores to the stack the address behind it, and then the RET statement retrieves that address to know where to resume the execution of the program after the routine execution.

You can also store or retrieve an address into the stack with PUSH and POP but be careful not to disturb the execution of program.
Be careful too not to overwrite data or a routine by over-storing addresses.

Statements for opcodes

There are several types of statement that we can classify into nine groups.

Note: The use of registers or value are not freely usable with all statement. Refer to a documentation to know the constraints.

Data transfers

  • LD <argument1>,(<argument2>)
  • LD (<argument1>),<argument2>
  • LD <argument1>,<argument2>
    • LD is the statement to transfer a value from one place to another.
    • <argument1> is the destination. (It can be a double/single register or a 8/16 bit value)
    • <argument2> is the source. (It can be a double/single register or a 8/16 bit value).
      The parentheses indicate that it is the content of memory pointed by the address of specified register.
  • LDD
    • Transfers an 8 bit value from RAM to RAM with decrementing of the address
  • LDDR
    • Transfers a series of 8bit values by auto-decrementing of the address
  • LDI
    • Transfers an 8 bit value from RAM to RAM with incrementing of the address
  • LDIR
    • Transfers a series of 8bit values by auto-incrementing of the address

Register Exchanges

  • EX <operand1>,<operand2>
  • EX (<operand1>),<operand2>
    • Exchanges the values of the two operands.
  • EXX
    • Exchanges the values of all double registers with the auxiliary registers.

Arithmetic operations

  • ADC <operand>
    • Add the Accumulator with operand and the bit Carry. (A = A + <operand> + Carry)
  • ADD <operand>
    • Add the Accumulator with operand. (A = A + <operand>)
  • DAA
    • Adjusts each quartet of the Accumulator to decimal.
  • DEC <operand>
    • Decrement the contents of register. (<operand> = <operand> - 1)
      If the operand is a double register, the operation does not modify the flags (register F).
  • DEC (<operand>)
    • Decrement the contents of memory specified by the register.
  • INC <operand>
    • Increment the contents of register. (<operand> = <operand> + 1)
      If the operand is a double register, the operation does not modify the flags (register F).
  • INC (<operand>)
    • Increment the contents of memory specified by the register.
  • NEG
    • Subtract zero with the contents of the Accumulator then place the result in the Accumulator. (A = -A)
  • SBC
    • Subtract the Accumulator with operand and the bit Carry. (A = A - <operand> - Carry
  • SUB <operand>
    • Subtract the Accumulator with operand. (A = A - <operand>)
  • SUB (<operand>)
    • Subtract the contents of memory specified by the register.

Logical operations

  • AND <operand>
    • Logical operation: A and <operand>.
  • AND (<operand>)
    • Logical operation: A and (<operand>).
  • CPL
    • Reverses all bits of A
  • OR <operand>
    • Logical operation: A or <operand>.
  • OR (<operand>)
    • Logical operation: A or (<operand>).
  • XOR <operand>
    • Logical operation: A xor <operand>.
  • XOR (<operand>)
    • Logical operation: A xor (<operand>).

Handling bit(s)

  • CCF
    • Reverses the status of the Carry bit (Register F).
  • RES <bit number>,<operand>
  • RES <bit number>,(<operand>)
    • Reset a bit of the operand. The parentheses indicate that it is the content.
  • RL <operand>
    • Carry and operand bits are shifted to left with rotation.
  • RLA
    • Carry and Accumulator bits are shifted to left with rotation.
  • RR <operand>
    • Carry and operand bits are shifted to right with rotation.
  • RRA
    • Carry and Accumulator bits are shifted to right with rotation.
  • RLC <operand>
    • Operand bits are shifted to left with rotation on itself. Carry takes the status of most significant bit from the operand.
  • RLCA
    • Accumulator bits are shifted to left with rotation on itself. Carry takes the status of most significant bit from the Accumulator.
  • RRC <operand>
    • Operand bits are shifted to right with rotation on itself. Carry takes the status of least significant bit from the operand.
  • RRCA
    • Accumulator bits are shifted to right with rotation on itself. Carry takes the status of least significant bit from the Accumulator.
  • SCF
    • Sets the Carry bit (Register F).
  • SET <bit number>,<operand>
  • SET <bit number>,(<operand>)
    • Sets a bit of the operand. The parentheses indicate that it is the content.
  • SLA <operand>
    • Operand bits are shifted to left. Least significant bit is reseted. Carry takes the status of least significant bit from the operand.
  • RLD
    • Shifts to the left of 4 bits with rotation of the operand and Accumulator bits, except for the 4 most significant bits of the Accumulator that remain fix.
  • SRA <operand>
    • Operand bits are shifted to right. Most significant bit keep its status. Carry takes the status of least significant bit from the operand.
  • SRL
    • Carry and Accumulator bits are shifted to right. Most significant bit is reseted. Carry takes the status of least significant bit from the operand.
  • RRD
    • Shifts to the right of 4 bits with rotation of the operand and Accumulator bits, except for the 4 most significant bits of the Accumulator that remain fix.

Data comparisons and bit(s) test

  • BIT <bit number>,<operand>
  • BIT <bit number>,(<operand>)
    • Tests a bit of the operand is reset or not. The parentheses indicate that it is the content of memory pointed by the address of specified register.
      (Z flag is set if the bit is reset.)
  • CP <argument>
    • Compare the argument with A. (Z flag is set if same, S is set if the argument is greater than A)
  • CPD
    • Comparison of a byte from memory with A. Then registers B (counter) and HL (pointer) are decremented.
  • CPDR
    • Repetitive comparison of a byte from memory with A. Register B (counter) and HL (pointer) are decremented until B is reseted.
  • CPI
    • Comparison of a byte from memory with A. Then register B (counter) is decremented and HL (pointer) is incremented.
  • CPIR
    • Repetitive comparison of a byte from memory with A. Register B (counter) is decremented and HL (pointer) is incremented until B is reseted.

Jumps, stack, subroutines

  • CALL <address>
    • Calls a routine to the specified address.
  • CALL <condition>,<address>
    • Calls a routine with condition
  • DJNZ <distance>
    • Relative jump and repetitive. Used for loop execution. B = Number of loop.
  • JP <address>
    • Jump to the specified address.
  • JP <condition>,<address>
    • Jump to the address with condition.
  • JP (<register>)
    • Jump to the address in register.
  • JR <distance>
    • Relative jump. Can jump to address -126 to +129 bytes further than current address.
  • JR <condition>,<distance>
    • Relative jump with condition.
  • POP <register>
    • Stores the contain of register to the stack.
  • PUSH <register>
    • Remove the address from the top of the stack and place it in the specified register.
  • RET
    • Resume execution after the last CALL executed.
  • RST <address on 8 bit>
    • Same as CALL but smaller and faster. <address on 8 bit> can be 0, 8, 10, 18, 20, 28, 30, 38.

Data Input/Output

Avoid using his statements on MSX. Only some ports are standard and you have to pay attention to timings. Use the BIOS corresponding to the hardware to use as much as possible through the jump table.

  • IN A,(<port>)
    • Read the specified port.
  • IND
    • Reading of the specified port by C and storage of the value into memory. Register B (counter) and HL (pointer) are decremented.
  • INDR
    • Repetitive reading of the specified port by C with storage of the the data into memory. Register B (counter) and HL (pointer) are decremented until B is reseted.
  • INI
    • Reading of the specified port by C and storage of the value into memory. Register B (counter) decremented and HL (pointer) is incremented.
  • INIR
    • Repetitive reading of the specified port by C with storage of the the data into memory. Register B (counter) decremented and HL (pointer) is incremented until B is reseted.
  • OUT (<port>),A
    • Send a byte to the specified port.
  • OUTD
    • Send a byte from memory to the specified port by C. Then register B (counter) and HL (pointer) are decremented.
  • OTDR
    • Send a series of bytes to the specified port by C. Register B (counter) and HL (pointer) are decremented until B is reseted.
  • OUTI
    • Send a byte from memory to the specified port by C. Then register B (counter) is decremented and HL (pointer) is incremented.
  • OTIR
    • Send a series of bytes to the specified port by C. Register B (counter) is decremented and HL (pointer) is incremented until B is reseted.

Interruptions and waiting times

The CPU receives interrupt signals from other devices. The statements below allow you to manage them. For example the VDP sends a signal at the end of each screen display (50 or 60 times per second). At each interruption the CPU executes a RST 38.

  • DI
    • Disables interrupts. (Interrupts are disabled immediately after DI execution.)
  • EI
    • Enables interrupts. (Interrupts are re-established after the next statement is executed.)
  • HALT
    • Waits up to next interrupt.
  • IM0
    • Interrupt mode 0.
  • IM1
    • Interrupt mode 1. Mode used by MSXs.
  • IM2
    • Interrupt mode 2.
  • RETI
    • Resume execution after an interrupt routine in mode 2.
  • RETN
    • Resume execution after a non maskable interrupt routine. (not used on MSX)
  • NOP
    • Wait 4 T states.

Labels

Labels is a symbolic name (symbol) for a routine or value. A label defines constant address or value. Set a label consists to place a name of your choice at the beginning of a line, followed by a colon. The colon is optional in many assemblers.

The symbol’s value will be the address of the label’s position when assembled, unless followed by an EQU statement, in which case the value will be the one specified as the EQU’s argument.

The ORG statement specifies the start address of the code follows it, and the address of any position in the code is derived from this.

Notes:

  • Many assemblers are case sensitive for labels.
  • Assemblers for old machine support short labels only to preserve the memory. This is the case for GEN80 by default but you can add the statement *S 14 at the beginning of your program to support labels up to 14 characters. Of course you can replace 14 with another value when necessary.
  • A error occurs with a few assemblers when a colon is used with the EQU statement.
  • A error occurs with a few assemblers when a label following by colon is used with a statement on the same line.

Program format

An assembler program is a plain text file that must be assembled to obtain an executable file.

We can only put one label followed by colon and one statement per line.
A label followed by colon and a statement must be separated by at least one space or tab.
Statement and its parameters must be separated by a space or tab.

The remarks should be placed at the end of the line behind a semicolon separated by at least one space or tab from the preceding statement or label.

Some assemblers do not support empty lines nor or statements without spaces or tabs in front.
Some assemblers do not support empty lines nor or statements without space or tab in front.

Values

Usually values can be specified in binary, octal, decimal, hexadecimal or with a character as follows.

1010000b = %1010000 = 120o = o120 = 80 = 50h = $50 = 'P' = "P"

If you use the notation with the H for the hexadecimal, it is necessary to put a digit at front. So if the value starts with a letter (from A to F), put a zero in front otherwise it will be taken for a label.

11111111b = %11111111 = 377o = o377 = 255 = $FF = 0FFh

Some assemblers support value format like in language C.

0b11111111 = 0o377 = 255 = 0xFF

Main expressions

Expressions that we can use are +, -, *, / for addition, subtraction, multiplication and division. Logical expressions are &, |, ^ for And, Or and Xor operations.

We can not use expressions for registers.

Data

Following statements can be used to insert data.

defb or db for entering 8 bit values or characters.
defw or dw for entering 16 bit values.
defs or ds to reserve an area. The first value is the length. The area will be filled with zeros or 255 by default.

Note: Each value must be separated by a comma. The characters must be a string in quotes or apostrophe.

That's all?

To begin, yes. Once you have assimilated the bases, you have to find a library of routines to complete the gaps of BIOS.
Assemblers also have statements for including external routines or making macros but you will be able to discover them for yourself once you understand the basics of this language.

Now, the main thing for you is to find a suitable assembler for your needs. I advise you a cross assembler. That is an assembler able to assemble machine code for Z80 with a modern PC. This will simplify the task.

Z80 Microprocessors User Manual by Zilog and the wiki will be a very valuable help. Good luke!

Program Samples

Here are some examples of programs to display the message "Hello world!".

; Print "Hello world!" under MSX-DOS using BDOS function.
;
; Save the assembled file with the name HELLO.COM then
; input HELLO under MSX-DOS

BDOS:	equ	0005h	; Set the address 0005h into label BDOS.
			; We can call several routines under MSX-DOS at address 0005h.

	org	100h	; Compilation start address.
			; MSX-DOS commands start always at address 0100h.

	ld	de,Hello_TXT	; Load the address of the label "Hello_TXT" into DE.
	ld	c,9		; C must contain function number of the MSX-DOS.
	call	BDOS		; Call the function 9 (Print function).
	ret			; Back to the MSX-DOS environment.

Hello_TXT:			; Set the current address into label Hello_TXT. (text pointer)

	db "Hello world!$"	; The character $ (24h) indicates the end of text for function 9.
; Print "Hello world!" under MSX-BASIC using BIOS function.
;
; Save the assembled file with the name HELLO.BIN then
; Load from MSX-BASIC with: BLOAD "HELLO.BIN",R

CHPUT:	equ	000A2h		; Set the address of character output routine of main Rom BIOS
				; Main Rom is already selected (0000h ~ 7FFFh) under MSX-Basic

; --> BLOAD header, before the ORG so that the header isn’t counted

	db	 0FEh		; Binary file ID
	dw	 Begin		; begin address
	dw	 End - 1	; end address
	dw	 Execute	; program execution address (for ,R option)

	org	0C000h	; Some assemblers do not support anything other than the EQU statement above.
			; In this cas, move ORG before the header and add -7 behind. 
Begin:

; Program code entry point
Execute:
	ld	hl,Hello_TXT	; Load the address from the label Hello_TXT into HL.
	call	Print		; Call the routine Print below.
	ret			; Back to MSX-BASIC environment.
	
Print:
	ld	a,(hl)		; Load the byte from memory at address indicated by HL into A.
	and	a		; Same as CP 0 but faster.
	ret	z		; Back behind the call print if A = 0
	call	CHPUT		; Call the routine to display a character.
	inc	hl		; Increment the HL value.
	jr	Print		; Relative jump to the address in the label Print.

Hello_TXT:			; Set the current address into label Hello_TXT. (text pointer)
	db "Hello world!",0	; Zero indicates the end of text.

End:
; Print "Hello world!" from cartridge environment.
;
; Save the assembled file with the name HELLO.COM then
; input HELLO under MSX-DOS

CHPUT:		equ 0A2h	; Set the address of character output routine of main Rom BIOS
				; Main Rom is already selected (0000h ~ 3FFFh/7FFFh) when a
				; Rom is being executed.
RomSize:	equ 4000h	; For 16kB Rom size.

; Compilation address
	org 4000h	; 8000h can be also used here if Rom size is 16kB or less.

; ROM header (Put 0000h as address when unused)
	db "AB"		; ID for auto-executable Rom at MSX start
	dw Execute	; Main program execution address.
	dw 0		; Execution address of a program whose purpose is to add
			; instructions to the MSX-Basic using the CALL statement.
	dw 0		; Execution address of a program used to control a device
			; built into the cartridge.
	dw 0		; Basic program pointer contained in ROM.
	dw 0,0,0

; Program code entry point
Execute:
	ld	hl,Hello_TXT	; Load the address from the label Hello_TXT into HL.
	call	Print		; Call the routine Print below.

; Halt program execution. Change to "ret" to return to MSX-BASIC.

Finished:
	jr	Finished	; Jump to itself endlessly.

Print:
	ld	a,(hl)		; Load the byte from memory at address indicated by HL to A.
	and	a		; Same as CP 0 but faster.
	ret	z		; Back behind the call print if A = 0
	call	CHPUT		; Call the routine to display a character.
	inc	hl		; Increment the HL value.
	jr	Print		; Relative jump to the address in the label Print.


; Message data
Hello_TXT:			; Set the current address into label Hello_TXT. (text pointer)
	db "Hello world!",0	; Zero indicates the end of text.
End:
; Padding with 255 to make the file of 16K size (can be 4K, 8K, 16k, etc) but
; some MSX emulators or Rom loaders can not load 4K/8K Roms.
; (Alternatively, include macros.asm and use ALIGN 4000H)

	ds 4000h+RomSize-End,255	; 8000h+RomSize-End if org 8000h