spim: un simulador del repertorio de instruccionesfsantiag/arqcomputadoras/apoyo para el uso de...
TRANSCRIPT
SPIM: Un simulador del repertorio de instrucciones
Por M. C. Felipe Santiago E.
Para el curso: Arquitectura de Computadoras
1. Introducción
SPIM es un simulador creado por el Dr. James Larus, graduado en la Universidad de
Wisconsin, Madison. Y actualmente investigador de la empresa Microsoft.
SPIM es un simulador autónomo para programas en lenguaje ensamblador escritos para los
procesadores R2000/R3000, los cuales son procesadores de 32 bits de la corporación MIPS.
SPIM lee y ejecuta el código en lenguaje ensamblador, proporciona un depurador simple y
un juego simple de servicios del sistema operativo.
SPIM soporta casi el conjunto completo de instrucciones del ensamblador-extendido para
el R2000/R3000 (omite algunas comparaciones de punto flotante complejas y detalles del
sistema de paginación de memoria.).
QTSPIM es la versión más reciente del simulador y se puede obtener desde:
http://pages.cs.wisc.edu/~larus/spim.html
En el sitio se encuentra el vínculo para descargar el programa QTSPIM ubicado en
SourceForge. También se encuentran links para el código fuente completo y
documentación.
En este documento se muestran algunos aspectos del programa SPIM útiles para simular los
programas hasta el momento realizados.
2. Aspecto del programa
El programa QTSPIM tiene el aspecto que se presenta en la figura 1, en la que se distinguen
dos columnas: La columna de registros y la columna de memoria.
En la columna de registros se puede ver el contenido de todos los registros, está separada
por dos secciones, los registros de enteros y los registros de punto flotante.
Por default se presentan los registros para enteros, incluyendo los registros de propósito
general (de $0 a $31), además del Contador del Programa (PC) y de otros registros para el
manejo de excepciones (una excepción es un evento erróneo debido a alguna incongruencia
durante la ejecución de un programa). También se muestran dos registros HI y LO, estos
registros son dedicados a las multiplicaciones y divisiones.
La segunda columna contiene una parte de la memoria en la que se colocarán los programas
de usuario (el código a evaluar), mostrando las instrucciones en notación simbólica
(ensamblador) y en código máquina. En esta ventana se observa al código descrito en el
manejador de excepciones, este código corresponde a una especie de kernel para la
máquina e incluye una llamada a la función main, de manera que cualquier programa que se
quiera simular deberá incluir al procedimiento principal (main).
Los usuarios avanzados puedan hacer sus propias rutinas para que hagan un manejo
diferente de las excepciones y configurar para que éste sea cargado en el arranque del
programa
Fig. 1 Aspecto del programa QTSPIM para Windows
La columna de la memoria tiene una ceja adicional (Data) en donde se puede ver la memoria en
en hexadecimal. La memoria incluye una sección de propósito general, una parte dedicada a la
pila (stack) y otra que forma parte del Kernel.
En la parte inferior de las columnas se muestra una ventana de mensajes, en la que se
describen los diferentes eventos que van ocurriendo durante la simulación.
3. La consola del programa SPIM
Además de la ventana principal del programa, cuando QTSPIM se ejecuta se despliega en
pantalla otra ventana conocida como la consola del programa SPIM (figura 2).
La consola es el mecanismo por medio del cual se van a insertar datos al programa o se van
a observar algunos resultados del mismo. El Kernel incluido permite el manejo de una
instrucción denominada SYSCALL. Con SYSCALL se realiza una llamada al Kernel para
solicitar algún servicio, que puede consistir en la captura de un dato o bien la presentación
de resultados en la consola.
Fig. 2 La consola del Programa QTSPIM
Antes de invocar a SYSCALL, se debe especificar el número de servicio en el registro $V0,
y si el servicio requiere argumentos, éstos se deberán colocar en los registros $a0 y $a1,
dependiendo del número de argumentos, sin embargo, si el servicio es para números en
punto flotante, se utilizará al registro $f0 para el argumento (La arquitectura MIPS incluye
32 registros para el manejo de números en punto flotante, y un hardware dedicado para las
operaciones). En la tabla 1 se muestran los servicios que soporta el Kernel.
Tabla 1. Servicios que proporciona el Kernel del Programa SPIM
4. Pseudo instrucciones.
Debido a que el repertorio MIPS es un repertorio de instrucciones reducido, para dar un
poco más de flexibilidad a los programadores, es posible generar un conjunto de pseudo
instrucciones; una pseudo instrucción realiza algún tipo de operación, sin embargo no tiene
una interpretación directa en Hardware, sino que tiene que traducirse a una o más
instrucciones reales para que pueda ser ejecutada.
Así por ejemplo, la pseudo instrucción:
move reg_destino, reg_fuente
Mueve el registro fuente al registro destino, pero no es una instrucción real, sino que el
simulador SPIM la traduce a:
Or reg_destino, $zero, reg_fuente
Para las multiplicaciones, se puede utilizar la pseudo instrucción:
mul $s1, $s2, $s3
esta pseudo instrucción en realidad es traducida en las instrucciones siguientes:
mult $s2, $s3 # Esta es la instrucción que multiplica a $s2 con $s3, pero el
# resultado queda en los registros HI y LO
mflo $s1 # Copia el contenido del registro LO en $s1
Puede notarse que la pseudo instrucción es suficiente cuando se sabe que el resultado
alcanza en un registro de 32 bits. Pero si se están manipulando números muy grandes,
además de la pesudo instrucción se debe usar la instrucción:
mfhi $s4 # Copia el contenido del registro HI en $s4
La pseudo instrucción mul también puede usarse con el segundo parámetro con un valor
inmediato, por ejemplo la pseudo instrucción:
mul $t4, $t1, 4
Es traducida a:
ori $t2, $zero, 4
mult $t2, $t1
mflo $t4
Otra pseudo instrucción muy útil es la siguiente:
la $a0, str1
Cuando se codifica un programa y se van a utilizar cadenas constantes, se sabe que éstas se
colocarán en memoria, sin embargo se ignora en qué dirección serán colocadas, por lo que
no se sabría cómo direccionarlas. Con la pseudo-instrucción se obtiene en el registro $a0 la
dirección donde inicia la cadena str1 (la – load address). Esta pseudo instrucción es
traducida a dos instrucciones, la primera para cargar la parte alta de la dirección (lui) y la
segunda para obtener la parte baja (ori).
Existen más pesudo instrucciones, sólo se han mencionado las más comunes y que son
necesarias para el desarrollo de algunos programas que se realizarán para la evaluación del
simulador. En el apéndice A del texto “Computer Organization & Design, The
hardware/software interface” se encuentra un listado con todas las instrucciones de los
procesadores MIPS R2000/R3000 y las pseudo instrucciones que soporta el simulador
SPIM. Este apéndice está disponible en formato PDF en la página del Dr. Larus, su
referencia es:
http://pages.cs.wisc.edu/~larus/HP_AppA.pdf
5. Ejemplos de uso del simulador.
Ejemplo 1: El programa “Hola Mundo”
Se trata de un programa que desplegará en la consola a la cadena “HOLA MUNDO”,
básicamente se requiere obtener la dirección del inicio de la cadena y solicitar el servicio 4
al kernel (con la instrucción SYSCALL).
El código del programa es:
main: addi $v0, $zero, 4 # Se usará el servicio 4
la $a0, cadena # Se obtiene el argumento
syscall # Solicita el servicio
jr $31 # Termina la función principal
.data
cadena: .asciiz "Hola Mundo"
La salida en la consola es:
Observaciones:
El código se puede escribir con cualquier editor de texto (texto sin formato) y salvarse
con cualquier extensión, se sugiere s ó asm por asociación con el programa.
El código principal debe incluir a la etiqueta main porque en el kernel hay un salto
hacia esa etiqueta.
La ejecución puede hacerse paso a paso con <F10> o con el botón run/continue <F5>.
Una vez que finaliza el código de usuario, si se continúa ejecutando, del main regresa al
Kernel y solicita el servicio 10 (exit).
Ejemplo 2: Un programa que suma dos números.
En este ejemplo se usará a la consola para obtener dos enteros, luego se sumarán y se
mostrará el resultado. El código del programa:
main:
addi $v0, $zero, 4 # Servicio 4
la $a0, str1 # se imprime una cadena
syscall # para pedir un número
addi $v0, $0, 5 # Servicio 5
syscall # se lee el número
add $t0, $0, $v0 # se coloca en $t0
addi $v0, $zero, 4 # Servicio 4
la $a0, str2 # se imprime una cadena
syscall # para pedir el otro número
addi $v0, $0, 5 # servicio 5
syscall # se lee el otro numero
add $t1, $0, $v0 # se coloca en $t1
addi $v0, $zero, 4 # Servicio 4
la $a0, str3 # para indicar que se
syscall # dará el resultado
add $a0, $t0, $t1 # Se coloca la suma como argumento
addi $v0, $0, 1 # Servicio 1
syscall # se muestra el resultado
addi $v0, $zero, 4 # Servicio 4
la $a0, str4 # muestra una cadena de
syscall # terminación del programa
jr $31 # fin del main
.data
str1: .asciiz "Dame un numero: "
str2: .asciiz "Dame otro numero: "
str3: .asciiz "La suma de los numeros es : "
str4: .asciiz "\n\nFin del programa, Adios . . ."
Una corrida generó la salida en la consola:
Ejemplo 3: El factorial de un número.
Este programa está basado en la función recursiva que se presentó con anterioridad
(Soporte de procedimientos) sólo se agregó el main y se hicieron algunas modificaciones
para el manejo correcto de las constantes.
En este programa se utilizó la pseudo instrucción li (por load immediate) para cargar una
constante en un registro, que es equivalente a hacer una operación OR del registro 0 con la
constante y colocar el resultado en el registro que se quiere cargar.
El código del programa:
main: addi $sp, $sp, -4 # Hace espacio en la Pila
sw $ra, 4 ($sp) # Salva la dirección de retorno
li $v0, 4 # Salida a la consola
la $a0, str1
syscall
li $v0, 5 # Lectura de un numero
syscall
add $s0, $0, $v0 # El numero esta en $v0, se copia a $s0
add $a0, $0, $s0 # Prepara el parametro
jal fact # Llama al factorial
add $s1, $v0, $zero # Respalda el resultado
li $v0, 4 # Una cadena a la consola
la $a0, str2
syscall
addi $v0, $0, 1 # Una entero a la consola
add $a0, $s0, $zero
syscall
li $v0, 4 # Una cadena a la consola
la $a0, str3
syscall
addi $v0, $0, 1 # Una entero a la consola
add $a0, $s1, $zero
syscall
li $v0, 4
la $a0, str4
syscall
lw $ra, 4 ($sp) # Recupera la dirección de retorno
addi $sp, $sp, 4 # Restablece el tope de la Pila
jr $31
fact:
addi $sp, $sp, -8 # adjust stack for 2 items
sw $ra, 4($sp) # save return address
sw $a0, 0($sp) # save argument
slti $t0, $a0, 1 # test for n < 1
beq $t0, $zero, L1
addi $v0, $zero, 1 # if so, result is 1
addi $sp, $sp, 8 # pop 2 items from stack
jr $ra # and return
L1: addi $a0, $a0, -1 # else decrement n
jal fact # recursive call
lw $a0, 0($sp) # restore original n
lw $ra, 4($sp) # and return address
addi $sp, $sp, 8 # pop 2 items from stack
mul $v0, $a0, $v0 # multiply to get result
jr $ra # and return
.data
str1: .asciiz "Dame un numero: "
str2: .asciiz "El factorial del numero "
str3: .asciiz " es : "
str4: .asciiz "\n\nFin del programa, Adios . . ."
Para probar el programa se obtuvo el factorial de 8:
Ejemplo 4: Manejo de un arreglo.
En este ejemplo se pretende mostrar como un arreglo de n enteros puede ser ubicado en la
pila. Para ello solo se piden algunos enteros al usuario para luego imprimirlos en la pantalla
en orden inverso.
El código C que realiza las actividades deseadas es: void main()
{
int i, n, *A, *t;
printf("Manejo de un arreglo\n");
printf("Indica el tamaño del arreglo: ");
scanf("%d", &n);
A = (int *) malloc(n*sizeof(int));
for( t = A, i = 0; i < n; i++, t++) {
printf(" Dame el dato %d : ", i);
scanf("%d", t);
}
printf("\nLos números en orden inverso: \n");
for( t = &A[n-1], i = 0; i < n; i++, t--)
printf(" %d\n", *t);
free(A);
}
Las variables i, n, A y t se asocian con los registros: $t0, $t1, $t2 y $t3 (este procedimiento
es aislado).
El código MIPS correspondiente al código C anterior es: # Programa que maneja un arreglo
# imprimiendo los elementos en orden inverso
main:
li $v0, 4 # El Primer Mensaje
la $a0, str1
syscall
li $v0, 4 #Pide el tamaño del arreglo
la $a0, str2
syscall
li $v0, 5 # Lee el tamaño
syscall
add $t1, $v0, $0 # El tamaño del arreglo se guardo en $t1
mul $t4, $t1, 4 # $t4 para el total de bytes
sub $sp, $sp, $t4 # Hace espacio en la pila
addi $t2, $29, 0 #$t2 apunta al comienzo del arreglo
# Primer ciclo for
addi $t0, $0, 0 # i = 0
addi $t3, $t2, 0 # t = A
for1: slt $t5, $t0, $t1
beq $t5, $zero, fin_for1
li $v0, 4 # Se pide el número
la $a0, str3
syscall
li $v0, 1 # Imprime el índice del numero
add $a0, $0, $t0
syscall
li $v0, 4 # imprime los dos puntos
la $a0, str4
syscall
li $v0, 5 # Toma el valor de la consola
syscall
sw $v0, 0($t3)
addi $t0, $t0, 1 # i ++
addi $t3, $t3, 4 # t ++
j for1 # regresa al inicio del ciclo
fin_for1:
li $v0, 4 # Mensaje de aviso
la $a0, str5
syscall
# Inicia el segundo for
addi $t0, $0, 0 # i = 0
addi $t4, $t1, -1 # $t4 = n - 1
mul $t4, $t4, 4 # $t4 = 4 * (n - 1)
add $t3, $t2, $t4 # t = &A[n-1]
for2:
slt $t5, $t0, $t1
beq $t5, $zero, fin_for2
lw $t6, 0($t3) # Carga el número
li $v0, 1
add $a0, $0, $t6 # Imprime el número
syscall
li $v0, 4 # retorno de carro
la $a0, str6
syscall
addi $t0, $t0, 1 # i ++
addi $t3, $t3, -4 # t --
j for2
fin_for2:
mul $t4, $t1, 4 # $t4 <- Bytes requeridos en la pila
add $sp, $sp, $t4 # Libera el espacio solicitado
jr $ra # fin de la función main
# Cadenas del programa
.data
str1: .asciiz " Manejo de un arreglo \n"
str2: .asciiz " Indica el tama~o del arreglo : "
str3: .asciiz " Dame el numero "
str4: .asciiz " : "
str5: .asciiz "\n Los numeros en orden inverso son: \n "
str6: .asciiz "\n "
Los resultados arrojados en la consola para una corrida del programa son:
6. Configuración del programa PSIM
El programa PSIM permite configurar algunos parámetros; la consola de configuración se
encuentra en la opción settings del menú simulator, y tiene la siguiente forma:
En la ayuda del programa se describe el objetivo de cada una de las opciones de
configuración. El programa funciona correctamente con los valores preestablecidos.
7. Ejercicios:
i. Realice un programa que solicite 3 números e indique cual es el menor de ellos.
ii. Realice el programa principal para la función SORT y simúlelo, recuerde que debe incluir
la función SWAP, el número de elementos deberá ser solicitado al usuario (sug. Utilice la
pila para almacenar el arreglo).
iii. Realice un programa que obtenga el menor y el mayor de un arreglo de n elementos
proporcionados por el usuario (sug. Acondicione la función desarrollada en la tarea
anterior).
iv. Realice un programa que evalúe la función de Fibonacci desarrollada en la tarea anterior.