INDEX

INTRODUCTION

Getting Started

REGISTERS

Shadow Registers

BASIC FLAGS Y JUMPS

Rules

Cases and Examples

Conditions

BASIC INSTRUCTIONS

LD

INC

DEC

ADD

SUB

LOGICAL INSTRUCCIONES

AND

OR

XOR

VARIABLES

Labels

DEFB / db

DEFW / dw

DEFS / ds

THE STACK

PUSH

POP

BIT INSTRUCTIONS

BIT

SET

RES

ADDRESSES OF INTEREST

Pixels (Screen Display)

Attributes (Screen Display)

Keyboard, Borders and Sound

Code and Stack

ADVANCED FLAGS

(Register) F

BIT HANDLING

Shift Bit Instructions

Rotate Bit Instructions

COLORS

ADVANCED INSTRUCTIONS

CPI

LDI

LDIR

LDD

LDDR

SBC

ADC

NEGATIVE NUMBERS - 2's Complement

KEYBOARD INPUT

PRACTICE EXERCISES

EXAMPLES AND OTHER ASSETS

Multiply and Divide

Power of 2

Power of 2^(-1)

Random Numbers

Header Example

Function Structure

Loops Examples

Pause function

Draw Screen Borders

XYC Square

RESOURCES TO KEEP ON LEARNING



INTRODUCTION

This guide was originally developed as a learning reference for the Computer Architecture

and Organization course; however, the guide has evolved enough to serve as an

introduction to the most basic concepts of z80 assembly.

Throughout the guide we will deal with the basic elements of the operation of the z80 Zilog

chip that we are going to use (battery, memory, color, etc.); as well as the most basic

instructions and reference codes on which to base ourselves.

The objective of this guide is to serve as a base and reference in case of doubts throughout the development (as a beginner) of programs in this system.

If you want to go deeper, a section with links of interest will be added at the end of the

document.

Getting Started

Installing the z80 emulator/compiler (if you don't have one):

        

  1. Install Visual Studio Code
  2. Install VS Code extension: DeZog (Debugger)
  3. Install VS Code extension: Z80 Macro-Assembler (Intellisense)

DeZog documentation(in case you have difficulties)



REGISTERS

A

F

ACCUMULATOR

FLAGS

B

C

(DJNZ)

-

D

E

-

-

H

L

(DIR)

(DIR)

SP

STACK POINTER

PC

PROGRAM COUNTER

IX

DIR

IY

DIR

I

INTERRUPT VECTORS (DI)

R

MEMORY REFRESH

THEY CAN BE USED FOR THEIR SPECIFIC USE AND TO SAVE VALUES

ACCUMULATOR REGISTER TO OPERATE AND COMPARE

REGISTERS FOR INDIRECT ADDRESSING WITH OFFSET

WE WON’T BE USING THEM

THERE ARE OTHER REGISTERS FOR EACH DOUBLE REGISTER, BUT WE ARE NOT GOING TO USE THEM

Types of values in memory addresses (and when we will use them):

It should be noted that all these data types are valid for any operation, we simply

standardize what we will do with each one to avoid confusion.

Registers sizes:

Shadow Registers

Marked by a '

A' F'        B' C'        D' E'        H' L'

They function as a second form of the register and serve to store registers whose value will be lost, without using the stack.

The EXX instruction changes BC, DE, and HL to BC' DE' HL'; although we will not use it.



BASIC FLAGS Y JUMPS

The flags are bits that can be 0 or 1 and determine that a previous operation or comparison has given a particular result. On a practical level, we will use these comparisons as if they were aif().

Rules

arg1 > a        Carry set

arg1 <= a        Carry reset

arg1 == a        Zero set

arg1 != a        Zero reset

Cases and Examples

Comparisons and similar operations are always done on the accumulator register (A). A flag is set when it becomes 0 and reset when it becomes 1. If an operation does not cause the flag to become set then the flag will be reset.

Flag Zero

Flag Carry

Accumulator

inc 255        

Set

Set

1

dec a

Set

Set

1

sub a

Set

Reset

x

cp a

Set

Reset

x

Dec and Inc don't trigger the carry flag even if we carry something.

Conditions

jr

jr z,addr1

jump if zero flag set

jr nz,addr1

jump if zero flag reset

jr c,addr1

jump if carry flag set

jr nc,addr1

jump if carry flag reset

ret

ret (n)flag,addr1

ret if flag (re)set

jp

jp (n)flag,addr1

jp if flag (re)set

call

call (n)flag,addr1

call if flag (re)set



BASIC INSTRUCTIONS

LD

        ld A, 4        ;loads a number in A
        ld B, A        ;makes B point to the value stored in A
        ld HL,addr1        ;loads value of addr1’s address in HL
        ld (IX), A        ;loads A into IX’s address

INC

        inc C                ;does C = C + 1

DEC

        dec C                ;does C = C - 1

ADD

        ld A, 4        ; loads 4 in A
        ld
B, 5        ; loads 5 in B
        add
A, B        ; A = A + B
        add
B                ; A = A + B

Esto también funciona con registros dobles

SUB

        ld A, 4        ; loads 4 in A
        ld
B, 5        ; loads 5 in B
        sub B                ; A = A - B



LOGICAL INSTRUCCIONES

The logical operations are done bit by bit on the 8 bits of register A (Bit 0 of A with bit 0 of the operand, Bit 1 of A with Bit 1 of the operand, Bit 2 of A with Bit 2 of the operand... up to bit 7).

AND

Performs the logical operation shown below on register A and the simple register entered:

AND

A

B

Resultado

0

0

0

0

1

0

1

0

0

1

1

1

        and C                ;does A = A AND C

OR

Performs the logical operation shown below on register A and the simple register entered:

OR

A

B

Resultado

0

0

0

0

1

1

1

0

1

1

1

1

        or C                ;does A = A OR C

XOR

Performs the logical operation shown below on register A and the simple register entered:

XOR

A

B

Resultado

0

0

0

0

1

1

1

0

1

1

1

0

        xor C                ;hace A = A XOR C



VARIABLES

Labels

Name that is assigned to the memory address that contains the instruction after the label, except if the EQU directive exists. For example:

label_example: EQU 5                ;makes label_example value 5

In any other case:

label_example:

        ld C, 8

label_example:

        jp lable_example

DEFB / db

Define byte > 1 byte (8 bits) can be defined at an address indicated by a label. The memory address of all those entered after the first byte is equal to the address of the first byte plus its position:

        1º Label address + 0

        2º Label address + 1

        3º Label address + 2

        …

array:

        .db 1, 4, 7, 2, 252        ;for some compilers the dot can be omitted

variable:

        .DEFB 0

DEFW / dw

Define word > 2 bytes (16 bits) can be defined at an address indicated by a label. The memory address of all those entered after the first byte is equal to the address of the first byte plus its position:

        1º Label address + 0

        2º Label address + 1

        3º Label address + 2

array:

        .dw 1 2, 7 8, 134 2, 0 252        ;for some compilers the dot can be omitted

array_de_2:

        .DEFW 0 0

DEFS / ds

Define space > A given number of bytes can be defined at an address indicated by a label. The memory address of all those entered after the first byte is equal to the address of the first byte plus its position:

        1º Label address + 0

        2º Label address + 1

        3º Label address + 2

array_de_8_a_0:

        .ds 8, 0                ; this is equivalent to: 0, 0, 0, 0, 0, 0, 0, 0

                                ; for some compilers the dot can be omitted

array_de_3_a_4:

        .DEFS 3, 4                ; this one would be: 4, 4, 4



THE STACK

The Stack Pointer (sp) always points to the last element stored on the stack. The stack grows by decrementing the memory location and values are removed by increasing the memory location.

The stack also stores double registers (16 bits); therefore, it increases or decreases 2 by 2.

We must also know that it is located inside of the RAM and, if we initialize SP in $0, we will have something like what is seen in the following table:

Memory Address

Value

$FFF8

$A38B

   $FFFA

$EAFD

sp  >  $FFFC

04 | 05

$FFFE

02 | 03

$0000

00 | 01

One of the important things to know is that when adding a new element to the stack, the pointer is first moved and then the value is added. Initializing the stack at $0000 makes it so that we never store anything in $0000, but instead to $FFFF and $FFFE and so on. We do this because the address $0000 is inside the ROM not the RAM.

PUSH

In memory, from lowest to highest position, the R_right is saved and then the R_left:

  1. Decrease SP
  2. Insert Left Register
  3. Decrease SP
  4. Insert Right Register

POP

It works the other way around:

  1. Insert Right Registro
  2. Increases SP
  3. Insert Left Register
  4. Increases SP



BIT INSTRUCTIONS

BIT

It checks the state (set or reset) of a specific bit and modifies the Z flag; If the bit is 0, the Z flag is reset to 1 and vice versa.

Receives:

        bit 1, C                 ;C = 0 1 0 0 1 0 1 1

SET

Set state to a specific bit

Receives:

        set 2, C         ;nuevo C = 0 1 0 0 1 1 1 1

RES

Reset state to a specific bit

Receives:

        res 6, C        ;nuevo C = 0 0 0 0 1 0 1 1



ADDRESSES OF INTEREST

Pixels (Screen Display)

Address → $4000

Size = 192 rows x 32 columns = 6144 = $1800

Bitmaps (spriting)

Attributes (Screen Display)

Address → $5800

Size = 24 rows x 32 columns = 756 = $300

Keyboard, Borders and Sound

It's an isolated I/O port, on a device called a ULA, that handles the keyboard, border, and sound:

Address → $fe

Code and Stack

We initialize the code and the stack at these positions, but it is remarkable that this is not necessary.

Code → (org $8000) → $8000

Stack Pointer → (sp 0) → $00



ADVANCED FLAGS

(Register) F

It checks the state (set or reset) of a specific bit to know the state of the flag it represents. These are:

F

Bit 7

Bit 6

Bit 5

Bit 4

Bit 3

Bit 2

Bit 1

Bit 0

S

Z

F5

H

F3

P/V

N

C

Where:

C (Carry) > > > Set if the result of an operation does not fit in a register.

Z (Zero) > > > Set if the result of an operation is 0

N (Subtract) > > > Set if the operation was a subtraction

S (Sign) > > > Set if the value of the complement to 2 in negative

P/V         > > > If odd parity in logical operations (odd number of ones in a byte) or 2's

Complement Overflow in math operations (which means 2's Complement does not fit

in 1 register)

To call (only functional with jp and call) the flags that we have not seen previously we use:

For Sign        > > >        s

For Subtract         > > >        n

For Parity         > > >        pe        > > >        Set if Parity Even

> > >        po        > > >        Set if Parity Odd



BIT HANDLING

Shift Bit Instructions

For % 0 0 0 0 1 1 0 1, if we rotate to the right:

Option 1:

 % 1 0 0 0 0 1 1 0 > The original bit is moved

Option 2:

% 0 0 0 0 0 1 1 0 > The original bit becomes 0

If we rotate to the left:

Option 1:

 % 0 0 0 1 1 0 1 1 > The original bit is kept

Option 2:

% 0 0 0 1 1 0 1 0 > The original bit becomes 0

SLA

        sla 8_bit_register

Rotate left and:

Bit 7 (the one that disappears) goes to the carry

flag Bit 0 (the new one) reset (it is set to 0)

SRA

        sra 8_bit_register

Rotate right and:

Bit 0 (the one that disappears) goes to the carry flag Bit

7 (the new one) reset (set to original value)

SRL

        srl 8_bit_register

Rotate the register to the right and:

Bit 0 (the one that disappears) goes to the carry flag

         Bit 7 (the new one) reset (set to 0)

Rotate Bit Instructions

For % 0 0 0 0 1 1 0 1, if we rotate to the right:

% 1 0 0 0 0 1 1 0

If we rotate to the left:

% 0 0 0 1 1 0 1 0

Imagine the byte as a continuous strip (i.e. glued to a cylinder).

RL

        rl 8_bit_register

Rotate register left and:

Bit 7 (the one that disappears) is set in the carry flag

Bit 7 (the new one) is set to 0

RLA

RL for register A.

RLC

        rlc 8_bit_register

Rotate register left and:

Bit 7 goes to bit 0

Bit 0 goes to carry flag

For % 1 1 1 1 0 0 0 0, RLC would result in:

% 1 1 1 0 0 0 0 1 and the carry would be 1

RLCA

RLC for register A.

RR

        rr 8_bit_register

Rotate register right and:

Bit 7 (the new one) becomes the value that the carry flag had

Bit 0 (the one that disappears) is put into the carry

RRA

RR for register A.

RRC

        rrc 8_bit_register

Rotate record right and

El bit 0 goes to bit 7

El bit 0 goes to carry flag

RRCA

RRC for register A.



COLORS

The colors in z80 are determined by the following graph, where the combination of 8 bits

generates the number that we must assign to the corresponding coordinates on the

screen to put a square of the color in that position.

Where:

F (Flash) determines the mode        

>>> Set Paper and Ink colors flicker

        >>> Reset shows the Ink color over the Paper color

B (Brightness) determines the brightness of the color

>>> Slear colors with set

        >>> Dark colors with reset

P2 to P0 (Paper) determines the color of the

background (binary)

I2 to I0 (Ink) determines the main color (binary)

An example would be the color light red:

        

F

B

PAPER

INK

0

1

0

1

0

0

0

0

Another example would be flashing between light yellow and green:

F

B

PAPER

INK

1

1

1

0

0

1

1

0


ADVANCED INSTRUCTIONS

CPI

Does the instructions shown below:

        cp (HL)
        dec
HL
        dec
BC

LDI

Does the instructions shown below:

        ld (DE), (HL)
        inc
DE
        inc
HL
        dec
BC

LDIR

Does LDI until BC is 0.

LDD

Does the instructions shown below:

        ld (DE), (HL)
        dec
 DE
        dec
HL
        dec
BC

LDDR

Does LDD until BC is 0.

SBC

Stores in register A (or HL, for 16 bits) the result of subtracting the sum of the second operand and the value of the carry flag from register A (or HL, for 16 bits).

        sbc A, 8_bit_register

        sbc HL, 16_bit_register

ADC

Saves in register A (or HL, for 16 bits) the result of the addition of the second operand, register A (or HL, for 16 bits) and the value of the carry flag.

        sbc A, 8_bit_register

        sbc HL, 16_bit_register



NEGATIVE NUMBERS - 2's Complement

In the representation of negative numbers in binary we use 2's complement. To do this, we take a number in binary and exchange the 1s for 0s and the 0s for 1s. In the following example we convert 5 to negative (251):

% 0000 0101 >> We swap >> % 1111 1010 +1

However, in z80 we don't stop there, the processor adds 256 (%1 0000 0000) to the number in question to store it as a negative.

You can load negatives directly with LD:

        

        ld A, -10        ;however, it is more advisable to make negatives

There are 2 ways to make negatives in z80:

Two's complement in z80 is done:

        ld A, 5        ;in A there is now a 5, that is %00000101

To make the 5 negative, we reverse the 1s and 0s:

        xor A                ;we use the exclusive or to convert it to %11111010        

        ;another way:
        cpl                 
;we swap 1 and 0 of register A

Then we add 1:

        inc A                ;now we have %11111011



KEYBOARD INPUT

Keyboard in Z80 works by checking bits from the memory addresses in the following table.

Bit

Puerto

0

1

2

3

4

$FEFE

Shift

Z

X

C

V

$FDFE

A

S

D

F

G

$FBFE

Q

W

E

R

T

$F7FE

1

2

3

4

5

$EFFE

0

9

8

7

6

$DFFE

P

O

I

U

Y

$BFFE

Enter

L

K

J

H

$7FFE

Space

Sym

M

N

B

key: db. 0   ; Variable where to store the last key pressed

check_keys:
 push
BC             ; Store the original value of the register BC on the stack
  push DE             ; Store the original value of the register DE on the stack
 ld
HL, key ; We store the address of the variable where we store the last key pressed pulsada

letter_T:         ; Check a letter
 ld
BC, $FBFE    ; We load in BC the port we are going to check
  in A,(C)        ; We read the port in the accumulator
 bit
4, A        ; Check bit 4 (here it is 'T')
 jr
z, pressed_T ; We check flag z to check if it has been pressed

 pop
DE          ; We restore the original value of DE
  pop BC          ; We restore the original value of BC
 ret


pressed_T:
 ld
(HL), 3      ; We load 3 (Blue) into the variable ‘key’

 pop
DE          ; We restore the original value of DE
  pop BC          ; We restore the original value of BC
 ret



PRACTICE EXERCISES

1) Sum of the elements of an array of unknown size with end delimiter

(with value 255) that is returned by register A.

This sum cannot be greater than 201; if it is, return the nearest smaller sum.

Entry > in HL the variable

Departure > in To the sum

2) Check if the first and last elements of an array of unknown size

with an end delimiter (with value 255) are equal.

If they are, return through register A a number whose bit 4 is 1 and, if not, bit 4 is 0.

3) Combine 2 arrays of size 4 into one of 8 so that the elements of both

alternate.

Which means that the result will look like:

Arr1 : 1a, 1b, 1c, 1d

Arr2 : 2a, 2b, 2c, 2d

ArrR : 1a, 2a, 1b, 2b, 1c, 2c, 1d, 2d

4) Eliminate the elements of value 3 from the following array of size 12:

Example array: [0, 2, 0, 3, 1, 0, 1, 3, 0, 3, 2, 1]

5) Paint the following array on the screen in an inverted way (it has the exact

necessary number of values):

label_display:

DEFS 32, $02, 32, $02, 32, $02, 32, $02, 32, $02, 32, $02, 32, $02, 32, $02, 32, $02, 32, $02, 32, $02, 32, $02
        DEFS 32, $01, 32, $01, 32, $01, 32, $01, 32, $01, 32, $01, 32, $01
        DEFS 32, $04, 32, $04, 32, $04
        DEFS 32, $03, 32, $03

7) Given that you know the number of elements in the stack, invert the stack

8) Divide a number in Hundreds, Tens and Units

An example:

number: db 231

divided: db 2, 3, 1

9) Tic-Tac-Toe

Game in which we manage turns, 2 players (red and blue), board and a game over.



EXAMPLES AND OTHER ASSETS

Multiply and Divide

Knowing that 3*3 = 3+3+3 and that 6/3 = nº(3+3) = 2

Multiply

        ld C, 2
        ld
B, 3
multiplicar:
        add A, C                ;A es el resultado final y cada iteración hacemos A = A + C
        djnz multiplicar        ;reduce B una vez y comprueba si es 0. Si no lo es, vuelve

Divide 8 bits / 8 bits (divisible by each other)

        ld C, 0                ;C es nuestro contador
dividir:
        inc C                        ;aumentamos
        sub
 B                        ;A = A - B
        jr nz, dividir         ;si no es cero, volvemos
        ld A, C                ;guardar el resultado en A

Division 8 bits / 8 bits (with remainder)

; D divided by E
; Result in D, remainder in A
   xor
A     ;A = 0
   ld
B, 8   ;number of bits in E
main_div_loop:

    sla D
   rla
   cp
E
   
jr c, remainder 
   
sub E
   inc
D
remainder:  djnz main_div_loop

Power of 2

        ld B, 2                ;a*2^(b-1)
power:
        add
 A, A                ;a*2
        djnz
power        

Power of 2^(-1)

        ld B, 2                ;a/2^(b-1)
pow_div:
        slr
A                        ;a/2
        djnz
pow_div

Random Numbers

Stored in A, generated from register R

random_number:
   ld
A, R
   and
%00001111           ; Between 0 and 15
   inc
A                    ; 1 and 16
   ret

Header Example

output "filename.bin"        ;output file generated (for Sublime Text)
       DEVICE
ZXSPECTRUM48  ;Spectrum device used VSCode
       
org $8000            ;Program starts at $8000 = 32768

beginning:
         di           ;Disable interruptions
               ld
sp,0      ;We start the stack pointer at &0000
               
jp mainLoop  ;We jump to the main loop
; --------------------
; Define variables
; --------------------


mainLoop:

Function Structure

funcName:

;We store in the stack the values of all the register we are going to use

        push AF
        push
BC
        push
DE
        push
HL
        push
IX
        push
IY

;function code


;We restore the values of all the register we stored in the stack
        pop
IY
        pop
IX
        pop
HL
        pop
DE
        pop
BC
        pop
AF

;We return the function to where it was called (the next instruction after call)
        
ret

Loops Examples

        ld B,10

loop1:

        djnz loop1        ;B == 0?

;----------------------------------

        ld C,10

loop2:

        inc C

        ld A,C

        cp 0 

        jr C, loop2 ;C <= 0?

;----------------------------------

        ld D,0

loop3:

        dec D

        ld A,D

        cp 10

        jr nz, loop3        ;D != 10?

        ld E,0

loop4:

        inc E

        cp 12

        jr z, loopend4        ;E == 12?

        jr loop4

loopend4:

Pause function

;Code by professor Daniel León - UFV

;Function call
 call
pause

;function
pause:
 
   push
AF            
    push DE

   ld
B,1 ; pause
paus0:
   ld
DE,10000        ; We start DE at 10000 to loop until it is 0
paus1:  
   dec
 DE              ; Decrement DE. For the 1st loop, DE will be 9999
   ld
A,D              ; Checks if DE is 0000, D has to be 0...
   or
E                ; ... and so does E
   jr nz,
paus1
    djnz paus0

   pop
DE
    pop AF

   ret

Draw Screen Borders

;Call function
 call
drawBorders

;Function

drawBorders:
 push
AF


 ; Colors de 0 a 7:
 ;
 ; 0 = black  2 = red     4 = green  6 = yellow
 ; 1 = blue   3 = purple  5 = cyan   7 = white
 ;


  ld A,4 
 out
(0xFE),A


 pop
AF
 ret

XYC Square

Paints a box of color A at the X,Y coordinates (1..32, 1..24). Receive X,Y and color

coordinates with registers B,C and A respectively.

This function determines the address with the formula: Address = $5800+32*Y+X.

;Code by professor Daniel León - UFV

;Call function
  call cuadroXYC

;Function

cuadroXYC:          
   push
AF           
   push
DE
   push
HL

   ld
H,0                ; Y*32
   ld
L,C                ;
   add
HL,HL            ; hl is 2*c
   add
HL,HL            ; hl is 4*c
   add
HL,HL            ; hl is 8*c
   add
HL,HL            ; hl 16*c
   add
HL,HL            ; hl 32*c 
   
   ld
D,0
   ld
E,B                ; b is X -> Loaded into DE

   add
HL,DE            ; HL now is 32*y+X
   ld
DE,$5800
    add HL,DE            ; HL now is $5800 + 32*Y + X

   sla
A
   sla
A
   sla
A

   ld
(HL),A 
   pop
HL             
    pop DE
   pop
AF

   ret



RESOURCES TO KEEP ON LEARNING

z80 Guide by Matt Heffernan - Youtube Playlist

z80 game development by James Malcolm - Website

z80 Heaven - Wiki

z80 Zilog - User Manual

Multiplatform z80 Assembly Programming by ChibiAkumas - Website

Zilog Z80A Technical Information by WorldOfSpectrum - Website 

Sprites in ZX Spectrum - Website

Última actualización: 10 / 2022