La jerarquía en Verilog es la organización de un diseño mediante módulos conectados entre sí desde un módulo raíz o top-level.
Hasta ahora, hemos escrito todo nuestro código en un solo archivo, dentro de un único module. Para un LED parpadeando está bien, pero imagina diseñar un procesador o una tarjeta gráfica escribiendo todo en un solo archivo de 10.000 líneas. Sería una locura.
En el diseño de hardware, la clave del éxito es Divide y Vencerás.
Al igual que en programación usamos funciones o clases para encapsular código, en Verilog usamos la Jerarquía. Creamos pequeños módulos que hacen una sola cosa muy bien (un contador, un filtro, un controlador de memoria) y luego los conectamos entre sí en un módulo superior.
Hoy vamos a aprender el arte de la Instanciación: cómo “soldar” virtualmente estos bloques para crear sistemas complejos.
Jerarquía y top-level
Imagina que estás diseñando una Placa de Circuito Impreso (PCB).
Tienes varios chips (Timer 555, Microcontrolador, Sensores).
Tienes pistas de cobre que los unen.
En Verilog:
- Los Chips son los Módulos (archivos
.vque ya has diseñado). - Poner un chip en la placa se llama Instanciar.
- Las Pistas de cobre son los
wireque conectan las instancias. - La PCB completa es el módulo Top-Level.
El Top-Level es el “jefe”. Es el módulo raíz que tiene acceso a los pines físicos reales de la FPGA (LEDs, botones, puertos GPIO). Dentro de él, instanciamos sub-módulos, que a su vez pueden tener otros sub-módulos dentro.
Sintaxis de instanciación
Supongamos que tenemos nuestro módulo debounce (del artículo anterior) y queremos usarlo en nuestro diseño principal.
// Definición del módulo original (La "clase" o "plano")
module debounce (
input wire clk,
input wire btn_in,
output reg btn_out
);
// ... lógica interna ...
endmodulePara usarlo, tenemos que instanciarlo (crear un objeto). La sintaxis es:
NombreDelModulo NombreDeLaInstancia ( ...conexiones... );
Existen dos formas de conectar los cables (puertos), y elegir la correcta es cuestión de vida o muerte para tu proyecto.
Conexión por posición
Funciona igual que las funciones en C: el orden importa.
// ⛔ NO RECOMENDADO
// Conectamos: clk -> reloj_sys, btn_in -> pin_boton, btn_out -> cable_limpio
debounce u1 (reloj_sys, pin_boton, cable_limpio);¿Por qué es malo?
Si mañana modificas el módulo debounce y cambias el orden de las entradas, o añades un puerto nuevo en medio, todas tus conexiones fallarán silenciosamente. Puedes acabar conectando el reloj al reset sin darte cuenta.
Conexión por nombre
Aquí decimos explícitamente: “Conecta el puerto A del módulo al cable B de mi diseño”. Usamos la sintaxis .puerto(señal).
// ✅ RECOMENDADO
debounce u1 (
.clk(reloj_sys), // Puerto 'clk' conectado a 'reloj_sys'
.btn_in(pin_boton), // Puerto 'btn_in' conectado a 'pin_boton'
.btn_out(cable_limpio) // Puerto 'btn_out' conectado a 'cable_limpio'
);- No importa el orden.
- Es autodocumentado.
- Si dejas un puerto sin conectar, es más fácil de detectar.
Consejo práctico: Siempre usa conexión por nombre. Te ahorrará horas de depuración cuando tus proyectos crezcan.
Ejemplo práctico: uniendo piezas
Vamos a construir un sistema completo. Queremos un contador que se incremente con un botón. Necesitamos:
- Un módulo
debounce(para limpiar el botón). - Un módulo
contador(la lógica). - Un módulo
top_system(que une todo y sale a los pines).
El cableado interno (wire)
Para conectar la salida del debouncer a la entrada del contador, necesitamos un “cable interno”. Este cable no sale fuera de la FPGA, vive dentro del silicio. Lo declaramos como wire.
module top_system (
input wire clk_12mhz, // Pin físico reloj
input wire rst,
input wire btn_fisico, // Pin físico botón
output wire [3:0] leds // Pines físicos LEDs
);
// --- CABLES INTERNOS (Interconnects) ---
wire boton_limpio; // Cable que une debounce -> contador
wire pulso_enable; // Un ciclo por cada pulsación
reg boton_anterior;
// --- INSTANCIA 1: El filtro antirrebote ---
debounce instancia_filtro (
.clk(clk_12mhz),
.rst(rst),
.btn_in(btn_fisico),
.btn_out(boton_limpio) // La salida se vuelca a nuestro wire interno
);
// Convertimos el nivel limpio en un pulso de un ciclo
always @(posedge clk_12mhz) begin
if (rst)
boton_anterior <= 1'b0;
else
boton_anterior <= boton_limpio;
end
assign pulso_enable = boton_limpio & ~boton_anterior;
// --- INSTANCIA 2: El contador ---
// (Asumimos que tenemos un módulo contador hecho)
contador_4bits instancia_contador (
.clk(clk_12mhz),
.enable(pulso_enable), // Incrementa una vez por pulsación
.cuenta(leds) // La salida va directa a los pines físicos
);
endmoduleFíjate en lo limpio que queda. El top_system no sabe cómo se filtra el botón ni cómo se cuenta. Solo sabe que tiene dos componentes y que debe conectarlos entre sí.
Puertos sin conectar y conexiones constantes
A veces no necesitamos usar todos los puertos de un módulo, o queremos fijar una entrada a un valor constante.
mi_modulo u2 (
.clk(clk),
.entrada_datos(8'hFF), // Fijamos la entrada a 255 (Hardcoded)
.reset(1'b0), // Desactivamos el reset permanentemente
.salida_debug() // Dejamos este puerto ABIERTO (sin conectar)
);Dejar entradas sin conectar es peligroso (pueden flotar o tomar valores aleatorios). Dejar salidas sin conectar es seguro (simplemente la señal se pierde y el sintetizador optimizará esa lógica borrándola).
Errores comunes al instanciar
Múltiples drivers
No puedes conectar las salidas de dos módulos diferentes al mismo wire.
// ❌ ERROR GRAVE
modulo_A u1 (.out(cable_comun));
modulo_B u2 (.out(cable_comun));
// ¿Quién gana? El sintetizador normalmente rechazará el diseño.Si necesitas combinar señales, describe explícitamente la lógica necesaria, por ejemplo un multiplexor o una puerta OR. Dos salidas internas enfrentadas suelen producir un error de síntesis; el riesgo de daño físico aparece al enfrentar pines externos reales.
Confundir el módulo con la instancia
El nombre del módulo (debounce) es el “tipo”. El nombre de la instancia (instancia_filtro) es el nombre único de esa copia.
Puedes tener muchas instancias del mismo tipo:
debounce btn1 (.btn_in(pin1), ...);
debounce btn2 (.btn_in(pin2), ...);
debounce btn3 (.btn_in(pin3), ...);