A long method body buried in the class hides the interface. The extern qualifier keeps only the prototype inside the class and moves the body to file scope, attached with the class scope operator.
An out-of-body definition is semantically inside the class. It sees this handle, local members and protected members exactly as an in-body method does.
Rule | Prototype (in class) | Definition (at file scope) |
|---|---|---|
Signature | extern function int get_total(); | function int counter_ops::get_total(); |
Qualifiers | virtual, protected and static live here | Never repeated here |
Scope | Declares the API in one readable block | Same scope as the class declaration |
One class split across a prototype block and scope-resolved bodies, proven through its local state.
class counter_ops;
local int total;
// The class body reads like a table of contents.
extern function new();
extern function void add(int n);
extern function int get_total();
endclass
// Out-of-body definitions: class_name::method_name.
function counter_ops::new();
total = 0;
endfunction
function void counter_ops::add(int n);
total += n; // local member: visible, this IS the class
endfunction
function int counter_ops::get_total();
return total;
endfunction
module top;
counter_ops c;
int errors = 0;
initial begin
c = new();
c.add(2);
c.add(3);
if (c.get_total() !== 5) errors++;
if (errors == 0) $display("PASS: extern bodies added 2 and 3 for a total of 5");
else $fatal(1, "FAIL: extern-built total differs from 5");
$finish;
end
endmodulePASS: extern bodies added 2 and 3 for a total of 5A driver class with fifteen methods reads best as a 25-line declaration followed by bodies. Reviewers ask to see the class and mean the prototypes, which is also the style of the UVM library sources.
The standard library shape below is structure, not compiled code here: one file per class, included once into a single package in dependency order.
// snippet: multi-file library shape, shown as structure rather than compiled code
// bus_pkg.sv: the only file on the compile list
package bus_pkg;
typedef class bus_driver; // forward declaration when two classes name each other
`include "bus_txn.sv" // one class per file, dependency order
`include "bus_driver.sv" // extern bodies live with their class file
`include "bus_env.sv" // depends on everything above
endpackage
// In the testbench: import bus_pkg::*; then bus_env env = new();Never include one class file from two scopes: each inclusion declares a separate copy that the compiler treats as an unrelated type. Bodies that drive pins with forever and clock timing need a vif and a live interface, so they stay out of the checked single-file form.
Signature drift between prototype and body fails compilation with errors that confuse beginners. Repeating virtual, static or protected on the definition is illegal, since qualifiers belong to the prototype alone.
Yes. The body is semantically inside the class, so local and protected members behave exactly as they do for in-body methods.
The type forks. Each inclusion declares its own class, so handles from one package never fit the other and statics double.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. These class benches were run on Verilator only.