Menu

Tu primer módulo Verilog: tutorial paso a paso

Escribe tu primer módulo Verilog completo desde cero - declaración, puertos, una pieza de lógica combinacional y un testbench que lo excita. Ejecutable en el navegador.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Un módulo es la unidad de Verilog

Todo en Verilog vive dentro de un module. Un módulo envuelve una pieza de circuito: declara qué señales entran, qué señales salen y qué wires/registros/lógica hay entre ellas. Cada chip que has visto es un árbol de módulos instanciando otros módulos, todo el camino hacia abajo hasta las primitivas de puertas suministradas por el proveedor.

La forma siempre es la misma:

module name(port_list);
    // declaraciones: wire, reg, parameter
    // cuerpo: assigns, instanciaciones, bloques always
endmodule

Construiremos un módulo completo por etapas.

El módulo útil más pequeño: una puerta AND de dos entradas

Necesitamos algo simple y autocontenido. Una puerta AND de dos entradas es perfecta: dos entradas, una salida, una línea de lógica.

Pulsa Ejecutar. Deberías ver las cuatro filas de una tabla de verdad AND. Vamos a recorrer cada pieza.

Leyendo la declaración del módulo

module and_gate(
    input  wire a,
    input  wire b,
    output wire y
);
  • module and_gate declara un módulo llamado and_gate. El nombre es como otros módulos lo instanciarán.
  • La lista entre paréntesis es la lista de puertos - las señales visibles desde fuera.
  • input wire a - a es un puerto de entrada; es un wire (excitado desde fuera).
  • output wire y - y es un puerto de salida excitado por algo dentro del módulo.

Si quisieras ser conciso podrías escribir input a en vez de input wire a - la dirección sola hace que el tipo por defecto sea wire. Pero ser explícito es un hábito que vale la pena formar. Module Ports cubre el conjunto completo de formas de puertos.

El cuerpo

assign y = a & b;

Eso es una asignación continua. Dice "cuando cambie a o b, recalcula y como el AND bit a bit de los dos". No hay reloj, ni timing - la relación siempre es verdadera. Eso es lógica combinacional pura.

endmodule cierra el bloque. El módulo está terminado.

El testbench

No puedes ejecutar and_gate solo. Necesitas un segundo módulo que excite sus entradas y observe sus salidas. Eso es el testbench, y por convención lo llamamos test, tb, o <design>_tb.

module test;
    reg  a, b;
    wire y;

    and_gate dut(.a(a), .b(b), .y(y));
    ...
endmodule

Tres cosas que observar:

  • Sin lista de puertos. Un testbench es la cima de la simulación - nada por encima.
  • reg a, b y wire y. Las entradas al diseño bajo prueba (DUT) son reg en el testbench porque las estamos excitando desde un bloque procedural. La salida es un wire porque la excita el DUT.
  • and_gate dut(.a(a), .b(b), .y(y)). Esto es instanciación. Estamos creando una copia de and_gate y llamándola dut (un nombre común - "design under test"). La sintaxis .a(a) dice "conecta el puerto llamado a de la instancia a la señal local llamada a". Module Instantiation profundiza más.

El estímulo

initial begin
    a = 0; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
    a = 0; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
    a = 1; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
    a = 1; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
    $finish;
end

El bloque initial se ejecuta una vez al inicio de la simulación. Dentro:

  • a = 0; b = 0; excita las entradas. Estas son asignaciones bloqueantes - el orden importa, y cada una sucede antes que la siguiente.
  • #1 avanza el tiempo simulado en 1 unidad. Lo necesitamos para que y tenga tiempo de estabilizarse tras el cambio en las entradas. Sin el #1, $display imprimiría el valor anterior de y.
  • $display(...) imprime en la consola de simulación. La cadena de formato funciona como el printf de C: %b es binario, %d es decimal, %h es hexadecimal, %t es tiempo de simulación.
  • $finish termina la simulación. Sin él, el simulador seguiría avanzando el tiempo para siempre esperando un evento que nunca llega.

Intenta romperlo

Modifica el módulo para que sea una puerta OR (cambia & por |) y vuelve a ejecutar. La tabla de verdad se da la vuelta. Ahora prueba un XOR:

El mismo esqueleto. Distinto operador. Ese es todo el juego para la lógica combinacional - declara puertos, escribe assigns, barre entradas, observa salidas.

Un módulo de dos salidas

Los módulos pueden tener más de una salida. Aquí hay un medio sumador - dos entradas, suma y acarreo de salida:

Las dos sentencias assign viven una al lado de la otra pero suceden en paralelo - no hay "primero calcula sum y luego calcula carry". Ambas son siempre verdaderas. Esa es la concurrencia de la que hablamos en Hardware vs Software, hecha concreta.

Lo que ahora sabes

Has visto todo el esqueleto de un archivo Verilog: un módulo de diseño con puertos declarados y un cuerpo, más un módulo testbench que lo instancia, excita sus entradas en un bloque initial y reporta resultados. Casi cada archivo fuente Verilog que vas a leer encaja en esta plantilla. El resto del lenguaje es solo rellenar lógica más rica, señales multibit, comportamiento sincronizado por reloj y testbenches más grandes.

A continuación: comentarios y estilo de código, para que tus módulos sigan siendo legibles a medida que crecen.

Preguntas frecuentes

¿Cuál es el módulo Verilog más simple?

El módulo Verilog legal más pequeño es simplemente module name; endmodule - sin puertos, sin cuerpo. El más pequeño útil es uno de una sola salida: module and_gate(input wire a, input wire b, output wire y); assign y = a & b; endmodule. Eso es una pieza real de lógica combinacional que puedes meter en cualquier diseño más grande.

¿Cómo ejecuto un módulo Verilog?

No puedes ejecutar un módulo por sí solo - es una descripción de un circuito, no un programa. Escribes un módulo testbench que instancia tu diseño y excita sus entradas, luego compilas ambos con iverilog -o sim design.v test.v y ejecutas vvp sim. El editor del navegador en esta página hace ambos pasos por ti cuando pulsas Ejecutar.

¿Qué es un testbench en Verilog?

Un testbench es un segundo módulo - normalmente sin puertos - cuyo trabajo es ejercitar tu diseño. Instancia el diseño, mueve sus entradas mediante un bloque initial, observa las salidas con $display o $monitor, y llama a $finish cuando termina. Los testbenches no son sintetizables; existen solo para verificar comportamiento.

¿Por qué mi código Verilog necesita $finish?

Porque el hardware nunca para. Un simulador finge que el tiempo está pasando, y sin un $finish explícito seguiría avanzando para siempre esperando nuevos eventos. $finish le dice al simulador 'ya terminamos, sal limpiamente'. En un testbench es la última línea del bloque initial - ejecuta la prueba, luego termina.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR