En las entregas anteriores de The FPGA Chronicles, Hackaday usó la Tang Nano 20K con las herramientas de GOWIN y con algunas herramientas de código abierto. Las abiertas tenían la ventaja de permitir simular y depurar el diseño antes de enviarlo al chip. Las de GOWIN, en cambio, incluyen un analizador lógico muy fácil de usar y con buenas capacidades, que es el tema de la tercera entrega, publicada el 13 de octubre de 2026.

Configuración del analizador GAO

Primero se necesita un archivo de configuración GAO. Al crearlo en el editor aparecen varias opciones complejas. Con un archivo GAO activo, la herramienta integra un analizador lógico al diseño, y el archivo define las condiciones de disparo.

Un consejo del autor. Solo puede haber un archivo GAO activo, y a veces no se quiere el analizador o se quiere una configuración alternativa. Si se hace clic derecho sobre casi cualquier archivo en la pestaña Design del IDE, aparece la opción de deshabilitarlo. El archivo queda marcado con una X roja y se excluye de las compilaciones siguientes, hasta que se vuelva a habilitar. Así se puede elegir entre varios archivos GAO, o incluso entre varios archivos Verilog, para una compilación concreta (por ejemplo, uno para pruebas y otro para producción).

Al crear un archivo GAO desde cero, la herramienta pregunta si se quiere el "Lite Mode". Según el autor, nunca conviene, y hay que usar siempre el Standard Mode. Casi siempre conviene también el modo RTL, que muestra (en su mayoría) los símbolos definidos por uno y no los que se agregan durante el procesamiento.

El manual completo está en línea, aunque el video del artículo original y el resto de la entrega bastan para partir rápido.

Lo básico de GAO

Para configurar el sondeo hay que entender el diseño, así que el resto de la entrega revisa el Verilog con que se prueba el analizador, y el video muestra cómo funciona. Las capturas de adelanto del artículo muestran dos cosas. A la izquierda, el pulso que invierte los LED, difícil de capturar porque el reloj de muestreo es de 27 MHz. A la derecha, datos seriales capturados con un reloj más lento dedicado a la depuración.

Dentro del Verilog

Verilog puede ser peligroso. Para quien ya sabe programar, se parece lo suficiente a un lenguaje de programación como para invitar a pensarlo de la forma equivocada. Verilog no es código. Describe cómo se quiere que se comporte el hardware, y una parte central de eso son los relojes.

La mayoría de los sistemas digitales prácticos tienen algún tipo de reloj que hace funcionar las cosas. Cosas muy simples, como el ejemplo de la entrega anterior, pueden no necesitarlo, pero los sistemas prácticos casi siempre lo usan. En teoría se puede hacer lógica asíncrona, pero es muy difícil. La lógica síncrona, con un reloj que controla todo, es mucho más común.

¿Para qué sirven los relojes?

Solemos pensar que las señales cambian al instante, pero no es así. Cambian en momentos levemente distintos por muchas razones, como el largo de los cables, la capacitancia, diferencias en la fabricación de los componentes, la temperatura y probablemente otras.

El autor propone un ejemplo exagerado. Un auto muy seguro apaga el motor si detecta que está en marcha y que la puerta está abierta. Eso es una compuerta AND. Para exagerar, supongamos que las señales de la puerta y del motor tardan un tiempo aleatorio de entre 100 y 900 milisegundos en llegar a la compuerta.

En general no importaría mucho. Uno cierra la puerta, revisa el espejo, pone su lista de música y arranca el auto. Pero si uno está apurado, se sube y arranca de inmediato, la señal de arranque puede llegar antes de que se apague la señal de puerta abierta. El motor se apaga justo después de encenderlo.

El ejemplo es inventado, pero el problema es real. Supongamos que se quieren sumar dos números. Uno cambia de 11 a 22 y el otro de 5 a 10. Se esperan como resultados 16 y 32, pero en cambio aparecen 16, 27 y después 32. O 16, 21 y después 32.

Los relojes, en esencia, dan tiempo a que todo se estabilice antes de calcular la salida, y esas salidas se mantienen estables hasta el siguiente pulso de reloj. Es una simplificación enorme, que la serie retomará más adelante. Cada elemento del diseño toma algún tiempo, y en general el más lento fija la frecuencia máxima del reloj. Si una parte del diseño tarda un microsegundo en cambiar de forma confiable, el período del reloj tiene que ser más largo que eso.

El diseño de ejemplo

El archivo de ejemplo es Verilog normal, aunque SystemVerilog (archivos .sv) tiene ventajas que el autor va señalando. Es un diseño muy simple que maneja algunos LED de la placa 20K. El reloj de entrada es de 27 MHz, así que para que los LED parpadeen hay que contar muchos ciclos de reloj y cambiar los LED solo de vez en cuando.

Además usa IP, bloques predefinidos, para armar una UART. La función es simple y artificial. Por defecto, cada carácter que entra por la UART se devuelve tal cual. Si se presiona el botón de usuario de la placa, pasa a un modo que le suma uno al carácter antes de reenviarlo. Al presionarlo de nuevo vuelve al eco directo. En ambos casos, al presionar el botón el dispositivo envía un asterisco.

Desde arriba

Al comienzo del archivo aparece esta línea:

Código
default nettype none

Es buena idea, porque sin ella Verilog crea redes nuevas a partir de errores de tipeo sin quejarse. Por ejemplo:

Código
countt=count+1;

Ahí count no cambia, pero se asigna un cable nuevo, countt, que nunca se usa. El único problema es que parte de la IP de GOWIN espera que el tipo de red por defecto sea wire, así que al final de los archivos hay que restablecerlo:

Código
default nettype wire

Otra rareza es que algunas herramientas entienden que una entrada o salida declarada es un wire salvo que se diga otra cosa. Las herramientas de GOWIN se niegan a hacerlo si el tipo por defecto es none.

Diseño modular

Lo siguiente es el módulo led. En teoría es una pieza reutilizable, pero también es el bloque superior del diseño. La sintaxis #() permite fijar parámetros que se pueden cambiar al crear el módulo, además de sus valores por defecto. En el banco de pruebas anterior, por ejemplo, el autor escribió:

Código
led #(.CLOCK_FREQUENCY(10, .INTERVAL_MS(1000)) uut(.reset(reset), .Clock(clk)...

Eso fija un reloj y un intervalo lentos para la simulación, en vez de usar los valores por defecto. No todos los módulos tienen parámetros. Después viene lo que parece una lista de argumentos de entradas y salidas. Son los puertos que se usan cuando este no es el módulo superior. Como aquí sí lo es, hacen falta restricciones para asignarlos a pines concretos del FPGA.

Las sentencias localparam son como los #define de C. Se usan para fijar el valor que debe alcanzar el contador y calcular cuántos bits necesita ($clog2 entrega el logaritmo en base 2 al momento de compilar).

La mayor parte del tiempo se trabaja con wire. Algo tiene que manejarlos, y son ideales para conectar cosas. También están los reg. Uno podría pensar que son flip-flops, pero no es exactamente así. Son, por decirlo de algún modo, las variables de Verilog.

Es fácil pensar que los wire y los reg valen 1 o 0, pero Verilog también admite valores como X (desconocido) y Z (algo parecido a un colector abierto). SystemVerilog incluso permite escribir logic en vez de wire o reg, un tema que la serie tratará pronto. Pero hay tanto Verilog "puro" dando vueltas que conviene entenderlo aunque se piense usar SystemVerilog en los proyectos propios.

Código
reg [COUNT_BITS-1:0] interval_count;
El generador de IP ofrece muchas opciones y enlaces a la documentación.

Esa línea declara un reg llamado interval_count, con bits que van desde el 0 (el menos significativo) hasta COUNT_BITS-1. El orden se puede invertir si tiene sentido para el diseño, o se puede partir en el bit 1, por ejemplo. No es un arreglo, aunque Verilog sí los soporta. Es una variable de varios bits. Si no se indican los bits, como en la definición de interval_tick que viene después, la variable es de un solo bit.

El bloque siguiente define las variables reg y wire que se conectan a la UART. Por ahora no se construye una UART propia. Se usa la IP que entrega GOWIN. El inconveniente es que está pensada para conectarse a un microprocesador, así que el autor hace un poco de trampa para que sea fácil de usar. La IP se inserta en un proyecto desde el menú Tools, en IP Core Generator.

La última sección de preparación maneja la entrada del botón y el estado de los LED. Los botones no están sincronizados con el reloj, y eso puede causar un problema serio. Además, los interruptores mecánicos rebotan. No pasan de apagado a encendido de una vez, sino que alternan varias veces entre apagado y encendido y, después de unos milisegundos, se quedan encendidos. El diseño incluye un módulo antirrebote para estos problemas, que por ahora se usa igual que la UART, aunque vive en debounce.v.

Instanciación

Así se crea un módulo antirrebote. En teoría se pueden crear todos los que se quiera:

Código
debounce #(
  .CLOCK_FREQUENCY (CLOCK_FREQUENCY),
  .DEBOUNCE_MS (10)
   ) button_filter (
     .Clock (Clock),
      .reset (reset),
       .noisy_button (pushbtn),
       .clean_button (button)
  );

La instancia se llama button_filter, y los parámetros le indican la frecuencia del reloj y el tiempo de antirrebote, 10 milisegundos. Las conexiones son las líneas de reloj y reset, la entrada del botón y, como salida, una versión limpia del botón.

Asignaciones y la UART

Hay algunas líneas que empiezan con assign. Son los cautines de Verilog. Conectan la salida de un wire a una variable o expresión, y eso pasa a ser ese cable. Ya aparecieron en las demos anteriores que solo manejaban LED.

Después viene una instanciación mucho más compleja, la de la UART. Aunque tiene más entradas y salidas, en el fondo es lo mismo que conectar el módulo antirrebote. En algunos casos el autor fuerza datos fijos en un pin (por ejemplo 3'b001, una constante binaria de tres bits, o 8'hff, un FF hexadecimal de 8 bits). Algunas conexiones son wire o reg, y otras son expresiones como ~reset (el inverso de reset).

El resultado es simple. Cuando uart_rx_ready_n baja, se puede leer un carácter desde uart_rx_data. La transmisión tiene una configuración parecida.

El flip-flop

El resto del archivo tiene tres expresiones always. Con always se pueden hacer dos cosas (SystemVerilog, de hecho, las separa, como se verá más adelante). Por ahora la entrega se concentra en cómo escribir flip-flops con reloj usando always @.

La más simple es la última, la que invierte el estado del LED:

Código
always @(posedge Clock) begin
  if (reset)
    led_state <= 1'b0;
  else if (interval_tick)
   led_state <= ~led_state;
end

Esto crea un flip-flop que se dispara en el flanco de subida de Clock. Si la línea de reset está en alto, el flip-flop deja led_state en 0. Si no, lo cambia al inverso de lo que valía antes.

Hay que notar un par de cosas. Primero, led_state no se puede asignar en ningún otro lugar que no sea este bloque always. El segundo punto es más delicado. Las asignaciones usan <= y no =. Se podría escribir =, pero eso trae un problema. Por ejemplo, este código dentro de un bloque always:

Código
qout=3'b000;
if (b) quot=~quot;