Preguntas: Moisés E. Ramírez G. | UTM |

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

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:


Observaciones:

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
      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 . . ."

La ejecución del programa genera la siguiente salida:

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)