Jump to content
alangloria

"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_object_param_utils_begin(utility::slave_sequence_item#(BASE))

        `uvm_field_object(wait_barrier_, UVM_ALL_ON)

        `uvm_field_object(fin_barrier_, UVM_ALL_ON)

    `uvm_ojbect_utils_end

    function new(string name="");

        super.new(name);

        wait_barrier_ = new("wait_barrier_", 2);

        fin_barrier_ = new("fin_barrier_", 2);

    endfunction

 

    // to be called by sequence

    task wait_for_transaction;

        wait_barrier_.wait_for;

    endtask

    task finish_transaction;

        finish_barrier_.wait_for;

    endtask

    // to be called by driver

    task indicate_transaction;

        wait_barrier_.wait_for;

        finish_barrier_.wait_for;

    endtask

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

 

Share this post


Link to post
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;

endclass

 

// 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;

        actual.frobnicate;

    endtask

endclass

 

// 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;

    `uvm_component_param_utils(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;

    endfunction

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);

        super.build_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);

        ....

        frobnicatables.push_back(monitor.as_frobnicatable_base);

        frobnicatables.push_back(driver.as_frobnicatable_base);

        frobnicatables.push_back(sequencer.as_frobnicatable_base);

    endfunction : build_phase

 

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

Share this post


Link to post
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.

 

True

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)

Share this post


Link to post
Share on other sites

True

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

endclass

 

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

    `uvm_component_param_utils(visitor_capable#(NODE,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;

    endfunction

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.

 

Sincerely,

AmkG

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

×