sintaxis-basica-verilog-modulos-puertos

Sintaxis básica de Verilog: módulos, puertos, wire y reg

  • 6 min

Un módulo Verilog es un bloque de hardware con puertos y lógica interna que podemos conectar con otros módulos para construir un diseño mayor.

Hoy vamos a poner los cimientos sólidos. Vamos a diseccionar la gramática de Verilog.

Si vienes de C o C++, verás muchas llaves { y puntos y coma ;, pero no te confíes: el significado es totalmente distinto. Aquí no definimos funciones y variables, definimos cajas negras y cables.

El módulo (module): la unidad fundamental

En Verilog, todo gira en torno al concepto de Módulo. Piensa en un módulo como en un chip físico o un componente electrónico que tienes en la mano.

  • Tiene una carcasa (el código).
  • Tiene patitas de metal para conectarse (los puertos).
  • Tiene un circuito dentro (la lógica).

La estructura básica siempre es esta:

module nombre_del_modulo (
    // Aquí definimos las "patitas" (Puertos)
    input wire a,
    input wire b,
    output wire y
);

    // --- Aquí va el circuito interno ---
    assign y = a & b; // Ejemplo: Una puerta AND

endmodule
Copied!

A diferencia de las funciones en C++, los módulos no se “llaman”. Se instancian.

Es decir, tú “sueldas” una copia de ese módulo en tu diseño. Si necesitas dos contadores, tendrás dos copias físicas del circuito del contador en tu FPGA.

Puertos: input, output e inout

Los puertos son la interfaz de comunicación de nuestro módulo con el mundo exterior (o con otros módulos). Son equivalentes a los argumentos de una función, pero con dirección física.

Existen tres tipos básicos:

  1. input: Señales que entran al módulo. Solo podemos leerlas.
  2. output: Señales que salen del módulo. Nosotros escribimos en ellas.
  3. inout: Señales bidireccionales (como los pines SDA de I2C). Son más complejas y requieren lógica de “alta impedancia” (Tri-state), por ahora las dejaremos aparcadas.

Buses y vectores

En hardware, rara vez trabajamos con un solo bit. Solemos trabajar con grupos de bits (buses de datos, contadores, direcciones).

Para definir un bus, usamos la notación [MSB:LSB] (Most Significant Bit : Least Significant Bit).

input wire clk,             // 1 solo bit (escalar)
input wire [7:0] data_in,   // Bus de 8 bits (del 7 al 0)
output wire [15:0] result   // Bus de 16 bits
Copied!

Cuidado con el orden: [7:0] significa que el bit 7 es el más significativo (Big Endian conceptual).

Es posible escribir [0:7], y algunos diseños lo necesitan, pero mezclar ambas orientaciones provoca errores con facilidad. Mantén una convención coherente y comprueba siempre qué índice representa cada extremo del bus.

Tipos de datos: wire y reg

Este es el punto donde el 90% de los principiantes se atasca. En Verilog existen dos tipos de “contenedores” de datos principales: wire y reg.

Entender la diferencia es importante, porque define cómo se asignan los valores.

El tipo wire (cable)

El tipo wire representa una conexión física, un cable conductor.

  • No tiene memoria. Solo transporta el valor de un punto A a un punto B.
  • Se usa para conectar módulos o salidas de lógica combinacional.
  • Puede recibir una asignación continua con assign, o ser conducido por la salida de otro módulo o de una primitiva.
wire cable_a;
assign cable_a = boton_entrada & interruptor; // Conexión continua
Copied!

Un wire sin ningún controlador no mantiene el valor anterior: en simulación queda en alta impedancia (Z).

El tipo reg (variable procedimental)

El tipo reg representa una variable a la que asignamos valor desde un bloque procedimental.

  • Conserva su último valor en la simulación hasta que el bloque le asigna otro.
  • Solo se puede asignar dentro de bloques procedimentales (always, initial).

¡OJO! El nombre engaña.

Un reg NO siempre se convierte en un Flip-Flop físico ni en un registro de hardware.

Es simplemente una “variable de Verilog”. Si lo usas en lógica combinacional, el sintetizador lo convertirá en un simple cable. Si lo usas con un reloj (posedge clk), entonces sí se convertirá en un Flip-Flop.

Criterio práctico para elegir

Ante la duda, usa esta regla mnemotécnica:

  1. ¿Vas a usar assign? => Usa wire.
  2. ¿Vas a usar un bloque always? => Usa reg.
module ejemplo_tipos (
    input wire clk,
    input wire a,
    output wire salida_directa, // Se asignará con assign
    output reg salida_memoria   // Se asignará dentro de un always
);

    // Uso de WIRE (Lógica combinacional simple)
    // "salida_directa es IGUAL a 'a' en todo momento"
    assign salida_directa = ~a;

    // Uso de REG (Lógica secuencial)
    // "Dentro del bloque always, actualizamos la variable"
    always @(posedge clk) begin
        salida_memoria <= a;
    end

endmodule
Copied!

Representación de números (literales)

En Arduino escribimos int x = 10;. En Verilog necesitamos ser explícitos con el ancho en bits de nuestros números, para evitar sorpresas con el desbordamiento.

El formato es: <tamaño_en_bits>'<base><valor>

FormatoDescripciónValor Decimal
8'd108 bits, decimal10
8'h0A8 bits, hexadecimal10
8'b000010108 bits, binario10
1'b11 bit, binario1 (True)
1'b01 bit, binario0 (False)

También podemos usar el guion bajo _ para mejorar la legibilidad en números largos, igual que en las versiones modernas de C++ o Java.

reg [31:0] contador_grande = 32'b1010_0011_1111_0000_1010_0011_1111_0000;
Copied!

Ejemplo integrador: un multiplexor

Para cerrar, vamos a ver un módulo que combina todo lo aprendido: un Multiplexor 2 a 1.

Es un circuito que tiene dos entradas (d0, d1) y una señal de selección (sel). Si sel es 0, sale d0. Si sel es 1, sale d1.

module multiplexor (
    input wire d0,      // Entrada de datos 0
    input wire d1,      // Entrada de datos 1
    input wire sel,     // Selector
    output wire y       // Salida
);

    // Implementación usando lógica booleana con assign (wires)
    // y = (d1 AND sel) OR (d0 AND NOT sel)
    assign y = (d1 & sel) | (d0 & ~sel);

    /*
    Alternativa usando el operador ternario (como en C):
    assign y = sel ? d1 : d0;
    */

endmodule
Copied!