Práctico 5

Anuncio
Aspectos Avanzados de Arquitectura de Computadoras
Práctico 5
Práctico 5
Tema: Assembler 8086. Recursión en 8086. Entrada/salida en 8086.
Objetivo: Familiarizarse con las instrucciones, registros y modos de
direccionamiento del microprocesador 8086. Compilar rutinas recursivas
manipulando adecuadamente el stack. Realizar accesos a E/S e implementar rutinas
de atención a los dispositivos (programada o interrupciones).
Nota:
 Se deben resolver todos los problemas en alto nivel y luego compilarlos a
assembler.
 Excepto cuando se indique las variables están almacenadas en el segmento
DS.
Ejercicio 1
Implementar una rutina en assembler 8086 que dados: D1 (dirección de memoria),
D2 (dirección de memoria) y N, toma N bytes consecutivos comenzando en D1 y los
copia a partir de D2. D1 viene dado en el registro AX, D2 en BX y N en CX.
Nota: AX no puede usarse para acceder a memoria.
Ejercicio 2
Un compilador emplea la siguiente manera de traducir la comunicación entre una
función y el programa que la invoca:
 Los argumentos de la función (que se puede suponer todos de 16 bits) se
envían al stack en el orden en que son declarados en la llamada a la función.
 La subrutina que implementa la función deja los argumentos en el stack y
devuelve el resultado en el registro AX, se deben mantener los valores de los
demás registros.
Se pide:
(a) Compilar en Assembler 8086 una función que haga el AND de tres palabras
de 16 bits (cumpliendo con las reglas anteriores):
function AND(x, y, z: integer) return integer;
(b) Escribir un programa Assembler 8086 que calcule el AND de los registros
AX, BX y CX llamando a la rutina de la parte anterior.
Ejercicio 3
Se considera la siguiente estructura de árbol binario:
type nodo =
record of
dato: integer;
hijoIzq,hijoDer: byte;
end
arbol = array 
0..(MAX_NODOS-1)of nodo;
donde:
dato es un entero de 16 bits.
hijoIzq e hijoDer son índices a los subárboles izquierdo y derecho
respectivamente.
Página 1 de 8
Aspectos Avanzados de Arquitectura de Computadoras
Práctico 5
El árbol tiene por lo menos un elemento. El valor 0 en hijoIzq o hijoDer significa que
ese nodo no tiene el sucesor correspondiente.
Se pide:
 Escribir en alto nivel una función RECURSIVA que busca un entero en el
árbol y devuelve TRUE si está y FALSE en caso contrario. La función recibe
como argumentos el entero a buscar y un índice al árbol o subárbol donde
buscar. Compilar la rutina en assembler 8086 sabiendo que en AX se recibe
el entero y en BX el puntero al árbol o subárbol. El árbol esta cargado en
memoria como un array de nodos a partir de la posición 0 del Extra-Segment.
El resultado se devuelve en el registro CL (1 es TRUE y 0 es FALSE). Se
deben conservar todos los demás registros.

Calcular el tamaño mínimo que debe tener el stack para que la función pueda
ser ejecutada en todos los casos, cualquiera sea el tamaño del árbol.
Ejercicio 4 ( Ex. Arq. 2 22 de diciembre de 2010)
Considere la siguiente estructura de datos de árbol y la declaración de la función
crearArbol.
typedef struct {
unsigned int altura;
*nodo izq, der;
} nodo;
nodo *crearArbol (nodo **buffer, unsigned int profundidad)
La función crearArbol crea un árbol completo y balanceado (todo nodo interno
tiene dos hijos y todas las hojas están a la misma profundidad). Recibe por
parámetro la profundidad del árbol a crear (un árbol con un solo nodo tiene
profundidad cero) y devuelve un puntero a la raíz del árbol creado. El campo altura
de cada nodo contiene la distancia del nodo a las hojas que descienden de él
(altura vale cero para una hoja).
El parámetro buffer determina el espacio de memoria donde crear los nodos del
árbol. Específicamente, *buffer es un puntero a un área de memoria donde el
llamador espera que se cree el árbol. La función crearArbol modifica el valor de
*buffer, de acuerdo a la cantidad de memoria que haya utilizado, para que apunte
a la primera dirección libre después de haber creado el árbol. La siguiente figura
ilustra un posible resultado de ejecución para una llamada con el parámetro
profundidad igual a 1.
Antes de ejecución
*buffer
...
Después de ejecución
resultado
...
altura=1 izq
*buffer
der
altura=0
izq=Null der=Null altura=0 izq=Null der=Null
Página 2 de 8
Aspectos Avanzados de Arquitectura de Computadoras
Práctico 5
Se pide:
A) Escriba en lenguaje de alto nivel la función crearArbol. Compile en
ensamblador de 8086. Los parámetros y el valor de retorno se pasan en el stack. Se
debe conservar el valor de todos los registros. Todos los punteros, incluido el
parámetro buffer, se representan mediante un desplazamiento Near con respecto
a DS. El puntero NULL se representa con un cero y se asume que buffer es distinto
de NULL.
B) Calcule el mayor valor de profundidad para el cual su implementación funciona
correctamente.
Ejercicio 5 ( Ex. Arq. De Comp. 25 de julio de 1997)
Se considera un edificio con un sistema anti-incendios controlado por un
microprocesador 8086 dedicado a este propósito. El sistema está constituido por
sensores de humo distribuidos en todo el edificio que comparten una línea de
interrupción. Cuando un sensor detecta un determinado nivel de humo, genera una
interrupción por nivel, atendida por la rutina HUMO. Cada sensor tiene asociada una
luz en el tablero del guardia de seguridad y un difusor de agua. La luz debe
encenderse cuando se produce una interrupción, y se debe abrir el difusor de agua si
transcurren 30 segundos sin que se oprima un botón de reconocimiento de la alarma.
Esta acción debe además apagar la luz en el tablero de alarmas.
Se dispone de un timer de hardware que produce una interrupción cada 200 ms,
atendida por la rutina TICK; cuando se oprime un botón de reconocimiento de alarma
se produce una interrupción, atendida por la rutina ACK.
Cada grupo sensor-difusor-luz-botón tiene asociado un byte de control B_MANDO_n,
donde n es el número de grupo. En total existen 10, alojándose consecutivamente a
partir de la dirección de E/S 40H. Cada byte de control tiene la siguiente estructura:
Bit 0: reservado
Bit 1: en 1 si el sensor generó una interrupción, se pone en 0 por hardware al ser
leído
Bit 2: se debe escribir en 1 para abrir el difusor de agua
Bits 3: se debe escribir en 1 para encender la luz de alarma, en 0 para apagarla.
Bits 4: se pone en 1 cuando se oprime el botón de reconocimiento, se pone en 0 por
hardware al ser leído
Bit 5 al 7: reservados
Se pide:
 Programar en un lenguaje de alto nivel las rutinas necesarias para
implementar el sistema.
 Compilar en assembler 8086.
Ejercicio 6 (Ex. Arq. 2 19 de febrero de 2010)
Consideramos un dispositivo dedicado a evaluar expresiones aritméticas en notación
prefija. Recordamos que en esta notación los operadores preceden a los operandos,
lo cual elimina la necesidad del uso de paréntesis. La siguiente tabla muestra
ejemplos de expresiones en notación prefija y la expresión equivalente en la notación
infija habitual.
Página 3 de 8
Aspectos Avanzados de Arquitectura de Computadoras
Práctico 5
El dispositivo en cuestión utiliza un procesador
8086 para leer desde un puerto una secuencia
de operadores y números que forman una
expresión en notación prefija. Cada vez que se
recibe una expresión completa, esta se evalúa
y el resultado se escribe en otro puerto. El
proceso se repite de forma continuada; la notación prefija permite identificar el final
de cada expresión sin necesidad de utilizar delimitadores. Por ejemplo, la secuencia
+ 2 3 + 2 * 3 4 define dos expresiones aritméticas, + 2 3 y + 2 * 3 4, que generan la
salida de dos números, 5 y 14, respectivamente.
Notación prefija
+23
+2*34
*2+34
Notación infija
2+3
2+3*4
2 * (3 + 4)
Cuando se recibe un operador o un número, se genera la interrupción
INT_ENTRADA y el elemento recibido queda disponible en el puerto de un byte de
lectura ubicado en la dirección PUERTO_IN. El bit más significativo del byte recibido
indica si se trata de un número o un operador. Si el bit más significativo es cero, los
siete bits menos significativos se interpretan como un entero sin signo. Si por el
contrario el bit más significativo es uno, los siete bits menos significativos indican un
operador de acuerdo a la siguiente correspondencia: 0 – suma, 1 – resta, 2 –
multiplicación, 3 – división.
Cada vez que se completa una expresión, el resultado se evalúa en precisión de
entero con signo de 16 bits y se emite por el puerto de escritura de una palabra
ubicado en la dirección PUERTO_OUT.
1) Escriba en un lenguaje de alto nivel y compile en 8086 todas las rutinas
necesarias para implementar el sistema propuesto. Asuma que no hay divisiones por
cero y todos los resultados son representables en 16 bits con signo.
2) Si las expresiones están acotadas a un máximo de N componentes (incluyendo
números y operadores), cuánta memoria de datos y stack se necesita para el
correcto funcionamiento de su implementación.
Sugerencia: una función para evaluar expresiones prefijas podría tener el siguiente
pseudocódigo.
int evaluar()
c = siguiente elemento de la expresión;
if (c es un número)
return c;
else
case c of
suma : return evaluar() + evaluar();
resta: return evaluar() - evaluar();
mult : return evaluar() * evaluar();
div
: return evaluar() / evaluar();
Página 4 de 8
Aspectos Avanzados de Arquitectura de Computadoras
Práctico 5
Ejercicios complementarios
Complementario 1
Se desea implementar una rutina que determine si un string es parte de un texto,
devolviendo la posición de comienzo de la primera ocurrencia o un -1 si no se
encuentra. El largo de ambos es variable siendo el del string menor o igual que el del
texto. Los dos contienen el carácter especial NULL que marca su fin.
Se pide:
(a) Escribir una función en lenguaje de alto nivel que realice la tarea descrita.
Texto y string se reciben como parámetros.
(b) Compilar la rutina anterior en assembler 8086. DS:SI apunta a Texto y DS:DI
a String. Devolver el resultado en AX.
(c) Discutir si podría aumentarse la velocidad del algoritmo en assembler si se
conocieran los largos de los strings.
Complementario 2 (compresión)
Se desea compactar un string de manera que se sustituyan n caracteres
consecutivos iguales por la secuencia ESC n char; donde ESC es el carácter
ESCAPE (1Bh), n es un byte que indica la cantidad de veces que se repite char y
char es el carácter repetido. El string termina con el carácter NULL (0h). El ESC no
esta en el string.
Se pide:
(a) Implementar una rutina en alto nivel que realice la compactación. La rutina
recibe el string a compactar en STRING_ENT y devuelve el string
compactado en STRING_SAL.
(b) Compilarla en assembler 8086. Definir y especificar detalladamente como se
realiza el pasaje de parámetros.
Complementario 3
Dada una fecha AAMMDD en una cierta dirección DIR, hallar la fecha del día
siguiente y almacenarla en la misma dirección. AA, MM, DD ocupan un byte cada
una y son enteros sin signo. No tener en cuenta los años bisiestos. Escribir un
programa en assembler 8086, considerando que DIR es 0100:0100.
Complementario 4 ( Ex. Arq. 21 de marzo de 2002)
Se considera la siguiente estructura para representar un árbol binario en memoria:
typedef struct {
int dato;
int izq;
int der;
} nodo;
nodo arbol[MAX_NODOS];
La variable global arbol es un array de nodos, donde en cada nodo el campo “dato”
contiene un número entero (16 bits) y los campos “izq” y “der” son los índices (de 16
bits) de la ubicación, dentro del array, de los hijos izquierdo y derecho
respectivamente.
El nodo en el índice cero del array no se utiliza y un valor cero en izq o der indica que
el nodo no tiene hijo por esa rama.
Se desea implementar una función que imprima el contenido del árbol utilizando la
función auxiliar “imprimirDato”. Esta función es dada y no debe implementarse.
Página 5 de 8
Aspectos Avanzados de Arquitectura de Computadoras
Práctico 5
Para cada nodo debe imprimirse primero el contenido del subárbol izquierdo, luego el
dato del nodo y finalmente el contenido del subárbol derecho.
Los cabezales de las funciones son:
void imprimirArbol (int indice_raiz);
void imprimirDato(int dato);
Se pide:
(a) Implemente la función imprimirArbol en un lenguaje de alto nivel.
Compile la función imprimirArbol en ensamblador 8086 sabiendo que:
 La función imprimirDato recibe su parámetro en el stack y lo retira. Puede
modificar el valor de cualquier registro (es decir no conserva los registros).
 La función imprimirArbol debe recibir su parámetro en el stack y retirarlo. No es
necesario que conserve los valores de los registros.
 Al momento de la invocación el registro ES apunta al segmento donde se
encuentra almacenada la estructura (el elemento 0 del array está al comienzo del
segmento)
(b) Si se dispone de N bytes de stack y la función imprimirDato consume K bytes
de stack incluyendo su parámetro y la dirección de retorno, calcule una cota
superior para K de modo que sea posible imprimir el contenido del siguiente
árbol completo:
1
2
10
Página 6 de 8
Aspectos Avanzados de Arquitectura de Computadoras
Práctico 5
Complementario 5 ( Ex. Arq. 23 de febrero de 2003)
Se considera un árbol binario cuyo nodo se define de la siguiente manera:
struct nodo {
long int puntero_izq;
long int puntero_der;
int dato;
};
donde:
(c) puntero_izq y puntero_der son direcciones segmentadas de 32 bits (16 bits
más significativos indican el segmento y los 16 bits menos significativos el
desplazamiento), que apuntan a los nodos hijo izquierdo e hijo derecho
respectivamente. Están almacenados en memoria siguiendo la regla de Intel
(respecto a la ubicación de la parte baja y la parte alta).
(d) dato es un entero de 16 bits
El árbol tiene por lo menos un elemento y no tiene porqué estar balanceado. El valor
0 en cualquiera de los punteros (derecho o izquierdo) significa que el nodo no tiene
un sucesor por la correspondiente rama del árbol.
Se pide:
 Escribir en un lenguaje de alto nivel una función recursiva que devuelve la
cantidad de nodos con exactamente dos hijos. La función recibe como
argumento el puntero al (sub)árbol a recorrer. Compilar la rutina en assembler
8086. El programa llamador hace la siguiente invocación:
PUSH puntero_segmento
PUSH puntero_desplazamiento
CALL nodos_dos_hijos
POP resultado
el resultado se devuelve en el stack y los argumentos deben retirarse del
stack.

Calcular el tamaño máximo del árbol (cantidad de nodos) para que la función
pueda ser ejecutada cualquiera sea la topología del mismo, sabiendo que se dispone
un segmento completo para el stack.
Complementario 6 (Ex. Arq. De Comp. 23 de diciembre de 1996)
Tiro al blanco es un sencillo juego controlado por un procesador 8086.
Consiste en 8 lámparas que se iluminan en secuencia a intervalos de 0.1 segundos.
Una lámpara encendida representa el blanco y el objetivo del jugador es presionar el
interruptor correspondiente mientras la lámpara esté encendida. Si se logra el
objetivo, la lámpara indicada debe permanecer encendida. Gana el jugador que logre
disparar a los 8 blancos en el menor tiempo posible.
La lámpara e-nésima (0..7) se enciende poniendo en 1el bit e-nésimo del puerto de
E/S 11h (de lectura / escritura).
Se dispone de un timer que genera una interrupción cada 10 ms y que es atendido
por la rutina Timer.
Al presionar un interruptor se genera una interrupción que es atendida por la rutina
Interruptor y en el byte del puerto de E/S 10h (de sólo lectura) se escribe un dígito
entre 0 y 7 correspondiente al ÚLTIMO interruptor presionado.
El juego se inicia al escribir 1 en el bit menos significativo del puerto de E/S 12h (de
sólo escritura). Al finalizar deberá guardarse en la dirección TIEMPO_TOT el tiempo
empleado por el participante en completar su objetivo. Si éste no se logra en un
tiempo menor a 900 segundos el juego debe finalizar escribiendo B0B0H en
TIEMPO_TOT.
Página 7 de 8
Aspectos Avanzados de Arquitectura de Computadoras
Práctico 5
Se pide:
 Implementar en un lenguaje de alto nivel el juego Tiro al blanco descripto.
 Compilarlo en Assembler 8086.
Página 8 de 8
Descargar