INSTRUCCIONES DE CARGA

Las instrucciones de carga transfieren contenidos de memoria a registros, de registros a memoria y entre registros.

Se trata del grupo principal de instrucciones del microprocesador, y su necesidad queda justificada, ya que todas las operaciones aritméticas y lógicas se hacen sobre registros del microprocesador, o entre estos y posiciones de memoria y casi siempre será necesario almacenar los resultados sobre la memoria.

Por otra parte, gran número de instrucciones utilizan registros para direccionar posiciones de memoria, bien sea mediante direccionamiento absoluto o indexado.

El formato básico de estas instrucciones es:

LD DESTINO, ORIGEN

El código LD del inglés "LOAD" (carga), indica al microprocesador que debe cargar en el "DESTINO" el valor contenido en el "ORIGEN".

El "DESTINO" y el "ORIGEN", pueden ser tanto registros, como posiciones de memoria, utilizaremos "r" y "r'" para referirnos a los registros de 8 bits, afectados por la instrucción, y "dd" para referirnos a los de 16 bits (pares de registros).

Los valores de "r" y "r'" usados para el código de máquina en este grupo de instrucciones, son los siguientes:

r y r'
registro
111
A
000
B
001
C
010
D
011
E
100
H
101
L

Los valores de "dd" usados para el código de máquina en este grupo de instrucciones, son los siguientes:

dd
par de registros
00
BC
01
DE
10
HL
11
SP

Grupo de instrucciones de carga en registros
LD r,r'
OBJETO:
Carga el contenido del registro indicado por r' en el registro indicado por r.
CODIGO MAQUINA:
0 1 <--- r ---><--- r' --->
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
1
CICLOS DE RELOJ:
4

EJEMPLO:

LD A,B

El contenido de "A" no es significativo, ya que será destruido por la instrucción. Supongamos que el contenido de "B" es 43 en decimal, 2Bh en Hexa.

(B):
0 0 1 0 1 0 1 1
2Bh

Ejecutamos la instrucción: LD A,B que carga en el registro "A", el contenido del registro "B":

LD A,B:
0 1 1 1 1 0 0 0
78h

Después de la ejecución, el registro "A" contendrá el valor que contenía el registro "B", mientras que el contenido de este último no se habrá modificado.

Contenido de "A" después de la ejecución:

(A):
0 0 1 0 1 0 1 1
2Bh

Contenido de "B" después de la ejecución:

(B):
0 0 1 0 1 0 1 1
2Bh

Como vimos en un capítulo anterior, los registros cumplen, en código máquina, una función similar a la de las variables en Basic, de forma que esta instrucción sería similar a la instrucción: LET A=B del Basic.

LD r,n
OBJETO:
Carga en el registro indicado por "r" el valor numérico "n" de 8 bits y en el rango de 0 a 255.
CODIGO MAQUINA:
0 0 <--- r ---> 1 1 0
<-------- n -------->
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
7

EJEMPLO:

LD A,47

Esta instrucción carga el valor 47 decimal (2Fh Hexa) en el registro "A", el contenido anterior de este registro se pierde al ejecutarse la instrucción.

La mayoría de los ensambladores, permiten introducir los números, tanto en decimal como en Hexa. Concretamente, en el caso del GENS 3, esta instrucción se podría escribir también como:

LD A,#2F

El signo "#" delante del número, indica al ensamblador que se trata de un número hexadecimal.

Instrucción.

LD A,47:
0 0 1 1 1 1 1 0
0 0 1 0 1 1 1 1
3Eh
2Fh

Contenido de "A" después de la ejecución:

(A):
0 0 1 0 1 1 1 1
2Fh

El equivalente en Basic de esta instrucción sería: LET A=47

LD r,(HL)
OBJETO:
Carga en el registro indicado por "r", el contenido del octeto de memoria cuya dirección es el valor del par de registros HL.
CODIGO MAQUINA:
0 1 <--- r ---> 1 1 0
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
7

EJEMPLO:

LD B,(HL)

Esta instrucción carga en el registro "B", el contenido de la posición de memoria cuya dirección es el contenido del par de registros "HL". En este caso, estamos usando el modo de direccionamiento indirecto para especificar el "ORIGEN".

Supongamos que el registro "HL" contiene el valor 5F47h (24391), el registro "H" contendrá 5Fh (95) y el registro "L" contendrá 47h (71); observe que 95x256+71=24391.

La posición de memoria cuyo contenido vamos a cargar, será por tanto, la 5F47h. Supongamos que a su vez, esta posición de memoria contiene el número 55h (85). Veamos cómo se desarrollan los acontecimientos.

(H):
(L):
0 1 0 1 1 1 1 1
0 1 0 0 0 1 1 1
5Fh
47h

Contenido de la posición de memoria 5F47h:

(5F47h):
0 1 0 1 0 1 0 1
55h

Ejecutamos la instrucción:

LD B,(HL):
0 1 0 0 0 1 1 0
46h

Tras la instrucción, sólo se habrá modificado el contenido del registro "B".

Contenido del registro "B" después de la instrucción:

(B):
0 1 0 1 0 1 0 1
55h
LD r,(IX+d)
OBJETO:
Carga en el registro indicado por "r", el contenido de la posición de memoria que resulta de sumar: el valor del registro índice "IX" con un entero de desplazamiento "d", el cual puede adquirir los valores desde -128 a +127.
CODIGO MAQUINA:
1 1 0 1 1 1 0 1
0 1 <--- r ---> 1 1 0
<-------- d -------->
DDh
 
 
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
5
CICLOS DE RELOJ:
19

EJEMPLO:

LD C,(IX+10)

En este caso, vamos a cargar el registro "C" con el contenido de la posición de memoria, cuya dirección es el resultado de sumar 10 al contenido del registro índice "IX".

Esta instrucción utiliza direccionamiento indexado para especificar el "ORIGEN"; obsérvese que el direccionamiento indexado es similar al indirecto, pero más sofisticado.

El contenido del registro "C" es irrelevante, ya que será destruido por la instrucción. Supongamos que el contenido de "IX" es 7743h (30531), por lo que accederemos a la posición de memoria 774Dh (30541). Supongamos también, que el contenido de esa posición de memoria es 41h (65).

Contenido de "IX":

(IX):
0 1 1 1 0 1 1 1
0 1 0 0 0 0 1 1
77h
43h

Contenido de la posición de memoria 774Dh:

(774Dh):
0 1 0 0 0 0 0 1
41h

Ejecutamos la instrucción:

LD C,(IX+10):
1 1 0 1 1 1 0 1
0 1 0 0 1 1 1 0
0 0 0 0 1 0 1 0
DDh
4Eh
0Ah

Contenido de "C" después de la ejecución:

(C):
0 1 0 0 0 0 0 1
41h

Observe que la posición de memoria leída es 7743h+10, es decir 7743h+Ah=774Dh. Tanto el contenido de esta posición de memoria, como el del registro "IX", no han sido alterados.

LD r,(IY+d)
OBJETO:
Carga en el registro indicado por "r", el contenido de la posición de memoria que resulta de sumar: el valor del registro índice "IY" con el entero de desplazamiento "d", el cual puede tomar los valores desde -128 a +127.
CODIGO MAQUINA:
1 1 1 1 1 1 0 1
0 1 <--- r ---> 1 1 0
<-------- d -------->
FDh
 
 
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
5
CICLOS DE RELOJ:
19

EJEMPLO:

LD A,(IY-15)

De forma similar al ejemplo anterior, vamos a cargar el acumulador con el contenido de la posición de memoria direccionada por el índice "IY" menos 15.

Supongamos que el contenido de "IY" es 7743h (30531), direccionamos, por tanto la posición de memoria 7734h (30516), a la que a su vez, le suponemos un contenido de 42h (66).

(IY):
0 1 1 1 0 1 1 1
0 1 0 0 0 0 1 1
77h
43h

Contenido de la posición de memoria 7734h:

(7734h):
0 1 0 0 0 0 1 0
42h

Ejecutamos la instrucción:

LD A,(IY-15):
1 1 1 1 1 1 0 1
0 1 1 1 1 1 1 0
1 1 1 1 0 0 0 1
FDh
7Eh
F1h

Contenido de "A" después de la ejecución:

(A):
0 1 0 0 0 0 1 0
42h

Obsérvese que hemos representado -15 como F1h, que es precisamente el complemento a 2 de 0Fh, es decir, el negativo de 15.

En el Z-80, el primer byte del código de operación de todas las intrucciones que utilizan el registro "IX" es DDh, y el de todas las que utilizan el "IY" es FDh.

Grupo de instrucciones de carga en memoria
LD (HL),r
OBJETO:
Carga el contenido del registro indicado por "r", en el octeto de memoria direccionado por el valor del par de registros HL.
CODIGO MAQUINA:
0 1 1 1 0 <--- r --->
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
7

EJEMPLO:

LD (HL),B

Esta instrucción carga en la posición de memoria cuya dirección es el contenido de "HL", el contenido del registro "B". Los contenidos previos de "B" y "HL" no son alterados y sí el contenido de la posición de memoria correspondiente.

En este caso, se utiliza direccionamiento indirecto para especificar el "DESTINO".

Supongamos que "HL" contiene 4723h (18211), ésta será por tanto, la posición a la que accederemos. Suponemos asimismo, que el registro "B" tiene un con tenido de 75h (117). El contenido de la posición de memoria 4723h es irrelevante, ya que será destruido por la instrucción.

Contenido del par "HL":

(H):
(L):
0 1 0 0 0 1 1 1
0 0 1 0 0 0 1 1
47h
23h

Contenido de "B":

(B):
0 1 1 1 0 1 0 1
75h

Ejecutamos la instrucción:

LD (HL),B:
0 1 1 1 0 0 0 0
70h

Contenido de la posición 7423h después de la ejecución:

(4723h):
0 1 1 1 0 1 0 1
75h
LD (IX+d),r
OBJETO:
Carga el contenido del registro indicado por "r", en el octeto de la posición de memoria que resulta de sumar: el valor del registro índice "IX" con el entero de desplazamiento "d", el cual puede adquirir los valores desde -128 a +127.
CODIGO MAQUINA:
1 1 0 1 1 1 0 1
0 1 1 1 0 <--- r --->
<-------- d -------->
DDh
 
 
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
5
CICLOS DE RELOJ:
19

EJEMPLO:

LD (IX+7),C

Supongamos que "IX" contiene 75B3h (30131), por lo que accederemos a la posición 75BAh (30138), cuyo contenido es irrelevante. Supongamos también que "C" contiene F0h (240).

Contenido del par "IX":

(IX):
0 1 1 1 0 1 0 1
1 0 1 1 0 0 1 1
75h
B3h

Contenido de "C":

(C):
1 1 1 1 0 0 0 0
F0h

Ejecutamos la instrucción:

LD (IX+7),C:
1 1 0 1 1 1 0 1
0 1 1 1 0 0 0 1
0 0 0 0 0 1 1 1
DDh
71h
07h

Contenido de la posición 75BAh después de la ejecución:

(75BAh):
1 1 1 1 0 0 0 0
F0h
LD (IY+d),r
OBJETO:
Carga el contenido del registro indicado por "r", en el octeto de la posición de memoria resultante de sumar: el valor del registro índice "IY" al entero de desplazamiento "d", el cual puede adquirir los valores desde -128 a +127.
CODIGO MAQUINA:
1 1 1 1 1 1 0 1
0 1 1 1 0 <--- r --->
<-------- d -------->
FDh
 
 
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
5
CICLOS DE RELOJ:
19

EJEMPLO:

LD (IY+10),B

Supongamos que el índice "IY" contiene 5F40h (24384), por lo que accederemos a la posición 5F4Ah (24394). Supongamos también, que el registro "B" contiene FFh (255). El contenido de la posición 5F4Ah no es significativo, ya que será destruido por la instrucción.

Contenido del índice "IY":

(IY):
0 1 0 1 1 1 1 1
0 1 0 0 0 0 0 0
5Fh
40h

Contenido del registro "B":

(B):
1 1 1 1 1 1 1 1
FFh

Ejecutamos la instrucción:

LD (IY+10),B:
1 1 1 1 1 1 0 1
0 1 1 1 0 0 0 0
0 0 0 0 1 0 1 0
FDh
70h
0Ah

Contenido de la posición 5F4Ah después de la ejecución:

(5F4Ah):
1 1 1 1 1 1 1 1
FFh
LD (HL),n
OBJETO:
Carga el valor del número entero "n", (entre 0 y 255) en la posición de memoria cuya dirección es el contenido del par de registros "HL".
CODIGO MAQUINA:
0 0 1 1 0 1 1 0
<-------- n -------->
36h
 
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
3
CICLOS DE RELOJ:
10

EJEMPLO:

LD (HL),57

Este ejemplo se podría escribir también como: LD (HL),#39 ya que 39h = 57.

Suponemos que el par de registros "HL" contiene 6ACBh (27339), por tanto, esa será la dirección de memoria a la que accederemos. El contenido de esta posición de memoria no es significativo, ya que será destruido por la instrucción.

Contenido de "HL":

(H):
(L):
0 1 1 0 1 0 1 0
1 1 0 0 1 0 1 1
6Ah
CBh

Ejecutamos la instrucción:

LD (HL),57:
0 0 1 1 0 1 1 0
0 0 1 1 1 0 0 1
36h
39h

Contenido de la posición 6ACBh después de la ejecución:

(6ACBh):
0 0 1 1 1 0 0 1
39h
LD (IX+d),n
OBJETO:
Carga el valor del número entero "n", en el octeto de la posición de memoria que resulta de sumar: el contenido del registro índice "IX" al entero de desplazamiento "d", el cual puede adquirir valores desde -128 a +127.
CODIGO MAQUINA:
1 1 0 1 1 1 0 1
0 0 1 1 0 1 1 0
<-------- d -------->
<-------- n -------->
DDh
36h
 
 
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
5
CICLOS DE RELOJ:
19

EJEMPLO:

LD (IX+3),7

Suponemos que "IX" contiene 73BCh (29628), por lo que accederemos a la posición 73BFh (29631), cuyo contenido es irrelevante.

Contenido de "IX":

(IX):
0 1 1 1 0 0 1 1
1 0 1 1 1 1 0 0
73h
BCh

Ejecutamos la instrucción:

LD (IX+3),7:
1 1 0 1 1 1 0 1
0 0 1 1 0 1 1 0
0 0 0 0 0 0 1 1
0 0 0 0 0 1 1 1
DDh
36h
03h
07h

Contenido de la posición 73BFh después de la ejecución:

(73BFh):
0 0 0 0 0 1 1 1
07h
LD (IY+d),n
OBJETO:
Carga el valor del número entero "n", en el octeto de la posición de memoria que resulta de sumar: el contenido del registro índice "IY" al entero de desplazamiento "d", el cual puede adquirir los valores desde -128 a +127.
CODIGO MAQUINA:
1 1 1 1 1 1 0 1
0 0 1 1 0 1 1 0
<-------- d -------->
<-------- n -------->
FDh
36h
 
 
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
5
CICLOS DE RELOJ:
19

EJEMPLO:

LD (IY+5),15

Le suponemos a "IY" un contenido de 5000h (20480), por lo que accederemos a la posición 5005h (20485). El contenido de esta posición es irrelevante.

Contenido de "IY":

(IY):
0 1 0 1 0 0 0 0
0 0 0 0 0 0 0 0
50h
00h

Ejecutamos la instrucción:

LD (IY+5),15:
1 1 1 1 1 1 0 1
0 0 1 1 0 1 1 0
0 0 0 0 0 1 0 1
0 0 0 0 1 1 1 1
FDh
36h
05h
0Fh

Contenido de la posición 5005h después de la ejecución:

(5005h):
0 0 0 0 1 1 1 1
0Fh
Grupo de instrucciones de carga en registro acumulador
LD A,(BC)
OBJETO:
Carga en el registro acumulador, el contenido de la posición de memoria direccionada por el par de registros "BC".
CODIGO MAQUINA:
0 0 0 0 1 0 1 0
0Ah
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
7

EJEMPLO:

LD A,(BC)

Supongamos que el par de registros "BC" contienen el número 76DFh (30431), esta es por tanto, la posición cuyo contenido cargaremos en el acumulador. Supongamos también, que el contenido de esta posición es AAh (170). El contenido del acumulador es irrelevante, ya que se pierde al ejecutar la instrucción.

Contenido del registro "BC":

(B):
(C):
0 1 1 1 0 1 1 0
1 1 0 1 1 1 1 1
76h
DFh

Contenido de la posición de memoria 76DFh:

(76DFh):
1 0 1 0 1 0 1 0
AAh

Ejecutamos la instrucción:

LD A,(BC):
0 0 0 0 1 0 1 0
0Ah

Contenido del acumulador después de la ejecución:

(A):
1 0 1 0 1 0 1 0
AAh
LD A,(DE)
OBJETO:
Carga en el registro acumulador, el contenido de la posición de memoria direccionada por el par de registros "DE".
CODIGO MAQUINA:
0 0 0 1 1 0 1 0
1Ah
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
7

EJEMPLO:

LD A,(DE)

Contenido del registro acumulador, no significativo.

Contenido del registro "DE":

(D):
(E):
0 1 0 0 1 1 1 1
1 1 1 1 1 1 1 1
4Fh
FFh

Contenido de la posición de memoria 4FFFh:

(4FFFh):
1 1 1 0 1 1 1 0
EEh

Ejecutamos la instrucción:

LD A,(DE):
0 0 0 1 1 0 1 0
1Ah

Contenido del acumulador después de la ejecución:

(A):
1 1 1 0 1 1 1 0
EEh
LD A,(nn)
OBJETO:
Carga en el registro acumulador, el contenido de la posición de memoria direccionada por el operando "nn".
CODIGO MAQUINA:
0 0 1 1 1 0 1 0
<-------- n -------->
<-------- n -------->
3Ah
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
4
CICLOS DE RELOJ:
13

EJEMPLO:

CERO EQU #456A
LD A,(CERO)

La primera línea de este ejemplo define una etiqueta, esta operación no tiene código máquina y sirve simplemente, para indicarle al ensamblador, que allá donde le digamos la palabra "CERO", debe entender que queremos decir el número 456Ah.

Esta instrucción también se podría haber escrito sin etiqueta de la siguiente forma:

LD A,(#456A)

La utilidad de las etiquetas es que si vamos a acceder a la posición 456Ah muchas veces, seguramente nos resulte más fácil recordar la palabra "CERO" que el número 456Ah.

Contenido de la posición de memoria 456Ah:

(456Ah):
0 0 0 0 0 0 0 0
00h

Ejecutamos la instrucción:

LD A,(#456A):
0 0 1 1 1 0 1 0
0 1 1 0 1 0 1 0
0 1 0 0 0 1 0 1
3Ah
6Ah
45h

Observe cómo se codifica el operando: el octeto de orden inferior (6Ah) se almacena, en la instrucción, delante del octeto de orden superior (45h).

Contenido del acumulador después de la ejecución:

(A):
0 0 0 0 0 0 0 0
00h
LD A,I
OBJETO:
Carga en el acumulador, el contenido del registro "I" (vector de página de interrupción), y carga en el indicador "P/V" del registro "F", el estado del flip/flop de aceptación de interrupción "IFF2", que será "1" si la interrupción está habilitada y "0" si está inhabilitada. De esta forma, es posible comprobar de una sola instrucción, el estado del microprocesador en cuanto a las interrupciones.
CODIGO MAQUINA:
1 1 1 0 1 1 0 1
0 1 0 1 0 1 1 1
EDh
57h
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:

S (signo): Pone a "1" si "I" es negativo, es decir, si su bit de más peso es "1".

Z (cero): Pone a "1" si "I" vale cero.

H (semiacarreo): Pone a "0".

P/V (Paridad/desbordamiento): Pone a "1" si las interrupciones están habilitadas y a "0" si están inhibidas.

N (suma/resta): Pone a "0".

C (acarreo): Permanece con su estado anterior.

CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
9

EJEMPLO:

LD A,I

Supongamos que el registro "I" contiene el valor 9Fh y que las interrupciones están habilitadas.

Contenido de "I":

(I):
1 0 0 1 1 1 1 1
9Fh

Ejecutamos la instrucción:

LD A,I:
1 1 1 0 1 1 0 1
0 1 0 1 0 1 1 1
EDh
57h

Contenido de "A" después de la instrucción:

(A):
1 0 0 1 1 1 1 1
9Fh

Estado de "F" después de la instrucción:

S
Z
H
P/V
N
C
(F):
1
0
x
0
x
1
0
*
x: Estado indeterminado.
*: El flag no cambia su estado anterior.
LD A,R
OBJETO:
Carga en el acumulador el contenido del registro "R" (registro de regeneración).
CODIGO MAQUINA:
1 1 1 0 1 1 0 1
0 1 0 1 1 1 1 1
EDh
5Fh
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:

S (signo): Pone a "1" si "R" es negativo.

Z (cero): Pone a "1" si "R" es cero.

H (semiacarreo): Pone a "0".

P/V (Paridad/desbordamiento): Copia el estado de IFF2.

N (suma/resta): Pone a "0".

C (acarreo): Preserva su contenido anterior.

CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
9

EJEMPLO:

LD A,R

Supongamos que en el momento de la ejecución, el registro "R" contiene el número 3Ah. Y que las interrupciones están inhibidas.

Contenido de "R":

(R):
0 0 1 1 1 0 1 0
3Ah

Ejecutamos la instrucción:

LD A,R:
1 1 1 0 1 1 0 1
0 1 0 1 1 1 1 1
EDh
5Fh

Contenido de "A" después de la instrucción:

(A):
0 0 1 1 1 0 1 0
3Ah

Estado de "F" después de la instrucción:

S
Z
H
P/V
N
C
(F):
0
0
x
0
x
0
0
*
x: Estado indeterminado.
*: Preserva el estado anterior.
Grupo de instrucciones para salvar el registro acumulador
LD (BC),A
OBJETO:
Carga en el octeto de la posición de memoria direccionada por el valor del par de registros "BC", el contenido del registro acumulador.
CODIGO MAQUINA:
0 0 0 0 0 0 1 0
02h
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
7

EJEMPLO:

LD (BC),A

Suponemos que el registro "BC" contiene el número C9C0h (51648) y que el acumulador contiene BCh (188). El contenido de la posición C9C0h es irrelevante, ya que será destruido por la instrucción.

Contenido del par de registros "BC":

(B):
(C):
1 1 0 0 1 0 0 1
1 1 0 0 0 0 0 0
C9h
C0h

Contenido del registro acumulador:

(A):
1 0 1 1 1 1 0 0
BCh

Ejecutamos la instrucción:

LD (BC),A:
0 0 0 0 0 0 1 0
02h

Contenido de la posición C9C0h después de la ejecución:

(C9C0h):
1 0 1 1 1 1 0 0
BCh
LD (DE),A
OBJETO:
Carga en el octeto de la posición de memoria direccionado por el valor del par de registros "DE", el contenido del registro acumulador.
CODIGO MAQUINA:
0 0 0 1 0 0 1 0
12h
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
7

EJEMPLO:

LD (DE),A

Suponemos que "DE" contiene 8000h (32768) y que "A" contiene FFh (255). El contenido de la posición 8000h es irrelevante.

Contenido del par de registros "DE":

(D):
(E):
1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
80h
00h

Contenido del registro acumulador:

(A):
1 1 1 1 1 1 1 1
FFh

Ejecutamos la instrucción:

LD (DE),A:
0 0 0 1 0 0 1 0
12h

Contenido de la posición 8000h después de la ejecución:

(8000h):
1 1 1 1 1 1 1 1
FFh
LD (nn),A
OBJETO:
Carga en el octeto de memoria direccionado por el valor del operando "nn", el contenido del registro acumulador.
CODIGO MAQUINA:
0 0 1 1 0 0 1 0
<-------- n -------->
<-------- n -------->
32h
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
4
CICLOS DE RELOJ:
13

EJEMPLO:

LD (29016),A

Suponemos que el acumulador "A" contiene 33h (51). El contenido de la posición 7158h (29016) es irrelevante.

La instrucción se podría haber escrito también como:

LD (#7158),A

Haciendo uso de la notación hexadecimal, o bien, con una etiqueta de la siguiente forma:

ETIQUE EQU #7158
LD (ETIQUE),A

Contenido del registro acumulador:

(A):
0 0 1 1 0 0 1 1
33h

Ejecutamos la instrucción:

LD (#7158),A:
0 0 1 1 0 0 1 0
0 1 0 1 1 0 0 0
0 1 1 1 0 0 0 1
32h
58h
71h

Contenido de la posición 7158h después de la ejecución:

(7158h):
0 0 1 1 0 0 1 1
33h
LD I,A
OBJETO:
Carga en el registro "I" (vector de página de interrupción), el contenido del registro acumulador.
CODIGO MAQUINA:
1 1 1 0 1 1 0 1
0 1 0 0 0 1 1 1
EDh
47h
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
9

EJEMPLO:

LD I,A

Suponemos que el acumulador contiene el número 02h. El contenido del registro "I" es irrelevante.

Contenido del registro acumulador:

(A):
0 0 0 0 0 0 1 0
02h

Ejecutamos la instrucción:

LD I,A:
1 1 1 0 1 1 0 1
0 1 0 0 0 1 1 1
EDh
47h

Contenido del registro "I" después de la ejecución:

(I):
0 0 0 0 0 0 1 0
02h
LD R,A
OBJETO:
Carga en el registro "R" (registro de regneración), el contenido del registro acumulador.
CODIGO MAQUINA:
1 1 1 0 1 1 0 1
0 1 0 0 1 1 1 1
EDh
4Fh
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
9

EJEMPLO:

LD R,A

Supongamos que el acumulador contiene el número 57h. El contenido del registro "R" es, de nuevo, irrelevante.

Contenido del registro acumulador:

(A):
0 1 0 1 0 1 1 1
57h

Ejecutamos la instrucción:

LD R,A:
1 1 1 0 1 1 0 1
0 1 0 0 1 1 1 1
EDh
4Fh

Contenido del registro "R" después de la ejecución:

(R):
0 1 0 1 0 1 1 1
57h
Grupo de instrucciones de carga en registros de 16 bits
LD dd,nn
OBJETO:
Carga en el par de registros indicados por "dd" el número entero de dos octetos "nn".
CODIGO MAQUINA:
0 0 d d 0 0 0 1
<-------- n -------->
<-------- n -------->
 
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
3
CICLOS DE RELOJ:
10

EJEMPLO:

LD BC,#6A7F

Vamos a cargar el número 6A7Fh (27263) en el registro doble "BC", esto quiere decir, que cargaremos 6Ah (106) en el registro "B", y 7Fh (127) en el registro "C".

Esta instrucción podría haberse escrito también como:

LD BC,27263

El contenido anterior del par de registros "BC" es irrelevante.

Ejecutamos la instrucción:

LD BC,#6A7F:
0 0 0 0 0 0 0 1
0 1 1 1 1 1 1 1
0 1 1 0 1 0 1 0
01h
7Fh
6Ah

Observe que el entero 6A7Fh se codifica con el orden de sus octetos invertido, es decir, primero el octeto menos significativo (LSB) y luego el más significativo (MSB).

Contenido de "BC" después de la ejecución:

(B):
(C):
0 1 1 0 1 0 1 0
0 1 1 1 1 1 1 1
6Ah
7Fh

Recuerde:

par
dd
BC
00
DE
01
HL
10
SP
11
LD IX,nn
OBJETO:
Carga en el registro índice "IX" el número entero de dos octetos "nn".
CODIGO MAQUINA:
1 1 0 1 1 1 0 1
0 0 1 0 0 0 0 1
<-------- n -------->
<-------- n -------->
DDh
21h
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
4
CICLOS DE RELOJ:
14

EJEMPLO:

LD IX,5

Esta instrucción se podría haber escrito también como:

LD IX,#0005

De lo que se trata es de cargar el registro índice "IX" con el número 0005h (5).

Obsérvese como los enteros se codifican, de nuevo, con el orden invertido, es decir, primero va el octeto menos significativo y luego, el más significativo.

Ejecutamos la instrucción:

LD BC,#6A7F:
1 1 0 1 1 1 0 1
0 0 1 0 0 0 0 1
0 0 0 0 0 1 0 1
0 0 0 0 0 0 0 0
DDh
21h
05h
00h

Contenido del registro "IX" después de la ejecución:

(IX):
0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1
0005h
LD IY,nn
OBJETO:
Carga en el registro índice "IY" el número entero de dos octetos "nn".
CODIGO MAQUINA:
1 1 1 1 1 1 0 1
0 0 1 0 0 0 0 1
<-------- n -------->
<-------- n -------->
FDh
21h
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
4
CICLOS DE RELOJ:
14

EJEMPLO:

LD IY,#33AA

De forma similar al ejemplo anterior, vamos a cargar el número 33AAh en el registro "IY". De nuevo, codificamos los octetos del entero, al revés.

Ejecutamos la instrucción:

LD BC,#6A7F:
1 1 1 1 1 1 0 1
0 0 1 0 0 0 0 1
1 0 1 0 1 0 1 0
0 0 1 1 0 0 1 1
FDh
21h
AAh
33h

Contenido del registro "IY" después de la ejecución:

(IY):
0 0 1 1 0 0 1 1 1 0 1 0 1 0 1 0
33AAh
LD HL,(nn)
OBJETO:
Carga en el registro "L" el octeto de memoria direccionado por "nn" y en el registro "H" el octeto de memoria direccionado por "nn+1".
CODIGO MAQUINA:
0 0 1 0 1 0 1 0
<-------- n -------->
<-------- n -------->
2Ah
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
5
CICLOS DE RELOJ:
16

EJEMPLO:

LD HL,(#6677)

Vamos a cargar el par de registros "HL", con un número de dos bytes (16 bits) contenido en la memoria. Dado que el número tiene 16 bits, ocupará dos posiciones de memoria contiguas. Como operando de la instrucción, tenemos que dar la dirección de la primera de las dos posiciones.

Como de costumbre, el microprocesador considera que los octetos que componen el número, están en orden inverso, es decir, primero el de menos peso (LSB) y luego el de más peso (MSB).

Supongamos que el número que vamos a cargar en "HL" es el 33FFh (13311), que se encuentra almacenado en las direcciones de memoria 677h y 6678h (26231 y 26232). La posición 6677h contendrá FFh y la 6678h contendrá 33h.

Situación de los octetos en la memoria:

(6677h):
(6678h):
1 1 1 1 1 1 1 1
0 0 1 1 0 0 1 1
FFh
33h

Al codificar la instrucción en código máquina, también deberemos invertir el orden de los octetos en el operando:

Ejecutamos la instrucción:

LD HL,(#6677):
0 0 1 0 1 0 1 0
0 1 1 1 0 1 1 1
0 1 1 0 0 1 1 0
2Ah
77h
66h

De forma que, una vez ejecutada la instrucción, el contenido de la dirección de memoria 6677h (LSB) se habrá cargado en el registro "L", y el contenido de 6678h (MSB), lo habrá hecho en el registro "H".

Contenido de "HL" después de la instrucción:

(H):
(L):
0 0 1 1 0 0 1 1
1 1 1 1 1 1 1 1
33h
FFh
LD dd,(nn)
OBJETO:
Carga, en la parte de orden inferior del par de registros indicado por "dd", el octeto de memoria direccionado por "nn" y en la parte de orden superior, el octeto direccionado por "nn+1".
Esta instrucción es similar a la que hemos descrito anteriormente, salvo que puede trabajar con todos los pares de registros, no sólo con el "HL". En compensación, ésta ocupa 4 bytes, en lugar de los tres que ocupaba la anterior.
CODIGO MAQUINA:
1 1 1 0 1 1 0 1
0 1 d d 1 0 1 1
<-------- n -------->
<-------- n -------->
EDh
 
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
6
CICLOS DE RELOJ:
20

EJEMPLO:

NUMERO EQU #6789
LD DE,(NUMERO)

En este ejemplo, vamos a cargar en "DE", el contenido de la posición de memoria 6789h y siguiente. El orden de los octetos está, de nuevo, invertido

A partir de estas instrucciones y según nos vayamos adentrando en otras más complejas, es de suma importancia que el lector analice cuidadosamente los cuadros que acompañan a los ejemplos y que muestran el contenido de los registros y posiciones de memoria, según van siendo afectados por la instrucción.

Supongamos que el número que vamos a cargar es el número 72C8h (29384), de forma que la posición 6789h, contendrá el número C8h y la posición 678Ah, el 72h.

En el código fuente, hemos utilizado una etiqueta, con el fin de que el lector se vaya habituando a su uso. No obstante, la instrucción podría haberse escrito como:

LD DE,(#6789)

Situación del número en memoria:

(6789h):
(678Ah):
1 1 0 0 1 0 0 0
0 1 1 1 0 0 1 0
C8h
72h

Ejecutamos la instrucción:

LD DE,(#6789):
1 1 1 0 1 1 0 1
0 1 0 1 1 0 1 1
1 0 0 0 1 0 0 1
0 1 1 0 0 1 1 1
EDh
5Bh
89h
67h

Contenido de "DE" después de la ejecución:

(D):
(E):
0 1 1 1 0 0 1 0
1 1 0 0 1 0 0 0
72h
C8h

Observe que con este código de máquina se puede codificar también la instrucción LD HL,(nn), sólo que ocuparía 4 octetos en lugar de 3. Cualquier ensamblador codificaría el código que menos octetos ocupase.

LD IX,(nn)
OBJETO:
Carga, en la parte de orden inferior del registro índice "IX", el octeto direccionado por "nn" y en la parte de orden superior, el octeto direccionado por "nn+1".
CODIGO MAQUINA:
1 1 0 1 1 1 0 1
0 0 1 0 1 0 1 0
<-------- n -------->
<-------- n -------->
DDh
2Ah
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
8(6?)
CICLOS DE RELOJ:
20

EJEMPLO:

LD IX,(#46F0)

En este caso, vamos a hacer lo mismo que en ejemplos anteriores, pero cargando el índice "IX". Suponemos que vamos a cargar el número BBAAh (48042).

Situación del número en memoria:

(46F0h):
(46F1h):
1 0 1 0 1 0 1 0
1 0 1 1 1 0 1 1
AAh
BBh

Ejecutamos la instrucción:

LD IX,(#46F0):
1 1 0 1 1 1 0 1
0 0 1 0 1 0 1 0
1 1 1 1 0 0 0 0
0 1 0 0 0 1 1 0
DDh
2Ah
F0h
46h

Contenido del índice "IX" después de la ejecución:

(IX):
1 0 1 1 1 0 1 1
1 0 1 0 1 0 1 0
BBh
AAh
LD IY,(nn)
OBJETO:
Carga, en la parte de orden inferior del registro índice "IY", el octeto de memoria direccionado por "nn" y en la parte de orden superior, el octeto de memoria direccionado por "nn+1".
CODIGO MAQUINA:
1 1 1 1 1 1 0 1
0 0 1 0 1 0 1 0
<-------- n -------->
<-------- n -------->
FDh
2Ah
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
6
CICLOS DE RELOJ:
20

EJEMPLO:

GRUPO EQU #AAEE
LD IY,(GRUPO)

De nuevo, utilizamos una etiqueta. Llamamos "GRUPO" al número AAEEh. A partir de ese momento, cada vez que pongamos en un operando la palabra "GRUPO", el ensamblador considerará que nos referimos a este número. La instrucción podía haberse escrito también como:

LD IY,(#AAEE)

El número que queremos cargar en "IY" es el 35BFh (13759), que está contenido en las posiciones de memoria AAEEh y AAEFh.

Situación del número en memoria:

(AAEEh):
(AAEFh):
1 0 1 1 1 1 1 1
0 0 1 1 0 1 0 1
BFh
35h

Ejecutamos la instrucción:

LD IY,(#AAEE):
1 1 1 1 1 1 0 1
0 0 1 0 1 0 1 0
1 1 1 0 1 1 1 0
1 0 1 0 1 0 1 0
FDh
2Ah
EEh
AAh

Contenido del índice "IY" después de la ejecución:

(IY):
0 0 1 1 0 1 0 1
1 0 1 1 1 1 1 1
35h
BFh
Grupo de instrucciones de carga en memoria, 16 bits
LD (nn),HL
OBJETO:
Carga en la dirección de memoria "nn" el contenido del registro "L" y en la dirección de memoria "nn+1", el contenido del registro "H".
CODIGO MAQUINA:
0 0 1 0 0 0 1 0
<-------- n -------->
<-------- n -------->
22h
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
5
CICLOS DE RELOJ:
16

EJEMPLO:

LD (#45FD),HL

Este grupo de instrucciones es el opuesto al visto anteriormente. De la misma forma que antes teníamos los números en memoria con el orden de los octetos invertidos, esta vez, el microprocesador se encarga de almacenarlos también, con el orden invertido. Esto hace que los dos grupos de instrucciones sean totalmente compatibles.

En otras instrucciones, también apreciaremos esta particuladidad; como regla general, podemos decir que todos los números de dos bytes que se almacenan en la memoria, deberán guardarse con el orden de sus octetos invertidos (primero el menos significativo y luego el más significativo). He aquí la razón última de porqué las Variables del Sistema tienen este formato.

En este ejemplo concreto, vamos a guardar en la dirección 45FDh y siguiente, el contenido del par "HL", que suponemos, es de AABBh.

Contenido del par de registros "HL":

(H):
(L):
1 0 1 0 1 0 1 0
1 0 1 1 1 0 1 1
AAh
BBh

Ejecutamos la instrucción:

LD (#45FD),HL:
0 0 1 0 0 0 1 0
1 1 1 1 1 1 0 1
0 1 0 0 0 1 0 1
22h
FDh
45h

Situación del número en memoria, después de la ejecución:

(45FDh):
(45FEh):
1 0 1 1 1 0 1 1
1 0 1 0 1 0 1 0
BBh
AAh
LD (nn),dd
OBJETO:
Carga en la posición de memoria "nn", el octeto de orden inferior del par de registros indicados por "dd", y en la posición de memoria "nn+1" el octeto de orden superior.
CODIGO MAQUINA:
1 1 1 0 1 1 0 1
0 1 d d 0 0 1 1
<-------- n -------->
<-------- n -------->
EDh
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
6
CICLOS DE RELOJ:
20

EJEMPLO:

TODO EQU #45FA
LD (TODO),BC

La palabra "TODO", es en este caso, una etiqueta, que sustituye al número 45FAh que es la dirección donde queremos almacenar el contenido del par "BC". Suponemos que este contenido es, por ejemplo, F00Fh.

Contenido del par "BC":

(B):
(C):
1 1 1 1 0 0 0 0
0 0 0 0 1 1 1 1
F0h
0Fh

Ejecutamos la instrucción:

LD (#45FA),BC:
1 1 1 0 1 1 0 1
0 1 0 0 0 0 1 1
1 1 1 1 1 0 1 0
0 1 0 0 0 1 0 1
EDh
43h
FAh
45h

Contenido de las posiciones afectadas por la instrucción:

(TODO):
(TODO+1):
0 0 0 0 1 1 1 1
1 1 1 1 0 0 0 0
0Fh
F0h

Recuerde que la palabra "TODO" es una etiqueta que equivale al número 45FAh.

LD (nn),IX
OBJETO:
Carga en la posición "nn" de memoria, el octeto de orden inferior del registro índice "IX" y en la posición "nn+1", el octeto de order superior.
CODIGO MAQUINA:
1 1 0 1 1 1 0 1
0 0 1 0 0 0 1 0
<-------- n -------->
<-------- n -------->
DDh
22h
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
6
CICLOS DE RELOJ:
20

EJEMPLO:

LD (#4527),IX

Esta instrucción, carga el octeto de orden bajo del registro "IX", en la dirección 4527h, y el octeto de orden alto en la siguiente.

Suponemos, como ejemplo, que el registro "IX" contiene el número C3ECh.

Contenido de "IX":

(IX) MSB:
(IX) LSB:
1 1 0 0 0 0 1 1
1 1 1 0 1 1 0 0
C3h
ECh

Ejecutamos la instrucción:

LD (#4527),IX:
1 1 0 1 1 1 0 1
0 0 1 0 0 0 1 0
0 0 1 0 0 1 1 1
0 1 0 0 0 1 0 1
DDh
22h
27h
45h

Situación de la memoria después de la instrucción:

(4527h):
(4528h):
1 1 1 0 1 1 0 0
1 1 0 0 0 0 1 1
ECh
C3h
LD (nn),IY
OBJETO:
Carga en la dirección "nn" de memoria, el octeto de orden inferior del registro índice "IY" y en la dirección "nn+1", el de orden superior.
CODIGO MAQUINA:
1 1 1 1 1 1 0 1
0 0 1 0 0 0 1 0
<-------- n -------->
<-------- n -------->
FDh
22h
LSB
MSB
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
6
CICLOS DE RELOJ:
20

EJEMPLO:

INDICE EQU #774f
LD (INDICE),IY

Este ejemplo es igual que el anterior, pero esta vez, hemos utilizado una etiqueta para referirnos al número 774Fh. Suponemos que el índice "IY", contiene DA5Dh.

Contenido de "IY":

(IY) MSB:
(IY) LSB:
1 1 0 1 1 0 1 0
0 1 0 1 1 1 0 1
DAh
5Dh

Ejecutamos la instrucción:

LD (#774f),IY:
1 1 1 1 1 1 0 1
0 0 1 0 0 0 1 0
0 1 0 0 1 1 1 1
0 1 1 1 0 1 1 1
FDh
22h
4Fh
77h

Contenido de la memoria después de la ejecución:

(774Fh):
(7750h):
0 1 0 1 1 1 0 1
1 1 0 1 1 0 1 0
5Dh
DAh
Grupo de instrucciones de carga en registro SP
LD SP,HL
OBJETO:
Carga en el registro puntero de pila "SP", el contenido del par de registros "HL".
CODIGO MAQUINA:
1 1 1 1 1 0 0 1
F9h
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
1
CICLOS DE RELOJ:
6

EJEMPLO:

LD SP,HL

Se trata de una instrucción rápida y que ocupa un solo byte de memoria. El microprocesador sólo accede a memoria una vez, para leer el código de operación y se limita a realizar una transferencia interna entre registros.

Supongamos que el contenido de "HL" es F000h, éste será el número que se transferirá al puntero de pila y que será la nueva dirección de la pila de máquina.

Contenido de "HL":

(H):
(L):
1 1 1 1 0 0 0 0
0 0 0 0 0 0 0 0
F0h
00h

Ejecutamos la instrucción:

LD SP,HL:
1 1 1 1 1 0 0 1
F9h

Contenido de "SP" después de la ejecución:

(SP) MSB:
(SP) LSB:
1 1 1 1 0 0 0 0
0 0 0 0 0 0 0 0
F0h
00h

Hay que tener sumo cuidado cuando se ejecute esta instrucción, ya que el puntero de pila cambia de lugar y no será posible recuperar los datos que estuvieran guardados en la pila antigua.

LD SP,IX
OBJETO:
Carga en el registro puntero de pila, "SP", el contenido del registro índice "IX".
CODIGO MAQUINA:
1 1 0 1 1 1 0 1
1 1 1 1 1 0 0 1
DDh
F9h
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
10

EJEMPLO:

LD SP,IX

Otra vez, se trata de una simple transferencia de registros desde el índice "IX" al puntero de pila "SP".

Supongamos que "IX" contiene C6E1h.

Contenido de "IX":

(IX):
1 1 0 0 0 1 1 0 1 1 1 0 0 0 0 1
C6E1h

Ejecutamos la instrucción:

LD SP,IX:
1 1 0 1 1 1 0 1
1 1 1 1 1 0 0 1
DDh
F9h

Contenido de "SP" después de la ejecución:

(SP):
1 1 0 0 0 1 1 0 1 1 1 0 0 0 0 1
C6E1h
LD SP,IY
OBJETO:
Carga en el registro puntero de pila, "SP", el contenido del registro índice "IY".
CODIGO MAQUINA:
1 1 1 1 1 1 0 1
1 1 1 1 1 0 0 1
FDh
F9h
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
2
CICLOS DE RELOJ:
10

EJEMPLO:

LD SP,IY

Exactamente igual que el ejemplo anterior, pero con el índice "IY". Suponemos que su contenido es 8D21h.

Contenido del registro "IY":

(IY):
1 0 0 0 1 1 0 1 0 0 1 0 0 0 0 1
8D21h

Ejecutamos la instrucción:

LD SP,IY:
1 1 1 1 1 1 0 1
1 1 1 1 1 0 0 1
FDh
F9h

Contenido del registro "SP" después de la ejecución:

(SP):
1 0 0 0 1 1 0 1 0 0 1 0 0 0 0 1
8D21h
Grupo de instrucciones de manejo de pila

Una pila es una cola LIFO (last input first output), último en entrar primero en salir. El termino pila es de uso habitual, se apilan cajas, revistas, etc. Pues bien, una pila en términos informáticos funciona igual, por ejemplo: una persona se compra todos los meses una revista, es fácil que las ordene en una pila; es decir, irá poniendo una encima de la anterior, de tal forma que la última colocada siempre estaría más al alcance.

De la misma manera, en un ordenador se pueden ir guardando en una tabla en memoria, mejor denominada cola, una serie de octetos, u en una palabra de control de dos octetos se guardaría la última dirección usada de la tabla, de forma que: para meter un nuevo octeto se sumaría uno a la palabra de control de tabla y se cargaría el octeto en esa dirección; para sacar un octeto se leería el octeto direccionado por la palabra de control y se le restaría uno a ésta.

Eso es lo que se pretende con las instrucciones que siguen, las cuales utilizan el registro puntero de pila "SP".

Para identificar los pares de registros usaremos el siguiente código:

qq
par
00
BC
01
DE
10
HL
11
AF

En el Spectrum, la pila se coloca en la parte alta de memoria, el sistema operativo la sitúa inmediatamente debajo de RAMTOP, durante la rutina de inicialización. Esto lo hace, cargando el registro "SP" con la dirección inmediatamente inferior a la de RAMTOP.

Cada vez que utilicemos la instrucción PUSH, meteremos en la pila el contenido de un par de registros y cada vez que utilicemos la instrucción POP, sacaremos el dato más alto de la pila y lo asignaremos a un par de registros.

Nuestra pila se expande "hacia abajo", lo cual quiere decir que cuando hablemos de "la parte superior de la pila", en realidad, nos estaremos refiriendo a la dirección más baja de ésta.

Por otro lado, todos los datos que se almacenan en la pila, tienen dos bytes de longitud, por lo cual, el registro "SP" se incrementa o se decrementa de 2 en 2.

El proceso de introducir el contenido de un par de registros en la pila, conlleva las siguientes operaciones:

  1. Se decrementa "SP".

  2. Se transfiere el octeto de orden alto del par de registros correspondiente a la dirección apuntada por "SP".

  3. Se vuelve a decrementar "SP".

  4. Se transfiere el octeto de orden bajo del par correspondiente a la dirección apuntada por "SP".

El proceso de sacar un número de la pila, implica que el microprocesador realice las mismas operaciones a la inversa:

  1. Se toma el contenido de la dirección apuntada por "SP" y se carga en el octeto de orden bajo del registro correspondiente.

  2. Se incrementa "SP".

  3. Se toma el contenido de la dirección apuntada ahora por "SP" y se carga en el octeto de orden alto del registro correspondiente.

  4. Se vuelve a incrementar "SP".

Algunos microprocesadores trabajan con dos pilas, una se denomina "pila de máquina" y otra "pila de usuario". La pila de máquina la utiliza el microprocesador para introducir sus datos y la pila de usuario, es la que el programador puede utilizar.

En el Z-80 no existe "pila de usuario", de forma que el programador debe usar la misma pila que la máquina. Esto lleva aparejados ciertos inconvenientes, así que vamos a ver para qué utiliza la máquina esta pila.

Cada vez que el microprocesador recibe una instrucción que le haga saltar a una subrutina, almacena en la pila la dirección a la que debe retornar cuando termine esa rutina. Por tanto, siempre que dentro de una subrutina utilicemos la pila, deberemos asegurarnos de sacar todos los datos que hayamos introducido antes de intentar retornar, ya que de lo contrario, el microprocesador tomaría nuestro último dato como dirección de retorno; si esto ocurriera, se diría que nuestra subrutina "corrompe la pila". Es imposible retornar con éxito desde una subrutina que corrompa la pila, por lo que hay que procurar que esto nunca ocurra.

A continuación, vamos a ver las instrucciones que puede utilizar el programador para trabajar sobre la pila.

PUSH qq
OBJETO:
Introducir el contenido del par de registros indicado por "qq" en la pila apuntada por el registro "SP". Esta instrucción ejecuta los siguientes pasos: decrementa el valor del registro SP y carga el octeto de orden superior del par de registros indicado por "qq" en la dirección especificada por "SP"; a continuación vuelve a decrementar el registro "SP" y carga el octeto de orden inferior.
CODIGO MAQUINA:
1 1 q q 0 1 0 1
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
3
CICLOS DE RELOJ:
11

EJEMPLO:

PUSH HL

Supongamos que el par "HL" contiene el número AAB5h y que el puntero de pila "SP", apunta a la dirección 4B89h, que será la del último dato introducido en la pila.

Contenido de "HL":

(H):
(L):
1 0 1 0 1 0 1 0
1 0 1 1 0 1 0 1
AAh
B5h

Contenido del puntero "SP":

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 1 0 0 1
4B89h

Ejecutamos la instrucción:

PUSH HL:
1 1 1 0 0 1 0 1
E5h

Contenido de "SP" después de la ejecución:

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 0 1 1 1
4B87h

Contenido de la pila

(4B87h):
(4B88h):
1 0 1 1 0 1 0 1
1 0 1 0 1 0 1 0
B5h
AAh
PUSH IX
OBJETO:
Introducir el contenido del registro índice "IX" en la pila apuntada por el registro "SP". Esta instrucción ejecuta los siguientes pasos: decrementa el valor del registro "SP" y carga el octeto de orden superior del registro "IX" en la dirección especificada por "SP", a continuación, vuelve a decrementar el registro "SP" y carga el octeto de orden inferior.
CODIGO MAQUINA:
1 1 0 1 1 1 0 1
1 1 1 0 0 1 0 1
DDh
E5h
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
4
CICLOS DE RELOJ:
15

EJEMPLO:

PUSH IX

Supongamos qeu el índice "IX" contiene EEF1h y "SP" está como lo dejamos después de la instrucción anterior, es decir apuntando a 4B87h

Contenido de "IX":

(IX):
1 1 1 0 1 1 1 0 1 1 1 1 0 0 0 1
EEF1h

Contenido de "SP":

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 0 1 1 1
4B87h

Ejecutamos la instrucción:

PUSH IX:
1 1 0 1 1 1 0 1
1 1 1 0 0 1 0 1
DDh
E5h

Contenido de "SP" tras la ejecución:

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 0 1 0 1
4B85h

Contenido de la pila

(4B85h):
(4B86h):
1 1 1 1 0 0 0 1
1 1 1 0 1 1 1 0
F1h
EEh
PUSH IY
OBJETO:
Introducir el contenido del registro índice "IY" en la pila apuntada por el registro "SP". Esta instrucción ejecuta los siguientes pasos: decrementa el valor del registro "SP" y carga el octeto de orden superior del registro "IY" en la dirección especificada por "SP", a continuación, vuelve a decrementar el registro "SP" y carga el octeto de orden inferior.
CODIGO MAQUINA:
1 1 1 1 1 1 0 1
1 1 1 0 0 1 0 1
FDh
E5h
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
4
CICLOS DE RELOJ:
15

EJEMPLO:

PUSH IY

Contenido de "IY":

(IY):
1 0 0 0 0 1 1 0 1 1 1 1 1 1 1 1
86FFh

Contenido de "SP":

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 0 1 0 1
4B85h

Ejecutamos la instrucción:

PUSH IY:
1 1 1 1 1 1 0 1
1 1 1 0 0 1 0 1
FDh
E5h

Contenido de "SP" después de la ejecución:

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 0 0 1 1
4B83h

Contenido de la pila

(4B83h):
(4B84h):
1 1 1 1 1 1 1 1
1 0 0 0 0 1 1 0
FFh
86h

Contenido de la pila después de las ejecuciones anteriores

(4B83h):
(4B84h):
(4B85h):
(4B86h):
(4B87h):
(4B88h):
1 1 1 1 1 1 1 1
1 0 0 0 0 1 1 0
1 1 1 1 0 0 0 1
1 1 1 0 1 1 1 0
1 0 1 1 0 1 0 1
1 0 1 0 1 0 1 0
FFh
86h
F1h
EEh
B5h
AAh
POP qq
OBJETO:
Introducir en el par de registros indicado por "qq", los dos primeros octetos de la pila apuntada por el registro "SP". Esta instrucción ejecuta los siguientes pasos: carga en la parte inferior del par de registros indicado por "qq", el octeto de la dirección especificada por el registro "SP"; incrementa el registro "SP" y carga el siguiente octeto direccionado, en la parte superior del par de registros; por último, vuelve a incrementar el registro "SP".
CODIGO MAQUINA:
1 1 q q 0 0 0 1
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
3
CICLOS DE RELOJ:
10

EJEMPLO:

POP HL

Suponemos que tenemos la pila y el registro puntero "SP", como quedó tras los ejemplos anteriores. Ahora, vamos a ir recuperando los datos desde la pila.

Contenido de "SP":

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 0 0 1 1
4B83h

Contenido de la pila, como quedó tras los ejemplos anteriores:

FF 86 F1 EE B5 AA

Ejecutamos la instrucción:

POP HL:
1 1 1 0 0 0 0 1
E1h

Contenido de "SP" después de la ejecución:

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 0 1 0 1
4B85h

Contenido de "HL" después de la ejecución:

(H):
(L):
1 0 0 0 0 1 1 0
1 1 1 1 1 1 1 1
86h
FFh
POP IX
OBJETO:
Introducir en el registro índice "IX", los dos primeros octetos de la pila apuntada por el registro "SP". Esta instrucción ejecuta los siguientes pasos: carga en la parte inferior del registro índice "IX", el octeto de la dirección especificada por el registro "SP"; incrementa el registro "SP" y carga el siguiente octeto direccionado, en la parte superior del registro índice; por último, vuelve a incrementar el registro "SP"
CODIGO MAQUINA:
1 1 0 1 1 1 0 1
1 1 1 0 0 0 0 1
DDh
E1h
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
4
CICLOS DE RELOJ:
14

EJEMPLO:

POP IX

Continuamos recuperando datos desde la pila, suponemos que la seguimos teniendo como estaba tras el ejemplo anterior.

Contenido de "SP":

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 0 1 0 1
4B85h

Contenido de la pila, como queda en el ejemplo anterior:

F1 EE B5 AA

Ejecutamos la instrucción:

POP IX:
1 1 0 1 1 1 0 1
1 1 1 0 0 0 0 1
DDh
E1h

Contenido de "SP" después de la ejecución:

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 0 1 1 1
4B87h

Contenido de "IX" después de la ejecución:

(IX):
1 1 1 0 1 1 1 0 1 1 1 1 0 0 0 1
EEF1h
POP IY
OBJETO:
Introducir en el registro índice "IY", los dos primeros octetos de la pila apuntada por el registro "SP". Esta instrucción ejecuta los siguientes pasos: carga en la parte inferior del registro índice "IY", el octeto de la dirección especificada por el registro "SP"; incrementa el registro "SP" y carga el siguiente octeto direccionado, en la parte superior del registro índice; por último, vuelve a incrementar el registro "SP"
CODIGO MAQUINA:
1 1 1 1 1 1 0 1
1 1 1 0 0 0 0 1
FDh
E1h
INDICADORES DE CONDICIÓN A LOS QUE AFECTA:
Ninguno.
CICLOS DE MEMORIA:
4
CICLOS DE RELOJ:
14

EJEMPLO:

POP IY

Vamos a extraer el último dato de la pila, esta vez, lo cargaremos en el registro "IY".

Contenido de "SP":

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 0 1 1 1
4B87h

Contenido de la pila, como quedó en el ejemplo anterior:

B5 AA

Ejecutamos la instrucción:

POP IY:
1 1 1 1 1 1 0 1
1 1 1 0 0 0 0 1
FDh
E1h

Contenido de "SP" después de la ejecución:

(SP):
0 1 0 0 1 0 1 1 1 0 0 0 1 0 0 1
4B89h

Contenido de "IY" después de la ejecución:

(IY):
1 0 1 0 1 0 1 0 1 0 1 1 0 1 0 1
AAB5h

Observe que la secuencia de instrucciones de los seis últimos ejemplos da como resultado el siguiente intercambio de registros:

IY
=>
HL
IX
=>
IX
HL
=>
IY

El uso principal de las instrucciones PUSH (empujar) y POP (explotar) --la traducción al castellano no tiene un significado muy completo--, es salvar el contenido de los registros para poder usarlos y después recuperar sus valores. Esto es muy útil en el empleo de sub-rutinas.

EJEMPLO:

Una sub-rutina que quiera usar los registros BC, DE y HL sin variar su contenido, comenzaría:

PUSH BC
PUSH DE
PUSH HL

Y terminaría:

POP HL
POP DE
POP BC

Observe cómo se recupera al revés de cómo se salvó, es decir, el primer registro que se recupera, es el último que se salvó. Recuerde que debe sacar de la pila todo lo que metió, antes de intentar retornar desde una subrutina.

Una mirada gráfica a la pila

Para quien no esté familiarizado con los ordenadores, el funcionamiento de una pila, puede resultar algo dificil de comprender. Haciendo cierto el refrán "una imagen vale más que mil palabras", vamos a ver de un modo gráfico, lo que ocurre en la pila y en los registros correspondientes, durante la ejecución de las anteriores instrucciones.

Miremos la FIGURA 5-1A, que representa al situación inicial de la que partimos. A la izquierda de la figura, vemos cuatro "ventanas" etiquetadas: "HL", "IX", "IY" y "SP"; se trata de una representación gráfica de los registros del microprocesador.

[Figura 5-1A]
Figura 5-1A. Situación inicial de registros y posiciones de memoria.

Cada ventana muestra un número hexadecimal, que representa el contenido del registro correspondiente, por ejemplo, el registro "HL" contiene AAB5h, el "IX" contiene EEF1h, etc.

El registro "SP" contiene 4B89h, que es la dirección de memoria a partir de donde crecerá la pila.

En la parte derecha de la figura, vemos una representación gráfica de la zona de memoria donde está situada la pila. A la derecha de cada casilla está su dirección y dentro de la casilla, su contenido hexadecimal. En principio, todas las casillas contienen "XX", lo que significa que su verdadero contenido nos es indiferente.

Vemos un cuadradito con las letras "SP" dentro de él; este cuadrado, apunta a la casilla cuya dirección es precisamente, el contenido del registro "SP"; de esta forma, nos indica cuál es el último dato introducido en la pila, es decir, el primero que podemos leer.

Partiendo de la situación que muestra esta figura, vamos a ejecutar la primera de nuestras instrucciones:

PUSH HL

Esta instrucción debe guardar en la pila, el contenido del par de registros "HL"; el registro "SP" se decrementará dos veces, y por tanto, el cuadradito que apunta a la memoria bajará dos casillas.

En la FIGURA 5-1B, podemos ver la situación después de que esta instrucción haya sido ejecutada. El registro "HL" contiene el mismo valor que antes, ya que éste ha sido copiado en la pila, pero no se ha destruido. Vemos que la dirección 4B88h contiene el número AAh y la dirección 4B87h, el número B5h, por tanto, las dos juntas componen el número AAB5h que es, precisamente, el contenido de "HL" que queríamos preservar. Por otro lado, vemos que el cuadradito (a partir de ahora, lo llamaremos puntero) ha bajado dos casillas, precisamente, para apuntar al último dato introducido.

[Figura 5-1B]
Figura 5-1B. Contenidos, después de ejecutar la instrucción: «PUSH HL».

Si ahora utilizáramos la instrucción POP para recuperar un dato de la pila, sería precisamente este dato el que podríamos recuperar.

Vamos con la segunda de nuestras instrucciones:

PUSH IX

En este caso, vamos a guardar en la pila el contenido del registro "IX"; sin por ello perder el dato que habíamos guardado anteriormente.

En la FIGURA 5-1C, se puede ver cómo quedan los contenidos después de esta última instrucción. La posición de memoria 4B86h contiene el número EEh, y la 4B85h el número F1h; juntos forman EEF1h, que es, de nuevo, el contenido que queríamos preservar. El puntero (cuadradito) ha vuelto a bajar dos casillas, para apuntar, de nuevo, al último dato introducido.

[Figura 5-1C]
Figura 5-1C. Contenidos, después de ejecutar la instrucción: «PUSH IX».

Vamos ahora, a meter en la pila el último de nuestros datos: el contenido del registro "IY".

PUSH IY

Con esta instrucción, entra en la pila el número 86FFh. En la FIGURA 5-1D, podemos ver, de nuevo, cómo queda la pila después de esta instrucción. Ahora el puntero ha bajado a la casilla 4B83h, con lo que otra vez, apunta al último dato introducido.

[Figura 5-1D]
Figura 5-1D. Contenidos, después de ejecutar la instrucción: «PUSH IY».

Podríamos seguir metiendo datos en la pila indefinidamente, hasta que agotáramos la memoria disponible, pero con estos tres ejemplos, ya hemos visto el proceso de expansión de la pila. Vamos a ver ahora, el proceso inverso: sacar datos de la pila.

Nuestra primera instrucción será:

POP HL

Que toma el último dato que hayamos introducido en la pila y lo coloca dentro del registro "HL".

En la FIGURA 5-1E, podemos ver cómo quedan pila y registros, después de esta instrucción. El último dato introducido en la pila (86FFh) ha pasado a ser el contenido del registro "HL" y el puntero ha subido dos casillas, para apuntar al dato anteriormente introducido.

[Figura 5-1E]
Figura 5-1E. Contenidos, después de ejecutar la instrucción: «POP HL».

Una observación interesante, es que el contenido de las casillas que componen la pila, no se ha modificado; el número 86FFh sigue estando ahí, aunque a nosotros nos da igual, recuérdese que sólo podemos acceder cada vez, al dato señalado por el puntero; las casillas 4B84h y 4B83h que contienen el dato 86FFh, sólo se borrarán totalmente cuando la pila vuelva a expandirse.

Ahora, vamos a recuperar el siguiente dato de la pila y lo asignaremos al registro "IX":

POP IX

Esta instrucción toma el último dato de la pila y lo asigna al registro "IX"; recuérdese que el último dato, es siempre el que es apuntado por el puntero. Recuérdese también, que la dirección de la casilla apuntada por el puntero es el contenido del registro "SP"; las letras "S" y "P", son las iniciales de "Stack Pointer", en inglés, "Puntero de Pila".

La FIGURA 5-1F, muestra los contenidos después de ejecurar la instrucción "POP IX", vemos otra vez que los datos de la pila no se han perdido, pero para nosotros no existen, ya que el puntero ha vuelto a subir dos casillas y ahora se considera que el último dato de la pila es AAB5h.

[Figura 5-1F]
Figura 5-1F. Contenidos, después de ejecutar la instrucción: «POP IX».

Vamos a recuperar, por último, este dato:

POP IY

Esta instrucción, toma el último dato de la pila y lo asigna al registro "IY".

En la FIGURA 5-1G, vemos la situación final, el dato AAB5h ha sido asignado al registro "IY" y el puntero se ha vuelto a incrementar dos veces, para apuntar al mismo sitio que lo hacía al principio de estos ejemplos.

[Figura 5-1G]
Figura 5-1G. Contenidos, después de ejecutar la instrucción: «POP IY».

Ya hemos sacado de la pila todos los datos que habíamos introducido. El puntero ha quedado en la misma posición donde estaba al principio. Si nuestro ejemplo fuese una subrutina de un programa, en la dirección donde apunta ahora el puntero se encontraría almacenada la dirección de retorno y ahora sería posible retornar al punto desde donde se llamó a esta subrutina.

Con la pila se pueden hacer muchas cosas. Supongamos que en Basic queremos intercambiar el contenido de dos variables "a" y "b"; en ese caso, necesitamos generar una tercera variable que nos sirve de "puente", de la forma:

10 LET c=a
20 LET a=b
30 LET b=c

Trabajando en código máquina, podemos intercambiar el contenido de dos registros, utilizando la pila en lugar de la variable "puente" del Basic, supongamos que queremos intercambiar los contenidos de los registros "BC" y "DE"; podríamos hacer:

PUSH BC
PUSH DE
POP BC
POP DE

Por supuesto, esto es sólo un ejemplo, no se quedan aquí las utilidades de la pila. Su función principal es la de salvar temporalmente, el contenido de algún registro, mientras se utiliza para algo y luego, restituirle, de nuevo su contenido. No obstante, con la pila se pueden hacer muchas más cosas, en capítulos posteriores veremos cómo utiliza la pila el intérprete de Basic, para poder retornar desde cualquier punto en caso de error.

Tablas de codificación

A continuación, vamos a ver una serie de tablas que nos han de servir para codificar las instrucciones rápidamente, cuando ensamblemos a mano.

En las tablas se ha representado el código máquina de cada instrucción, tanto en decimal como en binario.

Cuando el código máquina ocupa más de un byte, se han puesto uno a continuación del otro, separados por comas.

Donde pone "d", se entiende que ese byte va ocupado por un entero de despazamiento en complemento a dos.

Donde pone "n", debe ir el operando "n" que aparece en el código fuente de la instrucción.

Donde aparecen dos bytes seguidos con "n", debe ir el operando "nn" del código fuente de la instrucción; primero irá el octeto menos significativo y luego el más significativo; por ejemplo: supongamos que el operando "nn" fuera 2A4Bh, primero iría 4Bh y luego 2Ah.

En el apartado de ejemplos, veremos con claridad la forma de ensamblar a mano pequeños programas.

La disposición de las tablas es la siguiente:

Grupo de carga en registros:
FIG 5-2
Grupo de carga en memoria:
FIG 5-3
Grupo de carga en acumulador:
FIG 5-4
Grupo de salvar acumulador:
FIG 5-5
Grupo de carga en registros, de 16 bits:
FIG 5-6
Grupo de carga en memoria, de 16 bits:
FIG 5-7
Grupo de carga en registro "SP":
FIG 5-8
Grupo de manejo de pila:
FIG 5-9

Código Fuente
Hexadecimal
Decimal
LD A,A
7F
127
LD A,B
78
120
LD A,C
79
121
LD A,D
7A
122
LD A,E
7B
123
LD A,H
7C
124
LD A,L
7D
125
LD B,A
47
71
LD B,B
40
64
LD B,C
41
65
LD B,D
42
66
LD B,E
43
67
LD B,H
44
68
LD B,L
45
69
LD C,A
4F
79
LD C,B
48
72
LD C,C
49
73
LD C,D
4A
74
LD C,E
4B
75
LD C,H
4C
76
LD C,L
4D
77
LD D,A
57
87
LD D,B
50
80
LD D,C
51
81
LD D,D
52
82
LD D,E
53
83
LD D,H
54
84
LD D,L
55
85
LD E,A
5F
95
LD E,B
58
88
LD E,C
59
89
LD E,D
5A
90
LD E,E
5B
91
LD E,H
5C
92
LD E,L
5D
93
LD H,A
67
103
LD H,B
60
96
LD H,C
61
97
LD H,D
62
98
LD H,E
63
99
LD H,H
64
100
LD H,L
65
101
LD L,A
6F
111
LD L,B
68
104
LD L,C
69
105
LD L,D
6A
106
LD L,E
6B
107
LD L,H
6C
108
LD L,L
6D
109
LD A,n
3E,n
62,n
LD B,n
06,n
6,n
LD C,n
0E,n
14,n
LD D,n
16,n
22,n
LD E,n
1E,n
30,n
LD H,n
26,n
38,n
LD L,n
2E,n
46,n
LD A,(HL)
7E
126
LD B,(HL)
46
70
LD C,(HL)
4E
78
LD D,(HL)
56
86
LD E,(HL)
5E
94
LD H,(HL)
66
102
LD L,(HL)
6E
110
LD A,(IX+d)
DD,7E,d
221,126,d
LD B,(IX+d)
DD,46,d
221,70,d
LD C,(IX+d)
DD,4E,d
221,78,d
LD D,(IX+d)
DD,56,d
221,86,d
LD E,(IX+d)
DD,5E,d
221,94,d
LD H,(IX+d)
DD,66,d
221,102,d
LD L,(IX+d)
DD,6E,d
221,110,d
LD A,(IY+d)
FD,7E,d
253,126,d
LD B,(IY+d)
FD,46,d
253,70,d
LD C,(IY+d)
FD,4E,d
253,78,d
LD D,(IY+d)
FD,56,d
253,86,d
LD E,(IY+d)
FD,5E,d
253,94,d
LD H,(IY+d)
FD,66,d
253,102,d
LD L,(IY+d)
FD,6E,d
253,110,d
Fig. 5-2. Grupo de carga en registros.

Código Fuente
Hexadecimal
Decimal
LD (HL),A
77
119
LD (HL),B
70
112
LD (HL),C
71
113
LD (HL),D
72
114
LD (HL),E
73
115
LD (HL),H
74
116
LD (HL),L
75
117
LD (IX+d),A
DD,77,d
221,119,d
LD (IX+d),B
DD,70,d
221,112,d
LD (IX+d),C
DD,71,d
221,113,d
LD (IX+d),D
DD,72,d
221,114,d
LD (IX+d),E
DD,73,d
221,115,d
LD (IX+d),H
DD,74,d
221,116,d
LD (IX+d),L
DD,75,d
221,117,d
LD (IY+d),A
FD,77,d
253,119,d
LD (IY+d),B
FD,70,d
253,112,d
LD (IY+d),C
FD,71,d
253,113,d
LD (IY+d),D
FD,72,d
253,114,d
LD (IY+d),E
FD,73,d
253,115,d
LD (IY+d),H
FD,74,d
253,116,d
LD (IY+d),L
FD,75,d
253,117,d
LD (HL),n
36,n
54,n
LD (IX+d),n
DD,36,d
221,54,d
LD (IY+d),n
FD,36,d
253,54,d
Fig. 5-3. Grupo de carga en memoria.

Código Fuente
Hexadecimal
Decimal
LD A,(BC)
0A
10
LD A,(DE)
1A
26
LD A,(nn)
3A,n,n
58,n,n
LD A,I
ED,57
237,87
LD A,R
ED,5F
237,95
Fig. 5-4. Grupo de carga en acumulador.

Código Fuente
Hexadecimal
Decimal
LD (BC),A
02
2
LD (DE),A
12
18
LD (nn),A
32,n,n
50,n,n
LD I,A
ED,47
237,71
LD R,A
ED,4F
237,79
Fig. 5-5. Grupo de salvar acumulador.

Código Fuente
Hexadecimal
Decimal
LD BC,nn
01,n,n
1,n,n
LD DE,nn
11,n,n
17,n,n
LD HL,nn
21,n,n
33,n,n
LD SP,nn
31,n,n
49,n,n
LD IX,nn
DD,21,n,n
221,33,n,n
LD IY,nn
FD,21,n,n
253,33,n,n
LD HL,(nn)
2A,n,n
42,n,n
LD BC,(nn)
ED,4B,n,n
237,75,n,n
LD DE,(nn)
ED,5B,n,n
237,91,n,n
LD SP,(nn)
ED,7B,n,n
237,123,n,n
LD IX,(nn)
DD,2A,n,n
221,42,n,n
LD IY,(nn)
FD,2A,n,n
253,42,n,n
Fig. 5-6. Grupo de carga en registros de 16 bits.

Código Fuente
Hexadecimal
Decimal
LD (nn),HL
22,n,n
34,n,n
LD (nn),BC
ED,43,n,n
237,67,n,n
LD (nn),DE
ED,53,n,n
237,83,n,n
LD (nn),SP
ED,73,n,n
237,115,n,n
LD (nn),IX
DD,22,n,n
221,34,n,n
LD (nn),IY
FD,22,n,n
253,34,n,n
Fig. 5-7. Grupo de carga en memoria de 16 bits.

Código Fuente
Hexadecimal
Decimal
LD SP,HL
F9
249
LD SP,IX
DD,F9
221,249
LD SP,IY
FD,F9
253,249
Fig. 5-8. Grupo de carga de registro «SP».

Código Fuente
Hexadecimal
Decimal
PUSH BC
C5
197
PUSH DE
D5
213
PUSH HL
E5
229
PUSH AF
F5
245
PUSH IX
DD,E5
221,229
PUSH IY
FD,E5
253,229
POP BC
C1
193
POP DE
D1
209
POP HL
E1
225
POP AF
F1
241
POP IX
DD,E1
221,225
POP IY
FD,E1
253,225
Fig. 5-9. Grupo de manejo de pila.
Carga del registro «PC»

Seguramente, el lector ya se habrá dado cuenta de que no hemos mencionado en ninguna instrucción al registro "PC"; este hecho se debe a que se trata de un registro especial, que tiene adignada una función muy específica.

El registro "PC" o "Contador de programa", contiene siempre la dirección en memoria de la siguiente instrucción a ejecutar, por lo que el hecho de cargarlo con un número, implica que la siguiente instrucción será leída desde la posición de memoria apuntada por ese número, es decir, se producirá un salto o bifurcación en el flujo del programa. Vamos a verlo con un ejemplo.

Supongamos que acabamos de leer una instrucción de tres bytes de longitud, que ocupaba las posiciones 40000, 40001 y 40002. En este momento, el registro "PC" contiene el valor 40003 que es la dirección desde donde se leerá la siguiente instrucción; si la instrucción que estamos ejecutando, modifica el contenido del "PC", digamos que lo pone a 60000, la siguiente instrucción será leida desde esta dirección, con lo que se habrá producido un salto en el programa.

Los saltos y bifurcaciones tienen una importancia tan grande en cualquier lenguaje, que se ha reservado un grupo de instrucciones para este fin; se trata del grupo de instrucciones de "cambio de secuencia", que se estudiarán en el capítulo 10 de este curso. Hasta ese momento, suponemos que los programas se ejecutan en un orden secuencial, desde la primera instrucción hasta la última.

Ejemplos

A continuación, vamos a ver una serie de ejemplos prácticos que el lector podrá introducir en su ordenador, tanto si dispone de ensamblador, como si no.

A través de estos ejemplos, se pretende no sólo aprender a utilizar las instrucciones de carga, sino también, aprender a ensamblar un programa "a mano" y cargarlo desde Basic en cualquier lugar de la memoria.

Antes de eso, y como nota previa, vamos a ver la forma de retornar a Basic desde código máquina cuando finalice la ejecución de cada uno de nuestros programas. En general, llamaremos a nuestros programas con la función USR del Basic, esta función ejecutará nuestras rutinas como si se tratase de subrutinas del sistema operativo, por lo que el procedimiento de retornar a éste, será el mismo que para retornar desde cualquier subrutina, es decir, la instrucción "RET" que se ensambla como C9h (201) y es equivalente al RETURN del Basic.

Quizá esto se comprenda mejor cuando estudiemos las subrutinas en código máquina. Por ahora, nos basta con saber que al final de cada uno de nuestros programas, deberá ir la instrucción RET.

Empecemos por lo más sencillo, vamos simplemente a cargar un número en el registro "BC". Escogemos este registro, por que es su contenido el que nos devuelve la función USR cuando retorna a Basic.

Nuestro primer programa en código máquina podría ser el siguiente:

LD BC,27263
RET

Que también podría haberse escrito en hexadecimal de la siguiente forma:

LD BC,#6A7F
RET

Vamos a ensamblar a mano este sencillo ejemplo, y luego lo cargaremos en uno de los lugares que indicábamos en el capítulo 4, el buffer de impresora.

Cogemos las tablas de codificación, y vemos que la instrucción

LD BC,nn

tiene el código 01h (1), de forma que éste será el primer byte de nuestro programa.

A continuación, deberemos poner el operando de dos bytes "nn", con el orden de los octetos invertido. Como en este caso, el operando es 6A7Fh, deberemos poner primero 7Fh (127) y luego, 6Ah (106). Finalmente, pondremos el código de RET, C9h (201).

Nuestro programa queda, por tanto, de la siguiente forma:

01,7F,6A,C9

O escrito en decimal:

1,127,106,201

Hasta ahora, hemos hecho todo esto sobre el papel; por fin llega el momento de poner en marcha nuestro querido Spectrum.

Para introducir los cuatro valores que componen nuestro código máquina, podemos POKEarlos en memoria ayudándonos de un bucle FOR ... NEXT:

10 FOR n=23296 TO 23299
20 READ a: POKE n,a
30 NEXT n
40 DATA 1,127,106,201
50 PRINT USR 23296

Nuestro programa está en los datos de la línea 40, las líneas 10, 20 y 30 los van introduciendo secuencialmente en memoria, finalmente la línea 50 lo ejecuta imprimiendo el resultado de USR en el retorno.

Teclee el programa, revise que no haya habido errores, y pulse RUN...

Si todo ha ido correctamente, deberá ver el número 27263 en la esquina superior izquierda de la pantalla.

No parece un resultado muy espectacular comparado con los prodigios semimágicos que se suelen esperar del código máquina. Ciertamente, no se puede pretender más con cuatro bytes, un simple "PRINT a" de Basic implica la ejecución de cientos de instrucciones en código máquina.

No debe desanimarse el lector ni pretender hacer maravillas desde el primer momento. Lo más importante es ir aprendiendo todo claramente; las "virguerías" podrá hacerlas luego cada uno, no obstante, a lo largo del curso tenemos reservadas para nuestros lectores, maravillosas sorpresas.

Vamos con nuestro siguiente ejemplo, esta vez vamos a leer desde código máquina un número que habremos almacenado desde Basic en la variable del Sistema "SEED". Se trata de leer el contenido de SEED y sacarlo a pantalla a través del registro "BC". En esta ocasión, almacenaremos el programa a partir de la dirección de memoria 30000, para lo cual, bajaremos primero la RAMTOP a 29999. Estas direcciones son válidas tanto para usuarios de 16K como de 48K.

Nuestro programa es el siguiente:

SEED
ORG 30000
LD HL,(SEED)
LD B,H
LD C,L
RET
EQU #5C76

La primera línea: "ORG 30000" es un pseudo-nemónico, no se puede ensamblar y su única finalidad es indicarle al ensamblador que deberá ensamblar el programa a partir de la dirección 30000.

La última línea "SEED EQU #5C76" tampoco se puede ensamblar, se trata de una definición de etiqueta, su finalidad es asignarle a la etiqueta "SEED" el valor 5C76h (23670). El programa simplificado, quedaría:

LD HL,(#5C76)
LD B,H
LD C,L
RET

Para codificarlo, tomamos de nuevo las tablas y buscamos el código de:

LD HL,(nn)

que resulta ser 2Ah (42). A continuación, vendrá el operando invertido: 76h (118) y 5Ch (92). Ahora buscamos:

LD B,H

y

LD C,L

cuyos códigos resultan ser respectivamente: 44h (68) y 4Dh (77). Finalmente, ponemos el código de RET, es decir, C9h (201). Nuestro programa queda de la siguiente forma:

2A,76,5C,44,4D,C9

O escrito en decimal:

42,118,92,68,77,201

Vamos a construir el programa Basic que lo introduce en memoria y lo ejecuta:

10 CLEAR 29999 20 FOR n=30000 TO 30005
30 READ a: POKE n,a: NEXT n
40 DATA 42,118,92,68,77,201
50 INPUT "SEED ? ",a
60 RANDOMIZE a
70 PRINT USR 30000

La línea 10 baja la RAMTOP para preservar nuestro programa contra borrados accidentales. Las líneas 20 y 30 cargan en memoria el programa que se encuentra en los datos de la línea 40. La línea 50 nos pide un valor para SEED, y la línea 60 lo introduce en la variable "SEED" siempre que este valor no sea cero. Finalmente, la línea 70 ejecuta nuestro programa en código máquina e imprime en pantalla el resultado.

En este ejemplo, vemos que es posible establecer una comunicación bidireccional entre Basic y Código Máquina para transferir datos; existen otras muchas formas de realizar esta comunicación que se irán viendo en ejemplos sucesivos.

En nuestro tercer ejemplo, vamos a leer la variable del Sistema RAMTOP desde código máquina, y utilizaremos la pila para sacarla a pantalla por el registro "BC". Asimismo, veremos cómo almacenar una rutina en código máquina dentro de una línea REM del programa Basic.

Primero leeremos el contenido de la variable RAMTOP, cargándolo sobre el registro "HL", luego transferiremos este contenido al "BC" a través de la pila; el programa podría ser el siguiente:

RAMTOP
LD HL,(RAMTOP)
PUSH HL
POP BC
RET
EQU 23730

De nuevo, utilizamos una etiqueta que definimos en la última línea, antes de codificar el programa, eliminamos la etiqueta, quedando el programa simplificado:

LD HL,(#5CB2)
PUSH HL
POP BC
RET

Ahora, codificamos el programa, buscamos en las tablas el código de:

LD HL,(nn)

que resulta ser 2Ah (42), a continuación van los operandos B2h (178) y 5Ch (92). El código de:

PUSH HL

es E5h (229), y el de:

POP BC

es C1h (193). Por último, colocamos el código de RET: C9h (201). El programa completo, queda de la siguiente forma:

2A,B2,5C,E5,C1,C9

O prar quienes lo prefieran en decimal:

42,178,92,229,193,201

Ahora, sólo nos falta cargarlo en una línea REM de un programa Basic. Nuestra rutina tiene 6 bytes, por lo que crearemos una línea REM con, por ejemplo, 6 asteriscos. Estos asteriscos serán sustituidos por los bytes del programa cuando éste se cargue.

El programa podría ser el siguiente:

10 REM ******
20 LET prog=PEEK 23635+256*PEEK 23636
30 FOR n=5 TO 10
40 READ a: POKE prog+n,a
50 NEXT n
60 DATA 42,178,92,229,193,201
70 PRINT USR (prog+5)

Hay muchos puntos sutiles en este programa que conviene analizar detenidamente; como dijimos antes, la línea 10 contiene el espacio donde se cargará nuestra rutina en C/M.

En la línea 20, leemos la variable del Sistema PROG, para saber a partir de qué dirección de memoria está ubicado el programa Basic. Los dos primeros bytes de esta zona, constituyen el número de línea, los dos siguientes la longitud, y el quinto es el código de REM; a partir de ahí empiezan los asteriscos, que es donde deberemos cargar el código máquina, es decir, desde "prog+5" hasta "prog+10" tal y como se ve en las líneas 30, 40 y 50. La línea 60 contiene el programa en DATAs. Finalmente, la línea 70 ejecuta el programa desde la dirección "prog+5". En este caso, es imprescindible que el argumento de USR vaya entre paréntesis; es muy fácil omitir los paréntesis, olvidándose de que la función USR tiene una prioridad más alta que la suma.

Una vez ejecutado el programa, la línea 10 quedaría:

10 REM *SIN \ RESTORE STR$ <>

Nuestro siguiente ejemplo es más vistoso, y algo más complejo. Vamos a dibujar una silueta en pantalla, y dado que la cosa va de pantalla, almacenaremos esta rutina en el archivo de presentación visual, con lo que veremos físicamente los bytes que la componen, en forma de pixels en la primera línea.

El objetivo del programa es dibujar en la casilla central, la silueta de un muñeco, como si se tratara de un UDG. Dado que sólo podemos utilizar instrucciones de carga, el programa resulta considerablemente más largo de lo que es normal para trabajar con la pantalla.

En sucesivos ejemplos de capítulos más avanzados, iremos viendo otras formas más sencillas de imprimir en pantalla; y veremos también, cómo la peculiar manera en que está organizado el archivo de pantalla, que tan incómoda se hacía en Basic, resulta una gran ventaja cuando se trabaja en código máquina.

La forma más sencilla de imprimir un gráfico en pantalla, es almacenar en las ocho direcciones que componen una casilla, los ocho números que definen ese gráfico. En la FIGURA 5-10, vemos las direcciones de las posiciones de memoria correspondientes a la casilla central de la pantalla, así como los datos que vamos a almacenar en esas posiciones, para visualizar nuestro muñeco.

DATOS
DIRECCIONES
18
486F
19
496F
3E
4A6F
58
4B6F
98
4C6F
24
4D6F
24
4E6F
24
4F6F
Fig. 5-10. Datos y direcciones para crear un muñeco en pantalla.

El método general que vamos a utilizar, es cargar el registro "A" con el dato a almacenar, el registro "HL" con la dirección, y almacenar el dato "A" en la dirección apuntada por "HL". Como todavía no hemos aprendido a hacer bucles, tendremos que repetir esta secuencia 8 veces, si bien, las veces sucesivas será suficiente con que modifiquemos el valor del registro "H", ya que el del "L" permanece constante. El listado sería el siguiente:

LD A,#18
LD HL,#486F
LD (HL),A
LD A,#19
LD H,#49
LD (HL),A
LD A,#3E
LD H,#4A
LD (HL),A
LD A,#58
LD H,#4B
LD (HL),A
LD A,#98
LD H,#4C
LD (HL),A
LD A,#24
LD H,#4D
LD (HL),A
LD H,#4E
LD (HL),A
LD H,#4F
LD (HL),A
RET
3E,18
21,6F,48
77
3E,19
26,49
77
3E,3E
26,4A
77
3E,58
26,4B
77
3E,98
26,4C
77
3E,24
26,4D
77
26,4E
77
26,4F
77
c9

Obsérvese que, dado que las tres últimas líneas del dibujo son iguales, no ha sido necesario cargar el registro "A" más que 6 veces.

A la derecha del listado Assembler, está el código de cada una de las instrucciones. En este caso, vamos a utilizar una técnica más refinada para cargar el programa; escribiremos el código máquina en hexadecimal sobre una línea DATA del Basic, y utilizaremos una suma de control para detectar posibles errores. El programa en Basic sería el siguiente:

10 LET d=16384
20 DEF FN a(a$,n)=16+(CODE a$(
n)-48-7*(a$(n)>"9"))+(CODE a$(n+
1)-48-7*(a$(n+1)>"9"))
30 READ a$,s: LET cs=0
40 FOR n=1 TO LEN a$-1 STEP 2
50 LET a=FN a(a$,n)
60 LET cs=cs+a
70 POKE d,a: LET d=d+1
80 NEXT n
90 IF cs<>s THEN PRINT "<ERROR>": STOP
100 RANDOMIZE USR 16384
110 DATA "3E18216F48773E1926497
73E3E264A773E58264B773E98264C773
E24264D77264E77264F77C9"
120 DATA 2926: REM Checksum

Primero fijamos la dirección de carga al inicio del archivo de presentación visual. En la línea 20, definimos una función que nos ayuda a convertir de hexadecimal a decimal, la línea 30 lee el código máquina en la variable "a$", la suma de comprobación en la variable "s" y pone a cero el acumulador de checksum "cs". Las líneas 40 a 80 van pasando los códigos a decimal (línea 50), acumulándolos en el checksum (línea 60) y metiéndolos en sucesivas direcciones de memoria (línea 70).

La línea 90 comprueba que el valor acumulado en checksum sea igual a la suma de comprobación, y detiene el programa en caso contrario. Finalmente, la línea 100 ejecuta nuestra rutina en código máquina. La línea 110 contiene el código máquina en hexadecimal, y la 120 la suma de todos los bytes en decimal, que se utiliza como suma de comprobación.

Cuando se ejecute el programa, en la pantalla del ordenador tiene que aparecer algo similar a lo que se ve en la FIGURA 5-11.

[Figura 5-11]
Fig. 5-11.

En el siguiente capítulo, veremos las instrucciones que nos permiten realizar operaciones aritméticas y lógicas sobre los registros del microprocesador. Antes de ello, le recomendamos al lector que intente resolver los siguientes ejercicios, que le ayudarán a afianzar conocimientos.

EJERCICIOS
1.-
¿Qué valor retornará el siguiente programa en el registro "BC"? ¿Qué valor tendremos en la variable del sistema "SEED" después de ejecutarlo?:
SEED
LD HL,#4BFF
LD BC,#1100
LD H,C
LD (SEED),BC
PUSH HL
POP BC
RET
EQU #5C76
(Puede encontrar la solución por usted mismo, codificando el programa y ejecutándolo en el ordenador, para comprobar si la solución que ha dado es correcta).
2.-
Codificar (ensamblar) el siguiente programa:
LD BC,#1234
LD A,(BC)
LD (IX+7),A
PUSH AF
POP BC
RET
3.-
Escribir cuatro cargadores en Basic, cada uno de los cuales almacenen el programa anterior en uno de los siguientes lugares:
EN EL BUFFER DE IMPRESORA
ENCIMA DE RAMTOP
EN UNA LINEA REM
EN LA PANTALLA