Processes


index


Debugging

     

4. Hierarchy

Hierarchy in SystemC is provided in a similar way to VHDL. You add hierarchy by instantiating other modules (components) in your sc_module (architecture/entity pair). Figure one shows a system in VHDL and SystemC.

As one can see from the above image there is a lot of similarity between a VHDL and a SystemC system. Both consist of I/O ports, processes, process communications (signals) and other modules (components). What should also be clear from the figure is that SystemC provides additional modeling capabilities, such as channels, interfaces and event.

The code fragment below shows a simple 4 bits shiftregister build up out of 4 FlipFlops (dff1..4).

VHDL

SystemC

entity shiftreg is
   port (clk    : in  std_logic;
         reset  : in  std_logic;
         din    : in  std_logic;
         dout   : out std_logic);
end shiftreg;
 
architecture rtl of shiftreg is
 
component dff is
   port (clk    : in  std_logic;
         reset  : in  std_logic;
         din    : in  std_logic;
         dout   : out std_logic);
end component;  
 
signal q1_s,q2_s,q3_s : std_logic;

begin

    dff1: dff port map (clk  => clk,
                        reset=> reset,
                        din  => din,
                        dout => q1_s);

    dff2: dff port map (clk  => clk,
                        reset=> reset,
                        din  => q1_s,
                        dout => q2_s);

    dff3: dff port map (clk  => clk,
                        reset=> reset,
                        din  => q2_s,
                        dout => q3_s);

    dff4: dff port map (clk  => clk,
                        reset=> reset,
                        din  => q3_s,
                        dout => dout);
 
end rtl;

#include "dff.h"
 
SC_MODULE(shiftreg) {
 
    sc_in<bool >  clk;
    sc_in<bool >  reset;
    sc_in<bool >  din;
    sc_out<bool > dout;
 
    sc_signal<bool > q1_s,q2_s,q3_s;
 
    dff dff1;      // Instantiate FF
    dff dff2;
    dff dff3;
    dff dff4;
 
    SC_CTOR(shiftreg) : dff1("dff1"),dff2("dff2"),dff3("dff3"),dff4("dff4") {



        dff1.clk(clk);  // Named association
        dff1.reset(reset);
        dff1.din(din);
        dff1.dout(q1_s);
 
        dff2.clk(clk);
        dff2.reset(reset);
        dff2.din(q1_s);
        dff2.dout(q2_s);
 
        dff3.clk(clk);
        dff3.reset(reset);
        dff3.din(q2_s);
        dff3.dout(q3_s);
 
        dff4.clk(clk);
        dff4.reset(reset);
        dff4.din(q3_s);
        dff4.dout(dout);
    }
};

Starting from the top of the VHDL program, the component dff instantiation tells the VHDL compiler about the dff port mapping, this is somewhat similar to the #define "dff.h" statement in the SystemC program.

In VHDL you name the instance and bind the ports in a single statement dff1:dff port map(), in SystemC you instantiate and name the module dff dff1;  first and then you bind the ports in the constructor (SC_CTOR).

You need to do one additional step for which there is no VHDL equivalent and that is to call the constructor of each instantiated component. This is done on the so called initializer list,

SC_CTOR(shiftreg) : dff1("dff1"),dff2("dff2"),dff3("dff3"),dff4("dff4") {

The initalizer list consist of listing the module instance names e.g. dff1 and pass on a label e.g. "dff1" to its constructor.  Note that you need to list each component on the initializer list in the same order as you instantiated them example:

dff dff2;
dff dff1;
dff dff3;
SC_CTOR(shiftreg) : dff2("dff2"), dff1("dff1"), dff3("dff3),...

Port binding is done in the constructor. Similar to VHDL you can use positional or named association.

VHDL

SystemC

Note

dff1: dff port map (clk  => clk,
          reset=> reset,
          din  => din,
          dout => q1_s);

dff1.clk(clk);
dff1.reset(reset);
dff1.din(din);
dff1.dout(q1_s);

Name Associated, recommended style

dff1: dff  port map (clk,reset,din ,q1_s);

dff1 << clk << din << dout;

Positional Association, deprecated in SystemC 2.2

dff1: dff  port map (clk,reset,din ,q1_s);

dff1(clk);
dff1(reset);
dff1(din);
dff1(q1_s);

Alternative form of Positional Association, also deprecated in SystemC 2.2

As with VHDL the recommended style is to use the named association.

4.1 SC_MAIN

If you have looked at the sc_main.cp  file in some of the previous examples you have noticed a different style for component instantiation, the reason for this is that sc_main is not a module but a function. You can think of sc_main as a VHDL testbench, it looks like a normal entity-architecture pair but the entity is empty.

Because sc_main is a function, there is no constructor and hence you can not use any processes. The function of sc_main is to instantiate the top level (e.g. VHDL DUT) and start and stop the simulation. You can also pass on command line arguments to your simulation and setup a VCD trace file so that you can log any signals for later viewing.

The code fragment below shows a simple testbench for the above 4 bits shift register.

#include "shiftreg.h"

int sc_main(int argc, char* argv[]) {

    sc_signal<bool> reset, din, dout;   // Local signals

    sc_clock clk("clk",10,SC_NS);       // Create a 10ns period clock signal

    shiftreg DUT("shiftreg");           // Instantiate Device Under Test
    DUT.clk(clk);                       // Connect ports
    DUT.reset(reset);
    DUT.din(din); 
    DUT.dout(dout);

    sc_trace_file *fp;                  // VCD filepointer
    fp=sc_create_vcd_trace_file("wave");// Create wave.vcd file
    sc_trace(fp,clk,"clk");             // Add signals to trace file
    sc_trace(fp,reset,"reset");
    sc_trace(fp,din,"din");
    sc_trace(fp,dout,"dout");

    din=true;
    sc_start(100, SC_NS);               // Run simulation for 100 ns
    din=false;
    sc_start(100, SC_NS);               // Run another  100 ns
            
    sc_close_vcd_trace_file(fp);        // close wave.vcd
    return 0;                           // Return OK, no errors.
}                                       // no ;

The top level component is specified in the header file #include "shiftreg.h". Inside the sc_main() function the shiftreg component is instantiated and the ports are connected to some local signals. The latter is required for the VCD trace function which will be described later. Notice the component instantiation shiftreg DUT contains the initializer list label "shiftreg" and the port binding is not done inside a constructor.

You can start and stop the simulator using the methods sc_start() and sc_stop() respectively.

sc_start(33.4, SC_PS); // run for 33.4 picosecond. SC_FS, SC_PS, SC_NS, SC_US, SC_MS, SC_SEC
sc_start();            // run until sc_stop()
sc_start
(-1);          // run until sc_stop()
sc_start(0);           // run for 1 delta cycle!

To use sc_start() you need to specify a clock using the sc_clock module. The sc_clock arguments are passed on to its constructor,

// sc_clock signal( label, period(,) dutycycle, start_time(,) posedge_first);
sc_clock
clk1("clk1", 10,SC_NS);                     // Create a 100MHz clock signal
sc_clock
clk2("clk2", 10,SC_NS, 0.5, 0,SC_NS, true); // Same as clk1, default values
sc_clock
clk3("clk3", 5,SC_NS, 0.25);                // Create a 200MHz clock signal with a 25% high dutycycle
sc_clock clk4("clk4", 5,SC_NS, 0.25, 3,SC_NS, false);// first edge at 3ns, first edge is falling
sc_clock clk5("clk5", 5,SC_NS, 0.25, 3,SC_NS, true); // first edge at 3ns, first edge is rising

See the clock_test subdirectory for a simple sc_clock test that created the above waveform.

4.2 Simple Example

Figure 4.2 shows a simple adder test environment. The system consist of 4-bits ripple-carry adder, a stimulus generator and a checker module. The full source can be found in the adder subdirectory.

Some code fragments are discussed below.

sc_out<sc_uint<4> > sum;  // output port

sc_uint<4> sum_s;
        // Variable

bool fulladder(bool a, bool b, bool cif, bool& cof) {
        bool sumr;
        sumr =(a ^ b) ^ cif;
        cof=(a & b) | ((a ^ b) & cif);
        return sumr;
    }
 
    void p1() { 
        sum_s[0]=fulladder(ain.read()[0],bin.read()[0],ci.read(), co0);
        sum_s[1]=fulladder(ain.read()[1],bin.read()[1],co0,co1);
        sum_s[2]=fulladder(ain.read()[2],bin.read()[2],co1,co2);
        sum_s[3]=fulladder(ain.read()[3],bin.read()[3],co2,co3);
        sum.write(sum_s);
        co.write(co3);
    }

Code Fragment from adder.h

The fulladder is implemented using a simple C++ function. The point to note is the & character after the bool type (bool& cof). The & character is used to indicate that the cof argument expects a reference, that is, a pointer to an object rather than the object value itself. References allow you to modify the orginal argument which in this case is co0..co3. In VHDL terms this is like a function were the parameter list can not only be of type IN but also OUT and INOUT. Note that you can not pass a reference to an I/O port. On a similar note, you can not write to a single bit [], bit slice (,) or an I/O port (sum), hence you need to modify a variable (sum_s) and write the value back to the I/O port (sum.write(sum_s)).

    void ps1() {    

        ......

        ain.write("0b1110");
        bin.write("0b0010");
        ci.write(true);
        wait();             
        sc_stop();                          // End simulation
    }
 
    SC_CTOR(stim) { 
        SC_THREAD(ps1);                     // Run ps1 only ones
          sensitive << clk.pos();
    }

Code Fragment from stim.h

The stimulus module stim.h provide a sequence of stimuli before stopping the simulator using sc_stop(). Notice the method of writing a binary number, 0b is the C/C++ prefix of a binary value, similar to 0x for hexadecimal and 0d for decimal.

#include <iostream>
using namespace std;


SC_MODULE(check) {
    sc_in<bool> clk;
    sc_in<sc_uint<4> > ain, bin;
    sc_in<bool> ci;
    sc_in<sc_uint<4> > sum;
    sc_in<bool> co;
 
    sc_uint<5> sumc;
 
    void pc1() {
 
        sumc=ain.read() + bin.read() + ci.read();     // Model of an adder
 
        cout << "fulladder " << ain.read() << " + " << bin.read() << " + " << ci.read() << " =" << sum.read()+co.read()*16;
        if (sumc(3,0)==sum.read() && co==sumc[4]) {
            cout << " Passed" << endl;
        } else {
            cout << " Failed, expected sum=" << sumc(3,0) << " co=" << sumc[4] << endl;
        }
 
    }
 
    SC_CTOR(check) {
        SC_METHOD(pc1);
          sensitive << clk.pos();
          dont_initialize();
    }
};

Code fragment from check.h

The final module is the checker check.h, this module takes the stimuli input and the adder's output and compares this to a local model of an adder.  The cout statements are similar to VHDL's write/writeline statements and will be discussed in the next section.

 

 


home

 


Processes


index


Debugging