Menu

Verilog $display и $monitor: вывод из testbench

Как работают $display, $write и $monitor - format specifiers, разница между ними и когда какой инструмент правильный.

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

C printf-семейство Verilog

Три системные задачи печатают в stdout симулятора:

  • $display - печатать один раз, добавлять newline.
  • $write - печатать один раз, без newline.
  • $monitor - печатать автоматически каждый раз, когда наблюдаемый сигнал меняется.

Все три принимают форматную строку и список аргументов, как printf. Format specifiers похожи, но специфичны для Verilog.

$display: дефолт

$display - рабочая лошадка:

Увидишь что-то такое:

Hello, world.
byte_val  = ab
byte_val  = 10101011
byte_val  = 171
byte_val  = 171 (без padding)
multi: nibble=1010 count=42

Заметки:

  • %d дополняет до дефолтной ширины по размеру операнда. Для 8-битного reg это 3 символа (место для 255). Ведущие пробелы выглядят уродливо в табличном выводе - используй %0d, чтобы их убрать.
  • %h, %b, %o имеют схожее padding по умолчанию. Большинство кода testbench использует варианты %0, когда выравнивание не полезно.
  • Newline в конце автоматический. Не ставь \n в конце форматной строки $display - получишь пустую строку.

Format specifiers

Набор, поддерживаемый Verilog:

SpecifierЗначение
%bbinary
%ddecimal (знаковое, если сигнал знаковый)
%h или %xhex
%ooctal
%cодин ASCII-символ (младшие 8 бит)
%sstring
%tсимуляционное время
%mиерархическое имя текущего scope
%%литеральный %
%0Xбез leading padding, для любого %b, %d и т.д.

%b, %d, %h, %o - четыре, которые будешь использовать в 95% случаев. %t - следующий по частоте, всегда, когда хочешь timestamped лог-строку.

$write: без newline

$write идентичен $display, но не добавляет newline:

Вывод:

abc
done

Полезно для сборки одной строки из тела цикла:

$write("[");
for (integer i = 0; i < 8; i = i + 1) $write("%h ", arr[i]);
$display("]");

$monitor: авто-печать при изменении

$monitor регистрирует watch list. Симулятор перевычисляет и печатает, когда любой упомянутый в форматной строке сигнал меняется:

Увидишь три строки, по одной на каждое изменение входов. Не нужно вручную вызывать $display после каждого изменения стимулов - $monitor делает это сам.

Два ограничения:

  • Активен может быть только один $monitor. Повторный вызов заменяет предыдущий список. Используй $monitoroff и $monitoron, чтобы временно подавить и восстановить.
  • Изменения в одном временном шаге схлопываются в одну печать. Если a и b оба меняются в момент 5, монитор сработает один раз с обоими новыми значениями, не два.

Когда что использовать

  • $display: большая часть вывода testbench. Вызывай явно после стимулов, после важных переходов состояний или внутри сэмплирующего блока always @(posedge clk).
  • $write: когда хочешь собрать одну строку из цикла или нескольких маленьких кусочков.
  • $monitor: когда хочешь непрерывно отслеживать небольшой набор сигналов и видеть вывод только при изменении. Полезно для начальной отладки; сложнее использовать в regression-скриптах, потому что вывод не детерминирован по числу строк.

Для большинства workflow $display покрывает всё. К $monitor тянись только когда непрерывный change-driven-вывод - это реально то, что нужно.

Работа со временем

$time возвращает текущее симуляционное время как 64-битный integer. Пара с %0t:

$display("at %0t: signal flipped", $time);

Вывод выглядит как at 25: signal flipped (единица зависит от timescale).

Если нужна sub-tick-точность (редко) - используй $realtime, возвращает real.

%t автоматически форматирует время с дефолтной шириной, которую выбирает симулятор. %0t убирает padding.

Сэмплирование на фронте clock

Чистая идиома для мониторинга последовательностных дизайнов - отдельный блок always @(posedge clk), печатающий раз за такт:

Этот sampling-паттерн гарантирует одну лог-строку на такт - идеально для regression-тестов, паттерн-матчящих по выводу.

Логирование в файл

Открой файл через $fopen, логируй через $fdisplay (работает как $display, но пишет в file handle):

integer fd;
initial begin
    fd = $fopen("results.txt", "w");
    $fdisplay(fd, "test=%s status=%s", test_name, status);
    $fclose(fd);
end

$fopen возвращает 32-битный handle; передавай его первым аргументом в $fdisplay, $fwrite, $fstrobe и т.д. Функции в остальном идентичны своим консольным родственникам.

Что дальше

$display и компания дают текстовые логи. Для визуальной отладки - наблюдения сигналов как напряжений во времени - нужен VCD waveform. Следующий документ, Dumpfile and VCD, разбирает $dumpfile и $dumpvars, два вызова, превращающие твою симуляцию в графическую waveform, по которой можно скроллить.

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

В чём разница между $display и $monitor в Verilog?

$display печатает один раз, немедленно при выполнении - как printf в C. $monitor регистрирует список наблюдения; когда любой сигнал в списке меняется, форматированное сообщение печатается автоматически. Активен может быть только один $monitor за раз; повторный вызов заменяет предыдущий список.

Какие format specifier поддерживает Verilog $display?

Частые: %b (binary), %d (decimal), %h (hex), %o (octal), %c (один символ из младшего байта), %s (string), %t (симуляционное время), %m (иерархическое имя instance). Форма %0d убирает leading-zero-padding: %d дополняет до дефолтной ширины, %0d без padding.

Что такое $write в Verilog?

$write - как $display, но не добавляет newline. Полезно, когда хочешь собрать строку вывода из нескольких вызовов. Финальный $display (без аргументов или с trailing newline) завершает строку.

Как напечатать симуляционное время в Verilog?

Используй $time (или $realtime для sub-tick-разрешения) с format specifier %t: $display("at time %t: ...", $time);. Используй %0t, чтобы подавить дефолтное padding. Для простого целочисленного счётчика единиц времени %0d с $time тоже работает: $display("t=%0d", $time);.

Coddy programming languages illustration

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

НАЧАТЬ