
ÍNDICE
Shadow Registers (Registros fantasma)
NÚMEROS NEGATIVOS - Complemento a 2
Dividir Número en Centenas, Decenas y Unidades
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.
Instalación del emulador/compilador de z80 (en caso de no tener uno):
Documentación de DeZog (en caso de dificultades)
|
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:
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.
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().
arg1 > a Carry set
arg1 <= a Carry reset
arg1 == a Zero set
arg1 != a Zero reset
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.
|
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 |
|
ld A, 4 ;cargo un número en A |
|
inc C ;hace C = C + 1 |
|
dec C ;hace C = C - 1 |
|
ld A, 4 ; cargo 4 en A |
Esto también funciona con registros dobles
|
ld A, 4 ; cargo 4 en A |
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).
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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.
En memoria, vista de posición menor a mayor, se guarda la Rd y luego la Ri:
Funciona de manera inversa:
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 |
Pone el estado set a un bit en específico
Recibe:
|
set 2, C ;nuevo C = 0 1 0 0 1 1 1 1 |
Pone el estado reset a un bit en específico
Recibe:
|
res 6, C ;nuevo C = 0 0 0 0 1 0 1 1 |
$4000
Tamaño = 192 filas x 32 columnas = 6144 = $1800
Bitmaps para spriting
$5800
Tamaño = 24 filas x 32 columnas = 756 = $300
Es un puerto I/O aislada, en un dispositivo que se llama ULA, que maneja el teclado, el borde y el sonido:
$fe
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
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)
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)
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
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 |
Realiza el código a continuación:
|
cp (HL) |
Realiza el código a continuación:
|
ld (DE), (HL) |
Realiza LDI hasta que BC sea 0
Realiza el código a continuación:
|
ld (DE), (HL) |
Realiza LDD hasta que BC sea 0
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 |
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 |
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: |
Y luego sumamos 1
|
inc A ;ahora tenemos %11111011 |
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:
|
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.
Sabiendo que 3*3 = 3+3+3 y que 6/3 = nº(3+3) = 2
Multiplicar
|
ld C, 2 |
Dividir 8 bits / 8 bits (divisibles entre ellos)
|
ld C, 0 ;C es nuestro contador |
División 8 bits / 8 bits (con resto)
|
; D entre E sla D |
|
ld B, 2 ;potencia de a*2^(b-1) |
|
ld B, 2 ;potencia de a/2^(b-1) |
Guardado en A a partir del registro R
|
random_number: |
|
output "NombreArchivo.bin" ;Salida de fichero compilado usado en Sublime |
|
nombreDeLaFuncion: push AF ;Volver a la llamada de la función en el bucle principal del programa (la línea de debajo del call) |
|
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: |
|
;Valor de entrada valor: db 231
;Llamada a la función
divisionPartes: |
|
;Valor de entrada TextoOriginal: ; Utilizada para almacenar la frase original (9 elementos + 0 como marca de fin) push DE push HL pop HL pop DE
TextoVacio: ; Utilizada para almacenar una frase vacía (elementos + 0) db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
;Código por el profesor Daniel León - UFV;Llamada a la función ;Función de pausa |
|
;Llamada a la función ;Función pintarBordes:
; Para pintar los bordes tomaremos su dirección de memoria y utilizaremos OUT |
|
;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 ;Función pop BC |
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 ;Función cuadroXYC: |
|
;Llamada a la función ;Función invertirPila: |
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