24 / 37 · Práctica
Práctica: un banco de pruebas que informa sus fallos
Separa DUT, estímulos, referencia y comprobación para probar reset, enable y retorno sin carreras.
Las lecciones se pueden leer gratis. Inscríbete para guardar el progreso.
Separa implementación y checker
Un banco de pruebas suministra estímulos, calcula expectativas y denuncia discrepancias. Es código de simulación, no circuito para síntesis. Guarda modulo_tick de la lección de parámetros en modulo_tick.sv y este banco completo en tb.sv. Reset síncrono gana; sin enable se mantiene count y se limpia pulse; con enable se incrementa módulo N y pulse vale 1 únicamente al volver a cero. La referencia usa un entero, no estado interno del DUT.
`timescale 1ns/1ps
module tb #(parameter integer N = 5);
localparam integer CW = (N <= 1) ? 1 : $clog2(N);
logic clk = 1'b0;
logic rst = 1'b0;
logic enable = 1'b0;
wire [CW-1:0] count;
wire pulse;
integer expected_count = 0;
logic expected_pulse = 1'b0;
integer checks = 0;
modulo_tick #(.N(N)) dut (
.clk(clk), .rst(rst), .enable(enable),
.count(count), .pulse(pulse)
);
always #5 clk = ~clk;
task automatic step(input logic reset_value, enable_value);
begin
@(negedge clk);
rst = reset_value;
enable = enable_value;
if (reset_value) begin
expected_count = 0;
expected_pulse = 1'b0;
end else begin
expected_pulse = 1'b0;
if (enable_value) begin
expected_count = expected_count + 1;
if (expected_count == N) begin
expected_count = 0;
expected_pulse = 1'b1;
end
end
end
@(posedge clk);
#1;
if (count !== CW'(expected_count) || pulse !== expected_pulse)
$fatal(1, "Mismatch check=%0d count=%0d expected=%0d pulse=%b expected=%b",
checks, count, expected_count, pulse, expected_pulse);
checks = checks + 1;
end
endtask
initial begin
if (N < 1) $fatal(1, "N must be positive");
step(1'b1, 1'b1);
repeat (N-1) step(1'b0, 1'b1);
step(1'b0, 1'b0);
step(1'b0, 1'b1);
step(1'b0, 1'b0);
repeat (2*N+1) step(1'b0, 1'b1);
step(1'b1, 1'b1);
step(1'b0, 1'b0);
$display("PASS N=%0d checks=%0d", N, checks);
$finish;
end
initial begin
#10000;
$fatal(1, "Timeout");
end
endmoduleEvita carreras en el flanco activo
La tarea conduce entradas en el descendente, medio ciclo antes de capturar, y compara después de actualizar las no bloqueantes del ascendente. #1 es un retardo didáctico con timescale y reloj de diez nanosegundos, no análisis físico de clock-to-Q, setup o hold. Un entorno reutilizable puede usar clocking blocks o disciplina de regiones de eventos. El primer reset hace conocido al DUT: inicializar la referencia no lo inicializa. !== trata X/Z inesperados como discrepancias frente a valores conocidos.
Compila con top explícito
En un shell con Icarus Verilog instalado:
iverilog -g2012 -s tb -o sim.vvp modulo_tick.sv tb.sv
vvp sim.vvpCompila separadamente con -P tb.N=1, -P tb.N=2, -P tb.N=5 y -P tb.N=8. La documentación oficial de Icarus explica modo de lenguaje y top. Conserva la versión real porque la cobertura SystemVerilog varía entre simuladores.
Hazlo fallar deliberadamente
Modifica la comparación terminal para volver un flanco antes, o elimina la limpieza predeterminada de pulse. Algún escenario debe terminar con fallo no cero antes del timeout. Restaura luego el código correcto. Un checker que no detecta una mutación relevante proporciona evidencia débil. Registra hash, N, comandos, estados de salida, PASS y primera discrepancia. No llames a esto ejecución DDS: retardos e initial del banco general son distintos de la interfaz restringida de laboratorio con un reloj.