UVM object print support comes from four methods inherited from uvm_object: print(), sprint(), do_print() and convert2string(). The first three work together through a printer object, which formats every field consistently as a table, a tree or a single line. convert2string() skips the printer and returns whatever string you build.
Printing is the first debugging tool you reach for in a testbench. When a scoreboard flags a mismatch, a clear dump of the expected and actual transactions usually points straight at the problem, so it is worth setting up printing properly in every transaction class.
7 min read | Intermediate Level
What You'll Learn
- Print an object's fields automatically using field macros
- Implement
do_print()with the printer API - Choose between
print(),sprint()andconvert2string() - Switch between the table, tree and line printer formats
Syntax
function void print (uvm_printer printer = null); // print to the log
function string sprint (uvm_printer printer = null); // same output, returned as a string
virtual function void do_print (uvm_printer printer); // hook you override
virtual function string convert2string (); // your own free-form string
- When
printeris left out, the globaluvm_default_printeris used. It is a table printer unless you change it. print()andsprint()are not virtual. To add fields to their output, overridedo_print().convert2string()is called only by your own code. Nothing in the library calls it for you.
Two Ways to Print
On the printer path, print() and sprint() both call do_print(), and do_print() hands each field to the printer. Because the printer does all the formatting, every class in the testbench prints in the same layout, and you can change that layout for all of them at once.
On the convert2string() path there is no printer. You build the string yourself, typically with $sformatf, and pass it to a report macro. This suits compact one-line summaries, for example in a monitor that logs every transaction it sees.
Printing with Field Macros
The easiest way to make print() work is to register each field with the field macro for its type. UVM_DEFAULT includes the field in all the automatic operations: copy, compare, print, record and pack.
typedef enum {FALSE, TRUE} e_bool;
class Object extends uvm_object;
rand e_bool m_bool;
rand bit[3:0] m_mode;
rand byte m_data[4];
rand shortint m_queue[$];
string m_name;
constraint c_queue { m_queue.size() == 3; }
function new(string name = "Object");
super.new(name);
m_name = name;
endfunction
// Each variable has to be registered with a macro corresponding to its data
// type. For example, "int" types use `uvm_field_int, "enum" types use
// `uvm_field_enum, and "string" use `uvm_field_string
`uvm_object_utils_begin(Object)
`uvm_field_enum(e_bool, m_bool, UVM_DEFAULT)
`uvm_field_int (m_mode, UVM_DEFAULT)
`uvm_field_sarray_int(m_data, UVM_DEFAULT)
`uvm_field_queue_int(m_queue, UVM_DEFAULT)
`uvm_field_string(m_name, UVM_DEFAULT)
`uvm_object_utils_end
endclass
A minimal test creates the object, randomizes it and calls print().
class base_test extends uvm_test;
`uvm_component_utils(base_test)
function new(string name = "base_test", uvm_component parent=null);
super.new(name, parent);
endfunction
// In the build phase, create an object, randomize it and print
// its contents
function void build_phase(uvm_phase phase);
Object obj = Object::type_id::create("obj");
obj.randomize();
obj.print();
endfunction
endclass
module tb;
initial begin
run_test("base_test");
end
endmodule
The default table printer lists each field's name, type, size and value. Arrays and queues are expanded so that every element appears on its own row.
ncsim> run
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(392) @ 0: reporter [UVM/RELNOTES]
----------------------------------------------------------------
UVM-1.2
(C) 2007-2014 Mentor Graphics Corporation
(C) 2007-2014 Cadence Design Systems, Inc.
(C) 2006-2014 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
(C) 2013-2014 NVIDIA Corporation
----------------------------------------------------------------
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
-------------------------------------
Name Type Size Value
-------------------------------------
obj Object - @1899
m_bool e_bool 32 TRUE
m_mode integral 4 'hd
m_data sa(integral) 4 -
[0] integral 8 'h6c
[1] integral 8 'hf4
[2] integral 8 'he
[3] integral 8 'h58
m_queue da(integral) 3 -
[0] integral 16 'h3cb6
[1] integral 16 'h9ae9
[2] integral 16 'hd31d
m_name string 3 obj
-------------------------------------
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
The Type column uses short codes for containers: sa for a static array and da for a dynamic container such as this queue. The Size column is the bit width for scalar fields and the number of elements for arrays.
Implementing do_print()
Field macros expand into a large amount of code, which slows compilation and simulation, so many teams implement do_print() by hand instead. Inside it, each field is passed to a printer method that matches its type. You must go through the printer rather than calling $display, otherwise sprint() cannot capture the output and the formatting stops being consistent.
class Object extends uvm_object;
rand e_bool m_bool;
rand bit[3:0] m_mode;
rand byte m_data[4];
rand shortint m_queue[$];
string m_name;
constraint c_queue { m_queue.size() == 3; }
function new(string name = "Object");
super.new(name);
m_name = name;
endfunction
// Use "do_print" instead of the automation macro
`uvm_object_utils(Object)
// This function simply uses the printer functions to print variables based on their
// data types. For example, "int" variables are printed using function "print_field_int"
virtual function void do_print(uvm_printer printer);
super.do_print(printer);
printer.print_string("m_bool", m_bool.name());
printer.print_field_int("m_mode", m_mode, $bits(m_mode), UVM_HEX);
printer.print_string("m_name", m_name);
endfunction
endclass
Only three of the five fields are printed here, simply because only three are passed to the printer. The commonly used printer methods are:
print_field_int(name, value, size, radix)for integral values up to 64 bits, with an optional radix such asUVM_HEXorUVM_DECprint_string(name, value)for stringsprint_object(name, value)for a nested object, which the printer expands with its own fields
ncsim> run UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(392) @ 0: reporter [UVM/RELNOTES] ---------------------------------------------------------------- UVM-1.2 (C) 2007-2014 Mentor Graphics Corporation (C) 2007-2014 Cadence Design Systems, Inc. (C) 2006-2014 Synopsys, Inc. (C) 2011-2013 Cypress Semiconductor Corp. (C) 2013-2014 NVIDIA Corporation ---------------------------------------------------------------- UVM_INFO @ 0: reporter [RNTST] Running test base_test... ------------------------------- Name Type Size Value ------------------------------- obj Object - @1898 m_bool string 4 TRUE m_mode integral 4 'hd m_name string 3 obj ------------------------------- UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER] --- UVM Report Summary ---
Notice that m_bool now shows type string with size 4. It was printed with print_string() using the enum's name, so the printer only knows it received a four-character string. The macro version printed the same field as type e_bool with size 32.
Returning the Table with sprint()
sprint() produces exactly the same output as print() but returns it as a string. That lets you embed an object dump inside a report message, so it carries an ID, a verbosity level and a file and line number like any other message. This example uses the do_print() version of Object from the previous section.
class base_test extends uvm_test;
`uvm_component_utils(base_test)
function new(string name = "base_test", uvm_component parent=null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
Object obj = Object::type_id::create("obj");
obj.randomize();
// Instead of calling print() function, let us call "sprint"
`uvm_info(get_type_name(), $sformatf("Contents: %s", obj.sprint()), UVM_LOW)
endfunction
endclass
module tb;
initial begin
run_test("base_test");
end
endmodule
ncsim> run
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(392) @ 0: reporter [UVM/RELNOTES]
----------------------------------------------------------------
UVM-1.2
(C) 2007-2014 Mentor Graphics Corporation
(C) 2007-2014 Cadence Design Systems, Inc.
(C) 2006-2014 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
(C) 2013-2014 NVIDIA Corporation
----------------------------------------------------------------
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO testbench.sv(98) @ 0: uvm_test_top [base_test] Contents:
-------------------------------
Name Type Size Value
-------------------------------
obj Object - @1902
m_bool string 4 TRUE
m_mode integral 4 'he
m_name string 3 obj
-------------------------------
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
Wrapping sprint() in `uvm_info with a high verbosity such as UVM_HIGH keeps large object dumps out of normal regression logs, while still letting you turn them on with +UVM_VERBOSITY=UVM_HIGH when debugging.
Choosing a Printer Format
Both print() and sprint() accept a printer argument. UVM provides three ready-made printers as global instances, so you can change the format without writing any formatting code.
obj.print(); // uvm_default_printer, a table printer by default
obj.print(uvm_default_tree_printer); // nested tree layout with braces
obj.print(uvm_default_line_printer); // the whole object on a single line
uvm_default_printer = uvm_default_line_printer; // change the default for every print()
The line printer is handy in logs that are processed with scripts, because every object stays on one line. The table printer is easiest to read by eye, which is why it is the default.
Free-Form Output with convert2string()
convert2string() returns any string you choose. It never goes through a printer, and fields registered with field macros do not appear in it automatically.
class Object extends uvm_object;
rand e_bool m_bool;
rand bit[3:0] m_mode;
rand byte m_data[4];
rand shortint m_queue[$];
string m_name;
constraint c_queue { m_queue.size() == 3; }
function new(string name = "Object");
super.new(name);
m_name = name;
endfunction
// Register with the factory only; no field macros are used
`uvm_object_utils(Object)
virtual function string convert2string();
string contents = "";
$sformat(contents, "%s m_name=%s", contents, m_name);
$sformat(contents, "%s m_bool=%s", contents, m_bool.name());
$sformat(contents, "%s m_mode=0x%0h", contents, m_mode);
foreach(m_data[i]) begin
$sformat(contents, "%s m_data[%0d]=0x%0h", contents, i, m_data[i]);
end
return contents;
endfunction
endclass
Because it only returns a string, convert2string() has to be called inside a report macro or a $display.
class base_test extends uvm_test;
`uvm_component_utils(base_test)
function new(string name = "base_test", uvm_component parent=null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
Object obj = Object::type_id::create("obj");
obj.randomize();
`uvm_info(get_type_name(), $sformatf("convert2string: %s", obj.convert2string()), UVM_LOW)
endfunction
endclass
module tb;
initial begin
run_test("base_test");
end
endmodule
ncsim> run
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(392) @ 0: reporter [UVM/RELNOTES]
----------------------------------------------------------------
UVM-1.2
(C) 2007-2014 Mentor Graphics Corporation
(C) 2007-2014 Cadence Design Systems, Inc.
(C) 2006-2014 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
(C) 2013-2014 NVIDIA Corporation
----------------------------------------------------------------
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO testbench.sv(99) @ 0: uvm_test_top [base_test] convert2string:
m_name=obj m_bool=TRUE m_mode=0xe m_data[0]=0xf4 m_data[1]=0xe m_data[2]=0x58 m_data[3]=0xbd
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
Quick Check
Q: A class registers all its fields with field macros and also implements convert2string() that prints only m_name. What does `uvm_info("T", obj.convert2string(), UVM_LOW) show?
Show Answer
Only m_name. Field macros feed the printer path used by print() and sprint(), but they do not add anything to convert2string(), which returns exactly the string you build.
Common Mistakes
Mistake #1: Using $display inside do_print()
Wrong: $display("m_mode=%0h", m_mode); inside do_print()
Right: printer.print_field_int("m_mode", m_mode, $bits(m_mode), UVM_HEX);
Why? $display bypasses the printer. The text appears out of place in print() output and is missing entirely from the string that sprint() returns.
Mistake #2: Calling print() and expecting a string back
print() returns nothing; it writes straight to the log. Writing $sformatf("%s", obj.print()) does not compile. Use sprint() when you need the formatted table as a string.
Mistake #3: Overriding print() instead of do_print()
print() is not virtual. An override is skipped whenever the object is printed through a base class handle, such as a uvm_object handle stored in a scoreboard queue. Customize the output in do_print(), which both print() and sprint() call.
Summary
In this article, you learned:
print()andsprint()calldo_print(), and a printer object formats the fields- Field macros make
print()work automatically, and a hand-writtendo_print()must use printer methods such asprint_field_int() sprint()returns the same output as a string, which you can place inside`uvm_info- The table, tree and line printers change the layout without any extra code
convert2string()returns a free-form string, bypasses the printer and ignores field macros