Every call through a handle involves a declared type known at compile time and an object type known only at runtime. The virtual keyword decides which one resolves the call.
Non-virtual calls bind to the declared type before simulation starts. Virtual calls read the actual object at call time and run the most-derived override.
Call kind | Who picks the body | When decided |
|---|---|---|
Non-virtual | Handle's declared type | Compile time (static dispatch). |
Virtual | Object's runtime type | Call time (dynamic dispatch). |
Once virtual | Every descendant stays virtual | Repeating the keyword is documentation. |
Reusable code is written once against base types: it calls comp.run() or txn.compare() through base handles. For derived behavior to take effect, those base methods must be virtual.
A missing virtual is a silent failure. The derived override compiles cleanly and never runs through base handles.
// snippet
class cfg_base;
int mode = 1;
endclass
class cfg_child extends cfg_base;
int mode = 2; // hides: the object carries two integers named mode
endclassThe base body. Non-virtual calls bind to the declared handle type at compile time.
Two. The child field hides the base field instead of overriding it, and each handle view sees its own copy.
b is a base handle pointing at a child object. What do b.who_nv() and b.who_v() return?1 and 2. who_nv is not virtual, so the handle's type picks base's body; who_v is virtual, so the object's type picks child's body.
Dispatch codes plus a driver-hook pair with a testbench that proves which body runs.
class base;
function int unsigned who_nv();
return 1;
endfunction
virtual function int unsigned who_v();
return 1;
endfunction
endclass
class child extends base;
function int unsigned who_nv();
return 2;
endfunction
virtual function int unsigned who_v();
return 2;
endfunction
endclass
class bus_pkt;
bit [31:0] addr;
endclass
class base_driver;
int unsigned n_pre = 0;
virtual task pre_drive(inout bit [31:0] addr);
n_pre++;
endtask
virtual task drive(inout bit [31:0] addr);
addr = addr + 4;
endtask
endclass
class err_driver extends base_driver;
virtual task pre_drive(inout bit [31:0] addr);
n_pre++;
addr[1:0] = 2'b01;
endtask
endclass
PASS: static calls use handle type, virtual calls and hooks use object typeRun 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.