Jump to content

Creating runtime variable design configuration


Andy Tomlin

Recommended Posts

I am working on a tool to allow the specification of architectural definition ( its open source https://bitbucket.org/arch2code/arch2code/src/main/ )

I want to be able to switch between two different implementations of a block. The interfaces will be defined in a shared base class, and there will be two derived classes per block. one a systemC transaction model, and one a systemC verification wrapper containing verilated code. Many of the pieces are working but I wanted to be able to configure which implementations are used at run time so we dont need to recompile for every test. The shared base class will derive from blockBase defined as follows 

class blockBase
{
public:
    virtual ~blockBase() = default;

};

there is a factory class as follows

class instanceFactory
{
public:
    instanceFactory();
    // allow implementation to register thier constructors
    static void registerBlock(std::string blockType, std::function< std::unique_ptr< blockBase >(const char * blockName, uint64_t baseAddress_) > blockFactoryFunction)
    {
        getMap().emplace(blockType, blockFactoryFunction);
    }
    // allow top level to create mappings from instance names to block types prior to enumeration of model
    static void registerInstance(std::string instance, std::string blockType)
    {
        getInstMap().emplace(instance, blockType );
    }
    static std::unique_ptr< blockBase > createInstance(const char * blockName, uint64_t baseAddress_)
    {
        // figure out what type of block to create for this instance
        auto instIt = getInstMap().find(blockName);
        std::string blockType;
        if (instIt != getInstMap().end())
        {
            blockType = instIt->second;
        } else {
            Q_ASSERT(false, str(boost::format("Attempted to create an an unregistered instance %s") % blockName) );
        }

        auto it = getMap().find(blockType);
        if (it != getMap().end())
        {
            return it->second(blockName, baseAddress_);
        } else {
            Q_ASSERT(false, str(boost::format("Attempted to create an instance %s of an unregistered block type %s") % blockName % blockType) );
        }
    }
private:
    static std::map<std::string, std::function<std::unique_ptr< blockBase>(const char * blockName, uint64_t baseAddress_)> >& getMap()
    {
        static std::map<std::string, std::function<std::unique_ptr< blockBase>(const char * blockName, uint64_t baseAddress_)>> map;
        return map;
    }
    static std::map<std::string, std::string>& getInstMap()
    {
        static std::map<std::string, std::string> map;
        return map;
    }
};

In the implementation of the block the class is defined as (note the dual implementation shared definition stuff not implemented yet, I wanted to get the factory working first):

SC_MODULE(producer), blockBase
{
private:
    logBlock log_;
    uint64_t baseAddress;

public:
    // src ports
    //   test_rdy_vld
    rdy_vld_out< data_st > test_rdy_vld;
    //   test_rdy_vld_burst
    rdy_vld_burst_out< rdyVldBurstSt < test_st, data_st > > test_rdy_vld_burst;
    //   test_rdy_vld_burst_no_tracker
    rdy_vld_burst_out< rdyVldBurstSt < test_no_tracker_st, data_st > > test_rdy_vld_burst_no_tracker;
    //   test_req_ack
    req_ack_out< data_st, data_st > test_req_ack;
    //   test_vld_ack
    vld_ack_out< data_st > test_vld_ack;
    //   test_rdy_ack
    rdy_ack_out< data_st > test_rdy_ack;

    // dst ports

    // channels


    producer(sc_module_name blockName, uint64_t baseAddress_);

    // GENERATED_CODE_END

    struct registerBlock
    {
        registerBlock() 
        { 
            // lamda function to construct the block
            instanceFactory::registerBlock("producer_model", [](const char * blockName, uint64_t baseAddress_) -> std::unique_ptr<blockBase> { return static_cast<std::unique_ptr<blockBase>> (std::make_unique<producer>(blockName, baseAddress_));} );
        }
    };
    static registerBlock registerBlock_;
    ~producer() override = default;
};

The top block above in the hierarchy contains the instance of producer 

    std::unique_ptr<producer> uProducer;

in the constructor for top uProducer needs initializing. I tried instanceFactory::createInstance("uProducer", baseAddress_ + 0x0) but it complains that I cannot cast a std::unique_ptr<blockBase> to std::unique_ptr<producer>. I have tried various things. I could avoid using the std::unique_ptr directly and instead just use a producer * uProducer;

Chatgpt has not helped, any ideas

Link to comment
Share on other sites

I'd rather use

dynamic_cast<producer*>

as it checks that the pointer is of correct type. If not it returns a nullptr sp you might have to check this before using it.

And you should publicly inherit of blockBase.

Did you have a look at UVM-SystemC (https://systemc.org/overview/systemc-verification/)? There all those mechanics are available and well testet. And its purpose is to structure verification environments and testbenches...

Link to comment
Share on other sites

16 hours ago, Eyck said:

I'd rather use

dynamic_cast<producer*>

as it checks that the pointer is of correct type. If not it returns a nullptr sp you might have to check this before using it.

And you should publicly inherit of blockBase.

Did you have a look at UVM-SystemC (https://systemc.org/overview/systemc-verification/)? There all those mechanics are available and well testet. And its purpose is to structure verification environments and testbenches...

I switched the code to instead use shared_ptr instead of unique_ptr. So the member initialization list uses 

uProducer(std::dynamic_pointer_cast<producerBase>( instanceFactory::createInstance(name(), "uProducer", "producer", baseAddress_ + 0x0)))

There is no need to check the pointer in our use case as the code will just instantly crash on dereferencing the nullptr - although this should never happen as its all generated code, and should have already asserted in the instanceFactory

I already switched blockBase to public, thanks.

I will take a look at UVM-SystemC. Our goal is to have an end to end tool - from architecture, to modeling, HW dev and verification. We are using our model code and the generated connections as the verification framework. We have SystemVerilog code compiling with verilator and integrated with the model. You can find more info https://arch2code.org/ 

Link to comment
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...