UVM Tutorial for Candy Lovers – 33. Defining do_print

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_objects 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 the uvm_printer, but if you do so, you are not able to use the printer formats defined in the subclasses of uvm_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.

UVM printer classes
UVM printer classes

EDA Playground

You can view and run the code on EDA Playground.

2 thoughts on “UVM Tutorial for Candy Lovers – 33. Defining do_print”

Leave a Reply

Your email address will not be published. Required fields are marked *