Download Tema 10. Arquitectura y programación de un

Document related concepts
no text concepts found
Transcript
Centro Asociado Palma de Mallorca
Arquitectura de Ordenadores Tutor: Antonio Rivero Cuesta
Unidad Didáctica 3 El Lenguaje Ensamblador Tema 10 Arquitectura y Programación de un Procesador 16 bits II MC68000 En este capítulo se aborda la programación en
ensamblador del MC68000 de forma introductoria,
desde un punto de vista práctico a través de ejemplos.
Introducción Un programa escrito en ensamblador es un texto
escrito siguiendo ciertas reglas sintácticas.
Este archivo de texto es procesado por un programa
denominado programa ensamblador.
Produce como salida un archivo que contiene una
secuencia de ceros y unos agrupados en una columna
de bytes.
Este archivo no es directamente ejecutable y todavía
debe ser procesado por el programa enlazador.
Al archivo de texto o programa escrito
ensamblador se le conoce por programa fuente.
en
Al archivo obtenido por ensamblado se le conoce por
programa objeto.
Es preferible dividir el programa en módulos que se
programan y se procesan con el ensamblador de forma
independiente.
A continuación se procesa por un programa
especializado llamado enlazador, para obtener el
código máquina ejecutable por el procesador
MC68000.
El programa ensamblador reconoce en el programa
fuente ciertas palabras que tienen un significado
especial y que se denominan palabras reservadas.
Reconoce en el texto del programa fuente una serie de
separadores de campos.
Estos separadores son o espacios en blanco o signos
de puntuación como el punto y coma.
Por otro lado, el programa ensamblador toma cada
línea del programa fuente como una orden o
instrucción, salvo que se trate de una línea de
comentario.
La sintaxis de una línea completa del programa fuente
tiene la siguiente sintaxis:
[<etiqueta>] [<mnemotécnico de instrucción> [<operandos>]];[<comentarios>]
Una etiqueta es un alfanumérico que facilita la lectura
del programa y que el programa ensamblador
traducirá por la dirección en la que se deberá ubicar la
instrucción que le sigue.
El campo de operandos puede estar formado por un
operando, por dos operandos separados por una coma,
o puede no existir.
Eso depende del número de operandos que necesite
cada instrucción.
ADD.L D0, D1
Esta instrucción utiliza dos operandos, los registros
D0 y D1, separados por una coma.
NOP ; instrucción que no hace nada
RTS ; instrucción de retorno de subrutina
Las dos anteriores instrucciones no utilizan el campo
de operandos, ya que son instrucciones con modo de
direccionamiento implícito.
El campo de mnemotécnico puede estar constituido
por:

Una instrucción perteneciente al conjunto de
instrucciones del procesador.

Una pseudoinstrucción
ensamblador.
o
directiva
de
Una directiva de ensamblador, a diferencia de una
instrucción no produce código máquina ejecutable.
Es utilizada por el programador para dar instrucciones
de ensamblado al programa ensamblador.
Un ejemplo lo constituye la directiva ORG que sirve
para indicarle al programa ensamblador la dirección
en la que debe comenzar un fragmento de programa
objeto.
ORG
$4000
; dirección de memoria $4000
ADD.L D0, D1
Las dos instrucciones anteriores indican al programa
ensamblador que la instrucción ADD.L debe
almacenarse en la dirección $4000 de la memoria
principal.
Como se ha comentado antes, esas dos instrucciones
de ensamblador sólo generan una única instrucción en
código máquina.
La directiva ORG tiene gran interés para delimitar
zonas de programa y subrutinas, aparte de zonas de
datos.
Al realizar un programa para el MC68000 no se deben
utilizar las direcciones inferiores a 1024 ya que en
ellas se encuentran los vectores de excepción.
Conviene reservar, desde el primer momento, ciertas
zonas para las diferentes partes del programa.
Otro detalle a destacar en el fragmento anterior lo
constituyen los comentarios que ocupan una única
línea.
El programa ensamblador ignora cualquier texto que
aparezca entre el signo «;» y el salto de línea, por lo
que se pueden poner líneas enteras de comentarios
con sólo iniciarlas con punto y coma.
Esos comentarios sólo quedan en el texto del
programa fuente, no ocupan espacio en el programa
objeto y sólo tienen interés para el programador.
Otras directivas de ensamblador que son muy
habituales son las siguientes:

EQU.

DC.

DS.

EVEN.

END.
Otros elementos importantes de un programa
ensamblador lo constituyen los símbolos y las
expresiones.
Los símbolos son, al igual que las etiquetas, nombres
que substituyen a constantes, variables y direcciones
de memoria.
Permiten su utilización en expresiones y son
traducidos automáticamente por el programa
ensamblador facilitando el trabajo al programador.
El programa fuente debe finalizar con la directiva de
ensamblador END.
Sirve para indicarle al programa ensamblador que ahí
es donde finaliza el programa y no tiene que seguir
procesándolo.
Directivas de Ensamblador o Pseudoinstrucciones más Utilizadas La Directiva ORG Sirve para indicarle al programa ensamblador la
dirección en la que debe comenzar un fragmento de
programa objeto.
Su sintaxis es la siguiente:
ORG <dirección o expresión> [;<comentarios>]
Indica el origen absoluto (ORiGen) o dirección
absoluta de las instrucciones de programa que le
sigan.
La Directiva END Sirve para indicar al programa ensamblador que el
programa fuente ha finalizado.
Esta directiva no requiere de operandos.
Su sintaxis es la siguiente:
END [;<comentarios>]
La Directiva EQU Se utiliza para definir un símbolo que se va a utilizar
posteriormente en un campo de operando, en una
expresión o en una etiqueta.
Su sintaxis es la siguiente:
<etiqueta> EQU <valor o expresión> [;<comentarios>]
Todos los campos, excepto el de comentarios, son
requeridos en la pseudoinstrucción.
El programa ensamblador crea una tabla de símbolos
donde anota todos los símbolos definidos o
encontrados en forma de etiquetas y los asocia a un
valor.
Posteriormente
revisa
el
programa
fuente
substituyendo los símbolos o expresiones por los
valores correspondientes.
Un detalle importante reside en que esta directiva no
utiliza memoria ya que no da lugar a ninguna
instrucción en código máquina.
La Directiva DS Se utiliza para reservar posiciones de memoria con
vista a utilizarlas como variables.
Su sintaxis es la siguiente:
<etiqueta> DS.t <número de variables> [;<comentarios>]
La etiqueta va a dar lugar a que el programa
ensamblador la utilice como un símbolo y le
adjudicará la dirección de memoria que apunta a la
variable definida.
El programa ensamblador traducirá ese símbolo por
esa dirección cada vez que lo encuentre en el
programa fuente.
Esta directiva sí da lugar a un incremento del tamaño
de la memoria utilizada por el programa.
Esta directiva permite utilizarla para definir variables
de varios tamaños:

DS.B reserva tantos bytes como variables se
indiquen a continuación.

DS.W reserva palabras

DS.L reserva palabras largas.
La Directiva DC Se utiliza para definir datos constantes.
No debería sufrir modificaciones durante la ejecución
del programa.
Indica al programa ensamblador que debe fijar una o
varias posiciones de memoria como datos y almacena
en ellas los valores indicados.
La sintaxis es la siguiente:
<etiqueta> DC.t <valor o valores> [;<comentarios>]
Tiene el mismo efecto que la directiva DS.t con la
diferencia de que aquí no sólo se reserva espacio en
memoria, también se le asigna un valor.
Ejemplo El siguiente programa se corresponde con el código
máquina, mostrando las direcciones de memoria en
las que se encuentra almacenado, que se muestra
posteriormente.
Ese programa no hace nada, pero sirve de ejemplo de
utilización de las directivas vistas anteriormente.
El programa anterior, al ensamblarlo da lugar al
siguiente programa en código máquina.
Ejemplos de Realización de Estructuras de Datos En este apartado se abordará de forma introductoria
algunas estructuras de datos típicas en los problemas
de programación.
En los lenguajes de alto nivel, este problema suele
estar resuelto ya que es suficiente que el programador
defina un tipo de datos para que el compilador se
encargue de reservar la memoria necesaria.
En lenguaje ensamblador se trabaja directamente
sobre posiciones de memoria y el único apoyo que el
programa ensamblador presta al programador es el de
permitir símbolos y etiquetas que eviten el tener que
trabajar directamente con valores numéricos de las
direcciones de memoria.
Por otro lado la forma de realizar estructuras de datos
o de programa no siempre es única y las que aquí se
exponen no deben tomarse como excluyentes de otras
posibilidades sino más bien como orientaciones.
Definición de Constantes Una instrucción de alto nivel como:
CONST coef = 34;
Se trataría en ensamblador como
coef DC.t
34
;se reservan posiciones de memoria
inicializándolas
Aquí se ha dejado deliberadamente sin especificar el
tamaño del dato.
En el caso del MC68000 se permiten los tamaños:

byte.

palabra.

palabra larga.
En el caso de un compilador de un lenguaje de alto
nivel este dato suele estar definido, por defecto, a 16
bits y debe especificarse mediante la directiva
correspondiente en caso de que se quiera modificar.
Definición de Variables coches
DS.L 1
;se reserva una palabra larga de
memoria
El programa ensamblador se encarga de asignar la
dirección de memoria reservada a la etiqueta coches
que puede ser utilizada posteriormente como nombre
de variable en el programa fuente en ensamblador,
pero siempre con tamaño palabra larga y teniendo en
cuenta el tipo de dato que contiene.
Definición de Vectores de Datos El tratamiento de este caso es similar al anterior pero
reservando un dato, del tamaño oportuno, por cada
componente del vector.
La reserva se produce en posiciones de memoria
contiguas.
Este detalle es de gran importancia ya que en el
programa hay que tener mucho cuidado con leer
correctamente cada posición o con no salirse de la
zona reservada para el vector.
En ensamblador, el programador debe de tener en
cuenta este detalle.
Por ejemplo, si se define un vector de cuatro
componentes tipo entero de 16 bits, en lenguaje de
alto nivel se puede hacer referencia a la componente
vector[3] sin prestar más atención.
Sin embargo en ensamblador sólo se dispone de la
etiqueta de la cabecera del vector y cualquier
referencia hay que realizarla especificando el
desplazamiento oportuno.
Es el caso del ejemplo del apartado (1.1.6).
La asignación de lenguaje de alto nivel:
D0 := vector[3]
equivale a una transferencia de un dato tamaño
palabra mediante la instrucción:
MOVE.W vector+4, D0
cuatro bytes por delante de vector.
Por lo tanto, la definición de una variable tipo vector
en un lenguaje de alto nivel, de cuatro componentes se
realizaría como sigue:
VAR vector=ARRAY[1..4] OF integer;
Esta definición de un vector en un lenguaje de alto
nivel se puede trasladar a lenguaje ensamblador
mediante una reserva de cuatro posiciones de
memoria, asignando la dirección de inicio de la
primera componente a una etiqueta.
Ésta sería una posibilidad:
vector DS.B 4
reservaría espacio para 4 componentes de 8 bits;
vector DS.W 4
reservaría espacio para 4 componentes de 16 bits, y
vector DS.L 4
reservaría espacio para 4 componentes de 32 bits.
Cadenas de Caracteres Se trata de situar en memoria variables de tipo cadena
de caracteres.
No se trata del tipo carácter (CHAR) que se almacena
corrientemente en un único byte.
Sino de una cadena de éstos y cuyo tamaño puede ser
variable.
Normalmente se reserva una cantidad determinada de
bytes en el momento de definir la cadena.
En ensamblador puede codificarse realizando una
reserva de memoria para la variable cadena de 50
bytes:
cadena
DS.B 50
;se reservan 50 bytes en memoria
Para manejar cadenas se pueden seguir dos enfoques:

Procesar los 50 bytes de cadena.

Procesar sólo aquellos que contengan algún
carácter.
El enfoque usual es este último para lo que conviene
utilizar un marcador que permita identificar el final de
la cadena.
Los marcadores más utilizados son:

$0A.

$00.
Podría utilizarse cualquier otro a condición de no
coincidir con ningún carácter ASCII imprimible.
El marcador $00 presenta ventajas ya que la
instrucción MOVE.B también modifica el bit Z de
estado y permite implementar algoritmos de
procesamiento de cadenas de una forma muy sencilla.
Cargando un registro interno desde memoria
MOVE.B
(An)+, Dn ; se recorre la cadena de caracteres
Se va actualizando el bit Z que se pone a 1 cuando se
lea el marcador $00 y señala sin comparaciones el
final de la cadena.
Al crear cadenas también es útil el aumentar en una
posición el tamaño de la cadena e inicializarla con el
valor $00.
Así un bucle que recorra la cadena tiene asegurada su
terminación:
El bloque anterior representa el fragmento de
memoria utilizado por la variable cadena cuando se ha
inicializado con el valor ‘HOLA’, y utilizando una
posición adicional.
En este ejemplo se han utilizado caracteres pero lo
que realmente se almacena en memoria son los
números de código correspondientes a estos caracteres
según ASCII.
Si para este ejemplo suponemos, además, que la
etiqueta “cadena” representa a la dirección $3A00,
entonces se tendría la siguiente situación en memoria.
Los valores ASCII correspondientes con la palabra
“HOLA” son los siguientes:

‘H’ = $48.

‘O’ = $4F.

‘L’ = $4C.

‘A’ = $41.
Pilas Una pila es un espacio de datos de tamaño infinito en
el que, a partir de una dirección base, se van
almacenando los datos.
Unicamente se necesita de un puntero o registro que
almacene la dirección del dato que ocupa la cabeza de
la pila.
En el MC68000 existe ya una pila definida por USP o
registro A7.
Cada vez que se almacena un dato en esta pila, el
puntero se dirige a direcciones decrecientes.
Cada vez que se extrae un dato de la pila el puntero
incrementa su valor.
Sin embargo se pueden crear otras pilas por
programación o pilas de usuario.
Las instrucciones adecuadas para el manejo de estas
pilas son:
Como es lógico, la cantidad de memoria disponible no
es infinita sino que depende de cada sistema físico y
nunca puede exceder de la memoria direccionable por
el procesador.
Esta característica muestra que en la práctica una pila
se declara reservando una tamaño finito de palabras y
programando una subrutina que compruebe, en cada
acceso, que la pila no se desborde.
Un ejemplo podría ser:
En el fragmento anterior se reservan 512 bytes en
memoria que van desde la dirección etiquetada como
pila y que finaliza 512 posiciones después.
A continuación se define el puntero para manejar esa
pila creciendo hacia direcciones inferiores.
Para ello se define una variable denominada punt_pila
y se inicializa con un valor que es precisamente la
dirección que ocupa.
Esto es posible porque se utiliza como valor el
símbolo de la propia etiqueta y que el programa
ensamblador
sustituirá
por
la
dirección
correspondiente a la etiqueta punt_pila.
La representación gráfica de la memoria del resultado
es la siguiente (suponiendo que pila = $4000):
Si ahora se emplea, por ejemplo, el registro A6 para
almacenar momentáneamente este puntero, entonces
para almacenar un dato contenido en D3 se podría
ejecutar el fragmento siguiente:
Para extraer un elemento de la pila el procedimiento a
seguir es justo el contrario:
Estructuras de Programa En este apartado se tratarán, de forma muy básica, las
estructuras de programa más habitualmente utilizadas
para realizar un algoritmo.
Las estructuras básicas son:

La secuencia de instrucciones.

Las bifurcaciones.

Las iteraciones.

Los saltos a subrutinas.
Secuencia de Instrucciones Se trata de situar en un orden determinado una
instrucción tras otra.
Por ejemplo, un simple problema del cálculo del área
de rectángulo involucra tres variables: base, altura y
área.
Una vez definidas estas variables como posiciones de
memoria, es posible utilizarlas en lenguaje
ensamblador como etiquetas:
En el MC68000 no se puede operar directamente
sobre memoria y es preciso que los contenidos de
estas variables y que se hallan en memoria sean
transferidos a los registros internos del procesador
para su procesamiento.
La transferencia de memoria a un registro suele
denominarse carga.
La transferencia en sentido contrario se denomina
almacenamiento.
Esta nomenclatura procede de las instrucciones
LOAD y STORE.
Para realizar la carga se seleccionan unos registros no
utilizados y cuyos contenidos no necesiten ser
conservados.
Por ejemplo seleccionemos los registros R0 y R1.
Se aprecia que el resultado ha modificado R0, por lo
que si se desea volver a operar con la variable altura
entonces es preciso recurrir a una nueva carga.
Se aprovecha este ejemplo para advertir que no es
necesaria la carga del primer operando del producto,
base, ya que la instrucción MULU permite el
direccionamiento directo.
El programa anterior quedaría como sigue:
La asignación entre variables en alto nivel es muy
sencilla aunque en ensamblador requiere la carga
previa en uno de los registros internos.
Se realizaría como sigue (ejemplo para tamaño W):
Conviene tener presente que si una variable se utiliza
varias veces en una secuencia de instrucciones
siempre es susceptible de no ser cargada o
almacenada más que en una única ocasión.
Bifurcaciones Las bifurcaciones se basan en comparaciones que
determinarán el camino a seguir por la ejecución del
programa.
En ensamblador estas bifurcaciones se apoyan en
instrucciones de salto condicional.
Como las condiciones necesitan de la evaluación de
los códigos de condición también suelen necesitar de
una instrucción de comparación.
Existen varios tipos de bifurcaciones condicionales.
La bifurcación más sencilla es la representada por la
instrucción de alto nivel:
La comparación modifica el CCR y no debe
modificarse antes de ejecutar la instrucción de salto
condicional, por lo que lo mejor es que ésta se
encuentre inmediatamente después de la comparación.
A veces es más sencillo negar la comparación de
variables buscando el código de condición alternativo
y eliminar el salto incondicional BRA:
Iteraciones Por iteraciones entendemos aquellos fragmentos de
programa que se ejecutan repetidamente varias veces.
Las iteraciones también se denominan bucles ya que
al ejecutarse la última instrucción de ese fragmento se
vuelve a ejecutar la primera.
Bucles FOR En estas iteraciones el fragmento de programa
contenido en el bucle se repite un número de veces
predeterminado y, por lo tanto, requiere de un
contador que debe ser evaluado para conocer cuándo
se finaliza la ejecución del mencionado bucle.
Se necesita crear una variable para utilizarla como
contador que se comparará con el valor n.
Puede ser preferible alojar dicho contador en un
registro interno que se incrementa.
Aquí seguiremos esta última orientación y elegiremos,
por ejemplo, D5.
Bucles WHILE Estos bucles se caracterizan por evaluar la condición
de finalización de bucle justo antes de ejecutar las
instrucciones del cuerpo del bucle.
De esta forma el bucle puede no ejecutarse nunca si la
condición es falsa al evaluarla por primera vez.
En un lenguaje de alto nivel estos bucles se pueden
codificar como sigue:
Bucles REPEAT‐UNTIL Como sucede en los bucles while aquí también está
indefinido el número de veces que se ejecuta la
iteración.
También debe analizarse detenidamente para poder
asegurar que se ejecuta un número finito de veces,
esto es, que finalice la iteración y no se entra en un
bucle sin fin.
La característica que diferencia este tipo de bucles del
anterior consiste en que la condición de finalización
se evalúa al final del bucle y se asegura que se ejecuta
al menos en una ocasión.
Bucles LOOP Aunque se trata de iteraciones menos habituales, en
ocasiones se tiene la necesidad de evaluar la
condición de terminación de una iteración en uno o en
varios puntos del cuerpo del bucle distintos del inicio
y del final.
En estos bucles al llegar al final se salta al comienzo
del mismo y este proceso se repite indefinidamente.
En el interior del bucle debe existir entonces al menos
una comparación de terminación que determine si se
abandona el bucle o no.
Subrutinas Facilitan mucho la programación ya que permiten
simplificar mucho cualquier programa ya que cada
uno de estos subprogramas pueden ser ejecutados
mediante una llamada desde cualquier punto del
programa o desde cualquier módulo.
El único requerimiento consiste en que se le deben
suministrar los datos a utilizar de una forma más o
menos explícita.
Una subrutina es, por lo tanto, un fragmento de código
que interesa realizar fuera del código principal.
Un motivo para esto es que se trata de realizar labores
concretas que pueden repetirse muchas veces en otros
programas y puede ser conveniente aislar de un
programa principal para ensamblarlo aparte e incluirlo
en una librería.
La conexión entre ambos fragmentos se realiza con
instrucciones de salto especializadas:

BSR o JSR.

RTS o RTE.
La llamada a esta subrutina se produce en el programa
principal mediante la instrucción
BSR
subrutina
; salta a subrutina
Esta instrucción no sólo realiza el salto a la dirección
aquí etiquetada como etiqueta sino que, además
guarda el contenido del contador de programa en la
pila.
De esta forma se permite al procesador continuar con
la instrucción siguiente una vez finalizada la ejecución
de la subrutina.
Sin embargo para que suceda esto la instrucción RTS
debe ser la última de dicha subrutina.
Esta instrucción realiza el salto contrario volviendo a
la instrucción siguiente a la que se había realizado la
llamada a la subrutina.
Si la subrutina se encuentra a una distancia del punto
de llamada mayor a 256 bytes entonces se utiliza la
instrucción JSR.
No siempre se necesita llamar explícitamente a la
subrutina ya que en ocasiones la llamada la realiza el
propio procesador.
Esto es lo que ocurre cuando un dispositivo externo al
procesador activa una línea de petición de
interrupción.
En este caso las subrutinas suelen denominarse
rutinas de atención a una interrupción.
El mecanismo consiste en que se genera un evento
que da lugar a que el procesador guarde información
de su estado y pase a realizar un salto a la dirección
donde se encuentra la subrutina que puede gestionar
ese evento.
En estos casos la instrucción de retorno es RTE.
En la operación con subrutinas se aprecia que deben
existir dos partes diferenciadas en el programa:

una parte principal que se conoce como
programa principal.

un subprograma o subrutina.
La estructura podría ser la siguiente:
Otro detalle fundamental a tener en cuenta es cómo
transfiere los datos el programa principal a una
subrutina.
Una forma muy sencilla de traspasar los datos u
operandos a una subrutina consiste en utilizar los
registros internos del procesador, D0, D1, ... D7.
En este caso se suele hablar de paso de datos por
valor.
Este método es muy rápido y sencillo pero presenta
importantes limitaciones.
Dos características muy importantes de las subrutinas
son la posible recursividad o la posible reentrada de la
propia subrutina.
Conjunto de Instrucciones Ver páginas 506 y siguientes de libro base.