Part 2 · OOP for Verification · Intermediate

extern Methods & Class File Organization

extern prototypes with out-of-body definitions via class scope ::, organizing large classes, and include-based class libraries.

Prototype inside, body outside

As classes grow, a 40-line method body buried inside the class makes the class's interface — what it offers — impossible to read at a glance. The extern qualifier solves this: declare only the prototype inside the class, and define the body later at file scope, attached to the class with the class scope resolution operator ::. The out-of-body definition is semantically inside the class — it sees this, local and protected members, everything — it is just written elsewhere.

systemverilog
class bus_driver;
  virtual bus_if vif;
  local int      txn_count;

  // The class body reads like a table of contents:
  extern function new(virtual bus_if vif);
  extern task          run();
  extern protected task drive_one(bus_txn t);
  extern function int   get_count();
endclass

// Out-of-body definitions: class_name::method_name
function bus_driver::new(virtual bus_if vif);
  this.vif = vif;
endfunction

task bus_driver::run();
  bus_txn t;
  forever begin
    t = new();
    void'(t.randomize());
    drive_one(t);          // full access to class members
  end
endtask

task bus_driver::drive_one(bus_txn t);
  @(posedge vif.clk);
  vif.addr <= t.addr;
  txn_count++;             // local member: visible, this IS the class
endtask

function int bus_driver::get_count();
  return txn_count;
endfunction

Matching rules

  • The out-of-body signature must match the prototype exactly — return type, argument types, directions, and defaults.

  • Qualifiers like virtual, protected, and static appear only on the prototype, not repeated at the definition.

  • The definition must be in the same scope (same file/package compilation scope) as the class declaration.


Why this matters at scale

This is not cosmetic. On a real VIP, a driver class can have fifteen methods totaling a thousand lines. With extern, a new team member reads the 25-line class declaration and knows the entire API and which methods are protected extension hooks — without scrolling through implementations. It also mirrors how reviewers and interviewers think: 'show me the class' means 'show me the prototypes'. UVM library source code itself follows exactly this style.

diagram
ONE BIG CLASS BODY            EXTERN-ORGANIZED

  class drv;                    class drv;
    task run();                   extern task run();
      ... 200 lines ...           extern task drive_one(...);
    endtask                       extern function int stats();
    task drive_one();           endclass   ◄── API visible in 5 lines
      ... 150 lines ...
    endtask                     task drv::run();        ... endtask
    ...                         task drv::drive_one();  ... endtask
  endclass                      function int drv::stats(); ... endfunction
  ▲ interface buried            ▲ interface first, bodies after

Include-based class libraries

The standard packaging for a multi-class testbench library: one file per class , and a single package that \`includes them in dependency order. The package provides one compilation scope (so out-of-body definitions and forward typedef class references resolve) and one import point for users. Compile the package once; never \`include a class file directly from two different scopes, or you get two distinct copies of the 'same' class that the compiler treats as unrelated types.

systemverilog
// bus_pkg.sv — the ONLY file the compile list mentions
package bus_pkg;

  typedef class bus_driver;   // forward declaration if needed

  `include "bus_txn.sv"      // each file holds one class
  `include "bus_driver.sv"   // (with its extern bodies)
  `include "bus_monitor.sv"
  `include "bus_scoreboard.sv"
  `include "bus_env.sv"      // depends on all of the above

endpackage

// In the testbench:
import bus_pkg::*;
bus_env env = new();

Key takeaways

  • extern splits prototype (in class) from body (at file scope, named class_name::method).

  • Out-of-body definitions are fully inside the class semantically — this, local, protected all work.

  • Prototype and definition signatures must match exactly; qualifiers stay on the prototype.

  • Package one class per file, included once into a single package — one compilation scope, one type.

Common pitfalls

  • Signature drift between prototype and out-of-body definition — compile errors that confuse beginners.

  • Repeating virtual/static/protected on the out-of-body definition — illegal; prototype only.

  • Including a class file into two scopes — two unrelated copies of the same-named class.

  • Include order ignoring dependencies — base classes and typedefs must precede their users.