Qué es realmente un testbench
Un testbench es solo un módulo Verilog. La diferencia con un módulo sintetizable es lo que hay dentro:
- No tiene puertos - es la cima de la jerarquía de simulación.
- Instancia el diseño bajo prueba.
- Excita las entradas del DUT desde bloques
initialyalways. - Observa las salidas del DUT con
$display,$monitoro volcando un VCD. - Termina la simulación con
$finish.
Ese es todo el trabajo. No hay una palabra clave especial de "testbench" - la forma de saber que algo es un testbench es leyendo lo que hace.
El esqueleto estándar
Ese es el patrón completo. Cinco secciones, fácil de copiar y pegar, fácil de adaptar.
Para DUTs combinacionales (como el sumador), no necesitas reloj. Para DUTs secuenciales, sí - y esa es la siguiente capa.
Generación de reloj para DUTs secuenciales
Cuando el DUT tiene una entrada clk, el testbench tiene que producir uno. El one-liner estándar:
reg clk = 0;
always #5 clk = ~clk;
Eso conmuta clk cada 5 unidades de tiempo. Cada conmutación es la mitad del periodo del reloj, así que el periodo completo es de 10 unidades (un ciclo = alto durante 5 + bajo durante 5). Si tu timescale es 1ns / 1ps (por defecto en la mayoría de los simuladores), eso es un reloj de 100 MHz.
Elige el medio periodo basándote en lo que quieras modelar. Para un reloj de 10 MHz con el mismo timescale, usa #50. Para un reloj de 200 MHz, #2.5 (o cambia el timescale).
Secuencia de reset
Los diseños síncronos necesitan un reset que se mantenga en alto durante unos ciclos después del tiempo 0, y luego se libere. La forma convencional:
reg reset = 1; // start asserted
initial begin
// Hold reset for a few clocks.
#20 reset = 0;
end
Eso mantiene reset en alto hasta el tiempo 20, luego lo baja. Para cuando reset cae, el reloj ha hecho un par de ciclos, cada flip-flop ha capturado el valor de reset y el diseño está en un estado conocido.
Para reset activo en bajo (reset_n en vez de reset), invierte el valor inicial:
reg reset_n = 0; // active-low: 0 means asserted
initial begin
#20 reset_n = 1;
end
Un testbench secuencial completo
Combinando reloj, reset y estímulos:
Ese es un testbench completo para un contador de 4 bits. Pulsa Ejecutar y verás cómo el contador sale del reset, cuenta hacia arriba mientras está habilitado, pausa cuando enable cae, reanuda cuando sube, y luego para.
Tres cosas que observar sobre la estructura:
- Tres loci distintos de actividad: el generador de reloj (un
always), los estímulos (uninitial) y el monitor (otroalways). Cada uno tiene un trabajo. - Los retardos
#en el bloque de estímulos se miden en unidades de tiempo - no en ciclos de reloj. Si el periodo del reloj es de 10 unidades, entonces#10es exactamente un ciclo. Algunos testbenches usan@(posedge clk)en su lugar, que avanza un ciclo sin importar el periodo. - El monitor usa
$displaydesde un bloquealways @(posedge clk). Eso imprime cada ciclo. Para una salida más sofisticada, cambia a$monitor(cubierto a continuación).
Estímulos basados en ciclos
A veces "espera N ciclos" se lee mejor que "espera N unidades de tiempo":
initial begin
@(posedge clk); // wait for next clock edge
reset = 0;
repeat (5) @(posedge clk); // wait 5 more cycles
enable = 1;
repeat (8) @(posedge clk);
enable = 0;
repeat (10) @(posedge clk);
$finish;
end
@(posedge clk) bloquea hasta el siguiente flanco de subida del reloj. repeat (N) @(posedge clk); espera N ciclos. Este estilo es independiente del periodo del reloj - si cambias la frecuencia del reloj, el estímulo sigue haciendo lo mismo en términos de ciclos.
Para testbenches tempranos los retardos # son más simples. Para testbenches de producción que pueden correr a varias velocidades de reloj, basado en ciclos es el estilo más seguro.
Tests con autocomprobación
El testbench hasta ahora imprime lo que pasó, dejándote a ti leer la salida. Un testbench con autocomprobación en cambio comprueba la salida y reporta pass/fail:
initial begin
#1;
a = 10; b = 20;
#1;
if (sum !== 30) begin
$display("FAIL: 10 + 20 = %0d (expected 30)", sum);
$finish;
end
a = 250; b = 5;
#1;
if (sum !== 255) begin
$display("FAIL: 250 + 5 = %0d (expected 255)", sum);
$finish;
end
$display("PASS");
$finish;
end
Usa !== (el operador case-inequality) por seguridad - no devuelve x cuando un operando tiene bits desconocidos. El patrón: excita las entradas, espera a que las cosas se asienten, compara contra lo esperado, reporta pass o fail.
La autocomprobación es invaluable en suites de regresión: miles de tests pueden correr desatendidos, y solo los fallos necesitan atención.
Qué viene a continuación
Ya tienes la forma de testbench que es suficiente para cualquier módulo. Los siguientes docs cubren las herramientas que van dentro del testbench: Display and Monitor para una salida de texto más rica, Dumpfile and VCD para depuración gráfica de formas de onda, y Timescale and Delays para controlar exactamente cómo el tiempo de simulación se relaciona con el tiempo de reloj de pared.
Preguntas frecuentes
¿Qué es un testbench en Verilog?
Un testbench es un módulo Verilog - típicamente sin puertos - cuyo único propósito es ejercitar un diseño bajo prueba (DUT). Instancia el DUT, genera un reloj, excita estímulos mediante bloques initial y always, observa salidas con $display/$monitor, opcionalmente vuelca una forma de onda VCD, y termina la simulación con $finish.
¿Cómo genero un reloj en un testbench de Verilog?
El patrón estándar es una sola línea: always #5 clk = ~clk; (con reg clk = 0; declarado antes). Eso conmuta clk cada 5 unidades de tiempo de simulación, dando un periodo de 10 unidades (un reloj de 100 MHz si tu timescale está en nanosegundos). El #5 es el medio periodo - la mitad del tiempo clk está en alto, la mitad en bajo.
¿Qué es un DUT en Verilog?
DUT significa Design Under Test - el módulo que el testbench está ejercitando. La convención es nombrar la instancia del DUT como dut o u_dut en el testbench: my_module dut(.clk(clk), .reset(reset), .in(in), .out(out));. El nombre es solo una etiqueta; lo que importa es que esté instanciado, sus puertos conectados a señales del testbench y que el testbench excite esas señales.
¿Cuánto debería correr una simulación de Verilog?
Lo suficiente como para ejercitar todo lo que quieras verificar, y luego $finish. La mayoría de los testbenches establecen un límite de tiempo explícito (#1000 $finish) para que la simulación no pueda colgarse esperando un evento que nunca llega. Dentro de esa ventana, excita tus estímulos, deja que el DUT se asiente, e idealmente incluye algunas sentencias if de autocomprobación que impriman FAIL si la salida no coincide con lo esperado.