Jump to content
PlayDough

Generic/Runtime Specification of Ports

Recommended Posts

I'm working on a small simulation library based on SystemC for a research project.  What I'd like to do is have the I/O from a testbench module be dynamically determined at runtime (perhaps through some sort of configuration file).  The point is to avoid recompilation and have the testbench dynamically configure itself to accommodate the UUT.

 

 

The problem is not that I cannot traverse the hierarchy (I've figured that out) and get the ports.  The problem is that I cannot seem to dynamically generate a "database".  Since all the ports are templated, I can't generate a generic data structure for all ports.  For example, I'd like to be able to do something like:

 

std::unordered_map<const std::string, ????> dut_ports;

 

I don't know what to use for ????.  I've thought of using some sort of generic wrapper, e.g.:

 

struct port_wrapper

{

  template<T>

  port_wrapper(T port_)

    port(port_)

  {

  }

 

  T port;

};

 

But this is still a problem since T isn't known until runtime.

 

Is there a way to store a "generic" pointer to a sc_in, sc_out, or sc_inout?  Looking through the source code, it looks like everything is parameterized all the way back until sc_interface.  But sc_interface doesn't provide any methods to access the port (i.e. read/write).

 

Any hints or suggestions?

 

Share this post


Link to post
Share on other sites

I'm working on a small simulation library based on SystemC for a research project.  What I'd like to do is have the I/O from a testbench module be dynamically determined at runtime (perhaps through some sort of configuration file).  The point is to avoid recompilation and have the testbench dynamically configure itself to accommodate the UUT.

 

 

The problem is not that I cannot traverse the hierarchy (I've figured that out) and get the ports.  The problem is that I cannot seem to dynamically generate a "database".  Since all the ports are templated, I can't generate a generic data structure for all ports.  For example, I'd like to be able to do something like:

 

std::unordered_map<const std::string, ????> dut_ports;

 

I don't know what to use for ????.  I've thought of using some sort of generic wrapper, e.g.:

 

struct port_wrapper

{

  template<T>

  port_wrapper(T port_)

    port(port_)

  {

  }

 

  T port;

};

 

But this is still a problem since T isn't known until runtime.

 

Is there a way to store a "generic" pointer to a sc_in, sc_out, or sc_inout?  Looking through the source code, it looks like everything is parameterized all the way back until sc_interface.  But sc_interface doesn't provide any methods to access the port (i.e. read/write).

 

Any hints or suggestions?

 

Hello Sir,

The question(s) you have raised are very interesting, and are more 

related to the core C++ language than SystemC - after all, SystemC

is a C++ library. So whatever works in the core C++ language, will

work with SystemC.

You have stated "But this is still a problem since T isn't known until

runtime"  is the key question. It is very doubtful if there is any C++

compiler in use that will allow dynamic type definition. 

Please note that the mother language of C++ is good old C, and C

provides a an amazing feature - the void pointer - void* . By

definition, a void pointer cannot be dereferenced, but can be

cast at runtime to any type. Sure, the C++ gurus would cringe

at the thought of using good old C style features, but what is more

important to you -- solving your problem, or keeping the C++

gurus happy ?

Hope that helps.

 

 

Share this post


Link to post
Share on other sites

While traversing, you should have realised already, that all ports (and the other SystemC objects) are derived from sc_object. But even more specifically, you can use a pointer to sc_port_base, the non-templated base class of all ports.

 

That said, the port_wrapper you've shown won't work as is. You'd need to do proper type erasure. But even with that, you still need to instantiate the correctly templated ports/channels within your testbench.  The step from a configuration file, i.e. some runtime data, to a (statically typed) C++ object then requires some kind of factory.

 

hth,
Philipp

Share this post


Link to post
Share on other sites

The question(s) you have raised are very interesting, and are more 

related to the core C++ language than SystemC - after all, SystemC

is a C++ library. So whatever works in the core C++ language, will

work with SystemC.

Sure. It appears that the design decision when implementing SystemC (or perhaps even part of the standard) was the use of templates. Or at least, templates deep enough into the class hierarchy to preclude the use of polymorphism to pass around base pointers with relevant methods (though Phillip does point out sc_port_base below, but more on this point in a bit).

 

You have stated "But this is still a problem since T isn't known until

runtime"  is the key question. It is very doubtful if there is any C++

compiler in use that will allow dynamic type definition.

I get the need for a factory. That isn't the issue. Rather, it was how to read/write a port from a base class, when I don't see a base class that will let me do that.

 

While traversing, you should have realised already, that all ports (and the other SystemC objects) are derived from sc_object. But even more specifically, you can use a pointer to sc_port_base, the non-templated base class of all ports.

I misread the hierarchy. I followed the template parameters to sc_port<>, rather than sc_port itself. This point, by itself, already sparked an idea. But working through the idea, I'm still hit a roadblock at sc_interface.

I can use something like:

std::unordered_map<const std::string, sc_core::sc_port_base *> dut_ports;

Then, after doing a lookup, I can do something like:

 

sc_core::sc_port_base *port = dut_ports["port_name"];
port->get_interface()->???

The returned pointer from get_interface() is an sc_interface, which has no read/write method. I'd have to downcast it to a templated class (sc_in_if, sc_out_if, or sc_inout_if), which requires a factory.

 

That said, the port_wrapper you've shown won't work as is. You'd need to do proper type erasure. But even with that, you still need to instantiate the correctly templated ports/channels within your testbench.  The step from a configuration file, i.e. some runtime data, to a (statically typed) C++ object then requires some kind of factory.

I do get the need for a factory.  When I generate the ports on my testbench module, I'll need a factory to create them of the appropriate size.  But because of the templated nature of ports, this can become a pain. Given the need to support sc_in/out/inout<sc_logic> and sc_in/out/inout<sc_lv<N> > for N in 1..X, I have to choose X sufficiently large to be useful (128? 256? 1024?) and make that a limitation on the testbench.  I can generate the factory with a script for a given value of X, but if X is too small for a particular use, I have to regenerate the factory and recompile.

I was hoping there might be a more abstract way to get a pointer an object that I can read/write. Digging in the code, it looks like only sc_in_if has read() and sc_out_if() has write, but both of these are templated classes.

Share this post


Link to post
Share on other sites

Sure. It appears that the design decision when implementing SystemC (or perhaps even part of the standard) was the use of templates. Or at least, templates deep enough into the class hierarchy to preclude the use of polymorphism to pass around base pointers with relevant methods (though Phillip does point out sc_port_base below, but more on this point in a bit).

 

You seem to misunderstand the relationship between templates and polymorphism here. Only the interfaces/classes that depend on a template type parameter are actually defined as a template.  You're looking for something like dynamic typing (as known from languages like Python), where you could define a write method for different types without explicit overloads.  This is not supported by C++ directly and you would need to build a mechanism for it on your own, if you really want this.

 

I can use something like:

std::unordered_map<const std::string, sc_core::sc_port_base *> dut_ports;

Then, after doing a lookup, I can do something like:

 

sc_core::sc_port_base *port = dut_ports["port_name"];
port->get_interface()->???
The returned pointer from get_interface() is an sc_interface, which has no read/write method. I'd have to downcast it to a templated class (sc_in_if, sc_out_if, or sc_inout_if), which requires a factory.

 

Instead of just storing the sc_port_base pointer, you could store an adapter class, encapsulating the correct type conversion from some generic write parameter written by the testbench to the final C++ type used in the DUT.  The adapter would then be constructed from the factory.  Lookup "type erasure" in your favourite search engine.  Still, this would only cover a specific channel interface (sc_port's template parameter), e.g. sc_signal_out_if for a set of compatible data types, e.g. sc_int_base for sc_int<N>.

 

I'm not convinced that this simple architecture actually solves a real problem.  Instead, you'd probably need to integrate something like the UVM driver concept on top of such a technique to orthogonalise concerns here.

 

 

I do get the need for a factory.  When I generate the ports on my testbench module, I'll need a factory to create them of the appropriate size.  But because of the templated nature of ports, this can become a pain. Given the need to support sc_in/out/inout<sc_logic> and sc_in/out/inout<sc_lv<N> > for N in 1..X, I have to choose X sufficiently large to be useful (128? 256? 1024?) and make that a limitation on the testbench.  I can generate the factory with a script for a given value of X, but if X is too small for a particular use, I have to regenerate the factory and recompile.

 

Theoretically, there is no need to recompile the testbench itself.  Only the "missing" factory entries (i.e. adapter template instantiations) would need to be compiled and then linked to build the final simulation model.  But yes, there's nothing you can do about this. But this is simply a consequence of C++ being natively compiled instead of dynamically interpreted.

 

If you're just interested in signals of SystemC datatypes, you can also create a wrapper around your DUT and add adapters from the fixed-size types to their corresponding base classes (sc_int<N> -> sc_int_base, ...) and reduce the complexity of the problem.

 

/Philipp

Share this post


Link to post
Share on other sites

First off, thank you for your responses! You have been very helpful.

You seem to misunderstand the relationship between templates and polymorphism here. Only the interfaces/classes that depend on a template type parameter are actually defined as a template.  You're looking for something like dynamic typing (as known from languages like Python), where you could define a write method for different types without explicit overloads.  This is not supported by C++ directly and you would need to build a mechanism for it on your own, if you really want this.

I'm a bit confused here. Say I had the following class definitions:

 

class base
{
  public:
    virtual void write(int) = 0;
    virtual int read() = 0;
};
I can define a template class like this:

 

template<typename T>
class derived : public base
{
  public:
    virtual void write(int)
    {
      // Do whatever is specific to convert int to T
    }


    // Override specific to T
    virtual void write(T)
    {
    }

    virtual int read()
    {
      // Do whatever is specific to convert T to int
    }

    // Override specific to T (perhaps the return value is by value)
    virtual const T& read()
    {
    }
};
I can then do:

 

base *p = new derived<sc_logic>;
I can then access read/write with:

 

p->read();
p->write(0); // Int only.  T specific would require a downcast to derived.
Getting back to my OP, I'm saying that sc_interface lacks a read/write with conversions from standard types, relying instead upon derived classes to provide them (sc_in_if provides read() and sc_out_if() provides write(), and sc_inout_if derives from sc_in_if and sc_out_if).

Granted, I'm no C++ expert (I'm perhaps "upper middle class" in terms of knowledge/experience). However, it seemed that it was a design decision to push the read/write methods to the derived classes (which are templates).

 

Instead of just storing the sc_port_base pointer, you could store an adapter class, encapsulating the correct type conversion from some generic write parameter written by the testbench to the final C++ type used in the DUT.  The adapter would then be constructed from the factory.  Lookup "type erasure" in your favourite search engine.  Still, this would only cover a specific channel interface (sc_port's template parameter), e.g. sc_signal_out_if for a set of compatible data types, e.g. sc_int_base for sc_int<N>.

I did some reading on type erasure. I get the concept, and I think I know of it implicitly (though your mention of its explicit name is new to me). Your explanation has cleared some things up for me. It appears that in order to be fully generic, I have to explicitly support the various sc_lv<N> options and sc_logic in a factory.

 

I'm not convinced that this simple architecture actually solves a real problem.  Instead, you'd probably need to integrate something like the UVM driver concept on top of such a technique to orthogonalise concerns here.

I'm not familiar with UVM (only having heard of it). Though I did register on Mentors "Verification Academy" to do some reading on it. Perhaps UVM can already solve what I'm trying to do.

  

Theoretically, there is no need to recompile the testbench itself.  Only the "missing" factory entries (i.e. adapter template instantiations) would need to be compiled and then linked to build the final simulation model.  But yes, there's nothing you can do about this. But this is simply a consequence of C++ being natively compiled instead of dynamically interpreted.

Well, to be clear what I'm trying to do is support a dynamic SystemC object for mixed language simulations. The idea is to declare an interface to a testbench object (call it "tb_control") in another language (either Verilog or VHDL), and connect that object to the DUT(s) under test. This "tb_control" object would dynamically determine it's interface (perhaps based on a configuration file) and drive stimulus and verify output. The testbench object would be scripted (language yet to be determined, perhaps embedded Lua).

So it's more than just linking in a new library with the additional methods. Of course, this restriction is tool dependent.

 

If you're just interested in signals of SystemC datatypes, you can also create a wrapper around your DUT and add adapters from the fixed-size types to their corresponding base classes (sc_int<N> -> sc_int_base, ...) and reduce the complexity of the problem.

Unfortunately, the DUT may be in a language other than SystemC. So a wrapper in SystemC for each DUT requires compilation, which is what this exercise is trying to avoid.

The ultimate goal is to have a single shared object (call it "tb_control.so") that is pulled into the simulator (we are using Modelsim right now, but the goal is to be cross platform by using SystemC rather than the FLI--though the FLI is significantly easier to use). And being that the DUT language can be Verilog or VHDL, I don't want to rely upon the VPI or VHPI (since not everyone has dual-language licenses).

Any, thanks for your responses. They are helpful. I'm going to dig a bit more.

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

×