UVM Tutorial for Candy Lovers – 15. “Do” Hooks

Posted on January 4, 2013 (Last Updated on April 4, 2014)

This post will explain user-definable do_* hook functions. In Field Macros, we saw that the standard data methods, such as copy() and compare(), called the user-definable hook functions, such as do_copy() and do_compare(). I will define these hook functions in this post.

Jelly Bean Transaction Class with “do” Functions

Let’s add the “do” functions to the jelly_bean_transaction class.

do_copy

The do_copy() method is called by the copy() method. The do_copy() method is used to copy all the properties of a jelly_bean_transaction object (lines 10 to 14). Note that we have to cast a uvm_object (rhs) to a jelly_bean_transaction (that) in order to access the properties of the jelly_bean_transaction object (line 4). We must call the super.do_copy() to copy the properties defined in the super class (line 9).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   virtual function void do_copy( uvm_object rhs );
      jelly_bean_transaction that;
 
      if ( ! $cast( that, rhs ) ) begin         `uvm_error( get_name(), "rhs is not a jelly_bean_transaction" )
         return;
      end
 
      super.do_copy( rhs );      this.flavor     = that.flavor;      this.color      = that.color;      this.sugar_free = that.sugar_free;      this.sour       = that.sour;      this.taste      = that.taste;   endfunction: do_copy

do_compare

The do_compare() method is called by the compare() method. The do_compare() is used to compare each property of the jelly_bean_transaction object. The do_compare() returns 1 if the comparison succeeds, and returns 0 if the comparison fails (lines 6 to 11). Note that we have to cast a uvm_object (rhs) to a jelly_bean_transaction (that) again in order to access the properties of the jelly_bean_transaction object (line 4). We must call the super.do_compare() to compare the properties of the super class (line 6). The uvm_comparer argument provides a policy object for doing comparisons, but we do not use it.

1
2
3
4
5
6
7
8
9
10
11
12
   virtual function bit do_compare( uvm_object rhs, uvm_comparer comparer );
      jelly_bean_transaction that;
 
      if ( ! $cast( that, rhs ) ) return 0; 
      return ( super.do_compare( rhs, comparer )  &&               this.flavor     == that.flavor     &&               this.color      == that.color      &&               this.sugar_free == that.sugar_free &&               this.sour       == that.sour       &&               this.taste      == that.taste );   endfunction: do_compare

do_pack

The do_pack() method is called by the pack(), pack_bytes(), and pack_ints() methods. The do_pack() is used to pack each propery of the jelly_bean_transaction object using a uvm_packer policy object. Please see Register Abstraction for how each property is packed. The packer determines how the packing should be done. We must call the super.do_pack() to pack the properties of the super class (line 5).

1
2
3
4
5
6
7
8
9
10
11
12
13
   virtual function void do_pack( uvm_packer packer );
      bit       R1; // reserved bit
      bit [5:0] R6; // reserved bits
 
      super.do_pack( packer );      packer.pack_field_int( .value( flavor     ), .size( 3 ) );      packer.pack_field_int( .value( color      ), .size( 2 ) );      packer.pack_field_int( .value( sugar_free ), .size( 1 ) );      packer.pack_field_int( .value( sour       ), .size( 1 ) );      packer.pack_field_int( .value( R1         ), .size( 1 ) );      packer.pack_field_int( .value( taste      ), .size( 2 ) );      packer.pack_field_int( .value( R6         ), .size( 6 ) );   endfunction: do_pack

do_unpack

The do_unpack() method is called by the unpack(), unpack_bytes(), and unpack_ints() methods. The do_unpack() is used to unpack each property of the jelly_bean_transaction object using a uvm_packer policy object. The packer determines how the unpacking should be done. We must call the super.do_unpack() to unpack the properties of the super class (line 5).

1
2
3
4
5
6
7
8
9
10
11
12
13
   virtual function void do_unpack( uvm_packer packer );
      bit       R1; // reserved bit
      bit [5:0] R6; // reserved bits
 
      super.do_unpack( packer );      flavor     = flavor_e'( packer.unpack_field_int( .size( 3 ) ) );      color      = color_e '( packer.unpack_field_int( .size( 2 ) ) );      sugar_free =            packer.unpack_field_int( .size( 1 ) );      sour       =            packer.unpack_field_int( .size( 1 ) );      R1         =            packer.unpack_field_int( .size( 1 ) );      taste      = taste_e '( packer.unpack_field_int( .size( 2 ) ) );      R6         =            packer.unpack_field_int( .size( 6 ) );   endfunction: do_unpack

convert2string

The convert2string() method is called by the user to provide object information in the form of a string. The convert2string() is used to convert each property of the jelly_bean_transaction object into a string. We should call the super.convert2string() to convert the properties of the super class into the string (line 2).

1
2
3
4
5
6
7
8
9
10
   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

Test the “do” Hooks

Let’s test the “do” hooks. We will define the run_phase() task of the jelly_bean_test class as follows.

  1. Create three jelly_bean_transactions; jb_tx1, jb_tx2 and jb_tx3 (lines 11 to 13).
  2. Randomize the jb_tx1 (line 14).
  3. Copy the properties of the jb_tx1 to jb_tx2 (line 18).
  4. Copy the properties of the jb_tx1 to jb_tx3, but using the pack() and unpack() methods instead of calling the copy() method (lines 24 and 25).
  5. Now the jb_tx1, jb_tx2, and jb_tx3 should have the same property values. Verify it using the compare() method (lines 29 to 39).
  6. Print the properties of the jb_tx1, jb_tx2, and jb_tx3 using the convert2string() method to visually verify the property values (lines 43 to 45).
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
46
47
48
   task run_phase( uvm_phase phase );
      jelly_bean_transaction jb_tx1;
      jelly_bean_transaction jb_tx2;
      jelly_bean_transaction jb_tx3;
      uvm_packer jb_packer;
      bit bitstream[];
      int num_bits;
 
      phase.raise_objection( .obj( this ) );
 
      jb_tx1 = jelly_bean_transaction::type_id::create( "jb_tx1" );      jb_tx2 = jelly_bean_transaction::type_id::create( "jb_tx2" );      jb_tx3 = jelly_bean_transaction::type_id::create( "jb_tx3" );      assert( jb_tx1.randomize() ); 
      // copy jb_tx1 to jb_tx2
 
      jb_tx2.copy( jb_tx1 ); 
      // create jb_tx3 by packing and unpacking jb_tx1
 
      jb_packer = new;      jb_packer.big_endian = 0;      num_bits = jb_tx1.pack  ( bitstream, jb_packer );      num_bits = jb_tx3.unpack( bitstream, jb_packer ); 
      // check if jb_tx1, jb_tx2 and jb_tx3 have the same properties
 
      if ( jb_tx1.compare( jb_tx2 ) ) begin         `uvm_info( get_name(), "jb_tx1 and jb_tx2 matched", UVM_NONE )      end else begin         `uvm_error( get_name(), "jb_tx1 and jb_tx2 mismatched" )      end 
      if ( jb_tx2.compare( jb_tx3 ) ) begin         `uvm_info( get_name(), "jb_tx2 and jb_tx3 matched", UVM_NONE )      end else begin         `uvm_error( get_name(), "jb_tx2 and jb_tx3 mismatched" )      end 
      // print each object
 
      `uvm_info( get_name(), jb_tx1.convert2string(), UVM_NONE )      `uvm_info( get_name(), jb_tx2.convert2string(), UVM_NONE )      `uvm_info( get_name(), jb_tx3.convert2string(), UVM_NONE ) 
      phase.drop_objection( .obj( this ) );
   endtask: run_phase

You might have noticed that we created a uvm_packer object (jb_packer) on line 22 and set its big_endian property to 0 (line 23). We used this packer when we pack and unpack the jelly_bean_transaction (lines 24 and 25). This is to make sure we pack each property value in little endian as we did in Register Abstraction. If you just want to pack and unpack and don’t care about the bitstream representation, you don’t have to create the uvm_packer object. If you don’t specify the uvm_packer, the global uvm_default_packer policy will be used, of which the value of big_endian is 1. The following diagram shows how the value of big_endian affects the bitstream representation. The m_bits is a property of the uvm_packer and it represents the packed bitstream in the form of a bit array.

Pack in little endian and in big endian
Pack in little endian and in big endian

Simulation

Here is a simulation result. As you see, all three jelly_bean_transactions have same property values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
UVM_INFO jb15.sv(589) @ 0: uvm_test_top [uvm_test_top] jb_tx1 and jb_tx2 matched
UVM_INFO jb15.sv(595) @ 0: uvm_test_top [uvm_test_top] jb_tx2 and jb_tx3 matched
UVM_INFO jb15.sv(602) @ 0: uvm_test_top [uvm_test_top]
name      : jb_tx1
flavor    : CHOCOLATE
color     : RED
sugar_free: 1
sour      : 0
taste     : UNKNOWN
UVM_INFO jb15.sv(603) @ 0: uvm_test_top [uvm_test_top]
name      : jb_tx2
flavor    : CHOCOLATE
color     : RED
sugar_free: 1
sour      : 0
taste     : UNKNOWN
UVM_INFO jb15.sv(604) @ 0: uvm_test_top [uvm_test_top]
name      : jb_tx3
flavor    : CHOCOLATE
color     : RED
sugar_free: 1
sour      : 0
taste     : UNKNOWN

I hope this tutorial helped you to understand the “do” hooks.

Get source code

23 thoughts on “UVM Tutorial for Candy Lovers – 15. “Do” Hooks”

  1. Hi Keisuke-san,

    Thanks for all the useful tutorials provided on this site.
    I am trying to use do_compare method which I have declared as :
    virtual function bit do_compare (uvm_object rhs, uvm_comparer comparer);

    Still not able to call with compare only do_compare works.
    Any suggestions please.

    Regards,

    Chandan

  2. Hi Keisuke,

    If the jelly_bean_transaction class contains a handle to an object, how does the do_copy look like so it will be a “deep copy”?

    Thanks,

    Del

    1. As an example, let’s assume that the jelly_bean_transaction has a handle to a jelly_bean_ingredient.

      class jelly_bean_ingredient extends uvm_object;
         int sugar;
         int corn_syrup;
         int starch;
         // ...
      endclass: jelly_bean_ingredient
      class jelly_bean_transaction extends uvm_sequence_item;
         jelly_bean_ingredient ingredient;
         // ...
      endclass: jelly_bean_transaction

      If you do a shallow-copy, you would write:

      virtual function void do_copy( uvm_object rhs );
         this.ingredient = that.ingredient;
         // ...
      endfunction: do_copy

      In this case, both this and that point to the same ingredient object.

      On the other hand, if you do a deep-copy, you would write:

      virtual function void do_copy( uvm_object rhs );
         this.ingredient = new;
         this.ingredient.sugar      = that.ingredient.sugar;
         this.ingredient.corn_syrup = that.ingredient.corn_syrup;
         this.ingredient.starch     = that.ingredient.starch;
         // ...
      endfunction: do_copy

      In this case, this and that point to different ingredient objects even though the values of sugar, corn_syrup, and starch are the same. You could also do:

      virtual function void do_copy( uvm_object rhs );
         this.ingredient = new;
         this.ingredient.copy( that.ingredient );
         // ...
      endfunction: do_copy

      to delegate the copy to the ingredient object if it properly implements its own do_copy. Hope this helps.

  3. Hi Keisuke,

    I am implementing these do hooks in my transaction class, Basically I have a 32 bit word to send with 5 fields . My Transaction has other fields also(but for driving on interface I am using those 5 fields of 32 bit word).
    I am driving that 32bit word on a serial interface(one by one ,little endian). Now I am bit confused how can I use this Pack and Unpack method in

    1. Monitor( after collecting data from interface and before sending to the scoreboard) and in
    2. driver(Getting packet from sequencer and then before driving it on Interface)

    Thanks,

    1. 1) The monitor should collect the bit-stream on the bus to a dynamic array of the bit type (say, bit mon_bitstream[]) until 32-bit data has collected. Then, unpack the bit-stream into the five fields by calling your_transaction.unpack( mon_bitstream ).
      2) The driver should have a similar dynamic array of the bit type (say, bit drv_bitstream[]). When the driver receives a transaction, call your_transaction.pack( drv_bitstream ) to fill the bit-stream.

  4. Hi Keisuke-san,

    Would you please explain for me in function “do_unpack”, why taste use cast(‘) operator but R6 is not

    taste = taste_e ‘( packer.unpack_field_int( .size( 2 ) ) );
    R6 = packer.unpack_field_int( .size( 6 ) );

    Thanks,

    1. The packer.unpack_field_int() returns the type of uvm_integral_t, which is typedef-ed to logic signed[63:0]. Since the taste is an enum type, which is not equivalent to the uvm_integral_t, we need the cast to convert the type. On the other hand, the type of R6 is bit[5:0]. SystemVerilog automatically converts the logic signed[63:0] to bit[5:0] without having a cast.

  5. another question,
    if the transcation is not only the few variables for packing one int array,it has more than 50 vairables for packing 10 int arrays ,and I will process the 10 arrays data in the sequence,as fowllows:

    I use your example do_pack() in my transaction,

    function void do_pack(uvm packer packer);
        super.do_pack(packer);
        packer.pack_field_int(ul_int_format,14);
         .....   /// many variables same way
        packer.pack_field_int(cp_type);
        ///I just want the variables to pack into 10 int variables or int array ,wdata[10]. so I don't know this way is or not right
         
    endfunction
    

    and I called the pack function in the sequence.

       int unsigned bitstreams[];
       int unsigned my_wdata_array[];
       uvm_packer my_packer;
       my_packer = new;
       my_wdata_array = new[10];
       for(int i=0;i<10;i++) begin
          my_class.pack_ints(bitstreams,my_packer);
          my_packer.get_ints(my_wdata_array[i]);
          send(addr,wdata[i]); //// my process
       end
    

    but the result is not right , the wdata[i] value is wrong,I don't know where I am wrong,please help me.
    thanks!

    1. I’m not 100% sure what you are trying to do, but if you want to pack your transaction into the bitstreams[], your do_pack should be like this:

      virtual function void do_pack( uvm_packer packer );
        super.do_pack( packer );
        packer.pack_field_int( ul_int_format, 14 );
        // ...
        packer.pack_field_int( cp_type, $bits( cp_type ) );
        for ( int i = 0; i < 10; i++ )
          packer.pack_field_int( wdata[i], 32 );
      endfunction: do_pack

      And the sequence should be like this:

      int unsigned bitstreams[];
      uvm_packer my_packer;
      my_packer = new;
      my_class.pack_ints( bitstreams, my_packer ); // pack my_class into bitstreams[]

      Let me know if I misunderstood your question.

      1. thanks answer my question!
        but you misundertood my question. the array “wdata[i]” is not the variables of my transcation .
        my question is I want the variables of my transcation to pack into the int_arrays, how do I write the code?
        for example, I want the 50 variables of my transcation to pack into wdata[10](10 integer_arrays).because the 10 integer_arrays will be used for rewrite or managed in my sequence.

        I very expect your reply!

        1. Now I know your question. Basically your do_pack looks fine, but the sequence should be like this:

          int unsigned bitstreams[];
          uvm_packer my_packer;
          my_packer = new;
          my_class.pack_ints( bitstreams, my_packer ); // pack my_class into bitstreams[]
          foreach ( bitstreams[i] ) 
            send( addr, bitstreams[i] );
  6. Hi Keisuke-san
    Thanks your reply,I understand the function “pack” clear,but I have another question.
    you say,pack my class into bitstreams[]; the bitstreams[] is local variable,if the “pack” and “unpack” is used in different classes,
    how do the variable(bitstreams[]) in the class pass to another class? whether or not the my_packer(the my_packer variable)?

    I very expect your reply.^_^
    thanks!

    1. bitsream[] is local variable not required to share.
      I can give you the example where I used the same. bitstream[] will be your local variable only, lets say in Driver class and monitor class. When you get packet from your transactor/ sequencer into driver class before driving onto interface you can convert your packet fields into bitstream[](Local to Driver Class) using do_unpack method. and then having the same local variable you can collect the interface data into bitstream[] of monitor class and then you can convert into the relevant packet using do_pack method with this butstream(local to Monitor class).
      I hope you can some idea now.

  7. I have another question.
    if I call the unpack function 4 times in my reference model,the packer and bitstreams[] variables should declare and instance four different packers and four different bitstreams[] ,or only declare and instance once.

  8. Hi Keisuke-san,

    Would you please explain why and when exactly are the pack and unpack functions used.

    The only use scenario I see is for the monitor to grab the pin values from the interface and unpack them into the
    sequence item.

    What would be the use case for a driver, why would we pack the fields into the bitstream and not use the fields directly?

    Please, if you can, provide some real-world examples for the use of pack and unpack.

    Kind regards,
    Milan

Leave a Reply

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