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 (*.so, *.dll or *.dyn):

  • 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 ctypes before 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 as objdump, readelf or nm can be used to check if a symbol is visible. For instance, objdump -d | 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.


This example is complementary to shlib, since the VHDL simulation is built as a shared library, which is then loaded from a main C application (as in dlopen).

When main is executed:

  • The shared libray is loaded, symbol print_something is searched for, and it is executed.

  • Symbol ghdl_main is 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_main after 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.


Python’s 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 C.

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


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.


On some Linux environments, using VHDL 2008 works but VHDL 1993 does not return cleanly in case of failure. An Abortion is produced, which prevents the regular after-simulation execution of VUnit. See ghdl#803 and ghdl-cosim#15 for further details.



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 plot(x,y) from matplotlib.pyplot is used to draw x,y graphs from constrained arrays of integers in VHDL. The scheme is as follows:

  • 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!