Skip to content

L17: Practica 6‐5

Juan Gonzalez-Gomez edited this page Feb 24, 2026 · 4 revisions

L17: Biblioteca stdio: Salida a alto nivel

  • Objetivos de la sesión:
    • 🚧 TODO 🚧

Contenido

🚧 TODO 🚧

Introducción

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

Depuración

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

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

Encendido de varios LEDs: Números en binario

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

Depuración mediante consola

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

Función puts(): Imprimiendo cadenas

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

Ejemplo 1: Hola mundo

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
	EXIT

Para 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!

Fichero stdio.h: Macros

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_macro

Ejemplo 2: Imprimiendo cadenas con macros

En 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
	EXIT

Este es el resultado:

Imprimiendo en memoria

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

Dígitos BCD

🚧 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

Página principal


Sesiones de Prácticas

P1: Simulador RARs

L1: El simulador RARs
L2: Calculando expresiones
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

SESIONES ADICIONALES

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

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

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

Clone this wiki locally