Inheritance, polymorphism and parameterised classes

Learning objectives
  • Extend a base class and call its members with super
  • Explain how virtual methods select behaviour at run time
  • Use $cast to move a base handle back to a derived type safely
  • Define abstract classes with pure virtual methods
  • Write a parameterised class
1 Objective→ 2 Learn→ 3 Example→ 4 Practice→ 5 Verify→ 6 Continue
How to study this lessonUnderstand the idea first. Use the example to see the behaviour, practise where provided, then make sure you can explain the result before continuing.

Explanation

extends and super

A derived class inherits every property and method of its base class and may add new ones or override existing ones.

class base_txn;
  rand bit [7:0] addr;

  function new();
  endfunction

  virtual function string convert2string();
    return $sformatf("addr=%h", addr);
  endfunction
endclass

class write_txn extends base_txn;
  rand bit [31:0] data;

  function new();
    super.new();              // must be the first statement
  endfunction

  virtual function string convert2string();
    return {super.convert2string(), $sformatf(" data=%h", data)};
  endfunction
endclass

super.new() runs the base constructor. If the base constructor has arguments, the derived constructor must pass them. super.method() reuses the base behaviour instead of copying it.

Virtual methods and polymorphism

A base-class handle may refer to any derived object. Which method body runs depends on whether the method is virtual.

Method in base class Call through base handle to a derived object Body that runs
Non-virtual b.show() Base class version (chosen by handle type)
virtual b.show() Derived class version (chosen by object type)
base_txn  b;
write_txn w = new();

initial begin
  b = w;                              // upcast: always legal
  $display(b.convert2string());       // runs write_txn version: virtual
end

Polymorphism lets a driver hold base_txn handles and still process every kind of transaction correctly. Once a method is declared virtual, it stays virtual in every derived class.

$cast for downcasting

Assigning a base handle to a derived handle is not allowed directly, because the object might not be the derived type. $cast checks at run time.

write_txn w2;

if ($cast(w2, b))
  $display("data=%h", w2.data);
else
  $error("object is not a write_txn");

Use $cast as a function and check its return value. Called as a task, a failed cast is a run-time error.

Abstract classes and pure virtual methods

A virtual class cannot be created with new; it defines a common interface for derived classes. A pure virtual method has no body and must be implemented by every non-abstract derived class.

virtual class checker_base;
  pure virtual function bit check(int expected, int actual);
endclass

class exact_checker extends checker_base;
  virtual function bit check(int expected, int actual);
    return expected == actual;
  endfunction
endclass

Parameterised classes

A class can take value or type parameters, like a module.

class fifo_model #(type T = int, int DEPTH = 16);
  T items[$:DEPTH-1];

  function bit put(T item);
    if (items.size() >= DEPTH) return 0;
    items.push_back(item);
    return 1;
  endfunction
endclass

fifo_model #(byte, 8) small_fifo = new();

Each distinct parameter set is a distinct class type, so fifo_model#(byte,8) and fifo_model#(int,16) handles cannot be assigned to each other.

Tool note: Inheritance, virtual methods and parameterised classes run on Verilator 5.x; Icarus 12 does not support classes.

Worked example

A generator creates a mix of write_txn and read_txn objects but stores them in a base_txn queue. The driver pops each handle and calls convert2string(). Because the method is virtual, writes print their data and reads print only the address. If the virtual keyword is removed from the base class, every line prints only the address: the handle type, not the object type, now selects the method.

Check yourself

Why must super.new() appear first in a constructor? What decides which method body runs for a virtual call? When is $cast needed, and how do you detect a failed cast? Why are two specialisations of a parameterised class not assignment-compatible?

Engineering practice

Practice: Inheritance, polymorphism and parameterised classes

Task→Run / reason→Check result→Explain why

Create base_txn with a virtual convert2string() and a virtual compare(). Derive read_txn and write_txn, each overriding both methods and calling super. Store ten mixed objects in a base_txn queue, print them, and use $cast to count how many are writes. Then make the base class abstract with a pure virtual compare() and confirm that new on it is rejected.

Practice work is non-certified and is not stored by ASIC Campus.