Debugging OSVVM Co-simulation Programs
Introduction
Co-simulation features were added to OSVVM at release 2023.01. This allows C or C++ code to be compiled for the machine hosting a logic simulator to be run as part of a logic simulation and interact with that simulation via a provide API, and I have written an introductory blog on osvvm.org in the past and there is, of course, a manual.
With the introduction of software into a logic simulation there is the question of how this is going to be debugged—beyond using printf or equivalents. For some simulations, as we shall see, this is almost identical to debugging any normal executable. For more elaborate GUI based simulators, additional steps need to be taken, but the debugging tools are aware of such issues and provide features which make this straightforward as well.
To start with the debugging of co-simulation programs we will start by using GDB. Once a connection to debugging is made via GDB, it becomes possible to use an IDE, such as Eclipse, for a fully featured debugging environment and we shall take a look at how to set this up later. With the IDE setup for debugging, it also becomes possible to build the simulation code from the IDE as well, to make the debug-fix cycle more efficient—though for normal regression testing, building the co-simulation code should be done from within OSVVM scripts. So we’ll finish with looking at setting up for compilation.
So, the aim is that, by the end of this article, we will know how to debug OSVVM co-simulated code in a full IDE debug environment. An additional benefit, though, is that we will also be running a logic simulation and can use the simulator’s features to debug the logic, so we can fully dual-debug software and logic in the same verification environment. This is especially true of GUI based simulators, such as QuestaSim and Riviera-PRO. Particularly when running software on an ISS, both the target embedded application software can be co-developed and co-debugged with its target logic hardware. So, knowing how to use these tools together provides for some powerful debugging and development environment, whilst also getting all the benefits of the OSVVM verification methodology that underpins all this.
Debugging with Executable Simulators and GDB
OSVVM co-simulation is supported on a couple of open-source VHDL simulators—namely GHDL and NVC. Unlike commercial simulators, whether community editions or licenced, they have no direct GUI support and rely on outputting logging information that can be interpreted by a waveform viewer, such as GTKWave, post-simulation. For these simulators we can still debug co-simulation code by running the simulator’s executable itself as the program to debug.
GHDL
GHDL is by far the easiest to debug as the output of the compilation process is an executable for the host machine. Therefore we can just debug this generated executable as the co-simulation user program shared object (VUser.so) is loaded at runtime, though we will need to load the symbols from this library to reference the code in the debug session.
For example, assuming we have compiled for the OSVVM co-simulation test TbAb_CoSim, running the co-simulation test code usercode_size, then an executable will have been produced in the run directory called tbab_cosim.exe. (This is for MINGW on Windows. On Linux the .exe suffix is not present.) A GDB debug session for this might look like the following:
gdb tbab_cosim.exe
<gdb blerb output not shown>
(gdb) add-symbol-file VUser.so
Add symbol table from file "VUser.so"
(y or n) y
Reading symbols from VUser.so...
(gdb) list VUserMain0
92 // then you'll need to read in a configuration file.
93 //
94 // -----------------------------------------------------------------------
95
96 extern "C" void VUserMain0()
97 {
98 VPrint("VUserMain0(): node=%d\n", node);
99
100 std::vector<wtrans_t> vec;
101 wtrans_t wtrans;
(gdb) b 96
Breakpoint 1 at 0x3415d14f8: file C:/git/OsvvmLibraries/CoSim/tests/usercode_size/VUserMain0.cpp, line 98.
(gdb) r
With the session above, a breakpoint was set up on entering the top level VUserMain0 entry function and then the program run with the ‘r’ gdb command. This will then break once the user function is called. From this point the user code can be debugged just as for any normal program, setting further breakpoints, inspecting variable state etc.
NVC
NVC is similar to GHDL but it does not produce an executable but rather generates output which the nvc executable will interpret to run the simulation. However, nvc itself is an executable and it will load the VProc.so library at run-time which then load the VUser.so library containing the user test code. So, to debug with NVC, we debug the nvc executable itself and provide the command line options when running in the gdb session.
To get the command line options when the simulation for a given test is run, then inspecting the logs of the batch run (that presumably failed to require debugging) will reveal the command executed that was used to run the particular test. This can be quite a long set of command line options and it will have used absolute paths, though these will all be relative to the run directory. These options, then, can be cut-and-pasted from the log.
A GDB session of the TbAb_Cosim test, running usercode_size co-simulation software might look like the following, where the run command arguments have had the absolute path prefix removed.
gdb `which nvc`
<gdb blerb output not shown>
(gdb) add-symbol-file VUser.so
Add symbol table from file "VUser.so"
(y or n) y
Reading symbols from VUser.so...
(gdb) list VUserMain0
92 // then you'll need to read in a configuration file.
93 //
94 // -----------------------------------------------------------------------
95
96 extern "C" void VUserMain0()
97 {
98 VPrint("VUserMain0(): node=%d\n", node);
99
100 std::vector<wtrans_t> vec;
101 wtrans_t wtrans;
(gdb) b 96
Breakpoint 1 at 0x3b86515c4: file C:/git/OsvvmLibraries/CoSim/tests/usercode_size/VUserMain0.cpp, line 98.
(gdb) r --std=19 -H 128m --stderr=failure --ieee-warnings=off --work=osvvm_CoSim_TbAxi4:VHDL_LIBS/NVC-1.18-devel/OSVVM_COSIM_TBAXI4.19 -L VHDL_LIBS/NVC-1.18-devel -e --jit --no-save TbAb_CoSim -r --exit-severity=failure --load=./VProc.so TbAb_CoSim
Just as for the example session for GHDL, a breakpoint was set up on entering the top level VUserMain0 entry function and then the program run with the ‘r <options>’ gdb command. This will then break once the user function is called. From this point the user code can be debugged just as for any normal program, setting further breakpoints, inspecting variable state etc.
Debugging with GUI Based Simulators and GDB
There are various GUI based simulators, some of which also have community or starter editions for free. OSVVM co-simulation supports various simulators including ModelSim and QuestaSim from Siemens EDA and Riviera-Pro and Active-HDL from Aldec EDA. A characteristic of these tools is that they spawn multiple processes and the executable invoked isn’t the process that actually runs the simulation. So the methods mentioned for GHDL and NVC don’t apply and a new strategy is required.
Fortunately GDB can attach to running processes, so we need to discern which process we need to attach to. Also, we don’t want the simulation to start executing whilst we try and attach from GDB, potentially missing the point we want to debug. So the test bench itself must have a mode where it runs but then stops at time zero so that GDB can attach to the simulation process before allowing the simulation and the software to continue. So some of the OSVVM co-simulation tests’ top-level test benches define a boolean generic called STOP_AT_TIME_ZERO, which defaults to false. The OSVVM scripting provides a means to set this generic when the simulation is run:
SetLogSignals true SetInteractiveMode true simulate TbAb_CoSim [generic STOP_AT_TIME_ZERO TRUE]
If the top-level test control component (usually called TestCtrl) also has such a generic defined and connected to the test bench generic, this becomes available to the test code. Then, in the control process, say, after some initialisations but before any simulation time passes, the generic can be tested and std.env.stop called if true. E.g.:

When we run the test in the simulator it will stop at the start, ready for GDB attachment.
Note that if we want waveforms generated for the simulation then SetSignals TRUE must be in the script. In order to allow the test in the simulation to be continued after stopping at time zero, then SetInteractiveMode TRUE must be in the script. This was shown in the previous run script above.
So now we need to find the process number of the relevant simulation process. This can be found in a number of ways. On the command line, in windows with MINGW, the tasklist command can be used and, on Linux, the ps command. At a pinch, on Windows, the task manager can also be used to get the process number. The process we’re looking for has different names for the different simulators.

Once we have the process number we can run gdb with the -p command line option, specifying the process number to attach to:
$gdb –p 16372 <gdb blerb output not shown> (gdb) add-symbol-file VUser.so (gdb) list VuserMain0 (gdb) b 150 (gdb) c
We add the symbols from the VUser.so library so we can debug against our test code, for example listing the VUserMain0 function and setting breakpoints etc. Note that, for Active-HDL all the code, including the user test code, is compiled into a single VProc.so shared object, so this is the file from which to load symbols.
At this point the simulator and program are halted, so a continue command sets the code running again. The simulator is paused because the stop command was executed at time zero, so we can now run this—either to the end or for some time specification or other stop condition. If a GDB breakpoint is hit, then the simulator is blocked and debugging can be done on the software in the GDB session. If this is then continued once more in GDB, the simulation starts running again until, perhaps, the run command time expires, when waveforms can be inspected and then the simulation continued once again. And so on, but now with full dual software and logic debug.
Configuring and Using an IDE
Obviously, each IDE is different but they are likely to share many characteristics. So, in this section I’m going to choose a specific IDE to use as a case study. In particular, I will be using Eclipse 2026-06 in a Windows environment using mingw-w64, but there is little difference if using Linux.
This case study will be a step-by-step guide for setting up the IDE for debugging a specific OSVVM co-simulation test’s source code located in <OsvvmLibraries Location>/CoSim/tests/usercode_size.
Since OSVVM already has this test source code and the means to compile it along with the co-simulation code, we can use the existing makefile provided and “import” the code. So, assuming Eclipse has been started and the “Project Explorer” pane is shown, then we can right click on any blank part get a selection, including “import…” which is then selected:

This brings up a new window with various selections and the C/C++ entry should be expanded to reveal four choices, and the relevant one for our purposes is the “Existing code as Classic Makefile Project”.

Selecting this and clicking “Next>” changes the window to a “New Project” view. Various fields on this page must now be filled in. A “Project Name” is needed, and we’ll use OsvvmUserCodeSize for this example, then the location of the test co-simulation source code—and the browse button can be used here. We make sure that both the C and C++ check buttons are checked in the languages frame and finally pick a toolchain—for this example it will be Cross GCC so that we can use our external tools. So the final window will look something like the following:

After pressing “Finish” a new project will appear in the Project Explorer window and expanding this reveals the VUserMain0.cpp co-simulation test code. Double clicking this will open it up in the editor but, indicated on the right-hand side (and inspecting the code), there are a whole lot of warnings. To fix these we must set up some paths to locate relevant headers.
Right-clicking on the project and selecting “Properties…”, right at the bottom of the menu,brings up a new window. Expanding the C/C++ General option we can select “Preprocessor Include Paths, Macros etc.” to change the view to the following:

We want to add a path to the OSVVM co-simulation code to pick up the headers there by selecting ”GNU C++” and the “CDT User Setting Entries”, and then clicking on the “Add…” button on the right to pop up a new window. On this window change the top right selection to be “File System Path” and browse for the location of the CoSim/code directory to get the following window.

When the “OK” button is pressed, the directory should appear under the “CDT User Setting Entries”. This can be repeated for “GNU C” and “Assembly” if required. The “Apply and Close” button is then pressed and the source code should have the warnings disappear after a short delay as the IDE re-indexes.
This has created the project, but we now need to set up for debugging the code on a running simulator. From the main IDE window, selecting “Run” from the top menu bar and then picking “Debug Configurations…” will pop up a new window like the following:

From the choices presented we must choose “C/C++ Attach to Application” and then press the button at the top right, as indicated by the arrow, to generate a new configuration. This changes the window and it will have filled in some entries and look something like the following:

Note, however, that the “Disable auto build” has been checked instead of the default enabled. We then select the “Debugger” tab and in the “Debugger Options” pane, the “Main” tab should need no changes and will look like the following:

Optionally we can make some setting in the “Shared Libraries” tab. Here we can add the directory where the simulation will be run so that the IDE can pick up the VProc.so and VUser.so shared libraries and automatically load their symbols:

We can now “Apply” and “Close” and be ready to debug. Going back to the VUserMain0.cpp code in the IDE editor window we can set a debug point in the VUserMain0 entry function by double-clicking in the left-hand bar adjacent to the relevant source code line, when a blue dot appears. This means the simulation will stop as we enter the co-simulation code.

The simulator will need starting at this point and the simulation run in the normal manner, but with STOP_AT_TIME_ZERO generic set, just as for GDB described earlier. Once the simulation reaches this point we can start debugging the software. In the main window, as per the above diagram, the little ‘bug’ icon at the top left can be pressed to launch in debug mode. This will fire up a new window so we can select the running processes to attach to:

The top text box can filter the processes displayed and the diagram shows what’s displayed when running Riviera-PRO and “riv” is put in the filter box. The process we want in this case is “riviera_simulation_process.exe”, but for Siemen’s simulators, the filter could be “vsim” and then select “vsimk.exe”. Eclipse will then attach (via GDB) and show a “Debug” window with a lot of information.
At this point the simulation was stopped due to the STOP_AT_TIME_ZERO and the debugging session is also paused, which also pauses the simulator process. So, we can resume the debugger and simulation process by clicking the “Resume” icon (that looks like a Play button”) or do the same from the “Run menu”. Everything now starts again, but the simulator is still stopped and so needs a run command—either to run for a certain time, or run to the end, or any other condition. When this is done we will immediately hit the break point in our co-simulation test code:

From this point on, normal debugging can be done, just as for any program with the IDE. The only difference might be is that if the simulator was restarted with a stop condition and the debugged code runs to that point, the simulation will stop and the code pause, but now logic signalling can be inspected and the simulation resumed—and so on, just as for GDB but now in a full IDE environment.
Setting up the IDE for Compilation
As has been mentioned in a previous blog, OSVVM provides support for building the co-simulation code in the scripting. This can come in the form of the MkVproc TCL script or by using and configuring the provided make files. Therefore it is not strictly necessary to have our IDE build the code and we can continue to rely on the compilation in the scripting. However, in a debug cycle, it is often more efficient to compile and re-run from within the same development environment and so it is worth discussing how we can set up the IDE to do this—having remembered that our test run script must skip the normal MkVproc or make compilation step.
We configured our Eclipse environment for an existing make project and we will use the makefile provided by OSVVM to do compilation. Before discussing how Eclipse is set up, it is worth a recap of the provided make file and its configurable make variables that we’ll configure to do the compilation. The following table shows the variables that require configuring with a description:

For our example setup, a call to make might look something like the following:
make -C c:/git/OsvvmLibraries/CoSim \ OPDIR=C:/git/OSVVM/rivieraPro \ SIM=RivieraPRO \ ALDECDIR=C:/Aldec/Riviera-PRO-2025.10-x64 \ USRCDIR=tests/usercode_size \ USRFLAGS="" \ USRLDFLAGS=""
Note that the call made use of the -C option to locate the co-simulation makefile in the CoSim/ directory and any relative references will now be from this location, such as that used in setting USRCDIR. For this example there are no additional compilation or linker flags, and these could have been left off the command. So now we just need to configure the IDE to perform this same make operation. So, right-clicking the project and selecting “Properties” from the menu brings up the properties window and we can highlight “C/C++ build”.
On the “Builder Settings” tab nothing needs changing as we can use the default command, which is make. Also, the “Build directory” can be left at the default as this will be specified directly to the make command. For the “Behavior” tab we must select “Use Custom build arguments” and fill in the “Build arguments” text box with the arguments as shown above for the make command—only now they will have to be on a single line. Also, the “Build (Incremental build)” check box must be checked and a value of “all” in the text box. The “Clean” check box must also be checked and a value of “clean” in the text box. The “Apply and Close” button can now be clicked and we have set up for compiling the code.
To build the code, then from “Project” in the top menu bar, “Build Project” can be selected. This will call make with the appropriate options.

To do a clean of the compilation then “Clean Project” is selected. And this is the final step in setting up for debugging OSVVM co-simulation programs.