La interfaz de un módulo
La lista de puertos de un módulo es su interfaz - todo lo que el mundo exterior puede ver y tocar. Dentro del módulo, el cuerpo calcula salidas a partir de entradas. Los puertos son los wires que cruzan la frontera.
La declaración moderna de estilo ANSI pone dirección, tipo y ancho en línea:
module my_module(
input wire clk,
input wire reset,
input wire [7:0] data_in,
output reg [7:0] data_out,
output wire valid
);
// body
endmodule
Esa es toda la forma. Cada puerto tiene:
- Una dirección:
input,outputoinout. - Un tipo:
wireoreg(ologicen SystemVerilog). - Un ancho: un rango como
[7:0], o un solo bit si se omite. - Un nombre: el identificador que usarás dentro del cuerpo.
Las tres direcciones
input - excitado desde fuera
Las entradas siempre son wire. El que excita está fuera del módulo - o bien una señal de un módulo de nivel superior o el reg del testbench. Dentro de este módulo, puedes leer las entradas en expresiones pero nunca asignarles:
module reader(input wire [7:0] data);
initial $display("data = %h", data);
endmodule
Escribir data = ... dentro del módulo sería un error.
output - excitado desde dentro
Las salidas las excita algo dentro de este módulo. Pueden ser o bien wire (excitado por assign o por la salida de un submódulo) o reg (excitado desde always/initial).
module driver(
input wire a,
input wire b,
input wire clk,
output wire y, // wire - excitado por assign
output reg q // reg - excitado por always
);
assign y = a & b; // OK porque y es wire
always @(posedge clk)
q <= a; // OK porque q es reg
endmodule
Intentar asignar a y dentro del bloque always, o excitar q con un assign, sería un error de compilación. La elección de la palabra clave tiene que coincidir con quien la excita.
inout - bidireccional
Los puertos inout son wires que pueden ser excitados alternativamente por el módulo o por lo que esté conectado fuera. Aparecen en los límites del chip - líneas SDA de I²C, pines GPIO bidireccionales, buses de datos compartidos. Dentro del módulo, controlas la dirección con un patrón tri-state:
module bidir_pin(
input wire data_out,
input wire output_enable,
output wire data_in,
inout wire pin
);
assign pin = output_enable ? data_out : 1'bz;
assign data_in = pin;
endmodule
pin es el wire compartido. Cuando output_enable está en alto, el módulo excita pin a data_out. Cuando está en bajo, libera el pin a alta impedancia (z), permitiendo que un controlador externo lo use. data_in siempre observa lo que esté actualmente en el pin.
inout es raro en lógica puramente interna. Si estás construyendo un controlador de memoria, un núcleo de CPU o un pipeline de procesamiento de imagen, puede que nunca lo necesites. Es una característica para el anillo de I/O en los límites del chip.
Puertos de un solo bit vs multibit
El rango de ancho es opcional - omítelo para un puerto de un solo bit:
input wire valid, // 1 bit
input wire [7:0] data, // 8 bits
input wire [31:0] addr, // 32 bits
Puedes usar parámetros para los anchos, que es como funcionan los módulos parametrizados:
module bus #(
parameter WIDTH = 32
)(
input wire [WIDTH-1:0] in,
output wire [WIDTH-1:0] out
);
assign out = in;
endmodule
Estilos ANSI vs Verilog-1995
Ocasionalmente verás código antiguo que separa la lista de puertos y las declaraciones:
// Estilo Verilog-1995 antiguo - NO lo hagas en código nuevo
module foo(clk, data_in, data_out);
input clk;
input [7:0] data_in;
output reg [7:0] data_out;
// ...
endmodule
La lista de puertos contiene solo nombres; cada puerto luego se declara por separado dentro del cuerpo. Es verboso, propenso a errores "nombrado en la lista pero nunca declarado" y supone el doble de escribir. El estilo ANSI-2001 (el que se muestra a lo largo de estos docs) reemplaza ambos:
module foo(
input wire clk,
input wire [7:0] data_in,
output reg [7:0] data_out
);
// ...
endmodule
Usa el estilo ANSI. Las herramientas lo soportan desde 2001.
Un ejemplo completo
Ese único módulo tiene:
- Un parámetro (
WIDTH). - Entradas para reloj, reset, habilitación de carga, data y habilitación de shift.
- Una salida
reg(data_out) excitada dentro de un bloquealways. - Una salida
wire(msb_out) excitada por una asignación continua. - Una declaración completa de estilo ANSI que lista la dirección, el tipo y el ancho de cada puerto en línea.
Así es como está estructurado casi cada módulo sintetizable que vas a escribir.
Qué viene a continuación
El siguiente doc - Module Instantiation - muestra cómo coger este módulo y usarlo dentro de un diseño más grande. El shifter que acabas de escribir no es útil por sí solo; se gana su sitio cuando algo lo instancia y lo cablea en un sistema.
Preguntas frecuentes
¿Cuáles son las tres direcciones de puerto en Verilog?
input, output e inout. input se excita desde fuera del módulo. output se excita desde dentro del módulo. inout es bidireccional - útil para buses tri-state donde el módulo a la vez excita y lee el mismo pin. La gran mayoría de los puertos internos son input o output; inout solo aparece en los pines del chip.
¿Cuál es la diferencia entre output wire y output reg?
output wire significa que la salida se excita mediante una asignación continua o desde la salida de un submódulo - todo lo que está fuera de un bloque always. output reg significa que se excita desde un bloque procedural (initial o always). La palabra clave controla si puedes asignar a la señal desde dentro de un always, no si el hardware sintetizado contiene un flip-flop.
¿Qué es el estilo ANSI para puertos en Verilog?
El estilo ANSI declara la dirección y el tipo de cada puerto en línea dentro de la lista de puertos: module foo(input wire [7:0] data, output reg [7:0] result);. El estilo más antiguo de Verilog-1995 solo listaba nombres en la lista de puertos y los redeclaraba dentro del módulo. Usa siempre el estilo ANSI en código nuevo - es más corto, menos propenso a errores y estándar en todo lo moderno.
¿Se pueden tener varias entradas y salidas en un módulo Verilog?
Sí - un módulo puede tener tantos puertos como necesites, en cualquier combinación de direcciones. Sepáralos con comas en la lista de puertos. El orden en la lista de puertos determina el orden de conexión posicional al instanciar, pero casi siempre usas conexiones por nombre (.port(signal)) para no depender del orden.