-
Notifications
You must be signed in to change notification settings - Fork 20
L14: Practica 6‐2
-
Objetivos de la sesión:
- Entender el concepto de macro
- Aprender a hacer macros con el RARs
- Realizar llamadas al sistema mediante macros
- Introducción
- Macros sin parámetros
- Macros con parámetros
- Macros y etiquetas
- La macro PRINT
- Macros dentro de macros
- Macros y funciones
- Acceso al sistema operativo mediante macros: so.h
- Conclusiones
- Ejercicios para practicar
- Profesorado
- Licencia
- Enlaces
La programación en ensamblador es tediosa. Para hacer cosas simples, como por ejemplo llamar a una función a la que se le pasan parámetros, es necesario utilizar varias instrucciones. Lo mismo ocurre cuando invocamos los servicios del sistema operativo: hay que utilizar múltiples instrucciones. Además, la mayoría de las veces estos mecanismos son repetitivos, y siempre se utilizan de la misma manera
Para simplificar estas tareas repetitivas, y hacer más fácil la programación en ensamblador, aparece el concepto de macros, que también se utiliza en los lenguajes de alto nivel, como C o Rust por ejemplo
Las macros NO están definidas en la ISA del RISCV, y por tanto es algo que dependen del ensamblador utilizado. Cada uno tiene su propia sintáxis, aunque las ideas son comunes para todos ellos. En esta sesión aprenderemos a crear y utilizar macros en el simulador RARs
Las macros son una extensión de las constantes. Funcionan de manera similar, pero son más potentes. Cuando el ensamblador lee el identificador de una macro previamente difinida, la sustituye por el código en ella indicada. Es como si hiciese un "copy & paste". Con esto crea un nuevo programa en ensamblador, que es el que se convertirá en código máquina en una segunda pasada
En esta sesión aprenderemos a utilizar mácros a partir de ejemplos, empezando por las más sencillas: las que NO tienen parámetros. Luego veremos las que tienen parámetros, y cómo con ellas podemos hacer cosas más útiles. Finalmente crearemos una biblioteca de macros en el fichero so.h para acceder a los servicios del sistema operativo
Comenzamos con las macros más sencillas: las que NO tienen parámetros. La vamos a introducir progresivamente, a partir de un programa hola mundo que conocemos muy bien
Vamos a comenzar con una versión del programa Hola mundo, que imprime una cadena en la consola y termina. Este programa ya lo conocemos y tenemos totalmente controlado
.include "servicios.s"
.data
msg: .string "Esto es una prueba"
.text
#-- Imprimir mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- Terminar
li a7, EXIT
ecallEsto es lo que obtenemos al ejecutarlo:

El programa tiene 6 instrucciones:

Vamos a fijarnos en las dos instrucciones para TERMINAR:
li a7, EXIT
ecallSe trata de una llamada al sistema operativo, que ya conocemos de sobra. Siempre, en cada programa que queremos finalizar, tenemos que escribir estas dos instrucciones para devolver el control al sistema operativo. Y SIEMPRE SON LAS MISMAS DOS INSTRUCCIONES
Es posible crear una especie de CONSTANTE que represente a estas dos instrucciones. Cuando el programa ensamblador lee esta "CONSTANTE", la sustituye por las dos instrucciones. Esta consante especial se denomina MACRO
Vamos a definir la macro M_EXIT que incluye esas dos instrucciones. Lo hacemos con las directivas .macro y .end_macro
.macro M_EXIT
li a7, EXIT
ecall
.end_macroUna vez definida, cada vez que el ensamblador ve el identificador M_EXIT, lo sustituye por esas dos instrucciones
Así, el programa hola mundo lo podemos reescribir de esta manera:
#-- Ejemplo hola mundo
.include "servicios.s"
#-- Definicion de la macro M_EXIT
.macro M_EXIT
li a7, EXIT
ecall
.end_macro
.data
msg: .string "Esto es una prueba"
.text
#-- Imprimir mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- Terminar
M_EXITEl programa funciona igual y tiene exactamente las mismas 6 instrucciones. Al ensamblarlo vemos esto:

Las dos últimas instrucciones son las que el ensamblador ha añadido a partir de la macro M_EXIT. Es lo que se denomina la expansión de la macro
Observa que aparecen los números <7> y <8> delante de estas instrucciones. Eso indica que esas instrucciones están en realidad dentro de una macro, cuya definición está en las líneas 7 y 8
El nombre asignado a una macro puede ser cualquiera. El mismo que podemos dar a cualquier etiqueta. Sin embargo, igual que sucede con las CONSTANTES, típicamente se usan nombres EN MAYÚSCULAS
En esta sesión, además, las macros relaciondas con el sistema operaitivo llevan el pefijo M_ seguido por el nombre del servicio (que es el que usamos con las constantes del sistema oeprativo). Así, para acceder al servicio exit, sabemos que la constante EXIT contiene el código de servicio y M_EXIT es el nombre de la macro para acceder a este servicio
Para utilizar las macros hacemos lo mismo que con las constantes: las ponemos en un archivo separado que incluimos en el programa donde las vamos a usar. La macro M_EXIT está relacionada con el sistema operativo, por lo que lo colocamos en el mismo fichero donde tenemos las constantes del sistema operativo: servicios.s
-
Fichero:
servicios.s
#-- Código de los servicios del sistema operativo
.eqv PRINT_INT 1
.eqv READ_INT 5
.eqv PRINT_STRING 4
.eqv READ_STRING 8
.eqv PRINT_CHAR 11
.eqv READ_CHAR 12
.eqv EXIT 10
.eqv PRINT_INT_HEX 34
.eqv PRINT_INT_BINARY 35
.eqv SLEEP 32
#--- Macros para acceder a los servicios
#--- del sistema operativo
#-- Servicio para terminar
.macro M_EXIT
li a7, EXIT
ecall
.end_macroA partir de ahora, para acceder al servicio EXIT del sistema operativo hay que incluir el fichero servicios.s y escribir M_EXIT. Así es como queda el programa principal
.include "servicios.s"
.data
msg: .string "Esto es una prueba"
.text
#-- Imprimir mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- Terminar
M_EXITEl proceso de sustituir el nombre de la macro por sus instrucciones equivalentes se llama expansión de la macro. Esta es una operación que realiza el ensamblador. A partir del programa original el ensamblador genera otro programa donde ya NO hay macros, sino código como si lo hubiesemos tecleado nosotros
La forma de crear macros NO es parte de la ISA DEL RISCV. Esto significa que no está estandarizado y por tanto cada ensamblador define su propia sintáxis para implementar macros
No todos los ensambladores tienen la capacidad de incluir macros. Los que sí la incluyen se denominan macro-ensambladores
En este ejemplo se pide al usuario que apriete la tecla ENTER dos veces, y se imprimen dos mensajes diferentes. Para ello se llama al servicio READ_CHAR del sistema operativo
.include "servicios.s"
.data
msg1: .string "Pulsa ENTER"
msg2: .string "Pulsa ENTER otra vez"
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Esperar a que se apriete ENTER
li a7, READ_CHAR
ecall
#-- Imprimir mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Esperar a que se apriete ENTER
li a7, READ_CHAR
ecall
#-- Terminar
M_EXITEnsamblamos el programa. Tiene un total de 14 instrucciones

Este es el resultado de la ejecución:

Cada vez que llamamos al servicio READ_CHAR para leer un carácter hay que utilizar siempre estas dos instrucciones:
li a7, READ_CHAR
ecallEstán pidiendo a gritos que las metamos en la macro M_READ_CHAR. Esta es su definición:
.macro M_READ_CHAR
li a7, READ_CHAR
ecall
.end_macro Ahora desde el programa principal bastará con escribir M_READ_CHAR cada vez que queremos invocar el servicio READ_CHAR
Lo incluimos en el fichero servicios.s:
#-- Código de los servicios del sistema operativo
.eqv PRINT_INT 1
.eqv READ_INT 5
.eqv PRINT_STRING 4
.eqv READ_STRING 8
.eqv PRINT_CHAR 11
.eqv READ_CHAR 12
.eqv EXIT 10
.eqv PRINT_INT_HEX 34
.eqv PRINT_INT_BINARY 35
.eqv SLEEP 32
#--- Macros para acceder a los servicios
#--- del sistema operativo
#-- Servicio para terminar
.macro M_EXIT
li a7, EXIT
ecall
.end_macro
#-- Servicio para leer caracter
.macro M_READ_CHAR
li a7, READ_CHAR
ecall
.end_macro Así es como queda el programa principal, llamando a la nueva macro M_READ_CHAR:
.include "servicios.s"
.data
msg1: .string "Pulsa ENTER"
msg2: .string "Pulsa ENTER otra vez"
.text
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Esperar a que se apriete ENTER
M_READ_CHAR
#-- Imprimir mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Esperar a que se apriete ENTER
M_READ_CHAR
#-- Terminar
M_EXITEl programa es exactamente el mismo que antes. Tiene las mismas 14 instrucciones

En el ejemplo 2 hay 3 macros. Lo primero que hace el ensamblador es expandir estas macros, para obtener el programa completo a ensamblar como se muestra en esta figura
Las macros también pueden tener parámetros. Esto las hace mucho más útiles. Por ejemplo, las podemos utilizar para los servicios del sistema operativo PRINT_INT y PRINT_CHAR. En ellas hay que colocar en a0 el número o carácter a imprimir. La macro lo hace por nosotros
En este programa se imprime en la consola una fecha, en formado año/mes/dia. Para ello llamamos a los servicios PRINT_INT y PRINT_CHAR
#-- Imprimir una fecha
.include "servicios.s"
.text
#-- Imprimir año
li a0, 2025
li a7, PRINT_INT
ecall
#-- Imprimir '/'
li a0, '/'
li a7, PRINT_CHAR
ecall
#-- Imprimir mes
li a0, 12
li a7, PRINT_INT
ecall
#-- Imprimir '/'
li a0, '/'
li a7, PRINT_CHAR
ecall
#-- Imprimir dia
li a0, 21
li a7, PRINT_INT
ecall
#-- Imprimir salto de linea
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Terminar
M_EXITLa fecha que se imprime es 2025/12/21. Esta es la salida en la consola:

El programa ensamblado tiene 20 instrucciones:

Para la impresión de cada uno de los números de la fecha utilizamos 3 instrucciones. Todas son iguales salvo la primera, que tiene asigna al resigtro a0 el número a imprimir. Tiene esta sintáxis: li a0, <num>, donde <num> es el número a guardar en a0, que es diferente en cada caso. Primero 2025, luego 12 y por último 21
Ese número a guardar en a0 es nuestro parámetro
Para denotar parámetros en una macro utilizamos el prefijo %. En esta macro tenemos un parámetro que representa a un número constante, que llamaremos %num. La macro la definimos así:
.macro M_PRINT_INT(%num)
li a0, %num
li a7, PRINT_INT
ecall
.end_macroSi ahora, por ejemplo, queremos imprimir el número 2025, basta con escribir M_PRINT_INT(2025) y el ensamblador automáticamente lo expande a este código:
li a0, 2025
li a7, PRINT_INT
ecall¡Nos ahorramos escribir esas 3 líneas!. La impresión de un número entero es ahora mucho más fácil, y se parece más a cómo lo hacemos en un lenguaje de alto nivel
Igual que hicimos con las macros anteriores, la metemos en el fichero servicios.s para usarla con facilidad en nuestros programas
#-- Código de los servicios del sistema operativo
.eqv PRINT_INT 1
.eqv READ_INT 5
.eqv PRINT_STRING 4
.eqv READ_STRING 8
.eqv PRINT_CHAR 11
.eqv READ_CHAR 12
.eqv EXIT 10
.eqv PRINT_INT_HEX 34
.eqv PRINT_INT_BINARY 35
.eqv SLEEP 32
#--- Macros para acceder a los servicios
#--- del sistema operativo
#-- Servicio para terminar
.macro M_EXIT
li a7, EXIT
ecall
.end_macro
#-- Servicio para leer caracter
.macro M_READ_CHAR
li a7, READ_CHAR
ecall
.end_macro
#-- Servicio para imprimir un numero entero
#-- Parametro: %num: Numero a imprimir
#-- NOTA: %num tiene que ser un NUMERO CONSTANTE
.macro M_PRINT_INT(%num)
li a0, %num
li a7, PRINT_INT
ecall
.end_macroCon esta nueva macro el ejemplo 3 es más corto, y también más legible
#-- Imprimir una fecha
.include "servicios.s"
.text
#-- Imprimir año
M_PRINT_INT(2025)
#-- Imprimir '/'
li a0, '/'
li a7, PRINT_CHAR
ecall
#-- Imprimir mes
M_PRINT_INT(12)
#-- Imprimir '/'
li a0, '/'
li a7, PRINT_CHAR
ecall
#-- Imprimir dia
M_PRINT_INT(21)
#-- Imprimir salto de linea
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Terminar
M_EXITLa salida en consola es exactamente la misma, y el programa sigue teniendo las mismas 20 instrucciones:

Sin embargo hemos ganado en legibilidad. El código es más sencillo de escribir y de entender, sin incrementar su tamaño. ¡Todo son ganancias!
Hay que tener cuidado al usar las macros, especialmente si las ha creado otra perosna y no las tenemos controladas. La macro M_PRINT_INT() sirve para imprimir un número entero, pero al verla como una macro podemos estar tentados a pasar cualquier argumento
Por ejemplo, nos podría parecer intuitivo utilizarla para imprimir el número enterior que está almacenado en el registro t0:
.include "servicios.s"
.text
#-- En t0 tenemos el entero a imprimir
li t0, 2025
#-- Imprimir el entero de t0!
M_PRINT_INT(t0)
#-- Terminar
M_EXITSin embargo, al utilizarla de esta manera aparece un mensaje de error: `"t0": operand is of incorrect type´

El RARs nos lleva al fichero donde está definida la macro y nos señala la línea del error. Pero lo que vemos ahí en principio no es incorrecto: li a0, %num. De hecho la macro es correcto. El problema está causado en cómo hemos usado la macro
El ensamblador al expandir la macro NO COMPRUEBA EL TIPO DE PARÁMETRO. Lo que introduzcamos como parámetro lo mete directamente en la instrucción li a0, %num. Como hemos introducido t0, la expansión produce la instrucción li a0, t0, que es INCORRECTA
El menesaje de error lo genera el ensamblador al intentar ensamblar la instrucción li a0, t0
De igual forma, si usamos la macro M_PRINT_INT sin pasarle ningún parámetro, se produce el mensaje de error: forward reference or invalid parameters for macro "M_PRINT_INT"
Por ejemplo, tenemos este programa incorrecto:
.include "servicios.s"
.text
#-- llamar a la macro sin parametros
M_PRINT_INT()
#-- Terminar
M_EXITAl ensamblarlo obtenemos este mensaje de error:

El ejemplo de la impresión de la fecha todavía lo podemos simplificar más. Estamos llamando 3 veces al servicio PRINT_CHAR para imprimir dos caracteres / y un salto de línea \n. El servicio de impresión de un carácter también lo podemos convertir a una macro. En este caso el parámetro es ahora un carácter, por lo que lo llamamos %car. Así queda la macro definida:
.macro M_PRINT_CHAR(%car)
li a0, %car
li a7, PRINT_CHAR
ecall
.end_macroLa metemos en el archivo servicios.s, que ahora queda así:
#-- Código de los servicios del sistema operativo
.eqv PRINT_INT 1
.eqv READ_INT 5
.eqv PRINT_STRING 4
.eqv READ_STRING 8
.eqv PRINT_CHAR 11
.eqv READ_CHAR 12
.eqv EXIT 10
.eqv PRINT_INT_HEX 34
.eqv PRINT_INT_BINARY 35
.eqv SLEEP 32
#--- Macros para acceder a los servicios
#--- del sistema operativo
#-- Servicio para terminar
.macro M_EXIT
li a7, EXIT
ecall
.end_macro
#-- Servicio para leer caracter
.macro M_READ_CHAR
li a7, READ_CHAR
ecall
.end_macro
#-- Servicio para imprimir un numero entero
#-- Parametro: %num: Numero a imprimir
#-- NOTA: %num tiene que ser un NUMERO CONSTANTE
.macro M_PRINT_INT(%num)
li a0, %num
li a7, PRINT_INT
ecall
.end_macro
#-- Servicio para imprimir un caracter
#-- Parametro: %car: Un carácter constante
.macro M_PRINT_CHAR(%car)
li a0, %car
li a7, PRINT_CHAR
ecall
.end_macroAsí obtenemos la versión final del programa para imprimir la fecha. Queda mucho más reducido y compacto
#-- Imprimir una fecha
.include "servicios.s"
.text
#-- Imprimir año
M_PRINT_INT(2025)
M_PRINT_CHAR('/')
#-- Imprimir mes
M_PRINT_INT(12)
M_PRINT_CHAR('/')
#-- Imprimir dia
M_PRINT_INT(21)
M_PRINT_CHAR('\n')
#-- Terminar
M_EXIT¡Este programa es casi casi de alto nivel!
Sigue teniendo exactamente las mismas instrucciones que antes: 20

En esta figura se muestra el programa fuente original y en la derecha el programa generado por el ensamblador al expandir las macros
Se comprueba gráficamente cómo el programa original es más corto y compacto que el final. Pero generan ambos generan el mismo código máquina. Por el hecho de meter las macros el programa no aumenta en tamaño. También tarda los mismos ciclos en ejecutarse, porque tiene las mismas instrucciones
El programa de la fecha todavía se puede hacer aún más legible y más fácil de mantener. Para imprimir los números enteros podemos usar constantes. Estas constantes se las pasamos a la macro como parámetros. Es decir, que podemos usar CONSTANTES como parámetros de las macros
En el ejemplo definimos las constantes ANNO, MES y DIA, y se las pasamos a la macro M_PRINT_INT
.include "servicios.s"
#-- Constantes con los datos de la fecha
.eqv ANNO 2025
.eqv MES 12
.eqv DIA 21
.text
#-- Imprimir la fecha. Formato AÑO/MES/DIA
M_PRINT_INT(ANNO)
M_PRINT_CHAR('/')
M_PRINT_INT(MES)
M_PRINT_CHAR('/')
M_PRINT_INT(DIA)
M_PRINT_CHAR('\n')
#-- Terminar
M_EXITEl resultado es el mismo, y por supuesto el programa ocupa las mismas instrucciones: 20
En este ejemplo se simula una barra de progreso de 5 caracteres, que se actualiza cada 400ms:
#-- Barra de progreso
.include "servicios.s"
.text
#-- Contador del progreso
li t0, 5
next:
#-- Comprobar si ha terminado el progreso
beq t0, zero, fin
#-- Progreso no terminado
#-- Imprimir siguiente caracter
M_PRINT_CHAR('*')
#-- Decrementar progreso
addi t0, t0, -1
#-- Pausa
li a0, 400
li a7, SLEEP
ecall
#-- repetir
j next
fin:
#-- Terminar
M_EXITEste es el resultado:

La pausa se realiza llamando al servicio SLEEP del sistema operativo, y se le pasa como parámetro el tiempo en milisegundos. Esta llamada es la que vamos a convertir en una macro, pasándole como parámetro la espera en milisegundos
Para realizar la pausa en milisegundos se utiliza el servicio SLEEP del sistema operativo. Este servicio tiene un único parámetro: el tiempo en milisegundo. Por ello la macro es similar a las dos anteriores: M_PRINT_INT y M_PRINT_CHAR, pero ahora se usa el parámetro %ms
.macro M_SLEEP(%ms)
li a0, %ms
li a7, SLEEP
ecall
.end_macroEsta macro la incorporamos en el fichero servicios.s:
#-- Código de los servicios del sistema operativo
.eqv PRINT_INT 1
.eqv READ_INT 5
.eqv PRINT_STRING 4
.eqv READ_STRING 8
.eqv PRINT_CHAR 11
.eqv READ_CHAR 12
.eqv EXIT 10
.eqv PRINT_INT_HEX 34
.eqv PRINT_INT_BINARY 35
.eqv SLEEP 32
#--- Macros para acceder a los servicios
#--- del sistema operativo
#-- Servicio para terminar
.macro M_EXIT
li a7, EXIT
ecall
.end_macro
#-- Servicio para leer caracter
.macro M_READ_CHAR
li a7, READ_CHAR
ecall
.end_macro
#-- Servicio para imprimir un numero entero
#-- Parametro: %num: Numero a imprimir
#-- NOTA: %num tiene que ser un NUMERO CONSTANTE
.macro M_PRINT_INT(%num)
li a0, %num
li a7, PRINT_INT
ecall
.end_macro
#-- Servicio para imprimir un caracter
#-- Parametro: %car: Un carácter constante
.macro M_PRINT_CHAR(%car)
li a0, %car
li a7, PRINT_CHAR
ecall
.end_macro
#-- Servicio para realizar una espera
#-- Parametro: %ms: Tiempo en milisegundos
.macro M_SLEEP(%ms)
li a0, %ms
li a7, SLEEP
ecall
.end_macroAsí queda el ejemplo de la barra de progreso usando esta nueva macro:
#-- Barra de progreso
.include "servicios.s"
.text
#-- Contador del progreso
li t0, 5
next:
#-- Comprobar si ha terminado el progreso
beq t0, zero, fin
#-- Progreso no terminado
#-- Imprimir siguiente caracter
M_PRINT_CHAR('*')
#-- Decrementar progreso
addi t0, t0, -1
#-- Pausa
M_SLEEP(400)
#-- repetir
j next
fin:
#-- Terminar
M_EXITLa impresión de mensajes en la consola se utiliza muchísimo. Por ello tiene todo el sentido usar también macros. En este ejemplo se imprimen 3 mensajes cada segundo:
.include "servicios.s"
.data
msg1: .string "Mensaje 1...\n"
msg2: .string "Mensaje 2...\n"
msg3: .string "Fin"
.text
#-- Imprimir primer mensaje
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Esperar un segundo
M_SLEEP(1000)
#-- Imprimir segundo mensaje
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Esperar un segundo
M_SLEEP(1000)
#-- Imprimir tercer mensaje
la a0, msg3
li a7, PRINT_STRING
ecall
#-- Terminar
M_EXITEl funcionamiento del programa es el siguiente:

El programa tiene 20 instrucciones, y en el segmento de datos se han definido 3 etiquetas

Definimos la siguiente macro para el acceso al servicio PRINT_STRING:
.macro M_PRINT_STRING(%cad)
la a0, %cad
li a7, PRINT_STRING
ecall
.end_macroY lo metemos en el fichero servicios.s:
#-- Código de los servicios del sistema operativo
.eqv PRINT_INT 1
.eqv READ_INT 5
.eqv PRINT_STRING 4
.eqv READ_STRING 8
.eqv PRINT_CHAR 11
.eqv READ_CHAR 12
.eqv EXIT 10
.eqv SLEEP 32
.eqv PRINT_INT_HEX 34
.eqv PRINT_INT_BINARY 35
#--- Macros para acceder a los servicios
#--- del sistema operativo
#-- Servicio para terminar
.macro M_EXIT
li a7, EXIT
ecall
.end_macro
#-- Servicio para leer caracter
.macro M_READ_CHAR
li a7, READ_CHAR
ecall
.end_macro
#-- Servicio para imprimir un numero entero
#-- Parametro: %num: Numero a imprimir
#-- NOTA: %num tiene que ser un NUMERO CONSTANTE
.macro M_PRINT_INT(%num)
li a0, %num
li a7, PRINT_INT
ecall
.end_macro
#-- Servicio para imprimir un caracter
#-- Parametro: %car: Un carácter constante
.macro M_PRINT_CHAR(%car)
li a0, %car
li a7, PRINT_CHAR
ecall
.end_macro
#-- Servicio para realizar una espera
#-- Parametro: %ms: Tiempo en milisegundos
.macro M_SLEEP(%ms)
li a0, %ms
li a7, SLEEP
ecall
.end_macro
#-- Servicio para imprimir una cadena
#-- Parametro: %cad: Puntero a la cadena
.macro M_PRINT_STRING(%cad)
la a0, %cad
li a7, PRINT_STRING
ecall
.end_macroEl ejemplo lo reescribimos ahora de esta manera
.include "servicios.s"
.data
msg1: .string "Mensaje 1...\n"
msg2: .string "Mensaje 2...\n"
msg3: .string "Fin"
.text
#-- Imprimir primer mensaje
M_PRINT_STRING(msg1)
#-- Esperar un segundo
M_SLEEP(1000)
#-- Imprimir segundo mensaje
M_PRINT_STRING(msg2)
#-- Esperar un segundo
M_SLEEP(1000)
#-- Imprimir tercer mensaje
M_PRINT_STRING(msg3)
#-- Terminar
M_EXITCon esta macro es más fácil realizar las impresiones de cadenas. Sin embargo, todavía se puede hacer aún más fácil, como veremos en las siguientes secciones
Dentro de las macros también podemos definir etiquetas. Sabemos que las etiquetas son ÚNICAS. No se pueden definir dos etiquetas con el mismo nombre. Esto generaría un error de ensamblado
Por ello, las etiquetas definidas dentro de la macro son especiales. Se definen de la misma manera: con un texto terminado con el carćter :. PERO cuando se expande la macro se añade el sufijo _M<i> a la etiqueta, donde <i> es el número de la macro, comenzando por 0.
Cada vez que se expande una macro, se le asigna un nombre empezando en 0. Así, la primera macro es la M0, la segunda la M1, la tercer M2, etc...
En este programa de ejemplo se define la macro TEST_LABEL que contiene la etiqueta label delante de la instrucción nop. Se colocan 3 macros para ver lo que ocurre
.include "servicios.s"
.macro TEST_LABEL
label:
nop
.end_macro
.text
TEST_LABEL #-- Macro 0 (M0)
TEST_LABEL #-- Macro 1 (M1)
TEST_LABEL #-- Macro 2 (M2)
#-- Terminar
M_EXIT Si ahora ensamblamos y miramos la tabla de símbolos, comprobamos que se han definido 3 etiquetas diferentes, cuyos nombres son label_M0, label_M1 y label_M3. Se definen en ese orden. Las tres están en el segmento de código

Esas etiquetas definidas en la macro, son etiquetas normales. Es decir, que desde fuera de la macro las podríamos usar normalmente. Esto es algo que normalmente no se hace, pero es totalmente viable.
Este es el programa modificado en el que se hace un salto a la etiqueta label_M0 para comenzar a ejecutar la primera instrucción nop
.include "servicios.s"
.macro TEST_LABEL
label:
nop
.end_macro
.text
#-- Saltar a la etiqueta label de la
#-- primera macro
j label_M0
TEST_LABEL #-- Macro 0 (M0)
TEST_LABEL #-- Macro 1 (M1)
TEST_LABEL #-- Macro 2 (M2)
#-- Terminar
M_EXITEste programa ensambla correctamente y se ejecuta sin problemas
En esta figura se muestra el programa fuente original y el resultado de la expasión de las macros:
Las macros también sirven para colocar información en el segmento de datos. Partimos de este programa de ejemplo en el que se definen 2 vectores en el segmento de datos. Cada vector está compuesto por 3 palabras, correspondientes a sus coordenadas x,y,z. Desde el programa principal simplemente leemos estas componentes y las metemos en registros:
#-- Constantes para acceder a las coordenadas
#-- x,y,z de los vectores
.eqv X 0
.eqv Y 4
.eqv Z 8
.data
vec0: .word 1,2,3 #-- Primer vector
vec1: .word 4,5,6 #-- Segundo vector
.text
#-- Leer componentes x,y,z del primer vector
la s0, vec0
lw t0, X(s0)
lw t1, Y(s0)
lw t2, Z(s0)
#-- Leer componentes del segundo vector
la s0, vec1
lw t3, X(s0)
lw t4, Y(s0)
lw t5, Z(s0)
#-- Terminar
M_EXITLo ensamblamos y lo ejecutamos. Observamos que los vectores están en el segmento de datos y que al ejecutarse sus componentes se han leido en los vectores t0, t1, t2, y t3, t4 y t5 respectivamente

Los vectores los podemos incluir en el segmento de datos de dos formas diferntes, que veremos en los siguientes apartados
Las macros se pueden colocar también en el segmento de datos, y puede utilizar las directivas del ensamblador. Para colocar un vector en el segmento de datos definimos una macro como esta:
.macro VEC(%x,%y,%z)
vec: .word %x, %y, %z
.end_macroTiene 3 parámetros: %x, %y y %z. Esta macro sitúa estos 3 números enteros en el segmento de datos, usando la directiva .word. Además, le asigna la etiqueta base vec, que luego en la expansión de la macro se convierte en vec_M0 y vec_M1
Así queda nuestro programa. Observa que la macro VEC() se ha puesto DENTRO DEL SEGMENTO DE DATOS
.include "servicios.s"
#-- Constantes para acceder a las coordenadas
#-- x,y,z de los vectores
.eqv X 0
.eqv Y 4
.eqv Z 8
#-- Insertar un vector en el segmento de datos
.macro VEC(%x,%y,%z)
vec: .word %x, %y, %z
.end_macro
.data
VEC(1,2,3) #-- Primer vector
VEC(4,5,6) #-- Segundo vector
.text
#-- Leer componentes x,y,z del primer vector
la s0, vec_M0
lw t0, X(s0)
lw t1, Y(s0)
lw t2, Z(s0)
#-- Leer componentes del segundo vector
la s0, vec_M1
lw t3, X(s0)
lw t4, Y(s0)
lw t5, Z(s0)
#-- Terminar
M_EXITEl resultado al ensamblarlo y ejecutarlo es el mismo que el programa anterior, PERO las etiquetas que se generan en la expansión de las macros son vec_M0 y vec_M1. En el programa original usábamos vec0 y vec1

Sin embargo, el programa se puede simplificar aún más si incluimos las macros directamente en el segmento de código. Dentro de la macro ahora especificamos que lo que viene a continuación se debe situar en el segmento de datos, por lo que se coloca la directiva .data. Tras la colocación del vector se coloca la directiva .text para que las instrucciones que están tras la macro se coloquen en el segmento de código
Así queda la nueva macro:
.macro VEC(%x,%y,%z)
.data
vec: .word %x, %y, %z
.text
.end_macroY este es el nuevo programa:
.include "servicios.s"
#-- Constantes para acceder a las coordenadas
#-- x,y,z de los vectores
.eqv X 0
.eqv Y 4
.eqv Z 8
#-- Insertar un vector en el segmento de datos
.macro VEC(%x,%y,%z)
.data
vec: .word %x, %y, %z
.text
.end_macro
.text
#-- Crear los vectores (en el segmento de datos)
VEC(1,2,3) #-- Primer vector
VEC(4,5,6) #-- Segundo vector
#-- Leer componentes x,y,z del primer vector
la s0, vec_M0
lw t0, X(s0)
lw t1, Y(s0)
lw t2, Z(s0)
#-- Leer componentes del segundo vector
la s0, vec_M1
lw t3, X(s0)
lw t4, Y(s0)
lw t5, Z(s0)
#-- Terminar
M_EXITEste programa es exactamente igual al anterior. Tiene las mismas instrucciones y se almacenan en el segmento de datos los mismos vectores. La ejecución produce los mismos resultados
La diferencia está en que este programa es más parecido a los de alto nivel. Dentro del propio código se definen dos vectores con las macros, y el ensamblador los sitúa directamente en el segmento de datos
En la siguiente sección veremos un ejemplo extremadamente útil de esta idea de colocar datos desde el segmento de código: una macro para imprimir cadenas constantes
La gran ventaja de las macros es que nos permite simplificar mucho la programación en ensamblador. Y en muchos casos logramos que se parezca bastante a la programación en alto nivel
¿Recuerdas el programa hola mundo que vimos en la primera sesión del laboratorio? En su momento era un programa críptico y complicado de entender. Este programa lo fuimos desgranando poco a poco, en las sucesivas sesiones, para comprender todos los conceptos implicados
Ahora, gracias a las macros, lo podemos simplificar muchísimo más. Este es el resultado:
#-- Programa "Hola mundo"
#-- Super simplificado gracias a las MACROS
.include "servicios.s"
.text
PRINT("Hola mundo!")
#-- Terminar
M_EXITAl ensamblarlo y ejecutarlo, esto es lo que obtenemos

La magia de la nueva macro PRINT está en que sitúa automáticamente la cadena "Hola Mundo!" en el segmento de datos y llama al servicio PRINT_STRING pasando la dirección de esa cadena. Todo esto se hace de forma transparente. Así es como está definida la macro:
.macro PRINT(%str)
.data
msg: .string %str
.text
la a0, msg
li a7, PRINT_STRING
ecall
.end_macro La incluimos en el fichero servicios.s, que queda así:
#-- Código de los servicios del sistema operativo
.eqv PRINT_INT 1
.eqv READ_INT 5
.eqv PRINT_STRING 4
.eqv READ_STRING 8
.eqv PRINT_CHAR 11
.eqv READ_CHAR 12
.eqv EXIT 10
.eqv SLEEP 32
.eqv PRINT_INT_HEX 34
.eqv PRINT_INT_BINARY 35
#--- Macros para acceder a los servicios
#--- del sistema operativo
#-- Servicio para terminar
.macro M_EXIT
li a7, EXIT
ecall
.end_macro
#-- Servicio para leer caracter
.macro M_READ_CHAR
li a7, READ_CHAR
ecall
.end_macro
#-- Servicio para imprimir un numero entero
#-- Parametro: %num: Numero a imprimir
#-- NOTA: %num tiene que ser un NUMERO CONSTANTE
.macro M_PRINT_INT(%num)
li a0, %num
li a7, PRINT_INT
ecall
.end_macro
#-- Servicio para imprimir un caracter
#-- Parametro: %car: Un carácter constante
.macro M_PRINT_CHAR(%car)
li a0, %car
li a7, PRINT_CHAR
ecall
.end_macro
#-- Servicio para realizar una espera
#-- Parametro: %ms: Tiempo en milisegundos
.macro M_SLEEP(%ms)
li a0, %ms
li a7, SLEEP
ecall
.end_macro
#-- Servicio para imprimir una cadena
#-- Parametro: %cad: Puntero a la cadena
.macro M_PRINT_STRING(%cad)
la a0, %cad
li a7, PRINT_STRING
ecall
.end_macro
#-- Servicio para imprimir una cadena
#-- Parametro: %str: Cadena constante (Ej. "Hola")
.macro PRINT(%str)
.data
msg: .string %str
.text
la a0, msg
li a7, PRINT_STRING
ecall
.end_macro La macro mete la cadena constante pasada como parámetro (%str) en el segmento de datos, y le asocia la etiqueta msg. Esta etiqueta sabemos que se expande como msg_M0. Pero dentro de la macro nos da igual. Referenciamos la cadena como msg
A continuación, en el segmento de datos, se generan las instrucciones necesarias para invocar el servicio PRINT_STRING del sistema operativo, pasando a través de a0 la dirección de la cadena, que está en la etiqueta msg
En esta figura se muestra la expansión de las macros y cómo este hola mundo super-simplificado se convierte en el hola mundo real
Cuando se define una macro se puede invocar a cualquiera de las macros que hayan sido creadas anteriormente. Esto nos permite reutilizar código
En este ejemplo se hace el cálculo de 5*4 y se guarda el resultado en a0. Para realizarlo se usa la macro MULTX4() que suma cuatro veces el registro a0 y el resultado lo devuelve en a0. PERO esta macro está, a su vez, construida mediante llamadas a la macro MULTX2() que multiplica por 2 el registro a0
Así es como queda el programa. Es importante el orden en el que se definen las macros. Primero hay que crear MULTX2() y luego MULTX4() que es la que usa `MULTX2():
.include "servicios.s"
#-- Multiplicar por dos un registro
.macro MULTX2(%reg)
add %reg, %reg, %reg
.end_macro
#-- Multiplicar por 4 un registro
.macro MULTX4(%reg)
MULTX2(%reg)
MULTX2(%reg)
.end_macro
.text
#-- Calcular 5 x 4
li a0, 5
MULTX4(a0)
#-- Terminar
M_EXITLo ensamblamos y comprobamos que el programa funciona. En el registro a0 tenemos el valor 20

La expansión de las macros se hace en 2 fases. En la primera se expande MULTX4() (y M_EXIT), y en la segunda MULTX2():
A primera vista las macros parecen similares a las funciones, ¡PERO NO LO SON!. De hecho, otro de los usos de las macros es simplificar la llamada a las funciones, como veremos en la siguiente sección
Lo vemos con un ejemplos. Definimos la función strcp(dst, src) que copia la cadena fuente en la destino. El programa principal copia la cadena "HOLI" en la cadena destino, y la imprime:
.include "servicios.s"
#-- Longitud maxima de la cadena destino
.eqv MAX 20
.data
src: .string "HOLI"
dst: .space 20
.text
#-- Copiar cadena
#-- strcp(dst, src)
la a0, dst
la a1, src
jal strcp
#-- Imprimir la cadena resultante
la a0, dst
li a7, PRINT_STRING
ecall
#-- Terminar
M_EXIT
#-------------------------------
#-- strcp(dst, src)
#-- Copiar cadena src en dst
#--
#-- ENTRADAS:
#-- a0: Direccion cadena destino
#-- a1: Direccion cadena fuente
#-------------------------------
strcp:
next:
#-- Leer caracter fuente
lb t1, 0(a1)
#-- ¿Fin cadena?
beq t1, zero, fin
#-- Copiar caracter en destino
sb t1, 0(a0)
#-- Incrementar los punteros de las cadenas
addi a0, a0, 1
addi a1, a1, 1
#-- Repetir
j next
fin:
#-- Copiar un zero en la cadena destino
sb zero, 0(a0)
retEste es el resultado al ejecutarlo:

Siempre que queramos copiar una cadena a partir de su etiqueta hay que realizar una llamada a la funcion strcp(dst, src), que al escribirlo en ensamblador tiene esta pinta:
la a0, dst
la a1, src
jal strcpPodemos definir la macro STRCP() para sustituir esas tres instrucciones por esto:
STRCP(dst, src)que es mucho más legible y cercano al alto nivel
Así es como queda la macro definida:
.macro STRCP(%dst, %src)
la a0, %dst
la a1, %src
jal strcp
.end_macroY así es como queda ahora el programa principal. Mucho más claro:
.text
#-- Copiar cadena
STRCP(dst, src)
#-- Imprimir la cadena resultante
la a0, dst
li a7, PRINT_STRING
ecall
#-- Terminar
M_EXITEste es el ejemplo completo para probarlo directamente. Es exactamente igual que el original: tiene las mismas instrucciones y tarda el mismo tiempo:
.include "servicios.s"
#-- Longitud maxima de la cadena destino
.eqv MAX 20
.macro STRCP(%dst, %src)
la a0, %dst
la a1, %src
jal strcp
.end_macro
.data
src: .string "HOLI"
dst: .space 20
.text
#-- Copiar cadena
STRCP(dst, src)
#-- Imprimir la cadena resultante
la a0, dst
li a7, PRINT_STRING
ecall
#-- Terminar
M_EXIT
#-------------------------------
#-- strcp(dst, src)
#-- Copiar cadena src en dst
#--
#-- ENTRADAS:
#-- a0: Direccion cadena destino
#-- a1: Direccion cadena fuente
#-------------------------------
strcp:
next:
#-- Leer caracter fuente
lb t1, 0(a1)
#-- ¿Fin cadena?
beq t1, zero, fin
#-- Copiar caracter en destino
sb t1, 0(a0)
#-- Incrementar los punteros de las cadenas
addi a0, a0, 1
addi a1, a1, 1
#-- Repetir
j next
fin:
#-- Copiar un zero en la cadena destino
sb zero, 0(a0)
retLas macros y funciones parecen cosas similares, pero NO lo son. La principal diferencia es que las macros se expanden. Es como si cada vez que aparece la macro se hace un copy & paste del código. Por ello, si definimos una macro muy grande, cada vez que la usamos se incluyen muchas instrucciones. El programa puede crecer mucho en tamaño sin darnos cuenta
Las funciones son el elemento clave para abordar la complejidad de los proyectos. Nos permiten dividir algo complejo en trocitos más pequeños. Además, el código se reutiliza y es más fácil de mantener
Las macros nos permite escribir menos líneas y ahorrar tiempo de desarrollo, pero nunca hay que usarlas como sustituto de las funciones. En la sección anterior hemos visto un ejemplo muy claro de cómo una macro nos facilita la llamada a una función
Las macros típicamente se usan para compactar 2 ó 3 líneas de ensamblador. Si tienen más instrucciones entonces tal vez no sea una buena macro
Para aprender el uso de las macros hemos definidos macros que nos permiten llamar a los servicios del sistema operativo. Hemos partido del fichero servicios.s que contiene los códigos de los servicios en constantes y lo hemos ampliado con macros. Las diferenciamos mediante el prefijo M_
En esta sección vamos a definir el acceso al sistema operativo desde 0, utilizando constantes y macros. Estas nuevas definiciones están disponibles en el fichero so.h. Así podremos utilizar en nuestros programas el antiguo servicios.s o el nuevo so.h según nos convenga
También se cambia la extensión del fichero de .s a .h. De esta forma podemos diferenciar los ficheros que contienen ensamblador de los que sólo tienen constantes y macros. Es la misma nomenclatura utilizada que en el lenguaje C: Se utiliza .h para especificar los archivos que se pueden incluir, y que contiene definiciones
El acceso al sistema operativo lo haremos principalmente con macros, por lo que utilizaremos los nombres de los servicios para referirnos a ellas. Así, EXIT se refiere ahora a la macro que invoca el servicio exit, y NO al código de ese servicio
Los códigos de los servicios los definimos en constantes que comienzan con el prefijo COD_
Así es cómo quedan definidos los servicios que hemos usado hasta ahora:
# ===============================================
# Codigo de los servicios del sistema operativo
# ===============================================
.eqv COD_PRINT_INT 1
.eqv COD_READ_INT 5
.eqv COD_PRINT_STRING 4
.eqv COD_READ_STRING 8
.eqv COD_PRINT_CHAR 11
.eqv COD_READ_CHAR 12
.eqv COD_EXIT 10
.eqv COD_SLEEP 32
.eqv COD_PRINT_INT_HEX 34
.eqv COD_PRINT_INT_BINARY 35Para acceder a cualquier servicio del sistema operativo siempre se utilizan estas dos instrucciones:
li a7, <codigo>
ecallPara cada servicio se define una macro con el nombre del servicio. El resto de parámetro se deben introducir por los registros correspondientes indicados en la documentación del sistema operativo. A estas macros las llamamos macros de acceso directo. Son las siguientes:
# -------------------------------------------
# - Macros de acceso DIRECTO a los servicios
# -------------------------------------------
.macro EXIT
li a7, COD_EXIT
ecall
.end_macro
.macro PRINT_INT
li a7, COD_PRINT_INT
ecall
.end_macro
.macro PRINT_CHAR
li a7, COD_PRINT_CHAR
ecall
.end_macro
.macro PRINT_STRING
li a7, COD_PRINT_STRING
ecall
.end_macro
.macro PRINT_INT_HEX
li a7, COD_PRINT_INT_HEX
ecall
.end_macro
.macro PRINT_INT_BIN
li a7, COD_PRINT_INT_BIN
ecall
.end_macro
.macro READ_INT
li a7, COD_READ_INT
ecall
.end_macro
.macro READ_STRING
li a7, COD_READ_STRING
ecall
.end_macro
.macro READ_CHAR
li a7, COD_READ_CHAR
ecall
.end_macro
.macro SLEEP
li a7, COD_SLEEP
ecall
.end_macroEn este programa se usan algunos servicios como ejemplo. Lo importante es ver que con estas nuevas macros definidas podemos acceder a ellos exactamente igual que como lo hacíamos directamente con los códigos
.include "so.h"
.data
msg: .string "Mensaje de prueba\n"
.text
#-- Imprimir cadena
la a0, msg
PRINT_STRING
#-- Imprimir numero entero
li a0, 200
PRINT_INT
#-- Imprimir caracter
li a0, '\n'
PRINT_CHAR
#-- Imprimir numero hexa
li a0, 0xFF
PRINT_INT_HEX
#-- Terminar
EXITEl resultado es el esperado:

Para hacer más sencillo todavía el acceso a los servicios de sistema operativo vamos a crear nuevas macros que usan las macros de acceso directo. En determinados servicios que hay que pasar un parámetro, vamos a diferencias si lo que se pasa es un dato inmediato, un dato a través de registro o una referencia a través de una etiqueta. Utilizaremos los sufijos I, R y L respectivamente
Servicio de impresión de un carácter
| Macro | Parámetro | Descripcion |
|---|---|---|
| PRINT_CHAR | a0: Caracter a imprimir | Acceso inmediato |
| PRINT_CHARI(car) | car: Caracter inmediato | Ej: PRINT_CHAR('A') |
| PRINT_CHARR(reg) | reg: Registro | Ej: PRINT_CHAR(t0) |
La diferencia entre PRINT_CHARI y PRINT_CHARR es la instrucción usada para depositar el valor en el registro a0. En la primera macro es li y en la segunda mv
Estas son las definiciones de las macros:
.macro PRINT_CHARI(%car)
li a0, %car
PRINT_CHAR
.end_macro
.macro PRINT_CHARR(%reg)
mv a0, %reg
PRINT_CHAR
.end_macro- Programa de ejemplo:
.include "so.h"
.text
#-- Acceso directo
li a0, 'H'
PRINT_CHAR
#-- Imprimir caracter inmediato
PRINT_CHARI('\n')
#-- Imprimir Caracter en registro
li t0, 'I'
PRINT_CHARR(t0)
#-- Terminar
EXIT- Resultado:

Estas son todas las macros para acceder al servicio de impresión de números enteros
| Macro | Parámetro | Descripcion |
|---|---|---|
| PRINT_INT | a0: Numero a imprimir | Acceso inmediato |
| PRINT_INTI(num) | num: Numero inmediato | Ej: PRINT_INTI(20) |
| PRINT_INTR(reg) | reg: Registro | Ej: PRINT_INTR(t0) |
La diferencia entre PRINT_INTI y PRINT_INTR es la instrucción usada para depositar el valor en el registro a0. En la primera macro es li y en la segunda mv
Estas son las definiciones de las macros:
#-- Impresión de un entero INMEDIATO (constante)
.macro PRINT_INTI(%cte)
li a0, %cte
PRINT_INT
.end_macro
#-- Impresion de un entero en un registro
.macro PRINT_INTR(%reg)
mv a0, %reg
PRINT_INT
.end_macroEste es un un programa de ejemplo para hacer las pruebas
.include "so.h"
.text
#-- Acceso directo
li a0, 1
PRINT_INT
#-- Imprimir entero inmediato
PRINT_INTI(2)
#-- Imprimir entero en registro
li t0, 3
PRINT_INTR(t0)
#-- Terminar
EXIT- Resultado:

Impresión de un número entero en hexadecimal
| Macro | Parámetro | Descripcion |
|---|---|---|
| PRINT_INT_HEX | a0: Numero a imprimir | Acceso inmediato |
| PRINT_INT_HEXI(num) | num: Numero inmediato | Ej: PRINT_INT_HEX(20) |
| PRINT_INT_HEXR(reg) | reg: Registro | Ej: PRINT_INT_HEX(t0) |
- Definiciones de las macros:
#-- Impresión de un entero INMEDIATO (constante)
.macro PRINT_INT_HEXI(%cte)
li a0, %cte
PRINT_INT_HEX
.end_macro
#-- Impresion de un entero en un registro
.macro PRINT_INT_HEXR(%reg)
mv a0, %reg
PRINT_INT_HEX
.end_macro- Programa de ejemplo:
.include "so.h"
.text
#-- Acceso directo
li a0, 0xFF
PRINT_INT_HEX
PRINT_CHARI('\n')
#-- Imprimir entero inmediato
PRINT_INT_HEXI(1)
PRINT_CHARI('\n')
#-- Imprimir entero en registro
li t1, 0x03
PRINT_INT_HEXR(t1)
#-- Terminar
EXIT- Resultado:

Impresión de un número entero en binario
| Macro | Parámetro | Descripcion |
|---|---|---|
| PRINT_INT_BIN | a0: Numero a imprimir | Acceso inmediato |
| PRINT_INT_BINI(num) | num: Numero inmediato | Ej: PRINT_INT_BIN(20) |
| PRINT_INT_BINR(reg) | reg: Registro | Ej: PRINT_INT_BIN(t0) |
- Definiciones de las macros:
#-- Impresión de un entero INMEDIATO (constante)
.macro PRINT_INT_BINI(%cte)
li a0, %cte
PRINT_INT_BIN
.end_macro
#-- Impresion de un entero en un registro
.macro PRINT_INT_BINR(%reg)
mv a0, %reg
PRINT_INT_BIN
.end_macro- Programa de ejemplo:
.include "so.h"
.text
#-- Acceso directo
li a0, 0xFF
PRINT_INT_BIN
PRINT_CHARI('\n')
#-- Imprimir entero inmediato
PRINT_INT_BINI(1)
PRINT_CHARI('\n')
#-- Imprimir entero en registro
li t1, 0x03
PRINT_INT_BINR(t1)
#-- Terminar
EXIT- Resultado:

Impresión de una cadena
| Macro | Parámetro | Descripcion |
|---|---|---|
| PRINT_STRING | a0: Dirección de la cadena | Acceso inmediato |
| PRINT_STRINGI(str) | str: Cadena inmediata | Ej: PRINT_STRINGI("Hola") |
| PRINT_STRINGL(label) | label: Etiqueta con cadena | Ej: PRINT_STRINGL(msg1) |
| PRINT_STRINGR(reg) | reg: Registro con la dirección | Ej: PRINT_STRINGR(t0) |
- Definiciones de las macros:
.macro PRINT_STRINGI(%str)
.data
msg: .string %str
.text
la a0, msg
PRINT_STRING
.end_macro
.macro PRINT_STRINGL(%label)
la a0, %label
PRINT_STRING
.end_macro
.macro PRINT_STRINGR(%reg)
mv a0, %reg
PRINT_STRING
.end_macro- Programa de ejemplo:
.include "so.h"
.data
msg1: .string "MSG1\n"
msg3: .string "MSG3\n"
msg4: .string "MSG4\n"
.text
#-- Acceso directo
la a0, msg1
PRINT_STRING
#-- Imprimir cadena constante
PRINT_STRINGI("MSG2\n")
#-- Imprimir cadena dada por etiqueta
PRINT_STRINGL(msg3)
#-- Imprimir cadena dada por registro
la t2, msg4
PRINT_STRINGR(t2)
#-- Terminar
EXIT- Resultado:

Lectura de un número entero introducido por el usuario. NO HAY MACROS específicas
| Macro | Parámetro | Descripcion |
|---|---|---|
| READ_INT | Acceso inmediato |
- Programa de ejemplo:
.include "so.h"
.text
#-- Acceso directo
PRINT_STRINGI("Numero? ")
READ_INT
PRINT_INT
#-- Terminar
EXIT- Resultado:

Lectura de un carácter introducido por el usuario. NO HAY MACROS específicas
| Macro | Parámetro | Descripcion |
|---|---|---|
| READ_CHAR | Acceso inmediato |
- Definiciones de las macros:
.macro READ_CHAR
li a7, COD_READ_CHAR
ecall
.end_macro- Programa de ejemplo:
.include "so.h"
.text
#-- Acceso directo
PRINT_STRINGI("Caracter? ")
READ_CHAR
#-- Imprimir caracter
mv t0, a0
PRINT_CHARI('\n')
PRINT_CHARR(t0)
#-- Terminar
EXIT- Resultado:

Leer una cadena introducida por el usuario
| Macro | Parámetro | Descripcion |
|---|---|---|
| READ_STRING | a0: Dirección de la cadena. a1: Tamaño máximo cadena | Acceso inmediato |
| READ_STRINGL(label,max) | label: Etiqueta para cadena. max: Tamaño máximo cadena | Ej: READ_STRING(cad, 40) |
| READ_STRINGR(reg,max) | reg: Direccion cadena. max: Tamaño máximo cadena | Ej: READ_STRING(t0, 50) |
- Definiciones de las macros:
.macro READ_STRINGL(%label, %max)
la a0, %label
li a1, %max
READ_STRING
.end_macro
.macro READ_STRINGR(%reg, %max)
mv a0, %reg
li a1, %max
READ_STRING
.end_macro- Programa de ejemplo:
.include "so.h"
.eqv MAX 20
.data
cad: .space MAX
.text
#-- Acceso directo
PRINT_STRINGI("Cadena 1? ")
la a0, cad
li a1, MAX
READ_STRING
PRINT_STRING
#-- Cadena dada por etiqueta
PRINT_STRINGI("Cadena 2? ")
READ_STRINGL(cad, MAX)
PRINT_STRING
#-- Cadena nada en registro
PRINT_STRINGI("Cadena 3? ")
la t3, cad
READ_STRINGR(t3, MAX)
PRINT_STRING
#-- Terminar
EXIT- Resultado:

Realizar una pausa en milisegundos
| Macro | Parámetro | Descripcion |
|---|---|---|
| SLEEP | a0: Tiempo en milisegundos | Acceso inmediato |
| SLEEPI(delay) | delay: Tiempo en milisegundos | Ej: SLEEPI(1000) |
| SLEEPR(reg) | reg: Registro con el tiempo en milisegundos | Ej: SLEEPR(t0) |
- Definiciones de las macros:
.macro SLEEPI(%cte)
li a0, %cte
SLEEP
.end_macro
.macro SLEEPR(%reg)
mv a0, %reg
SLEEP
.end_macro- Programa de ejemplo:
.include "so.h"
.text
#-- Acceso directo
PRINT_CHARI('*')
li a0, 1000
SLEEP
#-- Pausa con numero inmediato
PRINT_CHARI('*')
SLEEPI(1000)
#-- Pausa tiempo en registro
PRINT_CHARI('*')
li t4, 1000
SLEEPR(t4)
#-- Terminar
EXIT- Resultado:

# ===============================================
# Codigo de los servicios del sistema operativo
# ===============================================
.eqv COD_PRINT_INT 1
.eqv COD_READ_INT 5
.eqv COD_PRINT_STRING 4
.eqv COD_READ_STRING 8
.eqv COD_PRINT_CHAR 11
.eqv COD_READ_CHAR 12
.eqv COD_EXIT 10
.eqv COD_SLEEP 32
.eqv COD_PRINT_INT_HEX 34
.eqv COD_PRINT_INT_BIN 35
# -------------------------------------------
# - Macros de acceso DIRECTO a los servicios
# -------------------------------------------
.macro EXIT
li a7, COD_EXIT
ecall
.end_macro
.macro PRINT_INT
li a7, COD_PRINT_INT
ecall
.end_macro
.macro PRINT_CHAR
li a7, COD_PRINT_CHAR
ecall
.end_macro
.macro PRINT_STRING
li a7, COD_PRINT_STRING
ecall
.end_macro
.macro PRINT_INT_HEX
li a7, COD_PRINT_INT_HEX
ecall
.end_macro
.macro PRINT_INT_BIN
li a7, COD_PRINT_INT_BIN
ecall
.end_macro
.macro READ_INT
li a7, COD_READ_INT
ecall
.end_macro
.macro READ_STRING
li a7, COD_READ_STRING
ecall
.end_macro
.macro READ_CHAR
li a7, COD_READ_CHAR
ecall
.end_macro
.macro SLEEP
li a7, COD_SLEEP
ecall
.end_macro
# -------------------------------------------
# -- Macros ESPECIFICAS de los servicios
# -------------------------------------------
#-- Impresión de un entero INMEDIATO (constante)
.macro PRINT_INTI(%cte)
li a0, %cte
PRINT_INT
.end_macro
#-- Impresion de un entero en un registro
.macro PRINT_INTR(%reg)
mv a0, %reg
PRINT_INT
.end_macro
#-------------- PRINT_STRING
.macro PRINT_STRINGI(%str)
.data
msg: .string %str
.text
la a0, msg
PRINT_STRING
.end_macro
.macro PRINT_STRINGL(%label)
la a0, %label
PRINT_STRING
.end_macro
.macro PRINT_STRINGR(%reg)
mv a0, %reg
PRINT_STRING
.end_macro
#---------------- READ_STRING
.macro READ_STRINGL(%label, %max)
la a0, %label
li a1, %max
READ_STRING
.end_macro
.macro READ_STRINGR(%reg, %max)
mv a0, %reg
li a1, %max
READ_STRING
.end_macro
#------------------ PRINT_CHAR
.macro PRINT_CHARI(%car)
li a0, %car
PRINT_CHAR
.end_macro
.macro PRINT_CHARR(%reg)
mv a0, %reg
PRINT_CHAR
.end_macro
.macro SLEEPI(%cte)
li a0, %cte
SLEEP
.end_macro
.macro SLEEPR(%reg)
mv a0, %reg
SLEEP
.end_macro
#------------------- PRINT_INT_HEX
#-- Impresión de un entero INMEDIATO (constante)
.macro PRINT_INT_HEXI(%cte)
li a0, %cte
PRINT_INT_HEX
.end_macro
#-- Impresion de un entero en un registro
.macro PRINT_INT_HEXR(%reg)
mv a0, %reg
PRINT_INT_HEX
.end_macro
#------------------- PRINT_INT_BINARY
#-- Impresión de un entero INMEDIATO (constante)
.macro PRINT_INT_BINI(%cte)
li a0, %cte
PRINT_INT_BIN
.end_macro
#-- Impresion de un entero en un registro
.macro PRINT_INT_BINR(%reg)
mv a0, %reg
PRINT_INT_BIN
.end_macroGracias a las macros, nuestros programas en ensamblador son ahora un poco más claros y concisos. Y también más legibles. Cada vez que haya patrones de código que se repiten, utilizamos una macro, como por ejemplo las llamadas a funciones a las que hay que pasar parámetros o la invocación de servicios del sistema operativo
A partir de ahora, en las sessiones siguientes, siempre utilizaremos el fichero so.h para acceder al sistema operativo, donde se definen macros directas y específicas para utilizar los servicios más frecuentes
🚧 TODO 🚧
- Juan González-Gómez (Obijuan)
- Miguel Ángel de Miguel Paraíso
- Javier Macias del Campo
- Juan Ignacio Pérez
- Katia Leal Algara
- TODAS las instrucciones de RV32I
- Simulador RARs: Simulador de RISC-V utilizado en este curso
L1: El simulador RARs
L2: Calculando expresiones
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
L13: RV32I: Resto de instrucciones
L14: Macros
L15: Entrada/Salida a bajo nivel
L16: Multiplicaciones/divisiones. Unit Tests 🚧
L17: Biblioteca stdio: salida a alto nivel 🚧
Los registros de estado y control (CSR)
Interrupciones y excepciones
Borrador
Soluciones a los ejercicios planteados en cada sesión para practicar:
Sesión L1
Sesión L2
Sesión L3
Sesión L4
Sesión L5
Sesión L6
Sesión L7
Sesión L8
Sesión L9
Sesión L10
Sesión L11
Sesión L12
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
GISAM. Extra. 2025-Jun-17
GISAM. Parcial 1. 2025-Oct-20
GISAM. Parcial 2. 2025-Nov-19
GISAM. Parcial 3. 2026-Enero-9
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
TELECO. Extra. 2025-Jun-17
TELECO. Parcial 1. 2025-Oct-10
TELECO. Parcial 2. 2025-Nov-13
TELECO. Parcial 3. 2026-Ene-21
TELECO. Extra. 2026-Jun-25
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
Robótica. Parcial 1. 2025-Feb-25
Robótica. Parcial 2. 2025-Mar-31
Robótica. Parcial 3. 2025-May-19
Robótica. Parcial 1. 2026-Feb-23
Robótica. Parcial 2. 2026-Abr-13
Robótica. Parcial 3. 2026-May-11
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
Datos. Parcial 1. 2025-Oct-15
Datos. Parcial 2. 2025-Nov-19