Altifigence Academy

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.

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.

SystemVerilog
`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
endmodule

Evita 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:

text
iverilog -g2012 -s tb -o sim.vvp modulo_tick.sv tb.sv
vvp sim.vvp

Compila 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.

Tu elección se aplica a este navegador. Puedes cambiarla en el pie de página.