In a random verification environment, data objects are generated and passed between components all the time. Debug becomes much easier when you can display the full contents of an object in one call, and that is what uvm_printer and the print() method give you.

9 min read | Intermediate Level

What You'll Learn

  • How print() and uvm_printer replace custom display functions
  • The table, tree and line printer styles and when to use each
  • Add fields to printing with field macros or a do_print() method
  • Customize output with printer knobs without side effects

Old-school style

Traditionally, object contents are printed to a log file or the screen with $display statements inside custom print functions.


class Packet;
	bit [31:0]  addr;
	int         count_id;
	bit [3:0]   length;
	...
	function display ();
		$display ("addr=0x%0h count_id=0x%0h length=0x%0h", addr, count_id, length);
	endfunction
endclass

module tb;
	initial begin
		Packet p1 = new();
		p1.display();
	end
endmodule

SystemVerilog has no built-in way to print a class, so testbench developers had to write a custom print function, like display() above, for every class. Each one had its own format, and each had to be updated by hand whenever a field was added.

How its done in UVM

UVM removes the need for custom print functions with its uvm_printer class, which knows how to format class properties. The print() method is defined in uvm_object, so every class derived from it, including sequence items and components, can call print().

An object does not contain its own printer. Instead, print() takes a printer as an argument and falls back to the global uvm_default_printer if none is given.


virtual class uvm_object extends uvm_void;
	...
	function void print (uvm_printer printer=null);
		if (printer==null)
    		printer = uvm_default_printer;
    	...
	endfunction
	...
endclass

The main advantage is the `uvm_field_* macros. They register class members with UVM so that print(), copy(), compare() and similar methods handle them automatically, with no hand-written print code.

Syntax


obj.print ();                              // Print with uvm_default_printer (a table printer)
obj.print (PRINTER);                       // Print with a specific printer instance
str = obj.sprint (PRINTER);                // Return the same text as a string instead

virtual function void do_print (uvm_printer printer);   // Hook for fields that are not
  super.do_print (printer);                             // registered with field macros
  printer.print_int ("NAME", VALUE, $bits(VALUE));
endfunction

Styles

UVM provides three printers, each with its own output format. The examples below print the same nested object.

Table printer - columns for name, type, size and value. Easiest to scan for small objects.

  ---------------------------------------------------
  Name        Type            Size        Value
  ---------------------------------------------------
  c1          container       -           @1013
  d1          mydata          -           @1022
  v1          integral        32          'hcb8f1c97
  e1          enum            32          THREE
  str         string          2           hi
  value       integral        12          'h2d
  ---------------------------------------------------

Tree printer - one field per line, with indentation and braces showing the nesting.

  c1: (container@1013) {
    d1: (mydata@1022) {
         v1: 'hcb8f1c97
         e1: THREE
         str: hi
    }  
    value: 'h2d
  }

Line printer - the tree format collapsed onto a single line. Useful for one-line-per-transaction logs that are searched with tools like grep.

  c1: (container@1013) { d1: (mydata@1022) { v1: 'hcb8f1c97 e1: THREE str: hi } value: 'h2d }

Usage

By default, UVM uses a table printer for every print() call, through the global handle uvm_default_printer. For convenience, a global instance of each printer type is also available everywhere in your testbench.

uvm_printer hierarchy

Note that uvm_line_printer is derived from uvm_tree_printer. It uses the same layout, with the newlines and indentation removed.


uvm_table_printer   uvm_default_table_printer = new();
uvm_tree_printer    uvm_default_tree_printer = new();
uvm_line_printer    uvm_default_line_printer = new();

uvm_printer uvm_default_printer = uvm_default_table_printer;

Pass any of the three printer instances to print() to print in that style.


class my_data extends uvm_sequence_item;
	bit [2:0] mode;
endclass

my_data obj0 = new ();                       // Object must exist before print()
obj0.print ();                               // Calls table printer by default
obj0.print (uvm_default_line_printer);       // Calls line printer

In the newer IEEE 1800.2 versions of UVM, the default printer is accessed with uvm_printer::get_default(), and printer settings are changed with setter methods instead of knobs. The concepts in this article are the same.

Quick Check

Q: You call obj.print() with no argument and have not changed any defaults. Which printer style do you get?

Show Answer

The table printer. print() uses uvm_default_printer, which points to uvm_default_table_printer unless you reassign it.

Conditions

print() can always be called, but it shows only the object's name, type and ID unless UVM knows which fields to print. There are two ways to tell it.

1. Register fields with field macros. Place each member that should be printed between `uvm_object_utils_begin and `uvm_object_utils_end.


class my_data extends uvm_sequence_item;
   rand bit [7:0]   data;
   rand bit [7:0]   addr;

   constraint c_addr { addr > 0; addr < 8;}

   `uvm_object_utils_begin (my_data)
      `uvm_field_int (data, UVM_ALL_ON)
      `uvm_field_int (addr, UVM_ALL_ON)
   `uvm_object_utils_end

   function new (string name = "my_data");
      super.new (name);
   endfunction
endclass  

Members registered this way are automatically included in the common uvm_object methods, such as print(), copy() and compare().

2. Define a do_print() method. If you do not use the utils_begin/end macros for a member, print it yourself in do_print(), using methods of the uvm_printer class such as print_int(), print_string() and print_object().


class derivative extends my_data;
   `uvm_object_utils (derivative)

   rand bit [2:0] mode;

   function new (string name="derivative");
      super.new (name);
   endfunction

   virtual function void do_print (uvm_printer printer);
      printer.print_int ("mode", mode, $bits(mode));
      `uvm_info ("DVR", "do_print called", UVM_MEDIUM)
   endfunction
endclass 

Both approaches can also be combined. Fields registered with macros are printed first, and whatever do_print() adds is appended after them.


class derivative extends my_data;

   rand bit [2:0] mode;
   rand bit [1:0] format;
   
   `uvm_object_utils_begin (derivative)
   		`uvm_field_int (format, UVM_ALL_ON)
   `uvm_object_utils_end

   function new (string name="derivative");
      super.new (name);
   endfunction

   virtual function void do_print (uvm_printer printer);
      printer.print_int ("mode", mode, $bits(mode));
      `uvm_info ("DVR", "do_print called", UVM_MEDIUM)
   endfunction
endclass 

Many teams avoid field macros in performance-critical classes and use only do_print(), because the macros add simulation overhead. See UVM Field Macros for the trade-offs.

Where is print() Used?

Scoreboards commonly call sprint() on both the expected and actual transaction when a mismatch is found, and include both in the `uvm_error message. The failing log then shows every field of both objects side by side, without any extra debug run.

Knobs

Every printer contains an instance of uvm_printer_knobs, available as printer.knobs. Its fields control what print() shows and how.


uvm_default_printer.knobs.size = 0;                   // Will not display the size column
uvm_default_printer.knobs.indent = 4;                 // Indents to the right by 4 spaces instead of the default 2
uvm_default_printer.knobs.hex_radix = "0x";           // Replaces the hex radix of 'h with 0x
KnobDefaultEffect
depth-1Levels of nested objects to expand; -1 means all levels
indent2Spaces added per nesting level
full_name01 prints each field with its full path, such as obj0.data
type_name10 hides the Type column
size10 hides the Size column
reference10 hides object IDs such as @2687
hex_radix'hPrefix used for hexadecimal values

These knobs can be set on any printer instance; the example below shows several of them in use.

Example

This example defines a few nested data objects. colors is nested inside format, which is in turn nested inside my_data. A fourth class, derivative, extends my_data.

example nested classes

//---------------------- colors --------------------------
class colors extends uvm_sequence_item;
   rand bit [3:0] color;
   rand bit       enable;

   `uvm_object_utils_begin (colors)
      `uvm_field_int (color, UVM_ALL_ON)
      `uvm_field_int (enable, UVM_ALL_ON)
   `uvm_object_utils_end

endclass

//---------------------- format --------------------------
class format extends uvm_sequence_item;

   rand bit [3:0]    header;
   rand bit [2:0]    footer;
   rand bit          enable;
   rand bit [1:0]    body;
   rand colors       m_color;

   `uvm_object_utils_begin (format)
      `uvm_field_int (header, UVM_ALL_ON)
      `uvm_field_int (footer, UVM_ALL_ON)
      `uvm_field_int (enable, UVM_ALL_ON)
      `uvm_field_int (body, UVM_ALL_ON)
   `uvm_object_utils_end

   function new (string name = "format");
      super.new (name);
      m_color = colors::type_id::create ("m_color");
   endfunction

   virtual function void do_print (uvm_printer printer);
      printer.print_object ("m_color", m_color);
   endfunction
endclass


//---------------------- my_data --------------------------
class my_data extends uvm_sequence_item;
   rand format      m_format0;
   rand bit [7:0]   data;
   rand bit [7:0]   addr;

   constraint c_addr { addr > 0; addr < 8;}

   `uvm_object_utils_begin (my_data)
      `uvm_field_int (data, UVM_ALL_ON)
      `uvm_field_int (addr, UVM_ALL_ON)
      `uvm_field_object (m_format0, UVM_ALL_ON)
   `uvm_object_utils_end

   function new (string name = "my_data");
      super.new (name);
      m_format0 = format::type_id::create ("m_format0");
   endfunction
endclass 

//---------------------- derivative --------------------------
class derivative extends my_data;
   `uvm_object_utils (derivative)

   rand bit [2:0] mode;

   function new (string name="derivative");
      super.new (name);
   endfunction

   virtual function void do_print (uvm_printer printer);
      printer.knobs.depth=0;
      printer.print_int ("mode", mode, $bits(mode));
      `uvm_info ("DVR", "do_print called", UVM_MEDIUM)
   endfunction
endclass 

Note the following about the code above:

  • All members of the class colors are added to the `uvm_object_utils_begin/end macros
  • All class objects must be created with new() or type_id::create(), here in each constructor, before they can be printed
  • In the class format, every member except m_color is registered with a macro, while m_color is printed in do_print()
  • The class derivative adds mode through its do_print() method

The test prints the same object with every printer style, then with a custom table printer tprinter whose knobs are changed in cfg_printer().


class base_test extends uvm_test;
   `uvm_component_utils (base_test)

   my_data obj0;
   derivative dv0;
   uvm_table_printer tprinter;

   function new (string name = "base_test", uvm_component parent);
      super.new (name, parent);
   endfunction

   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      obj0 = my_data::type_id::create ("obj0");
      dv0 = derivative::type_id::create ("dv0");
      tprinter = new();
      cfg_printer();
   endfunction

   virtual task run_phase (uvm_phase phase);
      void'(obj0.randomize());
      `uvm_info ("TST", "print() called with no arguments", UVM_MEDIUM)
      obj0.print ();     // uses uvm_default_printer

      `uvm_info ("TST", "Calling print (uvm_default_line_printer)", UVM_MEDIUM)
      obj0.print (uvm_default_line_printer);

      `uvm_info ("TST", "Calling print (uvm_default_tree_printer)", UVM_MEDIUM)
      obj0.print (uvm_default_tree_printer);

      `uvm_info ("TST", "Calling print (uvm_default_table_printer)", UVM_MEDIUM)
      obj0.print (uvm_default_table_printer);
      
      `uvm_info ("TST", "Calling print (tprinter)", UVM_MEDIUM)
      obj0.print (tprinter);
      
      `uvm_info ("TST", "Print derivative (tprinter)", UVM_MEDIUM)
      dv0.print (uvm_default_table_printer);
   endtask

   // Configuration to change display settings for the printer
   virtual function void cfg_printer ();
      tprinter.knobs.full_name = 1;               // Show full name of the variable relative to class instance
      tprinter.knobs.size = 0;                    // Do not show size column
      tprinter.knobs.depth = 1;                   // Only show upto 1 level of the nested hierarchy
      tprinter.knobs.reference = 0;               // Do not print object ID handles
      tprinter.knobs.type_name = 0;               // Do not show Type column
      tprinter.knobs.indent = 4;                  // Indent from the left by 4
      tprinter.knobs.hex_radix = "0x";            // Replace 'h radix by 0x - observe Value field to see this
   endfunction
endclass
Output
----------------------------------------------------------------
CDNS-UVM-1.1d (14.22-s009)
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO ./tb/test_pkg.sv(34) @ 0: uvm_test_top [TST] print() called with no arguments
-----------------------------------
Name          Type      Size  Value
-----------------------------------
obj0          my_data   -     @2687
  data        integral  8     'h5a
  addr        integral  8     'h1
  m_format0   format    -     @2700
    header    integral  4     'hb
    footer    integral  3     'h0
    enable    integral  1     'h1
    body      integral  2     'h1
    m_color   colors    -     @2711
      color   integral  4     'h8
      enable  integral  1     'h0
-----------------------------------
UVM_INFO ./tb/test_pkg.sv(37) @ 0: uvm_test_top [TST] Calling print (uvm_default_line_printer)
obj0: (my_data@2687) { data: 'h5a  addr: 'h1  m_format0: (format@2700) { header: 'hb  footer: 'h0  enable: 'h1  body: 'h1  m_color: (colors@2711) { color: 'h8  enable: 'h0  } } }
UVM_INFO ./tb/test_pkg.sv(40) @ 0: uvm_test_top [TST] Calling print (uvm_default_tree_printer)
obj0: (my_data@2687) {
  data: 'h5a
  addr: 'h1
  m_format0: (format@2700) {
    header: 'hb
    footer: 'h0
    enable: 'h1
    body: 'h1
    m_color: (colors@2711) {
      color: 'h8
      enable: 'h0
    }
  }
}
UVM_INFO ./tb/test_pkg.sv(43) @ 0: uvm_test_top [TST] Calling print (uvm_default_table_printer)
-----------------------------------
Name          Type      Size  Value
-----------------------------------
obj0          my_data   -     @2687
  data        integral  8     'h5a
  addr        integral  8     'h1
  m_format0   format    -     @2700
    header    integral  4     'hb
    footer    integral  3     'h0
    enable    integral  1     'h1
    body      integral  2     'h1
    m_color   colors    -     @2711
      color   integral  4     'h8
      enable  integral  1     'h0
-----------------------------------
UVM_INFO ./tb/test_pkg.sv(46) @ 0: uvm_test_top [TST] Calling print (tprinter)
-------------------------
Name                Value
-------------------------
obj0                -
    obj0.data       0x5a
    obj0.addr       0x1
    obj0.m_format0  -
-------------------------
UVM_INFO ./tb/test_pkg.sv(49) @ 0: uvm_test_top [TST] Print derivative (tprinter)
UVM_INFO ./tb/my_pkg.sv(94) @ 0: reporter@@dv0 [DVR] do_print called
-------------------------------------
Name          Type        Size  Value
-------------------------------------
dv0           derivative  -     @2722
  data        integral    8     'h0
  addr        integral    8     'h0
  m_format0   format      -     @2732
    header    integral    4     'h0
    footer    integral    3     'h0
    enable    integral    1     'h0
    body      integral    2     'h0
    m_color   colors      -     @2741
      color   integral    4     'h0
      enable  integral    1     'h0
  mode        integral    3     'h0
-------------------------------------

--- UVM Report catcher Summary ---

Points to notice in the output:

  • The default, table, tree and line printers all show the same fields. Only the layout changes. m_color appears inside m_format0 even though it is printed by do_print() rather than a macro.
  • tprinter shows full names, no Type or Size columns, the 0x radix, and stops at m_format0 because depth is 1. Object IDs are hidden because reference is 0.
  • For dv0, "do_print called" appears before the table. The table printer collects all rows first and emits the table at the end. mode is the last row because do_print() output is appended after the macro fields.

Where are Printer Knobs Used?

Large transactions, such as a packet with a 1 KB payload array, can fill thousands of lines with the default settings. Teams often create a dedicated printer with a reduced depth or a line printer for regression logs, and keep the full table printer for interactive debug.

Common Mistakes

Mistake #1: Changing knobs on a shared global printer

Wrong: uvm_default_printer.knobs.depth = 0; to shorten one print, or changing printer.knobs inside do_print(), as the derivative class does
Right: create your own printer, like tprinter, configure it once, and pass it to print()

Why? The global printers are shared by every print() call in the simulation. A knob changed for one object silently changes the output for all later prints, which makes logs confusing to read.

Mistake #2: Forgetting to create nested objects

A nested object registered with `uvm_field_object or printed with print_object() must be created before print() runs. If m_format0 were never created in the constructor, the printer would show it as a null object, and none of its fields would appear.

Mistake #3: Overriding do_print() without calling super.do_print()

Wrong: a class extending format whose do_print() prints only its own new fields
Right: call super.do_print(printer); first, then print the new fields

Why? Without the super call, the parent's do_print() never runs, so anything it printed, such as m_color in format, disappears from the child's output.

Practice Exercise

Challenge: Print a Transaction Without Field Macros

Write a class bus_txn that extends uvm_sequence_item and has an 8-bit addr, a string tag and a nested colors object m_color. Do not use field macros. Make print() show all three, then print an instance with the tree printer.

Hint

Register the class with `uvm_object_utils, create m_color in the constructor, and use print_int(), print_string() and print_object() in do_print().

Solution

class bus_txn extends uvm_sequence_item;
  `uvm_object_utils (bus_txn)              // No field macros

  rand bit [7:0] addr;
  string         tag = "WR";
  colors         m_color;

  function new (string name = "bus_txn");
    super.new (name);
    m_color = colors::type_id::create ("m_color");   // Must exist before printing
  endfunction

  virtual function void do_print (uvm_printer printer);
    super.do_print (printer);                         // Keep anything the parent prints
    printer.print_int    ("addr", addr, $bits(addr), UVM_HEX);
    printer.print_string ("tag", tag);
    printer.print_object ("m_color", m_color);        // Nested object, expanded by the printer
  endfunction
endclass

// In a test or sequence:
// bus_txn txn = bus_txn::type_id::create ("txn");
// void'(txn.randomize ());
// txn.print (uvm_default_tree_printer);

Because colors registers its own fields with macros, print_object() expands m_color to show color and enable inside the tree.

Summary

In this tutorial, you learned:

  • print() is defined in uvm_object and replaces hand-written display functions for every class
  • UVM provides table, tree and line printers, and uvm_default_printer is a table printer
  • uvm_line_printer is derived from uvm_tree_printer and prints the same structure on one line
  • Fields are printed when registered with `uvm_field_* macros or printed in do_print(), and do_print() output is appended after macro fields
  • Knobs such as depth, size, full_name and hex_radix control the output, and are best changed on your own printer instance
  • Nested objects must be created before printing, and overridden do_print() methods should call super.do_print()
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5