Skip to content

L16: Diseño digital (II): Domadores de bits

Juan Gonzalez-Gomez edited this page Nov 6, 2024 · 27 revisions

Sesión Laboratorio 16

  • Tiempo: 2h
  • Objetivos de la sesión:
    • Un Primer contacto con sistemas digitales en FPGA
    • Practicar con el diseño de circuitos sencillos en Icestudio
    • Colección iceK: manejo de constantes

Contenido

Introducción

Recordamos nuestro modelo de robot. Hay un elemento, el procesador que es el realiza todo el procesamiento de la información: a patir de la información de entrada recibida por los sensores se genera una nueva información de salida para los actuadores

Este procesamiento se puede realizar de diferentes formas. Típicamente se hace mediante software, usando un computador. En la memoria está almacenado el software, y la CPU va ejecutando las instrucciones

Es decir, que estamos a Nivel de Software. Sin embargo, también se puede realizar este procesamiento desde niveles más bajos: utilizando circuitos digitales

Eso será lo que haremos: bajaremos hasta el nivel de los bits para tener intuición de lo que está ocurriendo ahí. A partir de ahora, resetea tu mente. Ólvídate del pensamiento software. ¡Vamos a pensar en Hardware!

FPGAs

¿Cómo vamos a hacer nuestros circuitos digitales? Utilizando FPGAs

Cualquier circuito digital, por muy complicado que sea, está formado a nivel de electrónica digital sólo por 3 elementos:

  • Cables : Transportar los bits de un sitio a otro
  • Puertas lógicas: Combinar y transformar bits
  • Biestables: Almacenar bits

Las FPGAs son un chips que contienen todos estos elmentos, pero están SIN CONECTAR. Y estas conexiones son configurables

Hay unos bits, que se llaman bits de configuración que establecen estas uniones. Un bit a 1 hace que dos cables se conecten. Un bit a 0 hace que estén desconectados. Estableciendo las configuraciones correctas generamos nuestros circuitos

El flujo de trabajo es el siguiente. Partimos de un diseño fuente de nuestro circuito, que puede ser bien un esquema gráfico o bien estar descrito utilizando un lenguaje de descripción hardware (Como VHDL o Verilog)

Una herramienta, conocida con el nombre de sintetizador, procesa el circuito y genera los bits de configuración necesarios para establecer las conexiones en la FPGA y obtener nuestro circuito original. Estos bits reciben el nombre de Bitstream. Son los que se envían a la FPGA para establecer su configuración

FPGAs libres

Las FPGAs aparecieron en los años 80, sin embargo, la información sobre el formato del bitstream y la organización interna de los elementos en su interior NO estaban disponibles. Los fabricantes no dan acceso a esta información. Para usar las FPGAs sólo lo puedes hacer con sus Herramientas. No puedes crearte las tuyas propias por no tener acceso a esta información

Esto es algo particular del universo de las FPGAs, porque en otros ámbitos, como por ejemplo los procesadores, sí que estaba disponible la información sobre el formato de las instrucciones y su codificación en código máquina. Esto propición, desde sus orígenes, que apareciesen muchos compiladores y herramientas independientes, generándose un ecosistema muy rico, y dando lugar más adelante al ecosistema del software libre

Afortunadamente, en el 2015, la situación cambió, y gracias a la Ingeniera Austriaca Claire Wolf apareció el primer sintetizador libre de la historia. Esto fue un hito importantísimo, similar a la aparición del GCC, el primer compilador libre

A partir de ahí, la comunidad empezó a documentar las FPGAs. El nuevo ecosistema creado, para diferenciarlo del anterior, lo llamamos ecosistema de FPGAs libres. Y las FPGAs que están totalmente documentadas, las denominamos como FPGAs Libres

Actualmente, el proyecto de documentación y mantenimiento de las herramienta para FPGAs libres es F4PGA (Anteriormente conocido como proyecto Symbiflow)

Icestudio

Icestudio es la herramienta libre y multiplataforma que usaremos para crear nuestro circuitos digitales. Estos circuitos los crearemos de una manera gráfica

Antes de empezar a diseñar nuestros circuitos, y entender su funcionamiento a nivel hardware, vamos a aprender de forma intuitiva qué es lo que está ocurriendo a nivel de Icestudio para que nuestros circuitos fuentes se transformen en circuitos reales

Del circuito fuente al circuito real

A nivel de usuario, creamos nuestro circuito gráficamente, pinchamos en un botón y mágicamente este circuito se materializa en un circuito real, con existencia física

Así es como diseñaremos: construimos circuitos y los probamos físicamente, sin necesidad de conocer los detalles de lo que ocurre por debajo. Sin embargo, como ingenierios, nos interesa tener algo de intuición de lo que está pasando

En esta figura se resume el proceso de funcionamiento de Icestudio:

  • Creación del circuito fuente: El usuario diseña el circuito en Icestudio. Es lo que llamamos el Circuito fuente. Se almacena en un fichero con extensión .ice. En esta figura el circuito se llama Ejemplo.ice
  • Fase 1: Conversión a Verilog: Cuando el usuario aprieta el botón de cargar el circuito en la FPGA comienza la primera fase. Icestudio analiza el circuito gráfico y lo re-escribe en Lenguaje Verilog. El lenguaje Verilog es un lenguaje de descripción Hardware (HDL). Icestudio genera el fichero main.v con el código en verilog
  • Fase 2: Síntesis: El fichero en verilog se transforma en otro fichero que contiene los bits de configuración de las uniones dentro de la FPGA, para que nuestro circuito se materialice. Este fichero es binario, y lo llamamos Bitstream. En Icestudio el bitstream se crea en el fichero hardware.bin
    La fase de síntesis la realizan las herramientas del proyecto F4PGA (que en Icestudio están disponibles al instalar la toolchain). Esta fase es la más crítica, y complicada, y el que existan herramientas libres que hagan esta síntesis es lo que ha provocado la aparición del ecosistema de las FPGAs libres (y que puedan existir herramientas como Icestudio)
  • Fase 3: Carga en Flash. En esta fase el bitstream se almacena en la memoria flash de la placa que estemos utilizando (en nuestro caso en la flash de la tarjeta Alhambra II)
  • Fase 4: Configuración de la FPGA. El último paso es que se materialice el circuito en la FPGA. Es decir, es el proceso mediante el cual se lee la memoria flash con el bitstream y se establecen las uniones entre los componentes internos, dando lugar a la aparición de nuestro circuito. Esto lo hace automáticamente la FPGA al recibir la alimentación

Abriendo el circuito de ejemplo 1 de la colección por defecto

Para entender mejor estas ideas a nivel práctico, vamos a continuar por donde lo habíamos dejado en la sesión anterior: Cargamos de nuevo el circuito 01. One LED que simplemente enciende el LED0 de la placa

En las colecciones instaladas, además de bloques hay ejemplos. Para utilizarlos primero hay que seleccionar una colección. En nuestro caso, como bajos a usar la colección por defecto, nos vamos al menú Seleccionar/Colección y pinchamos sobre la opción Por defecto

Siempre se nos mostrará un tick ✅️ en la derecha de la colección seleccionada actualmente

Ahora nos vamos a Archivo/Ejemplo. Ahí nos aparecerán los ejemplos de la colección seleccionada. Seleccionamos el ejemplo 1.Básico/01.Un LED

Se nos abre y lo vemos. Ya podemos empezar a trabajar con él (Recuerda darle a la opción de CONVERTIR, como vimos en la sesión anterior)

Guardando el circuito en nuestro directorio

Este circuito de ejemplo viene con Icestudio, y está almacenado en un directorio del sistema (que normalmente es de sólo lectura). Vamos a guardarlo en un directorio en nuestro disco duro. Así lo podremos modificar y podremos ver los ficheros intermedios generados

Pinchamos en Archivo/Guardar como y le damos el nombre Ejemplo.ice. Lo guardamos por ejemplo en nuestro directorio personal (Home)

El nombre del fichero del circuito actual (sin extensión) lo vemos en la parte inferior izquierda:

Viendo los recursos que ocupa el circuito en la FPGA

Los circuito que hacemos consumen recursos de la FPGA. El tipo de recurso y la cantidad dependen de la FPGA usada. En el caso de la Tarjeta Alhambra II, la FPGA es una Lattice ICE40Hx8K que dispone de los siguientes recursos:

  • LC: Bloques lógicos: 7680
  • RAM: Bloques de memoria RAM de 4096 bits: 32
  • IO: Bloques de E/S: 256

Para conocer los recursos consumidos hay que activar la opción Ver/Recursos de la FPGA:

En la parte inferior es donde se verán los recuros, aunque hasta que no se haga la síntesis no aparecerán valores concretos:

Si ahora realizamos la síntesis (o la carga) ya sí podremos ver los recursos de este circuito:

En este ejemplo vemos que se usan 2 bloques lógicos, 8 bloques de E/S y Ningún bloque de RAM

Nos puede resultar curioso que se usen 8 bloques de E/S, cuando sólo se está usando un pin de salida. Esto es así porque por defecto están activadas las reglas de la placa. Estas reglas hacen que todos los pines de los LEDs no usados se conecten a 0 para que estén apagados. Como hay 8 LEDs, se usan 8 bloques IO

Podemos activar/desactivar las reglas de la placa en la opción Editar/Preferencias/Reglas de la placa. En nuestro caso están activas, y por eso aparece un tic ✅️ en la derecha:

Si desconectamos las reglas, sintetizamos, y vemos los recursos, aparece lo siguiente:

Ahora vemos que efectivamente, el circuito consume sólo 1 bloque de entrada/salida.

¡No olvidar volver a conectar las reglas!

Viendo el fichero fuente Ejemplo.ice

Los ficheros fuente de los circuitos de Icestudio son ficheros de texto en formato JSON. Para comprobarlo vamos a abrir el fichero Ejemplo.ice desde el VSCODE:

Como el fichero tiene extensión .ice, el VSCODE no activa el resaltado de sintáxis del formato JSON. Lo activamos manuamente pinchando en la parte inferior derecha, donde pone Plain text.

En la caja de búsqueda ponemos JSON, y lo seleccionamos. Ahora ya vemos la sintáxis resaltada

Las fuentes completas se pueden ver aquí:

{
  "version": "1.2",
  "package": {
    "name": "Led on",
    "version": "1.0.0",
    "description": "",
    "author": "",
    "image": ""
  },
  "design": {
    "board": "alhambra-ii",
    "graph": {
      "blocks": [
        {
          "id": "949075cb-26c0-49da-ba76-2496ea9aa7cc",
          "type": "basic.output",
          "data": {
            "name": "LED",
            "pins": [
              {
                "index": "0",
                "name": "LED0",
                "value": "45"
              }
            ],
            "virtual": false
          },
          "position": {
            "x": 352,
            "y": 136
          }
        },
        {
          "id": "a538a5b4-d5d5-4ace-a593-efb1fa9b930c",
          "type": "basic.info",
          "data": {
            "info": "Turn on a LED",
            "readonly": true
          },
          "position": {
            "x": 80,
            "y": 48
          },
          "size": {
            "width": 128,
            "height": 32
          }
        },
        {
          "id": "15e3946a-c638-4fc2-944d-54fc7e931efc",
          "type": "febcfed8636b8ee9a98750b96ed9e53a165dd4a8",
          "position": {
            "x": 80,
            "y": 136
          },
          "size": {
            "width": 96,
            "height": 64
          }
        }
      ],
      "wires": [
        {
          "source": {
            "block": "15e3946a-c638-4fc2-944d-54fc7e931efc",
            "port": "3d584b0a-29eb-47af-8c43-c0822282ef05"
          },
          "target": {
            "block": "949075cb-26c0-49da-ba76-2496ea9aa7cc",
            "port": "in"
          }
        }
      ]
    }
  },
  "dependencies": {
    "febcfed8636b8ee9a98750b96ed9e53a165dd4a8": {
      "package": {
        "name": "bit-1",
        "version": "0.2",
        "description": "Constant bit 1",
        "author": "Jesus Arroyo",
        "image": "%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2289.79%22%20height=%22185.093%22%20viewBox=%220%200%2084.179064%20173.52585%22%3E%3Cpath%20d=%22M7.702%2032.42L49.972%200l34.207%207.725-27.333%20116.736-26.607-6.01L51.26%2025.273%2020.023%2044.2z%22%20fill=%22green%22%20fill-rule=%22evenodd%22/%3E%3Cpath%20d=%22M46.13%20117.28l21.355%2028.258-17.91%2021.368%206.198%205.513m-14.033-54.45l-12.4%2028.26-28.242%205.512%202.067%208.959%22%20fill=%22none%22%20stroke=%22green%22%20stroke-width=%222.196%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22/%3E%3C/svg%3E"
      },
      "design": {
        "graph": {
          "blocks": [
            {
              "id": "3d584b0a-29eb-47af-8c43-c0822282ef05",
              "type": "basic.output",
              "data": {
                "name": ""
              },
              "position": {
                "x": 456,
                "y": 120
              }
            },
            {
              "id": "61331ec5-2c56-4cdd-b607-e63b1502fa65",
              "type": "basic.code",
              "data": {
                "code": "//-- Constant bit-1\nassign q = 1'b1;\n\n",
                "params": [],
                "ports": {
                  "in": [],
                  "out": [
                    {
                      "name": "q"
                    }
                  ]
                }
              },
              "position": {
                "x": 168,
                "y": 112
              },
              "size": {
                "width": 248,
                "height": 80
              }
            }
          ],
          "wires": [
            {
              "source": {
                "block": "61331ec5-2c56-4cdd-b607-e63b1502fa65",
                "port": "q"
              },
              "target": {
                "block": "3d584b0a-29eb-47af-8c43-c0822282ef05",
                "port": "in"
              }
            }
          ]
        }
      }
    }
  }
}

Viendo el fichero verilog generado

El fichero Ejemplo.ice con el ejemplo del LED lo hemos guardado en nuestro home (aunque podría estar en cualquier carpeta). En la misma carpeta donde está el fichero fuente aparece el directorio ice-build

Dentro de esa carpeta aparece una con el nombre Ejemplo y en su interior están todos los ficheros generados en la síntesis del circuito

El fichero con el código verilog es main.v, que abrimos también con el VSCODE

Si tenemos instalada una extensión de Verilog, como Verilog-HDL

Se resaltará la sintáxis y veremos mejor el fichero:

El fichero completo lo vemos aquí: main.v:

// Code generated by Icestudio 0.12

`default_nettype none

//---- Top entity
module main (
 output v0658ae
);
 wire w0;
 assign v0658ae = w0;
 vfebcfe v8f07dd (
  .v9fb85f(w0)
 );
endmodule

//---------------------------------------------------
//-- Led on
//-- - - - - - - - - - - - - - - - - - - - - - - - --
//-- 
//---------------------------------------------------
//---- Top entity
module vfebcfe (
 output v9fb85f
);
 wire w0;
 assign v9fb85f = w0;
 vfebcfe_vb2eccd vb2eccd (
  .q(w0)
 );
endmodule

//---------------------------------------------------
//-- bit-1
//-- - - - - - - - - - - - - - - - - - - - - - - - --
//-- Constant bit 1
//---------------------------------------------------

module vfebcfe_vb2eccd (
 output q
);
 //-- Constant bit-1
 assign q = 1'b1;
 
 
endmodule

Viendo el Bitstream

El fichero hardware.bin contiene el Bitstream: Los bits de configuración que establecen las unciones entre los elementos de la FPGA, y hacen que se materialice* nuestro circuito

Este fichero está en binario, por lo que hay que abrirlo con un editar hexadecimal. Esto también lo podemos hacer desde el VSCODE, con la extensión Hex Editor:

Abrimos el fichero hardware.bin. Nos indica que es binario y no lo abre por defecto. Pinchamos en Open anyway y buscamos y seleccionamos la opción Hex Editor

Ahora ya podemos ver el fichero binario:

Cables y LEDs

Comenzamos con la construcción de circuitos básicos para encender LEDs. Utilizaremos las constantes que se encuentran en la colección iceK

Reto 1: Encender un LED

Vamos a construir el circuito hola mundo desde cero: Encender un LED. En el camino aprenderemos el manejo básico de Icestudio: colocación de componentes, movimiento, borrado, conexión mediante cables, documentación...

(Reto-01-led-on.ice)

Colocación de componentes

Primero colocamos un bit constante a 1. Abrimos el panel lateral izquierda, seleccionamos la colección iceK, la carpeta Bits y el elemento bit-1. Lo colocamos en nuestro circuito

En esta animación se muestra el proceso:

El siguiente paso es poner un pin de salida. Es el lugar por donde saldrá el bit hacia el exterior. Esto lo hacemos pinchando en Basic/Output

A cada pin de salida se le puede dar un nombre opcional. Escribimos, por ejemplo, LED y pinchamos en OK

Aparecerá el bloque de salida, con el nombre dado en la parte superior. Moviendo el ratón lo posicionamos y apretamos el botón izquierdo del ratón

El bloque de salida colocado es genérico. Todavía no se ha asignado a ningún pin real de la FPGA. Para asociarlo a un pin pinchamos en el desplegable que pone NULL. Ahora seleccionamos el pin, por ejemplo LED7, que es el LED de mayor peso de la tarjeta Alhambra II. Es el LED que queremos encender

Tendremos el bloque de salida ya configurado, asociado al LED7

En esta animación se muestra el proceso:

Uniendo los componentes mediante un cable

Para completar el circuito hay que tirar un cable para unir el bit constante 1 con el pin del LED7. Para ello acercamos el cursor a la salida del Bit1. La forma del cursor cambiará a una cruz. En ese momento dejamos apretado el botón izquierdo del ratón y arrastramos hasta la parte izquierda del bloque de salida. Soltamos el bóton. ¡Ya tenemos nuestro primer cable!

En esta animación se muestra el proceso:

Panel de herramientas

El menú Basico se usa mucho. Es el que contiene, entre otras cosas, los bloques de entrada y salida. Debido a que se usa mucho, también está accesible como un panel de herramientas que se puede situar en cualquier parte, similar al panel de componentes

Se puede abrir a partir del menú Editar/Herramientas, o bien pulsando el atajo Ctrl-T:

Aparece el panel de herramientas, que podemos colocar en diferentes lugares

En esta Animación se muestra el proceso

Síntesis y carga del circuito

Ya tenemos nuestro primer circuito construido. Ahora lo sintetizaremos y cargaremos en la placa Alhambra-II. Este proceso se hace apretando el icono de carga situado en la parte inferior derecha:

En esta animación se muestra el proceso de carga:

Al cabo de unos segundos, el bitstream se habrá generado y cargado en la placa. Veremos cómo el LED7 está encendido (y el resto apagados por la acción de las reglas de la placa)

¡Hemos creado el circuito HOLA MUNDO desde cero!. Y lo hemos cargado en la placa. Hemos diseñado desde 0 nuestro primer circuito digital REAL

Añadiendo documentación

El circuito todavía no está completado. ¡¡Falta la documentación!!. En este caso es un circuito muy sencillo, pero como regla genera, SIEMPRE añadiremos documentación para que otras personas puedan entender mejor nuestro circuito

Para añadir documentación nos vamos al menú Básico/Información (O bien abrimos el panel de herramientas)

Aparecerá una caja negra que podemos colocar en la posición que queramos. Hacemos click con el botón izquierdo del ratón para fijar el objeto

Arrastrando la izquina inferior derecha podemos cambiar el tamaño de esta caja. Vamos a hacerla un poco más ancha para que nos quepa más texto

Es una caja de texto donde podemos escribir la documentación, usando el formato Markdown. Esto nos permite resaltar encabezados, poner palabras en negrita, o incluso añadir imágenes

Si pinchamos en la caja aparecerá un cursor parpadeante, que nos indica que podemos escribir. Es el momento de poner nuestra documentación en Markdown. Etamos en el modo de edición

Si ahora situamos el cursor en el marco de la caja y hacemos doble click, entramos en el modo renderizado donde se interpreta el markdown y se muestra el texto final

En esta animación se muestra el proceso completo

Editando documentación

Para pasar del modo renderizado al modo de edición basta con hacer doble click en el texto. Una vez que acabamos la edición (o el cambio del tamaño de la caja) volvemos a hacer doble click sobre el marco

Se muestra en esta animación

Navegación en Icestudio

Utilizamores nuesro primer circuito para aprender las maneras básicas de navegar. Pinchando con el Botón derecho del ratón y arrastrándolo movemos nuestro punto de vista (la cámara). NO estamos moviendo el circuito, nos estamos moviendo nosotros

En esta animación se muestra

Con la rueda del ratón hacemos zoom hacia dentro o hacia fuera. Este zoom se centra en el cursor del ratón. Es decir, donde tengamos el puntero es hacia donde nos acercamos o nos alejamos

En esta animación se hacen diferentes acercamientos y alejamientos:

Así es como nos ha quedado el circuito tras estos acercamientos y alejamientos:

Para volver a la situación inicial, con el circuito centrado en la pantalla, y con el zoom en su estado original, pulsamos la tecla Ctrl-1 (O bien al menú Editar/Ajustar contenido)

Moviendo bloques

En nuestro circuito hola mundo tenemos dos tipos de elementos: bloques y un cable Hay en total 3 bloques: la constante 1, la salida y la caja de texto de documentación

Los bloques se pueden arrastrar con el ratón, logrando su movimiento. Así los podemos recolocar

Fíjate que los cables siguen uniendo los bloques al moverlos, y cambian su trayectoria

Seleccionando bloques

Podemos seleccionar grupos de bloques, para actuar sobre ellos. Por ejemplo para mover varios bloques (dejando los otros en sus posiciones). Para selección pinchamos en un punto del fondo (donde no haya elementos) y lo arrastramos. Nos aparecerá un rectángulo azul que es el que delimita la zona con los componentes a seleccionar

Por ejemplo, vamos a seleccionar el bloque de texto:

Al soltar el botón izquierdo del ratón el texto quedará seleccionado

En esta animación se muestra cómo se seleccionan dos bloques, y luego se mueven a la vez

Borrando bloques

Para borrar un bloque o un grupo de bloque, primero los seleccionamos y luego apretamos la tecla DELETE o SUPR. En cualquier momento podemos recuperar el bloque borrado usando la tecla Ctrl-Z (Deshacer)

Si lo que se borra es un bloque que tenía un cable, el propio cable también se elimina. En esta animación se muestra el borrado de varios elementos (y su recuparación posterior con Ctrl-Z)

Borrando cables

Para borrar sólo los cables, hay que acercar el cursor al cable hasta que aparezca una X roja

Al clicar sobre la X desaparece el cable, tal y como se muestra en esta animación:

Editando cables

Por defecto los cables se colocan automáticamente, con una trayectoria determinada. Sin embargo es posible editarlos para añadir nuevos puntos por donde queremos que pasen. Esto es muy útil para ordenarlos o mejorar la legibilidad del circuito

Para añadir un nuevo punto basta con pinchar sobre una parte del cable y arrastrar el ratón. Aparecerá un círculo verde que se corresponde con el nuevo punto introducido

Podemos añadir tantos puntos como queramos para definir la trayectoria del cable. Vamos a añadir otro punto, repitiendo el proceso anterior:

Al apartar el cursor del cable, podemos ver cómo ha quedado:

Siempre que acerquemos el cursor al cable podremos ver los puntos añadidos, y por supuesto los podremos eliminar (pinchando en la X o bien moverlos a nuevas posiciones)

El proceso completo lo vemos en esta animación:

Explorando el ejemplo

Partimos de nuestro ejemplo inicial. Si hacemos doble click sobre el bloque del Bit 1, entramos en su interior. Podemos ver cómo está implementado ese bloque

En el caso del bloque Bit 1, como es un bloque básico, está implementado directamente en Verilog. En la parte superior izquierda vermos el nombre del bloque actual y su versión. En la parte inferior izquierda, vemos un mapa de situación, para saber en qué profundidad estamos. En este ejemplo el nivel superior es un documento Sin título (ya que todavía no lo hemos guardado). Nosotros estamos en un nivel inferior, dentro de Bit 1

Para volver al nivel anterior pinchamos en la opción que pone Volver

En esta animación se muestra cómo entramos y salimos del Bloque Bit-1:

Si ahora hacemos doble click en el Bloque de salida, nos aparece una ventana que nos permite cambiar/añadir un nombre.

Por ejemplo vamos a escribir TEST, y pulsamos el botón de OK

El nombre de la salida se habrá actualizado:

Por último, podemos cambiar en cualquier momento el pin asociado al bloque de salida. Ahora mismos está asociado al LED7, pero se puede cambiar a cualquier otro. Por ejemplo lo pondremos en el LED0

(Es posible escribir en la caja de búsqueda el texto del pin para ir más rápido)

Ahora el bloque de salida está asocado a un nuevo PIN (conectado al LED0)

Si ahora lo cargamos en la placa, se encenderá sólo el LED0:

Reto 2: Encender dos LEDs

Vamos a crear un circuito para encender 2 LEDs

Crear un circuito nuevo

Lo primero es crear un circuito nuevo pinchando en la opción File/New

Se abre una ventana nueva (pero la anterior se manteniene). Ahora tenemos dos ventanas independientes de Icestudio, cada una para un circuito

Guardar el nuevo circuito

Antes de empezar a diseñar el circuito, vamos a guardarlo en el fichero Reto-02.ice

Pinchamos en File/Save as

Escribimos el nombre del fichero y pinchamos en save

Ya tenemos el circuito vacío, con el nuevo nombre. ¡Listos para hacer el reto!

Copy & Paste del circuito del reto 1

Seleccionamos el cicuito del reto 1 y lo copiamos pinchando en la opción Edit/Copy o el atajo Ctrl-C

Ahora nos vamos a la ventana del reto 2 y pinchamos en Edit/Paste

Se copia el circuito

Duplicando el circuito

Para encender dos leds lo que hacemos es poner dos circuitos en paralelo. Esta es una de las ventajas del hardware, que todo funciona en paralelo. Seleccionamos el circuito y lo duplicamos pinchando en la opción Edit/Duplicate (o con el atacho Ctrl-D)

Nos aparece la copia del circuito

En esta animación se muestra el proceso:

Dos circuitos en paralelo

Ya tenemos dos circuitos en paralelo, que funcionan de manera independiente. Ahora hay que cambiar la salida de uno de estos circuitos porque tal cual está ahora se está violando una de las normas fundamentales de los circuitos digitales: DOS SALIDAS NO PUEDEN ESTAR CONECTADAS ENTRE ELLAS. O en nuestro caso, dos salidas no puede estar conectadas al mismo punto

Seleccionamos un pin distinto, por ejemplo el asociado al LED0

Ahora ya podemos cargar nuestro circuito en la FPGA y comprobar cómo los LEDs 7 y 0 se encienden

Añadimos un poco de documentación y ya tenemos el circuito final:

(Reto-02-dos-leds.ice)

Circuito equivalente

Un circuito equivalente, que enciende también los dos LEDs consumiendo los mismos recursos lo obtenemos uniendo el bloque Bit-1 a las dos salidas. Para hacerlo borramos uno de los bloques 1:

Y ahora tiramos un nuevo cable. Se tira exactamente igual que cualquier cable: situamos el ratón en la salida del bloque 1 y arrastramos para que aparezca el cable

Una vez colocado el nuevo cable, podemos añadir un nuevo punto para recolocarlo

En esta animación se muestra el proceso:

Practicando con el cableado

Para practicar con lo aprendido hasta ahora se proponen los siguientes ejercicios:

  • Ejercicio 2-1: Encender 4 LEDs
  • Ejercicio 2-2: Encennder 8 LEDs

Reto 3: Encendiendo LEDs con etiquetas

Vamos a ver circuitos equivalentes para encender LEDs

El problema del cableado

Cuando se trabaja con electrónica, que son sistemas físicos, existe un problema de cableado. Los componentes están unidos mediante cables, por donde fluyen los bits. El problema es que ¡HAY MUCHOS CABLES!, y los diseños físicos se convierten en marañas de cables

Al dibujar los planos de los circuitos ocurre algo similar: hay que tirar cables. Y cuando los diseños son complejos... ¡Hay muchos cables! Esto complica enormemente su diseño

Usando etiquetas

Para lidiar con este problema, se usan las etiquetas. Son cables que no se dibujan, sino que se sustituyen por identificadores

Icestudio permite utilizar etiquetas. Vamos a usarlas para sutituir los cables del reto 2: Encender dos LEDs

(Reto-03-leds-etiquetas.ice)

Hay una etiqueta de inicio, que representa el comienzo del cable, y luego dos etiquetas de final, que representan los dos extremos finales de los dos cables

Gracias a las etiquetas podemos simplificar los diseños

¡OJO!: Las etiquetas nos permite simplicar los diseños en el ordenador, pero luego, cuando se sintetizan y se crean físicamente en la FPGA, los cables están ahí!! con existencia real!!

Este circuito es totalmente equivalente al anterior. Se usa un par de etiquetas para representar un cable. El otro cable se utiliza de forma normal

Poniendo etiquetas

Partimos del ejemplo del reto 3 al que hemos eliminado todas las conexiones:

Vamos a encender el primer led usando etiquetas. Para ello pinchamos en la opción Basic/Paired Labels

Se abre una ventana para escribir el nombre de la etiqueta (por ejemplo test) y opcionalmente elegir el color. Luego pulsamos OK

Ahora situamos el par de etiquetas:

Y por último unimos las etiquetas a los bloques que queremos conectar:

Para conectar el segundo LED duplicamos la etiqueta de fin y la unimos, obteniendo el circuito final

Entradas y pulsadores

La información llega del exterior a través de las entradas. En la FPGA hay unos pines que se configuran como entradas y que permiten la entrada de bits. El periférico más sencillo para recibir bits el el pulsador. Si se lee 0 significa que el pulsador NO está apretado. Sin embargo, al apretar el pulsador se lee un 1

Vamos a aprender a hacer circuitos para leer los pulsadores y activar LEDs

Reto 4: Pulsador controlando un LED

Vamos a hacer un circuito para controlador un LED. Queremos que el LED se encienda al apretar el pulsador y que se apague al soltarlo. Este comportamiento lo podemos describir mediante una tabla de verdad. En la izquierda ponemos la entrada y en la derecha la salida

Entrada (pulsador) Salida (LED) Descripcion
0 (no pulsado) 0 (apagado) LED apagado
1 (pulsado) 1 (encendido) LED encendido

Colocando un pin de entrada

Lo primero que hacemos es insertar un pin de entrada. Pinchamos en Basic/Input

Nos aparece una nueva ventana para introducir el nombre de esta entrada. Este nombre es para nosotros, y es opcional. Vamos a llamarlo BOTON. Clicamos en OK al terminar

Colocamos el componente y apretamos en el botón izquierdo del ratón

En esta aniación se muestra el proceso

Asociando el pin de entrada al pulsador 1

Ya tenemos nuestro pin de entrada. Por defecto NO está asociado a ningún pin físico. Es decir, que está "al aire". Para conectar este pin al pulsador de entrada de la Alhambra-II pinchamos en el desplegable y seleccionamos SW1. Este es el nombre del pulsador 1 de esta placa

Para buscar el pulsador SW1 usamos la rueda del ratón para desplazarnos por todos los pines de entrada. Opcionalmente podemos usar la caja de búsqueda e introducir la cadena SW1. Nos llevará automáticamente a ese pin

Ya tenemos el pin de entrada asociado al pulsador SW1:

En esta animación se muestra el proceso:

Conectando el pulsador al LED

Ahora terminamos el circuito colocando un pin de salida asociado al LED0, y tirando un cable

El circuito está terminado. El bit que llega del pulsador se lleva directamente al LED. De esta forma, el pulsador controla el LED. En esta aniación se muestra en funcionamiento:

Observamos que se trata de un circuito SIN MEMORIA. El LED sigue al pulsador. Es un circuito puramente combinacional: acción-reacción. NO importa lo que haya ocurrido antes

¿Cómo implementaríamos este comportamiento usando un pensamiento software? Por ejemplo, usando un arduino

El código sería algo así (Tomado de la web de Arduino)

int ledPin = 13;  // LED connected to digital pin 13
int inPin = 7;    // pushbutton connected to digital pin 7
int val = 0;      // variable to store the read value

void setup() {
  pinMode(ledPin, OUTPUT);  // sets the digital pin 13 as output
  pinMode(inPin, INPUT);    // sets the digital pin 7 as input
}

void loop() {
  val = digitalRead(inPin);   // read the input pin
  digitalWrite(ledPin, val);  // sets the LED to the button's value
}

La CPU ejecuta el código máquina de ese programa de alto nivel (en C): lee el pin de entrada, lo almacena en una variable y luego lo escribe en el puerto de salida. El proceso se repite en un bucle infinito

Sin embargo, este mismo comportamiento en hardware se simplementa simplemente con UN CABLE que une el pin de entrada con el de salida. Este es un ejemplo en el que se ve cómo ciertos comportamientos se implementan de forma más sencilla mediante Hardware que mediante Software

Reto 5: Dos pulsadores controlando un LED cada uno

Ahora haremos un circuito para controlador dos LEDs mediante dos pulsadores, de manera independiente. Aquí es donde viene la magia del hardware: todos los circuitos funcionan EN PARALELO, a la vez. Así que simplemente basta con duplicar el circuito anterior y cambiarle los pines. En esta animación vemos el proceso:

El circuito obtenido es el siguiente

Tenemos dos circuitos independientes, que funcionan EN PARALELO. En esta animación se muestra su funcionamiento

Si programásemos esto en arduino, un posible programa podría ser este:

void setup() {
  pinMode(ledPin0, OUTPUT);
  pinMode(ledPin1, OUTPUT);
  pinMode(inPin1, INPUT);
  pinMode(inPin2, INPUT);
}

void loop() {
  val1 = digitalRead(inPin1);
  digitalWrite(ledPin1, val1);

  val2 = digitalRead(inPin2);
  digitalWrite(ledPin2, val2);
}

El funcionamiento NO ES EN PARALELO. Arduino es una CPU que ejecuta instrucciones secuencialmente. Primero se actualiza el primer led, y luego el segundo. Se hace muy rápido, pero NO EN PARALELO

Si probamos el programa en un arduino, veremos que el comportamiento es similar al de la Alhambra-II. Pero en realidad en el arduino los leds funciona de manera CONCURRENTE mientras que en Hardware funcionan EN PARALELO

Ejercicios

  • Ejercicio 5-1: Conectar un pulsador a los LEDs pares (con la etiqueta Boton1). Conectar el otro pulsador a los LEDs impares (con la etiqueta Boton2)

Modificando un Bit: NOT

En los circuitos que hemos realizado hasta ahora sólo hemos transportado bits, usando cables. Los hemos traído del exterior, transportado por el interior de la FPGA, y sacado hacia fuenta para encender LEDs

Lo siguiente que haremos será MANIPULAR estos bits. La forma más básica de manipulación es la OPERACION NOT que consiste en cambiar de estado los Bits. El funcionamiento de la puerta NOT se define mediante esta tabla de verdad

Entrada Salida
0 1
1 0

Reto 6: Pulsador invertido

En este circuito conectamos el pulsador de entrada al LED a través de una puerta NOT, de manera que el comportamiento será el inverso: Mientras el pulsador NO esté apretado, el LED estará encendido. Al apretar el pulsador, el LED se apaga

Colocando la puerta NOT

Partimos de un circuito en el que hemos colocado previamente un pin de entrada (asociado al pulsador SW1) y un pin de salida (asociado al LED0)

Abrimos el menú de las colecciones y seleccionamos la puerta not, que se encuentra en iceGates/Not

Colocamos la puerta NOT

En esta animación se muestra el proceso:

Cableando el circuito

Y por último tiramos los cables para unirlo todo:

Este es el resultado: El LED funciona a la inversa que antes

Ejercicios

  • Ejercicio 6-1: Conecta el otro pulsador a otro LED, sin NOT, para comparar

Reto 7: Dos LEDs en oposición de fase

En este reto queremos hacer un circuito que encienda dos LED, pero alternativamente. Mientras uno está encendido, el otro está apagado, y vice-versa. Cuando el pulsador no está apretado, el LED0 está está apagado y el LED1 encendido. Al apretar el pulsador ocurre lo contrario: el LED0 se enciende y el LED1 se apaga

Este comportamiento se muestra en la tabla de verdad:

Pulsador LED0 LED 1
0 0 1
1 1 0

El comportamiento del LED0 es el mismo que el pulsador: tiramos un cable del pulsador al LED0. El comportamiento del LED1 es el inverso: conectamos el pulsador al LED1 mediante una puerta NOT

Este esta animación se muestra el resultado:

Ejercicios

  • Ejercicio 7-1: Con un único pulsador hacer que se enciendan los LEDs pares al apretarlo, y los impares al soltarlo

Buses

Los circuitos digitales trabajan con números: modificándolos, transportándolos y almacenándolos. Los números se codifican en binario, y se usa un cable para transportar cada bit. Aunque esto es lo que sucede físicamente, a la hora de diseñar los circuitos agrupamos todos estos cables en un grupo que llamamos BUS

Así, por ejemplo, para transportar números de 8 bits tenemos que tirar 8 cables. Pero en el programa de diseño tiramos un único cable "gordo" de 8 bits de anchura: un BUS de 8 bits

Reto 8: Dos leds y dos pulsadores en buses

En este circuito controlamos dos LEDs con dos pulsadores, pero usando un BUS de dos bits para la conexión. Ahora la entrada la vemos como un número (de 2 bits) y queremos que ese mismo número se transporte al bus de salida donde están los LEDs

Bus de dos pulsadores de entrada

Primero creamos el BUS de entrada, asociado a los dos pulsadores. Lo creamos igual que un pin de entrada normal, pinchando en Basic/Input. Pero en el nombre especificamos la anchura del bus con la notación [N-1:0], donde N es el número de bits

En nuestro ejemplo, como queremos usar un número de 2 bits, creamos el pin de entrada con el nombre BOTON[1:0]

Colocamos el nuevo puerto de entrada de 2 bits:

Nos aparecen dos desplegables. El superior se corresponde con el bit 1, y el inferior con el bit 0. Los conectamos a los Pulsadores SW2 y SW1 respectivamente

Bus de dos LEDs de salida

Ahora hacemos lo mismo pero con los LEDS: Creamos un puerto de salida de 2 bits de anchura. Pinchamos en Basic/OUtput y como nombre usamos LED[1:0], para indicar que la anchura es de dos bits

Lo colocamos en su posición y le asignamos los LEDs a los que va conectados: LED0 y LED1

Tirar el Bus

Los buses se tiran exactamente igual que los cables. La única diferencia es visual: se ven más gordos. En esta animación se muestra el proceso:

Así es como queda el circuito:

Editando buses

Los buses se manejan exactamente igual que los cables. Los podemos eliminar, añadir puntos intermedios... En esta animación se muestran estas acciones:

Así es como queda el circuito con el BUS pasando por puntos intermedios

Circuito equivalente

Un bus de 2 bits es equivalente a utilizar 2 cables individuales. Así, estos dos circuitos SON EQUIVALENTES:

Esto significa que consumen los mismos recursos en la FPGA. Es totalmente indiferente usar un bus de 2 bits, que dos cables separados para unir los componentes

Reto 9: Etiquetas y buses

Las etiquetas se usan también con los Buses, igual que con los cables. Lo único es que hay que incluir el sufijo [N-1:0] en el nobre de la etiqueta

Este circuito es el mismo que el del reto 8: Dos pulsadores conectados a dos LEDs mediante un bus, pero usando la etiqueta boton[1:0]

Reto 10: Mostrando un número en los 8 LEDs

En el siguiente circuito vamos a sacar un número constante por los 8 LEDs de la tarjeta Alhambra. Este número se mostrará en binario

Comenzamos colocando un puerto de salida de 8 bits, conectado a los LEDs del 0 al 7

Colocando una constante de 8 bits

Los números constantes se encuentra en la colección icek. Desplegamos la opción icek/Bus/Bus-08/Generic

Colocamos una constante genérica de 8 bits

Y la conectamos a la salida

Ya tenemos nuestra constante genérica conectada a los LEDs. Ahora sólo nos falta establecer su valor. Esto lo hacemos colocando el elemento Constant situado en el menú Basic/Constant

Opcionalmente podemos dar un nombre a la constante. Pero en este ejemplo no lo necesitamos. Así que pinchamos directament en OK dejando el nombre en blanco

Colocamos el bloque en el espacio que hay encima del bloque constante

Y los conectamos. Este es el circuito que queda:

Por último introducimos el número que queremos asignar a la constante. Por ejemplo el 170

En esta animación se muestra el proceso:

Si cargamos el circuito, veremos el número 170 en binario en los leds, que es 10101010

Constante en Binario y Hexadecimal

El número 170 lo podemos expresar en diferentes bases. Típicamente usamos binario o hexadecima. Esta es la nomenclatura usada

  • 170 en Binario: 8'b10101010
  • 170 en Hexadecimal: 8'hAA

En esta imagen se ven los valores introducidos en las constantes:

Reto 11: Separando y juntando buses

Los cables que van por los buses se pueden extrar o inyectar. Esto lo hacemos con los bloques Split y Join de la colección iceWires

Separando la constante de 8 bits en 2 de 4 bits

Partimos de una constante de 8 bits, inicializada por ejemplo con el valor 0xAA, y la vamos a separar en dos BUSES de 4 bits. Colocamos el bloque Split-half para dividirlo en dos bloques iguales, cada uno de 4 bits

En esta animación lo vemos en acción:

Añadimos dos salidas de bus de 4 bits y lo cableamos todo

Cargamos el circuito. El resultado es el mismo que en el ejemplo anterior: En los LEDs vemos el patrón 🔴⚫🔴⚫🔴⚫🔴⚫

Juntar dos buses de 4 bits en uno de 8 bits

Ahora vamos a realizar la operación opuesta: juntamos los dos buses de 4-bits en uno de 8-bits. Utilizamos el bloque Join-half

En esta animación lo vemos en funcionamiento:

Tiramos los cables y ponemos una salida de bus de 8 bits para conectar a los 8 LEDs:

Animaciones en los LEDs

Para practicar y aprender más del pensamiento hardware, vamos a mover LEDs, como si fuesen elementos de un videojuego retro, pero hecho exclusivamente con Hardware

En los videojuegos tenemos una pantalla con muchos píxeles. Nosotros usaremos una que tiene sólo 8 píxeles (Los 8 LEDs). Uno de esos píxeles será nuestro personaje, que se moverá por este universo de 8 pixeles

Para realizar estos ejemplos necesitas instalar las siguientes colecciones:

Encendiendo un pixel

Empezamos encendiendo un único pixel, correspondiente a nuestro personaje. En nuestro universo, de momento, sólo puede estar nuestro personaje, que tiene el tamaño de 1 pixel

El componente que usamos para mostrar al personaje en la pantalla es un Demultiplexor de 1 a 8. Tiene una entrada de datos de 1 bit por donde introducimos el bit a mostrar en los LEDs. Tiene una entrada de selección de 3 bits, donde indicamos el canal de salida del dato (7-0). Si lo sacamos por el canal 0, se encenderá el pixel 0. Si lo sacamos por el canal 7, se encenderá el pixel 7. El resto de salidas permanecen a 0. Esto nos garantiza que únicamente hay un pixel encendido

Como queremos que el personaje esté siempre encendido, ponemos un bit constante a 1 en la entrada. Para indicar el pixel donde queremos que se dibuje usamos una constante de 3 bits. Cambiando el parámetro pixel modificamos su posición. Este es el circuito:

(videojuego-01-pixel-constante.ice)

Al sintetizar el circuito y probarlo en la placa, veremos como sólo se enciende el LED0 (El personaje está en el pixel 0). Si cambiamos el parámetro y cargas el circuito, veremos cómo cambia a la nueva posición

Moviendo el pixel con el pulsador

Ya tenemos a nuestro personaje en una posición fija. ¿Cómo hacemos ahora que se mueva a otras posiciones? Tenemos que modificar su posición y hacer que se muestre en la "pantalla". Utilizaremos un contador de 3 bits que se incremente manualmente cada vez que apretamos el botón SW1

(videojuego-02-pixel-movil-boton.ice)

Inicialmente el contador vale 0, por lo que nuestro personaje está en el Pixel 0. Al apretar le botón, se genera un pulso (tic) que hace que el contador se incremente en una unidad, pasando a valer 1. Esto hace que se encienda el pixel 1 y que se mantengan apagados el resto. Con cada pulsación el personaje pasará el siguiente pixel. Cuando llega al último (7) volverá al del principio (0). En esta animación vemos el funcionamiento:

V3: Pixel movil

Queremos que el pixel se mueve con un movimiento rectilineo uniforme.

  • Queremos que el pixel se mueva sólo, con movimiento rectilineo uniforme. Para ello hay que hacer que el contador de 3 bits, que define la posición de la partícula se incremente periódicamente. Utilizamos un Corazón. Es un bloque que está en la colección IceHeart que genera un pulso (tic) con la frecuencia que le indiquemos. Como queremos que el pixel se mueva despacio, configuramos su frecuencia para que sea de 1 Hz. De esta forma el contador se actualiza cada segundo, lo que hace que la partícula avance cada segundo

(videojuego-03-pixel-movil-corazon.ice)

En esta animación se muestra el funcionamiento:

V4: Construye tu propio contador

Con la versión actual la partículo sólo puede moverse en un sentido, ya que el contador sólo se incrementa una unidad. Si queremos que se mueva en el sentido contrario, tenemos que restar una unidad. Es decir, hay que crear nuestro propio contador personalizado. ¿Cómo hacemos eso?

Construir un contador es muy sencillo. Basta con incluir un registro, que almacena la posición actual, y un sumado para calcular la siguiente posición. El número que usamos para sumar es la velociad, y ahora puede variar. Si es 1 se mueve en un sentido y si es -1 hacia el otro. Basta cambiar esa constante para cambiar el sentido del movimiento

(videojuego-04-mi-propio-contador.ice)

Utilizamos un corazón configurado a 6 Hz (por ejemplo) para actualizar la posición actual del píxel. En todo momento la posición actual está en el registro, y la siguiente está esperando a su entrada. Cuando llega el pulso del corazón, el registro se actualiza a la posición siguiente y se calcula de nuevo la siguiente, que queda a la espera de ser calculada en el siguiente pulso

V5: Controlando el movimiento con un pulsador

Ahora que podemos cambiar el sentido de desplazamiento del pixel, vamos a hacer que ocurra al apretar un pulsador. Si el pulsador no está apretado, el pixel se mueve en un sentido. Si dejamos apretado el pulsador SW1, el pixel se mueve en el sentido contrario

Colocamos un Multiplexor de 2-1 para seleccionar qué velocidad, -1 ó 1, sumar a la posición actual. Si el pulsador NO está apretado (0), se usa el 1,pero al apretar el pulsasor (1) se usa el 1

(videojuego-05-pixel-adelante-atras.ice)

V6: Movimiento adelante/atrás con teclas

Ahora queremos que la partícula esté parada, en estado normal. Al apretar un pulsador que se mueve hacia adelante, y al apretar el otro hacia atrás. Ahora tenemos 3 valores que para la velocidad: 0 (parado), 1 (Adelante) y -1 atrás. Según el que apliquemos para sumárselo a la posición actual conseguimos los diferentes movimientos

Para lograrlo encadenamos dos multiplexores 2-1

(videojuego-06-pixel-adelante-atras-teclas.ice)

Pixel Gravedad

Este es un ejemplo más avanzado en el que el pixel "Salta" y cae por gravedad. Ahora la velocidad no es constante, sino que se actualiza en cada tic de update

Se utiliza un autómata de dos estados para controlar la partícula. Un estado es para indicar que la partícula está en reposo y el otro en movimiento. Para transitar de uno a otro se analizan dos transiciones. La transición de arranque se produce cuando se está en el estado inicial y se aprieta el pulsador. La transición del movimiento al modo reposo se produce si estamos en el estado de movimiento, la posición actual es 0 y la velocidad es negativa

(Pixel-gravedad.ice)

Retos para hacer

Se recopilan aquí todos los ejercicios para practicar

  • Reto 2-1: Haz un circuito para encender 4 LEDs
  • Reto 2-2: Haz un circuito para encender 8 LEDs
  • Reto 5-1: Haz un circuito para encender los LEDs pares con un pulsador y los impares con el otro. Utiliza las etiquetas Boton1 y Boton2
  • Reto 6-1: Circuito para encender un LED al apretar un pulsador. En paralelo debe haber otro pulsador conectado a un LED a través de una puerta NOT
  • Reto 7-1: Con un único pulsador haz que se enciendan los LEDs pares al apretarlo, y los impares al soltarlo

Autores

Licencia

Créditos

Enlaces

Clone this wiki locally