leoeltipo

Socket binding to module itself

8 posts in this topic

Hello,

 

I am new in the SystemC-TLM world. I am reading the LRM and looking at an example (chapter 13, systemc LRM, page 462).

 

In the module constructor it is written

 

...

init_socket.bind(*this);     // Initiator socket bound to initiator itself

...

 

The rest of the code is of no use for the sake of the question. I don't understand why the socket must be bound to the module itself. The example continues and the socket is bound to a target socket, but I don't get this one...

 

Thank you in advance.

 

Leo

Share this post


Link to post
Share on other sites

The "sc_export" part of each TLM-2.0 socket needs to be bound to an interface (tlm_bw_transport_if for an initiator, tlm_fw_transport_if for a target socket) in order to provide the implementation of the communication link. As you can see in the referenced example, the Initiator class inherits and implements the backwards interface:

struct Initiator: sc_module, tlm::tlm_bw_transport_if<> // Initiator implements the bw interface

As a result it can act as the "channel" to provide the behaviour for the sc_export part of the initiator socket.  This pattern is a quite common idiom in the TLM-2.0 world, at least if you don't use the convenience sockets.

 

So to answer your question: It's not the "module part" that is bound to the socket. The Initiator is-a backwards interface as well, and this "interface part" is bound.

 

Greetings from Oldenburg,

Philipp

Edited by Philipp A. Hartmann

Share this post


Link to post
Share on other sites

An other way to understand this is to look at SystemC/TLM as pure C++ code and see what is going on..

 

Let us assume that you have two classes, "Initiator" and "Target", that need to communicate with each other. Target implements a function called "transport" to be called by Initiator, and Initiator implements a function called "response" - that is called by target. For the sake of the explanation here, the payload itself is not important, which we will assume is plain "int" in both directions.

 

You would normally do this as following:

// Interface classes:
class initiator_if {
   public:
      void response(int i) = 0;
};
class target_if {
   public:
      void transport(int i) = 0;
};

class Initiator : public initiator_if
{
   public:
      // Implement the interface
      void response(int i) {
         cout << "Got: " << i << " from target" << endl;
      }

      void bind(target_if &_if) {
         // Store the pointer locally
         m_target = &_if;
      }
};

class Target : public target_if
{
   public:
      // Implement the interface
      void transport(int i) {
         cout << "Got: " << i << " from initiator " << endl;
      }

      void bind(initiator_if &_if) {
         // Store the pointer locally
         m_initiator = &_if;
      }
};

Next we instantiate the objects and "bind" them:

Initiator initiator;
Target target;

initiator.bind(target);
target.bind(initiator);

I hope you are seeing where we are going with this. What has been done above is a crude equivalent of sc_port binding. There is one problem however with the approach above. What if the class "Target" doesn't implement the target_if directly? Like so:

class TargetGrandChild : public target_if
{
   public:
      void transport(int i) {
         // Implement the interface..
         cout << "Got " << i << " from initiator (in grand child)" << std::endl;
      }
};

class TargetChild
{
   public:
      TargetGrandChild gch;

      /* Other stuff.. */
};

class Target
{
   public:
      TargetChild ch;

      /* Other stuff.. */
};

One way to deal with this is to change the way bind function is called:

initiator.bind(target.ch.gch);

This is ugly. Firstly, it makes the client of bind function dependent on an internal design aspect of the "Target" class. For example, if the Target class changes tomorrow (to let the TargetChild implement the "target_if" directly), the bind function call needs to change. Also, to allow the client to call the bind function as above, the elements "ch" and "gch" must be made public, which may not be necessarily a good thing.

 

A way out is to have an additional indirection. Let us call this as simple_export (a very simplified version of sc_export):

class simple_export : public target_if
{
    public:
       void transport(int i) {
          // One level of indirection
          p_real_target->transport(i);
       }
       void bind(target_if &_if) {
          p_real_target = &_if;
       }
    private:
       target_if *p_real_target;
};

The new version of the Target class now looks like the following:

class TargetGrandChild : public target_if
{
   public:
      void transport(int i) {
         // Implement the interface..
         cout << "Got " << i << " from initiator (in grand child)" << std::endl;
      }
};

class TargetChild
{
   public:
      TargetGrandChild gch;

      /* Other stuff.. */
};

class Target
{
   public:
      simple_export port;

   private: // private is ok
      TargetChild ch;

      /* Other stuff.. */

   Target() {
      // Tell "export" where the real implementation is
      port.bind(ch.gch);
   }
};

The initiator will be "bound" to the target like so:

initiator.bind(target.port);

So for the simple_export to work, you need two binds:

  1. First is where the initiator is bound to the simple_export instance
  2. Second is when the "real" implementation is bound to the simple_export instance

The simple_export class acts like a bridge. It forward the calls from the initiator to the "real" implementation.

 

In case the "Target" itself implements the interface, the bind would look like:

class Target : public target_if
{
   public:
      simple_export port;

   private: // private is ok
      TargetChild ch;

      /* Other stuff.. */

   Target() {
      // Tell "export" where the real implementation is
      port.bind(*this);
   }
};

I hope this explains the line you pointed out in the code. The TLM socket has both a port and an export.

 

Please note that the code snips above does not correspond to how OSCI simulator actually implements sc_port/sc_export!

Yuri Boyka likes this

Share this post


Link to post
Share on other sites

To continue with this topic a little bit, I understand that a socket has an sc_port and sc_export. However, what is confusing for me is that tlm_base_initiator_socket inherints from sc_port but instantiate an object of type sc_export. Otherwise, the "sc_port" part of the socket shoul also be bound with an interface...

 

Correct me if i am wrong...

 

Thanks

Share this post


Link to post
Share on other sites

The sc_port does have to be bound, but it is bound when you bind the initiator socket to the target socket. Binding the initiator socket to the target socket binds the initiator port with the target export; and the target export is bound to the target tlm_transport_fw_if. The result is that an initiator may call functions via the port in the initiator socket, via the export in the target socket,  which are ultimately implemented in the target.

 

The reverse is true for the target socket. See the introduction to section 13.2.1 in the 1666-2011 LRM.

 

regards

Alan

P.S. Remember you can download the 1666-2011 standard for free.

Share this post


Link to post
Share on other sites

Posted (edited)

On 1/18/2014 at 10:26 PM, karthickg said:

Thanks karthicks for perfect expanation... :)

 

Edited by Yuri Boyka

Share this post


Link to post
Share on other sites
On 1/18/2014 at 10:26 PM, karthickg said:

An other way to understand this is to look at SystemC/TLM as pure C++ code and see what is going on..

 

Let us assume that you have two classes, "Initiator" and "Target", that need to communicate with each other. Target implements a function called "transport" to be called by Initiator, and Initiator implements a function called "response" - that is called by target. For the sake of the explanation here, the payload itself is not important, which we will assume is plain "int" in both directions.

 

You would normally do this as following:


// Interface classes:
class initiator_if {
   public:
      void response(int i) = 0;
};
class target_if {
   public:
      void transport(int i) = 0;
};

class Initiator : public initiator_if
{
   public:
      // Implement the interface
      void response(int i) {
         cout << "Got: " << i << " from target" << endl;
      }

      void bind(target_if &_if) {
         // Store the pointer locally
         m_target = &_if;
      }
};

class Target : public target_if
{
   public:
      // Implement the interface
      void transport(int i) {
         cout << "Got: " << i << " from initiator " << endl;
      }

      void bind(initiator_if &_if) {
         // Store the pointer locally
         m_initiator = &_if;
      }
};

Next we instantiate the objects and "bind" them:


Initiator initiator;
Target target;

initiator.bind(target);
target.bind(initiator);

I hope you are seeing where we are going with this. What has been done above is a crude equivalent of sc_port binding. There is one problem however with the approach above. What if the class "Target" doesn't implement the target_if directly? Like so:


class TargetGrandChild : public target_if
{
   public:
      void transport(int i) {
         // Implement the interface..
         cout << "Got " << i << " from initiator (in grand child)" << std::endl;
      }
};

class TargetChild
{
   public:
      TargetGrandChild gch;

      /* Other stuff.. */
};

class Target
{
   public:
      TargetChild ch;

      /* Other stuff.. */
};

One way to deal with this is to change the way bind function is called:


initiator.bind(target.ch.gch);

This is ugly. Firstly, it makes the client of bind function dependent on an internal design aspect of the "Target" class. For example, if the Target class changes tomorrow (to let the TargetChild implement the "target_if" directly), the bind function call needs to change. Also, to allow the client to call the bind function as above, the elements "ch" and "gch" must be made public, which may not be necessarily a good thing.

 

A way out is to have an additional indirection. Let us call this as simple_export (a very simplified version of sc_export):


class simple_export : public target_if
{
    public:
       void transport(int i) {
          // One level of indirection
          p_real_target->transport(i);
       }
       void bind(target_if &_if) {
          p_real_target = &_if;
       }
    private:
       target_if *p_real_target;
};

The new version of the Target class now looks like the following:


class TargetGrandChild : public target_if
{
   public:
      void transport(int i) {
         // Implement the interface..
         cout << "Got " << i << " from initiator (in grand child)" << std::endl;
      }
};

class TargetChild
{
   public:
      TargetGrandChild gch;

      /* Other stuff.. */
};

class Target
{
   public:
      simple_export port;

   private: // private is ok
      TargetChild ch;

      /* Other stuff.. */

   Target() {
      // Tell "export" where the real implementation is
      port.bind(ch.gch);
   }
};

The initiator will be "bound" to the target like so:


initiator.bind(target.port);

So for the simple_export to work, you need two binds:

  1. First is where the initiator is bound to the simple_export instance
  2. Second is when the "real" implementation is bound to the simple_export instance

The simple_export class acts like a bridge. It forward the calls from the initiator to the "real" implementation.

 

In case the "Target" itself implements the interface, the bind would look like:


class Target : public target_if
{
   public:
      simple_export port;

   private: // private is ok
      TargetChild ch;

      /* Other stuff.. */

   Target() {
      // Tell "export" where the real implementation is
      port.bind(*this);
   }
};

I hope this explains the line you pointed out in the code. The TLM socket has both a port and an export.

 

Please note that the code snips above does not correspond to how OSCI simulator actually implements sc_port/sc_export!

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now