Un contador es un registro que cambia siguiendo una secuencia numérica en cada evento de reloj.
Saber hacer contar a una FPGA es fundamental. Es la base para:
- Crear temporizadores (como el
millis()de Arduino). - Generar señales PWM.
- Bajar la frecuencia del reloj (Prescalers).
- Recorrer direcciones de memoria.
Hoy vamos a aprender a dominar el tiempo.
Anatomía de un contador
Un contador no es más que un registro que se suma a sí mismo. Imagina un circuito con un registro (memoria) y un sumador.
En cada flanco de reloj, el registro lee su propio valor actual.
Le suma 1.
Guarda el nuevo resultado.
module contador_simple (
input wire clk,
output wire [3:0] leds
);
reg [3:0] cuenta = 0; // Inicializamos a 0
always @(posedge clk) begin
cuenta <= cuenta + 1; // Sumamos uno en cada ciclo
end
assign leds = cuenta;
endmoduleEl Desbordamiento (Overflow) es tu amigo.
Cuando un contador de 4 bits llega a 15 (1111) y le sumas 1, automáticamente vuelve a 0 (0000). No da error, simplemente da la vuelta. Esto es ideal para crear ciclos repetitivos.
El divisor de frecuencia (prescaler)
El reloj de nuestra FPGA suele ir muy rápido (12 MHz, 50 MHz, 100 MHz). A veces necesitamos que las cosas ocurran más despacio (por ejemplo, parpadear un LED a 1 Hz o leer un sensor a 100 kHz).
Para esto usamos un Prescaler. Básicamente, contamos pulsos del reloj rápido hasta llegar a un número tope, y entonces hacemos “algo”.
Existen dos formas de hacerlo: la fea y la menos fea.
Relojes derivados mediante lógica
Los principiantes suelen intentar crear una señal de reloj nueva y usarla en otro bloque always.
// ⛔ PRÁCTICA NO RECOMENDADA
reg reloj_lento = 0;
always @(posedge clk) begin
if (contador == LIMITE) begin
reloj_lento <= ~reloj_lento; // Invertimos
contador <= 0;
end
end
// Usar este reloj generado en otro sitio
always @(posedge reloj_lento) begin ... end ¿Por qué está mal? Las FPGAs tienen “autopistas” especiales dedicadas exclusivamente a la señal de reloj principal (Clock Tree) para garantizar que llega a todos los rincones del chip a la vez. Si creas un reloj con lógica normal (flip-flops), la señal viajará por caminos normales, tendrá retardos (skew) y tu diseño se volverá inestable.
Pulsos de clock enable (strobe)
En lugar de crear un reloj nuevo, creamos un pulso de habilitación (Strobe) que dura exactamente un ciclo de reloj. Todo el sistema sigue funcionando a la velocidad máxima (ej: 12 MHz), pero nuestro circuito solo se “activa” cuando recibe ese pulso.
// ✅ PRÁCTICA RECOMENDADA
reg pulso_1hz = 0;
always @(posedge clk) begin
// Valor por defecto (para que solo dure 1 ciclo)
pulso_1hz <= 0;
if (contador == LIMITE) begin
contador <= 0;
pulso_1hz <= 1; // ¡Disparo!
end else begin
contador <= contador + 1;
end
end
// En el otro módulo, seguimos usando clk, pero filtramos con el if
always @(posedge clk) begin
if (pulso_1hz) begin
// Esto se ejecuta solo 1 vez por segundo
led <= ~led;
end
endCómo calcular el límite
Para saber hasta cuánto tenemos que contar, la fórmula es trivial:
Ejemplo:
- Placa: IceStick / Alhambra (Reloj de 12 MHz = 12,000,000 Hz).
- Objetivo: Cambiar el estado del LED una vez por segundo (un ciclo completo cada dos segundos, es decir, 0,5 Hz).
Necesitamos un registro capaz de almacenar el número 12 millones.
2^23 = 8.388.608(se queda corto).2^24 = 16.777.216(es suficiente). Necesitamos un registro de 24 bits.
Ejemplo completo: un cambio por segundo
Vamos a implementar un módulo completo usando la técnica del Strobe.
module blinky_exacto (
input wire clk, // Entrada 12 MHz
output reg led = 0 // Salida LED
);
// Constante para el límite (usamos parameter para que sea configurable)
// 12 MHz - 1 porque contamos el 0
parameter FRECUENCIA_CLK = 12000000;
// Registro del contador (calculamos bits necesarios o ponemos de sobra)
reg [23:0] contador = 0;
// Señal de control (Strobe)
reg tic_segundo = 0;
// --- BLOQUE 1: El Prescaler ---
always @(posedge clk) begin
// Comportamiento por defecto
tic_segundo <= 0;
contador <= contador + 1;
if (contador == FRECUENCIA_CLK - 1) begin
contador <= 0; // Reiniciamos cuenta
tic_segundo <= 1; // Generamos el pulso de 1 ciclo
end
end
// --- BLOQUE 2: La Lógica del LED ---
// Fíjate que seguimos usando 'clk', NO 'tic_segundo' como reloj
always @(posedge clk) begin
if (tic_segundo) begin
led <= ~led; // Cambiamos el estado del LED
end
end
endmoduleContadores descendentes y otros modos
Por supuesto, no estamos limitados a sumar 1.
- Cuenta atrás:
contador <= contador - 1; - Cuenta de 2 en 2:
contador <= contador + 2; - Carga paralela: Podemos forzar el contador a un valor específico si se cumple una condición (útil para hacer temporizadores programables).
if (load_enable) begin
contador <= valor_inicial; // Carga manual
end else begin
contador <= contador - 1; // Cuenta regresiva
end