Что такое 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 падает, возобновляется, когда поднимается, и завершается.
Три вещи о структуре:
- Три отдельных места активности: генератор clock (один
always), стимулы (одинinitial) и монитор (ещё одинalways). У каждого одна работа. - Задержки
#в блоке стимулов измеряются в единицах времени - не в тактах clock. Если период clock 10 единиц, то#10- ровно один такт. Некоторые testbench используют@(posedge clk)вместо этого, что продвигает на один такт независимо от периода. - Монитор использует
$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, если выход не соответствует ожиданиям.