Using External Libraries with OSVVM Co-simulation
Introduction
The OSVVM Co-simulation features allow user supplied C or C++ code to drive model independent transaction (MIT) signals in a VHDL logic simulation via a supplied API, as I’ve discussed in a previous blog. When combined with one of the supplied Verification Components (VCs) or with a custom VC, the user software can generate protocol specific signalling to drive a design under test RTL implementation. It is also possible to have multiple user programs driving separate interfaces for as much complexity as needed. The user software is not running as separate processes with some inter-process communication links but is loaded by the logic simulator and run in threads as part of the simulator’s normal threading environment making it very fast to execute.
The user programs that can be run in the simulator can be pure test code and mirror what might be done in VHDL, but any kind of program can be written and the “OSVVM’s Co-simulation Framework” document gives two examples. The first of these details interfacing a RISC-V instruction set simulator so that RISC-V programs can be run and be debugged whilst driving memory load and store transactions into the logic simulation to access memory and memory mapped RTL components’ registers etc. Another example is implementing a TCP socket server to interface with externally running programs to generate transactions.
The OSVVM co-simulation API maps the model independent transaction VHDL procedure calls to equivalent C or C++ calls, to give the same rich environment for driving the signalling as for VHDL test code, but also allows the ability to use any C or C++ library that may be useful to do so, giving access to all the computing power any normal C or C++ program access on the host machine. In this blog I want to discuss how external code and libraries can be linked to the user supplied test code so that any arbitrary program can be run with OSVVM.
How the Normal User Code is Run
Before looking at linking to external code, it is worth reviewing how straightforward user test code is compiled and run within the OSVVM co-simulation environment. I’ll assume a constructed VHDL test environment already exists that makes calls to co-simulation VHDL procedures—CoSimInit (in all cases; one for each MIT being driven, each with their own unique “node” number and at least one from CoSimTrans, CoSimResp or CoSimStream. For each “node” (supplied as an argument to the VHDL procedures) as a minimum, the user must supply a “main” entry point in the form of VUserMain<n>, where <n> matches the node number. These must also have “C” linkage. From that point on any normal C or C++ code (as appropriate) can be written and use the supplied API. So, an example template for some user C++ code for a node of 0 might be:
#include "OsvvmCosim.h"
extern "C" void VUserMain0(int node)
{
/////////////////////////////////////////////
// User code and calls to other functions
/////////////////////////////////////////////
// If ever got this far then sleep forever
SLEEPFOREVER;
}
The SLEEPFOREVER call at the end just ensures the logic simulation can continue even though this code does nothing.
To compile this “program” let’s assume it is in a file called VUserMain.cpp in a directory called tests/basic. From within an OSVVM TCL script environment, the code can be compiled with the MkVproc procedure:
ChangeWorkingDirectory ./tests MkVproc basic
The MkVproc procedure takes a single argument specifying the directory where the source code is to be found. Since the script already changed to the tests directory, this argument is just basic. All the C and C++ code in the specified directory will be compiled to object files, with the directory also added to the include search path. Therefore, any amount of C or C++ source code, with any organisation, can be added to the directory to be compiled.
Once all the code is compiled to object files, these are all linked into a static library, libvuser.a, and then linked into a shared object (or dynamic linked library in Windows speak) VUser.so. The script will also compile the OSVVM co-simulation software into a libvproc.a library which is linked into a shared object, VProc.so. When the simulation is run it loads the VProc.so library to interface with the logic, and the VProc.so code loads the VUser.so library to start running the user code. As a side note, the reason there are these two separate shared objects rather than one big one, is that the compilation of the OSVVM co-simulation code is simulator and operating system dependent, with various flags and definitions required, depending on the environment. With a separate user library, its compilation is independent of the OS and simulator used, simplifying its compilation as a separate step.
Source Code
If third party code, that requires to be included in the user code, is in source-code form, then it could be added to the compilation directory and will automatically be compiled with the VUserMain code. If this is not practical, then the external source code can be compiled separately into object files which, after a MkVproc compilation can be appended to the libvuser.a static library. The compilation of VUser.so would then need to be re-done to include the new object files. E.g.
ar q libvuser.a <objfile1> <objfile1> ...
make -f <path to OsvvmLibraries>/CoSim/makefile SIM="" VUser.so
Here the makefile in the CoSim directory is being used directly, rather than called from MkVproc. If not using ModelSim, then the SIM make variable needs to be set to the null string to do an x64 link rather than an x86 32-bit link. A separate make file, makefile.avhdl, is supplied for Active-HDL, but it’s used in the same way.
Linking Prebuilt Static Libraries
To specify a static library to compile along with the user code, then a second MkVproc argument can give the library core name. E.g.
ChangeWorkingDirectory ./tests MkVproc iss rv32
This compiles the user code in ./iss and links the RISC-V ISS library from CoSim/lib, with an include path for CoSim/include. The supplied library name will be expanded depending on OS and simulator type, with a lib prefix added and a suffix of [win[32|64]][<blank>|x64]. For example, if using GHDL, it’s expanded to librv32x64.a—there are 32-bit versions for ModelSim and variants for both Linux and Windows under MSYS2. This scripting, though, relies on libraries and required include files placed or linked into the lib/ and include/ directories of the CoSim/ directory.
Using makefile for External Libraries
If more complex compiling and linkage is required then the makefile (or makefile.avhdl, if using Active-HDL) in CoSim/ can be used or copied and modified to add include search paths and link to external libraries, both static or dynamic as required. Normally, the call to the make file compiles everything in one step. If we break this into three steps, then there’s a chance to add customised flags to compile in external libraries. Assuming nothing has been compiled so far, then the VProc.so shared library must be compiled first:
make -f <path to OsvvmLibraries>/CoSim/makefile \ SIM="" \ PCIEDIR=<path to OsvvmLibraries>/PCIe \ SRCDIR=<path to OsvvmLibraries>/CoSim/code \ VProc.so
This is standard, so no customisations are required, but the paths to the relevant OsvvmLibraries sub-directories are needed as shown—either relative to the compilation directory or absolute paths. Note, again, that SIM must be set either to the null-string or to “ModelSim” if using the modelSim simulator. The next step is to compile the user static library libvuser.a:
make -f <path to OsvvmLibraries>/CoSim/makefile \ SIM="" \ SRCDIR=<path to OsvvmLibraries>/CoSim/code \ USRCDIR=<path to VUserMain test code directory> \ USRFLAGS="<my flags>" \ libvuser.a
SRCDIR needs specifying again to pick up the headers for the API. The USRCDIR is specified to compile all user test code, which must include the VUserMain entry C functions. In addition, USRFLAGS can add any gcc or g++ flags specific to the user code, including for headers of libraries to be linked or paths to libraries etc. The last step is to link the code for VUser.so:
make -f <path to OsvvmLibraries>/CoSim/makefile> \
SIM="" \
USRFLAGS="<my flags>" \
VUser.so
In this last step USRFLAGS is now used to add any library paths and library references in order to link to the desired external pre-built libraries. We now have VProc.so and VUser.so and we can skip the MkVproc step in the test script and simply run the simulation which will load the shared libraries and execute the programs using their external libraries.