Fundamentos de módulos en Verilog: Estructura y sintaxis
Un module es un bloque de código Verilog que implementa cierta funcionalidad. Los módulos pueden incrustarse en otros y un módulo de nivel superior se comunica con los de nivel inferior usando sus puertos de entrada y salida.
Sintaxis
Un módulo debe encerrarse entre module y endmodule. El nombre va justo después de module y una lista opcional de puertos puede declararse. Los puertos declarados en la lista no pueden redeclararse en el cuerpo.
module <nombre> ([lista_puertos]);
// Contenido del módulo
endmodule
// Módulo puede tener lista de puertos vacía
module nombre;
// Contenido del módulo
endmodule
Todas las declaraciones de variables, sentencias de flujo de datos, funciones, tareas e instancias de módulos inferiores deben definirse entre module y endmodule. Puede haber múltiples módulos con nombres distintos en el mismo archivo y en cualquier orden.
Ejemplo

El módulo dff representa un flip-flop D con tres entradas d, clk, rstn y una salida q. Su contenido describe el comportamiento para diferentes combinaciones de entradas. La entrada d se asigna a salida q en flanco positivo de clock si rstn es alto (reset activo en bajo).
// Módulo "dff" con 3 entradas y 1 salida
module dff (
input d,
input clk,
input rstn,
output reg q
);
// Contenido del módulo
always @ (posedge clk) begin
if (!rstn) q <= 0;
else q <= d;
end
endmodule
Esquema de hardware
Este módulo se convierte en el siguiente circuito digital durante síntesis.

¿Cuál es el propósito de un módulo?
Un módulo representa una unidad de diseño que implementa ciertas características de comportamiento y se convierte en circuito digital durante síntesis. Cualquier combinación de entradas puede darse al módulo y dará una salida correspondiente. Esto permite reutilizar el mismo módulo para formar módulos mayores que implementan hardware más complejo.
Por ejemplo, el DFF anterior puede encadenarse para formar un registro de desplazamiento.
module shift_reg (
input d, input clk, input rstn, output q
);
wire [2:0] q_net;
dff u0 (.d(d), .clk(clk), .rstn(rstn), .q(q_net[0]));
dff u1 (.d(q_net[0]), .clk(clk), .rstn(rstn), .q(q_net[1]));
dff u2 (.d(q_net[1]), .clk(clk), .rstn(rstn), .q(q_net[2]));
dff u3 (.d(q_net[2]), .clk(clk), .rstn(rstn), .q(q));
endmodule
Esquema de hardware
Las instancias dff se conectan con wires como describe el módulo RTL Verilog.

En lugar de construir desde bloques pequeños a mayores, también puede hacerse lo inverso. Considera la descomposición de un motor GPU simple en componentes menores, cada uno representable como módulo con funcionalidad específica. El motor GPU puede dividirse en cinco sub-bloques con funcionalidad específica. La unidad de interfaz de bus obtiene datos del exterior, otra unidad extrae instrucciones, otras procesan datos de la anterior.

Cada sub-bloque puede ser un module con señales de entrada/salida para comunicarse con otros y subdividirse en bloques más finos según se requiera.
¿Qué son los módulos de nivel superior (top-level)?
Un módulo top-level es uno que contiene a todos los demás. No se instancia dentro de ningún otro módulo.
Por ejemplo, los módulos de diseño se instancian normalmente dentro de módulos testbench de nivel superior para simulación con estímulos. Pero el testbench no se instancia en ningún otro módulo porque encapsula todo y es el top-level module.
Top Level de Diseño
El código de diseño siguiente tiene módulo top-level llamado design porque contiene todos los sub-módulos requeridos. Los sub-módulos pueden tener sub-módulos anidados como mod3 dentro de mod1 y mod4 dentro de mod2. Al instanciar mod1 y mod2, todos quedan incluidos en el módulo top-level.
//---------------------------------
// Código de diseño
//---------------------------------
module mod3 ( [lista_puertos] );
reg c;
// Código de diseño
endmodule
module mod4 ( [lista_puertos] );
wire a;
// Código de diseño
endmodule
module mod1 ( [lista_puertos] );
// Este módulo "mod1" contiene dos instancias
wire y;
mod3 mod_inst1 ( ... ); // Primera instancia de "mod3" llamada "mod_inst1"
mod3 mod_inst2 ( ... ); // Segunda instancia también de "mod3" llamada "mod_inst2"
endmodule
module mod2 ( [lista_puertos] );
// Este módulo "mod2" contiene dos instancias
mod4 mod_inst1 ( ... );
mod4 mod_inst2 ( ... );
endmodule
// Módulo top-level
module design ( [lista_puertos]);
// Desde perspectiva de diseño, este es el top-level
wire _net;
mod1 mod_inst1 ( ... );
mod2 mod_inst2 ( ... );
endmodule
Top Level de Testbench
El módulo testbench contiene estímulo para verificar funcionalidad del diseño y se usa para verificación funcional con simuladores. Por tanto, el diseño se instancia y llama d0 dentro del testbench. Desde perspectiva del simulador, testbench es el módulo top-level.
//-----------------------------------------------------------
// Código Testbench
// Desde perspectiva de simulación, este es el top-level
// porque 'design' se instancia dentro de este módulo
//-----------------------------------------------------------
module testbench;
design d0 ( [conexiones_puertos] );
// Resto del código testbench
endmodule
Nombres jerárquicos
Se forma una estructura jerárquica cuando módulos se instancian unos dentro de otros, y el top-level se llama root. Como cada instanciación de módulo inferior dentro de uno dado requiere nombres de identificador diferentes, no hay ambigüedad al acceder a señales. Un nombre jerárquico se construye con lista de identificadores separados por puntos . para cada nivel. Cualquier señal puede accederse desde cualquier módulo usando la ruta jerárquica.
// Ejemplo de módulos top-level
design.mod_inst1 // Acceso a instancia mod_inst1
design.mod_inst1.y // Señal "y" dentro de mod_inst1
design.mod_inst2.mod_inst2.a // Señal "a" dentro de mod4
testbench.d0._net; // Señal _net de design accedida desde testbench
Verilog
- Dominar Verilog: Tutorial completo para diseño de circuitos digitales
- Dominar la concatenación en Verilog: Guía práctica
- Verilog Esencial: Resumen conciso
- Comprender las asignaciones en Verilog: tipos, sintaxis y mejores prácticas
- Bloqueante vs. No bloqueante en Verilog: Diferencias clave explicadas
- Dominar las funciones en Verilog para diseño RTL eficiente
- Comprender las Tasks en Verilog: Funciones vs Tasks, sintaxis y uso
- Dominar el alcance de referencia jerárquica en Verilog: Evitar colisiones de nombres en módulos y bloques
- Generador de reloj en Verilog: Guía de diseño e implementación
- Dominar las funciones matemáticas en Verilog para diseño de hardware eficiente
- Dominar $timeformat en Verilog: Control de precisión y visualización