hola-mundo-fpga-blinky

Hola mundo en FPGA: Blinky con Verilog

  • 6 min

Un Blinky es el primer circuito mínimo que solemos hacer en una FPGA: un LED que parpadea usando el reloj del sistema y un contador.

En el mundo del software, el primer programa siempre es imprimir un texto en pantalla. En el mundo de la electrónica, nuestro “Hola Mundo” es hacer parpadear un LED. Lo llamamos cariñosamente Blinky.

Si vienes de Arduino, pensarás: “Bah, esto está chupado. Pongo un delay(1000) y listo”.

Bueno, tengo una mala noticia: en una FPGA no existe la función delay. De hecho, no existe el concepto de “esperar” como en un microcontrolador. El hardware siempre está funcionando, la corriente siempre fluye.

Hoy vamos a escribir nuestro primer módulo en Verilog y entenderemos cómo domar el tiempo en un circuito digital.

El problema del tiempo en hardware

Vamos a analizar por qué no podemos simplemente “esperar”.

En un procesador (Arduino), delay(1000) significa: “Señor procesador, quédese en este bucle sin hacer nada útil durante 1000 milisegundos”. Detienes el flujo de ejecución.

En una FPGA, tienes una señal de reloj (Clock) que está latiendo a una velocidad vertiginosa (por ejemplo, 12 MHz, es decir, 12 millones de veces por segundo). Cada vez que el reloj hace “tic”, todos tus Flip-Flops se actualizan. No puedes decirles que paren.

Si quieres que algo ocurra una vez por segundo, tienes que contar los tics.

Para hacer parpadear un LED a 1 Hz con un reloj de 12 MHz, necesitamos un circuito que cuente hasta 6 millones (encendido) y luego otros 6 millones (apagado).

Nuestro primer módulo Verilog

Vamos a describir este comportamiento. Usaremos un contador binario.

Imagina un contador que suma 1 en cada ciclo de reloj. Los bits más bajos cambiarán frenéticamente (0, 1, 0, 1…), pero los bits más altos cambiarán mucho más despacio. Si el contador es lo suficientemente grande, el bit más alto parpadeará a una velocidad visible para el ojo humano.

Aquí está el código completo para nuestro blinky.v:

// Definición del Módulo
module blinky (
    input wire clk,   // Entrada de Reloj (12 MHz en la IceStick/Alhambra)
    output wire led   // Salida hacia el LED
);

    // 1. Declaramos un registro (memoria) para contar
    // Un registro de 24 bits nos permite contar hasta 2^24 = ~16 millones
    reg [23:0] contador = 24'd0;

    // 2. Bloque de Lógica Secuencial
    // "Cada vez que la señal 'clk' tenga un flanco de subida (posedge)..."
    always @(posedge clk) begin
        contador <= contador + 1; // ...incrementamos el contador.
    end

    // 3. Lógica Combinacional (Salida)
    // Conectamos el cable 'led' al bit más significativo (el 23) del contador.
    assign led = contador[23];

endmodule
Copied!

¿Ves lo que hemos hecho? No hay bucles for ni while. Hay una descripción de estructura.

Analizando la sintaxis

Vamos a desglosar los elementos nuevos que han aparecido:

module y endmodule

Es la caja negra. Define las entradas y salidas de nuestro componente. Es equivalente a una “clase” en POO, pero en hardware.

reg [23:0] contador

Aquí estamos definiendo un bus de 24 cables que tienen memoria (Flip-Flops). La notación [23:0] indica que es un vector que va desde el bit 23 (el más alto) hasta el bit 0 (el más bajo).

always @(posedge clk)

Esta es la estructura más importante de Verilog. Define un bloque que se ejecuta siempre que ocurre un evento específico.

  • posedge clk: Significa Positive Edge (Flanco de Subida). Justo el instante en que el voltaje del reloj pasa de 0 a 1.
  • Todo lo que esté dentro de este bloque se convertirá en Flip-Flops en nuestra FPGA.

La asignación <=

Fíjate que hemos usado <= en lugar de =.

  • <= es una asignación no bloqueante. Se usa en lógica secuencial. Significa: “Calcula el valor ahora, pero no lo actualices hasta que termine el ciclo de reloj”.
  • Es importante para que todo el circuito cambie de estado al unísono.

assign led = contador[23]

Esto es asignación continua. Estamos “soldando” virtualmente un cable desde el bit 23 de nuestro registro contador hasta la patilla de salida del LED. Como el bit 23 es el que tarda más en cambiar (divide la frecuencia por 2^24), nuestro LED parpadeará lentamente. La inicialización del contador evita que la simulación comience en X; si buscas portabilidad entre familias, utiliza una entrada de reset explícita.

Matemáticas del parpadeo

Si nuestro reloj va a 12 MHz, el contador avanza 12 millones de veces por segundo.

El bit 0 cambia a 6 MHz. El bit 1 cambia a 3 MHz. … El bit n cambia a una frecuencia de:

f_bit = f_clk / 2^(n+1)
Copied!

Para el bit 23:

f_bit23 = 12.000.000 / 2^24 = 0,715 Hz
Copied!

¡Perfecto! Parpadeará un poco más lento que una vez por segundo, lo bastante despacio como para verlo a simple vista.

Comparativa: Arduino vs FPGA

Para que veas el cambio de mentalidad, mira cómo se consigue el mismo efecto en ambos mundos:

ConceptoArduino (C++)FPGA (Verilog)
FilosofíaInstrucciones SecuencialesFlujo de Datos Paralelo
Tiempodelay(ms) (Bloqueante)Contar ciclos de reloj (No bloqueante)
EjecuciónLa CPU se “duerme” en el delayEl contador trabaja, el resto del chip sigue libre
RecursosCiclos de CPUFlip-Flops y Sumadores

Lo maravilloso de la FPGA es que, aunque este contador esté funcionando, tienes el resto del chip 100% libre. Podrías añadir 50 contadores más, o un procesador de vídeo, y el LED seguiría parpadeando exactamente igual, sin verse afectado por el resto del código.

Cómo construir y probar el proyecto

Si has instalado Apio como vimos en el artículo anterior, el proceso para ver esto en tu placa es trivial:

  1. Guarda el código como blinky.v.
  2. Crea un archivo apio.ini indicando tu placa (ej: board = icezum).
  3. Ejecuta apio build (Síntesis).
  4. Ejecuta apio upload (Carga).

¡Y listo! Deberías ver tu LED parpadeando. Acabas de diseñar tu primer hardware digital. Ya no eres solo un programador, eres un arquitecto de hardware.