Utility & Field Macros
Every class in this section so far has opened with a line like `uvm_object_utils(bus_txn) or `uvm_component_utils(bus_driver), with a promise to explain it later. This page is that explanation — what these macros actually generate, the difference between the two forms, and how the same macro family can auto-generate everything covered on the previous page instead of hand-writing it.
Utility macros: factory registration
class bus_driver extends uvm_driver #(bus_txn);
`uvm_component_utils(bus_driver) // components
// ...
endclass
class bus_txn extends uvm_sequence_item;
`uvm_object_utils(bus_txn) // objects
// ...
endclass
At minimum, both macros do the same job: they register the class with the factory (see The UVM Factory) and generate the plumbing type_id::create() needs to work — get_type_name() (an instance method) and the static type_name() it depends on. Without this registration, the factory has no way to know the class exists at all, which is why every create() call in this section has depended on one of these two macros being present.
The plain forms register the class and nothing else — no automatic print, copy, compare, or pack. For that, both macros have an extended _begin/_end form that wraps field declarations:
class bus_txn extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit [31:0] data;
`uvm_object_utils_begin(bus_txn)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_object_utils_end
function new(string name = "bus_txn");
super.new(name);
endfunction
endclass
This single block replaces every hand-written do_print/do_copy/do_compare/do_pack/do_unpack from the previous page — each field macro call registers that field with all five mechanisms at once. There's also a parameterized variant, `uvm_object_param_utils/ `uvm_component_param_utils (and their _begin/_end forms), needed specifically when the class itself is parameterized (class my_seq #(type T=int) extends uvm_sequence #(T);) — the factory has to handle each type parameterization as effectively a distinct registered type.
Choosing uvm_object_utils vs. uvm_component_utils isn't just cosmetic — it has to match the class's actual constructor shape. uvm_object-branch classes take new(string name = ""); uvm_component-branch classes take new(string name, uvm_component parent). Using the wrong utils macro for a class's branch won't compile, which in practice makes this a self-checking choice rather than a trap — but it's worth knowing why the compiler rejects it rather than treating it as an arbitrary rule.
The field macro family
`uvm_field_int above is one member of a family with a matching macro per field type:
`uvm_object_utils_begin(bus_txn)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_enum(op_e, op, UVM_ALL_ON)
`uvm_field_string(label, UVM_ALL_ON)
`uvm_field_object(payload, UVM_ALL_ON)
`uvm_field_queue_int(history, UVM_ALL_ON)
`uvm_object_utils_end
Each takes the field name plus a flag argument controlling exactly which mechanisms apply to it. Two independent flag groups exist, and it's worth knowing both:
Behavior flags — which mechanisms this field participates in:
| Flag | Effect |
|---|---|
UVM_ALL_ON / UVM_DEFAULT | Participate in copy, compare, print, and pack (the normal default) |
UVM_NOCOPY | Excluded from copy()/clone() |
UVM_NOCOMPARE | Excluded from compare() |
UVM_NOPRINT | Excluded from print()/sprint() |
UVM_NOPACK | Excluded from pack()/unpack() |
UVM_REFERENCE | For object-type fields: copies the handle only, not a deep copy of the nested object |
Print-format flags — how a field's value is displayed, independent of whether it's included at all:
| Flag | Effect |
|---|---|
UVM_BIN / UVM_DEC / UVM_OCT / UVM_HEX | Radix for printing |
UVM_UNSIGNED | Display without a sign |
UVM_STRING | Treat as a string value |
UVM_TIME | Format as simulation time |
Flags combine with a bitwise OR when more than one applies, e.g. `uvm_field_int(addr, UVM_ALL_ON | UVM_HEX).
UVM_REFERENCE is worth calling out specifically: on an object-type field (declared with `uvm_field_object), the default behavior is a deep copy — cloning the nested object's own contents. UVM_REFERENCE changes that to a shallow handle copy, meaning the copy and the original end up pointing at the same nested object. That's occasionally exactly what's wanted (a shared config object, for instance), but it's a meaningfully different semantic from every other field type on this page, where copy always means "duplicate the value" — worth double-checking deliberately rather than assumed.
UVM_REFERENCE isn't just a style choice when the field's type is itself a uvm_component — it's close to mandatory. Deep-copying a component doesn't make sense (a component is a fixed node in the testbench hierarchy, not a value), so a `uvm_field_object on a component-typed field without UVM_REFERENCE will attempt exactly that illegal deep copy the first time copy()/clone()/compare() runs on it, and UVM raises a run-time FATAL error rather than silently doing something wrong. In practice this means: `uvm_field_object is for object-branch (data-like) fields such as a nested config or transaction, almost always with UVM_ALL_ON; if a field happens to hold a component handle, it needs UVM_REFERENCE explicitly, or it shouldn't be field-macro'd at all.
Also worth knowing: UVM_NOCOMPARE (and the other exclusion flags) only suppress the automatic, macro-generated comparison — they have no effect on a hand-written do_compare() override. If a class defines its own do_compare() that explicitly reads a field flagged UVM_NOCOMPARE, that field is still compared; the flag only opts a field out of the field-macro-generated behavior, it doesn't retroactively hide the field from code that looks at it directly.
Field macros still go inside _begin/_end
The structural rule ties directly back to the section above: field macros are only meaningful between `uvm_object_utils_begin/ `uvm_object_utils_end (or the component equivalents) — they're not standalone. And regardless of which registration form is used, objects still need to be created through the factory (bus_txn::type_id::create(...)) to get override support — the macros generate the registration and the automation hooks, but create() is still the caller's responsibility.
What's next
With the class hierarchy, object capabilities, and the macros that register/automate them all in view, the next section moves from theory into practice — building a complete testbench, component by component, starting with the interface and transaction that everything else in it will depend on.