These days I’m exploring the OpenRisc processor and the tools that are available to simulate its Verilog code. I found out that it’s possible to debug the software that is running on the core inside an RTL simulation using GDB. This is quite impressive. Usually, when debugging software, you can see the internal registers of the processor, run the program step by step, put breakpoints… but you don’t see what the hardware is doing around the processor. For example, setting a variable in the program causes the processor to initiate a write transaction on the system bus, and the transaction puts a value into the memory at the address where the variable is. When dealing with systems on chip (SoC) there are many peripherals involved, and seeing the effect of the software on the hardware is very useful both for the embedded software developer and the hardware designer.
In the embedded world you usually have a target microprocessor (integrated on a board) and a development PC, and they are connected through a debugging interface (usually a JTAG). The interface allows to upload a program and debug it using a development environment that runs on the PC. An example of this development kit that uses an FPGA to map the processor can be found on the OrSoc product pages. The OpenRisc SoC hardware development tools allows to use an RTL simulator instead of a physical board. Having everything on the PC means that you have visibility of all the digital signals that are moving in the system. It also means that the program will run very slow, as opposed to having a real microprocessor or an FPGA.
I took a screenshot of a debugging session that I performed on my Ubuntu PC to show an example of what can be done.
- The RTL simulator (Icarus Verilog) is:
- simulating the OpenRisc system;
- dumping the signal waveforms into a VCD file;
- listening for incoming GDB connections to port 5555;
- printing on the terminal the data that OpenRisc sends to the UART.
- DDD is opened using or32-elf-gdb as the back-end debugger, and the debugger is connected through port 5555 to the RTL simulator. I set a breakpoint in the source code, and the execution reached it and stopped.
- GTKWave is reading the VCD file and showing the waveforms. In particular it is showing:
- the JTAG interface, that is passing the GDB commands to the OpenRisc and returning related information back to the debugger.
- the UART pins (tx and rx); it can be noted that the tx pin is moving because it is sending the string “Execution starts, 10 runs through Dhrystone\n” that is eventually printed on the terminal.
How to try it yourself
Install the prerequisites:
- Icarus Verilog
- GNU GCC, Make, …
In Ubuntu you can do it from the command line:
sudo apt-get install build-essential ddd verilog gtkwave make gcc g++ flex bison patch texinfo libncurses-dev svn
Install the OpenRisc toolchain following the instructions here: http://opencores.org/openrisc,gnu_toolchain
When the procedure ends, add the toolchain path to your PATH environmental variable; for example I added at the end of my
Then it’s time to download the RTL description of the OpenRisc. You need to sign in an OpenCores account to download the source. To download the same version that I tried, run:
Press Return once if the username does not match, then enter your OpenCores username and password. Now you have a directory called orpsocv2 that contains the source code for the OpenRisc System On Chip.
To start the simulation, enter from the orpsocv2 directory:
make rtl-debug VCD=1
The “VCD=1″ option enables output of waveforms into the “sim/results” directory. The command will compile the Verilog code, then the simulator will start and it will wait for a GDB connection on TCP port 5555. In my case the output ends with:
#### Beginning simulation with VPI debug module enabled #### JTAG debug module with VPI interface enabled Starting RTL simulation of dhry-nocache-O2-vpi test VCD in /home/francesco/src/openrisc/orpsocv2/sim/run/../../sim/results/dhry-nocache-O2-vpi.vcd VCD info: dumpfile /home/francesco/src/openrisc/orpsocv2/sim/run/../../sim/results/dhry-nocache-O2-vpi.vcd opened for output. JTAG ID = 14951185 Stall or1k Read npc = 00003278 ppc = 00003274 r1 = 00018f14 Waiting for gdb connection on localhost:5555 Press CTRL+c and type 'finish' to exit.
To attach the debugger, open another terminal and enter from the orpsocv2 directory:
ddd --debugger or32-elf-gdb -- dhry-nocache-O2.or32
It will start the OpenRisc GDB debugger giving it the or32 executable.
dhry-nocache-O2.or32 contains the code and the debug symbols of the binary that is being run by the simulator. When the debugger opens, run inside it:
target remote localhost:5555
You can now control the simulation, let it continue and set your breakpoint. To see the waveforms of the RTL signals, open another terminal and enter from the orpsocv2 directory:
You can select from the menu “File -> Reload Waveform” to update the signals to the current simulation time. It can be noted that, even when the processor appears stopped from GDB, the simulator continues to run.
The main trick in order to implement this functionality is to use the Verilog Procedural Interface (VPI). VPI is the glue that connect the Software (C) world with the RTL (Verilog) world. With it, a C program can connect and interact with a Verilog simulator; in our case the C program is a GDB server, and on the other side it connects to a behavioral module whose responsibility is to drive and capture the pins of the JTAG interface. The sources of these two components can be found under the orpsocv2 subdirectories:
Here is a schema of what’s happening:
The user interfaces to the developer are the or32-elf-gdb command line, and the VCD files dumped by the OpenRisc Verilog simulator. So, when a GDB command is issued it goes through the TCP connection on port 5555, then it is passed to the GDB server connected to the simulator that tells the Debug Module how to move the JTAG pins; the OpenRisc understands the JTAG transactions and responds to the initial command, and everything that the hardware does can be seen on the waveforms that are dumped to file. It takes seconds to run a single GDB command, but the slowness of the execution is caused by the fact that Icarus Verilog must simulate every involved signal of the system accordingly.
I think this is a powerful example to understand what happens at a hardware level when code is running. Unfortunately the RTL designs of the most used processors are closed to the eyes of the public, so it’s not possible to understand what’s happening inside our PC or inside our smartphones at this level. In any cases, exploring OpenRisc gives an idea of how microprocessors work in general.