domingo, 19 de enero de 2014

El ensamblador EDTASM para el μP 6809 II

La gran mayoría de los programas ensambladores elaborados profesionalmente además de contar con un editor que permite escribir y editar los programas de texto en código fuente también cuentan con un debugger, una herramienta que permite correr dentro de un entorno seguro los programas elaborados y que permite la detección de errores. Como algo deseable, un buen debugger debe permitir la ejecución del programa ensamblado paso-a-paso, y en cada paso debe permitir la inspección de todos los registros, acumuladores, pila, contador de programa, registros indexados, etcétera. Como mínimo, debe ser posible la inserción de puntos de ruptura (conocidos como breakpoints) que permitan la inspección de la condición de todos los registros al llegar a cada punto de ruptura. En el caso del editor-ensamblador EDTASM, la herramienta se llama ZBUG. Para entrar en el entorno de ZBUG al estar en el entorno de EDTASM, basta con escribir al principio de una línea nueva el símbolo “Z” y oprimir la tecla de Entrada [Enter]. Se nos dará una confirmación de que ya estamos en ZBUG al cambiar el “prompt” puesto por la máquina al inicio de cada línea nueva con el símbolo “*” a un prompt “#” puesto al inicio de cada línea conforme vayamos avanzando. Una vez dentro de ZBUG, podemos examinar los registros del procesador 6809 con el comando “R”. La siguiente secuencia de pasos nos dá una idea de cómo dejamos el entorno de EDTASM y entramos al entorno de ZBUG para la inspección de los registros:




Los contenidos de los registros son dados en hexadecimal. Cuando arrancamos ZBUG “en frío” sin haber ejecutado programa alguno, los registros del procesador (a excepción del registro “S” que representa a la pila) están todos en ceros. Algunos de los registros son de un byte, y puesto que hay dos dígitos hexadecimales por cada ocho bytes, habrá dos dígitos asignados a cada uno de tales registros. Sin embargo, otros registros contienen dos bytes en lugar de uno, y en tal caso tendremos cuatro dígitos hexadecimales para ellos. El último registro dado arriba en el listado de ZBUG corresponde al Contador del Programa (PC, Program Counter); este es el registro del CPU que “apunta” hacia la siguiente instrucción del microprocesador en la memoria. El registro CC es el registro de los Códigos de Condición, las ocho “banderas”.



PROBLEMA: Dígase lo que lleva a cabo el siguiente programa después de ser ensamblado y ejecutado:





En este programa, al principio los acumuladores A y B son cargados con los números hexadecimales $FA y $2C. Al terminar el programa, con la ayuda de la pila, los dos números han sido intercambiados. Obsérvese que no hay instrucción alguna en el conjunto de instrucciones del microprocesador 6809 para intercambiar los contenidos entre los dos acumuladores. El programa fuente en el lenguaje del ensamblador, con comentarios añadidos en el campo de comentarios, es el siguiente:

Vista en mayor detalle, la acción que se lleva a cabo es la siguiente:




Habrá ocasiones en las cuales quienes son nuevos al arte de la programación de computadoras digitales en el nivel del lenguaje assembler tendrán la sensación de que el conjunto de instrucciones para cierto procesador en el que estén trabajando no es suficiente para algunas cosas que desan llevar a cabo, que faltan instrucciones necesarias que no se encuentran dentro del conjunto de instrucciones disponible dentro del procesador. Sin embargo, habiendo suficiente memoria disponible, se debe tener la certeza de que el conjunto de instrucciones propias de cada procesador es por diseño un conjunto mínimo con el cual se puede llevar a cabo cualquier objetivo de programación que se tenga la intención de convertirlo en realidad. En su quintaesencia, siempre y cuando se cuente con cierto mínimo de instrucciones, todas las computadoras digitales son equivalentes entre sí, lo que se puede lograr con una se puede lograr con cualquier otra siempre y cuando se cuente con una memoria ilimitada o lo suficientemente grande para poder llevar a cabo la emulación (esto se cubre en mayor detalle en el tema de la máquina Turing). Se puede requerir algo de ingenio para combinar ciertas instrucciones en lenguaje de máquina para lograr un efecto que no alcanzan a cubrir las instrucciones originales, pero de que tal cosa siempre se puede llevar a cabo de eso no debe quedar duda alguna.

Supóngase por ejemplo que queremos efectuar una operación AND lógica bit-por-bit entre dos palabras binarias de 32 bits cada palabra, siendo dichas palabras binarias las siguientes:

00100110100100111110100101001110

y:

10111110110110100111011111101110

Desde luego que en el conjunto de instrucciones del microprocesador 6809 no hay una ninguna instrucción que diga algo como:

“Llevar a cabo un AND lógico entre dos palabras de 32 bits”

Ni siquiera existe un registro dentro del microprocesador 6809 que tenga una capacidad de almacenamiento de 32 bits. Existe desde luego una instrucción AND lógica en el 6809, pero dicha instrucción efectúa la operación entre dos bytes, uno que es puesto en uno de los dos acumuladores del procesador y otro byte que es tomado de la memoria, con el resultado de la operación lógica quedando almacenado en el acumulador. Sin embargo, si separamos cada palabra de 32 bits en grupos de bytes, podemos ver que cada palabra consta de cuatro bytes de la siguiente manera:

00100110 10010011 11101001 01001110

y:

10111110 11011010 01110111 11101110

De este modo, cada palabra se puede almacenar en la memoria ocupando cuatro localidades contiguas de la memoria con cada localidad contigua almacenando un byte. La estrategia consiste entonces en tomar el byte menos significativo de cada palabra (que suponemos es el que está en el extremo derecho), ó sea 01001110 y 11101110, efectuar la operación AND lógica entre ambos bytes, y almacenando el resultado en la memoria. El procedimiento se repite con el siguiente par de bytes que está a la izquierda, haciéndose lo mismo con el tercer par de bytes y el cuarto par de bytes, y al terminar tendremos almacenado en la memoria el resultado del AND lógico entre las dos palabras de dos bytes.

Entrando en detalles, como primer paso tomamos con una instrucción de carga LDA el primer byte de la primera palabra poniéndolo en el acumulador A, hacemos a continuación con una instrucción AND la operación AND entre este byte que está en el acumulador y el byte que está en la localidad M de la memoria; la instrucción AND del 6809 dejará el resultado en el acumulador. Hecho esto, con una instrucción de almacenamiento a la memoria STA tomamos lo que está en el acumulador A y lo ponemos en la memoria en el mismo lugar en donde estaba cualquiera de los dos bytes originales. Esta secuencia es repetida hasta que hemos recorrido todos los bits de ambas palabras binarias.

A continuación se muestra un gráfico animado que nos muestra en detalle lo que ocurre en el proceso descrito arriba:




El resultado del AND lógico sobre las dos palabras de 32 bits dadas al principio viene siendo entonces la siguiente palabra de 32 bits (separada en bytes para mayor legibilidad):

00100110 10010010 01100001 01001110

PROBLEMA: Escribir en lenguaje assembler las instrucciones requeridas para efectuar la operación AND entre dos palabras de 32 bits.

Suponiendo que los cuatro bytes de la primera palabra binaria se almacenan en las cuatro localidades contiguas a partir del domicilio $70, y que los cuatro bytes de la segunda palabra binaria se almacenan en las cuatro localidades contiguas a partir del domicilio $011D, el programa fuente en lenguaje ensamblador para el procesador 6809 puede ser:


Programa AND para palabras de 4 bits
  START   LDA $070 Byte menos significativo
ANDA $11D
STA $070
  LDA $071 Segundo byte
  ANDA $11E
  STA $071
  LDA $072 Tercer byte
  ANDA $11F
  STA $072
  LDA $073 Byte más significativo
  ANDA $120
  STA $073
  SWI Interrupción de la ejecución
  END Fin del programa


Desde un principio se había señalado que si la capacidad de una computadora fueran números binarios de ocho ó de nueve bits, tal máquina no nos sería de mucha utilidad para cálculos aritméticos que podríamos efectuar incluso mentalmente sin tener que gastar dinero en el diseño, la construcción y la programación de una máquina tal.

Sin embargo, también se ha señalado que aunque el tamaño de cada palabra binaria puesto en una localidad de la memoria sea de ocho bits (un byte), es posible formar números más grandes uniendo los contenidos de localidades contiguas de la memoria. Esto nos lleva directamente al tema de los números de precisión múltiple.

En el microprocesador 6809, con la ayuda de la instrucción ADD, podemos sumar operandos de 8 y 16 bits. Si nos limitamos al manejo de números positivos, prescindiendo por lo tanto del signo, el número decimal máximo que puede ser representado en 8 bits es 255, mientras que el número decimal máximo que puede ser representado es 65,536. Una forma de poder manejar números grandes es usar números de “punto flotante”, cuya distintiva característica es que llevan anexado un punto decimal que no tienen los números enteros. Pero la otra manera, forjando números grandes concatenando bytes, es la primera opción. ¿Qué tan grande puede ser un número si usamos una cadena de cuatro bytes para representarlo? A continuación tenemos un número de precisión múltiple formado por cuatro bytes:




Como puede verse, con cuatro bytes podemos representar un número entero igual a 2 elevado a la 32ava potencia cercano a 4,295,000,000. Si queremos llevar a cabo cálculos aritméticos con números de mayor capacidad, podemos concatenar bytes adicionales para forma palabras de ocho o diez bytes, pero para nuestros propósitos presentes nos conformaremos con una palabra binaria formada por cuatro bytes.

Al manejar números de cuatro bytes, el formato de estos números de alta precisión es aproximadamente el mismo que el que se utiliza para la representación de los números de 8 ó 16 bits. El primer bit de la palabra binaria extendida puede ser un bit de signo (positivo o negativo), ello depende del que estemos trabajando con números absolutos o con números definidos en el formato de “dos complemento”. La única diferencia verdadera es que el número está extendido a lo largo de cuatro bytes, y que las sumas, restas y otras operaciones aritméticas tienen que ser manejadas en “trozos” de 8 ó 16 bits.

Supóngase que queremos sumar los dos números decimales mostrados a continuación:

8,000,001  +  8,777,215

En la base hexadecimal, esta suma viene siendo (usando el símbolo “$” como prefijo para indicar que se trata de números hexadecimales):

$7A1201  +   $85EDFF

En palabras de cuatro bytes, la representación hexadecimal de esta suma es:

$007A1201  +  $0085EDFF

De esto último podemos escribir de inmediato la representación de la suma en sistema binario, tomando en cuenta la facilidad con la cual se puede convertir un número hexadecimal a su equivalente en sistema binario:

0000 0000 0111 1010 0001 0010 0000 0001 
+
 0000 0000 1000 0101 1110 1101 1111 1111

Para llevar a cabo esta suma, podemos usar dos instrucciones assembler del procesador 6809. La primera instrucción es ADD, esto para sumar cuando no hay un bit de “llevar” viniendo de la derecha en virtud de que se trata de la suma de los dos bytes menos significativos y no hay nada que venga “antes” como resultado de otra adición. Pero una vez sumados los dos primeros bytes, al sumar los siguientes dos bytes (que están a la izquierda de los dos primeros bytes) es probable que tengamos que sumar un “1” que haya resultado como un bit de “llevar” de la suma de los primeros dos bytes, y en tal caso tenemos que usar la instrucción ADC. Por la misma razón, la adición de los siguientes tres bytes también requiere una instrucción ADC al igual que la suma de los siguientes cuatro bytes. Las formas requeridas serán de hecho ADDA y ADCA si usamos el acumulador A como el punto de referencia. De este modo, las operaciones requeridas son:




La instrucción ADC de 8 bits funciona de modo muy similar a la instrucción ADD. Un valor inmediato o los contenidos de una localidad de la memoria son sumados a los contenidos de los registros A ó B. Sin embargo, además de que se suma el segundo operando a lo que hay en el acumulador, el estado del código de condición de “llevar” (Carry) también debe ser sumado. Puesto que esta bandera puede estar puesta a “0” ó a “1”, la suma resulta en ya sea en una adición normal como la producida por la instrucción ADD, o un resultado que es mayor en 1 al resultado producido por ADD.

A continuación tenemos un programa escrito en lenguaje assembler para llevar a cabo la suma de dos números formados cada uno por cuatro bytes, junto con los resultados (en color azul) producidos por el ensamblaje llevado a cabo por el EDTASM sobre el código fuente:




El resultado de la suma de los dos números de cuatro bytes es el número hexadecimal:

$00000001000000000000000000000000

que viene siendo después de llevar a cabo la conversión de una base a otra el número entero (decimal) 16,777,216.