ÍNDICE

INTRODUCCIÓN        

Para Empezar        

REGISTROS        

Shadow Registers (Registros fantasma)        

FLAGS Y SALTOS BÁSICOS        

Reglas        

Casos y Ejemplos        

Condiciones        

INSTRUCCIONES BÁSICAS        

LD        

INC        

DEC        

ADD        

SUB        

INSTRUCCIONES LÓGICAS        

AND        

OR        

XOR        

VARIABLES        

Labels        

DEFB / db        

DEFW / dw        

DEFS / ds        

PILA        

PUSH        

POP        

INSTRUCCIONES CON BITS        

BIT        

SET        

RES        

ZONAS DE MEMORIA        

Pixeles (Pantalla)        

Atributos (Pantalla)        

Teclado, Bordes y Sonido        

Inicializar Código y Pila        

FLAGS AVANZADAS        

Registro F        

MANIPULACIÓN DE BITS        

Shift Bit Instructions        

Rotate Bit Instructions        

COLORES        

INSTRUCCIONES AVANZADAS        

CPI        

LDI        

LDIR        

LDD        

LDDR        

SBC        

ADC        

NÚMEROS NEGATIVOS - Complemento a 2        

LEER TECLADO        

EJERCICIOS PARA PRACTICAR        

EJEMPLOS Y AYUDAS        

Multiplicar y Dividir        

Potencias de 2        

Potencias de 2^(-1)        

Generar Aleatorio        

Ejemplo de Cabecera        

Ejemplo de Función        

Ejemplo de Bucles        

Dividir Número en Centenas, Decenas y Unidades        

Quitar Espacios de una Frase        

Función de Pausa        

Pintar Borde de la Pantalla        

Invertir Grupos de 6        

Cuadrado XYC        

Invertir Pila        

PROFUNDIZAR        



INTRODUCCIÓN

Esta guía fue desarrollada originalmente como referencia de aprendizaje para la asignatura de Arquitectura y Organización de Computadores; sin embargo, la guía ha evolucionado lo suficiente para servir de introducción a los conceptos más básicos de z80 assembly.

A lo largo de la guía trataremos con los elementos básicos del funcionamiento del chip z80 Zilog que vamos a utilizar (pila, memoria, color, etc.); así como las instrucciones más básicas y códigos de referencia en los que poder basarnos.

El objetivo de esta guía es servir como base y referencia en caso de dudas a lo largo del desarrollo (como principiante) de programas en este sistema.

En caso de querer profundizar, se añadirá un apartado con links de interés al final del documento.

Para Empezar

Instalación del emulador/compilador de z80 (en caso de no tener uno):

        

  1. Instalar Visual Studio Code
  2. Instalar extensión de VS Code: DeZog (Debugger)
  3. Instalar extensión de Vs Code: Z80 Macro-Assembler (Intellisense)

Documentación de DeZog (en caso de dificultades)



REGISTROS

A

F

ACUMULADOR

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

SE PUEDEN UTILIZAR PARA SU USO ESPECÍFICO Y PARA GUARDAR VALORES

REGISTRO ACUMULADOR PARA OPERAR Y COMPARAR

REGISTROS PARA DIRECCIONAMIENTO INDIRECTO CON DESPLAZAMIENTO

NO USAR

EXISTEN OTROS REGISTROS POR CADA REGISTRO DOBLE, PERO NO VAMOS A UTILIZARLOS

Tipos de valores en direcciones de memoria (y cuando los usaremos):

Cabe destacar que todos estos tipos de datos son válidos para cualquier operación, simplemente estandarizamos lo que haremos con cada una para evitar confusiones.

Tamaño de los registros:

Shadow Registers (Registros fantasma)

Marcados por '

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

Funcionan como una segunda forma del registro y sirven para almacenar registros cuyo valor se perderá, sin usar la pila.

La instrucción EXX cambia BC, DE y HL por BC' DE' HL'; aunque no la utilizaremos.



FLAGS Y SALTOS BÁSICOS

Los flags son bits que pueden ser 0 o 1 y determinan que una operación o comparación previa ha dado un resultado particular. A nivel práctico, utilizaremos estas comparaciones como si fuesen un if().

Reglas

arg1 > a        Carry set

arg1 <= a        Carry reset

arg1 == a        Zero set

arg1 != a        Zero reset

Casos y Ejemplos

Las comparaciones y operaciones semejantes se hacen siempre sobre el registro acumulador (A). Una flag está set cuando se convierte en 0 y reset cuando se convierte en 1. Si una operación no hace que el flag pase a set entonces se hará reset al flag.

                Flag Zero        Flag Carry        Acumulador

inc 255                Set                Set                1

dec a                Set                Set                1

sub a                Set                Reset                x

cp a                Set                Reset                x

Dec e Inc no activan el carry flag incluso si nos llevamos algo.

Condiciones

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



INSTRUCCIONES BÁSICAS

LD

        ld A, 4        ;cargo un número en A
        ld B, A        ;hago que B apunte al valor almacenado en A
        ld HL,addr1        ;cargo el valor de dentro de la dirección de memoria de HL en IX
        ld (IX), A        ;cargo dentro de ix el valor almacenado a A

INC

        inc C                ;hace C = C + 1

DEC

        dec C                ;hace C = C - 1

ADD

        ld A, 4        ; cargo 4 en A
        ld
B, 5        ; cargo 5 en B
        add
A, B        ; A = A + B
        add
B                ; A = A + B

Esto también funciona con registros dobles

SUB

        ld A, 4        ; cargo 4 en A
        ld
B, 5        ; cargo 5 en B
        sub B                ; A = A - B



INSTRUCCIONES LÓGICAS

Las operaciones lógicas se hacen bit a bit sobre los 8 bits del registro A (El bit 0 de A con el bit 0 del operando… así hasta el bit 7).

AND

Realiza la operación lógica mostrada a continuación sobre el registro A y el registro simple introducido:

AND

A

B

Resultado

0

0

0

0

1

0

1

0

0

1

1

1

        and C                ;hace A = A AND C

OR

Realiza la operación lógica mostrada a continuación sobre el registro A y el registro simple introducido:

OR

A

B

Resultado

0

0

0

0

1

1

1

0

1

1

1

1

        or C                ;hace A = A OR C

XOR

Realiza la operación lógica mostrada a continuación sobre el registro A y el registro simple introducido:

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

Nombre que se le asigna a la dirección de memoria que contenga la instrucción a continuación del label, excepto que exista la directiva EQU. Por ejemplo:

label_example: EQU 5                ;hace que label_example valga 5

independientemente de la dirección en la que esté.

label_example:

        ld C, 8

label_example:

        jp lable_example

DEFB / db

Define byte > 1 byte (8 bits) inicializado con el valor introducido en la dirección de memoria indicada por una label. La dirección de memoria de todos los que se introduzcan a partir del primer byte equivale a la dirección del primero más su posición:

        1º Dirección de la label + 0

        2º Dirección de la label + 1

        3º Dirección de la label + 2

        …

array:

        .db 1, 4, 7, 2, 252        ;según del compilador se puede omitir el punto

variable:

        .DEFB 0

DEFW / dw

Define word > 2 bytes (16 bits) inicializados con el valor (o valores) introducido en la dirección de memoria indicada por una label. La dirección de memoria de todos los que se introduzcan a partir del primer byte equivale a la dirección del primero más su posición:

        1º Dirección de la label + 0

        2º Dirección de la label + 1

        3º Dirección de la label + 2

array:

        .dw 1 2, 7 8, 134 2, 0 252        ;misma omisión del punto

array_de_2:

        .DEFW 0 0

DEFS / ds

Define space > Un número determinado de bytes inicializados (todos) con el valor introducido en la dirección de memoria indicada por una label. La dirección de memoria de todos los que se introduzcan a partir del primer byte equivale a la dirección del primero más su posición:

        1º Dirección de la label + 0

        2º Dirección de la label + 1

        3º Dirección de la label + 2

array_de_8_a_0:

        .ds 8, 0                ; esto sería: 0, 0, 0, 0, 0, 0, 0, 0 y posible omisión

array_de_3_a_4:

        .DEFS 3, 4                ; esto sería: 4, 4, 4



PILA

El Stack Pointer (sp) siempre apunta al último elemento almacenado en la pila. La pila crece mediante decrementar la posición de memoria y se eliminan valores mediante aumentar la posición de memoria.

La pila, además, almacena registros dobles (16 bits); por tanto, incrementa o aumenta de 2 en 2.

También debemos saber que se encuentra en la RAM y si inicializamos SP en $0 nos quedará algo como lo visto en la siguiente tabla:

Memoria

Valor

$FFF8

$A38B

   $FFFA

$EAFD

sp  >  $FFFC

04 | 05

$FFFE

02 | 03

$0000

00 | 01

Una de las cosas importantes que debemos saber es que, al añadir un nuevo elemento a la pila, primero se mueve el puntero y luego se añade el valor. Al inicializar la pila en $0000, nunca se guarda nada en $0000, sino en $FFFF y $FFFE y de ahí para abajo; porque la dirección $0000 es ROM no RAM.

PUSH

En memoria, vista de posición menor a mayor, se guarda la Rd y luego la Ri:

  1. Decrementa SP
  2. Inserta Registro izquierdo
  3. Decrementa SP
  4. Inserta Registro derecho

POP

Funciona de manera inversa:

  1. Inserta Registro derecho
  2. Incrementa SP
  3. Inserta Registro izquierdo
  4. Incrementa SP



INSTRUCCIONES CON BITS

BIT

Comprueba el estado (set o reset) de un bit en específico y modifica el flag Z; sii el bit comprobado es 0, el flag Z se pone a 1 y al revés.

Recibe:

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

SET

Pone el estado set a un bit en específico

Recibe:

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

RES

Pone el estado reset a un bit en específico

Recibe:

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



ZONAS DE MEMORIA

Pixeles (Pantalla)

$4000

Tamaño = 192 filas x 32 columnas = 6144 = $1800

Bitmaps para spriting

Atributos (Pantalla)

$5800

Tamaño = 24 filas x 32 columnas = 756 = $300

Teclado, Bordes y Sonido

Es un puerto I/O aislada, en un dispositivo que se llama ULA, que maneja el teclado, el borde y el sonido:

$fe

Inicializar Código y Pila

Inicializamos el código y la pila en estas posiciones, pero es remarcable que no es necesario que sea así.

Código:        (org $8000)         >         $8000

Pila:                (sp 0)                 >        $00



FLAGS AVANZADAS

Registro F

Comprueba el estado (set o reset) de un bit en específico para saber el estado de la flag a la que representa. Estas son:

Registro 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

Donde:

C (Carry)         >>> Set si el resultado de una operación  no cabe en un registro.

Z (Zero)         >>> Set si el resultado de una operación es 0

N (Subtract)        >>> Set si la operación ha sido una resta

S (Sign)         >>> Set si el valor del complemento a 2 en negativo

P/V                 >>> Si paridad impar en operaciones lógicas (número impar de unos en byte) o Overflow de complemento a 2 en operaciones matemáticas (es decir, el complemento a 2 no cabe en 1 registro)

Las llamadas (solo funcionales con jp y call) a los flags que no hemos visto previamente son:

        Para Sign         >>>        s

        Para Subtract        >>>        n

        Para Paridad        >>>         pe         >>>        Set si Parity Even (Paridad Par)

                        >>>        po        >>>        Set si Parity Odd (Paridad Impar)



MANIPULACIÓN DE BITS

Shift Bit Instructions

Para % 0 0 0 0 1 1 0 1, si rotamos hacia la derecha:

Opción 1:

 % 1 0 0 0 0 1 1 0 > El bit original se mueve

Opción 2:

% 0 0 0 0 0 1 1 0 > El bit original se vuelve 0

Si rotamos a la izquierda:

Opción 1:

 % 0 0 0 1 1 0 1 1 > El bit original se mantiene

Opción 2:

% 0 0 0 1 1 0 1 0 > El bit original se vuelve 0

SLA

        sla 8_bit_register

Rota hacia la izquierda y:

         El bit 7 (el que desaparece) va al carry flag

El bit 0 (el nuevo) reset (se pone a 0)

SRA

        sra 8_bit_register

Rota hacia la derecha y:

El bit 0 (el que desaparece) va al carry flag

El bit 7 (el nuevo) reset (se pone a valor original)

SRL

        srl 8_bit_register

Rota el registro a la derecha y:

         El bit 0 (el que desaparece) va al carry flag

         El bit 7 (el nuevo) reset (se pone a 0)

Rotate Bit Instructions

Para % 0 0 0 0 1 1 0 1, si rotamos hacia la derecha:

% 1 0 0 0 0 1 1 0

Si rotamos a la izquierda:

% 0 0 0 1 1 0 1 0

Imaginar el byte como una tira continua (p.ej. pegada a un cilindro).

RL

        rl 8_bit_register

Rota registro a la izquierda y:

El bit 7 (el que desaparece) se pone en el carry flag

         El bit 7 (el nuevo) se pone a 0

RLA

RL del registro A

RLC

        rlc 8_bit_register

Rota registro a la izquierda y:

El bit 7 va al bit 0

El bit 0 va al carry flag

Para % 1 1 1 1 0 0 0 0, RLC resultaría en:

% 1 1 1 0 0 0 0 1 y el carry seria 1

RLCA

RLC del registro A

RR

        rr 8_bit_register

Rota registro a la derecha y:

El bit 7 (el nuevo) se convierte en el valor que tenía el carry flag

El bit 0 (el que desaparece) se pone en el carry

RRA

RR del registro A

RRC

        rrc 8_bit_register

Rota registro a la derecha y

El bit 0 va al bit 7

El bit 0 va al carry flag

RRCA

RRC del registro A



COLORES

Los colores en z80 están determinados por el siguiente gráfico, donde la combinación de 8 bits genera el número que deberemos asignar a las correspondientes coordenadas en la pantalla para poner un cuadrado del color en esa posición.

Donde:

F (Flash) determina el modo        >>> Set Parpadean los colores de Paper e Ink

                                >>> Reset se muestra el Ink sobre el Paper

B (Brightness) determina el brillo del color         >>> Set para claros

                                                >>> Reset para oscuros

P2 a P0 (Paper) determina el color del fondo (en binario)

I2 to I0 (Ink) determina el color principal (en binario)

Un ejemplo sería el color rojo claro:

        

F

B

PAPER

INK

0

1

0

1

0

0

0

0

Otro ejemplo sería parpadear entre amarillo y verde claros:

F

B

PAPER

INK

1

1

1

0

0

1

1

0



INSTRUCCIONES AVANZADAS

CPI

Realiza el código a continuación:

        cp (HL)
        dec
HL
        dec
BC

LDI

Realiza el código a continuación:

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

LDIR

Realiza LDI hasta que BC sea 0

LDD

Realiza el código a continuación:

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

LDDR

Realiza LDD hasta que BC sea 0

SBC

Guarda en el registro A (o HL, para 16 bits) el resultado de restar la suma del segundo operando y el valor del carry flag al registro A (o HL, para 16 bits).

        sbc A, 8_bit_register

        sbc HL, 16_bit_register

ADC

Guarda en el registro A (o HL, para 16 bits) el resultado de la suma del segundo operando, el registro A (o HL, para 16 bits) y el valor del carry flag.

        sbc A, 8_bit_register

        sbc HL, 16_bit_register



NÚMEROS NEGATIVOS - Complemento a 2

En la representación de números negativos en binario utilizamos complemento a 2. Para ello, tomamos un número en binario e intercambiamos los 1s por 0s y los 0s por 1s. En el ejemplo siguiente convertimos 5 a negativo (251):

% 0000 0101 >> Intercambiamos >> % 1111 1010 +1

Sin embargo, en z80 no acabamos aquí, el procesador suma 256 (%1 0000 0000) al número en cuestión para almacenarlo como negativo.


Puedes cargar negativos directamente con LD:

        

        ld A, -10        ;sin embargo, es más recomendable el hacer negativos

Hay 2 formas de hacer negativos en z80:

El complemento a dos en z80 se hace así

        ld A, 5        ;en A ahora hay un 5, es decir %00000101

Para convertir el 5 en negativo, invertimos los 1 y los 0

        

        xor A                ;utilizamos el o exclusivo para convertirlo en %11111010
        

        ;otra opción:
        cpl                 
;intercambiamos 1 y 0 del registro A

Y luego sumamos 1

        inc A                ;ahora tenemos %11111011



LEER TECLADO

La lectura de teclado en Z80 funciona leyendo bits de las direcciones de memoria de la siguiente tabla.

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

tecla_pulsad: db. 0   ; Variable donde guardar la última tecla

leer_Teclas:


 push
BC             ; Guardamos en la pila el valor original del registro BC
  push DE             ; Guardamos en la pila el valor original del registro DE
 ld
HL, tecla_pulsad ; Cargamos la dirección de la variable donde almacenamos la última tecla pulsada

lecturaT:         ; Leer
 ld
BC, $FBFE    ; Cargamos en BC el puerto que vamos a mirar
  in A,(C)        ; Leemos el puerto en el acumulador
 bit
4, A        ; Testeamos el bit 4 (en este caso 'T')
 jr
z, leidoT    ; Si está a 0 significa que se ha pulsado

leidoT:
 ld
(HL), 3      ; Cargo el valor 3 (Azul)

 pop
DE          ; Devolvemos el valor original de DE
  pop BC          ; Devolvemos el valor original de BC

 ret



EJERCICIOS PARA PRACTICAR

1) Sumatorio de los elementos de un array de tamaño desconocido con delimitador de final (con valor 255) que se devuelve por el registro A.

Esta suma no puede ser mayor de 201; en caso de que lo sea, devolver la suma menor más cercana.

Entrada > en HL la variable

Salida > en A la suma

2) Comprobar si el primer y el último elemento de un array de tamaño desconocido con un delimitador de final (con valor 255) son iguales.

En caso de que lo sean, devolver por el registro A un número cuyo bit 4 sea 1 y, en caso de que no, que el bit 4 sea 0.

3) Combinar 2 arrays de tamaño 4 en uno de 8 de forma de que los elementos de ambos se vayan alternando.

Es decir, de la siguiente forma:

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

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

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

4) Elimina los elementos de valor 3 del siguiente array de tamaño 12:

Array de ejemplo: [0, 2, 0, 3, 1, 0, 1, 3, 0, 3, 2, 1]

5) Pinta el siguiente array por pantalla de manera invertida (tiene el número necesario exacto de valores):

label_pantalla:

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

6) Tic-Tac-Toe

Juego en el que se gestionen turnos, 2 jugadores (color rojo y azul), tablero y fin de partida.



EJEMPLOS Y AYUDAS

Multiplicar y Dividir

Sabiendo que 3*3 = 3+3+3 y que 6/3 = nº(3+3) = 2

Multiplicar

        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

Dividir 8 bits / 8 bits (divisibles entre ellos)

        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

División 8 bits / 8 bits (con resto)

; D entre E
; Cociente en D, resto en A
   xor
A     ;A = 0
   ld
B, 8   ;número de bits del divisor
modulo_bucle:

    sla D
   rla
   cp
E
   
jr c, resto  
   
sub E
   inc
D
resto:  djnz modulo_bucle

Potencias de 2

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

Potencias de 2^(-1)

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

Generar Aleatorio

Guardado en A a partir del registro R

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

Ejemplo de Cabecera

output "NombreArchivo.bin"   ;Salida de fichero compilado usado en Sublime
       DEVICE
ZXSPECTRUM48  ;Indicador de modelo de Spectrum usado para VSCode
       
org $8000            ; Programa ubicado a partir de $8000 = 32768

inicio:
         di           ;Deshabilitar interrupciones
               ld
sp,0      ;Establecer el puntero de pila en la parte alta de la                                         memoria
               
jp buclePrincipal ;Saltar al bucle principal del programa para                                                         evitar corromper la memoria con la variable
; --------------------
; Variables a utilizar
; --------------------


buclePrincipal:

Ejemplo de Función

nombreDeLaFuncion:

;Guardamos los valores originales (solo) de los registros que vamos a utilizar a lo largo de la función

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

;Código de lo que haga la función

;Sacamos los valores guardados en la pila que hayamos usado a lo largo de la función en su registro correspondiente (sobreescribiendo su uso y carga de valores en la función)
        pop
IY
        pop
IX
        pop
HL
        pop
DE
        pop
BC
        pop
AF

;Volver a la llamada de la función en el bucle principal del programa (la línea de debajo del call)
        
ret

Ejemplo de Bucles

        ld B,10        ;Bucle reduciendo valores automáticamente (djnz y B)

bucle1:

        ;código del bucle aquí

        djnz bucle1        ;B == 0?

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

        ld C,10        ;Bucle reduciendo valores de manera manual

bucle2:

        ;código del bucle aquí

        inc C

        ld A,C

        cp 0 

        jr C, bucle2 ;C <= 0?

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

        ld D,0        ;Bucle aumentando valores de manera manual

bucle3:

        ;código del bucle aquí

        dec D

        ld A,D

        cp 10

        jr nz, bucle3        ;D != 10?

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

        ld E,0        ;Bucle infinito que para cuando se cumple una condición

bucle4:

        ;código del bucle aquí

        inc E

        cp 12

        jr z, finbucle4        ;E == 12?

        jr bucle4

finbucle4:

Dividir Número en Centenas, Decenas y Unidades

;Valor de entrada

valor:

  db 231

;Llamada a la función
 
call divisionPartes


;Función

divisionPartes:
 push
AF
 push
BC
 push
IX
 push
IY

 ld
B,2                           ; B es el número máximo de centenas
 ld
 C,199                         ; C es B*100 - 1
centenas:
 ld
IX,valor                     ; Cargo en IX la dirección de 'valor'
 ld
A,(IX)                       ; Cargo en A el valor de 'valor'
 cp
C                            ; Comparo A (valor) con C
 jr nc,
guardar_centenas          ; Compruebo si A > C
 ld
A,B                          ; Cargo en A el valor de B
 cp
 1                            ; Comparo A (valor) con 1
 jr z,
guardar_centenas_vacio     ; Compruebo si A == 1
 ld
A,C                          ; Cargo en A el registro C
  sub 100                         ; Resto al acumulador (registro A) 100
 ld
C,A                           ; Cargo en C el registro A
 dec
B                            ; Reduzco B en 1
 jp centenas                      
; Salto de vuelta a 'centenas'
guardar_centenas_vacio:           
 
ld B, 0                          ; Cargo el valor 0 en B
guardar_centenas:
 
ld IY,dividido                   ; Cargo la dirección del primer elemento de 'dividido' en IY
 
ld (IY),B                        ; Cargo el registro B en la dirección recogida en IY
restar100:
 
sub 100                          ; Restamos 100 al registro A
  ld (IX),A                         ; Cargamos el registro A en la dirección recogida en IX 'valor'
  djnz restar100                   ; Hacemos B - 1 y comprobamos si es 0, si no es 0 saltamos a restar100
  inc IY                           ; Pasamos a la siguiente posición de 'dividido'

; Repetimos el mismo proceso anterior pero con las decenas
 
ld B,9                           ; Cargamos en B el número máximo de centenas
decenas:
 
ld A,C
 
sub 1O                           ; Ahora restamos 10 al registro A, en vez de 100
  ld C,A
 
ld A,(IX)
 cp
C
 jr c,
guardar_decenas
 
ld A,B
 
CP 1
 jr z,
guardar_decenas_vacio
 
ld A,C
 
sub 10
 
ld C,A
 
dec B
 jp
decenas
guardar_decenas_vacio:
 
ld B,0
guardar_decenas:
 
ld IY,dividido
 
ld (IY),B
restar10:
 
sub 10
 
ld (IX),A
 djnz
restar10
 
inc IY

; Guardamos las unidades directamente
unidades:
 
ld A,(IX)
 
ld (IY),A

 
pop IY
 
pop IX
 
pop BC
 
pop AF
 ret

; Poner las variables de la función siempre al final, después del return para no corromper la memoria

dividido:    ; Valores en centenas, decenas y unidades
  db 0, 0, 0

Quitar Espacios de una Frase

;Valor de entrada

TextoOriginal:      ; Utilizada para almacenar la frase original (9 elementos + 0 como marca de fin)
 db
65, 32, 48, 67, 32, 66, 52, 32, 73, 0

;Llamada a la función
 
call QuitarEspaciosPila

QuitarEspaciosPila:
 
 push
AF
 push
BC

  push DE

  push HL

 
ld HL,TextoOriginal
 
ld B,9
bucleIntroPila:
 
ld (HL),D
 
inc HL
 jr nz,
NoEspacioPila
 
push DE
NoEspacioPila:
 djnz
bucleIntroPila
GuardarTexto:
 
ld HL,TextoVacio
 
ld B,9
bucleG:
 
pop DE
 
ld (HL),D
 
inc HL
 djnz
bucleG
MeterPilaOriginal:
 
ld HL,TextoVacio
 
ld B,9
bucleMP:
 
ld D,(HL)
 
inc HL
 
push DE
 djnz
bucleMP
SacarPilaSinEspacio:
 
ld HL,TextoVacio
 
ld B,9
bucleSP:
  pop DE
 ld
(HL),D
 inc
HL
 djnz
bucleSP

  pop HL

  pop DE
 pop
BC
 pop
AF

 ret

TextoVacio:      ; Utilizada para almacenar una frase vacía (elementos + 0)

  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

Función de Pausa

;Código por el profesor Daniel León - UFV;Llamada a la función
 call
pausa

;Función de pausa
pausa:
 
   push
AF             ; Salva registros utilizados en la pila
    push DE

   ld
B,1 ; pausa
paus0:
   ld
DE,10000        ; Inicializa DE a 10000 para ciclar hasta 0 en el bucle interno
paus1:  
   dec
 DE              ; Decrementa DE. En la primera vuelta, DE valdrá 9999
   ld
A,D              ; Para comprobar si DE es 0000, D tiene que ser 0...
   or
E                ; ... y E también
   jr nz,
paus1        ; Si no lo es, sigue el bucle interno
    djnz paus0          ; B=B-1 y cicla a bucle externo hasta que B sea 0

   pop
DE              ; Recupera registros utilizados desde la pila
    pop AF

   ret                
 ; Retorna al punto de llamada

Pintar Borde de la Pantalla

;Llamada a la función
 call
pintarBordes

;Función

pintarBordes:
 
 push
AF

 ; Podremos cargar los colores de 0 a 7 de la siguiente forma:
 ;
 ; 0 = negro  2 = rojo    4 = verde  6 = amarillo
 ; 1 = azul   3 = morado  5 = cyan   7 = blanco
 ;
 ;


  ld A,4 

; Para pintar los bordes tomaremos su dirección de memoria y utilizaremos OUT
 out
(0xFE),A

 pop
AF

 ret

Invertir Grupos de 6

;Lista a utilizar

datosCaja:      

  .db 9,10,12,4,2,9 ,13,12,10,3,3,7 ,12,10,12,20,10,3 ,14,12,10,19,11,2 ,3,16,10,8,7,4 ,14,12,10,19,11,2 ,13,12,10,3,3,7 ,255

;Llamada a la función
 call
pintarBordes

;Función
invierteEnGruposDe6:
 push
AF
 push
BC
 push
DE
 push
IX
 push
IY

 
LD IX,datosCaja
PorGrupo:
 
LD IY,grupo6
 
LD B, 6
bucleGuardarGrupo:
 
LD A,(IX)
 
cp 255
 jr z,
finalCadenaGuardada
 
LD (IY),A
 
INC IX
 
INC IY
 djnz
bucleGuardarGrupo
guardarGrupoPila: 
 
LD IY,grupo6 
 
LD DE,(IY+5)
 
PUSH DE  
 
LD DE,(IY+4)
 
PUSH DE  
 
LD DE,(IY+3)  
 
PUSH DE
 
LD DE,(IY+2)  
 
PUSH DE
 
LD DE,(IY+1)  
 
PUSH DE
 
LD DE,(IY)
 
PUSH DE
 jp
PorGrupo
finalCadenaGuardada:
 
LD IX, datosCaja
inversionFinal:
 
LD A,(IX)
 
cp 255
 jr z,
finalinvertir
 
POP DE
 
LD (IX),D
 jp
inversionFinal
finalinvertir:

 pop
IY
 pop
IX
 pop
DE

  pop BC
 pop
AF
 
ret

grupo6:      ; Utilizada para almacenar los elementos de la pila al sacarlos
 .db
0, 0, 0, 0, 0, 0

Cuadrado XYC

Pinta un cuadro de color A en las coordenadas X,Y (1..32, 1..24). Recibe coordenadas X,Y y color en B,C y A respectivamente.

Esta función determina la dirección con la fórmula Dirección = $5800+32*Y+X

;Código por el profesor Daniel León - UFV;Llamada a la función
 call
cuadroXYC

;Función

cuadroXYC:          
   push
AF             ; Salva registros usados
   push
DE
   push
HL

   ld
H,0                ; Empieza calculando Y*32
   ld
L,C                ; Para multiplicar c (coord. Y) por 32, se usan sumas. Esto se puede hacer en un bucle también
   add
HL,HL            ; ahora hl es 2*c
   add
HL,HL            ; ahora hl es 4*c
   add
HL,HL            ; ahora hl es 8*c
   add
HL,HL            ; ahora hl 16*c
   add
HL,HL            ; ahora hl 32*c 
   
   ld
D,0
   ld
E,B                ; b Contiene el valor de la coordenada X -> Se carga en DE

   add
HL,DE            ; Ahora HL Contiene 32*y+X
   ld
DE,$5800          ; Se carga en DE la dirección base de la zona de atributos (colores)
    add HL,DE            ; Ahora HL contiene $5800 + 32*Y + X, el valor buscado,

   sla
A                 ; Ajuste de color para paper desplazando los bits a la zona de paper y bright
   sla
A                 ; mediante tres desplazamientos a la izquierda
   sla
A                 ; Esta secuencia es lo mismo que multiplicar por 8

   ld
(HL),A             ; Transfiere a memoria de video apuntada por HL el valor del color sobre el PAPER y el bit de brillo

   pop
HL                ; Recupera registros desde la pila
    pop DE
   pop
AF

   ret                  
; Retorno de la función

Invertir Pila

;Llamada a la función
 call
invertirPila

;Función

invertirPila:
 
push BC
 
push DE
 
push HL 
; Cargamos los 4 elementos de la pila para poder darle la vuelta a una pila no vacía
; En caso de querer darle la vuelta a cualquier número de elementos eliminar el código entre los [] que es el que cargamos en la pila para comprobar la funcionalidad:
;
;[
 
ld DE,1212
 
push DE
 
ld DE,1213
 
push DE
 
ld DE,1214
 
push DE
 
ld DE,1215
 
push DE
;]
SacarPila:
 
ld HL,mipila
 
ld B,4
bucleS:
 
pop DE
 
ld (HL),D
 
inc HL 
 
ld (HL),E
 
inc HL 
 djnz
bucleS

MeterPila:
 
ld HL,mipila
 
ld B,4
bucleM:
 
ld D,(HL)
 
inc HL 
 
ld E,(HL)
 inc
HL 
 
push DE
 djnz
bucleM

; Sacamos los valores de la pila para este caso de la pila
;
;[
 
pop DE
 
pop DE
 
pop DE
 
pop DE
;]

 
pop HL 
 
pop DE
 
pop BC
 ret
 
; Poner las variables de la función siempre al final

mipila:      ; Utilizada para almacenar los elementos de la pila al sacarlos
 
.dw 00, 00, 00, 00



PROFUNDIZAR

Guía de z80 por Matt Heffernan - Lista de Youtube

z80 game development por James Malcolm - Página de Inicio

z80 Heaven - Wiki

z80 Zilog - User Manual

Multiplatform z80 Assembly Programming por ChibiAkumas - Página de Inicio

Zilog Z80A Technical Information por WorldOfSpectrum - Página Web 

Sprites en ZX Spectrum - Página Web

Última actualización: 09 / 2022