Jump to content

"Mixin" classes - parametrizing the base class

Recommended Posts

Hi UVM and SystemVerilog users,


I've stumbled upon a particular pattern of writing a "utility" class, which I have called "mixin".


class derived_class#(type BASE=base_class) extends BASE;


endclass : derived_class


This pattern was inspired by some C++ Boost code I saw, where the base class is templated.  The reasoning was that under some compilers, multiple inheritance had higher overhead than chains of inheritance (specifically, an "empty" base class might be allocated the minimum size, so multiple inheritance would increase the size of the object, but if you used a chain of inheritance and a derived class in the chain was "empty" (i.e. did not add any data members), it would not increase the object size).  So instead of inheriting from multiple C++ classes, you'd typedef a class like foo<bar<nitz> > and derive from that.


Since SystemVerilog has no multiple inheritance, I thought this pattern would be appropriate for use in SV, to at least ease some of the "oh no SV has no multiple inheritance oh no" pain.


I've defined a simple utility class, utility::slave_sequence_item (utility is a package) defined like so:


class slave_sequence_item#(type BASE=uvm_sequence_item) extends BASE;

    local uvm_barrier wait_barrier_;

    local uvm_barrier fin_barrier_;


        `uvm_field_object(wait_barrier_, UVM_ALL_ON)

        `uvm_field_object(fin_barrier_, UVM_ALL_ON)


    function new(string name="");


        wait_barrier_ = new("wait_barrier_", 2);

        fin_barrier_ = new("fin_barrier_", 2);



    // to be called by sequence

    task wait_for_transaction;



    task finish_transaction;



    // to be called by driver

    task indicate_transaction;




endclass : slave_sequence_item



Basically, this slave sequence item class adds three new methods.  By default, you just derive from utility::slave_sequence_item.  But if you already have an existing sequence item type derived from uvm_sequence_item, you just do typedef utility::slave_sequence_item#(my_sequence_item) my_slave_sequence_item; and the added methods and variables will get "mixed in" the my_slave_sequence_item type.


What do you think?


I've tested it on Cadence IUS10.20-s103, and it seems to work properly.  From my understanding of the IEEE standard, the above is not specifically disallowed (but then it might not be well supported on actual simulators).


Link to comment
Share on other sites

Thank you for checking on other simulators.  Our shop has only one simulator, so I couldn't check.  I fear that even if the pattern is technically valid under IEEE 1800-2005, it might not be a common use model and simulators might not handle it correctly.  Even our IUS10.2 occasionally crashes if you make an otherwise-unrelated syntax error if I use this pattern; if I remove this pattern and leave in the syntax error the compiler will report the syntax error correctly.


Using mixin classes and a few helper classes, it's possible to emulate multiple inheritance to the point that you can treat a class as being derived from multiple classes, with a lot of coding overhead.  For example, see below:


// This is the "unrelated" class that can be converted to.

// Notice it is not derived from a UVM base class.

virtual class frobnicatable_base;

    pure virtual task frobnicate;



// This is a helper class to translate from your class

// to frobnicatable_base

class frobnicatable_base_param#(type this_type = frobnicatable_base)

        extends frobnicatable_base;

    // this is where the magic happens!

    this_type actual;

    function new(this_type actual); this.actual = actual; endfunction

    virtual task frobnicate;





// You can derive from this class via frobnicatable#(uvm_component)

// to get multiple inheritance of frobnicatable and uvm_component:

class frobnicatable#(type BASE=uvm_component) extends BASE;

    typedef frobnicatable#(BASE) this_type;


    function new(string name, uvm_component parent); super.new(name, parent); endfunction


    // perform frobnication

    virtual task frobnicate;

        `uvm_info("FROBNICATE", "The component was frobnicated!", UVM_NONE);

    endtask : frobnicate


    // conversion to frobnicatable base

    function frobnicatable_base as_frobnicatable_base;

        frobnicatable_base_param#(this_type) rv;

        rv = new(this);

        return this;


endclass : frobnicatable


With this, you can create a queue of frobnicatable objects, which can be monitors, drivers, sequencers, agents, scoreboards, etc., but all of which are frobnicatable.


    frobnicatable_base frobnicatables[$];


    virtual function build_phase(uvm_phase phase);


        monitor = my_frobnicatable_monitor::type_id::create("monitor", this);

        driver = my_frobnicatable_driver::type_id::create("monitor", this);

        sequencer = my_frobnicatable_sequencer::type_id::create("sequencer", this);





    endfunction : build_phase


This technique is called "proxy object", and is another technique I picked up from Boost C++ library.

Link to comment
Share on other sites

It's not really the same thing as interface classes, because here you can also inherit the implementation, not just the interface.



But using interfaces allows you to implement as many as you like thus providing a many "can do" implementations to a single class. I agree this is a different approach (that I'm still trying to find a use for)

Link to comment
Share on other sites


But using interfaces allows you to implement as many as you like thus providing a many "can do" implementations to a single class. I agree this is a different approach (that I'm still trying to find a use for)


UVM 1.2 Visitors:


class visitor_capable_wrapper#(type NODE=uvm_void, TYPE=uvm_component) extends uvm_visitor#(NODE);

    local TYPE actual;

    function new(TYPE actual); this.actual = actual; endfunction

    virtual function void begin_v(); this.actual.begin_v(); endfunction

    virtual function void visit(NODE node); this.actual.visit(node); endfunction

    virtual function void end_v(); this.actual.begin_v(); endfunction



virtual class visitor_capable#(type NODE=uvm_void, BASE=uvm_component) extends BASE;


    function new(string name, uvm_component parent); super.new(name, parent); endfunction

    virtual function void begin_v(); endfunction

    pure virtual function void visit(NODE node);

    virtual function void end_v(); endfunction


    function uvm_visitor as_uvm_visitor;

        visitor_capable_wrapper#(NODE,visitor_capable#(NODE, BASE)) rv;

        rv = new(this);

        return rv;


endclass : visitor_capable


This way, you can make any component (sequencer, driver, monitor, agent, scoreboard...) be a visitor.


You probably need another visitor_capable_object so that UVM objects can also be converted to UVM Visitors, but well.




Link to comment
Share on other sites

  • 4 weeks later...

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.

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...