Repository navigation
L17: Practica 6‐5
-
Objetivos de la sesión:
- 🚧 TODO 🚧
🚧 TODO 🚧
En la sesión anterior vimos cómo funciona la entrada/salida a bajo nivel, a través de una UART, y cómo al final nos proporciona dos funciones de interfaz: putchar() y getchar() para realizar la entrada/salida de un carácter. Los caracteres son la base de la comunicación hombre-máquina
También vimos cómo implementar la función puts(), de alto nivel, para imprimir una cadena de caracteres en la pantalla. Esta función la situamos en una biblioteca denominada stdio.s
En esta sesión ampliaremos la biblioteca stdio.s para imprimir números en diferentes bases: unario, binario, octal, decimal y hexadecimal. Todas estas funciones son de alto nivel y en última instancia tienen que llamar a putchar(). Nos centraremos únicamente en la salida
Nuestra misión es implementar desde cero todo el software para convertir un computador RISCV en una herramienta útil. Cuando trabajos en sistemas reales, todos las bibliotecas de entrada/salidas nos las proporciona el fabricante. Nuestro interés es crearlas nosotros mismos, desde cero, para comprenderlas perfectamente
Supongamos que estamos programando un sistema desde cero, basado en el RISCV, por ejemplo una raspberry pi pico 2. Una de las primeras cosas que hay que implementar es la comunicación mínima con el exterior para indicar que estamos vivos. Necesitamos una confirmación de que nuestro software se está ejecutando, y que, por tanto, el hardware está funcionando. Esto típicamente se realiza mediante el encendido de un LED
Esta es la forma más básica de depuración. Una vez que somos capaces de encender un LED, ya podemos saber qué parte de nuestro código se ha ejecutado, y cuáles no. También podemos hacer que parpadee a diferentes frecuencas para indicas diferentes situaciones. Para encender un LED ya sabemos que basta con conocer la dirección del puerto se salida y escribir un valor en ella
Sin embargo, con un LED nuestra capacidad de depuración es todavía muy limitada. Si queremos saber el valor de un registro, o de una variable, necesitamos varios LEDs. Típicamente los LEDs se conectan a un puerto de salida de 8 bits, por lo que podemos conocer el valor de un byte en binario
Utilizar 8 LEDs es todavía muy limitado. Sólo podemos observar valores de 8 bits en binario. Tanto los registros como las direcciones son de 32-bits, por lo que se queda corto. Podríamos conectar 32 LEDs. Sin embargo en la mayoría de sistemas empotrados no hay suficientes pines libres para esto
La siguiente opción de depuración es utilizar la consola a través de la UART. Si somos capaces de transmistir un carácter (un byte), entonces ya podemos construir mensajes de texto, lo que nos ayudará mucho con la tarea de depuración
Las consolas son de caracteres. Nos permiten mostrar caracteres fácilmente... sin embargo, ¿Cómo podemos mostrar números? ¿Cómo podemos mostrar en la consola el valor de un registro?. Esto NO es trivial. Internamente, la información está almacenada en binario. Necesitamos convertir estos números binarios en caracteres. Así por ejemplo, supongamos que una determinada variable de memoria tiene almacenado el valor 200. En realidad se trata de 1 byte, que está en binario 11001000
Para mostrárselo al usuario, hay que convertir este byte en sus diferentes dígitos. Esto depende de cómo lo queramos representar en la consola. Si se hace, por ejemplo, en el sistema decinal, necesitaremos 3 dígitos, es decir, 3 caracteres: El 2, el 0 y el 0. Si lo queremos mostrar en binario, nececitaremos 8 caracteres
En esta sesión veremos cómo se implementan las funciones para lograr esta conversión de binario a cadena
Comenzamos la implementación de stdio.s por donde lo habíamos dejado. La primera función (que ya está implementada) es puts() que permite imprimir una cadena. Para la impresión de los caracteres se llama a la función de bajo nivel putchar(). De las dos implementaciones que teníamos en la sesión anterior, vamos a usar la más cómoda: io_rars_os.s. Así veremos la salida en la consola integrada del RARs y NO hay que activar la herramienta Keyboard and Display MMIO Simulator
Los ejemplos que mostraremos utilizan 3 ficheros: Uno es el programa principal, que en este ejemplo se llama Ej01-hola-mundo.s. El otro es la propia biblioteca stdio.s, donde iremos colocando las funciones que vayamos desarrollando. Y el último es el módulo de entrada/salida de bajo nivel. En esta sesión utilizaremos io_rars_so.s: Es decir, que para imprimir un carácter se llama directamente al servicio del sistema operativo
Si bien vimos en la sesión anterior que para trabajar de manera multiplataforma hay que llamar primero a la función io_init() y al terminar a io_close(), en estos ejemplos lo vamos a omitir, ya que no es realmente necesario si estamos usando el sistema operativo del RARs. Y así hacemos el código más corto. Sin embargo, si lo queremos hacer multiplataforma sería necesario incluirlos
- Fichero: Ej01-hola-mundo.s
.include "so.h"
.data
msg: .string "Hola Mundo!\n"
.text
#-- Imprimir mensaje
la a0, msg
jal puts
#-- Terminar
EXITPara probarlo abrimos en 3 perstaños los ficheros mencionados anteriormente:

Lo ensamblamos y lo ejecutamos. Comprobamos que se obteiene el mensaje Hola Mundo! en la consola

¡Ya tenemos el entorno listo!
Para simplificar la programación y llamar de manera más sencillas a las funciones de la bilioteca stdio, vamos a crear macros en el fichero stdio.h. Utilizaremos la notación de usar el nombre de la función EN MAYÚSCULAS seguido del sufijo I, R ó L para indicar que el parámetro pasado es una Literal, Registro o Label (Etiqueta)
Para acceder a la función puts() definimos estas macros:
#------------------------ PUTS ---------------------------
#------------------------------------------------
#-- Imprimir cadena cuya direccion esta indicada
#-- en el registro proporcionado
#------------------------------------------------
.macro PUTSR(%reg)
mv a0, %reg
jal puts
.end_macro
#----------------------------------------
#-- Se pasa como parametro la etiqueta
#----------------------------------------
.macro PUTSL(%label)
la a0, %label
jal puts
.end_macro
#-----------------------------------------------
#-- Se pasa como paramtro una cadena LITERAL
#-----------------------------------------------
.macro PUTSI(%str)
.data
msg: .string %str
.text
la a0, msg
jal puts
.end_macroEn este ejemplo se imprimen 3 cadenas de texto utilizando las 3 macros asociadas a la función puts(): PUTSL, PUTSR y PUTSI
- Ficheros: Ej02-mensajes-macros.s
#-- Acceso a puts() mediante macros
.include "so.h"
.include "stdio.h"
.data
msg1: .string "Mensaje-1\n"
msg2: .string "Mensaje-2\n"
.text
#-- Imprimir mensaje pasando como parametro la etiqueta
PUTSL(msg1)
#-- Imprimir mensaje pasando la cadena por el registro t0
la t0, msg2
PUTSR(t0)
#-- Imprimir mensaje mediante cadena INMEDIATA
PUTSI("Mensaje-3\n")
#-- Terminar
EXITEste es el resultado:

Todas las funciones de stdio que vamos a implementar NO IMPRIMEN DIRECTAMENTE en la consola, sino que usaremos otro enfoque: imprimir en la memoria. Es decir, que las cadenas de caracteres que se tienen que generar, para representar los dígitos de los números, se almacenarán en un buffer de memoria. Luego, este buffer se imprime en la consola mediante puts(). Lo que hacemos es dividir el proceso de impresión en dos partes: una es la generación de la cadena de caracteres, y el segundo es su impresión en la consola
Por un lado, este enfoque es más flexible y permite que los programas puedan generar mensajes en los que se combinan textos con números. Basta con ir "imprimiendo en memoria" las distintas partes del mensajes (textos, números...) para obtener la cadena final. Esto simplifica mucho la creación de aplicaciones que usan la consola extensivamente (como por ejemplo el volcado de una zona de memoria)
PERO por otro este enfoque nos permite utilizar TESTS UNITARIOS para verificar que las funciones se comportan correctamente. Basta con chequear que las cadenas generadas en memoria son correctas. Si se imprimen directamente en la consola, no tenemos forma, desde el RARs, de comprobar si el texto sacado es válido o no
Las funciones que imprimen en memoria comienzan con el prefijo s de string, precisamente para indicar que su salida es una impresión en memoria en vez de en consola. Así, por ejemplo la función sputs() imprime una cadena en memoria, en vez de en la consola
🚧 TODO 🚧
- valor --> BCD --> cadena
- Tamaño del BCD es variable
- binario: 1
- octal: 3
- decimal: 4
- hexa: 4
- BCD_get_digit(value, pos, size): Obtener el digito BCD situado en la posicion pos. Los digitos bcd son de tamaño size
- BCD_get_digit(0x1F, 0, 4) = 0xF. Digito hexa de la posicion 0
- BCD_get_digit(0xCAFEBACA, 7, 4) = 0xC. Digito hexa de la posicion 7
- BCD_get_digit(0b10011001, 3, 1) = 1. Digito binario de la posicion 3
- BCD_get_digit(0b10011001, 4, 1) = 1. Digito binario de la posicion 4
- BCD_get_digit(0b10011001, 7, 1) = 1. Digito binario de la posicion 7
- BCD_get_digit(0o764, 2, 3) = 7. Digito octal de la posicion 2
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
GISAM. Parcial 1. 2026-Oct-21
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
TELECO. Parcial 1. 2026-Oct-09
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