Нашел забавную ошибку в Вериляторе. При иерархической передаче строки в параметре модуля через два и более модуля строка может не дойти до конечного потребителя. Зависит это от того, указан ли, и где конкретно, тип параметра. Если во всей цепочке тип параметра отсутствует (выводится из дефолтного значения), то строка проходит. Если тип везде указан, то тоже проходит. А если во втором модуле тип не указать, то туда приходит строка той же длины, только заполненная пробелами. Причём, если её передать дальше, то она совсем обнулится.
Вот пример кода с багом:
```
module string_param_0 #(parameter S = "");
initial $display("S = '%s'", S);
endmodule
module string_param_1 #(parameter string S = "");
string_param_0 #(.S(S)) u_sp0();
endmodule
module test;
string_param_1 #(.S("Greetings from the old shoes")) u_sp1();
initial \#1 $finish;
endmodule
```
Вывод такой:
S = ' '
Если добавить тип параметра в первый модуль или убрать тип из второго, то вывод будет корректным:
S = 'Greetings from the old shoes'
Немного поигравшись с Veryl пишу отзыв. Если кратко: потенциал есть, но пока не готов.
Не буду лить воду, пойду по фактам. Плюсы языка:
➡️Полярность сброса и клока можно выбрать при сборке. Очень полезная фича, но есть некоторые проблемы с передачей этих знаний в подключаемые модули на верилоге. Есть автоматическая инверсия сброса, если в вериложном модуле сброс не той полярности. Но пока нет инверсии клока и никак не передать информацию о типе сброса - синхронный он или асинхронный.
➡️Фигурные скобки в качестве операторных скобок конечно выглядят гораздо приятней многословных begin/end
и module/endmodule
.
➡️Нет разделения на блокирующее и неблокирующее присваивание. Язык предназначен для написания только синтезируемого кода, по этому синхронные присваивания - неблокирующие, асинхронные - блокирующие.
➡️if/else
как выражение (expression). Как бы реверанс в сторону функциональных языков, но по сути синтаксический сахар над тернарным оператором.
➡️Дженерики! Наконец-то шаг в сторону метапрограммирования. Шаг скромный, но в верилоге этого очень не хватает.
➡️Есть поддержка аннотации CDC. Если вдруг вы небезопасно передаёте данные из домена в домен, транслятор даст вам по рукам.
➡️Какая-никакая стандартная библиотека с FIFO и некоторыми полезными модулями (кое кто из чата FPGA Systemc был бы очень рад).
Теперь минусы:
➡️Нет приведения ширины. Есть приведение к типу, и как бы можно объявить новый тип с нужной шириной и к нему приводить. Но это лишняя писанина, и результирующий верилог не поддерживается парсером Yosys'а. Такая вот шляпа.
➡️В конструкции вида a = {b, '1}
единица не расширяется до размера a
, и код в неизменном виде попадает в верилог. Т.е. это ошибка и в верилоге и в вериле, что несколько расстраивает. Выражение как бы очевидное, но авторы решили сохранить совместимость с верилогом. А ещё линтер на это не ругается (а Верилятор ругается).
➡️Нельзя объявить несколько переменных одного типа с одной декларации. Для каждой переменной надо писать var бла_бла: тип;
.
➡️Невозможно задать начальное значение регистру или памяти. ROM в принципе можно описать в виде const
(которое транслируется в localparam
), но RAM только через readmem. Авторы говорят, что для асиков это не нужно, а проблемы плисоводов их не волнуют. Конструкция initial
поддерживает только вывод сообщений.
По инфраструктуре. В отличие от верилога, у которого только стандарт, Veryl "из коробки" имеет форматтер, линтер и language server. Это очень удобно. Например, на добавление полноценной поддержки языка с в Emacs у меня ушли сутки. А это форматирование с отступами, подсветка синтаксиса, подсветка ошибок в реальном времени, автодополнение, хождение по коду и попапы.
Кроме перечисленного, в комплекте с транслятором есть система документирования с поддержкой markdown и wavedrom, система юнит-тестирования с интегрированными тестбенчами на верилоге, и система сборки, которая умеет подгружать зависимости.
Есть и недостатки:
➡️Нет настроек форматирования, есть только настройка длины таба.
➡️У линтера есть только настройки стиля, языковых настроек нет. Например, нет предупреждения о неявном приведении ширины.
➡️При касте сброса транслятор может менять имя сигнала, добавляя к нему префикс или суффикс (настраиваемо). При этом он это делает и для портов вериложных модулей. По этому, если включена эта опция, при имплементации вериложного модуля к именам портов клока и сброса надо прибавлять префикс r\#
. Но это скорее ошибка, которую нужно исправлять.
➡️Часто неправильно указывает положение ошибки, особенно если забыли или лишняя точка с запятой.
В общем, впечатления от языка двойственные. С одной стороны линтер, language server и дженерики. С другой - ощущение, что пишешь на верилоге с новым синтаксисом и старыми проблемами. А зачем мне старые проблемы? Мне нужны новые ?
PS: Дописывая пост заглянул в репозиторий. Две недели назад вышла новая версия, в которой добавили приведение ширины. Вот так может и допишут до продакшена.
Хорошее замечание и ещё один аргумент в ~~срачах~~ спорах про сбросы. Получается (без учёта других аргументов), что тотальный сброс можно делать любым - синхронным или асинхронным, а частичный (только control path, например) лучше синхронным, чтобы в случае чего синтезатор смог сымитировать его на логике.
В комментах товарищ @Xtyll спросил, не упала ли скорость симуляции в Вериляторе после добавления событийного шедулинга. Сообщаю: не упала.
Добавил тест Верилятора 4-й версии в свой бенчмарк - разница в результатах с версией 5 на уровне погрешности. Для надежности сделал ещё тест на реальном проекте. Там тоже разницы нет. Вот как-то так.
Про асинхронный сброс и Verilator.
Вот типичный код асинхронного сброса:
always\_ff @(posedge clk or negedge rst\_n) begin
if (!rst\_n) ...
else ...
end
Обычно, код работает так же, как в железе. Однако, если в начале симуляции нет клока, то сброс может не сработать. Так выйдет, если rst_n
присвоить 0 в нулевом времени через инициализацию в объявлении. Как можно догадаться, в этом случае события negedge rst_n
не случится, т.к. по стандарту присвоение в объявлении выполняется раньше присвоения в процессах.
Очевидное решение - в объявлении rst_n
присвоить 1 (или вообще ничего не присваивать, X->0 тоже считается за negedge
), а в initial
- 0:
logic rst\_n = 1'b1;
initial rst\_n = 1'b0;
Или так:
logic rst\_n;
initial begin
rst\_n = 1'b1;
rst\_n = 1'b0;
end
Однако, и здесь есть нюанс: Verilator не захотел регистрировать это событие, и не выполнил код процесса. Раньше такого в Вериляторе быть не могло, потому что до недавнего времени он был чисто clock-accurate симулятором. А раз клок всегда был, то события negedge rst_n
можно было не дожидаться, процесс вызвался бы при первом posedge clk
. Но сейчас он стал фактически полноценным event-driven, по этому наверное дожен справляться с такими вещами.
В связи со всем вышесказанным думаю самым правильным решением было бы присвоение rst_n
единицы, а как минимум в следующем цикле симуляции сбрасывать его в ноль:
logic rst\_n = 1'b1;
initial \#1 rst\_n = 1'b0;
Запилил issue
Вы наверное знаете, что в стандарте верилога есть ключевое слово macromodule
, которое ничем не отличается от module
, за исключением того, что "An implementation may choose to treat module definitions beginning with the macromodule keyword differently". Интересно, откуда растут ноги у этого macromodule
и используется ли где нибудь возможность интерпретировать его отлично от module
?
Я нашел документ, в котором сказано, что в macromodule
можно описывать только wire
и операции с ними. Никаких переменных/регистров и процедурных присваиваний, только assign
. Интересно, откуда эта информация, если в стандарте об этом ни слова?
Столкнулся с очередным багом Верилятора: функция $random
(и $urandom
тоже) с одинаковым начальным сидом генерит разные последовательности. Этого конечно не должно быть, и другие симы это подтверждают - проверил на Икарусе и Квесте. Однако, семейство функций $dist_*
работает правильно. Так что если вам понадобится в одной программе генерить одинаковые случайные последовательности, пользуйтесь $dist_uniform
вместо $random
/$urandom
.
Запостил issue.
PS: Ни слова про опенсорс. Уверен, что ошибку быстро исправят ;)
Дополнение. Такое поведение наблюдается только при seed == 0. Посмотрел исходники, там есть тест на равенство $random
с одинаковым seed, но seed устанавливается в 10.
GitHub
$random sequences with the same seed do not equal · Issue #5074 · verilator/verilator
Generating random numbers using $random and $urandom with the same seed produces different sequences. At the same time, $dist\_* functions with the same seed produces identical numbers. Test code: `...
Возможно это глупый вопрос, но я рискну. Представьте, что у вас есть слейв, принимающий 32 бита по AXIS-подобной шине (хендшейк valid/ready) и выполняющий обработку данных побайтно. Возможны два варианта действий:
В режиме ожидания вы держите ready в единице. Как только приходит valid, защелкиваете все 32 бита в регистр, снимаете ready и начитаете побайтную обработку. По окончании поднимаете ready.
В режиме ожидания держите ready в нуле. По приходу valid начитаете побайтную обработку, снимая байты непоследственно с шины. По окончании дергаете ready.
В общем, оба варианта приемлемы, т.к. в спеке на AXI не требуется, чтобы ready был активен до прихода valid, а однажды выставленный valid не может быть снят до прихода ready. С другой стороны, второй вариант может порушить времянки, если данные от мастера формируются через комбинационные цепи. И возможны проблемы с арбитражем, если арбитр полагается на ready.
Что вы думаете по этому поводу? В комментах создам опрос, заходите.