p1id = 7Part 3 · Ownership and DV patterns
Draw the object graph before copying, eliminate unsafe aliases, and apply polymorphism, factories, and inherited constraints without brittle type checks.
Question directory
Open an authored answer, then use the adjacent object diagram or code example to verify the mechanism.
Distinguish handle assignment, built-in shallow object copy, explicit deep copy, shared configuration, and object lifetime so nested state cannot alias by accident.
p1id = 7H1v = 100P1[1] [2] [3]p2id = 7H1v = 100P1[1] [2] [3]same object IDs repeatH1 · P1 are aliased
Alias riskScalar fields are copied, but nested class handles still reach the same objects.
A shallow copy duplicates scalar members but preserves nested handles. A deep copy allocates every owned child object and then copies its value.
Handle assignment copies only the reference, so both variables name the same object. Object copying creates a second outer object, but the built-in copy is shallow for nested class handles.
| Operation | New outer object | Nested handles |
|---|---|---|
| b = a | No | Everything is shared |
| b = new a | Yes | Still shared |
b = a.deep_clone() | Yes | New objects when implemented correctly |
The copy form new rhs allocates a new object of the destination class and copies member values from rhs without running the normal user constructor. Nested object members are handles, so those handles are copied rather than the nested objects.
class Field;
int v;
endclass
class Packet;
int id;
Field hdr;
endclass
Packet p1 = new;
p1.hdr = new;
p1.hdr.v = 1;
Packet p2 = new p1; // shallow object copy
p2.hdr.v = 9; // p1.hdr.v is also 9Allocate a new outer object, copy scalar values, allocate a new object for every owned nested handle, recursively copy each nested value, and define how null or shared references should be treated.
function Packet deep_clone();
Packet clone = new(this.id);
if (this.hdr != null)
clone.hdr = new(this.hdr.v);
clone.payload.delete();
foreach (this.payload[i])
clone.payload.push_back(new(this.payload[i].v));
return clone;
endfunctionThe queue copy duplicates the sequence of handle values, not the objects those handles reference. Corresponding elements in both queues therefore point to the same child objects.
No. SystemVerilog class objects are garbage collected after no live handles reference them, and a user method named delete() has no special destructor semantics.
Apply polymorphic transactions, factories, singletons, and inherited constraints to verification architectures without turning every extension into a component rewrite.
read_packetwrite_packetpacket handle→pkt.display()pkt.format()base_c + child_cboth active · intersectbase_c → base_cderived block replaces baseDrivers and monitors can accept a base transaction handle while virtual methods preserve type-specific behavior. Constraint block names decide whether inherited rules layer or replace.
Verification code models many related transactions, policies, and components that evolve faster than the RTL interface. OOP lets common structure live in base types while derived classes add scenario-specific fields and behavior without rewriting generic infrastructure.
Accept a base packet handle and call virtual methods declared on the base. A read_packet or write_packet object then supplies its own override through runtime dispatch.
$cast chain inside the driver.class packet;
virtual function void display();
$display("BASE PACKET");
endfunction
endclass
class read_packet extends packet;
function void display();
$display("READ PACKET");
endfunction
endclass
class driver;
task drive(packet pkt);
pkt.display();
endtask
endclassA factory separates the request for a base contract from the decision about which concrete class to construct. Callers remain written against the base type while configuration selects a compatible derived implementation.
virtual class Driver;
pure virtual function void send(string message);
endclass
class UartDriver extends Driver;
function void send(string message);
$display("UART: %s", message);
endfunction
endclass
class DriverFactory;
static function Driver create(string kind);
UartDriver uart;
if (kind == "uart") begin
uart = new;
return uart;
end
return null;
endfunction
endclassA singleton provides one globally shared instance through a static accessor, which can suit a registry or process-wide service. Its risk is hidden global mutable state that couples tests and complicates reset, ordering, and parallel execution.
local property and protect construction so callers use get().class Logger;
local static Logger instance;
local function new();
endfunction
static function Logger get();
if (instance == null)
instance = new;
return instance;
endfunction
endclassInherited constraint blocks with different names are all active and their legal sets intersect. A derived constraint block with the same name replaces the inherited block of that name.
constraint_mode(0) can disable a named active block on an object at runtime.class BaseC;
rand int x;
constraint c_base { x inside {[0:5]}; }
endclass
class DerivedC extends BaseC;
constraint c_derived { x inside {[4:9]}; }
endclass
DerivedC d = new;
assert(d.randomize()); // x is 4 or 5
d.c_base.constraint_mode(0);
assert(d.randomize()); // x may be 4 through 9Check the return value from randomize() and $cast(). A failed randomization can leave stale field values, while a failed downcast leaves the destination handle unchanged and unusable as the requested derived type.
randomize()) or an explicit failure branch that reports enough context to debug the active constraint set.$cast(derived, base)) before accessing derived-only members, or assert the cast when failure is a testbench error.BasePacket base;
WritePacket write;
assert(base.randomize())
else $fatal(1, "packet randomization failed");
if (!$cast(write, base))
$fatal(1, "expected a WritePacket");Keep base contracts small, make ownership explicit, prefer composition unless a true substitutable subtype exists, use virtual methods only at intentional extension points, and keep construction separate from runtime work.
| Concern | Preferred practice | Failure signal |
|---|---|---|
| Subtype design | Small substitutable base contract | Frequent casts or type switches |
| Ownership | One documented owner or immutable sharing | Mutation visible in unrelated components |
| Construction | Factory or explicit new at a clear boundary | Concrete types scattered through callers |
| Lifecycle | Explicit phases and cleanup methods | Work starts from partially built objects |