Fake It ‘til You Make It - Emulating Multiple Inheritance in SystemVerilog

In the last post I talked about interface classes and how they can be used to separate what an object "can" do from "how" it does it. While using interface classes helps a lot in developing code that is modular and reusable, there are still things that are missing. In this discussion on LinkedIn, Ilia makes a very good observation with respect to extendibility. If we extend a base class (in our case, uvm_component), how can we propagate all of these changes to its subclasses (namely uvm_monitor, uvm_driver, etc.)? This is exactly the kind of problem which could be solved with multiple inheritance. Unfortunately, SystemVerilog doesn't support multiple inheritance, so I guess we're out of luck...


This is a companion discussion topic for the original entry at https://verificationgentleman.netlify.app/2014/09/02/emulating-multiple-inheritance-in-system-verilog.html
1 Like

Another good post! I’ll pack this technique into my little bag of tricks. :slight_smile:

hi Tudor, we’ve used mixins for a bunch of purposes (thanks to you for reminding us of the possibility) but I thought your readers might be interested in a potential problem with it. This problem caught me by surprise at first, although I suppose it should have been obvious. It definitely somewhat restricts the applicability of the mixin trick.

The problem is that the mixin’s constructor must call super.new() on its parameter/ancestor class, and therefore must know the signature of that class’s constructor. This, of course, severely restricts the set of classes to which the mixin can be applied. Similarly, the mixin’s constructor must have a signature matching its parameter/ancestor, otherwise anything derived from the mixin will get a nasty surprise when trying to call super.new().

For your example, this is straightforward. You always expect the mixin to be applied to some kind of uvm_component, so its constructor should always look like the usual uvm_component derivative:
– function new(string name, uvm_component parent = null);
– super.new(name, parent);
But suppose you have a mixin that could be applied equally to a uvm_object, a uvm_component, or some user-defined base class with a completely different constructor? Now you’re messed-up, because (a) the mixin’s constructor may call super.new() with the wrong arguments, and (b) the mixin’s constructor may have a signature that its child classes neither want nor expect.

There is, I believe, nothing that can be done about this. It’s just a fact of life in SystemVerilog, and it means that we need to write different mixins to suit different base classes. (Macros might help with that.) But it’s a limitation that users need to be aware of before they start work on it.

Yep, that’s true. I usually implement mixins for constraints (declarative code), so they’re targeted to a certain sequence item family. Luckily, the constructor for uvm_objects is fixed (which is a blessing in this case, but a curse in others), so this doesn’t cause problems.

For extending procedural code, I’m investigating callbacks more and more (a topic for a future post).

Hi Tudor,

Love your posts by the way, golden resource for advanced UVM stuff not covered elsewhere.

Anyway I am trying to use this mixin methodology for testcases like this:

typedef traffic_mixin #(base_test, my_traffic_type ) test_my_traffic_type;

And then my uvm_testname is set to test_my_traffic_type.

This is great except that since I am just using a typedef for the testname it is not registered with the factory and the test cannot be created.

By the way my mixin does use `uvm_component_param_utils() but that does not help.

The only way around this it seems is to do a full class extend such as:

class test_my_traffic_type extends traffic_mixin #(base_test, my_traffic_type);

`uvm_component_utils(test_my_traffic_type)

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

ugg… drowning in boilerplate.

Any ideas? Is there a way to register classes in the factory after creation?

Alan Wakeford

I have a similar setup where I want to write constrained tests like this. I also have a lot of boilerplate.

The solution I was thinking of (but haven’t tried yet) is to somehow create the UVM component factory proxies programatically (i.e. not via uvm\_component\_utils, but by re-writing some of the code it expands to). At the end of the day, UVM just wants some kind of proxy that can create something it calls test\_my\_traffic\_type. It doesn’t have to exist as a class. It can be an instance of base\_test with the constructor argument my\_traffic\_type.

Stay tuned.

Thanks Tudor for this wonderful article. I have a case where I have multiple users extending from a base UVM environment class that provides skeleton (basic features and connections). I also need to take all their implementations into single environment for stress test and other purposes. For demo I am not going add to tasks/function args template. Can the following be done, do I still need something like tlm_get_if.? Also I think I have to take care of class members.

interface class xyz_env_if;
	pure virtual task xyz_env_do_build();
	pure virtual function xyz_env_do_connect();
endclass

interface class lmn_env_if;
	pure virtual task lmn_env_do_build();
	pure virtual function lmn_env_do_connect();
endclass

interface class all_abc_env_if entends 
	xyz_env_if, 
	lmn_env_if;
endclass
	
my_environment extends uvm_environment;
	task build_phase();
		// ...
	endtask
	function connect_phase();
		// ...
	endfunction
endclass
	
base_top_xyz_env #(type BASE = my_environment) extends BASE implements xyz_env_if;
	virtual task xyz_env_do_build();
	endtask
	virtual function xyz_env_do_connect();
	endfunction
endclass

top_xyz_env extends base_top_xyz_env;
	task build_phase();
		super.build_phase();
		xyz_env_do_build();
	endtask
	function connect_phase();
		super.connect_phase();
		xyz_env_do_connect();
	endfunction
endclass

base_top_lmn_env  #(type BASE = my_environment) extends BASE implements xyz_lmn_if;
	task lmn_env_do_build();
	endtask
	function lmn_env_do_connect();
	endfunction
endclass

top_lmn_env extends base_top_lmn_env
	task build_phase();
		super.build_phase();
		lmn_env_do_build();
	endtask
	function connect_phase();
		super.connect_phase();
		lmn_env_do_connect();
	endfunction
endclass

top_all_abc_env extends 
	base_top_xyz_env #(base_top_lmn_env #(my_environment));
	task build_phase();
		super.build_phase();
		xyz_env_do_build();
		lmn_env_do_build();
	endtask
	function connect_phase();
		super.connect_phase();
		xyz_env_do_connect();
		lmn_env_do_connect();
	endfunction
endclass

This would work. For top_all_abc_env, you shouldn’t need to re-define build_phase or connect_phase, because you’re inheriting the implementations of both via the mixin inheritance. Since your interface classes only define functions that are used by build/connect_phase, and not functions that would be called by other code, you don’t even need them (the interface classes).

Regardless of interface classes, this code looks a bit inheritance-heavy. Is there no way to just instantiate an xyz env next to an lmn env in your top env?

1 Like

Thanks Tudor for the prompt response. Agree that its inheritance heavy as there will be more and more env with increasing size of design. The issue is that xyz, lmn and such are top level env for their respective tests so is the all_abc. How do I inherit all the functions by other env(s), just by parameters?

Today we copy all code from xyz and lmn env into all_abc as they inherit from same base env class. It leads to maintainability issues and code bloat as there are many more env than xyz and lmn:

class my_env extends uvm_environment; // base top env
// basic features
// ral agents
// reset agents
// autogenerated environments with agents for driving interface
// virtual sequencer connections
endclass

class top_xyz_env extends my_env;
// build and connections of custom xyz_lower_level env 
// with autogenerated environments.
endclass

class top_lmn_env extends my_env;
// build and connections of custom lmn_lower_level env
// with autogenerated environments.
endclass

class all_abc_env extends my_env;
// build and connections of xyz_lower_level env [copied over]
// build and connections of lmn_lower_level env [copied over]
endclass

Any other suggestion appreciated as I want to avoid asking other members or me having to repeatedly update all_abc_env and cause manual editing errors or breaks.