Es muy probable que el gran volumen de lectores nos haga
imposible mantener una correspondencia personalizada, pero aún
así, nos agradaría que quienes sigan el curso nos escriban
contándonos sus progresos o las dificultades que encuentran.
Estas cartas nos permitirán ir adaptando las explicaciones a
un nivel que satisfaga a todos y permita que nadie se quede
"descolgado".
En este mismo capítulo incluimos dos rutinas de utilidad
escritas en código máquina que permiten hacer "Scroll" lateral
de la pantalla a derecha e izquierda y pixel a pixel.
Las dos rutinas se han ensamblado una a continuación de la
otra y son reubicables, es decir, se pueden colocar en
cualquier parte de la memoria. Nosotros las hemos ensamblado
a partir de la dirección 55000, pero quien disponga de sólo
16K, puede colocarlas en cualquier otra dirección, haciendo
unas pequeñas modificaciones en el programa cargador, que
explicaremos un poco más adelante.
En la FIGURA 1, reproducimos fotográficamente el listado
en Assembler de las dos rutinas. No se preocupe el lector si
le suena a "chino", un listado en Assembler no es más dificil
de entender que uno en Basic, cuando se conoce. Al final del
curso, más de uno será capaz de mejorarlo.
El PROGRAMA 1 sirve, lógicamente, para cargar estas
rutinas en memoria sin necesidad de Ensamblador. De esta
forma, no es necesario saber código máquina para usarlas. Una
vez estén en memoria basta teclear:
Para que la pantalla se desplace un pixel a la izquierda,
y:
Para que lo haga hacia la derecha. Para salvar el código
en cinta, puede utilizar:
SAVE "Scroll" CODE 55000,60 |
|
Y para cargarlo:
CLEAR 54999 LOAD "Scroll" CODE 55000 |
|
El PROGRAMA 1 incluye una demostración sobre la forma de
utilizar estas rutinas. Quien esté interesado en usarlas en
sus programas, puede mirar atentamente las líneas 240 y 250
que resultan suficientemente ilustrativas.
Para adaptar las rutinas a la versión de 16K, se deben
realizar las siguientes modificaciones en el PROGRAMA 1:
50 CLEAR 31999: LET d=32000
240 IF INKEY$="q" THEN RANDOMIZE USR 32000
250 IF INKEY$="p" THEN RANDOMIZE USR 32030
|
En este caso, habrá que salvar las rutinas con:
SAVE "Scroll" CODE 32000,60 |
|
Y volverlas a cargar con:
CLEAR 31999 LOAD "Scroll" CODE 32000 |
|
Antes de seguir leyendo, le pedimos que cargue y ejecute
el PROGRAMA 1. Ponga mucho cuidado para no equivocarse a
partir de la línea 300, ya que los errores en código máquina
suelen tener consecuencias desastrosas.
¿Ya ha ejecutado el programa? Asombroso ¿no? Tenemos
pensado otro que hace el Scroll arriba y abajo, ya se lo
contaremos.
Ahora vamos a intentar introducirnos en el estudio del
código máquina partiendo desde la base más elemental, para que
incluso quien no tenga ni la más remota idea de lo que es esto
pueda sequirnos. Si no es su caso y es usted capaz de entender
sin problemas las explicaciones de los cuatro primeros
capítulos no es necesario que lea lo que sigue, aunque tal vez
pueda aclararle algunos conceptos.
Manejando una calculadora
Suponemos que todos nuestros lectores han manejado alguna
vez una calculadora de bolsillo. El conjunto formado por una
calculadora de bolsillo, la persona que la maneja, un lápiz y
un papel, pueden ser un símil bastante aproximado de lo que es
un ordenador.
Imaginémonos a un amigo con una calculadora y un lápiz;
esto equivale más o menos al microprocesador o CPU. Por otro
lado está el papel, que equivale a la memoria. Nuestro amigo
puede usar el papel para apuntar resultados o datos
intermedios de los cálculos, pero nosotros podemos usarlo
también, para apuntarle a él los cálculos que queremos que
realice. De esta forma, el papel (la memoria) cumple una
doble función, por un lado sirve para que el microprocesador
(nuestro amigo) anote datos, y por otro lado, sirve para que
nosotros le anotemos las instrucciones que tiene que seguir
(el programa).
Nuestro amigo no tiene ni idea de como se maneja una
calculadora, así que tendremos que decirle, una por otra, las
teclas que tiene que pulsar. Podemos decirle: "pulsa la
segunda tecla de la tercera fila" o simplemente: "pulsa
(2,3)"; esto sería "código máquina". Pero también podemos
decirle: "pulsa la tecla 5, luego la tecla «por» y
luego la tecla 7"; esto sería "Assembler".
Vamos a "programar" en "Assembler" a nuestro amigo, para
que nos calcule el cuadrado de 5 por 7 y nos escriba el
resultado en un recuadro de la hoja de papel el que
denominamos "archivo de presentación visual". La calculadora
puede ser la representada en la FIGURA 2.
El programa podría quedar más o menos así:
10 Pulsa "AC"
20 Pulsa "5"
30 Pulsa "POR"
40 Pulsa "7"
50 Pulsa "="
60 Pulsa "CUADRADO"
70 Escribe Resultado
80 Fin
|
|
Este programa se lo anotamos en el papel, y le damos la
orden de que lo ejecute. Al final, él nos escribe el resultado
en el papel.
Podemos sacar aún más partido a nuestro
ejemplo. Supongamos que nuestro amigo supiera manejar
perfectamente la calculadora, en ese caso, nos bastaría con
decirle: "Calcula el cuadrado de 5 por 7 y anota el
resultado". En este caso, estaríamos usando un "lenguaje de
alto nivel". El Basic es un lenguaje de alto nivel, y lo
podemos usar gracias a que nuestro ordenador tiene un
"intérprete", lo que hace que "sepa" manejar perfectamente el
microprocesador.
Vamos a estudiar detenidamente el proceso. Primero
pulsamos "AC", con lo que se borran los anteriores contenidos
de la calculadora. A continuación, pulsamos la tecla "5", con
lo que aparece en pantalla el número cinco. La pantalla de la
calculadora es el registro, y lo que hemos hecho ha sido
cargar este registro con el número cinco. Luego definimos la
operación a realizar, y cargamos el registro con el segundo
operando (siete). Al pulsar "=" se realiza la operación y el
resultado aparece de nuevo en ese registro. Finalmente, al
pulsar "CUADRADO", elevamos al cuadrado el contenido del
registro, y el resultado nos vuelve a aparecer en el
mismo.
La pantalla de una calculadora va acumulando los
resultados de todas las operaciones que vamos realizando, por
eso, podemos llamarla "registro ACUMULADOR". Todos los
microprocesadores tienen un registro acumulador, pero a
diferencia de las calculadoras, tiene también otros registros
que nos pueden servir para diversos fines.
A pesar de tener muchos registros, no son suficientes para
almacenar el enorme volumen de datos que maneja un ordenador,
por ello se recurre a un sistema de almacenamiento externo que
se denomina "memoria" y cumple la misma función que el papel
de nuestro ejemplo, sirve para almacenar tanto programas, como
datos.
El "papel" que utilizamos como memoria está "cuadriculado"
y nuestro microprocesador no sólo tiene "lápiz", sino también
"goma" por loque puede escribir y borrar en cualquiera de las
cuadriculas; pero sólo borra una cuadrícula cuando tiene que
escribir otro dato en ella. Las cuadrículas, a su vez, están
numeradas, por lo que en cada caso, se puede acceder a una de
ellas en concreto.
Existen más diferencias entre la calculadora y el
microprocesador. Este último no realiza las mismas operaciones
que una calculadora. Es cierto que puede sumar y restar, pero
puede realizar también otro tipo de operaciones como
incrementar un registro (sumarle 1), decrementarlo (restarle
1) rotarlo, y fundamentalmente, realizar lo que se denomina
"operaciones lógicas" (AND, OR, NOT, EXOR). Además, nos
informa continuamente de ciertas características del dato que
contiene el acumulador, por ejemplo, nos dice si es cero, si
es negativo, y otros cuya utilidad se irá viendo más
adelante.
No obstante, la diferencia fundamental entre nuestro
ejemplo y un verdadero microprocesador es que este último no
trabaja en base 10 (decimal), sino en
binario. Afortunadamente, no necesitamos trabajar siempre con
números binarios (que son súmamente incómodos) y podremos
utilizar números en base 16 (hexadecimales).
Es muy importante adquirir cierta soltura en el manejo de
la numeración hexadecimal, por ello, hemos dedicado un
capítulo entero a este tema. Con bastante frecuencia,
tendremos que convertir números decimales a hexadecimales o
viceversa. Para esto se pueden usar los métodos descritos en
el citado capítulo, pero resulta bastante tedioso, así que
hemos desarrollado un programa que hace ese trabajo por
nosotros. Este programa se encuentra en la página 11 del curso
(MICROHOBBY número 43). Existen también, calculadoras de
bolsillo capaces de operar en estas bases, y representan una
gran ayuda a la hora de programar en código máquina.
También hemos dedicado un capítulo a describir el
microprocesador Z-80 con el mayor detalle posible, ya que su
conocimiento es imprescindible para programarlo. Sería algo
así como el "manual" de la calculadora.
Finalmente, y antes de empezar a estudiar las
instrucciones, hemos dedicado un capítulo a describir la forma
en la que se debe elaborar un programa, independientemente del
lenguaje utilizado.
Algebra de Boole
Prácticamente todos los instrumentos matemáticos que se
utilizan en la programación de un pequeño ordenador como el
Spectrum, forman parte del bagaje cultural de cualquier
persona medianamente formada. Excepto, quizá, el álgebra de
Boole.
Tal vez por ser de aparición relativamente reciente, tal
vez por su escasa utilidad práctica en la realidad habitual,
el caso es que el álgebra de Boole no ha sido incluida en el
programa de estudios hasta fecha reciente; y lo ha sido dentro
de la asignatura de "matemáticas comunes" del
C.O.U. desgraciadamente, una de las "Marías".
Programando en Basic, hemos hecho uso de algunos conceptos
provenientes del álgebra de Boole; cuando utilizábamos en las
sentencias IF...THEN, los operadores OR, AND y NOT para
expresar conjunciones y disyunciones lógicas. Al programar en
Assembler o código máquina, haremos un uso mucho más profundo
y preciso de estos operadores, así como del operador EXOR que
no se utiliza en Basic.
Es tan frecuente (y útil) utilizar operadores lógicos en
Assembler, como lo puede ser utilizar la suma y la resta en
una calculadora de bolsillo. Por ello, es imprescindible tener
un cierto conocimiento del álgebra de Boole; que por otro
lado, es sumamente sencilla de aprender.
Dado que éste no pretende ser un manual de matemática
moderna, no entraremos a definir formalmente lo que constituye
un álgebra de Boole. Bástenos saber que un álgebra de Boole se
puede construir allá donde tengamos un conjunto de elementos
que puedan tomar dos valores (en nuestro caso, "0" y "1")
y definamos una relación de equivalencia ("ser igual a") y
dos operaciones internas al conjunto, que cumplan una serie de
propiedades, similares a las que cumplen la suma y el producto
en el álgebra a la que estamos acostumbrados. El hecho de que
las operaciones sean internas, quiere decir que al operar dos
elementos del conjunto, lo que se obtiene es otro elemento que
también pertenece al mismo. Lo que tiene por consecuencia, que
siempre que hagamos operaciones lógicas entre "ceros" y
"unos", obtendremos indefectiblemente, "ceros" y
"unos".
Puesto que un circuito electrónico (y los ordenadores lo
son) no pueden trabajar más que con "ceros"y "unos", el
álgebra de Boole parece un instrumento especialmente adecuado
a la Informática. Como se explica en el capítulo que trata de
los sistemas de numeración, agrupamos los "unos" y "ceros" en
secuencias de 8 o 16 para componer otros números; pero en
definitiva, estaremos trabajando con "ceros" y "unos", y el
juego de instrucciones del microprocesador nos permite aplicar
operaciones lógicas entre el contenido de los registros.
A continuación, vamos a ver uno a uno los operadores
lógicos de nuestro álgebra de Boole.
OPERADOR NOT
No se trata propiamente de un operador lógico pero podemos
considerarlo como tal. El operador NOT se aplica sobre un solo
elemento del conjunto, y lo convierte en su complementario. Es
decir, si aplicamos NOT sobre un "0", obtenemos un "1"; y
viceversa, si aplicamos NOT sobre un "1", obtenemos un "0". Su
"Tabla de verdad" sería la siguiente:
Una "tabla de verdad" equivale, en álgebra de Boole, a la
tabla de sumar o multiplicar. Se trata de una representación
de todas las soluciones posibles que se pueden obtener con un
operador determinado.
Si aplicamos el operador NOT al contenido de un registro,
lo que obtenemos es el "complemento" de ese registro, es
decir, cambiamos sus "unos" por "ceros" y sus "ceros" por
"unos". A esta operación se la denomina "complementar un
registro", y utilizamos para ello la instrucción CPL del
microprocesador.
Veamos un ejemplo: Supongamos que el registro acumulador contiene el número:
Que se podría escribir como "6Ah" en hexadecimal (ver
capítulo referente a los sistemas de numeración). Si lo
complementamos, obtenemos:
Que podría escribirse como "95h" en hexadecimal. Hemos
cambiado los "ceros" por "unos" y los "unos" por "ceros". El
número "95h" es el complementario de "6Ah" porque si los
sumamos, obtenemos "FFh", que a su vez, es el mayor número
posible (todos son "unos").
De esta forma, sería posible construir una "tabla" para la
operación NOT en hexadecimal. Esta tabla la hemos representado
en la FIGURA 3. Se puede observar que si sumamos cualquier
número, con el que resulta de aplicarle el operador NOT,
obtenemos "Fh" por eso son "complementarios".
NOT
|
0 1 2 3 4 5 6 7 8 9 A B C D E F
|
|
F E D C B A 9 8 7 6 5 4 3 2 1 0
|
|
Fig. 3. Tabla hexadecimal de "NOT".
|
OPERADOR OR
Se trata de una de las operaciones que se usan para
definir nuestra áñgebra de Boole, y es equivalente a la "suma"
en el sentido de que satisface las mismas propiedades
algebraicas.
Cuando operamos dos elementos de nuestro conjunto mediante
este operador, obtenemos un "1" si al menos, uno de ellos es
"1", o si lo son ambos; y obtenemos "0" en cualquier otro
caso. La tabla de verdad del operador "OR" es la
siguiente:
0 OR 0 = 0
0 OR 1 = 1
1 OR 0 = 1
1 OR 1 = 1
|
|
Veamos un ejemplo: Supongamos que el acumulador contiene
el número:
Es decir, "46h". Y le hacemos un "OR" con el número:
Es decir, "E3h". El resultado sería el número:
Que se representa en hexadecimal como "E7h". Vemos que
hemos puesto un "1" en los lugares donde había "1" en
cualquiera de los dos números y "0" en los lugares donde ambos
números tenían un "0". La tabla para este operador en
hexadecimal se puede ver en la FIGURA 4.
OR
|
0 1 2 3 4 5 6 7 8 9 A B C D E F
|
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
|
0 1 2 3 4 5 6 7 8 9 A B C D E F
1 1 3 3 5 5 7 7 9 9 B B D D F F
2 3 2 3 6 7 6 7 A B A B E F E F
3 3 3 3 7 7 7 7 B B B B F F F F
4 5 6 7 4 5 6 7 C D E F C D E F
5 5 7 7 5 5 7 7 D D F F D D F F
6 7 6 7 6 7 6 7 E F E F E F E F
7 7 7 7 7 7 7 7 F F F F F F F F
8 9 A B C D E F 8 9 A B C D E F
9 9 B B D D F F 9 9 B B D D F F
A B A B E F E F A B A B E F E F
B B B B F F F F B B B B F F F F
C D E F C D E F C D E F C D E F
D D F F D D F F D D F F D D F F
E F E F E F E F E F E F E F E F
F F F F F F F F F F F F F F F F
|
|
Fig. 4. Tabla hexadecimal de "OR".
|
OPERADOR AND
En cierto sentido, se puede considerar que este operador
es el opuesto al anterior. Equivale al producto, en cuanto a
las propiedades algebraicas que satisface.
Cuando operamos dos elementos de nuestro conjunto ("unos"
o "ceros") obtenemos un "1" solamente si ambos elementos son
"1"; y un "0" en cualquier otro caso. La Tabla de verdad del
operador AND es la siguiente:
0 AND 0 = 0
0 AND 1 = 0
1 AND 0 = 0
1 AND 1 = 1
|
|
Vamos a ver que ocurre si, con los números del ejemplo
anterior, aplicamos la operación AND:
Esta vez, hemos puesto un "1" sólo en los lugares donde
ambos números tenían un "1", y hemos puesto "0" en todos los
demás lugares. La tabla del operador "AND" en Hexadecimal,
está representada en la FIGURA 5.
AND
|
0 1 2 3 4 5 6 7 8 9 A B C D E F
|
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
0 0 2 2 0 0 2 2 0 0 2 2 0 0 2 2
0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3
0 0 0 0 4 4 4 4 0 0 0 0 4 4 4 4
0 1 0 1 4 5 4 5 0 1 0 1 4 5 4 5
0 0 2 2 4 4 6 6 0 0 2 2 4 4 6 6
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
0 0 0 0 0 0 0 0 8 8 8 8 8 8 8 8
0 1 0 1 0 1 0 1 8 9 8 9 8 9 8 9
0 0 2 2 0 0 2 2 8 8 A A 8 8 A A
0 1 2 3 0 1 2 3 8 9 A B 8 9 A B
0 0 0 0 4 4 4 4 8 8 8 8 C C C C
0 1 0 1 4 5 4 5 8 9 8 9 C D C D
0 0 2 2 4 4 6 6 8 8 A A C C E E
0 1 2 3 4 5 6 7 8 9 A B C D E F
|
|
Fig. 5. Tabla hexadecimal de "AND".
|
OPERADOR EXOR
No se trata propiamente, de una operación del álgebra de
Boole, pero es necesario describirlo dado el gran uso que se
hace de él cuando se programa en Assembler. El operador
"EXOR" es, en cierta forma, una mezcla de los operadores "AND"
y "OR".
Cuando operamos dos elementos de nuestro conjunto mediante
este operador, obtenemos un "1", sólo si uno de los dos
elementos es "1"; y obtenemos un "0" tanto si ambos son
"ceros", como si ambos son "unos". La Tabla de verdad del
operador "EXOR" es la siguiente:
0 EXOR 0 = 0
0 EXOR 1 = 1
1 EXOR 0 = 1
1 EXOR 1 = 0
|
|
Apliquemos este operador a los números del ejemplo anterior:
Hemos puesto un "0" en los lugares donde ambos números
eran iguales (dos "unos" o dos "ceros"), y un "1" donde eran
distintos ("cero" y "uno" o "uno" y "cero").
La tabla del operador "EXOR" en hexadecimal, está
representada en la FIGURA 6. Como curiosidad importante, cabe
señalar que si se realiza una operación "EXOR" de un número
consigo mismo, el resultado es siempre "cero"; lo cual es muy
útil, ya que si se quiere cargar el número "cero" en el
acumulador, se puede utilizar la instrucción "XOR A" ("EXOR"
del acumulador consigo mismo) que ocupa la mitad de memoria y
es el doble de rápida qu "LD A,0" (cargar el acumulador con
cero).
XOR
|
0 1 2 3 4 5 6 7 8 9 A B C D E F
|
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
|
0 1 2 3 4 5 6 7 8 9 A B C D E F
1 0 3 2 5 4 7 6 9 8 B A D C F E
2 3 0 1 6 7 4 5 A B 8 9 E F C D
3 2 1 0 7 6 5 4 B A 9 8 F E D C
4 5 6 7 0 1 2 3 C D E F 8 9 A B
5 4 7 6 1 0 3 2 D C F E 9 8 B A
6 7 4 5 2 3 0 1 E F C D A B 8 9
7 6 5 4 3 2 1 0 F E D C B A 9 8
8 9 A B C D E F 0 1 2 3 4 5 6 7
9 8 B A D C F E 1 0 3 2 5 4 7 6
A B 8 9 E F C D 2 3 0 1 6 7 4 5
B A 9 8 F E D C 3 2 1 0 7 6 5 4
C D E F 8 9 A B 4 5 6 7 0 1 2 3
D C F E 9 8 B A 5 4 7 6 1 0 3 2
E F C D A B 8 9 6 7 4 5 2 3 0 1
F E D C B A 9 8 7 6 5 4 3 2 1 0
|
|
Fig. 6. Tabla hexadecimal de "EXOR".
|
De la misma forma, si se hace un "OR" o un "AND" de un
número consigo mismo, éste no varía. En el programa de
ejemplo de este capítulo, hemos usado la instrucción "AND A"
("AND" del acumulador consigo mismo) para poner a cero el
indicador de acarreo sin que varíe el contenido del
acumulador.
El empleo de operadores lógicos nos va a permitir movernos
por tablas, calcular direcciones de memoria, poner "máscaras"
a algunos bits, y un sinfín de utilidades que justifican la
necesidad de adquirir el mayor dominio posible del álgebra de
Boole.
|