Грабли препроцессора Verilog HDL

Небольшая заметка о том, как работают include и define в Verilog HDL.   Вопреки ожиданиям, они работают совершенно иначе, чем в большинства языков программирования. В маленьком проекте с этим можно не столкнуться, но в более или менее большом проекте, где есть лицензированные/скаченные с OpenCores блоки от этого поведения можно очень больно огрести. (Я долго искал подходящую картинку, но лучше этой не нашел)

TL;DR: Определенные однажды макросы глобальны, и передаются из одного файла в другой в том порядке, в котором файлы передаются компилятору/синтезатору.

Покажите код!

Допустим у нас есть следующий пример, состоящий из трех файлов не сложнее хеллоуворлда:

1.vh

`define A 1

1.v

`include "1.vh"
 
module top;
top2 t();
 
`ifdef A
    initial begin
        $display("Yarr! (1)\n");
    end
`endif
 
endmodule

2.v

module top;
 
`ifdef A
    initial begin
        $display("Yarr! (2)\n");
    end
`endif
 
//This will warn us if we're redefining a macro
`define A B
 
endmodule

Выглядит просто, попробуем собрать. Я буду пользоваться тремя инструментами:
1. Icarus Verilog (OpenSource)
2. Verilator (OpenSource)
3. Cadence ncvlog/ncsim (По счастью,  у меня есть доступ к этим инструментам на работе. У кого нет – могут попробовать edaplayground.com)

Начнем с Icarus Verilog:

0 ✓ necromant @ silverblade ~/Work/test $ vvp a.out 
Yarr! (2)
 
Yarr! (1)
 
0 ✓ necromant @ silverblade ~/Work/test $ iverilog 2.v 1.v 
0 ✓ necromant @ silverblade ~/Work/test $ vvp a.out 
Yarr! (1)

Упс. Что за хрень только что произошла? 1.v проинклюдил 1.vh у когорого был задефайнен макрос A И этот дефайн “перепрыгнул” в файл 2.v. Но если мы поменяем порядок файлов – все внезапно станет вполне себе ожидаемо. Но что намного хуже – iverilog нас даже не предупредит о том, что происходит переопределение макроса (По крайней мере версия  0.9.7, которая была в репозиториях debian)

Verilator (v. 3.900 2017-01-15)  ведет себя аналогичным образом, но он хотя бы предупреждает.

0 ✓ necromant @ silverblade ~/Work/test $ verilator --cc 1.v 2.v 
%Warning-REDEFMACRO: 2.v:10: Redefining existing define: A, with different value: B 
%Warning-REDEFMACRO: Use "/* verilator lint_off REDEFMACRO */" and lint_on around source to disable this message.
%Warning-REDEFMACRO: 1.vh:3: Previous definition is here, with value: 1 
%Error: Exiting due to 2 warning(s)
%Error: Command Failed /usr/bin/verilator_bin --cc 1.v 2.v

Наконец, проверим тулы от Cadence. Оные стоят вагон денег, потому у кого этого вагона нет, и кто не работает в области микроэлектроники – милости просим на edaplayground.

Для этого теста я не буду использовать  irun (который с точки зрения старого программиста, повидавшего разные системы сборки – набор дичайших костылей и подпорок). Вместо этого буду использовать сборку в три шага . Инструмент под названием  ncvlog компилирует файлы в библиотеку, потому мы можем выбирать сразу собрать оба файла или по очереди. ВНЕЗАПНО от этого поведение будет различаться. В этом легко убедиться:

0 ✓ necromant @ silverblade ~/Work/test $ ncvlog -q 1.v  
0 ✓ necromant @ silverblade ~/Work/test $ ncvlog -q 2.v
0 ✓ necromant @ silverblade ~/Work/test $ ncelab -q top
0 ✓ necromant @ silverblade ~/Work/test $ ncsim -q top
ncsim> run
Yarr! (1)
 
ncsim: *W,RNQUIE: Simulation is complete.
ncsim> exit

Окей, то есть макрос неспропагировался! Круто! А если передадим все файлы сразу?

0 ✓ necromant @ silverblade ~/Work/test $ ncvlog -q 1.v 2.v
`define A B
              |
ncvlog: *W,MACRDF (2.v,9|14): text macro 'A' redefined - replaced with new definition.
0 ✓ necromant @ silverblade ~/Work/test $ ncelab -q top
0 ✓ necromant @ silverblade ~/Work/test $ ncsim -q top
ncsim> run
Yarr! (1)
 
ncsim: *W,RNQUIE: Simulation is complete.
ncsim> exit

Ну хотя бы предупреждает, и на том спасибо!

Что с этим делать?

Подсказка: Ничего. Жизнь боль. Страдайте!

Такое поведение не должно быть проблемой в маленьком проекте. В конце концов, можно использовать длинные имена дефайнов с префиксами. Но если Вы разрабатываете жирную микросхему, в которой есть куча сторонних блоков, как скачанных бесплатно, без смс и регистрации с opencores или лицензированных, есть ненулевой шанс, что дефайн определенный в одном блоке повлияет на то, как будет себя вести другой блок от другого производителя. Или, что еще хуже, исходники IP блока могут быть написаны так, что корректность их компиляции будет зависеть от того, что такая пропагация дефайнов присутствует.

Возможность некоторых тулов (напр. Cadence) изолировать область видимости дефайнов отдельными файлами/группами файлами здесь НЕ поможет (И вообще скорее всего является недокументированным багом/фичой): При синтезе нам все равно потребуется передать инструменту полный список файлов, и в итоге этот гоблин снова вылезет.

Итак, что делать?

  • Искать варнинги о переопределении макросов и считать их ошибками! (И, разумеется, пользоваться инструментами, которые эти варнинги выдают) Впрочем, от запрятанного где-то `ifdef внутри стороннего блока это не спасет
  • Использовать скрипты для поиска define / ifdef конструкций в стороннем коде

Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.