|
|
5. Debugging
Debugging is a crucial part of any development process. According to some statistics an engineer can spend more time on debugging than on the actual design process! If you are lucky to have one of the high-end simulators like Modelsim/Riviera/NCSim etc to your disposal then you can use the same familiar debugging environment as your VHDL or other HDL language. If not then there are a number of techniques to help you find that "feature" in your design.
5.1 Using VCD files
VCD files were used in the previous examples to look at waveforms. Creating a VCD file is simple:
- Declare a filepointer variable
- Open the VCD file for writing
- Add the signals you are interested in
- Run your simulation
- Close the file
- Open the VCD file in a waveform viewer
The code fragment below shows how to set up a VCD file in sc_main()
sc_trace_file *fp; // Declare FilePointer fp
fp=sc_create_vcd_trace_file("wave"); // Open the VCD file, create wave.vcd file
sc_trace(fp,clk,"clk"); // Add signals to trace file
sc_trace(fp,din,"din");
sc_trace(fp,dout,"dout");
sc_start(100, SC_NS); // Run simulation
sc_close_vcd_trace_file(fp); // close(fp)
The VCD filename is specified with the sc_create_vcd_trace_file command, note the missing .vcd extension in the filename "wave" argument. The third argument in sc_trace is the label that will appear in your VCD file.
The above examples shows how to add some local signals (sc_signal/sc_clock) to a VCD dump file. If you want to includes signals buried inside your design hierarchy then you must use the '.' operator as shown below
risc DUT("risc"); // Instantiate Device Under Test
sc_trace(fp,DUT.sig1,"DUT.sig1"); // Dump signal DUT signal sig1
sc_trace(fp,DUT.comp1.sig2,"DUT.comp1.sig2");// Dump signal sig2 of DUT component comp1
5.1.1 Calling sc_trace inside a module
If you want to call sc_trace inside a module then you have 2 options, either you open/close the VCD file in sc_main() and pass on the filepointer to the module or you create the VCD inside the module. An example code fragment of the first option is shown below:
int sc_main(int argc, char* argv[])
{
sc_trace_file *fp; // Declare VCD filepointer
fp=sc_create_vcd_trace_file("wave"); // open(fp), create wave.vcd file
dff DUT("dff",fp); // Instantiate DUT, pass on filepointer during dff instantiation
sc_close_vcd_trace_file(fp); // close(fp)
}
SC_MODULE(dff) {
sc_in<bool > clk;
sc_out<bool > dout;
sc_trace_file *fp; // Declare Local VCD filepointer
dff(sc_module_name _n, sc_trace_file* _fp) : sc_module(_n), fp(_fp) {
sc_trace(fp,clk,"clk"); // Add signals during construction
sc_trace(fp,dout,"dout");
}
};
The filepointer is declared and opened before passing it on as an argument to the DUT module. When the dff module is constructed the argument filepointer _fp is copied to the local fp value.
Instead of passing on the filepointer as an argument, the VCD file can also be created inside the module. During the elaboration stage (just before simulation) all constructors are called hence the constructor is a good place to open the VCD file and log the signals. Similarly, at the end of simulation the destructors are called (or more accurately when the module goes out of scope) and this can be used to close the VCD file. Note that this is not a requirement, the file can be created/closed at any time during the simulation provided that sc_trace is called after sc_create_vcd_trace_file.
int sc_main(int argc, char* argv[])
{
dff DUT("dff"); // Instantiate DUT
}
SC_MODULE(dff) {
sc_in<bool > clk;
sc_out<bool > dout;
sc_trace_file *fp; // Declare Local VCD filepointer
SC_CTOR(dff) { // Constructor
fp=sc_create_vcd_trace_file("wave");// open(fp), create wave.vcd file
sc_trace(fp,clk,"dff.clk"); // Add signals during construction
sc_trace(fp,dout,"dff.dout");
}
~dff() { // Destructor
sc_close_vcd_trace_file(fp); // close VCD file
}
};
There are 2 additional commands that can be used with VCD files. The first one is the tracing of Delta Cycles. This can be done by setting the delta_cycles variable to true as shown below. Note that delta cycles are incorrectly displayed as timesteps in the resulting VCD file. You can also add some comments to the VCD file using the write_comment method.
sc_trace_file *fp; // Declare VCD filepointer
fp=sc_create_vcd_trace_file("wave"); // Open wave.vcd file
fp->delta_cycles(true); // Enabling tracing of delta cycles
fp->write_comment("From Regression Test 3");// Add some comments to the VCD file
As far as I know no VCD viewer will display this information in any form, however, a bit of TCL can be used to filter out the $comment strings from the VCD file which might have its uses.
$comment
From Regression Test 3
$end
5.2 Using textio
There are occasions were you need some textual output from your simulation run instead of a waveform. Because SystemC can be used for both hardware and software modeling the textual I/O support is very good and obviously much better than what can be done in a Hardware Description Language like VHDL. If you want to write some formatted strings to your simulator log then you will quickly find out that doing this in VHDL is quite cumbersome. Luckily there are several free VHDL packages available on the web that can help you out. For some of my projects I use a free library that was presented during the 2004 Synopsys SNUG conference. This package is very simple to use and will give you similar formatted textio capability in VHDL as you will find in the stdio.h C library. For example, to print a 4 bits std_logic_vector in unsigned, signed and hex occupying 3 position with leading zero you just include the C library and use the C printf function as shown in the code fragment below:
LIBRARY C; -- From http://bear.ces.cwru.edu/vhdl/
USE C.stdio_h.all;
p1:process
begin
printf("Counter in unsigned=%03u signed=%03d hex=%03X\n",cnt,cnt,cnt);
end process;
# Counter in unsigned=007 signed=007 hex=007
# Counter in unsigned=008 signed=-08 hex=008
# Counter in unsigned=009 signed=-07 hex=009
# Counter in unsigned=010 signed=-06 hex=00A
Doing this in SystemC is very similar for the simple reason that you can use C code in C++/SystemC,
void p1() {
printf("Counter in unsigned=%03u signed=%03d hex=%03X\n",cnt.read().to_uint(),cnt.read().to_int(),cnt.read().to_uint());
}
Note the .read() method to read the value followed by .to_int() method which converts the value to a int type before passing it on to the printf function.
It is generally recommended to use C++ functions because the C functions are not type safe. For example, you can change one of the above arguments from %03d to %s (string) which the compiler will happily except but your simulation will crash. Note: you can catch these errors using the gcc -Wall command line argument. Using C++/SystemC iostream facilities the above printf changes to:
#include <iostream>
#include <iomanip>
using namespace std;
void p1() {
cout << "Counter in unsigned=" << dec << setfill('0') << setw(3) << cnt.read().to_uint();
cout << " signed=" << dec << setw(3) << cnt.read().to_int();
cout << " hex=" << hex << setw(3) << cnt.read().to_uint() << endl;
}
There is too much functionality in stdio and iostream to describe in this short SystemC tutorial, instead the table below shows some common used constructs:
|
Function |
SystemC iostream |
|
Print a Decimal number |
cout << dec << ... |
| Print a Hexadecimal number |
cout << hex << ... |
| Print an Octal number | cout << oct << ... |
| Print a newline |
cout << endl // or cout << "\n"; |
| Use n character to print in | cout << setw(8) << ... |
| Pad leading spaces with character 'c' | cout << setfill('0') << ... |
| Reset printing width | cout << setw(0) |
| Reset setfill character | cout << setfill(' ') // reset to space |
| Print SystemC copyright | cout << sc_copyright() |
| Print SystemC release and version | cout << sc_release() << sc_version() |
| Print simulation time |
cout << sc_time_stamp() |
| Print simulator resolution | cout << get_time_resolution() |
Note: some of the formatting manipulators like dec/hex/oct are sticky, that is, you only have to set them once, for example:
cout << 10 << hex << 11 << 12;
cout << 13 << 14 << dec << 15 << 16 << endl; // Output result is "10bcde1516"
5.3 Using Assertions
Assertions are a powerful debugging aid since it enables you to embed monitors in your design that are always active, that is, they always look for violation when your design is running. If an error or violation does occur then the user can be notified immediately, this in contrast to a testbench check were you have to apply the correct stimuli such that the error propagates to the toplevel. If it does then you have to trace back and figure out were the error occurred which is not always straightforward.
Like VHDL, SystemC also supports the assert statement, both will trigger when the condition is false. The SystemC assert is called sc_assert() although the C/C++ assert() statement can also be used but the output is slightly different as shown below.
|
VHDL |
SystemC Equivalent |
SystemC Kernel Output |
|
assert NOT(a='1' AND b='1') |
sc_assert (!(a.read()==true && b.read()==true)); |
Fatal: (F4) assertion failed: !(a.read()==true && b.read()==true) |
|
assert(!(a.read()==true && b.read()==true)); |
assertion "!(a.read()==true && b.read()==true)" failed: file "assert_test.h", line 18 |
Note the missing VHDL report clause, using sc_assert or the C/C++ assert does not allow you to print a text message to the simulation log. However, SystemC support 4 additional macros which can be used to report different kinds of exceptions.
|
VHDL |
SystemC Equivalent |
SystemC Kernel Output |
|
report "string" severity Note |
SC_REPORT_INFO("string1",string2") |
Print string1, string2 |
Using one of the above macros one can easily create a similar assertion action and reporting style as in VHDL (Modelsim output used).
|
VHDL |
SystemC Equivalent |
|
assert NOT(a='1' AND b='1') |
if (a.read()==true && b.read()==true) { |
|
VHDL Modelsim |
SystemC Kernel Output |
|
# ** Failure: Both inputs are high |
SystemC 2.2.0 --- Jul 5 2007 12:37:47 |
5.4 Using a Debugger
If you want to single-step through your code, set breakpoints, observer/watch signals and processes then you need to use a proper debugger. There are a number of excellent free debuggers you can use under Windows and Linux. GNU's gdb is probably the best know Linux one. Since gdb is a command line debugger you might want to add one of the many graphical front-ends which makes debugging a lot easier. Example of popular graphical front-ends are DDD, Insight and Kdbg. I have tried DDD under Cygwin-X but the performance was too slow, if you want to run Linux under Windows I would suggest you check out the free VMWare Player product and this page.
5.4.1 GDB debugger
If you are learning a new language you most likely will write small programs. In this case a command line debugger like gdb is probably quicker to use than a full blown graphical debugger. In the next section I am going to show a few simple gdb command to debug your design. I will be using Cygwin running on Windows.
To prepare the executable for gdb debugging just add the -ggdb (or -g) flag to g++ compiler flags (add to your makefile)
g++ -ggdb -Wno-deprecated -Wall -I. -I.. -I/usr/local/systemc-2.2/include -c sc_main.cpp
Your executable now has additional information like symbol tables than can help you during the debugging process. The executable will be slightly larger and slower but you probably won't notice this.
After compiling and linking your design you can run the debugger using:
gdb myprog.exe
Next set a breakpoint at the beginning of sc_main (for example break sc_main.cpp:1) or better some lines before were you think the program is failing. You have to do this unless you want to single step through hundreds of SystemC kernel code until you reach sc_main/your_module. After setting the breakpoint issue the "run" command:
$ gdb run.exe
GNU gdb 6.5.50.20060706-cvs (cygwin-special)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-cygwin"...
(gdb) break sc_main.cpp:1
Breakpoint 1 at 0x401150: file sc_main.cpp, line 1.
(gdb) run
Starting program: /home/Hans/SystemC/website/gdb_test/run.exe
Loaded symbols for /cygdrive/c/WINDOWS/system32/ntdll.dll
Loaded symbols for /cygdrive/c/WINDOWS/system32/kernel32.dll
Loaded symbols for /usr/bin/cygwin1.dll
Loaded symbols for /cygdrive/c/WINDOWS/system32/advapi32.dll
Loaded symbols for /cygdrive/c/WINDOWS/system32/rpcrt4.dll
SystemC 2.2.0 --- Jul 5 2007 12:37:47
Copyright (c) 1996-2006 by all Contributors
ALL RIGHTS RESERVED
Breakpoint 1, sc_main (argc=1, argv=0x7f2f90) at sc_main.cpp:12
12 {
Current language: auto; currently c++
(gdb) next
13 sc_signal<sc_logic > reset;
(gdb)
14 sc_signal<sc_int<4> > count;
(gdb)
16 sc_clock clk("clk",10,SC_NS,0.5,10,SC_NS,false); // Create a clock signal
(gdb)
18 counter DUT("counter"); // Instantiate Device Under Test, start at 32
(gdb)
20 DUT.clk(clk);
(gdb)
You can single step line by line using the "step" command. If you don't want to step into a function but just consider it as a single statement then use the "next" command. You only have to type the command once, after that each "carriage return" will issue a new step/next command. If you want more info on any gdb command just type "help" optionally followed by the command.
If your simulation crashes you most likely get a Segmentation Fault (trying to access memory outside your allocated region which is intercepted by the OS).
$ ./run.exe
SystemC 2.2.0 --- Jul 5 2007 12:37:47
Copyright (c) 1996-2006 by all Contributors
ALL RIGHTS RESERVED
Counter in unsigned=000 signed=000 hex=(null)
26 [main] run 2760 _cygtls::handle_exceptions: Error while dumping state (probably corrupted stack)
Segmentation fault (core dumped)
You can examine the "core dumped" file using the --core option on gdb. But before you can do this you need to set an environmental variable that calls the dumper.exe program when your program crashes. There are 2 ways to set this variable:
in your cygwin.bat file:
set CYGWIN='error_start=C:\cygwin\bin\dumper.exe'
or in your /user/home/.bash_profile file
export CYGWIN='error_start=C:\cygwin\bin\dumper.exe'
Next when you run your simulation and it crashes a <program_name>.exe.core file will be produced. You can then open the core dump in gdb using:
gdb --core=myprog.exe.core myprog.exe
followed by issuing the gdb command "backtrace full". A few notes, the info produced rarely gives me any useful info and on one of my machines (Cygwin version 1.5.24(0.156/4/2) the dumper.exe program hangs whereas on a second machine using an older version of Cygwin (1.5.21) it works fine.
Some useful gdb commands
|
Function |
gdb command |
| Start program, stop at the first line of code | start |
| Set a breakpoint on line n | break n |
| Set a breakpoint in <file> line n | break file:n |
| Set a breakpoint on a C++ function <func> see note1 below | break class::func(argument) |
| List all breakpoints set | info breakpoints |
| Ignore a breakpoint N M times | ignore N M |
| Clear breakpoint at line n | clear n |
| Clear breakpoint in <file> line n | clear file:n |
| Clear all breakpoints | clear |
| Run program or until breakpoint | run |
| Run program with arguments arg1..argn | run arg1 arg2 arg3 |
| Stop running program | kill or CTRL-C |
| Contine execution were left off (e.g. after a breakpoint) | continue |
| List source | list |
| Single Step 1 line of code, skipping functions | step |
| Single Step 1 line of code, continue into functions | next |
| Examine variable n | print n |
| Print inheritane relationship and other info | ptype typename |
| Stop when C++ throws an exception | catch throw |
| Stop when C++ catches an exception | catch catch |
| Get a backtrace | backtrace |
| help menu | help or help <command> |
| Quit gdb | quit |
Note1: Setting a breakpoint on a function requires you to list the class, function name and arguments. The reason for this is that C++ is polymorphic (similar to overloaded functions in VHDL, i.e. multiple functions with the same name but different arguments), so you must tell break which version of the C++ function you want to break on, you can do this by listing the argument types. You need to do this even if there is only 1 function!