Menu

Основы testbench в Verilog: как верифицировать module

Как написать Verilog-testbench - генерация clock, reset-последовательность, стимулы, наблюдение и стандартный скелет, который ведёт каждую симуляцию.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Что такое testbench на самом деле

Testbench - это просто module на Verilog. Отличие от синтезируемого module - в том, что внутри:

  • У него нет ports - это вершина иерархии симуляции.
  • Он инстанцирует design under test.
  • Он подаёт входы DUT из блоков initial и always.
  • Он наблюдает выходы DUT через $display, $monitor или дамп VCD.
  • Он завершает симуляцию через $finish.

Это вся работа. Никакого специального ключевого слова "testbench" - то, что что-то testbench, узнаёшь, читая, что он делает.

Стандартный скелет

Это весь паттерн. Пять секций, легко скопировать, легко адаптировать.

Для комбинационных DUT (как adder) clock не нужен. Для последовательностных нужен - и это следующий уровень.

Генерация clock для последовательностных DUT

Когда у DUT есть вход clk, testbench должен его произвести. Стандартный однострочник:

reg clk = 0;
always #5 clk = ~clk;

Это переключает clk каждые 5 единиц времени. Каждое переключение - половина периода clock, так что полный период - 10 единиц (один такт = 5 high + 5 low). Если timescale 1ns / 1ps (дефолт в большинстве симуляторов), это clock 100 МГц.

Выбирай полу-период по тому, что хочешь моделировать. Для 10 МГц на том же timescale - #50. Для 200 МГц - #2.5 (или меняй timescale).

Reset-последовательность

Синхронные дизайны нужен reset, который держится high несколько тактов после момента 0, потом отпускается. Стандартная форма:

reg reset = 1;     // стартуем выставленным

initial begin
    // Держим reset несколько тактов.
    #20 reset = 0;
end

Это держит reset high до момента 20, затем сбрасывает. К моменту падения reset clock сделал пару тактов, каждый flip-flop захватил reset-значение, и дизайн в известном состоянии.

Для active-low reset (reset_n вместо reset) переверни начальное значение:

reg reset_n = 0;   // active-low: 0 значит выставлен

initial begin
    #20 reset_n = 1;
end

Последовательностный testbench полностью

Сочетая clock, reset и стимулы:

Это полный testbench для 4-битного счётчика. Жми Run - увидишь, как счётчик выходит из reset, считает, пока enable, останавливается, когда enable падает, возобновляется, когда поднимается, и завершается.

Три вещи о структуре:

  1. Три отдельных места активности: генератор clock (один always), стимулы (один initial) и монитор (ещё один always). У каждого одна работа.
  2. Задержки # в блоке стимулов измеряются в единицах времени - не в тактах clock. Если период clock 10 единиц, то #10 - ровно один такт. Некоторые testbench используют @(posedge clk) вместо этого, что продвигает на один такт независимо от периода.
  3. Монитор использует $display из блока always @(posedge clk). Это печатает каждый такт. Для более изощрённого вывода переключайся на $monitor (разбираем следом).

Cycle-based стимулы

Иногда "жди N тактов" читается лучше, чем "жди N единиц времени":

initial begin
    @(posedge clk);     // ждём следующего фронта clock
    reset = 0;

    repeat (5) @(posedge clk);   // ждём ещё 5 тактов
    enable = 1;

    repeat (8) @(posedge clk);
    enable = 0;

    repeat (10) @(posedge clk);
    $finish;
end

@(posedge clk) блокирует до следующего нарастающего фронта clock. repeat (N) @(posedge clk); ждёт N тактов. Этот стиль не зависит от периода clock - поменяешь частоту, стимулы делают то же по тактам.

Для ранних testbench #-задержки проще. Для production testbench, которые могут работать на нескольких частотах, cycle-based безопаснее.

Self-checking тесты

Testbench до этого печатает, что произошло, оставляя тебе читать вывод. Self-checking testbench вместо этого проверяет выход и репортит pass/fail:

initial begin
    #1;
    a = 10; b = 20;
    #1;
    if (sum !== 30) begin
        $display("FAIL: 10 + 20 = %0d (ожидалось 30)", sum);
        $finish;
    end
    a = 250; b = 5;
    #1;
    if (sum !== 255) begin
        $display("FAIL: 250 + 5 = %0d (ожидалось 255)", sum);
        $finish;
    end

    $display("PASS");
    $finish;
end

Используй !== (case-неравенство) для безопасности - он не возвращает x, когда у операнда unknown-биты. Паттерн: подай входы, подожди устаканивания, сравни с ожиданием, репортни pass или fail.

Self-checking незаменим в regression-сьютах: тысячи тестов могут работать без присмотра, и только провалы требуют внимания.

Что дальше

Теперь у тебя форма testbench, которой хватит для любого module. Следующие документы разбирают инструменты внутри testbench: Display and Monitor для более богатого текстового вывода, Dumpfile and VCD для графической отладки через waveform и Timescale and Delays для управления тем, как симуляционное время связано с wall-clock.

Часто задаваемые вопросы

Что такое testbench в Verilog?

Testbench - это module на Verilog, обычно без ports, чья единственная цель - дёргать design under test (DUT). Он инстанцирует DUT, генерирует clock, подаёт стимулы через блоки initial и always, наблюдает выходы через $display/$monitor, опционально дампит VCD waveform и завершает симуляцию через $finish.

Как сгенерировать clock в Verilog-testbench?

Стандартный паттерн - одна строка: always #5 clk = ~clk; (с заранее объявленным reg clk = 0;). Это переключает clk каждые 5 единиц симуляционного времени, давая период 10 единиц (clock 100 МГц, если timescale в наносекундах). #5 - это полу-период: половину времени clk high, половину low.

Что такое DUT в Verilog?

DUT - это Design Under Test, module, который дёргает testbench. По соглашению instance DUT называют dut или u_dut в testbench: my_module dut(.clk(clk), .reset(reset), .in(in), .out(out));. Имя - просто метка; важно, что DUT инстанцирован, его ports подключены к сигналам testbench, и testbench их дёргает.

Сколько должна работать симуляция в Verilog?

Достаточно долго, чтобы прогнать всё, что хочешь верифицировать, потом $finish. Большинство testbench ставит явный лимит времени (#1000 $finish), чтобы симуляция не зависла, ожидая событие, которое не придёт. Внутри этого окна подавай стимулы, дай DUT устаканиться и в идеале включи self-checking if-операторы, которые печатают FAIL, если выход не соответствует ожиданиям.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ