Skip to content

UVM

Field Macros — `uvm_field_*

What field macros automate, complete catalogue, flag options, critical limitations, performance cost.

UVM Fundamentals · Module 16

What Field Macros Do — The Six Auto-Generated Methods

A uvm_sequence_item needs to support printing, copying, comparing, packing, unpacking, and recording. Without field macros you write all six yourself. With field macros you declare each field once and the framework generates all six.

Field macros automation diagram
Field macros automation diagram

Figure 1 — Field macros auto-generate six object automation methods. One `uvm_field_* line per field replaces 30–50 lines of manual do_copy/do_compare/do_print/do_pack/do_unpack/do_record code.

The Complete Field Macro Catalogue

MacroUsed ForExample
``uvm_field_int(NAME, FLAG)`All integral types: bit, logic, int, byte, integer, longint, packed structs``uvm_field_int(addr, UVM_ALL_ON)`
``uvm_field_string(NAME, FLAG)`string fields``uvm_field_string(tag, UVM_ALL_ON)`
``uvm_field_object(NAME, FLAG)`uvm_object sub-objects (handles)``uvm_field_object(sub_pkt, UVM_ALL_ON)`
``uvm_field_real(NAME, FLAG)`real fields (note: no packing)``uvm_field_real(voltage, UVM_ALL_ON)`
``uvm_field_enum(TYPE, NAME, FLAG)`Enum fields — requires the enum type as first arg``uvm_field_enum(opcode_t, op, UVM_ALL_ON)`
``uvm_field_array_int(NAME, FLAG)`Fixed or dynamic arrays of integers``uvm_field_array_int(byte_arr, UVM_ALL_ON)`
``uvm_field_array_object(NAME, FLAG)`Arrays of uvm_object handles``uvm_field_array_object(pkts, UVM_ALL_ON)`
``uvm_field_array_string(NAME, FLAG)`Arrays of strings``uvm_field_array_string(tags, UVM_ALL_ON)`
``uvm_field_queue_int(NAME, FLAG)`Queues of integers``uvm_field_queue_int(data_q, UVM_ALL_ON)`
``uvm_field_queue_object(NAME, FLAG)`Queues of uvm_object handles``uvm_field_queue_object(pkt_q, UVM_ALL_ON)`
``uvm_field_aa_int_string(NAME, FLAG)`Associative array: int[string]``uvm_field_aa_int_string(reg_map, UVM_ALL_ON)`
``uvm_field_aa_object_string(NAME, FLAG)`Associative array: uvm_object[string]``uvm_field_aa_object_string(db, UVM_ALL_ON)`
``uvm_field_event(NAME, FLAG)`SystemVerilog events (print/record only)``uvm_field_event(done_ev, UVM_ALL_ON)`

The begin/end Pattern — Every Field Macro Needs a Wrapper

Field macros must be enclosed between uvm_object_utils_begin` and uvm_object_utils_end (for objects) or ``uvm_component_utils_begin and uvm_component_utils_end` (for components). The simple uvm_object_utils(class)` macro is equivalent to begin/end with no fields in between.

SystemVerilog — complete APB transaction with all field types
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef enum bit { WRITE=0, READ=1 } dir_e;
 
class apb_txn extends uvm_sequence_item;
 
    // ── Field declarations ────────────────────────────────────────────
    rand bit [31:0]   addr;
    rand bit [31:0]   data;
    rand    dir_e     direction;
    rand bit [3:0]    strobe;    // byte enables
    bit     [1:0]    resp;      // not randomised
    string             tag;       // debug label
    bit     [7:0]    data_q[$]; // payload queue
 
    // ── Factory registration with field automation ────────────────────
    `uvm_object_utils_begin(apb_txn)
        `uvm_field_int   (addr,      UVM_ALL_ON)
        `uvm_field_int   (data,      UVM_ALL_ON)
        `uvm_field_enum  (dir_e, direction, UVM_ALL_ON)
        `uvm_field_int   (strobe,    UVM_ALL_ON)
        `uvm_field_int   (resp,      UVM_NOCOMPARE | UVM_PRINT)
        `uvm_field_string(tag,       UVM_NOCOMPARE | UVM_PRINT)
        `uvm_field_queue_int(data_q, UVM_ALL_ON)
    `uvm_object_utils_end
 
    function new(string name = "apb_txn");
        super.new(name);
    endfunction
 
    constraint c_valid_addr { addr[1:0] == 2'b00; }  // word-aligned
 
endclass
 
// ── What you get for free — all without writing any of these manually:
apb_txn a = apb_txn::type_id::create("a");
apb_txn b = apb_txn::type_id::create("b");
void'(a.randomize());
a.print();                    // formatted table via do_print
b.copy(a);                    // deep copy via do_copy
`uvm_info("T", $sformatf("%0d", a.compare(b)), UVM_LOW)  // 1 = match
bit [7:0] packed[$];
void'(a.pack_bytes(packed));   // serialize to bytes
b.unpack_bytes(packed);        // deserialize back

Flags and Options — Controlling Each Field's Behaviour

The second argument to every ``uvm_field_*macro is a flag that controls which methods include this field. Flags are bit masks combined with|`.

FlagValueEffect
UVM_ALL_ON0x3FFInclude field in all six operations. The standard choice for most fields.
UVM_COPY1 << 0 = 0x001Include in do_copy()
UVM_NOCOPY1 << 1 = 0x002Exclude from do_copy()
UVM_COMPARE1 << 2 = 0x004Include in do_compare()
UVM_NOCOMPARE1 << 3 = 0x008Exclude from do_compare() — use for debug fields like tag, timestamp, resp
UVM_PRINT1 << 4 = 0x010Include in do_print()
UVM_NOPRINT1 << 5 = 0x020Exclude from print — use for large arrays or sensitive data
UVM_PACK0x100Include in do_pack() / do_unpack()
UVM_NOPACK1 << 9 = 0x200Exclude from pack — use for metadata that doesn't travel with the packet
UVM_DEFAULT0x1FFThe combined "everything on" flag — copy, compare, print, record and pack
UVM_READONLYprint onlyPrint only — no copy, compare, or pack. Good for internal counters and timestamps.
UVM_HEXdisplay modePrint value as hexadecimal. Combine: UVM_ALL_ON | UVM_HEX
UVM_DECdisplay modePrint value as decimal.
UVM_BINdisplay modePrint value as binary.
SystemVerilog — flag combinations for common field categories
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`uvm_object_utils_begin(my_txn)
 
    // ── Payload fields: copy, compare, pack, print ────────────────────
    `uvm_field_int(addr,      UVM_ALL_ON | UVM_HEX)   // print as 0x...
    `uvm_field_int(data,      UVM_ALL_ON | UVM_HEX)
    `uvm_field_int(strobe,    UVM_ALL_ON | UVM_BIN)   // print as 0b...
 
    // ── Control field: all ops but show as decimal ────────────────────
    `uvm_field_int(burst_len, UVM_ALL_ON | UVM_DEC)
 
    // ── Response field: do not compare, do not pack — just print ─────
    `uvm_field_int(resp,      UVM_NOCOMPARE | UVM_NOPACK | UVM_PRINT | UVM_COPY)
 
    // ── Debug tag: print only — never affects compare or copy ─────────
    `uvm_field_string(debug_tag, UVM_READONLY)
 
    // ── Timestamp: print only, not compared, not packed ───────────────
    `uvm_field_int(timestamp, UVM_READONLY)
 
    // ── Sub-object: include in copy+compare, exclude from pack ────────
    `uvm_field_object(header_pkt, UVM_ALL_ON | UVM_NOPACK)
 
    // ── Queue: all operations ─────────────────────────────────────────
    `uvm_field_queue_int(payload_bytes, UVM_ALL_ON)
 
    // ── Enum: remember the type comes first ───────────────────────────
    `uvm_field_enum(opcode_t, opcode, UVM_ALL_ON)
 
`uvm_object_utils_end

What Gets Generated — The Simplified Expansion

Understanding what the macros expand to helps you predict their behaviour and know when they will not do what you expect.

SystemVerilog — simplified expansion of uvm_field_int(addr, UVM_ALL_ON)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// `uvm_field_int(addr, UVM_ALL_ON) expands to contributions inside each
// of the automation framework methods. Simplified equivalent:
 
// Contribution to do_copy:
function void do_copy(uvm_object rhs);
    apb_txn rhs_;
    $cast(rhs_, rhs);
    super.do_copy(rhs);
    this.addr = rhs_.addr;   // ← addr field contribution
    // ... data, direction, strobe ...
endfunction
 
// Contribution to do_compare:
function bit do_compare(uvm_object rhs, uvm_comparer comparer);
    apb_txn rhs_;
    $cast(rhs_, rhs);
    return super.do_compare(rhs, comparer) &&
           comparer.compare_field("addr", this.addr, rhs_.addr, $bits(addr));
endfunction
 
// Contribution to do_print:
function void do_print(uvm_printer printer);
    super.do_print(printer);
    printer.print_field("addr", this.addr, $bits(addr), UVM_HEX);
endfunction
 
// Contribution to do_pack/do_unpack:
function void do_pack(uvm_packer packer);
    super.do_pack(packer);
    packer.pack_field(this.addr, $bits(addr));
endfunction
 
function void do_unpack(uvm_packer packer);
    super.do_unpack(packer);
    this.addr = packer.unpack_field_int($bits(addr));
endfunction
 
// The actual macro expansion uses a dispatch table with virtual methods —
// it goes through uvm_field_automation() which adds runtime dispatch overhead.
// This is why field macros are slower than direct do_* implementations.

Limitations — When to Stop Using Field Macros

Side-by-Side: With Macros vs Manual do_* Methods

With field macros — convenient, and fixed in what it can express:

SystemVerilog — field macros
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class apb_txn extends uvm_sequence_item;
    `uvm_object_utils_begin(apb_txn)
        `uvm_field_int(addr, UVM_ALL_ON)
        `uvm_field_int(data, UVM_ALL_ON)
    `uvm_object_utils_end
 
    rand bit [31:0] addr, data;
    function new(string n="apb_txn"); super.new(n); endfunction
endclass
 
// What you give up:
//   1. Generic, introspective comparison on every compare/copy call
//   2. No sub-range comparison — cannot compare addr[15:0] only
//   3. No conditional logic — cannot skip a field based on ignore_mask
//   4. Display formatting is whatever the flag selects
//   5. Packed structs and unions need workarounds

Manual do_* — explicit, and able to express the thing the macros cannot:

SystemVerilog — manual do_copy / do_compare
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class apb_txn extends uvm_sequence_item;
    `uvm_object_utils(apb_txn)
 
    rand bit [31:0] addr, data;
    bit [31:0]      addr_mask = 32'hFFFF_FFFF;
 
    function new(string n="apb_txn"); super.new(n); endfunction
 
    function void do_copy(uvm_object rhs);
        apb_txn r;
        if (!$cast(r, rhs)) `uvm_fatal("COPY", "type mismatch in do_copy")
        super.do_copy(rhs);
        addr = r.addr;
        data = r.data;
    endfunction
 
    function bit do_compare(uvm_object rhs, uvm_comparer comparer);
        apb_txn r;
        bit ok = 1;
        if (!$cast(r, rhs)) `uvm_fatal("CMP", "type mismatch in do_compare")
        ok &= super.do_compare(rhs, comparer);
        // The point of going manual: compare only the masked address bits.
        ok &= comparer.compare_field("addr", addr & addr_mask,
                                     r.addr & r.addr_mask, 32);
        ok &= comparer.compare_field("data", data, r.data, 32);
        return ok;
    endfunction
endclass

Note &= rather than && in do_compare. && short-circuits, so the first differing field prevents every later compare_field call from running — and running is what records a miscompare with the comparer. do_copy and do_compare covers that trap in full.

Ready-to-Run Simulator Example

Complete demonstration: an APB transaction with field macros, showing print(), compare(), clone(), pack_bytes(), and the effect of selective flags. Copy to field_macros_demo.sv and run. Ready to Run — Questa / VCS / Xcelium

SystemVerilog — field_macros_demo.sv (complete, copy and run)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// field_macros_demo.sv — complete ready-to-run UVM field macro demonstration
//
// Compile and run:
//   Questa : vlog -sv field_macros_demo.sv &&
//            vsim -c field_macros_top -do "run -all; quit"
//   VCS    : vcs -sverilog -ntb_opts uvm field_macros_demo.sv && ./simv
//   Xcelium: xrun -sv -uvm field_macros_demo.sv -input "run; exit"
 
`include "uvm_macros.svh"
import uvm_pkg::*;
 
// ═══════════════════════════════════════════════════════════════════════
//  ENUM TYPE (must be defined outside the class)
// ═══════════════════════════════════════════════════════════════════════
typedef enum bit { APB_WRITE=0, APB_READ=1 } apb_dir_e;
 
// ═══════════════════════════════════════════════════════════════════════
//  TRANSACTION WITH FIELD MACROS
// ═══════════════════════════════════════════════════════════════════════
class apb_txn extends uvm_sequence_item;
 
    rand bit [31:0] addr;
    rand bit [31:0] data;
    rand    apb_dir_e direction;
    rand bit [3:0]  strobe;
    bit     [1:0]  pslverr;     // response — excluded from compare
    string           debug_tag;   // label — excluded from compare
    bit     [7:0]  payload[$];  // byte queue
    time             issue_time;  // readonly — print only
 
    `uvm_object_utils_begin(apb_txn)
        `uvm_field_int      (addr,      UVM_ALL_ON | UVM_HEX)
        `uvm_field_int      (data,      UVM_ALL_ON | UVM_HEX)
        `uvm_field_enum     (apb_dir_e, direction, UVM_ALL_ON)
        `uvm_field_int      (strobe,    UVM_ALL_ON | UVM_BIN)
        `uvm_field_int      (pslverr,   UVM_NOCOMPARE | UVM_PRINT | UVM_COPY)
        `uvm_field_string   (debug_tag, UVM_NOCOMPARE | UVM_PRINT)
        `uvm_field_queue_int(payload,   UVM_ALL_ON)
        `uvm_field_int      (issue_time,UVM_READONLY)
    `uvm_object_utils_end
 
    function new(string name = "apb_txn");
        super.new(name);
    endfunction
 
    constraint c_align  { addr[1:0] == 2'b00; }
    constraint c_strobe { strobe != 4'h0; }
 
endclass
 
// ═══════════════════════════════════════════════════════════════════════
//  TEST — exercises all field macro capabilities
// ═══════════════════════════════════════════════════════════════════════
class field_macro_test extends uvm_test;
    `uvm_component_utils(field_macro_test)
 
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
 
    task run_phase(uvm_phase phase);
        apb_txn a, b, c;
        bit [7:0] packed_bytes[$];
        phase.raise_objection(this);
 
        // ── Create and randomize ──────────────────────────────────────
        a = apb_txn::type_id::create("txn_a");
        void'(a.randomize() with { addr == 32'hA000_0000; direction == APB_WRITE; });
        a.data      = 32'h1234_5678;
        a.pslverr   = 2'b00;
        a.debug_tag = "test_write_1";
        a.issue_time = $time;
        a.payload   = { 8'h12, 8'h34, 8'h56, 8'h78 };
 
        // ── print(): shows formatted table ────────────────────────────
        `uvm_info("TEST", "=== print() output ===", UVM_NONE)
        a.print();
 
        // ── clone(): deep copy ────────────────────────────────────────
        $cast(b, a.clone());
        b.set_name("txn_b_clone");
        `uvm_info("TEST", "=== compare() after clone — must be 1 ===", UVM_NONE)
        `uvm_info("TEST", $sformatf("a.compare(b) = %0d", a.compare(b)), UVM_NONE)
 
        // ── Modify and compare — should show mismatch ─────────────────
        b.data = 32'hFFFF_0000;
        `uvm_info("TEST", "=== compare() after data change — must be 0 ===", UVM_NONE)
        `uvm_info("TEST", $sformatf("a.compare(b) = %0d", a.compare(b)), UVM_NONE)
 
        // ── pslverr is NOCOMPARE — change it, compare still passes ────
        $cast(c, a.clone());
        c.pslverr = 2'b11;  // error response
        `uvm_info("TEST", "=== compare() with different pslverr — must be 1 (NOCOMPARE) ===", UVM_NONE)
        `uvm_info("TEST", $sformatf("a.compare(c) = %0d", a.compare(c)), UVM_NONE)
 
        // ── pack_bytes(): serialize to byte array ─────────────────────
        void'(a.pack_bytes(packed_bytes));
        `uvm_info("TEST",
            $sformatf("pack_bytes() produced %0d bytes", packed_bytes.size()),
            UVM_NONE)
 
        // ── unpack_bytes(): deserialize back ──────────────────────────
        b.data = 0; b.addr = 0;
        void'(b.unpack_bytes(packed_bytes));
        `uvm_info("TEST",
            $sformatf("After unpack: addr=0x%0h data=0x%0h", b.addr, b.data),
            UVM_NONE)
 
        // ── sprint(): get the print as a string ───────────────────────
        `uvm_info("TEST", "=== sprint() into string ===", UVM_NONE)
        `uvm_info("TEST", a.sprint(), UVM_NONE)
 
        `uvm_info("TEST", "=== All field macro tests passed ===", UVM_NONE)
        phase.drop_objection(this);
    endtask
endclass
 
// ═══════════════════════════════════════════════════════════════════════
//  TOP MODULE
// ═══════════════════════════════════════════════════════════════════════
module field_macros_top;
    initial run_test("field_macro_test");
endmodule
 
// ═══════════════════════════════════════════════════════════════════════
//  EXPECTED OUTPUT (abridged):
//
//  TEST: === print() output ===
//  -----------------------------------------------
//  Name            Type        Size  Value
//  -----------------------------------------------
//  txn_a           apb_txn     -     @1
//    addr          integral    32    'ha0000000
//    data          integral    32    'h12345678
//    direction     apb_dir_e   1     APB_WRITE
//    strobe        integral    4     'b1111
//    pslverr       integral    2     'h0
//    debug_tag     string      12    test_write_1
//    payload       da(integral) 4    ...
//    issue_time    integral    64    'h0      (readonly)
//  -----------------------------------------------
//
//  TEST: compare() after clone — must be 1
//  TEST: a.compare(b) = 1
//
//  TEST: compare() after data change — must be 0
//  TEST: a.compare(b) = 0
//
//  TEST: compare() with different pslverr — must be 1 (NOCOMPARE)
//  TEST: a.compare(c) = 1
//
//  TEST: pack_bytes() produced N bytes
//  TEST: After unpack: addr=0xa0000000 data=0x12345678
//  TEST: === All field macro tests passed ===
// ═══════════════════════════════════════════════════════════════════════
Shell — compile and run commands for all major simulators
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
## ── Questa ────────────────────────────────────────────────────────────
vlog -sv -timescale 1ns/1ps field_macros_demo.sv
vsim -c field_macros_top +UVM_VERBOSITY=UVM_NONE \
     -do "run -all; quit -f"
 
## ── VCS ───────────────────────────────────────────────────────────────
vcs -sverilog -ntb_opts uvm -timescale=1ns/1ps field_macros_demo.sv -o simv
./simv +UVM_TESTNAME=field_macro_test +UVM_VERBOSITY=UVM_NONE
 
## ── Xcelium ───────────────────────────────────────────────────────────
xrun -sv -uvm -timescale 1ns/1ps field_macros_demo.sv \
     -input "run; exit" \
     +UVM_TESTNAME=field_macro_test +UVM_VERBOSITY=UVM_NONE
 
## ── To see UVM report summary:
##    Remove +UVM_VERBOSITY=UVM_NONE from any command above

Quick Reference

TaskSyntax
Wrap field macros (object)``uvm_object_utils_begin(class_name) ... uvm_object_utils_end
Wrap field macros (component)``uvm_component_utils_begin(class_name) ... uvm_component_utils_end
Integer field (all ops)``uvm_field_int(field_name, UVM_ALL_ON)`
Integer field (hex display)``uvm_field_int(field_name, UVM_ALL_ON | UVM_HEX)`
Enum field``uvm_field_enum(enum_type, field_name, UVM_ALL_ON)`
String field``uvm_field_string(field_name, UVM_ALL_ON)`
Object field (sub-object)``uvm_field_object(field_name, UVM_ALL_ON)`
Queue of integers``uvm_field_queue_int(field_name, UVM_ALL_ON)`
Exclude from compare (timestamps, responses)UVM_NOCOMPARE | UVM_PRINT | UVM_COPY
Print only (read-only metadata)UVM_READONLY
Print and call all auto methodstxn.print() / s = txn.sprint()
Clone (deep copy to new object)$cast(clone_h, txn.clone())
Pack to bytesvoid'(txn.pack_bytes(byte_queue))
Unpack from bytesvoid'(txn.unpack_bytes(byte_queue))

§9 — Code Examples

Example 1 — Beginner: Minimal APB Transaction with Field Macros

The most common starting point. Three fields, three macros, and you get print(), copy(), compare(), and pack/unpack — plus convert2string() and record() — all for free. This is what the generated output actually looks like.

SystemVerilog — APB transaction with field macros and generated output
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class apb_seq_item extends uvm_sequence_item;
    `uvm_object_utils_begin(apb_seq_item)
        `uvm_field_int(addr,  UVM_DEFAULT)
        `uvm_field_int(data,  UVM_DEFAULT)
        `uvm_field_int(write, UVM_DEFAULT)
    `uvm_object_utils_end
 
    rand logic [31:0] addr;
    rand logic [31:0] data;
    rand logic        write;
    function new(string name="apb_seq_item"); super.new(name); endfunction
endclass
 
// ── What each generated method produces ──────────────────────────────
//
// txn.print():
//   --------------------------------------------------------
//   Name        Type    Size  Value
//   --------------------------------------------------------
//   apb_seq_item  object   -
//     addr        integral  32    'h4000
//     data        integral  32    'hA5A5
//     write       integral  1     'h1
//
// txn.convert2string():
//   "addr='h4000 data='ha5a5 write='h1"
//
// txn1.compare(txn2):
//   Returns 1 if all three fields match, 0 otherwise.
//   Prints mismatch detail if +UVM_REPORT_VERBOSITY=UVM_HIGH
//
// txn_copy = txn.clone(): deep copy of all three fields (new object)
//
// txn.pack_ints(arr):
//   Packs addr[31:0] + data[31:0] + write[0] = 65 bits into integer array
 
// ── Scoreboard compare using auto-generated compare() ────────────────
function void write(apb_seq_item actual);
    apb_seq_item expected;
    // ... get expected ...
    if (!actual.compare(expected)) begin
        `uvm_error("SCB", $sformatf("Mismatch:\n  actual: %s\n  expected: %s",
            actual.convert2string(), expected.convert2string()))
    end
endfunction

Example 2 — Intermediate: Flags Controlling Comparison and Print

The UVM_DEFAULT flag is actually a combination. Breaking it apart gives you precise control over which operations include which fields.

SystemVerilog — field flags for selective comparison and print
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// UVM_DEFAULT = UVM_ALL_ON = 0x01FF
// Deconstructed: COPY | COMPARE | PRINT | RECORD | PACK | UNPACK | NORADIX
//
// Selectively disable specific operations per field:
 
class packet extends uvm_sequence_item;
    `uvm_object_utils_begin(packet)
        // addr: full default — compare, print, copy, pack all enabled
        `uvm_field_int(addr, UVM_DEFAULT)
 
        // data: compared and printed, but EXCLUDED from pack/unpack
        `uvm_field_int(data, UVM_DEFAULT | UVM_NOPACK)
 
        // timestamp: printed (for debugging) but NOT compared
        // Timestamps differ between expected and actual — compare must skip it
        `uvm_field_int(timestamp, UVM_DEFAULT | UVM_NOCOMPARE)
 
        // debug_tag: internal debug only — never printed in clean regression logs
        `uvm_field_int(debug_tag, UVM_COPY | UVM_COMPARE)
        // UVM_COPY | UVM_COMPARE: copy and compare but don't print
 
        // status: excluded from comparison entirely (DUT-side, cannot predict)
        `uvm_field_int(status, UVM_DEFAULT | UVM_NOCOMPARE)
    `uvm_object_utils_end
 
    rand logic [31:0] addr;
    rand logic [31:0] data;
    logic       [63:0] timestamp;
    logic       [7:0]  debug_tag;
    logic       [3:0]  status;
    function new(string n="packet"); super.new(n); endfunction
endclass
 
// ── Common flag values ────────────────────────────────────────────────
//
// UVM_DEFAULT    = 0x01FF  All operations enabled (copy+compare+print+pack+...)
// UVM_NOCOMPARE  = 0x0008  Exclude from compare()  (bit 3)
// UVM_NOPRINT    = 0x0010  Exclude from print()
// UVM_NOPRINTX   = 0x0010  (same as NOPRINT in most implementations)
// UVM_NOPACK     = 0x0100  Exclude from pack/unpack
// UVM_NOCOPY     = 0x0001  Exclude from copy() — rarely used
// UVM_COPY       = 0x0001  Explicitly enable copy
// UVM_COMPARE    = 0x0004  Explicitly enable compare
// UVM_PRINT      = 0x0008  Explicitly enable print

Example 3 — Verification: Dynamic Array and Queue Fields

Field macros handle dynamic arrays and queues natively — but the behaviour differs from what engineers expect when the array size changes between operations.

SystemVerilog — dynamic array and queue field macros
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class burst_txn extends uvm_sequence_item;
    `uvm_object_utils_begin(burst_txn)
        `uvm_field_int(start_addr, UVM_DEFAULT)
        // Dynamic array of 32-bit data words
        `uvm_field_array_int(data_words, UVM_DEFAULT)
        // Queue of byte-sized error flags
        `uvm_field_queue_int(error_flags, UVM_DEFAULT)
        // Enum field
        `uvm_field_enum(burst_type_e, burst_type, UVM_DEFAULT)
    `uvm_object_utils_end
 
    rand logic [31:0]  start_addr;
    rand logic [31:0]  data_words[];  // dynamic array
    rand logic [7:0]   error_flags[$]; // queue
    rand burst_type_e  burst_type;
    function new(string n="burst_txn"); super.new(n); endfunction
endclass
 
// ── What compare() does with arrays ──────────────────────────────────
// 1. Compares array SIZE first — different sizes → immediate mismatch
// 2. Compares elements index by index
// 3. Prints element-level mismatch detail at UVM_HIGH verbosity
//
// ── What copy() does with dynamic arrays ─────────────────────────────
// Creates a new dynamic array of the same size and copies each element.
// This IS a deep copy for int/logic arrays — each element is a value type.
// For object arrays (uvm_field_array_object), copy() does SHALLOW copy
// — each element is still a shared handle. Use do_copy() for true deep copy.
//
// ── Tricky: compare() on different-length arrays ─────────────────────
burst_txn a = burst_txn::type_id::create("a");
burst_txn b = burst_txn::type_id::create("b");
a.data_words = new[4]('{1,2,3,4});
b.data_words = new[3]('{1,2,3});
// a.compare(b) → 0 (FAIL) — size mismatch detected first
// Error: "data_words: size mismatch: lhs=4 rhs=3"

Example 4 — Tricky: Object Handle Field — The Shallow Copy Trap

The most dangerous field macro behaviour. When a transaction contains a handle to another object, ``uvm_field_object` does a shallow copy by default. Both the original and the copy point to the same sub-object. Modifying one corrupts the other.

SystemVerilog — object handle field: shallow vs deep copy
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class header extends uvm_object;
    `uvm_object_utils_begin(header)
        `uvm_field_int(hdr_type, UVM_DEFAULT)
    `uvm_object_utils_end
    rand int hdr_type;
    function new(string n="header"); super.new(n); endfunction
endclass
 
class complex_txn extends uvm_sequence_item;
    `uvm_object_utils_begin(complex_txn)
        // ❌ Shallow copy — both original and copy share the SAME header object!
        `uvm_field_object(hdr, UVM_DEFAULT)
        `uvm_field_int(payload, UVM_DEFAULT)
    `uvm_object_utils_end
    header hdr;
    rand int payload;
    function new(string n="complex_txn"); super.new(n); endfunction
endclass
 
// ── Demonstration of the shallow copy problem ────────────────────────
complex_txn orig = complex_txn::type_id::create("orig");
complex_txn copy;
orig.hdr = header::type_id::create("hdr");
orig.hdr.hdr_type = 1;
$cast(copy, orig.clone());  // clone calls copy(), which calls do_copy()
 
copy.hdr.hdr_type = 99;     // modifying copy's header...
// $display(orig.hdr.hdr_type) → prints 99, not 1!
// BECAUSE: `uvm_field_object does shallow copy — hdr pointer is shared!
 
// ✓ FIX: override do_copy() for deep copy of object fields ─────────────
function void do_copy(uvm_object rhs);
    complex_txn rhs_cast;
    super.do_copy(rhs);                     // copies scalar fields via field macros
    $cast(rhs_cast, rhs);
    // Manually deep-copy the object handle
    hdr = header::type_id::create("hdr");
    hdr.copy(rhs_cast.hdr);               // new object with same field values
endfunction

§10 — Bugs and Debugging

Bug 1 — UVM_NOCOMPARE Silently Making Scoreboard Always Pass

SystemVerilog — UVM_NOCOMPARE on data field: silent scoreboard bypass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ WRONG — UVM_NOCOMPARE applied to critical data field ─────────────
class apb_txn extends uvm_sequence_item;
    `uvm_object_utils_begin(apb_txn)
        `uvm_field_int(addr, UVM_DEFAULT)
        `uvm_field_int(data, UVM_NOCOMPARE)  // ← data NOT compared!
        `uvm_field_int(write, UVM_DEFAULT)
    `uvm_object_utils_end
    rand logic [31:0] addr;
    rand logic [31:0] data;
    rand logic        write;
endclass
 
// In scoreboard:
// txn_actual.compare(txn_expected) → returns 1 even if data differs!
// → `uvm_error never fires → CI reports PASS → DUT corruption undetected
 
// ✓ CORRECT — use UVM_DEFAULT for fields that must be compared ────────
`uvm_field_int(data, UVM_DEFAULT)  // ← compare enabled
 
// If you genuinely need to exclude from comparison, document it clearly:
// `uvm_field_int(timestamp, UVM_DEFAULT | UVM_NOCOMPARE)  // timestamp: DUT-generated, not predicted
// And add an assertion in check_phase to verify the field separately if needed.

Bug 2 — Missing begin/end Wrapper — Methods Not Generated

SystemVerilog — macro outside begin/end vs correct placement
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ WRONG — field macro outside the begin/end block ─────────────────
class bad_txn extends uvm_sequence_item;
    `uvm_object_utils(bad_txn)         // ← compact form, no begin/end
    `uvm_field_int(addr, UVM_DEFAULT)  // ← OUTSIDE the wrapper — ignored!
 
    rand logic [31:0] addr;
endclass
 
// bad_txn t;
// t.print() → shows "bad_txn" with NO fields listed
// t.compare(other) → always returns 1 (no fields registered for compare)
// t.clone() → creates empty object (no fields copied)
 
// ✓ CORRECT — field macros inside begin/end ───────────────────────────
class good_txn extends uvm_sequence_item;
    `uvm_object_utils_begin(good_txn)     // ← _begin form required for field macros
        `uvm_field_int(addr, UVM_DEFAULT)   // ← INSIDE: generates code for all 6 methods
    `uvm_object_utils_end                   // ← closes the block
 
    rand logic [31:0] addr;
    function new(string n="good_txn"); super.new(n); endfunction
endclass
 
// good_txn t;
// t.addr = 32'h4000;
// t.print() → shows addr = 'h4000  ← field visible
// t.compare(other) → actually compares addr field

Bug 3 — Performance Kill: Field Macros on High-Frequency Transactions

SystemVerilog — hand-written convert2string for performance
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Auto-generated (field macro): ~6 function calls per field ────────
class slow_txn extends uvm_sequence_item;
    `uvm_object_utils_begin(slow_txn)
        `uvm_field_int(addr, UVM_DEFAULT)
        `uvm_field_int(data, UVM_DEFAULT)
    `uvm_object_utils_end
    rand logic [31:0] addr, data;
    // convert2string() → uses generic recorder → slow for high-frequency use
endclass
 
// ── Hand-written: one $sformatf call ─────────────────────────────────
class fast_txn extends uvm_sequence_item;
    `uvm_object_utils(fast_txn)   // compact — no field macros
    rand logic [31:0] addr, data;
 
    function string convert2string();
        return $sformatf("addr=0x%0h data=0x%0h", addr, data);
    endfunction
 
    function void do_copy(uvm_object rhs);
        fast_txn rhs_t; super.do_copy(rhs);
        $cast(rhs_t, rhs);
        addr = rhs_t.addr;
        data = rhs_t.data;
    endfunction
 
    function bit do_compare(uvm_object rhs, uvm_comparer comparer);
        fast_txn rhs_t;
        if (!$cast(rhs_t, rhs)) return 0;
        return super.do_compare(rhs, comparer) &&
               (addr === rhs_t.addr) &&
               (data === rhs_t.data);
    endfunction
endclass
 
// Rule of thumb:
// ≤ 8 scalar fields + low transaction rate → use field macros (convenience wins)
// > 8 fields OR high transaction rate (> 10K/ms) → hand-write do_copy/do_compare/convert2string

§11 — Ready-to-Run: Field Macros vs Manual Implementation

Side-by-side: the same APB transaction implemented with field macros and with hand-written do_copy() / do_compare() / convert2string(). Run both and compare the print() output and compare() behaviour. Ready to Run — Questa / VCS / Xcelium

field_macro_demo.sv — macro vs manual, compile and run
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// field_macro_demo.sv
// Compile: vlog -sv field_macro_demo.sv
// Run:     vsim -c work.tb_fm_top +UVM_TESTNAME=fm_test -do "run -all; quit"
 
`include "uvm_macros.svh"
import uvm_pkg::*;
 
// ── VERSION A: field macros ────────────────────────────────────────────
class apb_macro_txn extends uvm_sequence_item;
    `uvm_object_utils_begin(apb_macro_txn)
        `uvm_field_int(addr,  UVM_DEFAULT)
        `uvm_field_int(data,  UVM_DEFAULT)
        `uvm_field_int(write, UVM_DEFAULT)
    `uvm_object_utils_end
 
    rand logic [31:0] addr;
    rand logic [31:0] data;
    rand logic        write;
    function new(string n="apb_macro_txn"); super.new(n); endfunction
endclass
 
// ── VERSION B: manual implementation ──────────────────────────────────
class apb_manual_txn extends uvm_sequence_item;
    `uvm_object_utils(apb_manual_txn)
 
    rand logic [31:0] addr;
    rand logic [31:0] data;
    rand logic        write;
 
    function new(string n="apb_manual_txn"); super.new(n); endfunction
 
    function string convert2string();
        return $sformatf("[APB] addr=0x%08h data=0x%08h write=%0b",
            addr, data, write);
    endfunction
 
    function void do_copy(uvm_object rhs);
        apb_manual_txn r;
        super.do_copy(rhs);
        $cast(r, rhs);
        addr  = r.addr;
        data  = r.data;
        write = r.write;
    endfunction
 
    function bit do_compare(uvm_object rhs, uvm_comparer comparer);
        apb_manual_txn r;
        if (!$cast(r, rhs)) return 0;
        if (addr  !== r.addr)  comparer.compare_field("addr",  addr,  r.addr,  32);
        if (data  !== r.data)  comparer.compare_field("data",  data,  r.data,  32);
        if (write !== r.write) comparer.compare_field("write", write, r.write, 1);
        return (addr === r.addr) && (data === r.data) && (write === r.write);
    endfunction
endclass
 
// ── Test: exercises both versions ─────────────────────────────────────
class fm_test extends uvm_test;
    `uvm_component_utils(fm_test)
    function new(string n, uvm_component p); super.new(n,p); endfunction
 
    task run_phase(uvm_phase phase);
        apb_macro_txn  a1, a2;
        apb_manual_txn m1, m2;
        phase.raise_objection(this);
 
        // ── Macro version ─────────────────────────────────────────────
        a1 = apb_macro_txn::type_id::create("a1");
        a1.addr = 32'h4000; a1.data = 32'hA5A5; a1.write = 1;
        `uvm_info("TEST", "=== Field Macro Version ===", UVM_LOW)
        `uvm_info("TEST", $sformatf("convert2string: %s", a1.convert2string()), UVM_LOW)
        a1.print();
 
        $cast(a2, a1.clone());
        a2.data = 32'hBEEF;  // a1.data unchanged (clone = deep copy of scalars)
        `uvm_info("TEST", $sformatf("compare (should differ): %0d",
            a1.compare(a2)), UVM_LOW)  // expect 0
 
        // ── Manual version ────────────────────────────────────────────
        m1 = apb_manual_txn::type_id::create("m1");
        m1.addr = 32'h4000; m1.data = 32'hA5A5; m1.write = 1;
        `uvm_info("TEST", "=== Manual Version ===", UVM_LOW)
        `uvm_info("TEST", $sformatf("convert2string: %s", m1.convert2string()), UVM_LOW)
 
        $cast(m2, m1.clone());
        m2.data = 32'hBEEF;
        `uvm_info("TEST", $sformatf("compare (should differ): %0d",
            m1.compare(m2)), UVM_LOW)  // expect 0
 
        phase.drop_objection(this);
    endtask
endclass
 
module tb_fm_top;
    initial run_test();
endmodule
 
// Expected output:
// TEST: === Field Macro Version ===
// TEST: convert2string: addr=4000 data=a5a5 write=1   (macro format)
// Name         Type    Size  Value
// apb_macro_txn  object  -
//   addr         integral 32   'h4000
//   data         integral 32   'ha5a5
//   write        integral 1    'h1
// TEST: compare (should differ): 0  ← data=0xa5a5 vs data=0xbeef
//
// TEST: === Manual Version ===
// TEST: convert2string: [APB] addr=0x00004000 data=0xa5a5a5a5 write=1
// TEST: compare (should differ): 0  ← same result, more control

The Bug Field Macros Are Best At Hiding

A field excluded from comparison does not announce itself. The scoreboard keeps reporting matches, the regression stays green, and the DUT bug the flag was masking ships.

SystemVerilog — nocompare_trap.sv (ready-to-run)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ================================================================
// nocompare_trap.sv — a mismatch that compare() reports as a match.
//   Questa : vlog -sv -L uvm_1_2 nocompare_trap.sv
//            vsim -c -L uvm_1_2 nocompare_trap_top -do "run -all; quit"
//   VCS    : vcs -sverilog -ntb_opts uvm-1.2 nocompare_trap.sv && ./simv
//   Xcelium: xrun -sv -uvm nocompare_trap.sv
// ================================================================
`include "uvm_macros.svh"
import uvm_pkg::*;
 
class apb_txn extends uvm_sequence_item;
    `uvm_object_utils_begin(apb_txn)
        `uvm_field_int(addr,   UVM_DEFAULT)
        `uvm_field_int(data,   UVM_DEFAULT)
        // Added during bring-up so a noisy debug field stopped failing runs.
        // It also silently removed the DUT's response code from every check.
        `uvm_field_int(resp,   UVM_DEFAULT | UVM_NOCOMPARE)
        // Never registered at all — the most invisible exclusion there is.
    `uvm_object_utils_end
 
    rand bit [31:0] addr, data;
    bit [1:0]       resp;
    bit             parity_err;      // ← not in any macro: not copied, not compared
 
    function new(string n="apb_txn"); super.new(n); endfunction
endclass
 
class nocompare_trap_test extends uvm_test;
    `uvm_component_utils(nocompare_trap_test)
    function new(string n, uvm_component p); super.new(n,p); endfunction
 
    task run_phase(uvm_phase phase);
        apb_txn expected, actual;
        phase.raise_objection(this);
 
        expected = apb_txn::type_id::create("expected");
        actual   = apb_txn::type_id::create("actual");
 
        expected.addr = 32'h1000; expected.data = 32'hDEAD_BEEF;
        expected.resp = 2'b00;    expected.parity_err = 1'b0;   // OKAY, clean
 
        actual.addr   = 32'h1000; actual.data   = 32'hDEAD_BEEF;
        actual.resp   = 2'b11;    actual.parity_err = 1'b1;     // ERROR + parity!
 
        // Two genuinely different transactions.
        if (expected.compare(actual))
            `uvm_info("TRAP", "compare() returned MATCH — resp and parity_err ignored",
                      UVM_LOW)
        else
            `uvm_error("TRAP", "compare() returned MISMATCH")
 
        // copy() has the same blind spot, from the same registration.
        begin
            apb_txn c = apb_txn::type_id::create("c");
            c.copy(actual);
            `uvm_info("TRAP", $sformatf(
                "after copy: resp=%0d (copied) parity_err=%0d (LOST — was %0d)",
                c.resp, c.parity_err, actual.parity_err), UVM_LOW)
        end
 
        phase.drop_objection(this);
    endtask
endclass
 
module nocompare_trap_top;
    initial run_test("nocompare_trap_test");
endmodule
 
// Expected:
//   UVM_INFO [TRAP] compare() returned MATCH — resp and parity_err ignored
//   UVM_INFO [TRAP] after copy: resp=3 (copied) parity_err=0 (LOST — was 1)

Two different exclusions, two different consequences. resp carries UVM_NOCOMPARE, so it is copied but never checked — a DUT returning an error response compares clean forever. parity_err was never registered at all, so it is neither compared nor copied: the scoreboard's stored copy silently loses it, and any later check against that copy sees a zero that was never observed.

The second is worse precisely because it is not a flag anybody chose. It is a field somebody added to the class and forgot to add to the macro block, and nothing in the language or the library connects the two.

1

A scoreboard reported 100% match while the DUT returned error responses on 4% of transfers

FIELD-MACRO-NOCOMPARE-MASK
Symptom

An APB scoreboard had matched cleanly for months. A firmware team then reported that roughly 4% of their reads came back with error responses that the RTL should never have generated.

Re-running the exact failing sequence in the UVM environment produced a clean pass. The environment and the firmware disagreed about the same traffic.

Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`uvm_object_utils_begin(apb_txn)
    `uvm_field_int(addr, UVM_DEFAULT)
    `uvm_field_int(data, UVM_DEFAULT)
    `uvm_field_int(resp, UVM_DEFAULT | UVM_NOCOMPARE)   // ← added during bring-up
`uvm_object_utils_end

Git blame put the UVM_NOCOMPARE at eleven months earlier, with the message "stop resp mismatches failing the smoke test". At the time the monitor was not yet sampling resp correctly, so every comparison failed on it. Excluding the field unblocked the suite, the monitor was fixed a week later, and the flag was never revisited.

Diagnostic Evidence

The transactions were clearly different when printed, which was the moment the investigation turned:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
expected: addr=0x00001000 data=0xdeadbeef resp=0
actual  : addr=0x00001000 data=0xdeadbeef resp=3
UVM_INFO ... [SB] MATCH

print() showed the field because UVM_PRINT was still set — only compare() was blind to it. That asymmetry is the signature of a flag-level exclusion rather than a monitor or scoreboard bug, and it is visible in one log line if you print both objects on a passing comparison.

Root Cause

UVM_NOCOMPARE removes the field from the generated do_compare entirely. There is no report, no verbosity level that reveals it, and no difference between "these transactions matched" and "these transactions matched on the fields I was allowed to look at."

The deeper cause is that the exclusion was a debugging workaround that outlived its reason. It was correct for one week and wrong for eleven months, and nothing in the code recorded which week it was for.

Fix

Remove the flag and let the field be checked:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`uvm_field_int(resp, UVM_DEFAULT)

Then close the class of bug rather than the instance. Three guards are worth having, in increasing order of value.

Comment every exclusion with a reason and an owner — // NOCOMPARE: DUT returns X during reset, see JIRA-1234 — so the next reader can tell a deliberate policy from an expired workaround. Add a CI grep for UVM_NO flags that flags any occurrence without an adjacent justification comment.

And most usefully, write a negative self-test for the transaction itself: build two objects that differ in exactly one field, assert compare() returns 0, and repeat per field. It is a dozen lines, it runs in milliseconds, and it fails the moment a field stops participating — whether by an added flag or by someone forgetting to register a new field at all. That last case is the one no amount of flag review catches.

2

Every stored transaction in the scoreboard held the same payload — the most recent one

FIELD-OBJECT-SHALLOW-COPY
Symptom

An AXI scoreboard stored each observed write transaction and compared it against the read-back later. With one transaction in flight it was perfect. Above about four outstanding, expected-value mismatches appeared, and the expected payloads were always wrong in the same direction — they matched the most recently observed transaction rather than the one under check.

The mismatch rate rose with outstanding depth, which sent the investigation into the reordering logic for two days.

Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class axi_payload extends uvm_object;
    `uvm_object_utils_begin(axi_payload)
        `uvm_field_array_int(bytes, UVM_DEFAULT)
    `uvm_object_utils_end
    byte bytes[];
endclass
 
class axi_txn extends uvm_sequence_item;
    `uvm_object_utils_begin(axi_txn)
        `uvm_field_int(addr,      UVM_DEFAULT)
        `uvm_field_object(payload, UVM_DEFAULT)   // ← default policy is SHALLOW
    `uvm_object_utils_end
    rand bit [31:0] addr;
    axi_payload     payload;
endclass
 
// In the monitor: one payload object, reused across transactions
function void sample();
    txn = axi_txn::type_id::create("txn");
    txn.addr    = observed_addr;
    txn.payload = m_payload;      // same handle every time
    ap.write(txn);
endfunction
Diagnostic Evidence

Printing the stored transactions at the point of mismatch showed distinct addresses and identical payloads:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
stored[0]: addr=0x1000 payload=@331 bytes={DE AD BE EF}
stored[1]: addr=0x2000 payload=@331 bytes={DE AD BE EF}
stored[2]: addr=0x3000 payload=@331 bytes={DE AD BE EF}

The @331 is the decisive detail. Three "different" stored transactions referenced one object. The addresses differed because addr is an integral field that was genuinely copied; the payload did not, because it is a handle.

Root Cause

uvm_field_object's default copy policy is shallow: the generated do_copy assigns the handle, not the object. Every stored transaction therefore pointed at the monitor's single reusable payload object, and each new sample overwrote the contents that all previously-stored transactions were sharing.

Two decisions combined to produce it, and neither is wrong alone. The monitor reusing one payload object is a normal allocation optimisation. The UVM_DEFAULT flag on an object field is the ordinary thing to write. Together they mean the scoreboard's history is one object wearing many addresses.

The depth-dependent symptom follows directly: with one transaction in flight, the store and the check happen before the next sample overwrites anything, so nothing is visibly wrong.

Fix

Make the copy deep, at whichever end you control. In the transaction, request the deep-copy policy on the object field:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`uvm_field_object(payload, UVM_DEFAULT | UVM_DEEP)

Or, more robustly, have the producer stop sharing the handle at all:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
txn.payload = axi_payload::type_id::create("payload");
txn.payload.copy(m_payload);        // or: $cast(txn.payload, m_payload.clone())

The second is preferable because it does not depend on every future consumer remembering a flag, and because a scoreboard should generally clone() anything it intends to keep — storing a handle to an object somebody else still owns is the general form of this bug, independent of field macros.

The guard that catches it early is a uniqueness assertion in the scoreboard: when storing, check that the incoming sub-object handle is not already present in the store. It is a few lines, it fires on the first duplicate rather than at the first depth-dependent mismatch, and it names the sharing directly instead of presenting as a reordering problem. do_copy and do_compare develops the same failure from the manual-implementation side, and Object Cloning covers clone() semantics.

Questions Engineers Actually Ask

They are the fastest way, not automatically the right one.

For a transaction that is a flat bag of integral fields with no conditional semantics, they are excellent: one line per field replaces thirty lines of hand-written do_copy and do_compare, and there is nothing to get wrong.

They stop fitting as soon as the semantics are conditional — comparing a sub-range, ignoring a field when a mode bit is set, treating an X as a match, or comparing floating-point values with a tolerance. None of that is expressible in a flag, and the usual failure is to contort the transaction to suit the macros rather than write the eight lines of do_compare that say what you mean.

A reasonable default: macros for VIP transactions that stay simple, manual do_* the moment a comparison needs an if.

Nothing visible, which is what makes it dangerous.

The field is not copied, not compared, not printed, not packed. It compiles, it simulates, and every scoreboard check that relies on it silently passes. A stored copy of the transaction will hold the field's default value rather than the observed one, so a later comparison against that copy is checking a value that was never sampled.

There is no diagnostic for this — the macros cannot know about a field they were not told about. The only reliable guard is a per-field negative self-test, as described in DebugLab 1: two objects differing in exactly one field must fail compare().

Where This Is Specified

  • IEEE 1800.2-2020 (UVM) — field automation macros. The uvm_field_int, uvm_field_enum, uvm_field_object, uvm_field_array_*, uvm_field_queue_* and uvm_field_aa_* families, and the uvm_object_utils_begin / uvm_object_utils_end block they must appear inside.
  • IEEE 1800.2-2020 — field flag definitions. UVM_COPY / UVM_NOCOPY, UVM_COMPARE / UVM_NOCOMPARE, UVM_PRINT / UVM_NOPRINT, UVM_RECORD / UVM_NORECORD, UVM_PACK / UVM_NOPACK as individually-numbered bit positions, and the composite UVM_DEFAULT.
  • IEEE 1800.2-2020 — uvm_object automation. How the generated do_copy, do_compare, do_print, do_pack, do_unpack and do_record implementations consume the registered field list.
  • IEEE 1800.2-2020 — uvm_comparer. compare_field and the accumulation of miscompares that a macro-generated comparison performs on your behalf.
  • IEEE 1800.2-2020 — object copy policies. The shallow default for uvm_field_object and the deep-copy policy flag.
  • IEEE 1800-2023 §11.4.7 — Logical operators. Short-circuit evaluation of &&, and why manual comparison accumulators use &=.

§12 — Interview Questions

Beginner Level

Intermediate Level

Senior / Architect Level

§13 — Best Practices

RulePracticeWhy It Matters
BP-1Always use uvm_object_utils_begin/end` when adding field macros — never uvm_object_utils` (compact form)Field macros outside the begin/end block are silently ignored — print/compare/copy all appear to work but process no fields
BP-2Use UVM_DEFAULT for all primary protocol fields (addr, data, write, etc.) that must be comparedUVM_NOCOMPARE on a primary field silently disables scoreboard checking — one of the hardest bugs to find
BP-3Explicitly use UVM_DEFAULT | UVM_NOCOMPARE for fields that should not be compared — and add a comment explaining whyDocuments intent; prevents future engineers from "fixing" the flag and breaking intentional exclusions
BP-4Never rely on ``uvm_field_object` for deep copy — always override do_copy()Shallow copy via field macros creates shared object handles; modifying one modifies all, causing silent data corruption
BP-5For transactions with > 8 fields or used in tight loops, hand-write convert2string()The macro-generated version goes through the recorder machinery; a direct $sformatf is 2–5× faster for high-frequency monitoring
BP-6Declare field macros in the same order as physical wire layout when using pack/unpackPack order follows declaration order; a different order from the wire protocol requires a manual do_pack()
BP-7Test your compare() by creating two objects with known differences and calling compare() — verify it returns 0A comparison that always returns 1 is worse than no comparison — it gives false confidence; always verify the mechanism works
BP-8Prefer hand-written do_compare() for production scoreboard transactions in shared VIPsExplicit comparison logic is auditable; field macros hide the comparison logic behind flags that require understanding of macro internals to audit

§14 — Summary

AspectField MacrosManual Implementation
Setup effortOne line per field — minimalWrite do_copy, do_compare, convert2string — 20–40 lines
Object handle fieldsShallow copy — dangerous for nested objectsFull control — explicit deep copy in do_copy()
Comparison logicPer-field flags only — no conditional comparisonsAny logic — compare field A only when field B has a specific value
Performance (high freq)Overhead from recorder and format machineryDirect $sformatf and === comparison — fastest possible
Pack/unpack orderFollows declaration order — may not match wire layoutAny bit layout expressible in do_pack()
AuditabilityRequires understanding flag combinations to auditFully explicit — every field and condition visible
Best used forQuick prototyping; transactions with ≤ 8 scalar fields at low transaction rateProduction VIPs; high-frequency monitors; complex nested objects

Continue learning