This tutorial shows you some basic debugging techniques, including the use of waveform tracing. First, it shows some basic text based methods, then extends the example from the Modules and Processes tutorial to use waveform tracing.
Even without waveforms, some simple techniques can be used to find out what is going on inside your design. These include the use of the name() method and careful placement of text output statements.
SystemC provides overloaded stream insertion operators for the built-in data types, so you can just use statements such as
cout << mydata << endl;
and it will work, even for SystemC data types.
Here is the code of the mon.h from the previous chapter
#include "systemc.h" #include SC_MODULE(mon) { sc_in<bool> A,B,F; sc_in<bool> Clk; void monitor() { cout << setw(10) << "Time"; cout << setw(2) << "A" ; cout << setw(2) << "B"; cout << setw(2) << "F" << endl; while (true) { cout << setw(10) << sc_time_stamp(); cout << setw(2) << A.read(); cout << setw(2) << B.read(); cout << setw(2) << F.read() << endl; wait(); // wait for 1 clock cycle } } SC_CTOR(mon) { SC_THREAD(monitor); sensitive << Clk.pos(); } };
Another use for cout is to find out how your design is built-up before simulation starts. Will the NAND gates of the previous chapter be constructed before the EXOR gate? They should be, but a simple way to prove it is to add a line inside each constructor printing out a message. E.g.
SC_CTOR(nand2) { cout << "Constructing nand2" << endl;
Here is the corresponding output after adding statements like that
Constructing stim Constructing nand2 Constructing nand2 Constructing nand2 Constructing nand2 Constructing exor2 Constructing mon
The disadvantage of this is that it does not show which instance is referred to. This can be overcome by using the name() method. The name() method is defined for most things in SystemC. We can simply say
cout << "Constructing nand2 " << name() << endl;
instead, and now the output is
Constructing stim Constructing nand2 exor2.N1 Constructing nand2 exor2.N2 Constructing nand2 exor2.N3 Constructing nand2 exor2.N4 Constructing exor2 Constructing mon
Note that name() returns what the user specifies in the labels of the instances. Other methods you might want to look at are dump() and print() (have a look at sc_object in the Doulos SystemC Golden Reference Guide to see examples).
The SystemC library supports waveform tracing. This requires adding statements to the top level of your system (inside sc_main). Here is the top level from the Modules and Processes tutorial with waveform tracing (to a vcd, Value Change Dump, file).
#include "systemc.h" #include "stim.h" #include "exor2.h" #include "mon.h" int sc_main(int argc, char* argv[]) { sc_signal<bool> ASig, BSig, FSig; sc_clock TestClk("TestClock", 10, SC_NS,0.5, 1, SC_NS); ... instance of stim exor2 DUT("exor2"); DUT.A(ASig); DUT.B(BSig); DUT.F(FSig); ... instance of mon sc_trace_file* Tf; Tf = sc_create_vcd_trace_file("traces"); ((vcd_trace_file*)Tf)->sc_set_vcd_time_unit(-9); sc_trace(Tf, ASig , "A" ); sc_trace(Tf, BSig , "B" ); sc_trace(Tf, FSig , "F" ); sc_trace(Tf, DUT.S1, "S1"); sc_trace(Tf, DUT.S2, "S2"); sc_trace(Tf, DUT.S3, "S3"); sc_start(); // run forever sc_close_vcd_trace_file(Tf); return 0; }
Note the sequence of operations:
Note also that it is possible to trace hierarchically (e.g. DUT.S1).
Here are the resulting waveforms:
Some debugging techniques have been demonstrated, based on outputting text and on waveform tracing.
For more difficult problems you might need to use a C++ debugger (for instance gdb/ddd on UNIX), or a dedicated SystemC debugger.