Home > SytemC Tutorials > SystemC Tutorial: interfaces and channels

SystemC Tutorial: interfaces and channels

Forewords

Inheritance and multiple inheritances are a pivotal part of interfaces and channels building. Channels in SystemC can be viewed as, a sophisticated form of communication mechanism; Akin to signals or wires in found in other HDLs. However what differentiates SystemC from common HDLs is the ability given to the designer to handcraft his own channels. Furthermore, the use of interfaces enables the separation of the modules’ implementation from the communication scheme used in between modules; This is an essential part of system level modeling.
In this section we will examine the existing predefined SystemC channels as well as look at the creation of user defined primitive and hierarchical channels.

Inheritance and SystemC channels/ Interface loose coupling

For the creation of channels, SystemC borrows from C++ a convenient principle called: “the class interface”. This principle is also known as abstract base classes and is used to define a common interface for related derived classes.
Sometimes in C++, designers want to create a class that will define a set of access methods that should be used by all the classes that will be derived from it; however this class in itself will never be used to create objects. This type of classes is an abstract base class. In SystemC, abstract base classes are used to define all the access methods that a channel should have. Consequently, a channel will be a C++ class derived from an abstract base class and will implement the access methods defined inside its abstract base parent class.

An abstract base class is created in C++ through the definition of one or more pure virtual methods as part of that class. A pure virtual method by definition is a method that will only be implemented inside a derived class from the abstract base class. The semantics for the declaration of a C++ pure virtual method is as follows:

virtual <return_type> function_name (args>)=0;

For instance the following line of code declares a method called bustWrite that takes three input arguments and returns an integer type:

virtual void burstWrite( int destAddress, int numBytes, sc_lv<8> *data ) = 0;

The keyword ‘virtual’ and the ‘=0’ are the essential parts of this declaration as they indicate to the compiler that ‘burstWrite’ is a pure virtual method and therefore, that the class containing this declaration can never be made into an object.

Once one or a number of abstract base classes (interfaces) have been designed, channels can be implemented by simply inheriting one or more of the base classes and implementing their virtual methods.

Following the creation of channel, instances of that channel can be made as any other C++ object.

 

Primitive Channels

The SystemC language extension distinguishes between two kinds of channel’s implementation: Primitive channels and non-primitive ones also referred as ‘hierarchical’ channels. Primitive channel as their name imply, have restrictions in regards to their construction. Firstly, any primitive channel is derived from both, its associated interfaces and the predefined SystemC class: sc_prim_channel. In addition, a primitive channel is not allowed to contain SystemC structures for instance threads, methods, other channels et caetera. However, although primitive channel appear limited, they uniquely support the SystemC defined ‘request_update()’ and ‘update()’ access methods.

 

The SystemC library defines a number of versatile primitive channels namely: sc_buffer<T>, sc_fifo<T>, sc_mutex, sc_semaphore, sc_signal<T>, sc_signal_resolved, sc_signal_rv<W>. Those channels can be used individually or can be combined as part of a ‘non-primitive’ channel to create more complex communication mechanisms. These channels can be declared as follows:

// A 32×16 bits elements fifo
sc_fifo<sc_lv<16> > inputFifo(32);
// A 16 bits bus
sc_signal<sc_lv<16> > dataBus;
// A 4 tokens semaphore
sc_semaphore gateKeeper(4); 

The predefined SystemC primitive channels rely on a number of access methods to allow users to examine or alter their internal state. As previously highlighted, those methods are defined inside the channel’s interface. For channels used to hold values, the read() and write() methods are provided. Most predefined channels also support the common methods: print(), dump(), trace()and kind(). The print(), dump() and trace() methods are used to display the internal values of a given primitive channel either using the output stream or a trace file in the case of the trace() method. The purpose of the kind() method is used to identify the current primitive channel by providing it’s kind as a string information.

Along with access and display methods, most primitive channels support queries of events. These methods may be used for two purposes: examining if a specific channel has currently had an event as part of an expression; such as an ‘if’ statement or supply information related to the activity/events of a channel as part of a sensitivity list of a SystemC process. The following examples illustrate the different method available for specific channels.

// A non blocking write on an sc_signal
dataBus.write(”1010111101010101″);
// A blocking write on a variable of type sc_lv
sc_lv<16> busVar = dataBus.read();
while ( inputFifo.num_free() != 0 )
// A blocking write on an sc_fifo
inputFifo.write(dataBus.read());
// A blocking access to an sc_semaphore
gateKeeper.wait();
// A non blocking access to an sc_semaphore
if ( gateKeeper.trywait() != 0 )
cout << “Got a lock !” << endl;

Although SystemC provides us with numerous pre-defined channels, it is also possible to create user defined channels. As mentioned previously, the creation of a channel is achieved through multiple-inheritance. As a rule, a channel inherits from one or more interfaces as well as from either the sc_prim_channel or the sc_channel SystemC class. Sophisticated relationship between channels can be created by multiple inheritances from various interfaces. The following example describes the creation of a simplified version of the SystemC’s semaphore primitive channel.

class simple_semaphore_if : virtual public sc_interface {  // Line 1
public:
   // the classical operations: wait(), trywait(),
   // and post(), lock (take) the semaphore,
   // block if not available
   virtual int wait() = 0; // Line 7

   // lock (take) the semaphore, return -1
   // if not available
   virtual int trywait() = 0; // Line 10

   // unlock (give) the semaphore
   virtual int post() = 0; // Line 13

   // get the value of the semphore
   virtual int get_value() const = 0; // Line 16

   protected:
   // constructor
   simple_semaphore_if() { } // Line 21
};

// Creation of the semaphore Channel
class simple_semaphore: public simple_semaphore_if, public sc_prim_channel
{
   public:
   // constructors
   explicit simple_semaphore( int init_value_ );

   // interface methods
   // lock (take) the semaphore, block if
   // not available
   virtual int wait(); // Line 37

   // lock (take) the semaphore, return -1 if
   // not available
   virtual int trywait(); // Line 40

   // unlock (give) the semaphore
   virtual int post(); // Line 43

   // get the value of the semaphore
   virtual int get_value() const // Line 46
   { return m_value; }

   protected:
   // support methods
   bool in_use() const // Line 53
   { return ( m_value <= 0 ); }

   protected:
   int m_value; // Line 58
   sc_event m_free; // Line 58
};

// constructors
simple_semaphore::simple_semaphore( int init_value_ ) : 
   sc_prim_channel( sc_gen_unique_name( “semaphore” ) ),
   m_value( init_value_ ) { }
// interface methods
// lock (take) the semaphore, block if
// not available
int simple_semaphore::wait() {
   while( in_use() )  sc_prim_channel::wait( m_free );
   -- m_value;
   return 0;
}

// lock (take) the semaphore, return -1 if
//not available
int simple_semaphore::trywait() {
   if( in_use() ) {
      return -1;
   }
   -- m_value;
   return 0;
}

// unlock (give) the semaphore
int simple_semaphore::post() {
   ++ m_value;
   m_free.notify();
   return 0;
}

The first part of this example defines the interface for the semaphore’s channel. line 1 declares the interface class named ‘simple_semaphore_if’ and inherits virtually, the sc_interface SystemC predefined class. The virtual inheritance is used here to prevent any problems related to repeated inheritance later-on during the creation of the simple_semaphore channel. The lines 7 to 16 declare pure virtual methods that will be used to access the semaphore channel. Finally the line 21 defines a default constructor for the interface. Following the interface declaration, we find the declaration of the semaphore channel called ‘simple_semaphore’. The class is created through the inheritance of both the simple_semaphore_if interface class and the SystemC sc_prim_channel pre-defined class. The lines 37 to 46 make the previously pure virtual methods defined inside the interface into effective methods inside the channel. A local method is created in line 53 to used to keep track of the current value of the semaphore. Lastly in lines 58 and 59 we find the definitions of internal member variables used by the semaphore channel. The ultimate part of this example implements each individual methods defined inside the semaphore channel.

Hierarchical Channel

Up until now we have considered the simplest form of channels: the primary channels. Although primary channels are of great use, one of the main advantages that SystemC has over conventional HDLs, is its ability to create complex communication mechanisms in the form of hierarchical channels.
Hierarchical channels are used as a convenient way to abstract exchanges between communicating objects such as sc_modules. Commonly hierarchical channels are used as transactors, converting high level commands to RTL style signals and vice versa.

Herarchical channel can be considered in a lot of ways as, more sohisticated forms of sc_modules. As such, a hierarchical channel can contain ports, as well as, a hierarchy of sc_modules or other channels. Furthermore, hierarchical channels may contain SystemC processes such as: SC_METHODS or SC_THREAD. However, unlike their sc_modules counterparts, hierarchical channels will also provide methods for implementing the functions defined inside their associated interfaces.

As we mention earlier, a channel is always created from one or multiple existing interfaces. An interface in SystemC is an abstract class derived from the existing SystemC class: sc_interface.

By definition interfaces will only define pure virtual access methods. These methods will eventually have to be implemented in any deriving channels. The following illustrates the creation of a user defined interface called dma_interface with a methods called: burstWrite() and bustRead().

class dma_interface: virtual public sc_interface {
public:
   virtual void burstWrite(int destAddress, int numBytes, sc_lv<8> *data ) = 0;
   virtual void burstRead(int sourceAddress, int numBytes, sc_lv<8>* data) = 0;
};

Commonly the inheritance of the pre-defined SystemC class: sc_interface, is done as, public and virtual. The virtual mechanism is used as a safety net, preventing the rise of issues related to repeated inheritance when a channel inherits more that one interface.

After the creation of an interface, numerous channels can be created to provide the required implementation of the interfaces’ methods. The creation of a hierarchical channel differs from a primitive one, only by the type of the inherited pre-defined SystemC class: sc_channel. Along with the sc_channel class, one to any number of interface classes can be inherited. The following code illustrates the creation of a channel called: dma_channel, publicly inheriting form the interface class: dma_interface.

class dma_channel: public dma_interface, public sc_channel {
public:
   sc_out_rv<16> address_p;
   sc_inout_rv<8> data_p;
   sc_out_resolved rw_p;
   dma_channel(sc_module_name nm): sc_channel(nm)
      ,address_p(”address_p”)
      ,data_p(”data_p”)
      ,rw_p(”rw_p”)
   { }
   virtual void burstWrite( int destAddress, int numBytes, sc_lv<8> *data );
   virtual void burstRead(int sourceAddress, int numBytes, sc_lv<8>* data);
};

void dma_channel::burstWrite( int destAddress, int numBytes, sc_lv<8> *data ) {
   sc_lv<8> *ite = data;
   for (int i=0; i<numBytes; i++) {
      address_p->write(destAddress++);
      data_p->write( *(ite++) );
      wait(10, SC_NS);
      cout << “Write out ” << data_p->read() << endl;
      rw_p->write(SC_LOGIC_0); // Write pulse
      wait(50, SC_NS);
      rw_p->write(SC_LOGIC_Z);
      address_p->write(”ZZZZZZZZZZZZZZZZ”);
      data_p->write(”ZZZZZZZZ”);
      wait(10, SC_NS);
   }
}
void dma_channel::burstRead(int sourceAddress, int numBytes, sc_lv<8>* data) {
   for (int i=0; i<numBytes; i++) {
      address_p->write(sourceAddress++);
      wait(10, SC_NS);
      rw_p->write(SC_LOGIC_1); // Read pulse
      wait(10, SC_NS);
      *(data++) = data_p->read();
      cout << “Data read ” << data_p->read() << endl;
      wait(40, SC_NS);
      rw_p->write(SC_LOGIC_Z);
      address_p->write(”ZZZZZZZZZZZZZZZZ”);
      data_p->write(”ZZZZZZZZ”);
      wait(10, SC_NS);
   }
}

The first part of this code illustrates the creation of the hierarchical channel called dma_channel. This is done through the multiple inheritance of the sc_channel class and the dma_interface class.
Ports are created to illustrate the flexibility of channels. Those port are initialised inside the constructor of that class.
The last part of the dma_channel class declaration restates the existance of the burstWrite() and burstRead() methods found in the parent class: dma_interface.

Lastly the code illustrates the implementation of the two methods burstWrite() and burstRead(). The code itself is of little importance but it demonstrates how high level transactions can be refined into low-level RTL signal activities.

For the pupose of completeness of this example the following code illustrates how can this channel could be used in a verification environment.
This code shows the creation of a simple testbench sending a burstWrite() and a burstRead() request to a slave RTL memory via the dma_channel.

class test_bench: public sc_module {
   public:
   sc_port<dma_interface> master_port;

   void stimuli()
   {
      sc_lv<8> data_sent[10] = {20, 21, 22, 23, 24, 25,26,27,28,29};
      sc_lv<8> data_rcv[10] = {0,0,0,0,0,0,0,0,0,0};
      master_port->burstWrite(100, 10, data_sent);
      wait(100, SC_NS);
      master_port->burstRead(100, 10, data_rcv);
      for (int i=0; i<10; i++) {
         if (data_sent[i] != data_rcv[i]) {
            cout << data_sent[i] << ” ” << data_rcv[i] << endl;
            cout << “data missmatch” << endl;
         }
      }

   SC_HAS_PROCESS(test_bench);
   
   test_bench(sc_module_name nm): sc_module(nm) {
      SC_THREAD(stimuli);
   }
};

class rtl_memory: public sc_module {
public:
   sc_in_rv<16> address_p;
   sc_inout_rv<8> data_p;
   sc_in_resolved rw_p;
   sc_lv<8> *mem_arr;
   void run() // sensitive rw_p
   {
      while(true) {
         // read cycle
         if (rw_p->read() == SC_LOGIC_1) {
            data_p->write( *( mem_arr+(sc_uint<16>(address_p->read())) ) );
            // write cycle
         } else if (rw_p->read() == SC_LOGIC_0) {
            *(mem_arr + (sc_uint<16>(address_p->read()))) = data_p->read();
         }
         wait();
      }
   }

   SC_HAS_PROCESS(rtl_memory);
   rtl_memory(sc_module_name nm, int mem_size = 100): sc_module(nm) {
      mem_arr = new sc_lv<8>[mem_size];
      for (int i=0; i< mem_size; i++) {
         mem_arr[i] = sc_lv<8>(0);
      }
   SC_THREAD(run);
      sensitive << rw_p;
   }

   ~rtl_memory() { delete []mem_arr; } };

// Main program
int sc_main(int argc, char* argv[])
{
   sc_set_time_resolution(1, SC_NS);
   
   sc_signal_rv<16> address_s;
   sc_signal_rv<8> data_s;
   sc_signal_resolved rw_s;
   
   test_bench tb(”tb”);
   dma_channel transactor(”transactor”);
   rtl_memory uut(”uut”, 1000);
   
   tb.master_port(transactor);
   transactor.data_p(data_s);
   transactor.rw_p(rw_s);
   transactor.address_p(address_s);
   uut.address_p(address_s);
   uut.data_p(data_s);
   uut.rw_p(rw_s);
   
   sc_start();
   return 0;
}

Summary

In this section we covered the existance of two kind of channels in SystemC: primitive, hierarchical. The main differences between those two kinds being: primitive channels cannot contain SystemC structural objects (ports, channels, processes) but can use the update() method to implement non blocking update mechanisims; The hierachical channels however can have structural objects but cannot use the update() method.
I addition we illustrated the creation of both a primitive and hierarchical channel as well as their use in the context of a verification environment.

The code for this tutorial can be found HERE.

Et voila !

David Cabanis

About these ads
Categories: SytemC Tutorials
  1. Anand
    January 24, 2010 at 7:55 am | #1

    Fantastic! Really clear and thorough. Keeps me from abandoning SystemC.

  2. Jack
    May 18, 2010 at 5:53 am | #2

    Can I have a pointer semaphore that can point to another semaphore? Just like in eCos …

  3. zitroneneis
    February 21, 2012 at 12:48 am | #3

    Thank you very much for this explanation. It is very helpful and aids to get an better understanding of the concepts of SystemC

  4. Anto
    February 28, 2012 at 9:50 am | #4

    http://www.openomy.com/download/systemclive/main.cc -> this link is unavailable, someone can post another link pls

  5. Moon Diller
    March 12, 2013 at 5:46 pm | #5

    class test_bench: public sc_module {
    public:
    sc_port master_port;

    void stimuli()
    {
    sc_lv data_sent[10] = {20, 21, 22, 23, 24, 25,26,27,28,29};
    sc_lv data_rcv[10] = {0,0,0,0,0,0,0,0,0,0};
    master_port->burstWrite(100, 10, data_sent);
    wait(100, SC_NS);
    master_port->burstRead(100, 10, data_rcv);
    for (int i=0; i<10; i++) {
    if (data_sent[i] != data_rcv[i]) {
    cout << data_sent[i] << ” ” << data_rcv[i] << endl;
    cout << “data missmatch” << endl;
    }
    }
    //missing one closing bracket here

    SC_HAS_PROCESS(test_bench);

    test_bench(sc_module_name nm): sc_module(nm) {
    SC_THREAD(stimuli);
    }
    };

  6. June 6, 2013 at 10:43 am | #6

    Hey! I simply wish to give a huge thumbs up for the nice information
    you have right here on this post. I can be coming again to your blog for extra
    soon.

  7. June 7, 2013 at 6:25 am | #7

    Howdy! I just want to give a huge thumbs up for the good
    info you will have right here on this post.
    I might be coming again to your weblog for more soon.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: