UVM Tutorial for Candy Lovers – 14. Field Macros

Last Updated: September 14, 2013

This post will explain how UVM field macros (`uvm_field_*) work. In Transactions and Sequences, we used the UVM field macros to automatically implement the standard data methods, such as copy(), compare(), and pack() for the jelly_bean_transaction.

1
2
3
4
5
6
7
`uvm_object_utils_begin(jelly_bean_transaction)
   `uvm_field_enum(flavor_e, flavor, UVM_ALL_ON)
   `uvm_field_enum(color_e,  color,  UVM_ALL_ON)
   `uvm_field_int (sugar_free,       UVM_ALL_ON)
   `uvm_field_int (sour,             UVM_ALL_ON)
   `uvm_field_enum(taste_e,  taste,  UVM_ALL_ON)
`uvm_object_utils_end

The following pseudo code shows how these field macros are expanded.

Click here to expand the macros.

Wow! What a long code! Each field macro was expanded to about 80 lines of code. You don’t need to fully understand the code, but basically the code does the followings:

  • The `uvm_object_utils_begin() macro creates a virtual function called __m_uvm_field_automation (the first highlighted block of code in yellow).
  • Each `uvm_field_* macro creates a case statement (the second highlighted block) and performs the functionality of copy, compare, and pack, depending on the value of the what__ argument passed to the __m_uvm_field_automation() function.

The __m_uvm_field_automation() is then used in uvm_object class. As you see the following diagram, the uvm_object::copy() calls the __m_uvm_field_automation() with UVM_COPY as the value of the what__. Similarly uvm_object::compare() calls the __m_uvm_field_automation() with UVM_COMPARE.

Some Standard Data Methods of the uvm_object Class
By now you might think that these field macros are convenient but not efficient. For more efficient and more flexible implementation, we can use user definable do_*() hooks. As shown above the uvm_object calls the do_*() hook after calling the __m_uvm_field_automation(). For example, the uvm_object::copy() calls the do_copy() after calling the __m_uvm_field_automation(), and the uvm_object::compare() calls the do_compare() after calling the __m_uvm_field_automation(). By default these do_*() methods are empty. I will explain more detail about the do_*() methods in the next post. If no field macros are used, the __m_uvm_field_automation() does almost nothing (only the code between the two highlighted blocks will remain).

I hope this post helped you to understand the field macros.

14 thoughts on “UVM Tutorial for Candy Lovers – 14. Field Macros”

  1. Hi I have started working on UVM a fresh.
    I would like to know why do we require this feild macros. I mean, when we say copy, what are we copying ? If you see the following example, cmd, addr, data are the members of class my_transaction. Then what is the need to declare an object of my_transaction ‘tx’ and cast it to the already exixting uvm object and then copy them to members of the class ?
    Basically I want to understand:
    1) Why do we use this macros like copy, compare ? What if we not using them ?
    2) Is casting operation doing a cast with UVM_OBJECT ? what ar we ensuring from this ?
    3) When we use cmd = tx.cmd, are we actually copying the handles to members of my_transaction class ?

    I thank you in advance.

      class my_transaction extends uvm_sequence_item;
      
        `uvm_object_utils(my_transaction)
      
        rand bit cmd;
        rand int addr;
        rand int data;
      
        constraint c_addr { addr >= 0; addr = 0; data < 256; }
        
        function new (string name = "");
          super.new(name);
        endfunction
        
        function string convert2string;
          return $sformatf("cmd=%b, addr=%0d, data=%0d", cmd, addr, data);
        endfunction
    
        function void do_copy(uvm_object rhs);
          my_transaction tx;
          $cast(tx, rhs);
          cmd  = tx.cmd;
          addr = tx.addr;
          data = tx.data;
        endfunction
        
        function bit do_compare(uvm_object rhs, uvm_comparer comparer);
          my_transaction tx;
          bit status = 1;
          $cast(tx, rhs);
          status &= (cmd  == tx.cmd);
          status &= (addr == tx.addr);
          status &= (data == tx.data);
          return status;
        endfunction
    
      endclass: my_transaction
    
    1. First of all, using the field macros such as `uvm_field_enum and `uvm_field_int is not recommended because of the overhead I mentioned in the article. It is recommended to use the do_* hooks such as do_copy and do_compare like you did in the my_transaction.

      (1) Why use do_copy() ?
      Let’s assume you have two instances of the my_transaction.

      my_transaction tr0 = new( "tr0" );
      my_transaction tr1 = new( "tr1" );
       
      tr0.cmd  = 0;
      tr0.addr = 0;
      tr0.data = 0;
       
      tr1.cmd  = 1;
      tr1.addr = 1;
      tr1.data = 1;

      Then you copy the tr0 to the tr1 as follows:

      tr1.copy( tr0 );

      The copy() function internally calls the do_copy(), which is empty by default. That means if you don’t define the do_copy(), the properties (cmd, addr, and data) of the my_transaction are not copied. For example:

      $display( "tr1.cmd=%b", tr1.cmd );

      still displays tr1.cmd=1, not tr1.cmd=0.
      On the other hand, if you define the do_copy() like what you did, the properties are copied. For example:

      $display( "tr1.cmd=%b", tr1.cmd );

      displays tr1.cmd=0.

      (2) Why $cast is necessary?

      function void do_copy(uvm_object rhs);
         my_transaction tx;
         $cast(tx, rhs);
         cmd  = tx.cmd;
         addr = tx.addr;
         data = tx.data;
      endfunction

      The type of rhs is uvm_object. The uvm_object does not know the properties such as cmd and addr. The $cast assigns the rhs to the my_transaction type so that you can access the cmd and addr.

      (3) What does cmd = tx.cmd do?
      It copies the value of tx.cmd to this.cmd.

      1. Hi Keisuke Shimizu,

        I found your answers very helpful. I have a question regarding uvm_comparer, in the above code pasted by surya it says function bit do_compare(uvm_object rhs, uvm_comparer comparer);
        -> my question is what does uvm_comparer do as a second argument?

        Regards,
        Imran

        1. The uvm_comparer is a so-called policy class that you can delegate the comparison task to. For example, Surya’s do_compare function can be rewritten using the uvm_comparer as follows:

          virtual function bit do_compare( uvm_object rhs, uvm_comparer comparer );
             my_transaction tx;
             bit status = 1;
             $cast( tx, rhs );
             status &= comparer.compare_field( .name("cmd" ), .lhs(cmd ), .rhs(tx.cmd ), .size( 1) );
             status &= comparer.compare_field( .name("addr"), .lhs(addr), .rhs(tx.addr), .size(32) );
             status &= comparer.compare_field( .name("data"), .lhs(data), .rhs(tx.data), .size(32) );
             return status
          endfunction: do_compare

          However, because of the overhead the uvm_comparer class adds, it is not recommended to use this class.

          1. 1) Can you describe what overhead the uvm_comparer class adds? Did accellera recommend not to use this?

            2) Does .compare() also respect the UVM_NOCOMPARE flag on a `uvm_field_()? (Im trying this now and it doesnt appear to be working.)

          2. 1) The compare_field function I used above executes the following code:

            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
            49
            50
            51
            
              virtual function bit compare_field (string name, 
                                                  uvm_bitstream_t lhs, 
                                                  uvm_bitstream_t rhs, 
                                                  int size,
                                                  uvm_radix_enum radix=UVM_NORADIX); 
                uvm_bitstream_t mask;
                string msg;
             
                if(size < = 64)
                  return compare_field_int(name, lhs, rhs, size, radix);
             
                mask = -1;
                mask >>= (UVM_STREAMBITS-size);
                if((lhs & mask) !== (rhs & mask)) begin
                  uvm_object::__m_uvm_status_container.scope.set_arg(name);
                  case (radix)
                    UVM_BIN: begin
                          $swrite(msg, "lhs = 'b%0b : rhs = 'b%0b", 
                                   lhs&mask, rhs&mask);
                         end
                    UVM_OCT: begin
                          $swrite(msg, "lhs = 'o%0o : rhs = 'o%0o", 
                                   lhs&mask, rhs&mask);
                         end
                    UVM_DEC: begin
                          $swrite(msg, "lhs = %0d : rhs = %0d", 
                                   lhs&mask, rhs&mask);
                         end
                    UVM_TIME: begin
                        $swrite(msg, "lhs = %0t : rhs = %0t", 
                           lhs&mask, rhs&mask);
                    end
                    UVM_STRING: begin
                          $swrite(msg, "lhs = %0s : rhs = %0s", 
                                   lhs&mask, rhs&mask);
                         end
                    UVM_ENUM: begin
                          //Printed as decimal, user should cuse compare string for enum val
                          $swrite(msg, "lhs = %0d : rhs = %0d", 
                                   lhs&mask, rhs&mask);
                          end
                    default: begin
                          $swrite(msg, "lhs = 'h%0x : rhs = 'h%0x", 
                                   lhs&mask, rhs&mask);
                         end
                  endcase
                  print_msg(msg);
                  return 0;
                end
                return 1;
              endfunction

            This is the overhead I mentioned. You can do the equivalent comparison without using the uvm_comparer like this:

            status &= ( cmd === tx.cmd );

            Accellera did not comment on this, though.

            2) The compare function calls both __m_uvm_field_automation and do_compare functions internally. The __m_uvm_field_automation does not compare the field if the UVM_NOCOMPARE flag is set. But if you have your own do_compare (and it compares the field), the field is still compared because the do_compare is not aware of the UVM_NOCOMPARE flag.

    1. The compare() function internally calls do_compare(), which is empty by default. The do_compare() defines the strategy of comparison. In your my_transaction, you compare the cmd, addr, and data. By doing this, you defined two objects being equal if the values of cmd, addr, and data are equal in the two objects.

  2. Shimizu,

    I am very much happy to receive your answers. Thank You Very much…!
    Your website is fabulous and very informative and I am trying all sort of nonsence in UVM 🙂

    All the best..!

  3. Hello Keisuke,

    Your articles are really helpful,

    Can u explain the exact usage of pack and unpack macros how they work and when do we need to write our own do_pack and do_unpack?

    Also can u explain the concept of big_endian?

    I will be really thankful to you!!

    Regards
    Shreemant

    1. The pack methods are used to convert object’s properties into an array of bits, bytes, or ints. The unpack methods convert back the array into the properties. You can use the pack method to convert a SystemVerilog object into an array of bits and pass it to a C++ program, for example. Or, you can use the pack method to convert an Ethernet packet into a bit-stream representation.
      To pack/unpack an object, you need to specify which properties of the object to pack/unpack. You can either use `uvm_field macros (which are not recommended due to an overhead) or write your own do_pack and do_unpack methods. If the bit-stream representation is crucial (for example, if you are creating an Ethernet bit-stream), it is almost certain that you create your own do_pack and do_unpack.
      When you pack a property that requires more than one bit to represent itself, the big_endian property of the uvm_packer determines the order of bits. Please see Pack in little endian and in big endian in this article.

Leave a Reply to Keisuke Shimizu Cancel reply

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