Пытаемся сдружить симуляторы Verilog и ctest

Если Вы программист, которому по долгу службы пришлось испачкать руки 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() на каждый чих, если что-то пошло не так. А в большом проекте такое сплошь и рядом.

Решение как всегда велосипедистое, но работает:

  1. Пишем в result.txt file что-то отличное от цифры 0 при старте
  2. Моделируем.
  3. Перезаписываем содержимое result.txt реальным кодом выхода и вызываем $finish()
  4. Оборачиваем запуск симулятора скриптом, который по завершении моделирования выплюнет

В этом случае если из какой-то библиотеки триггеров имени Васи Пупкина вылезет ассерт, мы все равно оборвем тест и пометим его как провалившийся.

Вот примерная реализация этого алгоритма:

 
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().

Пожалуй, на этом у меня на сегодня все, всем удачной отладки.

 

 

 

 

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

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