PROGRAMACION EN ASSEMBLER | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
En este capítulo se está ya en condiciones de saber qué es la programación en ASSEMBLER; esto es, escribir una serie de códigos entendibles por el usuario que posteriormente serán convertidos en código de máquina entendible por el microprocesador, en este caso el Z-80. La programación en ASSEMBLER requiere cuidados especiales si se desea sacar el máximo rendimiento, por ejemplo, ante dos instrucciones que obtengan el mismo resultado se debe elegir aquella que tenga menos ciclos de máquina o de reloj, o aquella que ocupe menos posiciones de memoria; incluso en algunos casos habrá que elegir entre ocupar menos posiciones o ser más rápidos, en función de las necesidades que se tengan. Esto no quiere decir que sea necesario conocer de memoria los ciclos de cada instrucción; un manual de ASSEMBLER debe contener toda la información necesaria, con un método de acceso fácil, a pesar de que en algún caso resulte redundante. Se pretende que este curso, además de como tal, pueda servir en el futuro como un manual de consulta rápida, por lo que en algunos casos, es posible que el lector encuentre información reiterada. Otra buena costumbre cuando se programa en ASSEMBLER es poner comentarios; siempre hay una manera de ponerlos en cada instrucción o intercalados entre ellas. Los comentarios sólo ocupan lugar en el código simbólico o programa fuente; cualquier ensamblador los ignora cuando convierte el programa en código de máquina, lo cual quiere decir que no ocupará más un programa absoluto porque su simbólico tenga comentarios, pero tampoco irá más despacio. Cuando pase el tiempo y queramos modificar alguna parte del programa y se haya olvidado el porqué de cada instrucción, los comentarios serán de gran ayuda. Siguiendo con la exposición de buenas costumbres nos referiremos, por último, al empleo de subrutinas. Ya veremos cómo se hacen y cómo se accede a ellas, pero hay que irse mentalizando a su uso. Esto es importante en este momento porque se trata de un problema de estructura del programa. Las ventajas son múltiples; una estructura de subrutinas es más fácil de entender, por lo tanto de modificar. Se da con frecuencia el caso de necesitar en un programa operaciones iguales o semejantes a las de otro, por lo tanto, con limitarse a copiar totalmente estas partes o como mucho, adaptarlas algo a las características del nuevo programa, saldríamos adelante. Resumiendo, hay que acostumbrarse desde un principio a estos métodos y a la realización del organigrama, sobre el cual daremos algunas orientaciones al final de este capítulo. Toda esta información, más los diseños que se hagan de pantalla, de campos o de tablas, se guardan juntas en una carpeta o cosidas con grapa y se conseguirá una buena documentación de cada programa, documentación que nos será muy útil en el futuro. Realización de un programaExisten una serie de pasos que se deben seguir para dar por definitiva la realización de un programa. Cuando más detallada sea la organización de estos pasos, más fácil será la realización del siguiente. El concepto programa abarca a todo lo que se puede realizar en una corriente de instrucciones dentro de un ordenador, por lo cual, tan programa es un juego, como llevar la contabilidad de una casa o la solución de problemas científicos; la metodología a seguir es la misma. PASOS A SEGUIR:
Todo lo anteriormente descrito se puede hacer o no, pero son métodos ya muy estudiados que producen unos buenos resultados. La metodología anteriormente descrita es una orientación de la más elemental; se pueden usar técnicas más complicadas pero que se salen un poco de lo que es un micro-ordenador. Se dice que en un programa vale todo, siempre y cuando funcione. Si existe un método que lleve a un buen resultado, no sólo que funcione, sino que sea lo más rápido posible, que no se repitan procesos, que ocupe la menor cantidad de memoria y que esté claro, ¿por qué no usarlo? Formatos de instrucción en código máquinaLa memoria del SPECTRUM está dividida en octetos. Un octeto es una agrupación de 8 bits y un bit es la unidad más pequeña de información; sólo puede informar de dos estados: o está activo o no. Cuando de un SPECTRUM se dice que tiene 16K indica que tiene, aproximadamente, 16.000 octetos de memoria disponible, que son exactamente en decimal 16.384; para uno de 48K se multiplica esta cantidad por 3. Los 48K de memoria disponible en RAM más los 16K que usa el programa monitor en ROM, suman 64K, que son 65.535 octetos. Esto es, desde la posición 0 de memoria a la 65.535, que es en hexadecimal FFFFh y en binario 1111111111111111b y a su vez la máxima cantidad que se puede escribir en dos octetos. Las instrucciones en el SPECTRUM pueden ocupar 1, 2, 3 o 4 octetos en función del código de operación y básicamente, tiene los formatos que se ven en la FIGURA 5 del CAPITULO 3. Como se vé, lo que es común a todos los formatos es que los primeros tienen el código de operación y los últimos el operando. Sobre el operando hay que tener en cuenta que cuando son dos octetos el microprocesador espera encontrar el octeto de orden inferior en el primer octeto del operando, y el de orden superior en el segundo, de tal forma que al referirse la valor de operando 7F3Bh el ensamblador almacenará la instrucción en memoria de la siguiente manera:
Existe una combinación diferente de bits para cada instrucción en el código de operación, a pesar de que la única diferencia sea el registro usado. Por lo tanto, habrá una parte fija y otra variable en el código de operación en función de dichos registros. Otra cosa muy curiosa es ver cómo el microprocesador va leyendo una serie de octetos llenos de instrucciones cuando estos pueden tener cuatro longitudes distintas. Es muy sencillo. Se recordará que el microprocesador tiene un registro PC que indica la dirección de la memoria donde está la próxima instrucción a ejecutar. Cuando la lee, en función del código de operación, sabe cuantos octetos ocupa; en ese momento sólo tiene que incrementar el registro PC en esa cantidad, mientras va leyendo los restantes octetos de la instrucción, para que cuando vaya a leer la siguiente instrucción, este registro ya la tenga apuntada. Ver FIGURA 4-2.
Necesidad de conocer el código máquina Podríamos preguntarnos: voy a escribir mi programa en el lenguaje simbólico ASSEMBLER, tengo un ensamblador magnífico que me lo va a traducir, ¿qué necesidad tengo de conocer que bit tiene que estar activo y cual no? Desde luego, el que tenga un buen ensamblador y escriba un programa de principio a final, buena gana de estar descifrando códigos y pasándolos de binario a hexadecimal. Pero no siempre se dispone de un buen ensamblador o se quiere estar cargándolo para pocas instrucciones, con lo cual se meterían en la memoria los valores de las instrucciones y se saltaría a esa posición con lo que el microprocesador empezaría a trabajar. Además, existen algunas técnicas en programación que hacen necesario este conocimiento, por ejemplo:
Por otro lado, no es necesario tener un ensamblador para poder utilizar el Spectrum en código máquina; es posible escribir el programa en Assembler y traducirlo a código máquina, manualmente; si el programa no es muy largo, la labor no resulta excesivamente tediosa. De hecho, es preferible aprender primero a ensamblar «a mano», ya que esto proporciona un conocimiento más profundo del Assembler, y facilita la utilización posterior de un programa ensamblador. En este mismo curso, describimos en detalle la utilización del que consideramos el mejor ensamblador que se ha escrito para el Spectrum, el "GENS 3", que desde ahora, recomendamos a nuestros lectores; mientras tanto, indicaremos con todo detalle la forma de ensamblar "a mano" cada instrucción y todos los ejemplos que demos, se podrán introducir en el ordenador sin necesidad de ensamblador, por lo que para seguir este curso, sólo es necesario disponer de un Spectrum. Formatos de instrucción en lenguaje simbólicoComo se recordará, el lenguaje simbólico es aquél en el que escribimos el programa fuente. Los códigos nemotécnicos que se han utilizado para el microprocesador Z-80, son abreviaturas de las palabras inglesas que definen la operación que realizan. El formato de la instrucción y sus normas de sintaxis pueden variar algo en función del ensamblador elegido; pero siempre hay unas reglas mínimas que suelen cumplir y a esas nos referiremos hasta el capítulo que trate más profundamente el ensamblador. El formato normal es el siguiente:
ETIQUETA. La etiqueta es opcional, sólo debe ponerse cuando sea necesario referirse a esta instrucción desde otra, bien para saltar a ella o para modificarla. Como etiqueta sirve cualquier sucesión de letras o números siempre que empiece por una letra; los espacios no son significativos, por lo cual se usa el símbolo "_" para separar palabras. Sólo los seis primeros caracteres son tratados como etiqueta. Ejemplos:
NEMOTECNICO. Es el código de la instrucción y siempre estará presente pues es el que propiamente la define. Consta de 1 a 4 letras mayúsculas que recuerdan en parte la operación que realizan. Ejemplo:
OPERANDOS. Este es el campo más variable de la instrucción. Muchas instrucciones no tienen necesidad de que se les definan operandos, ya que estos están implícitos en su operación. Otras tienen necesidad de tener definidos dos operandos, en este caso irán separados por coma ",". Operando podrá ser un número, una etiqueta, un registro o un par de registros. Cuando el valor del ooperando se refiera al contenido de la posición de memoria indicada, el operando se pondrá entre paréntesis. Ejemplos:
COMENTARIOS. En un número limitado de caracteres, es una explicación del porqué y para qué de esta instrucción. Va separado por ";" de los operandos. Ejemplo:
Contador de posición El ensamblador, en tiempo de ensamblaje (mientras está ensamblando), mantiene un contador de posición (location counter). Este contador tiene el valor de la dirección de la instrucción que se está ensamblando. Es posible acceder a este valor usando el símbolo "$" (dólar) que lo representa. Este símbolo se usa como una etiqueta en el campo de operando de la instrucción, de tal forma que si se quiere saltar a diez posiciones de memoria más adelante, se saltaría a "$+10". Cuando se use esta facilidad hay que tener en cuenta el número de octetos de cada instrucción. Generación de palabras de datosPor la misma razón que en el formato de instrucciones, pueden existir diferencias entre ensambladores cuando se definen datos; por lo tanto, seguiremos en la línea de lo que suele ser habitual. La definición de datos se hace por medio de unos códigos seudonemotécnicos, también llamados directivos, que actúan de una manera similar a las instrucciones. Estos directivos sólo tienen valor en tiempo de ensamblaje (en realidad son comandos del ensamblador y no tienen código de operación), generan una o varias palabras de datos y quedan definidas dentro del programa absoluto. El formato es el siguiente:
ETIQUETA: Sigue las mismas normas que para instrucciones, y su uso está justificado por la necesidad de acceder a los datos. Sólo es obligatorio con el directivo EQU. SEUDO-NEMOTECNICO: Son una serie de caracteres en mayúsculas, basados en el idioma inglés, que recuerdan el tipo de dato que definen. Como más usuales citaremos: EQU expresiónDiagrama de flujo Conocidos también como organigramas u ordinogramas, son una construcción gráfica del programa. Un buen organigrama facilita la codificación posterior y proporciona una representación visual de todas las situaciones o ramas del programa. Si se utilizan los símbolos estándar, cualquier otro usuario podrá entenderlo; por lo tanto, se definirán a continuación los más utilizados, que son suficientes para la realización de los organigramas del SPECTRUM. Como normas generales se tendrá en cuenta:
Símbolos básicos
Símbolos especializados de entrada/salida
Símbolos especializados
Ejemplos:
Otros símbolos usados Básicos Especializados de entrada/salida
Especializados de proceso Una tabla de saltos se puede representar de la siguiente manera: En la FIGURA 4-3, se puede ver un ejemplo de lo que podría ser un organigrama que representara las actividades básicas de una persona. Creemos que el ejemplo es bastante ilustrativo de cómo se hace un organigrama. Esperamos, no obstante, que ninguno de nuestros lectores rija su existencia por un bucle de tan escasas posibilidades.
Presentación de las instrucciones A partir del próximo capítulo iremos estudiando por grupos, todas las instrucciones que usa el Z-80. Veremos la forma de utilizarlas en Assembler, y la forma de ensamblarlas en código máquina para aquellos que no dispongan de ensamblador. También veremos una serie de ejemplos que irán creciendo en complejidad, y que el lector podrá teclear en su ordenador para irse habituando al uso de este lenguaje. Las instrucciones se presentarán de la siguiente manera:
Por otro lado, tambíen veremos ejemplos que se podrán introducir en el ordenador, y cuya realización explicaremos de forma exhaustiva. De cada ejemplo, se dará el listado Assembler, para que quien lo desee, pueda teclearlo por medio de un ensamblador. Para quienes no dispongan de ensamblador, se acompañará cada ejemplo de un programa en Basic (también explicado), que introduzca el código en memoria y lo ejecute. Ejecución de código máquina en el SpectrumQuienes dispongan de ensamblador, deberán mirar las instrucciones del mismo, para ver cómo deben introducir sus programas en memoria. En cualquier caso, en un capítulo posterior, estudiaremos en profundidad el manejo de ensambladores, y concretamente, del GENS 3, que a pesar de todo, tiene el pequeño inconveniente de traer las instrucciones en inglés. Por ahora, aprenderemos a utilizar el código máquina desde el Basic, construyendo pequeños programas cargadores de C/M. Para introducir en el Spectrum un programa en C/M, empezaremos por escribirlo en Assembler sobre un papel. Una vez decidido en qué lugar de la memoria lo vamos a cargar, lo ensamblaremos a mano siguiendo las normas que daremos en los siguientes capítulos. El resultado, será una serie de números, comprendidos entre 0 y 255, que constituyen el código máquina propiamente dicho. Mediante un bucle FOR ... NEXT en Basic, vamos introduciendo estos números en sucesivas posiciones de memoria a partir de la RAMTOP (que previamente habremos bajado). Y finalmente, utilizaremos la función USR para ejecutarlo. Veamos un ejemplo: Supongamos que el programa que deseamos cargar, está representado por los números: 12, 65, 87, 80, 68, 91, 18, 71, 33, y 27 (en este ejemplo, los números son aleatorios, así que no se moleste nadie en desensamblarlo, por que no tiene sentido). Supongamos también, que lo queremos introducir a partir de la dirección 50000 y que se ejecuta a partir de 50005 (un programa en C/M no tiene por qué ejecutarse siempre desde la primera dirección). Nuestro programa en Basic, empezaría por:
A continuación, utilizaremos un bucle FOR ... NEXT para introducir el código.
Ahora, sólo nos queda ejecutar el programa; para ello utilizaremos la función USR, que como todos saben, nos devuelve en el retorno, el contenido del par de registros BC (como regla nemotécnica, acuérdese de "Basic Comunicator", -comunicador con el Basic-). USR, como toda función, debe ir precedida de un comando, el que utilicemos, dependerá de lo que queramos hacer con el resultado; si no nos importa el valor de BC en el retorno, podemos hacer RANDOMIZE USR ... que sólo ocupa dos bytes. Si queremos imprimir el resultado, podemos hacer PRINT USR ... y si queremos asignar el resultado a una variable, para luego trabajar con él, podemos hacer LET a=USR ... En cualquier caso, detrás de USR deberá ir la dirección a partir de la cual se debe ejecutar nuestro programa. Supongamos que en nuestro ejemplo, no nos importa el resultado, así que haríamos:
Con lo que el Sistema Operativo pasa el control a nuestro programa en C/M, hasta que el microprocesador se encuentre una instrucción de retorno, ya que el S/O (Sistema Operativo) trata a nuestro programa como si se tratase de una subrutina suya; esto se verá más claramente cuando estudiemos el capítulo dedicado a las subrutinas. Codificación hexadecimalCon el procedimiento visto hasta ahora, utilizamos 10 números metidos en DATAs, para representar un programa de 10 bytes de longitud. Estas DATAs, nos ocuparán cerca de 70 bytes de memoria dentro del programa Basic; si tuviéramos que representar en DATAs un programa de 2K (2048 bytes), probablemente, no nos cabrían los DATAs en un 16K. Para evitar esta forma de malgastar la memoria, existe un procedimiento al que quizá esté acostumbrado el lector por los listados de nuestra revista, este procedimiento consiste en codificar el programa en hexadecimal, e introducirlo como una cadena de caracteres, que sólo ocupará en DATAs el doble de la longitud del programa. Veámoslo con un ejemplo: Primero haríamos:
De la misma forma que antes, pero esta vez, definiremos una función que nos ayude a decodificarlo.
Puede parecer complicado, pero esta función nos ayuda a pasar los números de hexa a decimal antes de POKEarlos en las direcciones de memoria. Usaremos también, una suma de comprobación (checksum) para detectar si nos equivocamos al teclear los DATAs. El programa seguiría:
La línea 30 lee toda la cadena, la suma de comprobación y pone a cero el contador de checksum. El bucle entre las líneas 40 y 80, va leyendo los caracteres de la cadena de checksum y finalmente, los introduce en la dirección adecuada. En la línea 90, se detectan los posibles errores, comparando el contador de checksum con la suma correcta que está en la línea 120. Finalmente, la línea 100 ejecuta el programa de l misma forma que en el caso anterior. La cadena de la línea 110, está compuesta por la representación hexadecimal de los números que componen el código máquina que queríamos introducir en el ordenador. Dónde ubicar un programa en C/MEn principio, un programa en código máquina se puede colocar en cualquier lugar de la memoria, de hecho, existen programas comerciales que la ocupan prácticamente por completo. No obstante, para nuestros fines existen zonas más adecuadas que otras. Se supone que un programador aficionado, utilizará rutinas en C/M combinadas con un programa principal en Basic, por lo que habrá que respetar una zona de memoria para que el Basic pueda trabajar. Básicamente, existen cuatro zonas donde situar nuestros programas:
Veámoslas una por una: 1. Por encima de la RAMTOP: Es la zona más adecuada para colocar un programa en C/M, ya que queda protegido de borrados por el sistema Basic. En primer lugar, deberemos bajar la RAMTOP con el uso de CLEAR, como se veía en el ejemplo anterior. Una vez cargado nuestro programa, no podrá ser borrado ni siquiera con NEW; para volver a la situación inicial, deberemos teclear:
Que sí borrará el programa en C/M y todo lo que haya en la memoria del ordenador. Otra forma de destruir nuestro programa sería volver a subir la RAMTOP. 2. En el buffer de impresora: Existe en la RAM, una zona reservada de 256 bytes, que empieza en la dirección 23296 (5B00h) y acaba en la 23551 (5BFFh); esta zona la utiliza el Spectrum cuando trabaja con una impresora tipo ZX-Printer (Alphacom-32 o Seikosha GP-50S); si no va a utilizar ninguna de estas impresoras, puede almacenar en esta zona una rutina corta (256 bytes máximo) que no le ocupará, por tanto, memoria en la zona de programa. Tenga en cuenta, no obstante, que su rutina será borrada si utiliza los comandos: NEW, LPRINT, LLIST y COPY. 3. En el archivo de pantalla: En casos especiales, se utiliza el archivo de pantalla para almacenar programas en C/M, es una técnica usada en algunos copiadores para no ocupar memoria útil. Si no desea "ensuciar" la pantalla, puede poner los atributos correspondientes al mismo color de tinta y papel, con lo que los bytes no se visualizarán en forma de pixels. Cuando utilice esta técnica, tenga en cuenta que su programa puede ser corrompido por el uso de NEW, CLEAR y cualquier comando que afecte a la pantalla. El archivo de pantalla va desde 16384 (4000h) hasta 22527 (57FFh). 4. Dentro del programa Basic: Esta era la técnica usada en el ZX-81, consiste en hacer que la primera línea del programa sea una línea REM, con tantos espacios, como bytes tenga el programa C/M a almacenar. La dirección de inicio de esta zona es (PROG)+5. Este método tiene la ventaja de poder salvar juntos el Basic y su Código Máquina, si bien, su empleo no es recomendable si se tiene conectado el INTERFACE 1, ya que este dispositivo desplaza el programa Basic, y por tanto, nuestra rutina en C/M, a menos que esta sea reubicable y entremos en ella, calculando cada vez la dirección de entrada a partir del contenido de la variable PROG. En este caso, nuestra rutina sólo se borra editando la línea, o borrando el programa Basic con NEW. De todos éstos, el sistema usado con más frecuencia es el primero, y es el que usaremos en nuestros ejemplos. Si se tiene conectado un interfce de impresora INDESCOMP, ha de tenerse en cuenta que su software ocupa los 1000 bytes más altos de la memoria. |