Shared libs and dynamic loading#
As explained in Loading a simulation, in order to load binaries/libraries dynamically, those need to be built as position independent code/executables (PIC/PIE).
This set of examples is an introduction from scratch for users who are familiar with C, but who have never built or loaded
shared libraries (
shlib: write C code and compile it as a shared library.
dlopen: write two shared libraries in C, and write a main C program for loading and executing both of them.
shghdl: build a simulation as a shared library, and write a main C program for loading and executing it.
py: build a simulation as a shared library, and write a Python script for loading and executing it.
pycb: build a simulation as a shared library, and write a Python script for loading it and replacing callbacks through
ctypesbefore executing the simulation.
This example features the same functionality as ‘rand’ from stdlib. However, custom C sources are used (as in custom C) and these are built as a shared library. See Loading foreign objects from within a simulation for further info.
Although this example does not include a simulation built with GHDL, it is a test and the introduction to the next
example. In this test, two separate shared libraries are built from C sources, both including a function named
ghdl_main. Then, in a main C application, both shared libraries are dynamically loaded at the same time, and both
are executed (one after the other).
This example tests whether symbol
ghdl_main is visible in the shared libraries, and whether the same symbol name
can be loaded from multiple shared libraries (and used) at the same time.
If the symbol is not found, try adding -g, -rdynamic and/or -O0 when building the shared libraries. Tools such
nm can be used to check if a symbol is visible. For instance,
objdump -d corea.so | grep ghdl_main.
Building multiple designs as separate artifacts and dynamically loading them at the same time is a naive approach to multi-core simulation with GHDL. It is also a possible solution for coarse grained co-simulation with Verilator.
main is executed:
The shared libray is loaded, symbol
print_somethingis searched for, and it is executed.
ghdl_mainis searched for, and it is executed three times. Unfortunately, GHDL does not currently support reseting/restarting the simulation runtime. Hence, in this example the shared library is unloaded and loaded again before calling
ghdl_mainafter the first time.
See Generating shared libraries for further details with regard to the visibility of symbols in the shared libraries.
On GNU/linux, both executable binaries and shared libraries use the ELF format. As a result, although hackish, it is
possible to load an executable binary dynamically, i.e. without using any of the
shared options explained in
Generating shared libraries. In this example, this case is also tested. However, this
is not suggested at all, since it won’t work on all platforms.
ctypes module is a built-in “foreign function library for Python”, which
“provides C compatible data types, and allows calling functions in DLLs or shared libraries”. Thus, it is posible to
reproduce shghdl by loading and executing the simulation from Python, instead of
This example uses the testbench and C sources from exitcb, but the simulation is built as a shared library and loaded dynamically from Python. A helper Python module is used for providing OS agnostic utilities (see pyaux.py).
On some Linux environments, failing simulations that are dynamically loaded from Python do produce an Abortion. This forces the wrapper/caller to exit inmediately, without running any post-check. See ghdl#803 and ghdl-cosim#15 for further details.
This is equivalent to the previous example (py). Instead of calling GHDL’s CLI explicitly, VUnit’s Python aided plumbing is used. This allows including any of VUnit’s VHDL features into the simulation models, which are then to be loaded dynamically from Python. In fact, this is the foundation of VUnit/cosim.
This example is conceptually built on top of shghdl and py;
so it is strongly suggested to carefully read those first. Since Pyhon and
ctypes are used, a good understanding of
the underlaying C syntax and semantics is required.
The main purpose of this example is to showcase how to execute an arbitrary Python function from a VHDL testbench, by calling
it as a regular VHDL procedure/function and passing (complex) parameters from the VHDL domain. Precisely, function
matplotlib.pyplot is used to draw
x,y graphs from constrained arrays of integers in VHDL. The scheme is as
A function prototype is defined in C. This is the definition that will be common to C, VHDL and Python. The prototype in this example is
(int* x, int* y, int l), i.e. two pointers to arrays of integers are passed, and a third argument tells the length.
(optional) A default implementation of the function is written in C. This is just a placeholder/canary.
A function pointer variable is created, and it is initialized to the address of the default implementation.
The function pointer variable is used (dereferenced and executed) either from C, or from VHDL through VHPIDIRECT.
C/VHDL sources are built as a shared library.
From Python, an alternative implementation of the function is written. After loading the library, but before executing the simulation, the function pointer variable is set to the address of the alternative implementation.
As a result, at runtime, when VHDL calls the external function, the Python callback is executed.
For didactic purposes, the run script in this example first uses C and Python only. I.e., arrays are initialized in C and plotted in Python. Then, in subdir pycb, arrays are initialized in a VHDL testbench instead.
In dbhi/vboard: VGA test pattern, this example and VGA (RGB image buffer) are combined for providing a virtual VGA screen using Python’s NumPy, Pillow and Tkinter. Equivalent solutions can be implemented using Python libraries such as matplotlib, panda3d, pygame, cocos2dpy, pyglet, etc. Do you want to take up the challenge? Propose a PR!