Part 5 · Sequences · Intermediate

Field Macros: Print, Compare, Copy Automation

uvm_field_* macros inside uvm_object_utils — structured debug, scoreboard compare, and deep copy without hand-written functions.

Why field macros exist

Every verification engineer spends hours printing transactions and comparing expected vs actual. Without automation, each item class needs hand-written print(), compare(), and copy() functions that drift out of sync whenever someone adds a field. UVM field macros register members once inside `uvm_object_utils_begin/end and the library generates consistent implementations for all registered fields.

The investment pays off immediately in debug: a single req.print() at UVM_MEDIUM verbosity shows every payload field with consistent formatting. In scoreboards, exp.compare(act) returns 0 on mismatch and prints the first differing field — no custom compare boilerplate per protocol.

Field macros also enable recording, pack/unpack for replay, and factory integration. Skipping registration on a field means it silently disappears from compare and copy — a common scoreboard bug source.

diagram
[ITEM] field macro benefits

  ONE registration   print | compare | copy | record | pack/unpack
  Add new field?       Register with `uvm_field_* — all utilities update
  Skip registration?   Field invisible to compare/copy — silent scoreboard bugs

Complete item with field macros

Here is a realistic AXI-style item with randomized fields, inline constraints, and full field registration:

systemverilog
class axi_item extends uvm_sequence_item;
  rand bit [31:0] addr;
  rand bit [31:0] data;
  rand bit        write;
  rand bit [2:0]  burst_len;
  rand bit [3:0]  id;

  constraint legal_addr {
    addr[1:0] == 2'b00;
    addr inside {[32'h0000:32'hFFFF]};
  }

  `uvm_object_utils_begin(axi_item)
    `uvm_field_int(addr,      UVM_ALL_ON)
    `uvm_field_int(data,      UVM_ALL_ON)
    `uvm_field_int(write,     UVM_ALL_ON)
    `uvm_field_int(burst_len, UVM_ALL_ON)
    `uvm_field_int(id,        UVM_ALL_ON)
  `uvm_object_utils_end

  function new(string name = "axi_item");
    super.new(name);
  endfunction
endclass
  • `uvm_object_utils_begin/end` wraps all field registrations for this class.

  • `uvm_field_int(field, flags)` for integral members — most common macro.

  • UVM_ALL_ON enables print, compare, copy, and record — good default for payload.

  • Constraints live outside macros — they control randomize(), not automation.


Print and sprint for debug

During bring-up, printing items is faster than waveform hunting. The UVM print format is consistent across all registered classes — grep-friendly in regression logs.

systemverilog
axi_item req = axi_item::type_id::create("req");
assert(req.randomize() with { write == 1; addr == 32'h1000; });

req.print();    // prints at current verbosity to log
string s = req.sprint();
`uvm_info("TXN", s, UVM_MEDIUM)

// Typical output:
// axi_item@1234
//   addr      'h1000
//   data      'h7a3f2b1c
//   write     'h1
//   burst_len 'h0
//   id        'h5
diagram
[UVM] print in the stimulus path

  [SEQ] body():
    start_item(req);
    req.randomize();
    req.print();           ← verify BEFORE finish_item
    finish_item(req);

  [DRV] run_phase:
    get_next_item(req);
    req.print();           ← same object, same format
    drive(req);
    item_done();

Verbosity control

  • print() respects component verbosity — use UVM_HIGH for per-beat, UVM_MEDIUM for summaries.

  • sprint() returns string — embed in custom messages or write to file.

  • Set type_print hook for custom formatting on specific classes (advanced).


Compare for scoreboards

Scoreboards call compare on expected vs actual items. The macro-generated compare walks registered fields in order and prints the first mismatch — essential for self-checking at scale.

systemverilog
function void compare_pair(axi_item exp, axi_item act);
  if (!act.compare(exp)) begin
    mismatch_count++;
    `uvm_error("SCB", $sformatf("mismatch exp=%s act=%s",
                                  exp.sprint(), act.sprint()))
  end else begin
    match_count++;
  end
endfunction
diagram
[ITEM] compare walkthrough — id=5 addr mismatch

  exp: addr=0x1000 data=0xAA  id=5
  act: addr=0x1004 data=0xAA  id=5

  act.compare(exp):
    addr: 'h1000 vs 'h1004   MISMATCH reported
    (data, write, id not checked — first diff stops)

  Fix: register ALL payload fields — unregistered addr alias won't compare

Common field flag choices

  • UVM_ALL_ON — print, compare, copy, record — default for payload fields.

  • UVM_NOCOMPARE — printed but skipped in compare (e.g., cycle timestamps).

  • UVM_NOPRINT — compared and copied but hidden from print (large arrays).

  • UVM_NOPACK — skip in pack/unpack when field not needed for replay.

  • UVM_DEC — display as decimal instead of hex for small counters.


Copy and clone

Monitors reuse transaction objects; scoreboards queue snapshots. copy() duplicates all registered fields into an existing object; clone() is a convenience wrapper. Never queue the live handle.

systemverilog
axi_item exp = axi_item::type_id::create("exp");
exp.copy(req);              // deep copy registered fields req → exp

axi_item snap = axi_item::type_id::create("snap");
req.clone(snap);            // equivalent: snap.copy(req) after create

// Scoreboard pattern
function void write_exp(axi_item t);
  axi_item snap = axi_item::type_id::create("snap");
  snap.copy(t);
  exp_queue.push_back(snap);
endfunction
diagram
[ITEM] copy vs handle queue — silent bug

  WRONG:  exp_queue.push_back(req);     ← monitor overwrites req next cycle
  RIGHT:  snap = create(); snap.copy(req); exp_queue.push_back(snap);

  All queued entries must be independent snapshots
  • copy/clone duplicate all UVM_FIELD-registered members only.

  • Unregistered fields are NOT copied — register everything that matters.

  • For nested objects, use uvm_field_object with UVM_ALL_ON.


APB item field macro reference

Complete APB item showing practical flag choices for a mix of payload and debug-only fields:

systemverilog
class apb_item extends uvm_sequence_item;
  rand bit [31:0] addr, data;
  rand bit        write;
  bit [31:0]      rdata;
  time            sample_time;   // monitor-only — don't compare

  `uvm_object_utils_begin(apb_item)
    `uvm_field_int(addr,        UVM_ALL_ON)
    `uvm_field_int(data,        UVM_ALL_ON)
    `uvm_field_int(write,       UVM_ALL_ON)
    `uvm_field_int(rdata,       UVM_ALL_ON)
    `uvm_field_int(sample_time,  UVM_ALL_ON | UVM_NOCOMPARE)
  `uvm_object_utils_end
endclass

Key takeaways

  • Register every payload field with uvm_field_* — unregistered fields vanish from compare/copy.

  • UVM_ALL_ON is the right default; use NOCOMPARE/NOPRINT flags surgically.

  • print/sprint for debug; compare for scoreboards; copy/clone before queueing.

Common pitfalls

  • Adding a field without uvm_field_* — compare silently passes on mismatched new field.

  • UVM_NOCOMPARE on payload fields — hides real DUT bugs in scoreboard.

  • Hand-written compare that diverges from field macros — maintenance nightmare.

  • copy without type_id::create on destination — factory overrides skipped.