Недавно потребовалось для нужд кровавой отладки сделать свое VPI расширение для verilog HDL и встал вопрос как интегрировать это добро с какой-нибудь нормальной системой сборки. К сожалению, в плане инструментов для сборки, отладки и тестирование люди проектирующие СБИС живут если не в каменном веке, то в своем особом мирке, со своей особой атмосферой. Опенсурсный iverilog в этом плане ничуть не лучше коммерческих тулов, которые кладут огромный болт на многие общепринятые в мире linux практики.
Ну да хрен с ними. Надо решать как с этим жить. В этой заметке я постараюсь пройтись по основным нюансам написания CMakeLists.txt для сборки vpi расширения и его unit-тестирования.
Итак, что такое VPI? Если опустить маркетинговый bullshit, это такой интерфейс, который позволяет добавить к симулятору свои таски/функции, которые реализованы на С. Для этого есть API, используя который надо скомпилировать разделяемую библиотеку. Только в виде расширения у нее вместо .so/.dll будет .vpi. В случае с iverilog для этого существует костыль под названием iverilog-vpi. То есть компилировать наш код предлагается так:
iverilog-vpi vpilib.c src2.c ... |
На выходе будет vpilib.vpi, название берется из имени первого файла с исходниками (sic!). С этим безобразием надо было что-то делать, потому я начал с того, что разтрепанировал iverlog-vpi, который оказался просто баш скриптом. И в самом верху скрипта мы обнаруживаем столь нужное
# These are the variables used for compiling files CC="gcc -std=gnu99" CXX=g++ CFLAGS="-fPIC -Wall -Wshadow -g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -I/usr/include/iverilog" SUFFIX= # These are used for linking... LD=$CC LDFLAGS="-shared -L/usr/lib/x86_64-linux-gnu" LDLIBS="-lveriuser$SUFFIX -lvpi$SUFFIX" |
Окей, ребята ничегошеньки не слышали про pkg-config, как теперь будем с этим жить? Ведь хочется нормальных юнит-тестов и прочих плюшек. Выход был один — научить cmake собирать для нас VPI. Для этого открываем CMakeLists.txt и начинаем готовить очень сильное колдунство:
Начал я с того, что в коде разделил собственно часть, которая объявляет VPI таски и привязывает API моей библиотеки к verilog’у и все остальное.
SET(CORE_SOURCES core.c panic.c) SET(VPI_SOURCES vpi_wrapper.c) |
Следующим шагом, переосмыслив все то, что мы увидели в iverilog-vpi стали следующие строчки, куда я тупо запихнул все те флаги, которые использует iverilog-vpi, а так же установил на выходное имя файла библиотеки суффикс «.vpi» и убрал префикс.
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -fPIC -Wall -Wshadow -g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security") SET(CMAKE_EXE_LINKER_FLAGS "-L/usr/lib/x86_64-linux-gnu -lrt -lpthread -ldl -lm") SET(CMAKE_SHARED_LIBRARY_SUFFIX ".vpi") SET(CMAKE_SHARED_LIBRARY_PREFIX "") INCLUDE_DIRECTORIES( /usr/include/iverilog ${CMAKE_SOURCE_DIR}/include ) |
Теперь самое сложное. Тесты. Тесты у меня двух видов. Простые тесты моей библиотеки, которую я цеплял к verilog’у, и собственно несколько простых тестбенчей на верилоге, которые тоже надо собирать и запускать.
Начнем с простого и понятного — с тестах на родном для нас языке С. Тесты на языке С я привык делать по такому шаблону:
- инициализация
- собственно тестовый сценарий
- полная деинициализация и освобждение всех ресурсов
Такой подход позволяет прогнать тестовый сценарий через valgrind и сразу увидеть есть на каких тестах что-то потечет. Как правило я запускаю каждый тест дважды. Один раз без vagrind’а, а один раз с ним. Иногда некоторые тупые на вид косяки могут вызывать ситуацию когда наличие отладчика сказывается на результатах. Ниже я привожу пример из другого проекта как можно пачкой добавлять unit-test’ы к проекту. Эта функция подсасывает из указанной директории все .c файлы, каждый компилирует как бинарник и добавляет в ctest в двух экземплярах: обычный запуск, запуск из под valgrind’а. В самом примитивном виде это выглядит вот так:
file(GLOB TESTS_C ${CMAKE_SOURCE_DIR}/tests/c/*.c) foreach(file ${TESTS_C}) GET_FILENAME_COMPONENT(f ${file} NAME_WE) add_executable(${f} ${file}) target_link_libraries(${f} lprobestatic) add_test(${f} ./${f}) add_test(memcheck-${f} valgrind --error-exitcode=1 --read-var-info=yes --leak-check=full --show-leak-kinds=all --suppressions=${CMAKE_SOURCE_DIR}/valgrind.suppress --undef-value-errors=no --xml=yes --xml-file=${f}.xml ./${f}) endforeach() |
И тут нас ждут очень неприятные грабли. Если мы просто слинкуем тест с vpi расширением, то получим ошибки в виде undefined reference, так как ожидается что это дело будет запускать симулятор. Решением было выкинуть обертку vpi и тестировать только нашу бибилиотеку на этом этапе. Это можно сделать например так:
add_library(lprobestatic STATIC ${CORE_SOURCES}) add_library(lprobe SHARED ${VPI_SOURCES}) target_link_libraries(lprobe lprobestatic pthread rt) |
И уже линковать c этой библиотекой наше vpi расширение:
add_library(lprobe SHARED ${SOURCES}) target_link_libraries(lprobe pthread rt lprobestatic |
Ну ладно, теперь двигаемся дальше, а как нам быть с тестбенчами на verilog, которые подключают это vpi расширение? Ведь нам их надо собрать и запустить?
В случае с iverilog каждый тестбенч собирается в скрипт для инструмента vvp, который и запускает симуляцию. Из командной строки сборка и запуск тестбенча выглядят примерно так:
iverilog -m myvpilib 1.v 2.v -o testname vvp -M/path/to/dir/with/vpi ./test |
Немного подумав мы получаем вот такой вот костыль, который добавляет таргеты после сборки, собственно, vpi расширения. lprobe в моем случае это имя vpi расширения, которое мы собираем.
function(verilog_test_case name) foreach(f ${ARGN}) SET(files "${files} ${CMAKE_SOURCE_DIR}/${f}") endforeach() add_custom_command(TARGET lprobe POST_BUILD COMMAND iverilog -m lprobe ${files} -o ${name} COMMENT "Compiling Verilog Testbench: ${name}" ) add_test(${name}-run vvp -M ${CMAKE_BINARY_DIR} ./${name}) endfunction() file(GLOB TESTS_V ${CMAKE_SOURCE_DIR}/tests/verilog/*.v) foreach(file ${TESTS_V}) GET_FILENAME_COMPONENT(f ${file} NAME_WE) verilog_test_case(${f} ${file}) endforeach() |
Вот, собственно и все. Такой подход позволяет нам использовать все плюшки cmake и без проблем использовать такие радости жизни, как coveralls, coverity и прочие, которые только можно прикрутить к cmake.
Так как iverilog не единственный в мире симулятор, то правильным подходом будет вынести большую часть iverilog-специфичных штук в отдельный cmake скрипт и подключать в зависимости от того, к какому симулятору verilog мы заходим это добро прикрутить