Hierarchy


index


AppendixA

     

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:

  1. Declare a filepointer variable
  2. Open the VCD file for writing
  3. Add the signals you are interested in
  4. Run your simulation
  5. Close the file
  6. 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')
   severity failure;

sc_assert (!(a.read()==true && b.read()==true));

Fatal: (F4) assertion failed: !(a.read()==true && b.read()==true)
In file: assert_test.h:18
In process: assert_test.p1 @ 30 ns
Aborted (core dumped)

 

assert(!(a.read()==true && b.read()==true));

assertion "!(a.read()==true && b.read()==true)" failed: file "assert_test.h", line 18
Aborted (core dumped)

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
report "string" severity Warning
report "string" severity Error
report "string" severity Failure

SC_REPORT_INFO("string1",string2")
SC_REPORT_WARNING("string1",string2")
SC_REPORT_ERROR("string1",string2")
SC_REPORT_FATAL("string1",string2")

Print string1, string2
as SC_REPORT_INFO + file,line,process and time
as SC_REPORT_WARNING + throw exception
as SC_REPORT_WARNING + abort()

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')
   report "Both inputs are high"
   severity failure;

if (a.read()==true && b.read()==true) {
   SC_REPORT_ERROR("","Both inputs are high");
}

VHDL Modelsim

SystemC Kernel Output

# ** Failure: Both inputs are high
#    Time: 30 ns  Iteration: 1  Process: /assert_test_tb/dut/line__24 File: assert_test.vhd
# Break in Architecture rtl at assert_test.vhd line 23
# Stopped at assert_test.vhd line 23

             SystemC 2.2.0 --- Jul  5 2007 12:37:47
        Copyright (c) 1996-2006 by all Contributors
                    ALL RIGHTS RESERVED

Error: (E1) : Both inputs are high
In file: assert_test.h:15
In process: assert_test.p1 @ 30 ns

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!

 

 


home

 


Hierarchy


index


AppendixA