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 | Значение |
|---|---|
%b | binary |
%d | decimal (знаковое, если сигнал знаковый) |
%h или %x | hex |
%o | octal |
%c | один ASCII-символ (младшие 8 бит) |
%s | string |
%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);.