Jump to content
stefans

Explicit parent object

Recommended Posts

In our code we sometimes create sockets on demand. I.e. when some other modules ask for a socket during elaboration
of the design instead of in the constructor of the module to which the socket belongs.
This works great in general, but the hierarchical name of the created sockets end up in the scope of the module asking for
the socket rather than in the scope of the module that has the socket.  So when creating the sockets I would need
to be able to explicitly set the parent object of the socket, but I haven't yet found a way to do this.

Is there a way, or is this something to add to upcoming revisions of the standard and POC simulator?

Share this post


Link to post
Share on other sites

It is possible, but :

  • Not standardized
  • Requires knowledge of SystemC kernel internals

If it's ok for you, take a look on sc_object::sc_object_init (sc_object.cpp) to understand how it is created.

Now you can try something like this: (I'm creating sc_in ports, instead of tlm sockets here):

 

#include <systemc.h>

struct bottom : sc_module {

    std::vector<std::unique_ptr<sc_in<bool>>> inputs;

    void add_input() {
        sc_get_curr_simcontext()->hierarchy_push(this);
        inputs.emplace_back(std::make_unique<sc_in<bool>>(sc_gen_unique_name("input")));
        sc_get_curr_simcontext()->hierarchy_pop();
    }

    SC_CTOR(bottom) { }
};

struct top: sc_module {

    bottom b{"b"};

    SC_CTOR(top) {
        b.add_input();
        b.inputs[0]->bind(bsig);
        b.add_input();
    }

    sc_signal<bool> bsig;

};

int sc_main(int argc, char** argv) {
    top t{"t"};
    sc_start();
    return 0;
}

 

If you run it, you will have an error message with correct hierarchical name:

Error: (E109) complete binding failed: port not bound: port 't.b.input_1' (sc_in)

 

I think your usage case is legitimate and we need to think of standardizing some solution. 

 

Share this post


Link to post
Share on other sites

Update:

In sysc/kernel/sc_object_int.h there is a scope guard (https://stackoverflow.com/questions/31365013/what-is-scopeguard-in-c)  for this particular case.

So it is possible to rewrite code in previous answer like this:

#include <systemc.h>
#include <sysc/kernel/sc_object_int.h>
...
struct bottom : sc_module {
   ...
    void add_input() {
        sc_object::hierarchy_scope scope(this);
        inputs.emplace_back(std::make_unique<sc_in<bool>>(sc_gen_unique_name("input")));
    }

 

Share this post


Link to post
Share on other sites

OK. Thanks!

The first version seems to work nicely for my case. I do not see any error messages:

tlm::tlm_initiator_socket<> *port_repos<T>::add_init(const std::string &name) {
  ...
  sc_core::sc_get_curr_simcontext()->hierarchy_push(&this->module);
  init_sock_t *sock = new init_sock_t(name.c_str());
  sc_core::sc_get_curr_simcontext()->hiearchy_pop();
  ...
  return sock;
}


The second version is probably less portable as it includes a file not intended to be included, according to the comments in the file.

Thanks,
-stefan

Share this post


Link to post
Share on other sites
Quote

The second version is probably less portable as it includes a file not intended to be included, according to the comments in the file.

Both versions are non-portable, as the SystemC simcontext is not part of the standard either.  I am even surprised that the hierarchy_push/pop functions are not restricted (as they should be).  So the first solution might in fact break in future versions of SystemC as you shall not mess with the object hierarchy stack yourself.

Whether or not the "hierarchy scope guard" can be extended to become a standardized solution will likely require some discussions first, especially how to avoid breaking the object hierarchy.  Certainly, this internal hierarchy stack is not  something to arbitrarily use from the model side, other than from the sc_module_name usage today.

For the particular use case, you can now look into the new "optionally bound convenience sockets" in SystemC 2.3.2, which you can safely leave unbound when not used.  An arbitrary number of sockets can be bound to the multi_passthrough_*_socket, without having to do any such magic above.

For a generic pattern to request additional structural things to "appear later", you can use an sc_vector with delayed initialization (since SystemC 2.3.2). This is (currently) limited to a single call, but I could envision to extend this to support distributed emplace_back() calls during elaboration.  Such an approach would at least restrict the external modification of the hierarchy to much more controllable contexts.

Regarding your current use case: How does the destination module actually use these newly added ports/sockets?

/Philipp

Share this post


Link to post
Share on other sites
5 hours ago, Philipp A Hartmann said:

For a generic pattern to request additional structural things to "appear later", you can use an sc_vector with delayed initialization (since SystemC 2.3.2). This is (currently) limited to a single call, but I could envision to extend this to support distributed emplace_back() calls during elaboration.  Such an approach would at least restrict the external modification of the hierarchy to much more controllable contexts.

My understanding is that it does not solves the problem with processes.  In the past I was using this technique in RTL verification, to add ports to fabric verification model on request.  And very often you need to add a process that will handle bus protocol.  Pseudocode:

add_slave_port (  master_type *master ) {
    sc_object::hierarchy_scope scope(this);
    slave_port_vector.emplace_back
    bind_master_to_slave
    spawn_protocol_service_thread
} 

In general you need a separate process for each port, because protocols can be multicycle and each port can be in a different state.

But I agree that emplace_back for sc_vector will be more convenient than using regular vector of pointers.

Share this post


Link to post
Share on other sites

To me, this approach still looks backwards.

If you need to finalize things in the model after the configuration is complete (e.g. spawning threads, etc), you can use the before_end_of_elaboration hook.  You can even do the instantiation itself there, if needed, by just storing the pointer to the master and doing both instantiation and process spawning during before_end_of_elaboration.

Adding emplace_back to sc_vector would just reduce the boilerplate of the delayed instantiation of structural members.  But there are already ways to achieve all of this without any changes to the language.

Share this post


Link to post
Share on other sites
On 22/10/2017 at 2:04 PM, Philipp A Hartmann said:

Regarding your current use case: How does the destination module actually use these newly added ports/sockets?

/Philipp

The sockets are simply bound to other sockets. E.g:

class my_module: public sc_module {
...
    xbar interconnect{"ic"};  // This module allocates socket "on request"
    cpu cpu{"cpu"};
    mem mem{"mem"};
    uart uart{"uart"};
...
    my_module(...) ... {
        cpu.init(interconnect.tgt("cpu"));
        mem.tgt(interconnect.init("mem"));
        uart.tgt(interconnect.init("uart"));
        interconnect.map("mem", 0, 0x1000);
        interconnect.map("uart", 0x1000, 0x100);
...

It is mainly a convenience thing, to not have to specify the interconnect ports twice.
It is also makes reuse easier as the names and number of ports of an interconnect can depend on e.g. elaboration time parameters.
We also use this mechanism when connecting SystemC to Python. In that case the Python interpreter is a module where ports are allocated on demand from SystemC. These sockets are then available to Python code.

Share this post


Link to post
Share on other sites

As said above, for the TLM-2.0 interconnect case, you can just use multi sockets on don't add additional sockets to the interconnect model at all.

My question hasn't been on the "surrounding side", but on the usage of the sockets inside the interconnect and the cpu/mem/uart models.  Thes modules actually have to make use of the "injected sockets", which likely requires some additional hacks.  How does the memory handle a second "tgt()" socket during simulation?  How does the CPU suddenly leverage a second initiator?

Share this post


Link to post
Share on other sites

In the example, only the interconnect allocates sockets "on demand".  I agree that it would be less useful for the other modules.
Maybe the example should have said: "cpu.init.bind(interconnect.tgt("cpu"));" etc, instead for clarity.

The interconnect module will add handlers for b_transport() etc for each added socket, then use the memory mapping information given to route transactions and DMI requests and invalidations.  So nothing strange there. 

And yes, multi socket can be used, but does not solve the naming problem that was my original concern.
I.e. that the sockets become visible in the hierarchy with the names given when connecting ("ic.mem", "ic.cpu" and "ic.uart" in the example).
Disregarding the naming, our existing solution works just fine.

And I have not looked at the sc_vector stuff yet.

Share this post


Link to post
Share on other sites

If you invert the logic and add a function add_initiator to the interconnect, temporarily storing the pointer to the to-be-connected initiator socket (and maybe a name, although this can be derived from the given socket or its parent), you can complete the binding and target socket creation in the interconnect's before_end_of_elaboration hook.  Sockets instantiated in this function will be placed correctly in the hierarchy and this approach is fully standards-compliant.

Share this post


Link to post
Share on other sites

Interesting.

Seems like a nice solution, but will unfortunately break a lot of code as the API would change.

Thinking a bit more about it.  Maybe what would be useful is something similar to SystemVerilogs bind statement?
In C++, I don't think this could be exactly the same, but it could at least affect the name-scoping.

Share this post


Link to post
Share on other sites

One drawback of before_end_of_elaboration  solution is that we can't remove pointer to temporary storage of ports to bind.
 

SC_MODULE (foo) {

std::vector <port_type *> * deffered_bind_ports; 
  
void before_end_of_elaboration() {
  for (auto port* : *deffered_bind_ports)
    create_and_bind_port( port );
  
  delete deffered_bind_ports;
  deffered_bind_ports = nullptr;
}

};

 

So it will be forever there as a class member.  This can be workarounded by some global variable, but does not looks good to me either.

Share this post


Link to post
Share on other sites

Interestingly, I just encountered an HLS design that relies on dynamic ports creation in class methods. As a result sc_object names hierarchy is totally messed-up. But design is synthesizable and working!

Even more: looks like HLS tools do not support phase callbacks. So probably it would be still a good idea to standardize hierarchy scope guard.

Share this post


Link to post
Share on other sites

I agree.  We won't stop using dynamic port creation, because it makes life so much easier.

So either we'll have to live with a messed up hierarchy, or get a good standard compliant way to achieve it.
Or as now, use a non-compliant method.

Also, we've just started modeling power gating in TLM, which relies on traversing the SystemC object hierarchy and e.g. making sockets aware that they are off and resetting stuff after power on. With a messed up hierarchy, this might break down.

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

×