Gotcha: Calling Virtual Functions From SystemVerilog Class Constructor new() Method

Do you know that the SystemVerilog LRM does not recommend calling a virtual function from within the class constructor new() function? Check out chapter 8.7 from IEEE Std 1800-2017 regarding Constructors. If you don’t know about this, for sure you might wonder why.

Here is a snippet from the LRM:

After the base class constructor call (if any) has completed, each property defined in the class shall be initialized to its explicit default value or its uninitialized value if no default is provided. After the properties are initialized, the remaining code in a user-defined constructor shall be evaluated. The default constructor has no additional effect after the property initialization. The value of a property prior to its initialization shall be undefined.

Let’s say, we implement the class c1:

class c1;
  int a;
  function new(string name="c1");
    super.new(name);
    set_defaults();// not recommended
  endfunction
  
  virtual function void set_defaults();
    a = 1;
  endfunction
endclass

Seems like a simple, regular piece of SystemVerilog code. You would think it makes sense to encapsulate the variable initialization logic inside a function. And why not make use of the object oriented programming feature and make that function virtual. By doing so, you might think you gain flexibility. You can decide which version of the function to call, depending on the class hierarchy.

But, hey, surprise: this should be avoided!

Here is a fully fledged example. Some simulators might actually do exactly what you expect, but others won’t.

class c_base;
  int a = 0;
  int b = 0;
  
  function new(string name="c_base");
    set_defaults();
  endfunction
  
  virtual function void set_defaults();
    a=1;
  endfunction
endclass

class c1 extends c_base;
  function new(string name="c1");
    super.new(name);
  endfunction
  
  virtual function void set_defaults();
    super.set_defaults();
    b=1;
  endfunction
endclass

module top;
  c1 c1_inst;
  
  initial begin
    c1_inst = new();    
    $display("c1_inst.a=", c1_inst.a);
    $display("c1_inst.b=", c1_inst.b);
  end
endmodule

Simulator X output:

c1_inst.a=1
c1_inst.b=1

Simulator Y output:

c1_inst.a=1
c1_inst.b=0

According to the standard, Simulator Y is right. Variable b is initialized to zero after calling the base class constructor.

Experiment and convince yourself by playing with this code on EdaPlayground.

In my work, I learned this the hard way. I had a similar set_defaults() function inside the verification environment code and I was also making use of it. In real projects this issue is stranger and at times you might think that you are chasing a ghost. Thus I lost many hours, probably days, debugging it.

The second time I saw this pattern, for a different client, it just rang a bell and I was prepared to notify the team and to search for a solution to it.

There is an easy way to spot issues like this by making use of a code linter for SystemVerilog (e.g. Verissimo).
But using code linters, is still not a wide-spread practice for the SystemVerilog testbench code. Thus, be aware when calling virtual functions from inside the class constructor.

Enjoy!

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to our newsletter

Do you want to be up to date with our latest articles?