Weird Trickery: Compiling verilog VPI extension and unit-testing it using cmake/ctest

A few months ago I needed to write a VPI extension for verilog HDL and (just as I would normally do) I needed a proper buildsystem for that stuff. Unfortunately in terms of build/debug/test tools the folks doing ASIC are living… Well, not in the stone age, but in their own small isolated world and keep reinventing the wheel over and over again. OpenSource iverilog simulator didn’t go far away from the proprietary counterparts that tend to ditch commonly used in linux environments best practices.
Okay, now let’s stop bitching about the way things are and decide how to deal with that kind of stuff. In this note I’ll try to describe how to make a CMakeLists.txt for compiling a VPI extension and unit-testing it with ctest.

So, let’s start from the basics. What is VPI? Marketing bullshit aside, this is an API that allows you to add your own tasks and functions to the verilog simulator that are implemented in С. A VPI module is basically a shared library, but instead of .dll/.so extension it has a .vpi one. In case of iverilog – there’s a helper script iverilog-vpi to compile these modules. So, instead of invoking gcc you run:

iverilog-vpi vpilib.c src2.c ...

And grab your vpilib.vpi. The name is taken from the first file of the sources (sic!). As you might have guessed, even wrapping this in a Makefile or something won’t get us ever near a proper buildsystem. I had to do somthing about it, so I got a closer look at iverlog-vpi, that turned out to be a simple bash script (d’oh!). At the very top we’ll see the following:

# 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"

So… Guys never heard of pkg-config, how are we going to deal with that? For normally I’d want unit-testing and the rest of the goodies you’d normally want to expect. The only way out was  -teach cmake to build us VPI. For that we open our fresh new CMakeLists.txt and start writing some magic:

The very start was separating my actual code and the VPI wrapper. We’ll need that for unit-testing

SET(CORE_SOURCES core.c panic.c)
SET(VPI_SOURCES vpi_wrapper.c)

Next step, after having another close look at iverilog-vpi I added the following lines that instructed cmake to use the very same flags iverilog-vpi does and generate a “.vpi” shared libraru with no “lib” prefix (e.g blah.vpi instead of libblah.so)

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
)

Now the most tricky part. Testing. I have two types of tests here. First test the C code that I hook to VPI and do not use verilog at all, and the other are actual full-blown verilog testbenches that we need to compile and run.

Let’s start with simple things. Plain С tests. I’ve created tests in plain C that follow the following template :

  • Init
  • The actual test
  • Full deinit and resource deallocation

This way allows me to pipe the whole test through valgrind and quickly see if anything is leaking. I normally run each test twice. First one justs executes the binary without valgrind present, the second one runs the same binary with valgrind and tells it to return a non-zero exit code if anything has leaked. It turns out that there are some nasty bugs when the presence of the debugger can affect the result. Below is a cmake script that takes care to adding a bunch of C files from a directory as unit-tests. Each file will be compiled into an executable binary, which in turn will be added as 2 distinct unit-tests: one run with valgrind, one run without.

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

Sounds simple, but that is the place where the first nasty stuff waits us. If we just link our our .vpi library with a binary file we’ll end up with a ton of undefined references with all the vpi functions we call starting with vpi_printf(). After all, we’re running without iverilog.  The solution is, as pointed at the very beginning, is to separate our vpi wrapper code and the actual code. We can do it like this:

add_library(lprobestatic STATIC ${CORE_SOURCES})
add_library(lprobe SHARED ${VPI_SOURCES})
target_link_libraries(lprobe lprobestatic pthread rt)

And link our VPI extension with this static library containing all of our (non-vpi) code.

add_library(lprobe SHARED ${SOURCES})
target_link_libraries(lprobe pthread rt lprobestatic

Okay, fast forward, what are we going to do with verilog testbenches? teh ones that use our vpi extension? We’ve got to somehow compile and run them?
In case of iverilog each testbench is made into a binary executed with vvp application that does the actual simulation. In commandline it looks like that:

iverilog -m myvpilib 1.v 2.v -o testname
vvp -M/path/to/dir/with/vpi ./test

In CMake compiling and adding that as a test might look like that:

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

That basically sums it all up. This approach allows us to use all the goodies of cmake/ctest. Since iverilog is not the only verilog simulator out there, the right approach is to move out all iverilog-specific stuff to a cmake submodule but that is totally another story that I might tell later.

Leave a Reply