stefans Posted October 18, 2017 Report Posted October 18, 2017 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? Roman Popov 1 Quote
Roman Popov Posted October 19, 2017 Report Posted October 19, 2017 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. Quote
Roman Popov Posted October 19, 2017 Report Posted October 19, 2017 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"))); } Quote
stefans Posted October 20, 2017 Author Report Posted October 20, 2017 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 Quote
Philipp A Hartmann Posted October 22, 2017 Report Posted October 22, 2017 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 maehne 1 Quote
Roman Popov Posted October 22, 2017 Report Posted October 22, 2017 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. Quote
Philipp A Hartmann Posted October 23, 2017 Report Posted October 23, 2017 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. Quote
stefans Posted October 24, 2017 Author Report Posted October 24, 2017 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. Quote
Philipp A Hartmann Posted October 24, 2017 Report Posted October 24, 2017 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? Quote
stefans Posted October 24, 2017 Author Report Posted October 24, 2017 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. Quote
Philipp A Hartmann Posted October 24, 2017 Report Posted October 24, 2017 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. Quote
stefans Posted October 25, 2017 Author Report Posted October 25, 2017 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. Quote
Roman Popov Posted October 25, 2017 Report Posted October 25, 2017 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. Quote
stefans Posted October 26, 2017 Author Report Posted October 26, 2017 So the bottom line is that it can be done today, but in a rather convoluted and non-obvious way. Or in a way that isn't standard compliant. Quote
Roman Popov Posted February 8, 2018 Report Posted February 8, 2018 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. Quote
stefans Posted February 15, 2018 Author Report Posted February 15, 2018 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. Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.