En esta sección describiremos al simulador SPIM, 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 e inmediatamente 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.).
El doctor Larus tiene disponible al programa SPIM para diferentes sistemas operativos, los cuales pueden obtenerse libremente desde su página web situada en:
http://pages.cs.wisc.edu/~larus/spim.html
en la que inmediatamente se encuentra el vínculo al programa SPIM. También se encuentra el código fuente completo y documentación.
Es necesario descargar el programa para poder evaluar lo que hemos estudiado a lo largo del capítulo 2. En esta sección solo revisaremos algunos aspectos del programa SPIM que nos ayudaran a simular los programas hasta el momento realizados.
En la versión para WINDOWS el programa SPIM tiene el aspecto que se presenta en la figura 3.11.1, en la que se distinguen cuatro ventanas:

Figura 3.11.1 Simulador SPIM en el entorno Windows
- La primer ventana contiene a los registros, se muestra el valor de todos 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. De estos registros comentaremos un poco mas adelante.
- La segunda ventana contiene una parte de memoria en la que se colocará el código de usuario (el código a evaluar). Por default se encuentra un segmento de código descrito en el archivo trap.handler, este código corresponde a una especie del kernel de la máquina. La idea es que los usuarios avanzados puedan hacer su propio archivo para que hagan un manejo diferente de las excepciones. En el código que carga el archivo trap.handler se incluye una llamada a la función main, de manera que cualquier programa que se quiera simular deberá incluir al procedimiento principal (main).
- La tercer ventana contiene una parte de la memoria en la que se colocarán los datos, se incluye una sección de propósito general, una parte dedicada a la pila (stack) y otra que forma parte del Kernel.
- La cuarta ventana es la ventana de mensajes, en la que se describen los diferentes eventos que van ocurriendo durante la simulación.
Instalación de SPIM en Ubuntu
Primero hay que buscar el paquete spim en el repertorio de programas (usaremos synaptic en modo super-usuario)
Seleccionar el paquete con el botón derecho y seleccionar la opción de instalar. Después de la instalación desde la consola puede invocar al simulador en modo gráfico:
$ xspim
y aparecerá una ventana similar a la del entorno Windows
su uso es idéntico al SPIM para Windows.
La consola del programa SPIM
Además de las cuatro ventanas que se encuentran en la ventana principal del programa, cuando el programa se ejecuta se despliega en pantalla otra ventana conocida como la consola del programa SPIM (ver figura 3.11.4).
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.
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 punto flotante). En la figura 3.11.5 se muestran todos los servicios que soporta el Kernel.

Figura 3.11.4 Consola del simulador SPIM en el entorno Windows

Figura 3.11.5 Servicios que nos proporciona el Kernel del Programa SPIM
Pseudo instrucciones
Debido a que el repertorio MIPS es un repertorio de instrucciones reducido, para dar un poco mas de flexibilidad a los programadores, se generaron 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 mas 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:
ori reg_destino, $zero, reg_fuente
Para las multiplicaciones, se puede utilizar la pseudo instrucción:
mul $1, $2, $3
esta pseudo instrucción en realidad es traducida en las instrucciones siguientes:
mult $2, $3 # Esta es la instrucción que multiplica a $2 con $3, pero el
# resultado se queda en los registros HI y LO
mflo $1 # Esta instrucción coloca la parte baja del resultado y la
# coloca en $1toma la parte baja del resultado
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 debería usar a la instrucción:
mfhi $4 # Para colocar la parte alta en un registro de propósito general
Otra pseudo instrucción bastante útil es la siguiente:
la $a0, str1
Cuando codificamos un programa y vamos a utilizar cadenas constantes, sabemos que éstas se colocarán en memoria, sin embargo lo que no sabemos es en que dirección serán colocadas, por lo que no sabriamos como direccionarlas. Con la obtenemos en el registro $a0 la dirección donde inicia la cadena str1. 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 mas pesudo instrucciones, sólo hemos mencionado a las que utilizaremos durante el desarrollo de programas que se realizarán para la evaluación del simulador. En el apéndice A del libro de texto (página A-55) se encuentra un listado con todas las instrucciones de los procesadores MIPS R2000/R3000 y las pseudo instrucciones que soporta el simulador PSIM. Este apéndice esta disponible en formato PDF en la página del Dr. Larus, su referencia es:
http://pages.cs.wisc.edu/~larus/HP_AppA.pdf
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:

- El código se puede escribir con cualquier editor de texto (texto sin formato) y salvarse con cualquier extensión, se sugiere .asm o .s sólo por organización.
- El código principal debe incluir a la etiqueta main porque en el kernel del programa existe un salto hacia esa etiqueta.
- Una vez que finalice la ejecución del programa (la cual puede hacerse paso a paso o con múltiples pasos), si se continua ejecutando, ocurrirá una excepción, debido a que el kernel no tiene mas código por ejecutar.
Ejemplo 2: Un programa que suma 2 números leídos por teclado
En este ejemplo se usará a la consola para obtener dos enteros, luego los enteros se sumaran 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 $8, $0, $v0 # se coloca en $8
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 $9, $0, $v0 # se coloca en $9
addi $v0, $zero, 4 # Servicio 4
la $a0, str3 # para indicar que se
syscall # dará el resultado
add $a0, $8, $9 # 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 . . ."
La ejecución del programa genera la siguiente salida:
Ejemplo 3: El factorial de un número.
Este programa esta basado en la función recursiva que se presentó en la sección 2.5 (Soporte de procedimientos) sólo se le 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
La ejecución del programa genera la siguiente salida:
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, $zero, $v0 # El numero esta en $v0, se copia a $s0
add $a0, $zero, $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, $zero, 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, $zero, 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: # La funcion que obtiene el factorial
addi $sp, $sp, -8 # Hace espacio en la Pila
sw $ra, 4 ($sp) # Salva la dirección de retorno
sw $a0, 0 ($sp) # Salva al argumento n
# Se evalúa para ver si ocurre el caso base (cuando n < 1):
slti $t0, $a0, 1 # $t0 = 1 si n < 1
beq $t0, $zero, L1 # salta a L1 si no ocurre el caso base
# Si ocurre el caso base, deberían recuperarse los datos de pila,
# pero como no se han modificado, no es necesario.
# Lo que si se requiere es restablecer al puntero de la pila.
addi $v0, $zero, 1 # retorno = 1
addi $sp, $sp, 8 # Restablece al apuntador de la pila
jr $ra # Finaliza regresando el resultado en $v0
# Si no ocurre el caso base, prepara la llamada recursiva
L1: addi $a0, $a0, -1 # n = n - 1
jal fact # llama a fact con n - 1
# Después de la llamada, se hace la restauración de los registros:
lw $a0, 0($sp) # Recupera el valor de n
lw $ra, 4($sp) # recupera la dirección de retorno
addi $sp, $sp, 8 # Restablece al apuntador de la pila
#Para concluir, se actualiza el valor de retorno y se
#regresa el control al invocador:
mul $v0, $a0, $v0 # Retorno = n * fact (n - 1)
jr $ra # regresa al invocador
.data
str1: .asciiz "Dame un numero: "
str2: .asciiz "El factorial del numero "
str3: .asciiz " es : "
str4: .asciiz "\n\nFin del programa, Adios . . ."
Ejemplo 4: Impresion de una serie.
Traducir el siguiente código en C a MIPS
int main()
{
int n;
scanf("%d", &n);
fun(n);
}
void fun(int n)
{
printf("%d", n);
if(n!=0)
fun(n-1);
}
Respuesta
main:
addi $v0, $zero, 5
syscall #$v0 tiene n
add $a0, $zero, $v0 #valor para imprimir
addi $v0, $zero, 1 #para llamar a syscall
jal fun #llamada a la función
jr $ra
fun:
addi $sp, $sp, -4 #Almacenamiento de la dirección
sw $ra, 0 ($sp) #de retorno
syscall #impresion del valor
beq $a0, $zero, finfun # fin ocasionado por el caso base
addi $a0, $a0, -1 # n = n -1
jal fun #llamada recursiva
finfun:
lw $ra, 0 ($sp) #recuperación de la direccion
addi $sp, $sp, 4 #de retorno
jr $ra
La ejecución del programa genera la siguiente salida:
Ejercicios Para estos ejercicios, en un archivo pongan su código (comentado) y pongan 2 impresiones de pantalla de su consola. Entreguen en un PDF (por correo)
- Para este ejercicio, pida el valor de N, que corresponde a la cantidad de números que serán posteriormente ingresados. Enseguida capture cada uno de los números y finalmente muestre un mensaje indicando el mayor de los N números
- Leer un número por teclado e imprimir el número en hexadecimal.
Noten que hay 2 aproximaciones para resolver el problema:
a) hacer divisiones sucesivas, sin embargo los digitos obtenidos serán obtenidos en orden inverso al orden en que tendran que imprimirlas.
b) usar una mascara tomando los 4 bits más significativos del número e imprimir el caracter correspondiente a cada valor. Enseguida hacer corrimientos para aplicar de nuevo la máscara,...
- Leer un número N, que indicará que tienes que imprimir los primeros N números de la serie de fibonacci. Para este problema usar recursividad (también existe una versión iterativa)