-
Notifications
You must be signed in to change notification settings - Fork 21
L4: Practica 2

- Tiempo: 2h
-
Objetivos de la sesión:
- Aprender a utilizar constantes definidas mediante identificadores (Directiva .eqv)
- Aprender cómo funciona el sistema de Entrada/Salida mapeado en memoria
- Practicar con un display de 7 segmentos
- Fecha: 2019/Oct/20
Haz click en la imagen para ver el vídeo en Youtube
Haz click en la imagen para ver el vídeo en Youtube
Haz click en la imagen para ver el vídeo en Youtube
- Introducción
- Definiendo constantes: Directiva .eqv
- Periféricos mapeados en memoria
- Display de 7 segmentos
- Recopilación de instrucciones hasta el momento
- Actividades NO guiadas
- Autores
- Licencia
- Enlaces
Nuestros programas se dividen en código y datos, ambos almacenados en la memoria. Cada uno en su segmento: de código o de datos. Las instrucciones del programa se almacenan en el segmento de código, y las variables en el segmento de datos. Nos falta una parte muy importante. ¿Cómo se comunica el procesador con el exterior? Necesitamos una forma de realizar la Entrada/Salida de datos (E/S)
En nuestros programas tendremos que usar constantes numéricas. Por ejemplo para establecer el valor inicial de un contador, o el de determinadas variables. Estas constantes siempre tienen el mismo valor durante toda la ejecución del programa
Es una buena práctica de programación el definir las constantes al principio del programa, de forma que se puedan cambiar fácilmente, sin tener que navegar por el código
Para definir constantes usaremos la directiva .eqv. Este es un ejemplo de uso
.eqv INICIAL 5Se trata de una directiva, por lo que NO genera código máquina. Le dice al ensamblador que cada vez que vea el identificador INICIAL lo sustituya por el valor 5, en este caso
Modificaremos el programa del contador para que empiece su cuenta con un valor inicial, establecido mediante una constante. El programa es:
#-- Contador con valor inicial definido mediante
#-- un identificador
#-- Definir el valor inicial para el contador
.eqv INICIAL 20
.text
#-- Inicializar contador
#-- El ensamblador sustituye el identificador INICIAL
#-- por el numero 20
li x5, INICIAL
bucle:
#-- Incrementar contador: x5 = x5 + 1
addi x5, x5, 1
#-- Repetir
j bucle
Ensamblamos el programa, y nos fijamos en la ventana del segmento de código. Vemos que la instrucción de inicialización que aparece es li x5, 20

En el editor habíamos especificado li x5, INICIAL, pero el ensamblador ha sustituido el identificador INICIAL por su valor 20. Es el mismo comportamiento que ocurre con el #define del lenguaje C
La zona del mapa de memoria situada en la parte superior se denomina MMIO (Memory mapped I/O): Entrada/salida mapeada en memoria. En esas direcciones se encuentran todos los periféricos de nuestro sistema. La comunicación con ellos se realiza igual que un acceso a memoria: con la instrucción store se envía información a los periféricos y con la instrucción load se lee de ellos. Pero para el RISC-V es exactamente igual que cualquier otro acceso a memoria

El acceso a estas posiciones de memoria es igual que al resto de la memoria: necesitamos colocar en un registro la dirección en donde se encuentra el periférico. Usaremos las instrucciones de load y store para leer o escribir en el periférico respectivamente
Una manera muy utilizada para enviar información hacia el exterior es mediante los puertos de salida de 8 bits (aunque pueden ser de mayor cantidad de bits). Lo que se encuentra mapeado es un registro. Al hacer un store se escribe el valor en este registro, y sus bits salen al exterior a través de los pines

El puerto de salida del RISC-V que tenemos en la FPGA está en la dirección 0xFFFF0000, y los 8 LEDs están conectados a este puerto. De esta forma, al escribir un valor de 8 bits en esa dirección, aparece en binario en los LEDs. Cada bit a 1 se corresponde con un LED encendido, y cada bit a 0 con un LED apagado

Así, si escribimos el valor 0xAA (que en binario es 10101010) en la dirección 0xFFFF0000 se encenderán 4 leds, correspondientes a los cuatro unos del valor 0xAA
Vamos a hacer un programa para escribir nuestro primer dato en el puerto de salida. El valor que típicamente se utiliza es 0xAA por tener los bits a 1 y 0 alternados. En el simulador simplemente veremos este valor almacenado en "la memoria", pero en el sistema real comprobaremos que se encienden los LEDs
Para escribir en este puerto de salida necesitamos almacenar en un registro 0xFFFF0000, que es la dirección del puerto de salida. Luego cargamos 0xAA en otro registro y hacemos el store
#-- Ejemplo de escritura en un puerto de salida
#-- Sacar un valor binario por los LEDs
#--- Direccion donde está mapeado el Puerto de salida
#--- conectado a los LEDs
.eqv LEDS 0xFFFF0000
#-- Valor a sacar por el puerto de salida
#-- Se corresponde con el valor binario 10101010
.eqv VALOR 0xAA
.text
#-- Usamos el regitro x5 como puntero de acceso al puerto
#-- Cargamos en x5 la direccion de memoria del puerto de salida
li x5, LEDS
#-- Cargar el valor a sacar por los LEDs en el registro x6
li x6, VALOR
#-- Sacar el valor por el puerto, para que se iluminen los LEDs
sb x6, 0(x5)
#-- Ya no hacemos nada mas
#-- Terminamos con un bucle infinito (porque en el RISC-V de la FPGA
#-- no hay un Sistema operativo)
stop: j stopPara acceder al puerto de salida definimos el identificador LEDS, asociado a la dirección. Somos humanos, trabajamos mejor con letras que con números. También definimos otro identificador para el valor a enviar: VALOR
El programa funcionaría igual sin estos identificadores, sin embargo es una buena práctica de programación: hacen el programa más legible y más fácil de modificar
Fíjate en el final del programa. En vez de terminar como normalmente hacemos, invocando a las instrucciones "mágicas" de terminación, que veremos más adelante, usamos un bucle infinito que no hace nada
stop: j stopEstá hecho así para que el mismo código nos sirva tanto para probarlo en el simulador, como para probarlo en el sistema real. En este último NO tenemos un sistema operativo, y las instrucciones de terminación no tienen efecto. Por ello, para "detener" el procesador y que no siga ejecutando instrucciones lo metemos en este bucle infinito
Lo ensamblamos y lo simulamos paso a paso. Al ejecutar el store vemos que la ventana del segmento de datos cambia y aparece la zona de entrada/salida: MMIO. Comprobamos que efectivamente en la dirección 0xFFFF0000 se ha guardado el valor 0xAA. También comprobamos que el registro x5 contiene la dirección del puerto

En esta animación se muestra en funcionamiento

¡Hemos enviado nuestro primer dato hacia el exterior! En el simulador no es muy impactante... tan solo un valor escrito en la memoria. Vamos a probarlo en un sistema real 😃
Vamos a probar el programa anterior en nuestro sistema real. Una vez ensamblado exportamos el código máquina a un fichero binario: leds-on.bin

Nuestro programa tiene 5 instrucciones, por lo que ocupa 20 bytes. Es el tamaño de nuestro fichero leds-on.bin
Ahora lo grabamos en la memoria flash de la placa con el RISC-V. Usamos la herramienta Apio, que NO está instalada en el laboratorio. Lo dejo documentado aquí para que se vea el proceso. Desde un terminal ejecutamos este comando:
apio raw "iceprog -o 1M 01-leds-on.bin"
En la consola saldrá algo como esto:
$ apio raw "iceprog -o 1M 01-leds-on.bin"
init..
cdone: high
reset..
cdone: low
flash ID: 0xEF 0x40 0x16 0x00
file size: 20
erase 64kB sector at 0x100000..
programming..
reading..
VERIFY OK
cdone: high
Bye.
$
Se carga el programa en la flash... ¡y se ejecuta! ¡Nuestro primer programa que funciona en un RISC-V real!. Veremos cómo los LEDs se encienden, con el patrón indicado por el valor 0xAA
En este vídeo de youtube puedes ver la demo en acción. Pincha sobre la imagen:
A través del puerto de salida hemos controlado 8 LEDs que estaban colocados en línea recta, uno detrás de otro. Un display de 7 segmentos está formado por 8 LEDs colocados en forma de dígito. 7 de los leds se corresponden con los segmentos verticales y horizontales y el octavo es el punto. Esta es la pinta que tiene el componente

Se inventaron en la década de los 60s para mostrar números en las primeras calculadoras electrónicas. Con ellos "dibujamos" los dígitos del 0 al 9, usando luz. Según qué segmentos se iluminen (1) o cuáles estén apagados (0), se muestran unos dígitos u otros
Aunque actualmente tenemos modos más sofisticados de mostrar la información, los displays de 7 segmentos se siguen usando muchísimo en aparatos electrónicos, como electrodomésticos, despertadores, etc.

Pero sin duda, una de las aplicaciones más épicas de los 7-segmentos es en la interfaz de la computadora de guiado del Apolo 11, la nave que llevó al primer hombre que pisó la Luna

Los displays de 7 segmentos están compuestos por 8 LEDs independientes, 7 de ellos tienen una forma alargada para representar las líneas de los dígitos, y uno para representar el punto

Según los segmentos que se enciendan, se representa un dígito u otro

Pero al tratarse de segmentos individuales podemos representar otras cosas, aunque no sean los dígitos del 0 al 9, como por ejemplo la letra H, E... En total hay un total de 128 combinaciones (2 elevado a 7)
Para dibujar los dígitos necesitamos nombrar los diferentes segmentos, igual que hemos hecho en los ejemplos anteriores. La nomenclatura estándar es usar las letras a, b, c, d, e, f y g para los segmentos. El punto lo llamaremos p
Cada dígito lo controlaremos con un bit independiente. El bit 0 se corresponde con el segmento a, el bit 1 con el b,..., el bit 6 con el f y finalmente el bit 7 (de mayor peso) con el punto

En el simulador RARs tenemos acceso a dos displays de 7 segmentos, además de un teclado hexadecimal. Están accesibles desde la opción Tools/Digital Lab Sim

Al abrirlo nos aparecen en la izquierda los dos displays de 7 segmentos y en la derecha un teclado de 16 teclas

Los dos diplays, el derecho y el izquierdo, están mapeados en las direcciones 0xFFFF0010 y 0xFFFF0011 respectivamente. Para practicar, utilizaremos el display derecho. Vamos a encender todos sus segmentos. Para ello tenemos que poner a 1 todos sus bits. Basta con escribir el valor 0xFF en la dirección 0xFFFF0010
El programa es exactamente igual que el que hemos usado para sacar un valor por los LEDs. Pero ahora cambiamos la dirección del puerto de salida, y el valor a enviar
#-- Encender todos los segmentos del display de 7 segmentos
#-- (incluido el punto)
#-- Bastante con escribir el valor 0xFF en la dirección 0xFFFF0010
#--- Direccion donde está mapeado el display derecho
.eqv DISP_R 0xFFFF0010
#-- Valor a sacar por el puerto del display
#-- Para encender todos los segmentos debe ser 0xFF
.eqv VALOR 0xFF
.text
#-- Usamos el regitro x5 como puntero de acceso al puerto
#-- Cargamos en x5 la direccion de memoria del puerto
li x5, DISP_R
#-- Cargar el valor a sacar por el Display
li x6, VALOR
#-- Sacar el valor por el puerto, para que se iluminen
#-- los segmentos del display
sb x6, 0(x5)
#-- Ya no hacemos nada mas
#-- Terminamos con un bucle infinito (porque en el RISC-V de la FPGA
#-- no hay un Sistema operativo)
stop: j stopPara probarlo ensamblamos el programa y arrancamos la herramienta de los displays. Apretamos el botón que pone connect to program

Los displays están apagados, porque todavía NO hemos ejecutado el programa. Ponemos un Breakpoint en la última instrucción y le damos al play para ejecutar el programa. Todos los segmentos del display derecho se ponen en rojo (y también el punto) para indicar que están encendidos

Según el valor que escribamos en puerto del display, se encenderán unos segmentos u otros y aparecen los diferentes dígitos o caracteres
Nota: Por un fallo del simulador, si se modifica el estado de ambos displays simultáneamente, solo actualiza el estado del display de la derecha (0xFFFF0010).
El programa anterior lo vamos a probar en la placa real, donde hay tenemos conectado un display de 7 segmentos. Ensamblamos el programa y lo exportamos en formato binario. Lo grabamos en la flash con este comando:
iceprog -o 1M Disp-7seg-all.bin
Veremos cómo se han encendido todos los segmentos de nuestro display real, incluido el punto. Igual que en el simulador 😄

En este vídeo de youtube vemos el proceso completo:
Para aquellos que sabéis lenguaje C, o estáis estudiándolo, os puede interesar esta nota.
Para enviar un valor a un puerto de salida, donde hay por ejemplo unos LEDs, y suponiendo que están mapeado en la dirección 0xFFFF0000, ya hemos visto que usaríamos las siguientes instrucciones en ensamblador:
li x5, 0xFFFF0000 #-- Meter la direccion de mapeo de los leds en x5
li x6, 0xAA #-- Valor a enviar a los LEDs
sw x6, 0(x5) #-- Sacar el valor por los LEDs
stop: j stop #-- Bucle infinito¿Cómo lo podríamos hacer desde un programa en C?
Una manera directa sería esta:
void main()
{
//-- Sacar un valor por los LEDs
*(volatile uint32_t*)0xFFFF0000 = 0xAA;
//-- Bucle infinito
while (1);
}También se puede definir un puntero inicializado con la dirección de los LEDs y acceder a su contenido:
volatile uint32_t* leds; //-- Puntero
void main()
{
//-- Inicializar puntero con la direccion de los LEDs
leds = (uint32_t*) 0xFFFF0000;
//-- Sacar valor por los leds
*leds = 0xAA;
//-- Bucle infinito
while (1);
}- Instrucciones básicas: Son las que se transforman a código máquina y que ejecuta el procesador

- Pseudo-instrucciones: No existen realmente como instrucciones. El ensamblador las transforma en instrucciones básicas. Una pseudo-instrucción puede dar lugar a 1 ó varias instrucciones básicas

- Directivas: Dar información al programa ensamblador. No generan código máquina

La práctica hace al ingeniero. Para dominar algo, hay que practicarlo. Mucho. Y hay que pensar. Muchas veces se nos piden cosas que no entendemos a la primera. Hay que pensar. Hay que probar. Hay que equivocarse... hasta que se obtenga la solución
Escribe un programa que defina la variable contador cuyo valor inicial esté dado por el identificador INICIO. Dentro de un bucle infinito se incrementará esta variable en la cantidad indicada por el identificador INC. Para comprobar su funcionamiento usa los valores de INICIO = 100 e INC=10. No olvides colocar un Breakpoint en el bucle infinito, o bien ejecutarlo paso a paso
Los identificadores definidos mediante la directiva .eqv son muy útiles. Nos permite por ejemplo asignar un identificador a un desplazamiento de una tabla, para acceder de una forma sencilla a sus elementos. En este programa se define una tabla de 4 palabra, con unos valores iniciales
.eqv E1 0
.eqv E2 4
.eqv E3 8
.eqv E4 0xC
.data
tabla: .word 0xBEBECAFE, 0xFACEB00C, 0x00FABADA, 0xCACABACA
.text
la x5, tabla
lw x10, E1(x5)
lw x11, E2(x5)
lw x12, E3(x5)
lw x13, E4(x5)
#-- Terminar
li a7, 10
ecallEjecuta el programa e indica los valores que se almacenan en los registros x10, x11, x12 y x13. Fíjate en cómo se están usando los identificadores E1, E2, E3 y E4
Modifica el programa anterior para que cada elemento de la tabla se incremente en INC unidades, donde INC es un identificador (prueba el programa con INC=1). El programa leerá cada elemento, lo incrementará en INC unidades y lo almacenará en su sitio. Simula el programa y comprueba que tras su ejecución, efectivamente todos los elementos de la tabla se han modificado
Modifica el programa del contador del ejercicio 1 para que en vez de almacenar la cuenta en la variable contador, se envíe por el puerto de salida de la dirección 0xFFFF0000. Simúlalo, coloca un Breakpoint en el bucle principal, y comprueba que en esa dirección se van almacenando los valores de contador
Si este programa se ejecutase en el RISC-V real se vería la cuenta en binario en los LEDs (Bueno, en realidad habría que añadir una pausa para ralentizar el micro, pero ya veremos cómo se hace)
Modifica el programa del ejercicio 4 para que el valor del contador se envíe a la dirección 0xFFFF0010 donde se encuentra el Display de 7 segmentos. Haz que el valor inicial sea de 0, y el incremento de 1. Lanza el display en el simulador y conéctalo. Simula el programa (no olvides el Breakpoint o bien bajar la velocidad de la ejecución). Observa lo que aparece en el display. ¿Qué valores del contador hace que aparezcan dígitos o letras reconocibles en el display? (hay 128 combinaciones posibles, 2 elevado a 7, sin tener en cuenta el punto)
Escribe un programa que muestre por el display de 7-segmentos derecho el dígito 3. Utiliza un identificador para este valor que hace aparecer un 3. Obtén los valores para que aparezcan los dígitos 1, 2 y 4
Escribe un programa, similar al del ejercicio 2, donde se defina una tabla con 4 palabras, accesible mediante los identificadores E1, E2, E3 y E4. Esta tabla deberá estar inicializada con los valores que hacen aparecer los dígitos 1,2,3 y 4 en el display de 7 segmentos (calculados en el ejercicio anterior). El valor de estos dígitos estará definido también con identificadores: DIG1, DIG2, DIG3 y DIG4
El programa leerá cada uno de estos valores de la tabla y los enviará al display, uno detrás de otro, de forma que al simularlo paso a paso veremos los dígitos 1,2,3 y 4
Amplía la tabla para contener 10 valores, que hagan aparecer los dígitos del 0 al 9 en el display. El programa deberá leer los valores, uno a uno y mandarlos al display, para ver los dígitos del 0 al 9 en el display. Luego termina
Todavía no sabemos hacer bucles así que hazlo igual que en el ejercicio anterior, con instrucciones lw y sw para cada dígito
Escribe un programa que saque por el display de 7 segmentos derecho una cuenta atrás: 9, 8, 7....0 al llegar a 0 volverá otra vez mostrar el 9 y repetir la cuenta atrás indefinidamente (bucle infinito). Simula tu programa sin poner breakpoints, pero a baja velocidad, para que puedas ver la cuenta en acción
¿Sabrías hacer un programa que muestre una animación en el display de 7 segmentos en la que sólo hay un único segmento encendido cada vez, que va recorriendo el perímetro en sentido horario? Primero se enciende el segmento a, luego el b, luego el c... y finalmente el f (el g no se enciende nunca). Se repite en un bucle infinito. Usa sólo las instrucciones que conocemos hasta el momento
- Título informal de la clase: "¡Ey! ¡Que estoy vivo!"
- Situación: Año 3000. Científico extraordinario que encuentra la cura del cáncer... pero está a punto de morir... Guaran su cerebro en un envase, con soporte de vida: que le proporciona alimento, etc... PERO NO TENEMOS SENTIDOS: No podemos captar la realidad... PERO LO PEOR... No podemos comunicarnos con nadie...
- Un computador, por MUY INTELIGENTE QUE SEA, por muchas cosas que haga... si no se comunica con el exterior no vale para nada...
- Necesitamos al menos, un mecanismo mínimo para enviar información al exterior, desde nuestra CPU... Ese es nuestro objetivo de hoy
- (Anécdota del episodio Angustia de "Hitchcock presenta" sobre el hombre catatónico) Vídeo en youtube
- Katia Leal Algara
- Juan González-Gómez (Obijuan)


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 🚧
Macros
Entrada/Salida a bajo 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
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
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
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




