Está en la página 1de 4

Organización del Computador 1 29 de Abril de 2010

Facultad de Ciencias Exactas y Naturales


Universidad de Buenos Aires

Clase Práctica
Programación en Assembler “Orga 1”

¿Qué es el lenguaje ensamblador?

El ensamblador (o assembly, abreviado ASM) es un lenguaje que se refiere directamente a


las instrucciones de hardware de la computadora que lo ejecuta. Por esta razón, el lenguaje
ASM está fuertemente atado a la computadora subyacente, y esto hace que sea necesario
especificar de qué tipo de ASM se trata. En nuestro caso, se llama ASM “Orga 1” porque es
para la máquina del mismo nombre.

ASM es un 2GL (por las iniciales de Second Generation Language, o Lenguaje de Segunda
Generación). Los lenguajes de programación se clasifican de forma no muy estricta en 5
“generaciones”, donde la primera corresponde al lenguaje de máquina, la tercera a la
mayoría de los lenguajes convencionales y la quinta a lenguajes donde programar se parece
más a especificar la solución del problema que a dar un método para resolverlo. En este
sentido, en la medida que se pasa de la primera generación a la quinta, el cambio principal
es el nivel de abstracción – los lenguajes buscaron progresivamente facilitar la tarea del
programador.

La máquina “Orga 1”

La máquina “Orga 1” cuenta con 4 familias de instrucciones:


• Operaciones de manejo de memoria (MOV)
• Operaciones aritmético-lógicas (ADD, SUB, OR, etc.)
• Operaciones relacionadas con llamadas a función (CALL, RET)
• Saltos (condicionales o no)
Estas operaciones usan a su vez uno o más de los siguientes 6 modos de direccionamiento:

Modo Ejemplo
Inmediato 0xEF06

Directo [0x28]

Indirecto [[0x67E]]

Registro R3

Indirecto a registro [R2]

Indirecto a registro indexado [R4+2]

En todos los casos las direcciones de memoria se refieren a palabras. Para mayor
información, se puede consultar el apunte de la máquina que está al final de la práctica 3.

Este conjunto de instrucciones hace que la máquina “Orga 1” sea lo que se considera una
máquina RISC (Reduced Instruction Set Computer, Computadora de Conjunto de
Instrucciones Reducido), a diferencia de las computadoras de escritorio convencionales con
arquitectura Intel que son CISC (Complex Instruction Set Computer).

Ejercicio 1

Escribir un programa que calcule la división entera entre dos enteros sin signo de 16 bits.
En caso de que el divisor sea 0, devolverá 0.
• R1 contiene la dirección de memoria donde se aloja el par <dividendo, divisor>.
• El resultado debe colocarse en R3.

¡Seudocódigo!

Para resolver un ejercicio de programación en ASM lo primero que deberíamos hacer es


escribir un seudocódigo de la solución. El seudocódigo es un lenguaje informal de tan alto
o bajo nivel como resulte cómodo para expresar lo que debe hacer el algoritmo. Esto
permite usar abstracciones que no están disponibles en ASM para pensar la solución del
problema, y recién en una segunda instancia dedicarse a la codificación. Esto tiene la
ventaja de que al ser la segunda parte del trabajo relativamente mecánica, permite
concentrarse en los detalles del lenguaje y así cometer menos errores. Por otro lado, el
seudocódigo resulta de gran utilidad para la comprensión del programa en ensamblador,
que puede ser bastante críptico.

Respecto del enunciado, la única particularidad que presenta está en la representación del
par <dividendo, divisor>. La estructura en memoria de un par es idéntica a la de un arreglo
de 2 posiciones. En este caso, en la dirección de memoria indicada en R1 estará el
dividendo, y en la posición siguiente, el divisor.

El seudocódigo del algoritmo de división es el usual:


resultado = 0
if divisor == 0:
listo
else:
while dividendo >= divisor:
dividendo = dividendo – divisor
resultado = resultado + 1
listo

Y “compilando” manualmente a ASM obtenemos:

main: MOV R3, 0 R1: dirección del par <dividendo, divisor>


MOV R2, [R1] R2: dividendo
MOV R4, [R1+1] R3: resultado
R4: divisor
CMP R4, 0
JE fin
ciclo: CMP R4, R2
JGU fin
SUB R2, R4
ADD R3, 1
JMP ciclo
fin: RET

El código no tiene mayores complicaciones. Recordemos que para saltar fuera del ciclo
utilizamos el salto correspondiente a la negación de la guarda del while. En este caso,
saltamos a fin cuando el divisor (R4) es estrictamente mayor que el dividendo (R2)
haciendo, en este caso, una comparación de enteros sin signo.

Ejercicio 2

Estoy cansado de que me salgan las fotos oscuras con mi cámara digital. Hagan un
programa que duplique el brillo de mis fotos para remediar mi angustia.
• R1 contiene la dirección donde está la imagen como una matriz de enteros sin signo de 16 bits
• La foto tiene 200x200 pixels y la imagen debe ser modificada en el lugar.

¡Seudocódigo!

Cada punto que constituye una imagen se denomina píxel y utiliza un entero sin signo de
16 bits cuyo valor 0x0000 representa al color negro, su valor máximo 0xFFFF representa
al valor blanco, y los valores intermedios corresponden a los distintos tonos de gris. Este
valor (que en una imagen en tonos de gris se denomina luminancia) es una representación
del brillo de cada punto de la imagen. Así, si duplicamos este valor en todos los píxeles,
obtendremos una imagen más clara.

Una matriz se representa en memoria como un vector de vectores. Así, el orden que tienen
los píxeles en memoria es el mismo que si recorremos una matriz en de arriba hacia abajo
y de izquierda a derecha. Si olvidamos el hecho de que está constituida por filas y
columnas, una matriz de N*M es idéntica en memoria a un vector de N*M posiciones.
Por último, es de destacarse que si lo que queremos es aumentar el brillo de la imagen,
tenemos que tener cuidado con el resultado de nuestras operaciones. Si al duplicar el valor
de un píxel, el resultado no entra en 16 bits, debemos saturarlo, esto es, reemplazar el
resultado de la operación por el valor correspondiente al color blanco, ya que de lo
contrario obtendríamos un tono más oscuro del deseado.

El seudocódigo que resulta es el siguiente:

i = 0
while i < 200*200:
tmp = 2 * img[i]
if tmp > 0xFFFF:
tmp = 0xFFFF
img[i] = tmp
i = i + 1
listo

Y el código ensamblador obtenido es:

main: MOV R2, 0x9C40 R1: cursor dentro de la matriz


MOV R3, 0 R2: 200 * 200 (40 000)
ciclo: CMP R2, R3 R3: i (contador)
R4: tmp
JE fin
MOV R4, [R1]
ADD R4, R4
JCS saturar
seguir: MOV [R1], R4
ADD R1, 1
JMP ciclo
fin: RET

saturar: MOV R4, 0xFFFF


JMP seguir

Este código ensamblador tiene algunas particularidades. En primer lugar, para realizar la
multiplicación por dos de un valor utilizamos la operación de suma. Esto a su vez nos
permite utilizar el bit de carry para determinar si el resultado de la duplicación entra o no
en un entero de 16 bits. Si hubo acarreo sabemos que el resultado necesita de un bit
adicional para su representación, y corresponde saturarlo.

En segundo lugar puede verse que estamos “destruyendo” el parámetro recibido en R1 ya


que estamos modificando su valor (utilizándolo como cursor dentro de la matriz). Esto es
válido siempre que no se especifique lo contrario.

Por último puede observarse que el fragmento de código etiquetado como “saturar” obliga
a definir una etiqueta “seguir” para poder volver a la ejecución del ciclo cuando se termina
de saturar una posición. La operación CALL tiene la ventaja de eliminar esta etiqueta extra,
ya que utiliza el stack pointer de la máquina para recordar la dirección donde debe seguir
ejecutando después del procedimiento de saturación (esto es lo que hace la instrucción
RET).