-
Notifications
You must be signed in to change notification settings - Fork 22
Practica 4: Soluciones a los ejercicios
Soluciones comentadas a los ejercicios propuestos en las sesiones de la práctica 4. Antes de ver las soluciones es importante que hayas intentado hacer los ejercicios por tu cuenta
Solución a los ejercicios de la Sesión de laboratorio L9:4-1
- La función a implementar NO tiene parámetros de entrada ni de salida. Su nomblre es saludar por lo que usaremos una etiqueta con ese mismo nombre para definir su punto de entrada. Para llamar a la función desde el programa principal usaremos jal saludar
#-- Solución al ejercicio 1-1
.include "servicios.asm"
.data
msg_main: .string "Comienza el programa principal\n"
msg_saludo: .string "Hola!\n"
.text
#----- PROGRAMA PRINCIPAL ----------------
#-- Imprimir mensaje desde programa principal
la a0, msg_main
li a7, PRINT_STRING
ecall
#-- Llamar a la subrutina
jal saludar
#-- Terminar (Punto de salida)
li a7, EXIT
ecall
#-----------------------------------------
#-------------------------------------------------------------------
#------ SUBRUTINA: saludar
#------ * Parametros de entrada: Ninguno
#------ * Parametros de salida: Ninguno
#-------------------------------------------------------------------
saludar: #-- Punto de entrada
#-- Saludar
la a0, msg_saludo
li a7, PRINT_STRING
ecall
#-- Retornar (Punto de salida)
ret
Los datos de la cadena de saludo están almacenados en el segmento de datos, junto a los datos usados por el programa principal. Sin embargo, es más conveniente situar los datos de la subrutina junto su código. De esta forma, el programa principal tendrá su código y sus datos, y los mismo la subrutina: su código y sus datos. Es el enlazador del simulador el que luego lo junta todo y sitúa todo el código en el segmento de código y todos los datos en el segmento de datos.
El programa es igual, pero ahora también incluimos las directivas .data y .text para la subrutina:
#-- Solución al ejercicio 1-1
#-- La subrutina tiene su propio segmento de
#-- datos y código
.include "servicios.asm"
.data
msg_main: .string "Comienza el programa principal\n"
.text
#----- PROGRAMA PRINCIPAL ----------------
#-- Imprimir mensaje desde programa principal
la a0, msg_main
li a7, PRINT_STRING
ecall
#-- Llamar a la subrutina
jal saludar
#-- Terminar (Punto de salida)
li a7, EXIT
ecall
#-----------------------------------------
#-------------------------------------------------------------------
#------ SUBRUTINA: saludar
#------ * Parametros de entrada: Ninguno
#------ * Parametros de salida: Ninguno
#-------------------------------------------------------------------
#--- Datos usados sólo por la subrutina
.data
msg_saludo: .string "Hola!\n"
#-- Código de la subrutina
.text
saludar: #-- Punto de entrada
#-- Saludar
la a0, msg_saludo
li a7, PRINT_STRING
ecall
#-- Retornar (Punto de salida)
ret
- Ahora nos piden lo mismo, pero separado en dos ficheros: Ej1-main1.s y saludar.s
- Fichero con el programa principal: Ej1-main1.s
#-- Solución al ejercicio 1-2
.include "servicios.asm"
.data
msg_main: .string "Comienza el programa principal\n"
.text
#----- PROGRAMA PRINCIPAL ----------------
#-- Imprimir mensaje desde programa principal
la a0, msg_main
li a7, PRINT_STRING
ecall
#-- Llamar a la subrutina
jal saludar
#-- Terminar (Punto de salida)
li a7, EXIT
ecall
#-----------------------------------------
- Fichero con la subrutina: saludar.s
#-------------------------------------------------------------------
#------ SUBRUTINA: saludar
#------ * Parametros de entrada: Ninguno
#------ * Parametros de salida: Ninguno
#-------------------------------------------------------------------
.include "servicios.asm"
#-- Punto de entrada de la subrutina
.globl saludar
#--- Datos usados sólo por la subrutina
.data
msg_saludo: .string "Hola!\n"
#-- Código de la subrutina
.text
saludar: #-- Punto de entrada
#-- Saludar
la a0, msg_saludo
li a7, PRINT_STRING
ecall
#-- Retornar (Punto de salida)
ret
Para ejecutarlo deben estar abiertos ambos ficheros en las pestañas del editor, y la opción settings/assemble all files currently open también activada
- El fichero con la subrutina NO hay que cambiarlo: es el mismo. Sólo hay editar el programa principal para realizar una segunda llamada a la función saludar:
#-- Solución al ejercicio 1-3
#-- Se invoca dos veces a la subrutina
#-- de saludo
.include "servicios.asm"
.data
msg_main: .string "Comienza el programa principal\n"
.text
#----- PROGRAMA PRINCIPAL ----------------
#-- Imprimir mensaje desde programa principal
la a0, msg_main
li a7, PRINT_STRING
ecall
#-- Llamar a la subrutina
jal saludar
#-- Llamar a la subrutina otra vez
jal saludar
#-- Terminar (Punto de salida)
li a7, EXIT
ecall
#-----------------------------------------
Empezamos analizando la subrutina (función) que nos piden. Su nombre es línea, y tiene sólo un único parámetro de entrada. Su punto de entrada lo definimos con una etiqueta que tenga el nombre de la función: linea, y como está en un fichero separado, se debe añadir la directiva .globl linea, para indicar al ensamblador que línea es una etiqueta global, accesible desde otros ficheros
Así, la subrutina es esta, almacenada en su propio fichero:
#-----------------------------------------------------
#-- SUBRUTINA LINEA
#--- * Entrada: a0: numero de asteriscos a imprimir
#--- * Salida: Ninguna
#-----------------------------------------------------
.include "servicios.asm"
.globl linea
.text
linea: #-- PUNTO DE ENTRADA
#--- Estamos dentro de una subrutina
#--- usamos SIEMPRE registros temporales
#-- t0: contador. Inicializado a 0
li t0, 0
#-- a0 contiene el numero de asteriscos
#-- Lo guardamos en t1 para no perderlo
mv t1, a0
bucle:
#-- Si t0 > t1 --> Terminar
bge t0, t1, fin
#-- Imprimir un asterisco
li a0, '*'
li a7, PRINT_CHAR
ecall
#-- Incrementar contador de asteriscos
addi t0, t0, 1
#-- Repetir bucle
b bucle
fin:
#-- Retornar
ret
El programa principal se muestra a continuación, almacenado en otro fichero separado. Se pide al usuario la cantidad de caracters, se invoca a la subrutina línea y se termina
#-- Solución al ejercicio 2
#-------------------------------------------------------
#-- PROGRAMA PRINCIPAL
#-------------------------------------------------------
.include "servicios.asm"
.data
msg1: .string "Introduce el numero de asteriscos: "
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir el numero de asteriscos
li a7, READ_INT
ecall
#-- a0 contiene el numero de asteriscos
#-- Llamar a la funcion linea(a0)
jal linea
#-- Terminar
li a7, EXIT
ecall
#--------------------------------------------------------
Modificamos primero la subrutina. En la definición de la función el primer parámetro es el carácter a usar, y el segundo parámero es el número de caracteres a imprimir. Por tanto, hay que usar el registro a0 para pasar el carácter y el registro a1 para el número (y es la única asignación posible. Cualquier otra sería incorrecta)
#-----------------------------------------------------
#-- SUBRUTINA LINEA
#--- * Entradas:
#--- * a0: Carácter a usar
#--- * a1: numero de caracteres a imprimir
#--- * Salida: Ninguna
#-----------------------------------------------------
.include "servicios.asm"
.globl linea
.text
linea: #-- PUNTO DE ENTRADA
#--- Estamos dentro de una subrutina
#--- usamos SIEMPRE registros temporales
#-- t0: contador. Inicializado a 0
li t2, 0
#-- Meter en t0 el carácter
mv t0, a0
#-- Meter en t1 la cantidad
mv t1, a1
bucle:
#-- Si t2 > t1 --> Terminar
bge t2, t1, fin
#-- Imprimir el caracter
mv a0, t0
li a7, PRINT_CHAR
ecall
#-- Incrementar contador de asteriscos
addi t2, t2, 1
#-- Repetir bucle
b bucle
fin:
#-- Retornar
ret
Y este es el nuevo programa principal:
#-- Solución al ejercicio 2
#-------------------------------------------------------
#-- PROGRAMA PRINCIPAL
#-------------------------------------------------------
.include "servicios.asm"
.data
msg1: .string "\nIntroduce el caracter: "
msg2: .string "\nIntroduce la cantidad: "
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir el caracter
li a7, READ_CHAR
ecall
#-- t0 contiene el caracter
mv t0, a0
#-- Imprimir mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Pedir la cantidad
li a7, READ_INT
ecall
#-- t1 contiee el numero de caracteres a imprimir
mv t1, a0
#-- Llamar a la funcion linea(car, x)
mv a0, t0
mv a1, t1
jal linea
#-- Terminar
li a7, EXIT
ecall
#--------------------------------------------------------
La subrutina len tiene un único parámetro de entrada con la dirección de memoria en donde está situada la cadena que queremos calcular su longitud. Por tanto, hay que usar el registro a0 (y NO podemos usar otro).
La función devuelve como resultado la longitud de la cadena. Este valor se debe devolver también por a0 (y no se puede usar otro registro)
Su punto de entrada está definido por la etiqueta len. Esta es la subrutina, que estará almacenada en su *propio fichero
#-- Subrutina de calculo de la longitud de una cadena
#-- int len(*cad)
#-- ENTRADAS:
#-- * a0: Puntero a la cadena
#-- SALIDAS:
#-- * a0: Longitud de la cadena
#-- Punto de entrada
.globl len
#-- Punto de entrada
len:
#-- Inicializar contador de caracteres: t0
li t0, 0
bucle:
#-- Leer caracter de la cadena (t1)
lb t1, 0(a0)
#-- Si es '\0' terminar
li t2, '\n'
beq t1, t2, fin
#-- Incrementar contador de caracteres
addi t0, t0, 1
#-- Apuntar al siguiente caracter
addi a0, a0, 1
#-- Repetir el bucle
b bucle
fin:
#-- Devolver el numero de caracteres
mv a0, t0
#--- Punto de salida
ret
Este es el programa principal
-- Solución al ejercicio 4
#-- Programa principal
.include "servicios.asm"
#-- Longitud maxima de la cadena
.eqv MAX 1024
.data
msg1: .string "Introduce una cadena: "
msg2: .string "La longitud es: "
#-- Cadena introducida por el usuario
cad: .space MAX
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir cadena
la a0, cad
li a1, MAX
li a7, READ_STRING
ecall
#-- a0 contiene el puntero a la cadena
#-- Llamar a la función n = len(a0)
jal len
#-- a0 Contiene la longitud de la cadena. Guardarlo en t0
mv t0, a0
#-- Imprimir mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Imprimir la longitud
mv a0, t0
li a7, PRINT_INT
ecall
#-- Terminar
li a7, EXIT
ecall
La función count() tiene dos parámetros de entrada y uno de salida. El primero (a0) es el puntero a la cadena. El segundo (a1) es el carácter a contar. Sólo se pueden usar esos dos registros. Si cambiamos a0 por a1 estamos violando la especificación que nos ha puesto nuestro jefe de proyecto
El resultado de la cuenta se devuelve por a0 (y sólo se puede devolver por a0). El punto de entrada de la subrutina es la etiqueta count
#-- Subrutina para contar el numero de veces que aparece un carácter
#-- int count(*cad, car)
#-- ENTRADAS:
#-- * a0: Puntero a la cadena
#-- * a1: Carácter a contador
#-- SALIDAS:
#-- * a0: Número de veces que aparece el caracter en la cadena
#-- Punto de entrada
.globl count
#-- Punto de entrada
count:
#-- Inicializar contador de caracter: t0
li t0, 0
bucle:
#-- Leer caracter de la cadena (t1)
lb t1, 0(a0)
#-- Si es '\0' terminar
li t2, '\n'
beq t1, t2, fin
#-- Comprobar si el caracter es a1
#-- Si NO lo es pasar al siguiente
bne t1, a1, next
#-- Caracter detectado. Incrementar contador
addi t0, t0, 1
next:
#-- Apuntar al siguiente caracter
addi a0, a0, 1
#-- Repetir el bucle
b bucle
fin:
#-- Devolver el numero de caracteres
mv a0, t0
#--- Punto de salida
ret
Ahora en el programa principal hay que tener cuidado, porque podemos violar el convenio del uso de los registros. En el registro s0 se guarda la dirección de la cadena introducida por el usuario. El convenio de uso de registros NOS GARANTIZA que tras la llamada a la subrutina len, los registros s0-s11 mantienen el mismo valor que tenían antes de la llamada. Esto NO está garantizado para los registros temporales
#-- Solución al ejercicio 5
#-- Programa principal
#-- Contar el número de caracteres 'a' y 'e' que hay en una frase pedida al usuario
.include "servicios.asm"
#-- Longitud maxima de la cadena
.eqv MAX 1024
.data
msg1: .string "Introduce una cadena: "
msg2: .string "Numero de caracteres 'a': "
msg3: .string "\nNumero de caracteres 'e': "
#-- Cadena introducida por el usuario
cad: .space MAX
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir cadena
la a0, cad
li a1, MAX
li a7, READ_STRING
ecall
#-- Almacenar el puntero a la cadena en s0
#-- ¡OJO! ESTO ES MUY IMPORTANTE! Hay que almacenarlo en
#-- cualquiera de los registros estáticos s0-s11...
#-- PERO NO PODEMOS USAR LOS TEMPORALES t0-t6
mv s0, a0
#-- Llamar a la funcion na = count(a0, 'a')
li a1, 'a'
jal count
#-- El convenio de uso de registros especifica que tras la ejecucion
#-- de una subrutina, los registros estáticos s0-s11 deben tener
#-- el mismo valor que tenian antes de la llamada....
#-- Sin embargo el valor de los temporales se debe considerar
#-- perdido. Por eso hemos almacendo el puntero de la cadena en S0
#-- a0 Contiene el número de 'a'. Lo guardamos en t0
mv t0, a0
#-- Imprimir mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Imprimir la cantidad
mv a0, t0
li a7, PRINT_INT
ecall
#-- Llamar a la función na = count(a0, 'e')
mv a0, s0
li a1, 'e'
jal count
#-- a0 Contiene el número de 'e'. Lo guardamos en t0
mv t0, a0
#-- Imprimir mensaje 3
la a0, msg3
li a7, PRINT_STRING
ecall
#-- Imprimir la cantidad
mv a0, t0
li a7, PRINT_INT
ecall
#-- Terminar
li a7, EXIT
ecall
En este ejercicio hay que crear un programa principal que realiza llamadas a dos subrutinas, cada una de ellas en un fichero diferente. En dos pestañas abrimos las subrutinas de los ejercicios 4 y 5: len() y count(). En una tercera pestaña colocamos el programa principal, desde donde ensamblamos:
#-- Solución al ejercicio 6
#-- Programa principal
#-- Contar el número de caracteres 'a' y 'e' que hay en una frase pedida al usuario
#-- Contar la longitud total de la cadena
.include "servicios.asm"
#-- Longitud maxima de la cadena
.eqv MAX 1024
.data
msg1: .string "\nIntroduce una cadena: "
msg2: .string "\nNumero de caracteres 'a': "
msg3: .string "\nNumero de caracteres 'e': "
msg4: .string "\nLongitud total: "
#-- Cadena introducida por el usuario
cad: .space MAX
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir cadena
la a0, cad
li a1, MAX
li a7, READ_STRING
ecall
#-- Almacenar el puntero a la cadena en s0
#-- ¡OJO! Solo podemos almacenarlo en un registro estático...
#-- porque tras la llamada a subrutina está garantizado
#-- que su valor se mantiene... Pero NO ESTÁ GARANTIZADO
#-- para los temporales
mv s0, a0
#-- Llamar a la función n = len(a0)
jal len
#-- a0 contiene la longitud
mv t0, a0
#-- Imprimir mensaje 4
la a0, msg4
li a7, PRINT_STRING
ecall
#-- Imprimir la longitud de la cadena
mv a0, t0
li a7, PRINT_INT
ecall
#-- Llamar a la funcion na = count(a0, 'a')
mv a0, s0
li a1, 'a'
jal count
#-- a0 Contiene el número de 'a'. Lo guardamos en t0
mv t0, a0
#-- Imprimir mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Imprimir la cantidad
mv a0, t0
li a7, PRINT_INT
ecall
#-- Llamar a la función na = count(a0, 'e')
mv a0, s0
li a1, 'e'
jal count
#-- a0 Contiene el número de 'e'. Lo guardamos en t0
mv t0, a0
#-- Imprimir mensaje 3
la a0, msg3
li a7, PRINT_STRING
ecall
#-- Imprimir la cantidad
mv a0, t0
li a7, PRINT_INT
ecall
#-- Terminar
li a7, EXIT
ecall
La función para detectar si una cadena es un palíndromo tiene un parámetro de entrada: el puntero a la cadena (a0) y devuelve, también por a0, un 1 en caso de que sea un palíndromo o un 0 si NO lo es.
#--------------------------------------------------------------
#-- Función para detectar si una cadena es palíndromo o no
#-- ENTRADAS:
#-- * a0: Puntero a la cadena a analizar
#-- SALIDAS:
#-- * a0: 0: NO es palíndromo
#-- 1: SI es palíndromo
#-------------------------------------------------------------
#-- Punto de entrada
.globl palindromo
.text
palindromo:
#-- Inicializar los punteros a la cadena
#-- t0: Puntero izquierdo
#-- t1: Puntero derecho
mv t0, a0
mv t1, t0
#----------- Actualizar t1 para que apunte al final de la cadena
#-- Comprobar si el caracter actual es 0
bucle1:
lb t2, 0(t1)
beq t2, zero, final_cadena
#-- Apuntar al siguiente caracter
addi t1, t1, 1
b bucle1
final_cadena:
#-- t1 apunta al final de la cadena
#-- Hay que retroceder 2 caracteres: uno es el 0, el otro \n
addi t1, t1, -2
#-- Ahora t1 apunta al último carácter ASCII legible
#-- Que comiencen los juegos del palindromo!
bucle:
#-- Condicion de salida: si el puntero derecho (t1)
# es menor o igual que el izquierdo (t0):Terminamos:
# es un palindromo
ble t1, t0, es_palindromo
#-- Leer caracteres izquierdo (t2) y derecho (t3)
lb t2, 0(t0)
lb t3, 0(t1)
#-- Si no son iguales: no es un palindromo
bne t2, t3, no_palindromo
#-- Actualizar los punteros
addi t0, t0, 1 #-- Puntero izquierdo
addi t1, t1, -1 #-- Puntero derecho
#-- repetir
b bucle
#------- La palabra NO es un palindromo
no_palindromo:
#-- a0 = 0: No es un palindromo
li a0, 0
b fin
#--------- La palabra SÍ es un palíndromo
es_palindromo:
#-- a0 = 1: Si es palíndromo
li a0, 1
fin:
ret
Este es el programa principal. El bucle se repite hasta que se introduce la cadena vacía: "\n" (Pulsar ENTER)
#-- Solucion ejercicio 7
#-- Determinar si una cadena es un palindromo o no
#-- El usuario debe introducir la palabra por la consola
#-- Ejemplos de palindromos: rotor
.include "servicios.asm"
.eqv MAX 1024
.data
#-- Almacenamiento de la cadena introducida por el usuario
cadena: .space MAX
#-- Mensajes a imprimir en la consola
msg1: .string "\n\nIntroduzca cadena: "
pal_si: .string "ES UN PALINDROMO"
pal_no: .string "NO es palindromo"
.text
bucle:
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Esperar a que el usuario introduzca la cadena
la a0, cadena
li a1, MAX
li a7, READ_STRING
ecall
#-- Si el primer caracter de la cadena es '\n' --> Hemos terminado
lb t0, 0(a0)
li t1, '\n'
beq t0, t1, fin
#-- Llamar a la funcion palindromo(a0)
jal palindromo
#-- a0 contiene la respuesta
beq a0, zero, no_palindromo
#-- ES un palindromo
#-- Imprimir mensaje
la a0, pal_si
li a7, PRINT_STRING
ecall
#-- Repetir
b bucle
#------- La palabra NO es un palindromo
no_palindromo:
#-- Imprimir mensaje
la a0, pal_no
li a7, PRINT_STRING
ecall
#-- Repetir
b bucle
fin:
# -- Terminar
li a7, EXIT
ecall
La función cifrar() tiene dos parámetros, el primero contiene la dirección de la cadena a cifrar, y el segundo es el número K con el que incrementar cada carácter para cifrarlo. No devuelve ningún valor de retorno. Esta es la subrutina:
#-----------------------------------------
#-- FUNCION void cifrar(*cad, K)
#-- ENTRADAS:
#-- * a0: Puntero a la cadena a cifrar
#-- * a1: Numero a incrementar cada caracter de la cadena en el cifrado
#-- SALIDAS: Ninguna
#--------------------------------------------------
.globl cifrar
.text
cifrar:
bucle:
#-- Leer caracter
lb t0, 0(a0)
#-- Condicion de finalizacion
li t1, '\n'
beq t0, t1, fin
#-- Sumar K al caracter
add t0,t0, a1
#-- Almacenar el caracter cifrado
sb t0, 0(a0)
#-- Apuntar al siguiente caracter
addi a0, a0, 1
#-- Repetir
b bucle
fin:
ret
Este es el programa principal:
#-- Solución al ejercicio 8
#-- Cifrar una cadena
.include "servicios.asm"
#-- Tamaño máximo de la cadena
.eqv MAX 1024
#-- Constante a sumar, para cifrar
.eqv K 5
.data
cad: .space MAX
msg1: .string "Introduce cadena: "
msg2: .string "Cadena cifrada: "
.text
#-- Imprimir mensaje msg1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir cadena al usuario
la a0, cad
li a1, MAX
li a7, READ_STRING
ecall
#-- Almacenar el puntero a la cadena en s0
#-- Solo lo podemos almacenar en registros estáticos
#-- que son los únicos que está garantizado que mantiene
#-- su valor al realizar una llamda a subrutina
mv s0, a0
#-- Llamar a la funcion cifrar(a0, K)
li a1, K
jal cifrar
fin: #-- Hemos terminado
#-- Imprimir el mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Imprimir la cadena
mv a0, s0
li a7, PRINT_STRING
ecall
#-- Terminar
li a7, EXIT
ecall
La subrutina para pasar a mayúsculas solo tiene un único parámetro de entrada: la cadena a convertir
#---------------------------------------------------------------
#-- Función void upper(*cad)
#-- ENTRADAS:
#-- * a0: Puntero a la cadena
#-- SALIDAS:
#-- Ninguna
#-----------------------------------------------------------------
#-- Punto de entrada
.globl upper
.text
upper:
bucle:
#----------- Pasar a mayúsculas
#-- Leer caracter
lb t0, 0(a0)
#-- Condicion de terminacion
beq t0, zero, fin
#-- Si el caracter es menor que 'a' o mayor que 'z'
#-- hay que pasar al siguiente
li t1, 'a'
blt t0,t1,next
li t1, 'z'
bgt t0, t1, next
#-- Hay que pasarlo a mayusculas
addi t0,t0,-32
#-- Guardarlo
sb t0, 0(a0)
next:
#-- Pasar al siguiente caracter
addi a0, a0, 1
#-- Repetir
b bucle
#-- Punto de salida
fin: ret
Y este es el programa princpal:
#-- Solución al ejercicio 9
#-- Pasar una cadena de minúsculas a mayúsculas
.include "servicios.asm"
#-- Tamaño máximo de la cadena
.eqv MAX 1024
.data
cad: .space MAX
msg1: .string "Introduce cadena: "
msg2: .string "Cadena en mayusculas: "
.text
#-- Imprimir mensaje msg1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir cadena al usuario
la a0, cad
li a1, MAX
li a7, READ_STRING
ecall
#-- Almacenar a0 en s0
mv s0, a0
#-- Pasar cadena a mayusculas
jal upper
#-- Imprimir el mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Imprimir la cadena
mv a0, s0
li a7, PRINT_STRING
ecall
#-- Terminar
li a7, EXIT
ecall
La función para pasar de cadena a número tiene un único parámetro de salida, y un valor de retorno:
#------------------------------------------------------------------------
#-- Funcion int atoi(*cad)
#-- ENTRDADAS:
#-- a0: Puntero a la cadena a convertir
#-- SALIDAS:
#-- a0: Numero
#------------------------------------------------------------------------
.globl atoi
.text
atoi:
#--- t0: Resultado parcial
li t0, 0
next_car:
#--- Leer digito
lb t1, 0(a0)
#-- ¿Es \n? Hemos terminado
#-- El resultado es el que esta en t0
li t5, '\n'
beq t1,t5, fin
#-- El caracter no es cero
#-- Multiplicar t0 por 10
li t2, 10
mul t0, t0, t2
#-- Obtener el numero del digito (t1 - '0')
li t3, '0'
sub t4, t1, t3 #-- t4 = t1 - '0
#-- t0 = t0 + t4
add t0, t0, t4
#-- Apuntar al siguiente caracter
addi a0, a0, 1
#-- Repetir
b next_car
#-- Punto de salida
fin:
#-- Devolver el valor calculado
mv a0, t0
ret
Este es el programa principal:
#-- Solución al ejericio 10: Conversion de una cadena a número
#-- No se tiene en cuenta la gestión de errores
#-- Supondremos que el usuario ha introducido una cadena con
#-- los caracteres '0'-'9'
.include "servicios.asm"
.eqv MAX 1024
.data
cadnum: .space MAX #-- Cadena a convertir
msg1: .string "\nIntroduzca cadena: "
msg2: .string "\nNumero: "
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir cadena al usuario
la a0, cadnum
li a1, MAX
li a7, READ_STRING
ecall
#-- Llamar a num = atoi(a0)
jal atoi
#-- El numero retornado lo podemos guardar en cualquier registro
#-- estático o temporal, porque no hay más llamadas a subrutina
mv s0, a0
#-- Imprimir el mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Imprimir el numero calculado
mv a0, s0
li a7, PRINT_INT
ecall
#-- Terminar
li a7, EXIT
ecall
Solución a los ejercicios de la Sesión de laboratorio L10:4-2
Tenemos un fragmento de código del Programa principal. Este dato es muy importante. Como es un fragmento de código, no sabemos qué hay al principio ni qué hay al final. Tampoco sabemos qué hace la función tarea1 (ni qué registros usa)
#....
li s0, 30
li t0, 5
jal tarea1
mv a0, s0
mv a1, t0
#....
- ¿Cuánto vale a0? Nos están pidiendo el valor de a0 tras ejecutarse este fragmento de código. La solución es: a0 = 30
El razonamiento es el siguiente. Primer se asgina el valor 30 al registro s0. Este registro es un registro estático. Y por tanto, preserva su valor entre llamadas. Es decir, que tras realizar la llamada a tarea1 el registro s0 seguirá valiendo 30. Esto me lo garantiza la ABI del RISCV (El valor de s0 SIMEMPRE se preserva)
Por tanto, al retornar de tarea1, s0 vale 30. Y luego en el programa principal asignamos s0 al registro a0, por lo que a0 valdrá 30
- ¿Cuánto vale t0?. La respuesta es: ESTE CÓDIGO NO ES VÁLIDO PORQUE SE ESTÁ VIOLANDO LA ABI DEL RISCV (El convenio de uso de registros). El registro t0 NO preserva su valor entre llamadas. Esto quiere decir que tras invocar a la función tarea1, su valor es INDEFINIDO (puede tener cualquier valor, según cómo esté implementada la tarea 1). Pero al margen del valor que tenga, tras una llamada a subrutina NO PODEMOS leer ningún registro que no preserve su valor. Los podemos usar, pero primero hay que inicializarlos a un valor conocido
Nos indican que es un fragmento del programa principal y que la función print_int admite un parámetro de entrada y ninguno de salida. El valor pasado como parámetro se imprime en la consola
#....
li a0, 5
jal print_int
jal print_int
#.....
La solución es que ESTE PROGRAMA ES INVÁLIDO porque VIOLA el convenio del uso de registros del RISCV en la segunda llamada a print_int. La primera llamada se hace correctamente. Se pasa el valor 5 como primer argumento y se llama a la función print_int. Por tanto, se imprimirá el "5" en la consola. Hasta aquí todo ok.
Sin embargo, el registro a0 NO PRESERVA SU VALOR entre llamadas. Al realizar la primera llamada a0 vale 5, pero la función no retorna ningún valor (que según el convenio ser haría por a0) por lo que el valor real de a0 puede ser cualquiera (depende de cómo esté implementada la función print_int). Es decir, su valor es INDEFINIDO. Y por tanto, estamos invocando (la segunda vez) a una función en la que le pasamos un parámetro INDEFINIDO. Es un código inválido porque es obligatorio que a0 tenga un valor conocido antes de llamar a print_int
Este es otro fragmento de un programa principal
li t0, 5
li s0, 25
jal tarea1
addi s1, s0, 1
jal tarea2
addi s2, s1, 1
mv t0, s0
- ¿Cuanto vale el registro t0 al ejecutarse? La solución es t0 = 25
De un vistazo rápido vemos que el registro s0 se inicializa al principio al valor 25. Al ser un registro estático, se preserva su valor entre llamadas. Y además no hay ninguna otra instrucción este fragmento de código que lo modifique. Por lo que en la última línea se asigna s0 al registro t0. Así, t0 contiene 25
Nos indican que la función tarea1() NO recibe ningún parámetro de entrada ni tiene ninguno de salida
# -- Punto de entrada
tarea1:
mv t1, s0
#.....aquí habría más código...
Se está VIOLADO el convenio de uso de registros porque la función tarea1 está leyendo un valor del exterior a través del registro s0. Sin embargo, es una función que NO recibe ningún ningún parámetro de entrada. Por el convenio de uso de los registros, para pasar argumentos a una función hay que hacerlo exclusivamente a través de los registros a0-a7. NO podemos pasar información a través de S0
Se define la función f = inc(x), que toma un valor de entrada y lo devuelve incrementado
#-- Punto de entrada
inc:
#-- Leer el primer argumento
mv s0, a0
#-- Incrementar su valor en una unidad
addi s0, s0, 1
#-- Devolver el valor incrementado por a0
mv a0, s0
ret
En esta función se ESTÁ VIOLANDO el convenio de uso de los registros. El registro s0 debe preservar su valor: El valor que tenga en el punto de entrada debe ser igual al valor que tenga en el punto de salida. Y esto no es así. Al almacenar a0 en s0, estamos modificando el valor de s0
Este es el fragmento de la función par():
#-- Punto de entrada
par:
#.... resto de instrucciones
#.... es par
li t0, 1
#-- Imprimir mensaje
jal mensaje_par
#-- devolver codigo de retorno (que está en t0)
mv a0, t0
ret
Se está VIOLANDO el convenio de uso de registros. Tras llamarse a la función mensaje_par() el valor del registro t0 es INDEFINIDO. No podemos leer t0 para asignarlo a a0. Antes hay que inicializarlo a un valor conocido.
El problema está con el registro t0, que NO preserva su valor al realizar la llamada a tarea1. Como estamos en el programa principal, la solución más sencilla es utilizar un registro que SÍ preserve su valor, como por ejemplo el s1. Así, si sustituimos t0 por s1, ya no se viola el convenio
#....
li s0, 30
li s1, 5
jal tarea1
mv a0, s0
mv a1, s1
#....
El registro a0 hay que usarlo como argumento en ambas llamadas a print_int. Una solución es guardar a0 en s0, que preserva su valor, y luego volver a asignárselo a a0 antes de llamar a print_int() la segunda vez. Podemos asignar cualquier valor a s0 ya que estamos en el programa principal
#....
li a0, 5
mv s0, a0
jal print_int
mv a0,s0
jal print_int
#.....
La solución más sencilla es NO usar el registro s0, para no violar el convenio. Podemos usar directamente el registro a0 para incrementarlo y devolver su valor
#-- Punto de entrada
inc:
#-- Incrementar su valor en una unidad
addi a0, a0, 1
ret
Si por alguna razón necesitásemos utilizar el registro s0, el código anterior sería válido, pero antes hay que guardar s0 en la pila, y luego recuperarlo al final:
#-- Punto de entrada
inc:
#-- Reservar espacio para la pila
addi sp, sp, -16
#-- Almacenar s0 en la pila
sw s0, 0(sp)
#-- Leer el primer argumento
mv s0, a0
#-- Incrementar su valor en una unidad
addi s0, s0, 1
#-- Devolver el valor incrementado por a0
mv a0, s0
#-- Recuperar s0 de la pila
lw s0, 0(sp)
#-- Liberar espacio de la pila
addi sp, sp, 16
ret
Para preservar el valor de t0 al realizar la llamada a mensaje_par() hay que guardarlo en la pila, y luego recuperar. Así que en el comienzo de la subrutina reservamos espacio para la pila y al final lo recuperamos
#-- Punto de entrada
par:
#-- Reservar espacio en la pila
addi sp, sp, -16
#.... resto de instrucciones
#.... es par
li t0, 1
#-- Guardar t0 en la pila
sw t0, 0(sp)
#-- Imprimir mensaje
jal mensaje_par
#--- Recuperar t0 de la pila
lw t0, 0(sp)
#-- devolver codigo de retorno (que está en t0)
mv a0, t0
#--- Liberar espacio de la pila
addi sp, sp, 16
ret
El programa principal es el que nos dan en el enunciado, no hay que realizar ninguna modificación. La función tarea2() se implementa en un fichero separado. Como es una función hoja, que no realiza llamadas a ninguna otra función, no hace falta guardar la dirección de retorno en la pila. Y no necesitamos preservar ningún registro, por lo que no nececitamos pila:
- Función tarea2()
#-------------------------
#-- Subrutina tarea2
#-- ENTRADAS: Ninguna
#-- SALIDAS: Ninguna
#-------------------------
#-- Punto de entrada
.globl tarea2
.include "servicios.asm"
.data
msg: .string " Tarea 2\n"
.text
tarea2:
#-- Imprimir mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- Terminar
ret
- Función tarea1()
Ahora es una subrutina intermedia, que realiza una llamada a la subrutina tarea2(). Ya no es una subrutina Hoja, por lo que necesitaremos crear la pila para guardar la dirección de retorno
#---------------------------------
#-- Subrutina Tarea 1
#-- ENTRADAS: Ninguna
#-- SALIDAS: Ninguna
#---------------------------------
#-- Punto de entrada
.globl tarea1
.include "servicios.asm"
.data
msg: .string " Tarea 1\n"
.text
#-------- Punto de entrada
tarea1:
#-- Reservar memoria en la pila
addi sp,sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Imprimir el mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- Llamar a la tarea 2
jal tarea2
#-- Imprimir el mensaje
la a0, msg
li a7, PRINT_STRING
ecall
##-- Recuperar dirección de retorno
lw ra, 12(sp)
#-- Liberar memoria de la pila
addi sp, sp, 16
#------- Punto de salida
ret
- Programa principal: No tiene ninguna complicación. Sólo hay que asegurarse de que se llama correctamente a la función print_int() pasando el número a través de registro a0 (y sólo se puede pasar a través de a0)
#---------- Programa pricipal
.include "servicios.asm"
.data
msg: .string "Introduce un numero: "
.text
#-- Imprimir cadena
la a0, msg
li a7, PRINT_STRING
ecall
#-- Pedir numero al usuario
li a7, READ_INT
ecall
#-- a0 contiene el numero
#-- Llamar a print_int(a0)
jal print_int
#-- Terminar
li a7, EXIT
ecall
- Función print_int(): Se trata de una función Hoja, por lo que no es necesario guardar en la pila la dirección de retorno. Sin embargo, sí que necesitamos la pila para guardar el número entero recibido como parámetro en a0, ya que hay que usar el registro a0 para imprimir la cadena "-->". Luego se recupera el valor de a0 de la pila y se imprime el número entero. Necesitamos una pila mínina: usamos 16 bytes. Podemos elegir cualquiera de las 4 palabras de la pila para guardar el registro: offset 0,4,8 ó 12. En esta solución se ha usado el offset 12
#-----------------------------------------
#-- Subrutina: Print_int(num)
#-- ENTRADA:
#-- a0: Imprimir el numero entero
#-- SALIDA: Ningua
#-----------------------------------------
#-- Punto de entrada
.globl print_int
.include "servicios.asm"
.data
msg: .string "---> "
.text
print_int:
#-- El parametro a0 lo necesitamos para imprimir la
#-- la cadena "--->"
#-- Para no perderlo, hay que guardarlo en la pila
#-- Crear pila
addi sp, sp, -16
#-- Guardar a0 en la pila
sw a0, 12(sp)
#-- Imprimir el mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- Recuperar a0 de la pila
lw a0, 12(sp)
#-- Imprimir el numero entero
li a7, PRINT_INT
ecall
#-- Recueprar pila
addi sp, sp, 16
ret
En total este proyecto consta de 4 partes, situadas cada una en un fichero separado: Programa principal, y las funciones operar(), print_int() y mult2()
- Programa principal: Hay meter el número entero en a0 y llamar a la función operar()
#---------- Programa pricipal
.include "servicios.asm"
.data
msg: .string "Introduce un numero: "
.text
#-- Imprimir cadena
la a0, msg
li a7, PRINT_STRING
ecall
#-- Pedir numero al usuario
li a7, READ_INT
ecall
#-- a0 contiene el numero
#-- Llamar a operar(a0)
jal operar
#-- Terminar
li a7, EXIT
ecall
- Función print_int(): La función print_int() es similar a la del ejercicio 4, pero hay que imprimir un carácter de salto de línea (\n) al final:
#-----------------------------------------
#-- Subrutina: Print_int(num)
#-- ENTRADA:
#-- a0: Imprimir el numero entero
#-- SALIDA: Ningua
#-----------------------------------------
#-- Punto de entrada
.globl print_int
.include "servicios.asm"
.data
msg: .string "---> "
.text
print_int:
#-- El parametro a0 lo necesitamos para imprimir la
#-- la cadena "--->"
#-- Para no perderlo, hay que guardarlo en la pila
#-- Crear pila
addi sp, sp, -16
#-- Guardar a0 en la pila
sw a0, 12(sp)
#-- Imprimir el mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- Recuperar a0 de la pila
lw a0, 12(sp)
#-- Imprimri el numero entero
li a7, PRINT_INT
ecall
#-- Imprimir un \n
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Recueprar pila
addi sp, sp, 16
ret
- Función operar(). Al tratarse de una función intermedia, que a su vez llama a otras, tenemos que almacenar la dirección de retorno en la pila. Además, como se llama primero a la función print_int() con el parámetro a0, tenemos que guardarlo en la pila, porque lo necesitamos más adelante para llamar a mult2() y nuevamente a print_int(). Elegimos por ejemplo la palabra que está debajo de ra, que tiene offset 8, pero podríamos almacenarlo en cualquiera de las otras: offset 0, 4 u 8
#-----------------------------------
#--- Subrutina operar(num)
#--- ENTRADAS:
#-- * a0: Numero entero
#-- SALIDAS: Ninguna
#-----------------------------------
#-- Punto de entrada
.globl operar
.text
operar:
#-- Crear la pila
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Hay que guardar el numero en la pila
#-- tras el jal lo hemos perdido
#-- (El reg. a0 NO preservar su valor)
sw a0, 8(sp)
#-- Imprimir el numero
jal print_int
#-- Recuperar el numero de la pila
lw a0, 8(sp)
#-- Multiplicar por dos
#-- a0 = mult2(a0)
jal mult2
#-- Imprimir el numero multiplicado por dos
#-- En a0 ya tenemos el numero multiplicado por dos
#-- porque nos lo ha devuelto la funcion mult2
jal print_int
#-- Recuperar el numero otra vez de la pila
lw a0, 8(sp)
#-- Volver a imprimirlo
jal print_int
#-- Recuperar direccion de retorno
lw ra, 12(sp)
#-- Recuperar la pila
addi sp, sp, 16
ret
- Función mult2(): Se trata de una función hoja, por lo que NO hay que guardar la dirección de retorno. Tampoco es necesario preservar el valor de ningún registro por lo que la pila no es necesaria. La función de multiplicar por dos se ha implementado con una suma: 2 * x = x + x. Pero también valdría la instrucción de multiplicación. Eso a voluntadad del programador
#----------------------------------------------
#-- Subrutina mult2(num)
#-- Multiplicar el numero por dos
#-- ENTRADA:
#-- * a0: numero a multiplicar por dos
#-- SALIDA:
#-- * a0: Numero multiplicado por dos
#-------------------------------------------------
#-- Punto de entrada
.globl mult2
.text
mult2:
#-- Multiplicar por dos num es calcular num + num
add a0, a0, a0
ret
- Programa principal: Como hay que pedir las dos coordenadas al usuario, usando el servicio READ_INT, guardamos a0 en el registro estático s0 (para preservar su valor, que será necesario más adelante, al llamar a la función operar())
#---- Programa principal
.include "servicios.asm"
.data
msg_x: .string "Coordenada x: "
msg_y: .string "Coordenada y: "
.text
#----- Pedir la coordenada x
la a0,msg_x
li a7, PRINT_STRING
ecall
li a7, READ_INT
ecall
#-- Guardar cordenada x en s0
mv s0, a0
#----- Pedir la coordenada y
la a0,msg_y
li a7, PRINT_STRING
ecall
li a7, READ_INT
ecall
#-- Guardar cordenada y en a1
mv a1, a0
mv a0, s0
#--- Llamar a la subrutina operar(x,y)
jal operar
#-- Terminar
li a7, EXIT
ecall
- Función print_vec(): Es una función hoja, por lo que no hace falta guardar su dirección de retorno en la pila. Sin embargo, es necesario usar la pila para guardar el parámetro x, que viene por a0, ya que el registro a0 lo nencesitamos para llamar al servicio de imprimir un carácter. Podemos elegir cualquier de las 4 posiciones libres de la pila: offsets 0, 4, 8 u 12. En este ejemplo hemos usado el 0
#-----------------------------------------
# Subrutina print_vect(x,y)
#--
#-- Imprimir el vector (x,y)
#--
#-- ENTRADA:
# a0: Coordenada x
# a1: Coordenada y
#-- SALIDA: Ninguna
#--------------------------------------------
#-- Punto de entrada
.globl print_vec
.include "servicios.asm"
.text
print_vec:
addi sp, sp, -16
#-- Necesitamos guardar a0 en la pila
sw a0, 0(sp)
li a0, '('
li a7, PRINT_CHAR
ecall
#-- Imprimir coordenada x
lw a0, 0(sp)
li a7, PRINT_INT
ecall
#-- Imprimir la coma
li a0, ','
li a7, PRINT_CHAR
ecall
#-- Imprimir coordenada y
mv a0, a1
li a7, PRINT_INT
ecall
li a0, ')'
li a7, PRINT_CHAR
ecall
li a0, '\n'
li a7, PRINT_CHAR
ecall
addi sp, sp, 16
ret
- Función operar(): Es una función intermedia, por lo que hay que guardar en la pila la dirección de retorno. Además, tenemos que guardar los registro a0 y a1 que contienen los parámetros x,y, ya que se realiza una llamada a print_vec() al comienzo, y para cumplir el convenio, sus valores se deben dar por perdidos. Cuando los necesitamos más adelante los leemos de la pila
#-----------------------------------------------------
#-- Subrutina operar(x,y)
#--
#-- Imprimir el vector (x,y)
#-- Imprimir el vector incrementado (x+1, y+1)s
#--
#-- ENTRADAS:
#-- a0: Coordenada x
#-- a1: Coordenada y
#-- SALIDAS: Ninguna
#----------------------------------------------------
#-- Punto de entrada
.globl operar
.text
operar:
#--- Crear pila
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Almacenar a0 y a1 en la pila
sw a0, 8(sp)
sw a1, 4(sp)
#-- Imprimir el vector (x,y)
jal print_vec
#-- Recuperar x,y
lw a0, 8(sp)
lw a1, 4(sp)
#-- Incrementar las coordenadas
addi a0, a0, 1
addi a1, a1, 1
#-- Imprimir el vector incrementado (x+1, y+1)
jal print_vec
#-- Recuperar direccion de retorno
lw ra, 12(sp)
addi sp, sp, 16
ret
- Programa principal: El programa principal es igual, pero ahora debe invocar a la función operar pasándole 4 argumentos. A través del registro a2 pasamos el incremento de x, y a través de a3 el incremento de y
#---- Programa principal
.include "servicios.asm"
.data
msg_x: .string "Coordenada x: "
msg_y: .string "Coordenada y: "
.text
#----- Pedir la coordenada x
la a0,msg_x
li a7, PRINT_STRING
ecall
li a7, READ_INT
ecall
#-- Guardar cordenada x en s0
mv s0, a0
#----- Pedir la coordenada y
la a0,msg_y
li a7, PRINT_STRING
ecall
li a7, READ_INT
ecall
#-- Guardar cordenada y en a1
mv a1, a0
mv a0, s0
#--- Llamar a la subrutina operar(x,y, 10, 100)
li a2, 10
li a3, 100
jal operar
#-- Terminar
li a7, EXIT
ecall
- Función: operar(): Se trata de una función intermedia, por lo que habrá que guardar la dirección de retorno en la pila. Además, es necesario guardar sus cuadro argumentos: a0, a1, a2, y a3 para no perderlos al llamar a print_vec (Sus valores NO se preservan). Por ello, necesitamos crear una pila con espacio para 5 palabras: la dirección de retorno más los cuatro parámetros. Como el espacio reservado en la pila tiene que ser múltiplo de 16 bytes, pasamos a una pila de 32 bytes (8 palabras). Ahora la dirección de retorno se almacena en el offset 28, y el resto de parámetros en los offsets que queramos. En esta implementación se han elegido 0,4,8 y 12 para los registros a0,a1,a2 y a3 respectivamente
#-----------------------------------------------------
#-- Subrutina operar(x,y, xinc, yinc)
#--
#-- Imprimir el vector (x,y)
#-- Imprimir el vector incrementado (x+xinc, y+yinc)
#--
#-- ENTRADAS:
#-- a0: Coordenada x
#-- a1: Coordenada y
#-- a2: Incremento de x
#-- a3: Incremento de y
#-- SALIDAS: Ninguna
#----------------------------------------------------
#-- Punto de entrada
.globl operar
.text
operar:
#--- Crear pila
addi sp, sp, -32
#-- Guardar direccion de retorno
sw ra, 28(sp)
#-- Almacenar los 4 parametros en la pila
sw a0, 0(sp)
sw a1, 4(sp)
sw a2, 8(sp)
sw a3, 12(sp)
#-- Imprimir el vector (x,y)
jal print_vec
#-- Recuperar x,y, xinc, yinc
lw a0, 0(sp)
lw a1, 4(sp)
lw a2, 8(sp)
lw a3, 12(sp)
#-- Incrementar las coordenadas
add a0, a0, a2
add a1, a1, a3
#-- Imprimir el vector incrementado (x+1, y+1)
jal print_vec
#-- Recuperar direccion de retorno
lw ra, 28(sp)
addi sp, sp, 32
ret
- Función print_vec(): Esta función es igual que en el ejercicio 6. No hace falta modificarla
- Programa principal: Se usan los regitros estáticos s0 y s1 para guardar los valores de carácter a usar y la anchura del cuadrado a dibujar. La función box() tiene 3 parámetros de entrada por lo que hay que usar los registros a0, a1 y a2
#---- Programa principal
.include "servicios.asm"
.data
msg_car: .string "\nCaracter: "
msg_anch: .string "\nAnchura: "
msg_alt: .string "Altura: "
.text
#----- Pedir caracter
la a0,msg_car
li a7, PRINT_STRING
ecall
li a7, READ_CHAR
ecall
#-- Guardar caracter en s0
mv s0, a0
#----- Pedir la anchura
la a0, msg_anch
li a7, PRINT_STRING
ecall
li a7, READ_INT
ecall
#-- Guardar anchura en s1
mv s1, a0
#----- Pedir la altura
la a0, msg_alt
li a7, PRINT_STRING
ecall
li a7, READ_INT
ecall
#-- Guardar altura en a2
mv a2, a0
#--- Llamar a la subrutina box(car, anch, alt)
mv a0, s0
mv a1, s1
jal box
#-- Terminar
li a7, EXIT
ecall
- Función line(): Es una función hoja, por lo que no hay que guardar la dirección de retorno. Tampoco es necesario presevar el valor de ningún registro, por lo que no necesitamos usar la pila
#-----------------------------------------
# Subrutina line(car, long)
#--
#-- Imprimir una linea con el caracter car y de longitud long
#--
#-- ENTRADA:
# a0: caracter a usar en la linea
# a1: Numero de caracteres a usar
#-- SALIDA: Ninguna
#--------------------------------------------
#-- Punto de entrada
.globl line
.include "servicios.asm"
.text
line:
bucle:
#-- Quedan caracteres por imprimir?
beq a1, zero, fin
#-- Si. Imprimir el siguiente caracter
li a7, PRINT_CHAR
ecall
#-- Queda un caracter menos
addi a1, a1, -1
#-- Repetir
b bucle
fin: #-- Imprimir el caracter de new line
li a0, '\n'
li a7, PRINT_CHAR
ecall
ret
- Función box(): Es una función intermedia por lo que tenemos que usar la pila para guardar la dirección de retorno. Además, necesitamos guardar en la pila los registros a0 y a1. En el bucle principal usamos el registro s0 como contador, de manera que hay que guardarlo también en la pila, para que la función principal no "vea" que se ha modificado, y cumplir con el convenio del uso de los registros
#-----------------------------------------------------
#-- Subrutina box(car, anch, alt)
#--
#-- Imprimir un rectangulo de caracteres car,
#-- de anchura anch y altura alt
#--
#-- ENTRADAS:
#-- a0: Caracter a usar para dibujar el rectangulo
#-- a1: Anchura del rectangulo
#-- a2: Altura del rectangulo
#-- SALIDAS: Ninguna
#----------------------------------------------------
#-- Punto de entrada
.globl box
.text
box:
#--- Crear pila
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Guardar parametros en la pila
sw a0, 0(sp)
sw a1, 4(sp)
#-- El registro s0 lo usamos como contador
#-- Hay que guardarlo en la pila
sw s0, 8(sp)
#-- Ahora es seguro modificar s0
mv s0, a2
bucle:
#--- Si quedan 0 lineas por imprimir--> terminar
beq s0, zero, fin
#-- Recuperar parametros de la pila
lw a0, 0(sp) #-- Caracter
lw a1, 4(sp) #-- Anchura
#-- Imprimir una linea
jal line
#-- Queda un linea menos por imprimir
addi s0, s0, -1
#-- Siguiente linea
b bucle
fin:
#-- Restaurar valor de s0
lw s0, 8(sp)
#-- Recuperar direccion de retorno
lw ra, 12(sp)
addi sp, sp, 16
ret
- Programa principal:
#---- Programa principal
.include "servicios.asm"
.eqv MAX 1024
.data
prefijo: .space MAX
cadena: .space MAX
sufijo: .space MAX
msg_prefijo: .string "Prefijo: "
msg_cadena: .string "Cadena: "
msg_sufijo: .string "Sufijo: "
.text
#----- Pedir Cadena
la a0,msg_cadena
li a7, PRINT_STRING
ecall
la a0, cadena
li a1, MAX
li a7, READ_STRING
ecall
#----- Pedir prefijo
la a0,msg_prefijo
li a7, PRINT_STRING
ecall
la a0, prefijo
li a1, MAX
li a7, READ_STRING
ecall
#----- Pedir sufijo
la a0,msg_sufijo
li a7, PRINT_STRING
ecall
la a0, sufijo
li a1, MAX
li a7, READ_STRING
ecall
#-- Concatear la cadena al prefijo
la a0, prefijo
la a1, cadena
jal concat
#-- Concatenar el sufijo
la a0, prefijo
la a1, sufijo
jal concat
#-- Imprimir salto de linea
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Imprimir la cadena completa
la a0, prefijo
li a7, PRINT_STRING
ecall
#-- Terminar
li a7, EXIT
ecall
- Función concat(): Es una función hoja por lo que no necesitamos guardar la dirección de retorno. Tampoco tenemos que preservar ningún registro, por lo que no es necesario usar la pila
#-----------------------------------------------------
#-- Funcion: concat(pcad1, pcad2)
#--
#-- Concatenar la cadena 2 a la 1
#--
#-- ENTRADAS:
#-- a0: Puntero a la cadena 1
#-- a1: Puntero a la cadena 2
#-- SALIDAS: Ninguna
#----------------------------------------------------
#-- Punto de entrada
.globl concat
.text
concat:
#--Ir al final de la cadena 1
bucle:
li t1, '\n'
lb t0, 0(a0)
#-- Si llegamos al caracter '\n' hemos terminado
beq t0, t1, copiar
#-- Apuntar al siguiente caracter
addi a0, a0, 1
#-- Repetir
b bucle
#-- Copiar la cadena 2 a continuacion de la 1
copiar:
#-- Leer caracter de cadena 2
lb t1, 0(a1)
#-- Copiarlo en la cadena 1
sb t1, 0(a0)
#-- Si es el caracter \0 terminar
beq t1, zero, fin
#-- Incrementar los punteros
addi a0, a0, 1
addi a1, a1, 1
#-- Repetir
b copiar
fin:
#-- Terminar
ret
- Programa principal: Se usa el registro s0 para guardar la clave
#---- Programa principal
.include "servicios.asm"
.eqv MAX 1024
.data
cadena: .space MAX
msg_cadena: .string "Cadena: "
msg_clave: .string "Clave (0-255): "
msg_orig: .string "\nCadena sin cifrar: "
msg_cifrada: .string "Cadena cifrada: "
msg_descifr: .string "Cadena descifrada: "
.text
#----- Pedir Cadena
la a0,msg_cadena
li a7, PRINT_STRING
ecall
la a0, cadena
li a1, MAX
li a7, READ_STRING
ecall
#----- Pedir clave
la a0, msg_clave
li a7, PRINT_STRING
ecall
li a7, READ_INT
ecall
#-- Guardar a0 en s0
mv s0, a0
#-- Imprimir cadena original
la a0, msg_orig
li a7, PRINT_STRING
ecall
la a0, cadena
li a7, PRINT_STRING
ecall
#-- Cifrar la cadena
la a0, cadena
mv a1, s0
jal cifrar
#-- Imprimir cadena cifrada
la a0, msg_cifrada
li a7, PRINT_STRING
ecall
la a0, cadena
li a7, PRINT_STRING
ecall
#-- Descifrar la cadena
la a0, cadena
mv a1, s0
jal descifrar
#-- Imprimir cadena descifrada
la a0, msg_descifr
li a7, PRINT_STRING
ecall
la a0, cadena
li a7, PRINT_STRING
ecall
#-- Terminar
li a7, EXIT
ecall
- Función cifrar(): Es una función hoja por lo que no necesitamos guardar la dirección de retorno. Además tampoco es necesario preservar el valor de ningún registro, por lo que NO necesitamos usar la pila
#-----------------------------------------------------
#-- Funcion: cifrar(pcad, k)
#--
#-- Cifrar la cadena apuntada por pcad usando la clave k
#--
#-- ENTRADAS:
#-- a0: Puntero a la cadena
#-- a1: clave k
#-- SALIDAS: Ninguna
#----------------------------------------------------
#-- Punto de entrada
.globl cifrar
.text
cifrar:
bucle:
#-- Leer caracter
lb t0, 0(a0)
#-- Si es '\n' hemos terminado
li t1, '\n'
beq t0, t1, fin
#-- Sumar la clave
add t0, t0, a1
#-- Guardar el caracter cifrado
sb t0, 0(a0)
#-- Pasar al siguiente caracter
addi a0, a0, 1
#-- Repetir
b bucle
fin:
#-- Terminar
ret
- Función descifrar: Es una función intermedia: hay que guardar en la pila la dirección de retorno. Esta función simplemente multiplica por -1 la clave para obtener -k y usarla para descrifrar el mensaje
#-----------------------------------------------------
#-- Funcion: descifrar(pcad, k)
#--
#-- desCifrar la cadena apuntada por pcad usando la clave k
#-- Para descifrar hay que llamar a la funcio de cifrar con -k
#--
#-- ENTRADAS:
#-- a0: Puntero a la cadena
#-- a1: clave k
#-- SALIDAS: Ninguna
#----------------------------------------------------
#-- Punto de entrada
.globl descifrar
.text
descifrar:
#-- Crear la pila
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Multiplicar a1 por -1
li t0, -1
mul a1, a1,t0
#-- Llamar a cifrar
jal cifrar
#-- Recuperar la direccion de retorno
lw ra, 12(sp)
#-- Limpiar la pila
addi sp, sp, 16
#-- Terminar
ret
Solución a los ejercicios de la Sesión de laboratorio L11:4-3
- Programa principal. Es igual que el resto de programas principales. No se tiene que hacer nada especial por el hecho de llamar a una función recursiva. La longitud de la cadena se almacena en el registro s0
#--- Programa principal
#--- Imprimir la longitud de una cadena introducida por el usuario
#--- Se debe llamar a la funcion len(), que es recursiva
.include "servicios.asm"
.eqv MAX 1024
.data
cad: .space MAX
msg: .string "Longitud de la cadena: "
.text
#-- Pedir cadena
la a0, cad
li a1, MAX
li a7, READ_STRING
ecall
#-- Calcular su longitud
#-- a0 = len(pcad)
jal len
#-- Guardar la longitud en s0
mv s0, a0
#---- Imprimir mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- Imprimir la longitud
mv a0, s0
li a7, PRINT_INT
ecall
#-- Terminar
li a7, EXIT
ecall
- Función len(pcad): Cuando se alcanza el nivel mayor de profundidad, simplemente hay que retornar el valor 0, por lo que no hace falta almacenar la dirección de retorno. Sin embargo en el caso general, donde sí hay recursividad, utilizamos la pila para guardar la dirección de retorno al nivel superior
La función tiene un único punto de salida. No hace falta que sea así, pero es una buena práctica de programación
#------------------------------------------
#-- Subrutina len()
#-- Calcular la longitud de una cadena
#-- El final de la cadena se determina por el caracter \n
#-- (usando un algoritmo recursivo)
#-- ENTRADAS:
#-- a0: Puntero a la cadena
#-- SALIDAS:
#- a0: Longitud de la cadena
#-------------------------------------------
.globl len
len:
#-- Leer primer caracter de la cadena
lb t0, 0(a0)
#-- Comprobar si es el ultimo caracter
li t1, '\n'
bne t0, t1, len_rec
#-- Es el último carácter
#-- La longitud es 0
li a0, 0
#-- Ir al punto de salida
b fin
#-- No es el ultimo caracter
#-- La longitud será 1 + longitud de la cadena
#-- menos el caracter inicial
len_rec:
#-- Crear la pila para almacenar la direccion de retorno
addi sp, sp, -16
#-- Guardar la direccion de retorno
sw ra, 12(sp)
#-- Incrementar el puntero
addi a0, a0, 1
#-- Calcular la longitud de la nueva cadena
jal len
#-- La longitud será a0 + 1
addi a0, a0, 1
#-- REcuperar la direccion de retorno
lw ra, 12(sp)
#-- Recuperar la pila
addi sp, sp, 16
#-- Punto de salida
fin:
ret
- Programa principal:
#--- Programa principal
#-- Pedir al usuario una cadena e imprimirla del revés
#-- Ej: Hola --> aloH
.include "servicios.asm"
.eqv MAX 1024
.data
cad: .space MAX
msg1: .string "Introduce una cadena: "
.text
#-- Imprimri mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir cadena al usuario
la a0, cad
li a1, MAX
li a7, READ_STRING
ecall
#-- Llamar a la funcion reverse
jal print_reverse
#-- Terminar
li a7, EXIT
ecall
- Función print_reverse():
#-------------------------------------------------
#--- Subrutina Reverse(pcad)
#--- Imprimimr la cadena al revés
#--- El algoritmo es recursivo
#-- ENTRADAS:
#-- a0: Punter a la cadena a dar la vuelta
#-- SALIDAS:
#-- Ninguna
#--------------------------------------------------
.include "servicios.asm"
#-- Punto de entrada
.globl print_reverse
.text
print_reverse:
#-- Leer primer caracter
lb t0, 0(a0)
#-- Comprobar si es el ultimo (\n)
li t1, '\n'
bne t1, t0, reverse_rec
#-- Es el último: retornar
b fin
#-- No es el ultimo caracter
reverse_rec:
#-- Reservar memoria en la pila
addi sp, sp, -16
#-- Guardar dir. de retorno
sw ra, 12(sp)
#-- Guardar el primer caracter en la pila
sw t0, 0(sp)
#-- Apuntar al siguiente caracter
addi a0, a0, 1
#-- Imprimir al revés la cadena restante
jal print_reverse
#-- Imprimir el primer caracter al final
lw a0, 0(sp)
li a7, PRINT_CHAR
ecall
#-- Recuperar dir. de retorno
lw ra, 12(sp)
addi sp, sp, 16
#-- Punto de salida
fin:
ret
- Programa principal
#---- Programa principal
#---- Contar el numero de caracteres de una cadena
#---- Se pide una cadena al usuario
#---- se pide el caracter a contar
#---- se imprime la cantidad de ese caracter en la cadena
.include "servicios.asm"
.eqv MAX 1024
.data
msg1: .string "Introduce cadena: "
msg2: .string "Introduce caracter a contar: "
msg3: .string "\nCaracteres encontrados: "
cad: .space MAX
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir cadena
la a0, cad
li a1, MAX
li a7, READ_STRING
ecall
#-- Imprimir mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Pedir caracter
li a7, READ_CHAR
ecall
#-- llamar a la funcion count_char (pcad, char)
mv a1, a0
la a0, cad
jal count_char
#-- a0 contiene el resultado
#-- Guardar a0 en s0
mv s0, a0
#-- Imprimir mensaje 3
la a0, msg3
li a7, PRINT_STRING
ecall
#-- Imprimir cantidad de caracteres encontrados
mv a0, s0
li a7, PRINT_INT
ecall
#-- Terminar
li a7, EXIT
ecall
- Función count_char():
#------------------------------------------
#-- Funcion count_char(pcad, char)
#--
#-- Contar la cantidad de caracteres que hay en la cadena
#-- USANDO UN ALGORITMO RECURSIVO
#--
#-- ENTRADAS:
#-- a0: Puntero a la cadena
#-- a1: Caracter a encontrar
#-- SALIDA:
#-- a0: La cantidad de caracteres que hay
#--------------------------------------------
#-- Punto de entrada
.globl count_char
.text
count_char:
#-- Leer el primer caracter
lb t0, 0(a0)
#--bSi es \n se devuelve 0
li t1, '\n'
bne t0,t1, count_char_rec
#-- Es el ultimo caracter
#-- Devolver 0
li a0, 0
b fin
count_char_rec:
#-- Reservar espacio para la pila
addi sp, sp, -16
#-- Guardar la direccion de retorno
sw ra, 12(sp)
#-- Guardar el primer caracter en la pila
sw t0, 0(sp)
#-- Guardar el caracter a contar en la pila
sw a1, 4(sp)
#-- Contar la cantidad de caracteres en la subcadena
addi a0, a0, 1
jal count_char
#-- a0 contiene el numero de caracteres en la subcadena
#-- Si el primer caracter es igual al caracter a contar,
#-- lo incrementamos en una unidad
lw t0, 0(sp)
lw a1, 4(sp)
beq t0,a1, encontrado
#-- El primer caracter no es el buscado
#-- En a0 esta el numero de caracteres encontrdos en la subcadena
b terminar
encontrado:
#-- Incrementar a0 en una unidad: un caracter mas encontrado
addi a0, a0, 1
terminar:
#-- Recuperar la direccion de retorno
lw ra, 12(sp)
addi sp, sp, 16
#-- PUnto de salida
fin:
ret
- Programa principal
#------- Programa principal --------------
#-- Pedir al usuario un cadena que contenga digitos numeros '0'-'9'
#-- Se llama a la funcion de conversion para convertirlo en entero
#-- Se imprime el numero entero calculado
#-----------------------------------------
.include "servicios.asm"
.eqv MAX 10
.data
msg1: .string "Introduce un numero: "
msg2: .string "Numero entero: "
cad: .space MAX
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir la cadena
la a0, cad
li a1, MAX
li a7, READ_STRING
ecall
#-- Calcular el numero entero
jal conv
#-- En a0 está el numero calculado
#-- Guardarlo en s0
mv s0, a0
#-- Imprimir mensaje
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Imprimir el numero
mv a0, s0
li a7, PRINT_INT
ecall
#-- TErminar
li a7, EXIT
ecall
- Función conv():
#---------------------------------------------------------
#-- Subrutina conv(pcad)
#-- ENTRADA:
#-- a0: Puntero a la cadena con los digitos
#-- SALIDA:
#-- a0: Numero entero convertido
#-- a1: Peso del digito de mayor peso: 1,10,100...
#---------------------------------------------------------
.globl conv
.text
conv:
#-- Leer el caracter actual
lb t0, 0(a0)
#-- Si es \n se retorna (0,0). El peso es 0
li t1, '\n'
bne t0, t1, conv_rec
#-- Fin de la cadena
#-- Devolver (0,0)
li a0, 0
li a1, 0
b fin
#-- Caso general
conv_rec:
#-- Resevar espacio en la pila
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Guardar en la pila el primer digito
sw t0, 0(sp)
#-- Apuntar al siguiente byte
addi a0, a0, 1
#-- Calcular el numero de la subcadena
jal conv
#-- Convertir el primer digito a numero
lw t0, 0(sp)
li t1,'0'
sub t0, t0, t1
#-- Analizar los casos del numero calculado
#-- Si el peso es 0, retornar el (dig, 1)
beq a1, zero, peso_es_0
#-- El peso es diferente de 0
#-- Calcular la expresion dig*p*10 + n
#-- Multiplicar el peso por 10
li t1, 10
mul a1,a1,t1
#-- Multiplicar el digito por p*10
mul t2, a1, t0
#-- Sumar el numero de la subcadena
add a0, t2, a0
#-- Terminar
b terminar
peso_es_0:
mv a0, t0
li a1, 1
terminar:
#-- REcueprer direccion de retorno
lw ra, 12(sp)
#-- Liberar espacio de la pila
addi sp, sp, 16
fin:
ret
- Programa principal:
#-------- PROGRAMA PRINCIPAL --------------------
.include "servicios.asm"
.eqv MAX 1024
.eqv K 5
.data
msg1: .string "Introduce una cadena: "
msg2: .string "Introduce la clave (0-255): "
msg3: .string "Cadena cifrada: "
msg4: .string "Cadena descifrada: "
cad1: .space MAX
cad2: .space MAX
cad3: .space MAX
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir cadena inicial
la a0, cad1
li a1, MAX
li a7, READ_STRING
ecall
#-- Imprimir mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Pedir la clave
li a7, READ_INT
ecall
#-- Guardar la clave en s0
mv s0, a0
#--- Cifrar la cadena y depositarla en cad2
la a0, cad1
la a1, cad2
mv a2, s0
jal cifrar
#-- Imprimir mensaje
la a0, msg3
li a7, PRINT_STRING
ecall
#-- Imprimir la cadena cifrada
la a0, cad2
li a7, PRINT_STRING
ecall
#-- Descifrar la cadena
la a0, cad2
la a1, cad3
#-- a2 = -K
li t0, -1
mul a2, s0, t0
jal cifrar
#-- Imprimir mensaje 4
la a0, msg4
li a7, PRINT_STRING
ecall
#-- Imprimir la cadena descifrada
la a0, cad3
li a7, PRINT_STRING
ecall
#-- Terminar
li a7, EXIT
ecall
- Función cifrar()
#--------------------------------------------------
#-- Subrutina cifrar(pcad1, pcad2, K)
#-- Cifrar la cadena cad1 y almacenarla en cad2
#--
#-- ENTRADA:
#-- a0: Puntero a la cadena origen
#-- a1: Puntero a la cadena destino
#-- a2: Clave
#--
#-- SALIDA:
#-- Ninguna
#------------------------------------------------------
.globl cifrar
.text
cifrar:
#-- Leer el caracter inicial
lb t0, 0(a0)
#-- Si es igual a '\n' hemos terminado
li t1, '\n'
bne t0, t1, cifrar_rec
#-- Almacena en la cadena destino \n
#-- y finalizarla con el \0
sb t0, 0(a1)
sb zero, 1(a1)
#-- Terminar
b fin
cifrar_rec:
#-- Guardar espacio para la pila
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Cifrar el primer caracter
add t0, t0, a2
#-- Almacenarlo en la cadena destino
sb t0, 0(a1)
#-- Apuntar a las subcadenas
addi a0,a0, 1
addi a1,a1, 1
#-- Cifrar las subcadenas
jal cifrar
#-- Recuperar direccion de retorno
lw ra, 12(sp)
#-- Liberar espacio de la pila
addi sp, sp, 16
#-- Punto de salida
fin:
ret
- Programa principal:
#----- Programa principal
#-- Calculo del maximo
.include "servicios.asm"
.data
#-- Lista de bytes para calcular el valor maximo
num: .byte 5,8,9,20,112,73,21,7,0
msg1: .string "Valor maximo: "
.text
#-- Llamar a la funcion para calcular el maximo
la a0, num
jal maximo
#-- Guardar a0 en s0
mv s0, a0
#-- Imprimir el mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Imprimir el valor máximo
mv a0, s0
li a7, PRINT_INT
ecall
li a7, EXIT
ecall
- Función maximo()
#---------------------------------------------------
#-- Subrutina maximo(pdata)
#-- Calcular el maximo de un conjuto de datos almacenados definidos por su puntero
#-- El valor maximo se calcula de forma recursiva
#--
#-- ENTRADAS:
#-- a0: Puntero a los datos
#-- SALIDAS:
#-- a0: Valor maximo de esos datos
#-------------------------------------------------------------------------
#-- Punto de entrada
.globl maximo
.text
maximo:
#-- Leer el primer byte y comprobar si es el ultimo
lb t0, 0(a0)
#-- Comprobar si es el ultimo byte
bne t0,zero, maximo_rec
#-- Si no hay mas elementos, devolver 0
li a0, 0
b fin
maximo_rec:
#-- Crear la pila
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Guardar primer byte en la pila
sw t0, 0(sp)
#-- Calcular el maximo del resto de valores
addi a0, a0, 1
jal maximo
#-- Recuperar el valor maximo
lw t0, 0(sp)
#-- Calcular el maximo entre a0 y t0
bgt a0, t0, op1_max
#-- El valor maximo es t0
#-- Lo llevamos a a0 para devolverlo
mv a0, t0
op1_max:
#-- El valor maximo esta en a0
#--- Recueprar direccion de retorno
lw ra, 12(sp)
#-- Liberar espacio de la pila
addi sp, sp, 16
#--- Punto de salida
fin:
ret
- Programa principal:
#------- Programa principal
#-- Calculo de la secuencia de Fibonacci
.include "servicios.asm"
.data
msg1: .string "Introduce el término de Fibonacci a calcular (>0): "
msg2: .string "Resultado: "
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Pedir termino a calcular
li a7, READ_INT
ecall
#-- Llamar a fibo(n)
jal fibo
#-- Meter n en s0
mv s0, a0
#-- Imprimir mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Imprimir el resultado
mv a0, s0
li a7, PRINT_INT
ecall
li a7, EXIT
ecall
- Función Fibo():
#------------------
#-- Subrutina fibo(n)
#-- Punto de entrada
.globl fibo
.text
fibo:
#--- Si n <=2 se devuelve 1
li t0,2
bgt a0,t0, fibo_rec
#-- Devolver 1
li a0, 1
b fin
fibo_rec:
#-- Reservar espacio pila
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Guardar n en la pila
sw a0, 0(sp)
addi a0, a0, -1
#-- Llamar a fibo(n-1)
jal fibo
#-- a0 contiene fibo(n-1)
#-- guardarlo en la pila
sw a0, 4(sp)
#-- Recuerar n
lw a0, 0(sp)
addi a0, a0, -2
#-- Llamar a fibo(n-2)
jal fibo
#-- a0 contiene fibo(n-2)
#-- Recuperar fibo(n-1)
lw t0, 4(sp)
#-- a0 = fibo(n-1) + fibo(n-2)
add a0, t0, a0
#-- Recuperer direcciond de retorno
lw ra, 12(sp)
addi sp, sp, 16
#-- Punto de salida
fin:
ret
- Programa principal
#------ Programa principal
#-- Evalucacion de una suma de números de un solo dígito
#------------------------------------------------------------
.include "servicios.asm"
.data
msg1: .string "Expresion: "
#-- Cadena a evaluar (el resultado es 36)
expr: .string "1+5+3+4+5+8+9+0+1+0+0+0+0+0+0+0\n"
msg2: .string "Resultado: "
msg_error: .string "ERROR en la expresion"
.text
#-- Llamar a la funcion de evaluacion
la a0, expr
jal evaluar
#-- Comprobar si la expresion es correcta
bne a1,zero, error
#-- El resultado esta en a0
#-- Guardarlo en s0
mv s0, a0
#-- Imprimir el mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Imprimir la expresion
la a0, expr
li a7, PRINT_STRING
ecall
#-- Imprimir mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Imprimir el resultado
mv a0, s0
li a7, PRINT_INT
ecall
b fin
#-- Error en la expresion
error: #-- Imprimir mensaje error
la a0, msg_error
li a7, PRINT_STRING
ecall
fin:
#-- Terminar
li a7, EXIT
ecall
- Función is_digit():
#----------------------------------------------------------
#---- Subrutina: is_digit(char)
#---- Determina si el caracter es un dígito '0'-'9'
#----
#---- ENTRADA:
#-- * a0: Carácter a evaluar
#-- SALIDA:
#-- * a0: El digito convertido (si era un digito ok)
#-- * a1: 0--> Es un digito ok
#-- 1 --> No es un digito
#---------------------------------------------------------
#-- Punto de entrada
.globl is_digit
.text
is_digit:
#--- Comprobar si es un digito correcto
li t0, '9'
bgt a0, t0, error #-- a0 > '9' ---> Error
li t0, '0'
blt a0, t0, error #-- a0 < '0' --> Error
#-- Es un dígito correcto
#-- Pasarlo a entero
#-- a0 = a0 - '0'
sub a0, a0, t0
#-- Conversion ok
li a1, 0
#-- Terminar
b fin
error:
li a0, 0
li a1, 1
fin:
ret
- Función evaluar()
#----------------------------------
#-- Subrutina evaluar(pcad)
#--
#-- Evaluar una expresion compuesta por sumas de números de 1 dígito
#-- Se hace de forma recursiva
#--
#-- ENTRADAS:
#-- a0: Puntero a la cadena
#--
#-- SALIDAS:
#-- a0: Valor calculado
#-- a1:
#-- 1: Hay un error en la expresión
#-- 0: Expresion evaluada ok
#------------------------------------------
.globl evaluar
.text
evaluar:
#-- Crear pila
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Leer primer caracter
lb t0, 0(a0)
#-- Leer operacion
lb t1, 1(a0)
#-- Guardar operacion en la pila
sw t1,4(sp)
#-- Actualizar puntero para apuntar al siguiente digito
addi a0, a0, 2
#-- Guardar puntero en la pila
sw a0, 0(sp)
#-- a0 = primer digito
mv a0, t0
#-- Comprobar si es un digito
jal is_digit
#-- Guardar digito convertido
sw a0, 8(sp)
#-- Si a1 es distinto de cero, hay un error
bne a1,zero, error
#--Recuperar operacion
lw t0, 4(sp)
#-- Si la operacion es '\n', hay que devolver el valor del digito
li t1, '\n'
beq t0, t1, last_digit
#-- Comprobar si la operaccion es valida
li t1, '+'
bne t0, t1, error
#-- Evaluar la expresion
lw a0, 0(sp)
jal evaluar
#-- Recuperar digito convertido
lw t0, 8(sp)
#-- Sumar la expresion más el digito
add a0, a0,t0
#-- conversion ok
li a1,0
b terminar
last_digit:
#-- Recuperar digito convertido
lw a0, 8(sp)
li a1, 0
b terminar
#-- Devolver a1 = 1 para indicar error
error: li a0, 0
li a1, 1
terminar:
#-- Recueprar direccion de retorno
lw ra, 12(sp)
#-- Liberar la pila
addi sp, sp, 16
ret
- Programa principal
#--- Programa principal
#-- Contador recursivo
#-- Mostrar una cuenta desde 1 hasta 9 en la consola y en el display de 7 segmentos
.include "servicios.asm"
.text
#-- Poner el contador a 0
li a0, 0
jal print_display
#-- Contar hasta 10
li a0, 9
jal contar_up
#-- Terminar
li a7, EXIT
ecall
- Función print_display()
#------------------------------------------------------
#-- Subrutina para mostrar un digito '0'-'9' en el
#-- display de 7 segmentos
#-- ENTRADAS:
#-- a0: Numero a mostrar
#-- DEVUELVE:
#-- nada
#------------------------------------------------------
#-- Punto de entrada
.globl print_display
#-- Direccion en memoria del display derecho
.eqv DISP 0xFFFF0010
.data
#-- Tabla con los valores a envair
#-- para mostrar los numeros 0 - 9
#-- en el display de 7 segmentos
tabla: .byte 0x3F #-- Digito 0
.byte 0x06 #-- Digito 1
.byte 0x5B #-- Digito 2
.byte 0x4F #-- Digito 3
.byte 0x66 #-- Digito 4
.byte 0x6D #-- Digito 5
.byte 0x7D #-- Digito 6
.byte 0x07 #-- Digito 7
.byte 0x7F #-- Digito 8
.byte 0x6F #-- Digito 9
.text
print_display:
#-- Puntero base a la tabla de conversion
la t0, tabla
#-- Obtener direccion del digito: t0 + a0
add t1, t0, a0
#-- Leer el valor
lb t2, 0(t1)
#-- Meter en a0 la direccion base del display
li a0, DISP
#-- Escribirlo en la direccion del display de 7 segmentos
sb t2, 0(a0)
ret
- Función contar_up():
#---------------------------------------------------------------------------------------
#-- Subrutina contar-up(n)
#-- Contar desde 1 hasta n, usando un algoritmo recursivo
#-- La cuenta actual se saca por la consola y por el display de 7 segmentos derecho
#-- (Para poder verlo, poner la velocidad de simulación a 30 instr/sec)
#--
#-- ENTRADAS:
#-- a0: Numero hasta el que contar
#-- SALIDAS:
#-- Ninguna
#---------------------------------------------------------------------------------------
.globl contar_up
.include "servicios.asm"
.text
contar_up:
#-- Crear la pila
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Si la cuenta es hasta 1
li t0, 1
bne a0, t0, contar_rec
#-- Terminar. No mas llamadas recursivas
b fin
contar_rec:
#-- Guardar a0 en la pila
sw a0, 0(sp)
#-- Contar hasta n-1
addi a0, a0, -1
jal contar_up
#-- Recuperar a0 de la pila
lw a0, 0(sp)
fin:
#-- Imprimir numero actual
#-- En la consola
li a7, PRINT_INT
ecall
#-- Sacar número actual en el display de 7 segmentos derecho
jal print_display
#-- Recuperar la direccion de retorno
lw ra, 12(sp)
#-- Liberar la pila
addi sp, sp, 16
ret
- Programa principal:
#--- Programa principal
#-- Contador recursivo
#-- Mostrar una cuenta desde 9 hasta 1 en la consola y en el display de 7 segmentos
.include "servicios.asm"
.text
#-- Poner el contador a 9
li a0, 9
jal print_display
#-- Contar desde 9
li a0, 9
jal contar_down
#-- Terminar
li a7, EXIT
ecall
- Función contar_down():
#---------------------------------------------------------------------------------------
#-- Subrutina contar_down(n)
#-- Contar desde n hasta 1, usando un algoritmo recursivo
#-- La cuenta actual se saca por la consola y por el display de 7 segmentos derecho
#-- (Para poder verlo, poner la velocidad de simulación a 30 instr/sec)
#--
#-- ENTRADAS:
#-- a0: Numero desde el que contar
#-- SALIDAS:
#-- Ninguna
#---------------------------------------------------------------------------------------
#----------------------
#def contar_down(n):
# print(n)
# print_display(n)
# if n > 1:
# contar_down (n-1)
#
.globl contar_down
.include "servicios.asm"
.text
contar_down:
#-- Crear la pila
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Imprimir numero actual
#-- En la consola
li a7, PRINT_INT
ecall
#-- Guardar n en la pila
sw a0, 0(sp)
#-- Sacar número actual en el display de 7 segmentos derecho
jal print_display
#-- Recuperar n de la pila
lw a0, 0(sp)
#-- Si la cuenta es desde 1
li t0, 1
bne a0, t0, contar_rec
#-- Terminar. No mas llamadas recursivas
b fin
contar_rec:
#-- Contar desde n-1
addi a0, a0, -1
jal contar_down
fin:
#-- Recuperar la direccion de retorno
lw ra, 12(sp)
#-- Liberar la pila
addi sp, sp, 16
ret
- Katia Leal Algara
- Juan González-Gómez (Obijuan)
L1: Práctica 1-1. RARs
L2: Práctica 1-2. Ensamblador
L3: Práctica 1-3. Variables
L4: Pract 2-1. E/S mapeada
L5: Práctica 2-2: Inst. ecall
L6: Prác 2-3: Cadenas
L7: Práct 3-1: Bucles y saltos
L8: Práct 3-2: Cadenas II
L9: Pract 4-1: Subrut. Nivel-1
L10: Pract 4-2: La pila
L11: Pract 4-3: Recursividad
L12: Pract 5-1. Heap. Listas
Simulacro examen 1
GISAM. Ordinario. 2019-Dic-11
GISAM. Extra. 2020-Jul-03
GISAM. Ordinario. 2021-Ene-21
GISAM. Ordinario. 2022-Ene-10
GISAM. Extra. 2022-Jun-29
GISAM. Parcial 1. 2022-Oct-26
GISAM. Parcial 2. 2022-Nov-30
GISAM. Parcial 3. 2022-Dic-21
GISAM. Parcial 1. 2023-Oct-09
GISAM. Parcial 2. 2023-Nov-11
GISAM. Parcial 3. 2023-Dic-20
GISAM. Extra. 2024-Jun-17
GISAM. Parcial 1. 2024-Oct-14
GISAM. Parcial 2. 2024-Nov-13
GISAM. Parcial 3. 2024-Dic-16
TELECO. Ordinario. 2019-Dic-13
TELECO. Extra. 2020-Jul-07
TELECO. Ordinario. 2021-Ene-21
TELECO. Extra. 2021-Jul-02
TELECO. Ordinario. 2022-Ene-10
TELECO. Extra. 2022-Jun-29
TELECO. Ordinario. 2023-Ene-10
TELECO. Extra. 2023-Jun-29
TELECO. Parcial 1. 2023-Oct-20
TELECO. Parcial 2. 2023-Nov-17
TELECO. Parcial 3. 2023-Dic-22
TELECO. Extra. 2024-Jun-17
TELECO. Parcial 1. 2024-Oct-10
TELECO. Parcial 2. 2024-Nov-21
TELECO. Parcial 3. 2024-Dic-19
Robótica. Ordinario. 2020-Jun-1
Robótica. Extra. 2020-Jul-13
Robótica. Ordinario. 2021-Mayo-20
Robótica. Extra. 2021-Junio-16
Robótica. Parcial 1. 2022-Feb-25
Robótica. Parcial 2. 2022-Abril-1
Robótica. Parcial 3. 2022-Mayo-6
Robótica. Parcial 1. 2023-Feb-27
Robótica. Parcial 2. 2023-Mar-27
Robótica. Parcial 3. 2023-May-08
Robótica. Parcial 1. 2024-Feb-26
Robótica. Parcial 2. 2024-Mar-20
Robótica. Parcial 3. 2024-May-06
Robótica. Extra. 2024-Junio-24
Datos. Parcial 1. 2023-Oct-09
Datos. Parcial 2. 2023-Nov-15
Datos. Parcial 3. 2023-Dic-20
Datos. Parcial 1. 2024-Oct-09
Datos. Parcial 2. 2024-Nov-13
Datos. Parcial 3. 2025-Ene-17
Práctica 1: Sesiones 1,2 y 3
Práctica 2: Sesiones 4, 5 y 6
Práctica 3: Sesiones 7 y 8
Práctica 4: Sesiones 9, 10 y 11
Práctica 5: Sesión 12