En la entrega anterior de la serie, Hackaday mostró cómo partir con las herramientas de GOWIN y una FPGA Tang Nano 20K. El software de GOWIN funciona bien. Tiene algunas rarezas y su código es cerrado. Además, la simulación depende de un paquete de terceros que cambió después de una adquisición. Hay muchísimos simuladores gratuitos muy buenos, y existe una cadena de herramientas de código abierto para esta FPGA.

Se puede juntar todo pieza por pieza, pero no hace falta. Hay varios proyectos que arman una cadena completa a partir de las distintas piezas, y esta entrega se concentra en uno, APIO.

¿Qué es APIO?

APIO es menos una cadena de herramientas para FPGA que un agregador de cadenas de herramientas. Al autor le recordó a PlatformIO, y el propio proyecto dice que se inspiró en él. Actualiza las herramientas necesarias, incluye sus propias bibliotecas y ofrece un flujo de trabajo común para las FPGA que soporta.

Se puede descargar para la línea de comandos, pero también se instala como extensión de Visual Studio Code, que fue la opción del autor. Basta con crear un archivo simple que describe el proyecto.

Problemas de instalación

Como APIO trae sus propias bibliotecas, puede chocar con las del sistema. En el caso del autor, el archivo libreadline.so.8 (en ~/.apio/bin/_internal) provocaba problemas que impedían que funcionara cualquier cosa. Lo renombró para sacarlo del camino, aunque también se puede borrar. Con eso se resolvió.

APIO solo orquesta otras herramientas, como Yosys y GTKWave. Aunque uno tenga sus propias versiones instaladas, APIO espera usar sus copias privadas. Por ejemplo, la versión de GTKWave del sistema del autor es distinta de la de APIO, y si intenta leer archivos de ondas fuera de APIO recibe mensajes de error. Sí se puede abrir una terminal desde el menú Tools/Misc del panel de APIO en Visual Studio Code.

El archivo de proyecto de APIO

Para probar APIO, el autor hizo un diseño simple que hace parpadear un LED. El Verilog que lleva adentro queda para la próxima entrega, y por ahora se puede tratar como una caja negra. Cada proyecto de APIO necesita un archivo apio.ini. Este es el suyo:

Código
[env:default]
board = sipeed-tang-nano-20k
top-module=led

Eso es todo. Por supuesto, se puede hacer más, aunque pocas veces hace falta. Más abajo aparece la versión un poco más completa que el autor suele usar.

Para iniciar un proyecto existe el comando apio create, con opciones para fijar la placa, el módulo superior y la ruta. Pero no parece haber una forma fácil de correrlo desde Visual Studio Code. La mejor que encontró el autor es abrir el panel de APIO, hacer clic en Tools y luego en APIO Shell. Desde ahí se puede correr apio create con sus argumentos, y apio create -h muestra cuáles se pueden y cuáles se deben indicar.

Se gana algo y se pierde algo

Hay un grupo grande que preguntará por qué usar las herramientas de GOWIN si existe esto. Otro grupo pregunta para qué molestarse si las de GOWIN funcionan bien. Hay razones para elegir una u otra.

Las herramientas de código abierto son excelentes para Verilog. Con APIO se puede formatear el código fuente y revisarlo con un linter sofisticado. Después está la simulación, donde APIO tiene un muy buen controlador para simular diseños con herramientas abiertas.

Las herramientas de GOWIN están bien. El problema es que su simulación depende de un simulador que, al parecer, ya no está disponible en la forma en que lo usan. Eso puede cambiar, claro, pero existen herramientas abiertas muy buenas y APIO las deja a un clic.

Una planilla para crear restricciones de pines

Entonces, ¿por qué usar GOWIN? Si se quiere usar su IP (bloques funcionales que van desde algo tan simple como generar un reloj hasta algo tan complejo como una CPU), es difícil integrarla con APIO, aunque probablemente no imposible. GOWIN también permite agregar un analizador lógico al diseño para examinar lo que pasa en tiempo de ejecución, y eso tampoco se adapta con facilidad a APIO. Hay alternativas abiertas para hacer algo parecido, pero no son tan prolijas como las herramientas integradas.

También se pierde el editor gráfico de restricciones, el que permite definir qué pines corresponden a qué nombres de Verilog. Es un problema menor, porque los archivos de restricciones se pueden escribir a mano como texto. O se puede usar la planilla que armó el autor para facilitarlo, o ir a GitHub por una versión para Excel con pocas pruebas. Basta con guardar una copia propia y editarla. Para quien prefiere Visual Studio Code está esta extensión para editar restricciones.

Queda otra duda. ¿Qué herramienta de síntesis hace mejor trabajo? Es difícil decirlo sin probar, y aun así una puede ganar en un aspecto y perder en otro. ¿Se busca más velocidad, menos uso de recursos o acceso a todos los recursos especiales del chip? La única forma de saberlo es probar las dos. APIO no hace por sí mismo la síntesis del Verilog ni el place and route. Existen otros agregadores parecidos, pero suelen usar las mismas herramientas, así que elegir uno u otro no cambia mucho el resultado.

Un ejemplo con código abierto

Los archivos del ejemplo se pueden descargar. Por ahora el foco está en usar APIO y no en el detalle del Verilog. El diseño usa un reloj de 27 MHz. Hay varias formas de llevar un reloj así a la FPGA, pero para una FPGA los relojes son un tipo especial de señal, y la mayoría, incluida esta, tiene pines especiales que funcionan mejor como entrada de reloj.

Como esta vez no se mira dentro de la caja, esta es la parte central del módulo superior led:

Código
module led #(
    parameter integer CLOCK_FREQUENCY = 27_000_000,
    parameter integer INTERVAL_MS     = 500
) (
    input wire reset,
    input wire Clock,
    input wire serialin,
    input wire pushbtn,
    output wire [5:0] leds,
    output wire serialout
);

Así el módulo sabe qué frecuencia de reloj esperar y a qué ritmo parpadear. Tiene entradas para reset, Clock, serialin y pushbtn, y salidas para los LED y serialout. El puerto serie y los botones no se usan en esta entrega.

Programar el reloj

Al conectar la Nano 20K debería aparecer un puerto serie. En Linux, ls -l /dev/serial/by-id ayuda a identificarlo. Si se abre con el programa de terminal serie favorito, lo más probable es que no se vea nada, porque la FPGA no está enviando ni recibiendo datos.

El indicador de la Tang Nano 20K en una terminal serie

Ese puerto serie pasa por un microcontrolador, el mismo que graba la FPGA, y está siempre atento a una secuencia especial de caracteres, ^X^C [Enter]. Con ella debería aparecer el indicador "TangNano20K />". Si se sospecha que la versión es antigua, se puede actualizar con las instrucciones de Sipeed. Hay que elegir el firmware correcto de la tabla, porque hay versiones parecidas que se ven más nuevas pero no son compatibles.

La placa tiene varios relojes programables. Este ejemplo usa O0, que está en el pin 10 de la FPGA. Una vez en el indicador, se escribe:

Código
pll_clk O0=27M -s

Para revisar el estado basta con pll_clk sin argumentos. El ejemplo no usa la UART por ahora, pero si se usara, "choose uart" la devuelve al modo normal.

Con eso, se descarga el proyecto simple del LED, se abre Visual Studio Code y, si no se ha hecho antes, se instala la extensión de APIO desde el marketplace. Así se puede abrir el proyecto.

Desde el panel de APIO se abren los comandos "make" y se elige "build". Cuando eso funcione, "upload" envía el archivo a la memoria flash del dispositivo, que es la opción por defecto. APIO se comporta como Make, o sea que si se ejecuta upload después de cambiar el código fuente, vuelve a compilar.

En vez de grabar la flash cada vez, el autor quería programar la SRAM por defecto. APIO admite entornos, así que dejó su archivo ini así:

Código
[apio]
default-env = ram

[common]
board = sipeed-tang-nano-20k
top-module = led

[env:ram]
programmer-cmd = openFPGALoader -b tangnano20k ${BIN_FILE}

[env:flash]
programmer-cmd = openFPGALoader -b tangnano20k -f ${BIN_FILE}

Ahora la descarga va a la SRAM por defecto. El autor no encontró forma de que la extensión de Visual Studio Code cambie de entorno, así que para grabar la flash hay que abrir la terminal de APIO y correr:

Código
apio upload --env flash

Otro detalle menor. Al autor le gusta desactivar archivos que usa para simular o depurar, algo que el IDE de GOWIN permite. Por ejemplo, puede tener tres copias de un módulo con distintas salidas de depuración. Con APIO no hay una forma fácil de hacerlo. Lo mejor que encontró es renombrar archivos como foo.v y foo-test.v a foo.v.option y foo-test.v.option, y luego enlazar foo.v a cualquiera de los dos, algo fácil de automatizar con un script. También se pueden renombrar cada vez, que igual se automatiza sin problema.

Probando, probando

Conviene probar los diseños antes de cargarlos en el hardware real. Depurar suele ser mucho más fácil cuando se ve todo y se pueden preparar condiciones específicas para la prueba. Para eso se crea un testbench, un archivo Verilog que alimenta al Verilog "real" y genera una salida.

En un archivo Verilog normal hay que cuidar de hacer solo lo que el sintetizador puede implementar razonablemente en la FPGA. Si algo está mal, a veces se niega. Otras veces genera resultados pésimos que uno no quiere. En un testbench, en cambio, se pueden hacer muchas cosas, como retardos. El simulador se parece más a una simulación por software de ejecución en paralelo y es mucho más permisivo.

Este es un testbench simple, que la serie retomará en una entrega futura. Por ahora ya viene incluido en el proyecto.

Código
`timescale 1ms/1us
// each "tick" is worth 1ms so #100 == 0.1 second delay

`default_nettype none

module led_tb;
    reg clk;
    reg reset;
    wire [5:0] leds;
    wire serialout;
// create top block for testing
    led #(
        .CLOCK_FREQUENCY(10),  // but tell it to expect 10 Hz
        .INTERVAL_MS(1000)
    ) uut (
        .reset(reset),
        .Clock(clk),
        .serialin(1'b0),
        .pushbtn(1'b0),
        .leds(leds),
        .serialout(serialout)
    );

// generate simulation clock
    initial begin
        clk = 0;
        forever #50 clk = ~clk;
    end

// simulation "main"
    initial begin
        $dumpvars(0, led_tb);  // store variables
        reset = 1;             // generate a reset pulse
        #200;
        reset = 0;
        #50000                 // let simulation run for a bit
        $finish;               // exit
    end

endmodule

La idea básica es crear el módulo superior del diseño (led), pero configurado para correr a 10 Hz con un intervalo de un segundo. Se podría simular a 27 MHz, pero eso generaría archivos enormes y más difíciles de manejar. Al iniciar la simulación se genera un cambio de reloj cada 50 milisegundos, así que el período es de 100 milisegundos, o sea 10 Hz.