Если Вы программист, которому по долгу службы пришлось испачкать руки Verilog/SystemVerilog — первое что приходит на ум — заюзать какую-нибудь штатную запускалку тестов. Если бы разработка велась на myhdl то можно было бы использовать все то добро, что сделано для unit-тестирования кода на python.
Но если мы используем самые что ни есть классические инструменты, проект достаточно большой, с кучей разных библиотек и IP ядер из разных концов света, велик шанс, что простых скриптов на bash/csh для запустка тестов маловато. Да и не хочется тратить драгоценные минуты жизни на переизобретение велосипеда, когда можно использовать что-то готовое.
В этой заметке я расскажу, как интегрировать verilog симуляторы со стандартными запускалками тестов, на примере ctest (Из комплекта cmake) и какие грабли нас ждут при этом.
return 0;
Итак, что такое запускалка тестов? Кэп подсказывает, что программулина, которая запускает наши тесты. (Возможно, в несколько потоков, чтобы процесс шел быстрее), обрабатывает таймауты и генерирует прикольные отчетики и графики в разных форматах, которые можно вывешивать где-нибудь, например в jenkins’е, а так же постить на cdash или тупо распечатывать на мягкой бумаге в рулонах для последующего использования.
Круто, а как тест (который по сути — просто программа) говорит запускалке заветное ‘passed’ или ‘failed’? Очевиднр, при помощи кода возврата. Если 0 — PASSED. Что-то другое — EPIC FAIL. Выглядит просто.
Отлично, значит если наш тест, это тестбенч на верилоге, все что нам нужно сделать — оборвать моделирование с ненулевым кодом выхода. Звучит просто? Вообще-то, как показывает практика, нет.
Классический вызов $finish() в верилоге не берет на вход аргументов и всегда обрывает моделирование с кодом выхода 0. (Разве что наш код уронил симулятор… каким-то макаром).
Некоторые симуляторы позволяют отступить от стандарта и передавать код выхода вот так:
$finish(1); |
А в SystemVerilog есть новый вызов $fatal() который обрывает моделирование и который нам и необходимо использовать… Но только если мы не используем кучу библиотек и чужого кода, в котором ассерты вызывают $finish() на каждый чих, если что-то пошло не так. А в большом проекте такое сплошь и рядом.
Решение как всегда велосипедистое, но работает:
- Пишем в result.txt file что-то отличное от цифры 0 при старте
- Моделируем.
- Перезаписываем содержимое result.txt реальным кодом выхода и вызываем $finish()
- Оборачиваем запуск симулятора скриптом, который по завершении моделирования выплюнет
В этом случае если из какой-то библиотеки триггеров имени Васи Пупкина вылезет ассерт, мы все равно оборвем тест и пометим его как провалившийся.
Вот примерная реализация этого алгоритма:
task exit(int fd, int code); $display("Exiting with code %02d", code); $fwrite(fd, "%02d\n", code); $fclose(fd); /* We're done */ $finish(); endtask module tb; int result_fd, tmp; string resultfile = "result.txt"; initial begin $display("Initializing resultfile"); result_fd = $fopen(resultfile, "w"); /* Assume a crash by default */ $fwrite(result_fd, "1\n"); $fflush(result_fd); tmp = $rewind(result_fd); end initial begin #100 exit(result_fd, 0); end endmodule |
Собрать и промоделировать можно icarus’ом. Для этого сохраняем исходник в 1.sv и запускаем:
iverilog -g2012 1.sv ./a.out |
А вот так может выглядеть упрощенный скрипт-обертка для запуска тестов:
vvp a.out [ ! -f "result.txt" ] && code=1 || code=`cat result.txt` echo "[!] Simulation complete, exit code $code" exit $code |
Обертку, кстати, можно генерировать сразу из cmake через configure_file.
Параллельный запуск тестов и плохие паттерны
Очевидно, тестов много, ядер на сервере тоже. Чтобы ускорить процесс, хочется запускать все это добро параллельно. Если мы используем ctest, то все что нам надо — набрать ctest -jN, где N — количество одновременно параллельно запускаемых тестов.
И ctest все сделает за нас. Благо, одну и ту же модель можно запустить несколько раз параллельно — это позволяют сделать все, даже icarus verilog. Так было бы в идеальном мире, если бы не одно большое НО:
Традиционно в верилоге, разнообразные модели устройств зачитывают входные файлы и пишут логи в директории, откуда был запущен симулятор. И по мере роста объема проекта вариант «поправить эту фигню в модели руками» становится все менее и менее привлекательным.
Решение здесь — запускать собранную один раз модель, из РАЗНЫХ рабочих директорий. Это можно разрулить при добавлении теста через WORKING_DIRECTORY аргумент к add_test().
Пожалуй, на этом у меня на сегодня все, всем удачной отладки.