Joint RISC-V Code and Logic Debug with rv32 and OSVVM Co-simulation
Introduction
In the previous OSVVM co-simulation article, I wrote about debugging co-simulation programs with GDB and how to add an IDE on top of this to give full dual debug capability with a software IDE, such as Eclipse, working alongside a GUI based logic simulator, such as Riviera-PRO or QuestaSim. This was about debugging the program that’s running on an OSVVM co-simulation node.
One of the programs that can be run as a co-simulation program is the rv32 RISC-V instruction set simulator (ISS), written in C++. This ISS can run RISC-V binaries compiled from application source code using the RISC-V GCC toolchain. It would be good if we could debug these programs, but the methods described in the last article are for the software running as co-simulation program and , in this case, this is the ISS—and we’ll assume that this is working. However, we can still debug code that the ISS is running.
The rv32 ISS has code that can emulate a gdbserver program, which GDB can communicate with over a TCP/IP socket as if it is remote hardware. When gdb is started, a “target remote <port address>” command can connect to an open TCP/IP socket over which “machine interface” commands can be sent by gdb that the gdbserver code, running on the target RISC-V processor, will interpret. In our case, though, rv32 GDB code, that sits on top of the main ISS, interprets these commands and makes appropriate calls to the ISS’s API. The difference here, though, is that additional gdbserver code, added to the application, is not running as would be the case on hardware, and the rv32 GDB code does not interfere with the timings of the running application.
So, in this article I want to cover using GDB and an IDE when running rv32 in OSVVM co-simulation. I have made this as a separate article as, strictly speaking, this is not part of OSVVM co-simulation but is a feature of the rv32 ISS which can also be used when rv32 is run as a stand-alone program. In fact, the rv32 manual covers using the GDB features and setting up an IDE and also I’ve written an article on the GDB remote target interface and its machine interface commands and using this with rv32. But what I haven’t covered anywhere before is using this in an OSVVM environment and having dual debug capability with hardware in a logic simulation. For the rest of this article, then, I will give this the same treatment that was given to debugging OSVVM co-simulation code but for RISC-V code debug that’s running on the rv32 model. Strictly speaking, one could also debug co-simulation code (running on a different node to the ISS, say) along with the RISC-V code and the logic. I’ve not tried this, and it’s probably hard to get one’s head around, so I’m not recommending it.
An rv32 and OSVVM Co-simulation Scenario
Before embarking and the details of setting up GDB and an Eclipse IDE it is worth setting the context of when this might be used within an OSVVM environment. The diagram below gives one such possible scenario.

The diagram shows a simple setup where, in the VHDL at the bottom of the diagram, there is a process, ManagerProc, that drives a model independent transaction (MIT) signal, ManagerRec, connected to an AXI4 manager verification component (VC). This takes the generic transaction requests on the ManagerRec signal and converts them to protocol specific signalling—in this case AXI4 manager signalling—which, in turn, drives a subordinate interface on the design-under-test (DUT).
The MangerProc process is a co-simulation test process and drives the ManagerRec MIT signal via calls to the CoSimTrans procedure (and CoSimInit for the node would have been called first but is not shown). This means that, ultimately, the signal is driven from the running co-simulation program by making calls to the API. In this scenario the rv32 ISS has been integrated with some additional code making use of its callback features to intercept memory accesses and make appropriate calls to the co-simulation API to generate the MIT requests. There is also an IrqProc process that reads any interrupt state from the DUT and calls CoSimIrq if that state changes. This state gets saved in the integration code for the ISS to read via its interrupt callback. I have written about this integration process in a little more detail in a section of a previous article on OSVVM co-simulation.
Of course, this setup is run on a logic simulator which may be GUI based, such as QuestaSim or Riviera-PRO, and so there is full logic visibility. With the integrated ISS now the running co-simulation program there is potential to use its GDB remote interface. Normally, the integration software sets up any configuration, registers the callback functions, loads the executable to be run, and then calls the ISS’s run() method to start the code running. If configured to run in debug mode, though, the rv32gdb_process_gdb() function is called instead. This takes the ISS object as an argument and also a port number as the one it will use in its TCP/IP socket server code. It also takes the rv32 configuration structure as an argument for the ISS to use. The application code won’t be run at this point, but it will initialise the TCP/IP socket and advertise a port number to the console—and then it will wait for a connection. It is at this point that we get gdb involved and, as we shall see, this allows an IDE, such as Eclipse, to be added on top of this, as shown in the diagram. So now, as for normal co-simulation debug, we have full software visibility along with logic visibility.
Debugging RISC-V code with GDB
Unlike in my previous article, the procedures for using GDB with executable simulators such as GHDL and NVC, and, as described in the next section, for GUI simulators is the same as we’re connecting to the rv32 model in both cases. One other thing to note that is different from the normal co-simulation setup is that it isn’t strictly necessary to have a “stop at time zero” mechanism as described in the previous article, as the simulation will block as soon as rv32gdb_process_gdb() is called and connection can be made without the simulation advancing time. However, it is highly recommended that this is still done, especially for GUI simulators, if there is any intention of inspecting logic state in the debug session and I will assume this in the rest of this article.
When a simulation is run in debug mode, with rv32 called via rv32gdb_process_gdb, the simulation will pause and the model will advertise a port number for GDB connection with a message such as:
RV32GDB: Using TCP port number: 49152
We can then run the RISC-V toolchain’s GDB and an example session is shown below:
riscv64-unknown-elf-gdb test.exe
GNU gdb (GDB) 9.1
<gdb blerb output not shown>
Reading symbols from test.exe...
(gdb) target remote :49152
Remote debugging using :49152
_start () at crt0.S:55
warning: Source file is more recent than executable.
55 csrrwi zero, mstatus, 0 // Clear mstatus; disable machine-level interrupts
(gdb) list main
50 #define BUFSIZE 2048
51
52 static char obuf [BUFSIZE];
53
54 int main (int argc, char** argv)
55 {
56 uint32_t* uartptr = (uint32_t*)UART_BASE_ADDR;
57
58 // Compress in-built message data and place in obuf, returning
59 // length of compressed data
(gdb)
60 int comp_size = compress(message, obuf, strlen(message));
61
62 // Send compressed data to UART
63 for (int idx = 0; idx < comp_size; idx++)
64 {
65 *uartptr = obuf[idx];
66 }
67
68 return 0;
69 }
(gdb) b 60
Breakpoint 1 at 0x722: file test.c, line 60.
(gdb) c
Continuing.
Breakpoint 1, main (argc=0, argv=0x0 <_start>) at test.c:60
60 int comp_size = compress(message, obuf, strlen(message));
Here we fire up GDB using the riscv64-unknown-elf-gdb command with the RISC?V executable code, though your toolchain’s prefix may differ slightly depending on what you have installed. The important thing to remember is that you can’t use the normal gdb command for your host computer. We can now connect to the rv32 remote target port using target remote :49152 (assuming rv32 advertised its default port number). On the simulation console it should’ve acknowledged the attachment:
RV32GDB: host attached.
In the example above the main function is listed and a breakpoint set up at line 60. We can then “continue” and the RISC-V code and the simulation will start running again until our breakpoint is reached. At this point we can do normal debug things like step into the compress function, inspect state, set new breakpoints etc. At any point where the program is continued the simulation will then run freely. If, in a GUI simulation, a “stop at time zero” mechanism was set, then it may be that the simulation was run for a set time, say 1 ms, and the simulation will pause at this point so that logic waveforms may be inspected and using the simulator’s other debug facilities. The simulation may then be run further, or to the end, but any new breakpoints that were set in the code will pause the simulation and program debug may recommence—and so on until the simulation is complete.
Configuring and Using an IDE with RISC-V Code
Setting up an IDE on top of GDB is similar for RISC-V code as it was for OSVVM co-simulation code. The main differences are that we must use the RISC-V toolchain’s GDB and that we need to set up for a remote target instead of attaching to a running process. In fact, as far as the IDE is concerned, the target looks like a piece of hardware, such as a development board, and we will set things up accordingly.
As for the co-simulation setup, I will be using Eclipse 2026-06 in a Windows environment using mingw-w64 as the example, but other IDEs should be similar, and this is just as applicable in a Linux environment.
Like for co-simulation code, we will assume there exists a makefile to compile our RISC-V application already, so when Eclipse is run a new project is created by right clicking in the “Project Explorer” and selecting “Import” as shown below:

Once this is done a new window appears to make some selections:

For this example, we will choose “Existing code as a Classic Makefile Project” as the code already exists and there is an associated makefile. This brings up a new window as shown below:

We must give the project a name in the top text box, and we can “Browse” to the directory where the code is located. Ensuring that C and C++ check boxes are selected, for this setup we choose “<none>” for the toolchain. We can then click on “Finish” and our new project will appear in the “Project Explorer” tab. We now need to configure for debug, so right clicking on the project and selecting “Properties” from the menu that popped up, we get the following window:

As mentioned before, the IDE will see the rv32 model running in the OSVVM simulation as a remote piece of hardware running gdbserver over a TCP/IP socket. Therefore, we select “GDB Hardware Debugging” and press the symbol in the top left of the pane to add a new configuration. This opens up a new window as shown below.

On the “Main” tab we must just use the “Browse” button to find the RISC-V executable file we want to debug, and to select “Disable auto build” as this setup has no visibility of the building that’s to be done with the make file. We then select the “Debugger” tab:

For this tab we must change the “GDB Command:” to riscv64-unknown-elf-gdb, from gdb, in order to use the RISC-V toolchain’s debugger. This assumes that it is the search path and has this name, but whatever appropriate setting can be used to point to an appropriate RISC-V GDB. “Use remote target” should already be checked and the “Debug server:” set for “Generic TCP/IP” with “Protocol” set as “remote”. The “Connection” text box will probably need changing to change the default 1234 port number to the rv32 default of 49152. We can now apply this and close the window, ready to debug with this configuration when the simulation is run.
We can open up the source code for the example—in this case test.c— and we may want to address some of the warnings that will be displayed as it doesn’t know the paths for the includes. Selecting the project’s properties and then, under “C/C++ General”, choose the “Paths and Symbols”.

Here, for this example, two entries are added using the “Add…” button, which pops up a new window where the “File system” button is used to locate the appropriate directory. It is also worth checking the “Add to all languages” and, if appropriate, “Add to all configurations”.
As a minimum, we must add the path for all the standard headers supplied with the RISC-V tool chain. For the MSYS2/mingw-w64 set up this is the path shown first (your installation path may be different). The second path is just for this example as it uses code from the directory above, and that path is the second entry.
With this done we can set a breakpoint in the code, such as at line 60, just as for the GDB example earlier:

Now, when the simulation is run in debug mode and the simulator stops at the point of waiting for an attachment we can run the debug configuration we set up under “Run”->”Debug Configurations…”, select the “Rv32Demo” we created, and click on the “Debug” button. This will attach to the running TCP/IP debug port. At this point the simulation is blocked on the rv32 model, and we can start running the RISC-V program by continuing the debug session by pressing the ‘resume’ button in the IDE icons above the editor (or select from the “Run” menu). This will run until our breakpoint is hit and, just as for GDB, we can step, inspect state and so on. We can resume again and the simulation will continue until the end, or to any other break condition, or we hit another software break point. And so we can dual debug RISC-V software and simulated logic, just as was described earlier for GDB.
Setting Up the IDE to Build
Since we have a make file to compile the RISC-V code it is fairly straight forward to set up the IDE to use this to compile the code. We will use this make file to decide whether compilation is required, so the IDE will always call make. If we get the properties of the project and select “C/C++ Build”, we get the following window:

Most of the “Builder Settings” Tab remain as the defaults, but it is worth setting the “Build directory:” text box, using the “File System…” button to point to the directly where the program is located. We then select the “Behavior” tab:

The default setting on this tab should also be suitable as the example makefile uses “all” to do an incremental build and also has a “clean” target. After “Apply and Close”, the IDE should be able to build by right clicking the project and selecting “Build Project”. The make command will then be run to do the necessary compilations.
We now have the IDE integrated with the rv32 model running with OSVVM co?simulation and the logic simulator for full joint debug capability, with the IDE debugging the software and the simulator debugging the HDL logic in tandem.