L4: Practica 2 - myTeachingURJC/2019-20-LAB-AO GitHub Wiki

Sesión Laboratorio 4: Práctica 2-1: Entrada salida mapeada en memoria

  • 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

Vídeos

  • Fecha: 2019/Oct/20

Vídeo 1/3: Entrada/salida mapeada en memoria

Haz click en la imagen para ver el vídeo en Youtube

Click to see the youtube video

Vídeo 2/3: Probando el Puerto de salida en un sistema Real

Haz click en la imagen para ver el vídeo en Youtube

Click to see the youtube video

Vídeo 3/3: Display de 7 segmentos

Haz click en la imagen para ver el vídeo en Youtube

Click to see the youtube video

Contenido

Introducción

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)

Definiendo constantes: Directiva .eqv

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 5

Se 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

Ejemplo: Contador con valor inicial

Modificaremos el programa del contador para que empiece su cuenta con un valor inicial, establecido mediate una constate. 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
	b 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

Periféricos mapeados en memoria (MMIO)

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

Puerto de salida de 8 bits

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

Escribiendo un valor en el puerto de salida

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
	sw 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:   b stop

Para 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:   b stop

Está 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 :smiley:

Encendiendo LEDs 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:

Click to see the youtube video

Display de 7 segmentos

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

Segmentos

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)

Nomenclatura

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

Displays de 7 segmentos en el Simulador

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

Encendiendo todos los segmentos de un display

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
	sw 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:   b stop

Para 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

Probando el display de 7 segmentos real

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

En este vídeo de youtube vemos el proceso completo:

Click to see the youtube video

Nota sobre acceso de E/S en lenguaje C

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: b 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);

}

Recopilación de instrucciones hasta el momento

  • 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

Actividades NO guiadas

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

Ejercicio 1

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

Ejercicio 2

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
	ecall

Ejecuta 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

Ejercicio 3

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

Ejercicio 4

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)

Ejercicio 5

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)

Ejercicio 6

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

Ejercicio 7

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

Ejercicio 8

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

Ejercicio 9

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

Ejercicio 10

¿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

Notas para el profesor

  • Título informal de la clase: "¡Hey! ¡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

Autores

Licencia

Enlaces