Friday, May 8, 2015

Polymorphism in SystemVerilog

Polymorphism is one the most important OOP features used by the modern SystemVerilog test benches. This article explores a basic use case for this feature.

Assume a testbench needs to monitor a number of DUT interfaces. Those monitors need to be able to reset their internal queues and some state variables whenever reset event happens during the simulation. In addition to resetting queues and variables, they all have one common reset requirement. Here's the code that reflects those assumptions:

class mon_a;
...
function common();
...
endfunction : common
function mon_a_specific();
...
endfunction : mon_a_specific
function reset();
$write("Reset A in progress...\n");
common();
mon_a_specific();
...
endfunction : reset
endclass : mon_a

class mon_b;
...
function common();
...
endfunction : common
function mon_b_specific();
...
endfunction : mon_b_specific
function reset();
$write("Reset B in progress...\n");
common();
mon_b_specific();
...
endfunction : reset
endclass : mon_b

After instantiating the monitors in the test bench, we can create them and access their reset methods whenever reset conditions are met.

class tb_env;
...
mon_a m_mon_a;
mon_b m_mon_b;
...
mon_n m_mon_n;
...

m_mon_a = new();
m_mon_b = new();
...
m_mon_n = new();

... @ (reset) begin
   m_mon_a.reset();
   m_mon_b.reset();
   ...
end
...
endclass : tb_env

In the example above, each monitor has its own independent reset method. The solution is acceptable, but not efficient. We had to copy and paste the same code to make sure that mon_a and mon_b complete common() as part of their reset procedure. We also had to implement common() method in both classes and make sure it's exactly the same. Any subsequent change will result in excessive maintenance. Not only it's tedious and time consuming, it's also prone to errors. Moreover, any additional monitor will require to re-implement common(). Maintenance hardship increases as the number of monitors grows.

A solution to avoid the maintenance debacle is to create a base class and derive monitors and other dynamic components from it. Classes derived from mon_base can overwrite reset() method and by doing so capitalize on polymorphism. The reset() method in the base class takes care of the common functionality and the reset() methods in monitor classes deal with specifics.

class mon_base;
function common();
...
endfunction : common
virtual function reset();
$write("Reset in progress...\n");
common();
endfunction : reset
endclass : monbase

class mon_a extends mon_base;
...
function mon_a_specific();
...
endfunction : mon_a_specific
function reset();
super.reset();
mon_a_specific();
...
endfunction : reset
endclass : mon_a

class mon_b extends mon_base;
...
function mon_b_specific();
...
endfunction : mon_b_specific
function reset();
super.reset();
mon_a_specific();
...
endfunction : reset
endclass : mon_b

A keyword "virtual" must be used in declaration of reset() function in mon_base to make it available to be overwritten in derived class. Calling super.reset() in derived class enables functionality from the base class to be reused by the derivative. Those classes don't have to call super methods of course, and they can rewrite the base class methods from scratch.

Adding a base class saves time spent on maintenance and lowers a chance of accidental errors caused by copy and paste. In addition, tb_env becomes agnostic to the specific nature of each monitor when it reacts to the reset. Let's put all the monitors into one big array using the base class and see what happens:

class tb_env;
...
mon_base monitors[N];
...
mon_a Mon_a = new("Mon_a");
mon_b Mon_b = new("Mon_b");
...
monitors[0] = Mon_a;
monitors[1] = Mon_b;
...

@ (reset) begin
   foreach (monitors[i]) begin
      monitors[i].reset();
   end
end
...
endclass : tb_env 

From tb_env perspective, it deals with a number of identical components that have reset() methods. It doesn't care about specifics. Though tb_env sees all the objects as the objects of the base class type, whenever it calls reset(), it essentially calls reset() of the derived class, not the base class. 

In this trivial example, use of polymorphism allowed us to create a framework that can be reused by many testbenches in the future. This framework may be shared across multiple module level testbenches and subsystems in the same project and across multiple projects. In addition, it provides a common look and feel to all the testbenches and serves as a basis for a library.

Friday, November 15, 2013

Interface class in SystemVerilog. What is it good for?

SystemVerilog is an object oriented language. Unfortunately, not all the features inherent in such a language are available. Multiple inheritance is one of them. The new SystemVerilog standard IEEE Std 1800-2012 (SV12) introduces a new class type called "interface class" which can be extended from two or more other interface classes. Unfortunately, the multiple inheritance starts and ends with this type of the class, and due to strict limitations of what can be done with it, the new class type won't solve a problem described below. Moreover, we can't really talk about inheritance when we talk about Interface class.

With continuous migration to standard verification methodologies, such as VMM, OVM, and UVM, the lack of ability to inherit from multiple classes causes a lot of grief to the testbench developers. Every SystemVerilog based verification methodology offers a library full of methodology specific base classes. It's essential to extend from those classes when building business unit libraries which are later used as a foundation for  testbenches used to verify units, subsystems, IP's, or systems on chip. Lately, UVM (Universal verification methodology) emerges as the most popular in the industry. I will use it to demonstrate a problem which may require multiple inheritance.

Some of the core building blocks of UVM testbench are drivers and monitors. UVM provides base classes for both of them. Those classes are extensions of uvm_component. When building a business unit library it's best to create classes in such a way that a testbench developer would never need to extend from a methodology class. It helps to make the testbench methodology agnostic and adds a layer which enables bringing some business unit specific functionality without modifying existing testbenches.

In case of UVM, let's assume there is a need for business unit extensions of all dynamic objects in the testbench to have some common method which may be called during one of the UVM phases. Since every dynamic object is an extension of uvm_component, it's a good idea to create bu_component as an extension of uvm_component, and place the required methods there. This approach dictates the rest of the library components to extend from bu_component. There is a problem though, since uvm_driver and uvm_monitor are left out, becasue they do not extend from bu_component. If you look at those classes in UVM 1.1, they don't seem like much, but cutting them away will prevent from benefiting from future methodology developments. 

An ideal solution to this problem would be the use multiple inheritance, which means, the bu_driver should inherit both, bu_component and uvm_driver. But how to do that, if SystemVerilog doesn't allow extension from multiple classes? There are multiple ways to achieve the desired.

One way is to copy contents of uvm_driver and uvm_monitor into bu_driver and bu_monitor respectively, while bu_driver and bu_monitor extend from bu_component. It can be done using scripts each time a new UVM version is introduced. Another way is to create a wrapper around uvm_driver or uvm_monitor instance, which extends from bu_component. This way there is no need to update the business unit classes when a new version of UVM is published, but the wrapper adds additional layer of complexity.

Even though we can extend an Interface class from two or more other Interface classes, it doesn't mean there is multiple inheritance. The reason is - there is nothing to inherit. Interface class defines parameters, type definitions and methods. It cannot provide implementations or variables. In fact, 1800-2012 specifically mentions that no part of Interface class is ever inherited when some other class implements its methods. Even parameters defined in Interface class cannot be referred directly, they are only accessible through the scope resolution operator. Whenever a class extends a parent and implements Interface class, there is no multiple inheritance. Moreover, the class cannot implement two versions of the method defined Interface class. Parametrizing won't help, since SystemVerilog does not support two methods with the same name but different output or variables type. In other words, there is no C++ style polymorphism in SystemVerilog.

Interface classes can be used to provide an elegant solution to list methods required by business unit specific functionality, making sure any business unit extension of methodology class adheres to the business unit rules. They help to make code more modular and self explanatory. They also allow to standardize methods names which perform similar functions. If needed, no method declaration can be allowed inside implementation classes, creating a clear divide between declarations and implementations. Functionally, there is nothing new this class is bringing to the table. It provides some other way of doing the same things, but doesn't make your life easier or tougher. It can help enforce some coding and naming conventions, and that's about it.

The example below demonstrates the use of interface class: 

interface class bu_interface_class #(type T = bit);
   typedef enum {
                 EN_PASS,
                 EN_FAIL,
                 EN_INCONCLUSIVE
                 } t_buStatus;
   
   pure virtual function string BuSpecificMethod(T Var);
endclass : bu_interface_class

class vm_base_class;

   string name;
   
   rand bit a;
   rand int b;

   function new(string name = "vm_base");
      this.name = name;
   endfunction : new
   
   virtual function string psdisplay(string prefix);
      psdisplay = $psprintf("%s:%s: a=%0b, b=%0d",prefix,name,a,b);
   endfunction : psdisplay
         
endclass : vm_base_class

virtual class bu_base_class extends vm_base_class implements bu_interface_class #(bit);
   rand bu_interface_class#(bit)::t_buStatus Status;

   function new(string name = "test");
      super.new(name);
   endfunction : new
   
   virtual function string BuSpecificMethod(bit Var);
      if (Var == 1)
        BuSpecificMethod = Status.name();
      else
        BuSpecificMethod = "Unsupported";
   endfunction : BuSpecificMethod

   virtual function string psdisplay(string prefix);
      psdisplay = $psprintf("%s %s",
                  super.psdisplay(prefix),
                  BuSpecificMethod(1'b1));
   endfunction : psdisplay
   
endclass : bu_base_class


class test_class extends bu_base_class;

   constraint c_test {
      a == 0;
      b inside {[1:10]};
      Status inside {bu_interface_class#(bit)::EN_PASS,bu_interface_class#(bit)::EN_FAIL};
   }

   function new(string name = "test");
      super.new(name);
   endfunction : new
   
endclass : test_class

   
program test();
   vm_base_class Vm;
   test_class    TestVm;
   
   initial begin
      Vm     = new("base_vm");
      TestVm = new("test_vm");
      
      TestVm.randomize();
      Vm.randomize();

      $display(Vm.psdisplay("TEST"));
      $display(TestVm.psdisplay("TEST"));
      $finish;
   end
   
endprogram : test

The code above produces the following simulation results:

TEST:base_vm: a=0, b=-2124444300
TEST:test_vm: a=0, b=8 EN_PASS
$finish called from file "ex01.sv", line 80.
$finish at simulation time                    0


As you can see in this example, I was able to extend from a methodology specific base class, while adding a business specific method at the same time. I was forced to implement the method defined by interface class to adhere to the business rules. In case the business unit specific functionality requires a change (add methods or change a type or a number of variables), once interface class has changed, my extension most likely won't compile, which will prompt me to review my implementation and fix the code accordingly. 

The same result could have been achieved without using Interface class. I don't find enough compelling reasons to start using it on a mature testbench. It can be considered while developing new libraries and to enforce naming conventions across multiple testbenches.