Part 2 · OOP for Verification · Intermediate

Abstract Classes & Interface Classes

virtual class, pure virtual methods, interface class implements, and the API-contract pattern for drivers.

virtual class — a base that cannot be instantiated

Declaring virtual class stim_item; makes the class abstract : calling new() on it is a compile error. You can declare handles of the type, extend it, and pass derived objects around through it — you just cannot build a bare instance. Use it when the base exists only to define a shape for derived classes: a generic 'stimulus item' is meaningless on its own, so making it abstract turns 'someone instantiated the placeholder base' from a runtime mystery into a compile error.

Inside a virtual class, a pure virtual method pure virtual function bit check(); — declares a prototype with no body. Every concrete (non-abstract) subclass MUST implement it, enforced at compile time. This is how a base class makes a method mandatory rather than optional-with-default.

systemverilog
virtual class stim_item;            // abstract — cannot new()
  string name;

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

  pure virtual function void pack(ref bit [7:0] bytes[$]);
  pure virtual function bit  check();

  // abstract classes CAN have concrete methods too
  virtual function void announce();
    $display("[%s] sending", name);
  endfunction
endclass

class eth_frame extends stim_item;
  rand bit [47:0] dst, src;

  function new(string name = "eth_frame");
    super.new(name);
  endfunction

  // compile error if either pure virtual is left unimplemented
  virtual function void pack(ref bit [7:0] bytes[$]);
    bytes = {>>{dst, src}};
  endfunction
  virtual function bit check();
    return dst != 48'h0;
  endfunction
endclass

stim_item s;
s = new();              // COMPILE ERROR — abstract
eth_frame f = new();    // OK
s = f;                  // OK — abstract handle, concrete object

interface class — multiple inheritance of contract

SystemVerilog allows only one extends parent, but a class may implements any number of interface classes . An interface class is a pure contract: pure virtual method prototypes (plus typedefs/parameters), no data, no implementation. Implementing one promises 'I provide these methods', checked at compile time. This gives you multiple inheritance of contract without the data-layout ambiguities of full multiple inheritance — a component can be simultaneously 'reportable' and 'resettable' regardless of what it extends. Note the unfortunate name clash: an interface class is a class construct, unrelated to the signal-bundle interface used with DUT pins.

systemverilog
interface class reportable;
  pure virtual function string report();
endclass

interface class resettable;
  pure virtual task do_reset();
endclass

// ONE extends parent + MANY implements contracts
class axi_driver extends base_component
                 implements reportable, resettable;
  int txn_count;

  virtual function string report();
    return $sformatf("axi_driver: %0d txns", txn_count);
  endfunction

  virtual task do_reset();
    txn_count = 0;
  endtask
endclass

// Generic services work on the CONTRACT, not the class tree:
task reset_all(resettable items[$]);
  foreach (items[i]) items[i].do_reset();
endtask
diagram
CONTRACT vs IMPLEMENTATION INHERITANCE

  virtual class (abstract)          interface class
  ─────────────────────────        ─────────────────────────
  data + methods + pure protos     pure virtual protos ONLY
  extends — ONE parent             implements — MANY allowed
  shares implementation            shares only a promise

         base_component   reportable   resettable
                │              │            │
                └─ extends ─┐  └─implements─┤
                            ▼               ▼
                        axi_driver ─────────┘
        one implementation parent, two contracts

The API-contract pattern for drivers

A practical use in verification: define the driver's API as an abstract class (or interface class), and let the environment depend only on that contract. The env stores an abstract handle; tests inject any concrete driver — real protocol driver, error-injecting driver, no-op stub for bring-up — without the env changing. The abstract base makes the dependency direction explicit: the env owns the contract, concrete drivers conform to it. This is dependency inversion, and it is the hand-rolled ancestor of the UVM factory override mechanism.

systemverilog
virtual class driver_api;
  pure virtual task start();
  pure virtual task drive(bus_txn t);
  pure virtual task wait_idle();
endclass

class env;
  driver_api drv;        // env depends ONLY on the contract

  function void set_driver(driver_api d);
    drv = d;
  endfunction

  task run(mailbox #(bus_txn) mb);
    bus_txn t;
    drv.start();
    forever begin
      mb.get(t);
      drv.drive(t);      // dispatches to whatever was injected
    end
  endtask
endclass

// test selects the concrete behavior:
//   env.set_driver(real_axi_driver_inst);   // normal test
//   env.set_driver(error_driver_inst);      // error-injection test
//   env.set_driver(stub_driver_inst);       // env bring-up

Interview angle

  • 'virtual class vs interface class?' — abstract class: data + partial implementation, single extends; interface class: pure contract, multiple implements.

  • 'What does pure virtual buy you?' — compile-time enforcement that every concrete subclass implements the method; no risky empty default.

  • 'How do you get multiple inheritance in SV?' — you don't for implementation; interface classes give multiple inheritance of contract.

Key takeaways

  • virtual class = abstract: cannot be instantiated, exists to be extended; handles of it are fine.

  • pure virtual methods make implementation mandatory in concrete subclasses, checked at compile time.

  • interface class + implements gives multiple contracts per class without multiple implementation parents.

  • Code environments against abstract contracts; inject concrete drivers per test — the manual factory pattern.

Common pitfalls

  • new() on a virtual class — compile error; build a concrete subclass and assign it to the abstract handle.

  • Empty default bodies instead of pure virtual — forgotten overrides run silent no-ops instead of failing the build.

  • Putting data or method bodies in an interface class — illegal; contracts are prototypes only.

  • Confusing interface class with the signal interface construct — same keyword, completely different feature.