1. OOP in SystemVerilog - Polymorphism

Polymorphism is a fundamental OOP concept that allows objects of different classes to be treated through a common base class interface. It's essential for UVM and enables flexible, reusable verification code.

What is Polymorphism?

Polymorphism means "many forms." In SystemVerilog:

  • A base class handle can point to a derived class object
  • When calling a virtual method, the actual method executed depends on the object type, not the handle type
  • Enables writing generic code that works with any derived class
Key Insight: Polymorphism requires the virtual keyword. Without it, the method called depends on the handle type, not the object type.

Virtual vs Non-Virtual Methods

flowchart LR
    subgraph VIRTUAL["Virtual Method"]
        V1["Base handle"] --> V2["Looks at OBJECT type"]
        V2 --> V3["Calls derived method"]
    end
    
    subgraph NONVIRTUAL["Non-Virtual Method"]
        N1["Base handle"] --> N2["Looks at HANDLE type"]
        N2 --> N3["Calls base method"]
    end
    
    style V3 fill:#d1fae5,stroke:#10b981
    style N3 fill:#fee2e2,stroke:#ef4444
AspectVirtual MethodNon-Virtual Method
Keywordvirtual functionfunction
ResolutionRuntime (object type)Compile-time (handle type)
PolymorphismYesNo
OverrideDerived method calledBase method called

Complete Example

class Animal;

  string name;

  function new(string name);
    this.name = name;
  endfunction

  // Virtual method - enables polymorphism
  virtual function void speak();
    $display("%s makes a sound", name);
  endfunction

  // Non-virtual method - no polymorphism
  function void info();
    $display("Animal: %s", name);
  endfunction

endclass


class Dog extends Animal;

  function new(string name);
    super.new(name);
  endfunction

  // Override virtual method
  virtual function void speak();
    $display("%s says: Woof!", name);
  endfunction

  // Override non-virtual method
  function void info();
    $display("Dog: %s", name);
  endfunction

endclass


class Cat extends Animal;

  function new(string name);
    super.new(name);
  endfunction

  virtual function void speak();
    $display("%s says: Meow!", name);
  endfunction

  function void info();
    $display("Cat: %s", name);
  endfunction

endclass

Testbench Demonstrating Polymorphism

module tb;

  initial begin
    Animal animal;
    Dog    dog;
    Cat    cat;

    dog = new("Buddy");
    cat = new("Whiskers");

    $display("=== Direct Access ===");
    dog.speak();  // Dog::speak()
    dog.info();   // Dog::info()
    cat.speak();  // Cat::speak()
    cat.info();   // Cat::info()

    $display("\n=== Polymorphism via Base Handle ===");
    
    // Base handle pointing to Dog object
    animal = dog;
    animal.speak();  // Dog::speak() - VIRTUAL: uses object type
    animal.info();   // Animal::info() - NON-VIRTUAL: uses handle type

    // Base handle pointing to Cat object
    animal = cat;
    animal.speak();  // Cat::speak() - VIRTUAL
    animal.info();   // Animal::info() - NON-VIRTUAL

    $display("\n=== Array of Mixed Types ===");
    process_animals();
  end

  // Generic function that works with any Animal
  function void process_animals();
    Animal zoo[3];
    
    zoo[0] = new("Generic");
    zoo[1] = Dog::new("Rex");
    zoo[2] = Cat::new("Tom");

    foreach (zoo[i]) begin
      zoo[i].speak();  // Polymorphic call
    end
  endfunction

endmodule

Output

=== Direct Access ===
Buddy says: Woof!
Dog: Buddy
Whiskers says: Meow!
Cat: Whiskers

=== Polymorphism via Base Handle ===
Buddy says: Woof!
Animal: Buddy
Whiskers says: Meow!
Animal: Whiskers

=== Array of Mixed Types ===
Generic makes a sound
Rex says: Woof!
Tom says: Meow!

Inheritance Chain Example

classDiagram
    class A {
        +virtual display()
        +message()
    }
    class B {
        +display()
        +message()
    }
    class C {
        +display()
        +message()
    }
    A <|-- B : extends
    B <|-- C : extends
    
    note for A "virtual keyword here"
    note for B "virtual inherited"
    note for C "virtual inherited"
class A;

  // Virtual in base class - polymorphism enabled for ALL derived classes
  virtual function void display();
    $display("Display: Class A");
  endfunction

  // Non-virtual - no polymorphism
  function void message();
    $display("Message: Class A");
  endfunction

endclass


class B extends A;

  // No need to redeclare 'virtual' - inherited from A
  function void display();
    $display("Display: Class B");
  endfunction

  function void message();
    $display("Message: Class B");
  endfunction

endclass


class C extends B;

  // Still virtual (inherited through A -> B)
  function void display();
    $display("Display: Class C");
  endfunction

  function void message();
    $display("Message: Class C");
  endfunction

endclass

Test Results

initial begin
  A a_h;
  B b_h;
  C c_h;

  b_h = new();
  c_h = new();

  // A handle -> B object
  a_h = b_h;
  $display("A handle -> B object:");
  a_h.display();  // "Display: Class B" (virtual)
  a_h.message();  // "Message: Class A" (non-virtual)

  // B handle -> C object
  b_h = c_h;
  $display("\nB handle -> C object:");
  b_h.display();  // "Display: Class C" (virtual)
  b_h.message();  // "Message: Class B" (non-virtual)
end

Polymorphism in UVM

Polymorphism is fundamental to UVM's flexibility:

UVM FeatureUses Polymorphism For
FactoryOverride components/transactions at runtime
SequencesBase sequence handle runs different sequence types
CallbacksInsert custom behavior without modifying base code
PhasesCall appropriate phase methods in derived components
// UVM Factory override example
class base_test extends uvm_test;
  `uvm_component_utils(base_test)

  my_env env;

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    
    // Factory creates the registered type (or its override)
    env = my_env::type_id::create("env", this);
  endfunction
endclass


class error_test extends base_test;
  `uvm_component_utils(error_test)

  virtual function void build_phase(uvm_phase phase);
    // Override transaction type for this test
    my_seq_item::type_id::set_type_override(error_seq_item::get_type());
    
    super.build_phase(phase);
  endfunction
endclass

Common Mistakes

MistakeResultFix
Forgetting virtualBase method called instead of derivedAdd virtual to base class method
Wrong signature in derivedNew method, not overrideMatch return type and arguments exactly
Declaring virtual only in derivedPartial polymorphismDeclare virtual in the topmost base class
Assigning base to derived handleCompile errorUse $cast() for downcasting
// WRONG - virtual only in derived class
class Base;
  function void foo();  // Not virtual
    $display("Base");
  endfunction
endclass

class Derived extends Base;
  virtual function void foo();  // Too late!
    $display("Derived");
  endfunction
endclass

// Result: Base handle calling foo() always prints "Base"

$cast for Downcasting

class Animal;
  virtual function void speak();
    $display("Animal sound");
  endfunction
endclass

class Dog extends Animal;
  function void fetch();
    $display("Fetching ball!");
  endfunction

  virtual function void speak();
    $display("Woof!");
  endfunction
endclass

module tb;
  initial begin
    Animal a;
    Dog d, d2;

    d = new();
    a = d;  // Upcast: OK (implicit)

    // a.fetch();  // ERROR: Animal doesn't have fetch()

    // Downcast: requires $cast
    if ($cast(d2, a)) begin
      d2.fetch();  // OK: d2 is Dog handle
    end else begin
      $error("Cast failed");
    end
  end
endmodule

Interview Questions

Q1: What is polymorphism in SystemVerilog?

Answer: Polymorphism allows a base class handle to reference derived class objects and call overridden methods. The actual method executed is determined at runtime based on the object type, not the handle type.

Q2: What keyword enables polymorphism?

Answer: The virtual keyword. It must be declared in the base class; derived classes inherit it automatically.

Q3: What happens without the virtual keyword?

Answer: The method call is resolved at compile-time based on the handle type. The base class method is called even if the object is of a derived type.

Q4: How does UVM use polymorphism?

Answer: UVM uses polymorphism extensively in the factory (type overrides), sequences, callbacks, and phase execution. It allows flexible, configurable testbenches without modifying base code.

Key Takeaways

  • virtual keyword enables polymorphism - declare in base class
  • Virtual methods: object type determines which method runs
  • Non-virtual methods: handle type determines which method runs
  • virtual is inherited - no need to redeclare in derived classes
  • Use $cast() for downcasting (base handle to derived handle)
  • UVM relies heavily on polymorphism for flexibility and reuse
Author
Mayur Kubavat
VLSI Design and Verification Engineer sharing knowledge about SystemVerilog, UVM, and hardware verification methodologies.

Comments (0)

Leave a Comment