When we implemented the “do” hooks before, we defined the convert2string
function, but we did not define our own do_print
function. This was because the convert2string
is very flexible and light weight as it does not require a so-called print policy that provides a print format. But if you define your own do_print
, you can enjoy pre-defined print formats immediately. You can also create your own formats. This article shows how.
Defining do_print
Defining do_print
itself is not very difficult. But before doing so, let’s look at the convert2string
function we defined before. As you see, the convert2string
function defines what variables to print and how.
1 2 3 4 5 6 7 8 9 10 11 12 13 | class jelly_bean_transaction extends uvm_sequence_item; virtual function string convert2string(); string s = super.convert2string(); s = { s, $psprintf( "\nname : %s", get_name() ) }; s = { s, $psprintf( "\nflavor : %s", flavor.name() ) }; s = { s, $psprintf( "\ncolor : %s", color.name() ) }; s = { s, $psprintf( "\nsugar_free: %b", sugar_free ) }; s = { s, $psprintf( "\nsour : %b", sour ) }; s = { s, $psprintf( "\ntaste : %s", taste.name() ) }; return s; endfunction: convert2string // ... endclass: jelly_bean_transaction |
Unlike convert2string
, usually do_print
does not define a print format by itself. Instead, it delegates the formatting to a print policy object (printer
). Below is our definition of do_print
. The uvm_printer
class provides an interface for printing uvm_object
s in various formats (line 1). In the do_print
function, we merely list the variables we want to print using the functions of uvm_printer
class (lines 3 to 8).
Strictly speaking, you can define the
do_print
without using theuvm_printer
, but if you do so, you are not able to use the printer formats defined in the subclasses ofuvm_printer
.
1 2 3 4 5 6 7 8 9 | virtual function void do_print( uvm_printer printer ); super.do_print( printer ); printer.print_string( "name", get_name() ); printer.print_string( "flavor", flavor.name() ); printer.print_string( "color", color.name() ); printer.print_field_int( "sugar_free", sugar_free, .size( 1 ) ); printer.print_field_int( "sour", sour, .size( 1 ) ); printer.print_string( "taste", taste.name() ); endfunction: do_print |
Using do_print
The print
and sprint
functions of uvm_object
call the do_print
. Let’s call the sprint
in our jelly bean scoreboard. For convenience, UVM pre-defines three print policies (uvm_default_table_printer
, uvm_default_tree_printer
, and uvm_default_line_printer
; lines 5 to 7). If you do not specify a print policy, uvm_default_printer
, which is set to uvm_default_table_printer
by default, is used (line 4). For comparison, we call convert2string
on line 3, too.
1 2 3 4 5 6 7 8 9 10 | class jelly_bean_sb_subscriber extends uvm_subscriber#( jelly_bean_transaction ); function void write( jelly_bean_transaction t ); `uvm_info( get_name(), { "using convert2string", t.convert2string() }, UVM_LOW ) `uvm_info( get_name(), { "using uvm_default_printer\n", t.sprint() }, UVM_LOW ) // use uvm_default_printer `uvm_info( get_name(), { "using uvm_default_table_printer\n", t.sprint( uvm_default_table_printer ) }, UVM_LOW ) `uvm_info( get_name(), { "using uvm_default_tree_printer\n", t.sprint( uvm_default_tree_printer ) }, UVM_LOW ) `uvm_info( get_name(), { "using uvm_default_line_printer\n", t.sprint( uvm_default_line_printer ) }, UVM_LOW ) endfunction: write // ... endclass: jelly_bean_sb_subscriber |
Let’s see the each output. After running a simulation, you should see something like these:
Output of convert2string
This is our old friend. We see the output as specified in the function.
UVM_INFO env.svh(134) @ 185: uvm_test_top.jb_env.jb_sb [jb_sb] using convert2string name : jb_tx flavor : BUBBLE_GUM color : RED sugar_free: 1 sour : 1 taste : YUMMY |
Output of sprint using uvm_default_printer
This is the output if we do not specify the print policy. As we mentioned before, the uvm_default_printer
is used, which is set to uvm_default_table_printer
by default.
UVM_INFO env.svh(136) @ 185: uvm_test_top.jb_env.jb_sb [jb_sb] using uvm_default_printer ----------------------------------------------------------------- Name Type Size Value ----------------------------------------------------------------- jb_tx sugar_free_jelly_bean_transaction - @861 name string 5 jb_tx flavor string 10 BUBBLE_GUM color string 3 RED sugar_free integral 1 'h1 sour integral 1 'h1 taste string 5 YUMMY ----------------------------------------------------------------- |
Output of sprint using uvm_default_table_printer
This output is exactly same as the above.
UVM_INFO env.svh(138) @ 185: uvm_test_top.jb_env.jb_sb [jb_sb] using uvm_default_table_printer ----------------------------------------------------------------- Name Type Size Value ----------------------------------------------------------------- jb_tx sugar_free_jelly_bean_transaction - @861 name string 5 jb_tx flavor string 10 BUBBLE_GUM color string 3 RED sugar_free integral 1 'h1 sour integral 1 'h1 taste string 5 YUMMY ----------------------------------------------------------------- |
Output of sprint using uvm_default_tree_printer
The uvm_default_tree_printer outputs the object in a tree form.
UVM_INFO env.svh(140) @ 185: uvm_test_top.jb_env.jb_sb [jb_sb] using uvm_default_tree_printer jb_tx: (sugar_free_jelly_bean_transaction@861) { name: jb_tx flavor: BUBBLE_GUM color: RED sugar_free: 'h1 sour: 'h1 taste: YUMMY } |
Output of sprint using uvm_default_line_printer
The uvm_default_line_printer
outputs the same information as the uvm_default_tree_printer
does, but in one line.
UVM_INFO env.svh(142) @ 185: uvm_test_top.jb_env.jb_sb [jb_sb] using uvm_default_line_printer jb_tx: (sugar_free_jelly_bean_transaction@861) { name: jb_tx flavor: BUBBLE_GUM color: RED sugar_free: 'h1 sour: 'h1 taste: YUMMY } |
Defining our own print policy
If the pre-defined formats do not satisfy your needs, you can create your own print policy. As an example, we are going to create a policy that prints an uvm_object
in JSON format. Our goal is to print a jelly_bean_transaction
like this:
{ "jb_tx": { "name": "jb_tx", "flavor": "BUBBLE_GUM", "color": "RED", "sugar_free": "'h1", "sour": "'h1", "taste": "YUMMY" } } |
For those of you who are not familiar with JSON, please see this link.
To create your own print policy, you need to extend the uvm_printer
class and define the emit
function. The information to print is stored in an array (m_rows
) of uvm_printer_row_info
structure (line 16) which holds the name of variable, size, value, etc. Line 31 prints a name-value pair such as "flavor":"BUBBLE_GUM"
. I’m not going to explain the implementation line-by-line because most of the other lines deal with detail formatting. One note I have to mention, though, is that the printer settings are defined in knobs
(an object of uvm_printer_knobs
class). We use one of the settings (indent
; the number of spaces to use for level indentation) on line 18.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | class json_printer extends uvm_printer; function new(); super.new(); endfunction: new virtual function string emit(); string s; string comma = ""; string space = { 100 { " " } }; string indent; int next_level; s = "{\n"; // begin JSON foreach ( m_rows[i] ) begin uvm_printer_row_info row = m_rows[i]; indent = space.substr( 1, ( row.level + 1 ) * knobs.indent ); s = { s, comma, indent }; if ( i == m_rows.size() - 1 ) begin // last row next_level = 0; end else begin // not last row next_level = m_rows[ i + 1 ].level; if ( row.level < next_level ) begin // next level is deepr s = { s, "\"", row.name, "\": {\n" }; // begin nested JSON object comma = ""; continue; end end s = { s, "\"", row.name, "\": \"", row.val, "\"" }; // name-value pair comma = ",\n"; if ( next_level < row.level ) begin // next level is shallower for ( int l = row.level; l > next_level; l-- ) begin indent = space.substr( 1, l * knobs.indent ); s = { s, "\n", indent, "}" }; // end nested JSON object end end end // foreach ( m_rows[i] ) emit = { s, "\n}" }; // end JSON m_rows.delete(); endfunction: emit endclass: json_printer |
To use the new print policy, we create it (line 6) and pass it when we call sprint
(line 10).
1 2 3 4 5 6 7 8 9 10 11 12 13 | class jelly_bean_sb_subscriber extends uvm_subscriber#( jelly_bean_transaction ); json_printer json_p; virtual function void build_phase( uvm_phase phase ); super.build_phase( phase ); json_p = new; endfunction: build_phase function void write( jelly_bean_transaction t ); `uvm_info( get_name(), { "using json_printer\n", t.sprint( json_p ) }, UVM_LOW ) endfunction: write // ... endclass: jelly_bean_sb_subscriber |
You should see something like this when you run:
UVM_INFO env.svh(144) @ 185: uvm_test_top.jb_env.jb_sb [jb_sb] using json_printer { "jb_tx": { "name": "jb_tx", "flavor": "BUBBLE_GUM", "color": "RED", "sugar_free": "'h1", "sour": "'h1", "taste": "YUMMY" } } |
For your reference, these are the classes and struct we used in this article.
You can view and run the code on EDA Playground.
What is the difference between $sformatf and $psprintf ?
They provide the same functionality.
$sformatf
is an IEEE-standard system function, whereas$psprintf
is not.