domingo, 13 de abril de 2014

Tutorial FPGA en VHDL: Parte 1, comparador de 1 bit

Durante los últimos 3 años he "coleccionado" placas de desarrollo de FPGAs con el objetivo de facilitar los proyectos que realizaba en la universidad.  Hasta el momento tengo las siguientes:

1) Spartan 3AN : Una placa muy interesante, pues permite grabar el programa en la memoria interna del FPGA y no en la memoria externa.

2) Spartan 3E Starter Board : Excelente placa de desarrollo, pues entre otras cosas se la puede programar con LabView.

3) Basys 2: La primera placa que adquirí, diseñada para aprendizaje

4) Elbert: quizá la placa más económica que se pueda conseguir ($34.95 incluido envío al Ecuador).  Esta placa me la regalaron los señores de NumatoLab.

5) Saturn: Este pequeño monstruo es la placa de FPGA más potente que tengo, de igual manera me la regalaron los señores de NumatoLab.

Tanto las placas 1,2 y 3 son de código cerrado, pues aunque existen los esquemáticos que proporcionan los fabricantes, el código del microcontrolador que programa el FPGA no está disponible, en cuanto a las placas 4 y 5 son completamente de código abierto, pudiendo encontrar el código de programación del microcontrolador y el PCB en KiCad.

Pues bien, me di cuenta que durante estos años he utilizado dichas placas para proyectos específicos (sobre todo basados en control de motores) mediante la aplicación de IPcores descargados para el efecto (una buena página para obtener diseños o librerías es OpenCores), pero nunca aprendí los conceptos básicos de programación, así que lo intentaré con una serie de tutoriales pensados para la implementación de circuitos lógicos básicos, microcontroladores por software y generadores de señales VGA.

No entraré en detalles sobre el funcionamiento del FPGA, pues eso lo pueden encontrar en la web, lo que intentaré explicar en estos tutoriales es cómo programar el FPGA, cómo hacer una simulación, y cómo hacer una placa de circuito impreso para implementar estas soluciones en proyectos DIY.

El presente tutorial se basará en el aprendizaje de VHDL, no está pensado en el aprendizaje de Verilog.  ¿Por qué?, pues porque según lo que he investigado en estos años, aprender primero Verilog y luego VHDL es similar a aprender primero Basic y luego Assembler para programar un microcontrolador, es decir, si bien el primero es fácil de aprender y entender, para aplicaciones avanzadas siempre nos quedará un vacío en cuanto al manejo de hardware y nos tocará aprender un nuevo lenguaje más complicado (si me equivoco, háganmelo saber y agregaré al tutorial el aprendizaje de Verilog), mientras que si empezamos por lo más complejo, el aprendizaje de un lenguaje más simple se nos hará fácil. 

Además intentaré siempre cambiar entre placas de FPGA, es decir, un tutorial será con el Elbert, otro con el Saturn, otro con la Basys 2, y así.  Esto pues aprender un lenguaje debe ser de aplicación universal y no orientado a una plataforma específica.  Es similar a aprender C para microcontroladores en general, o aprender MikroC para PIC, si bien MikroC es fácil de aprender, cuando queramos cambiar de plataforma y programar un AVR en C, se nos hará complicado.

Por último (lo cual contradice al párrafo anterior), trabajaré con FPGAs de Xilinx, esto pues todas las placas que tengo son de ese fabricante.  Espero algún día tener una placa de Altera para hacer las comparaciones respectivas.

Nota:  Algunos ejemplos tratados se tomarán del libro de Pong P. Chu "FPGA Prototyping by VHDL Examples

Programa a realizar

Pues bien, para empezar con este tutorial, como se mencionó anteriormente, se implementará un comparador de 1 bit, el cual funciona de la siguiente manera (tabla 1):


Tabla 1.  Tabla de verdad del comparador de 1 bit


Es decir, cuando los bits de ingreso sean iguales, la salida nos dará un 1 lógico (XNOR).  Ahora, nos pondremos como requisito construir el código en VHDL utilizando compuertas AND y NOT.  Esta solución sería la siguiente (figura 2):

Figura 2.  Comparador de 1 bit con compuertas AND y NOT


Plataforma de desarrollo/FPGA

Una vez que tenemos la solución lógica a implementar, seleccionaremos la plataforma de desarrollo en la cual implementaremos dicha solución.  Para este tutorial he seleccionado la plataforma Elbert (figura 3).

Figura 3.  Placa de desarrollo Elbert

La placa Elbert tiene 8 leds de salida, 4 pulsantes, 8 dip switch, y 16 puertos de propósito general.  En este caso utilizaremos 2 pulsantes como entradas y un led como salida.

Plataforma de programación

Para programar un FPGA de Xilinx requeriremos el software ISE, el cual se lo puede descargar de manera gratuita (figura 4).  

Figura 4.  Entorno de desarrollo ISE

Una vez abierto el ISE, seleccionamos File>New Project, y se nos abrirá la pantalla de la figura, en la cual ingresaremos el nombre del proyecto, la ubicación del mismo, y el lenguaje que queremos utilizar.  El lenguaje a seleccionar será HDL (figura 5).

Figura 5.  Nuevo proyecto

Al hacer click en "siguiente" en la pantalla de la figura 6, seleccionamos la configuración del proyecto.  La información que tenemos que llenar es: La familia del dispositivo (Spartan 3, 3A, 3AN, 3E, etc.), el dispositivo en si, el empaquetado, la velocidad del dispositivo, y el lenguaje de programación.  Esta información la encontramos escrita en el circuito integrado (figura 7).

Figura 6.  Configuración de proyecto

Figura 7. Identificación del tipo de FPGA utilizado

Para el caso del Elbert, el dispositivo es un XC3S50A, empaquetado VQG100, con grado de velocidad 4.  Hacemos click en "siguiente" y se nos mostrará el sumario del proyecto (figura 8)


Figura 8.  Sumario del proyecto creado

Una vez terminado este proceso, debemos crear el fichero donde haremos la programación en VHDL, para esto hacemos click derecho en el dispositivo creado (figura 9)

Figura 9.  Agregar un fichero al proyecto

Nos aparecerá el asistente para la creación de fuentes (figura 10), seleccionamos un módulo VHDL, colocamos el nombre del archivo y su destino.

Figura 10.  Crear un módulo VHDL

En la siguiente pantalla (figura) podemos definir los puertos de entrada y salida para el módulo creado, en este caso están declarados "LED" como salida, y "SW0" y SW1" como entrada (figura 11). Esto se puede modificar en la programación, por lo que es un paso que se puede obviar.

Figura 11.  Declaración opcional de pines de entrada y salida

Al terminar, se nos creará una plantilla en VHDL (figura 12) en la cual ya se encuentran declarados los ingresos y salidas del módulo, y cargadas las librerías requeridas.

Figura 12.  Plantilla VHDL creada para el proyecto

Programación en VHDL

El programa en VHDL que permitirá el funcionamiento del esquema lógico propuesto es el siguiente:


Ahora analicemos el mismo para comprender cada parte.  El código implementa el circuito de la figura 13.  Se utilizan dos señales intermedias "p0" y "p1" para realizar la lógica requerida.

Figura 13.  Señales externas e internas del módulo.  Fuente: libro de Chu

La primera parte del código " library ieee; use IEEE.STD_LOGIC_1164.ALL" carga el paquete STD_LOGIC de la librería IEEE para el manejo de operaciones lógicas y funciones.

La segunda parte "entity" declara la entidad utilizada en VHDL.  Una entidad define un elemento en VHDL  en función de sus entradas y salidas.  En este elemento se declaran dos puertos: A y B como entradas lógicas, y un puerto eq como salida lógica.  El tipo seleccionado "std_logic" nos indica que es una entrada o salida lógica (0, 1, o Z para alta impedancia), además se puede declarar como "U" o "Z" en el caso que no se tenga un valor de inicio.  En el caso de utilizar un byte como puerto, cada variable se debe inicializar como "std_logic_vector".

La tercera parte "architecture" hace referencia a la operación en sí del circuito.  Para este caso se utiliza la arquitectura "sop_arch" la cual nos permite hacer una suma de productos lógicos.  Existen otras arquitecturas como el "behavorial" en la cual se indica el comportamiento del circuito.  Dentro de la arquitectura se definen las señales internas (p0 y p1 como std_logic), las cuales se utilizan únicamente dentro de la arquitectura pero no tienen salida al hardware.  La descripción del circuito se la realiza entre el "begin" y "end", dentro del cual se hacen las operaciones lógicas requeridas.  Para este ejemplo primero cargamos el OR entre p0 y p1 en eq (resultado final), obtenemos p0 como el negado de A por el negado de B (not A and not B), y p1 como el producto de A y B ( A and B).

Conexión con el hardware del FPGA

Hasta ahora tenemos realizado el software que irá en el FPGA, pero aún no le indicamos al compilador a qué pines están conectadas las entradas y salidas del módulo creado.  Para esto necesitamos conocer la placa de desarrollo en la que estamos trabajando, esto se puede saber de varias maneras:


  1. Si la placa es un diseño propio, pues sabemos qué pines están conectados a cada puerto del FPGA
  2. Conociendo el esquemático del fabricante
  3. (la manera más fácil) descargándonos el archivo UCF (User Constraints File) del fabricante de la placa
En el archivo UCF se encuentran descritas las conexiones entre el FPGA y los periféricos instalados (en nuestro caso LEDs y pulsantes).  Para el Elbert el archivo UCF se lo puede descargar aquí, el cual lo modificaremos para que las señales de entrada (A y B) y de salida (eq) coincidan con los pines del FPGA de la siguiente manera:

1) Creamos un documento nuevo y seleccionafor UCF con el nombre requerido (figura 14)

Figura 14.  Creación del archivo ucf

2) Modificamos el archivo ucf descargado y lo dejamos de la siguiente manera:


          # Onboard LEDs
          NET "eq" LOC = P3;

          # Push Button Switches.
          # Internall pull-ups need to be enabled since 
          # there is no pull-up resistor available on board
          NET "A" PULLUP;
          NET "B" PULLUP;

         NET "A" LOC = P7;
         NET "B" LOC = P15;

Este código nos indica que la señal "eq" está localizada en el pin P3 del FPGA, la señal "A" en el pin P7, y la señal "B" en el pin 15.  Las declaraciones de PULLUP indican que se deben activar las resistencias internas del puerto de salida del FPGA, esto pues en el Elbert los pulsantes no tienen resistencias externas.

Compilación y descarga del archivo de configuración al FPGA

Para la compilación del programa, se guardan los cambios y se hace click en el símbolo de compilación mostrado en la figura 15.  En el caso de que no existan problemas en la programación, la compilación nos mostrará el mensaje "Process "Generate Post-Place & Route Static Timing" completed successfully"

Figura 15.  Implementación/compilación

Para la descaga del archivo de configuración, el procedimiento depende de cada fabricante.  En el caso del Spartan 3AN y el 3E Starter Board, son reconocidas directamente por el ISE, pero en el caso del Elbert la descarga del archivo de configuración debe realizarse mediante el "Configuration Downloader" de NumatoLab (Figura 16).  Este aplicativo nos permite seleccionar el puerto de comunicación al cual se conecta el dispositivo (para el cual necesitamos un driver usb), y nos pide cargar un archivo .bin.

Figura 16.  Elbert Configuration Tool

Para generar el archivo .bin, hacemos click derecho en "Generate Programming File" y seleccionamos "Process Properties" (figura 17)

Figura 17.  Propiedades del archivo de programación

En la ventana que nos aparece, seleccionamos la generación del archivo de configuración binario (figura 18)

Figura 18.  Creación del archivo binario

Aplicamos, aceptamos, y damos doble click en "Generate Programming File".  Si todo está bien, se generará el archivo .bin del proyecto, el cual podemos cargarlo utilizando la aplicación de Numato (figura 19).

Figura 19.  Descarga del archivo binario al FPGA

Eso es todo, si la configuración se realizó correctamente, podremos ver el LED0 del Elbert encenderse, y cumplir con la programación realizada.