Assigning a derived handle to a base handle is an implicit, always-safe upcast. Every axi_txn is a bus_txn, so code expecting the base contract is satisfied.
The reverse claim needs runtime proof. Direct downcast assignment is a compile error, so all downcasts go through $cast, which checks the object's real type.
$cast used as a function returns 1 on success and 0 on failure, leaving the destination untouched. Used as a task, a failed cast is a simulator runtime error.
Reach for the function form on mixed streams where a miss is normal. Reserve the task form for casts that must succeed, where failure means a wiring bug.
Form | On success | On failure |
|---|---|---|
Function: if ($cast(d, s)) | Destination views the same object. | Returns 0; destination keeps its old value. |
Task: $cast(d, s); | Same reference transfer, no copy. | Simulator runtime error. |
Siblings | Never succeeds. | Shared parent creates no sideways path. |
Its old value, often null. Always branch on the return value before touching the destination.
b points at an axi_txn. What do $cast(a2, b) and $cast(h, b) return, with a2 an axi_txn handle and h an ahb_txn handle?1 and 0. The object really is an axi_txn, so the first cast succeeds; ahb_txn is a sibling, so the second fails and leaves h null.
Upcast, both $cast forms, sibling rejection, and enum conversion in one self-checking block. $cast also checks an int-to-enum conversion: it returns 0 for a value with no named member, such as 7 here.
class bus_txn;
bit [31:0] addr;
endclass
class axi_txn extends bus_txn;
bit [3:0] qos;
endclass
class ahb_txn extends bus_txn;
bit [7:0] hprot;
endclass
typedef enum {IDLE, BUSY, DONE} state_e;
PASS: upcast, checked downcast, sibling rejection, enum cast all holdRun 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.