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.
- Create three
jelly_bean_transactions;jb_tx1,jb_tx2andjb_tx3(lines 11 to 13). - Randomize the
jb_tx1(line 14). - Copy the properties of the
jb_tx1tojb_tx2(line 18). - Copy the properties of the
jb_tx1tojb_tx3, but using thepack()andunpack()methods instead of calling thecopy()method (lines 24 and 25). - Now the
jb_tx1,jb_tx2, andjb_tx3should have the same property values. Verify it using thecompare()method (lines 29 to 39). - Print the properties of the
jb_tx1,jb_tx2, andjb_tx3using theconvert2string()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.

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.
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
Can you elaborate the problem with “compare”?
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
As an example, let’s assume that the
jelly_bean_transactionhas a handle to ajelly_bean_ingredient.If you do a shallow-copy, you would write:
In this case, both
thisandthatpoint to the same ingredient object.On the other hand, if you do a deep-copy, you would write:
In this case,
thisandthatpoint to different ingredient objects even though the values ofsugar,corn_syrup, andstarchare the same. You could also do:to delegate the copy to the ingredient object if it properly implements its own
do_copy. Hope this helps.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) The monitor should collect the bit-stream on the bus to a dynamic array of the
bittype (say,bit mon_bitstream[]) until 32-bit data has collected. Then, unpack the bit-stream into the five fields by callingyour_transaction.unpack( mon_bitstream ).2) The driver should have a similar dynamic array of the
bittype (say,bit drv_bitstream[]). When the driver receives a transaction, callyour_transaction.pack( drv_bitstream )to fill the bit-stream.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,
The
packer.unpack_field_int()returns the type ofuvm_integral_t, which istypedef-ed tologic signed[63:0]. Since thetasteis anenumtype, which is not equivalent to theuvm_integral_t, we need the cast to convert the type. On the other hand, the type ofR6isbit[5:0]. SystemVerilog automatically converts thelogic signed[63:0]tobit[5:0]without having a cast.Consider adding ‘do_print’ to this example.
convert2string is nice in that its flexible. But do_print offers a mechanism to print (and sprint) that is in-line with how we implement the other do_ methods. Most important (IMO) the uvm_printer policy object.
Something like what is found here:
https://www.vmmcentral.org/uvm_vmm_ik/files3/base/uvm_object-svh.html#uvm_object.do_print
I wrote a new article about
do_print. Thanks for your comment.pack return unsigned value,if I want return signed value ,what can I do
uvm_object::packreturnsintwhich is 32-bit signed integer.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 endfunctionand 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 endbut the result is not right , the wdata[i] value is wrong,I don't know where I am wrong,please help me.
thanks!
I’m not 100% sure what you are trying to do, but if you want to pack your transaction into the
bitstreams[], yourdo_packshould be like this:And the sequence should be like this:
Let me know if I misunderstood your question.
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!
Now I know your question. Basically your
do_packlooks fine, but the sequence should be like this: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!
The
uvm_packerdetermines how to pack and unpackuvm_objects, but it does not carry the bitstream itself. You have to pass (copy or pass by reference) thebitstreamfrom one class to another.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.
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.
Typically you need only one
uvm_packerunless you want to use different policy. You can also declare thebitstreamonce and reuse it four times.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
A driver could pack a class object into an Ethernet frame, for example. Pack/unpack can be used to encode/decode CPU instructions, too.