Skip to content

Practica 4: Soluciones a los ejercicios

Juan Gonzalez-Gomez edited this page Nov 22, 2019 · 91 revisions

Práctica 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

Contenido

Práctica 4

Sesión 9

Solución a los ejercicios de la Sesión de laboratorio L9:4-1

Ejercicio 1

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

  1. 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
	#-----------------------------------------

Ejercicio 2

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

#--------------------------------------------------------

Ejercicio 3

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

#--------------------------------------------------------

Ejercicio 4

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

Ejercicio 5

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

Ejercicio 6

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

Ejercicio 7

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

Ejercicio 8

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

Ejercicio 9

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

Ejercicio 10

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

Sesión 10

Solución a los ejercicios de la Sesión de laboratorio L10:4-2

Ejercicio 1

Apartado 1

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
Apartado 2

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

Apartado 3

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

Apartado 4

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

Apartado 5

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

Apartado 6

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.

Ejercicio 2

Apartado 1

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

#....
Apartado 2

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 
#.....
Apartado 3

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
Apartado 4

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

Ejercicio 3

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

Ejercicio 4

  • 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, 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

Ejercicio 5

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

Ejercicio 6

  • 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

Ejercicio 7

  • 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

Ejercicio 8

  • 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

Ejercicio 9

  • 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

Ejercicio 10

  • 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

Sesión 11

Solución a los ejercicios de la Sesión de laboratorio L11:4-3

Ejercicio 1

  • 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

Ejercicio 2

  • 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

Ejercicio 3

  • 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

Ejercicio 4

  • 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

Ejercicio 5

  • 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

Ejercicio 6

  • 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

Ejercicio 7

  • 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

Ejercicio 8

  • 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

Ejercicio 9

  • 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

Ejercicio 10

  • 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

Autores

Licencia

Enlaces

Página principal


Sesiones de Prácticas

P1: Simulador RARs

L1: Práctica 1-1. RARs
L2: Práctica 1-2. Ensamblador
L3: Práctica 1-3. Variables

P2: E/S mapeada. Llamadas al sistema

L4: Pract 2-1. E/S mapeada
L5: Práctica 2-2: Inst. ecall
L6: Prác 2-3: Cadenas

P3: Bucles y Saltos condicionales

L7: Práct 3-1: Bucles y saltos
L8: Práct 3-2: Cadenas II

P4: Subrutinas

L9: Pract 4-1: Subrut. Nivel-1
L10: Pract 4-2: La pila
L11: Pract 4-3: Recursividad

P5: Memoria Dinámica

L12: Pract 5-1. Heap. Listas

VÍDEO DE DESPEDIDA

Ejercicios de examen

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

SOLUCIONES

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

Clone this wiki locally