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_transaction
s;jb_tx1
,jb_tx2
andjb_tx3
(lines 11 to 13). - Randomize the
jb_tx1
(line 14). - Copy the properties of the
jb_tx1
tojb_tx2
(line 18). - Copy the properties of the
jb_tx1
tojb_tx3
, but using thepack()
andunpack()
methods instead of calling thecopy()
method (lines 24 and 25). - Now the
jb_tx1
,jb_tx2
, andjb_tx3
should have the same property values. Verify it using thecompare()
method (lines 29 to 39). - Print the properties of the
jb_tx1
,jb_tx2
, andjb_tx3
using 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_transaction
s 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_transaction
has a handle to ajelly_bean_ingredient
.If you do a shallow-copy, you would write:
In this case, both
this
andthat
point to the same ingredient object.On the other hand, if you do a deep-copy, you would write:
In this case,
this
andthat
point to different ingredient objects even though the values ofsugar
,corn_syrup
, andstarch
are 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
bit
type (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
bit
type (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 thetaste
is anenum
type, which is not equivalent to theuvm_integral_t
, we need the cast to convert the type. On the other hand, the type ofR6
isbit[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::pack
returnsint
which 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,
and I called the pack function in the sequence.
but 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_pack
should 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_pack
looks 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_packer
determines how to pack and unpackuvm_object
s, but it does not carry the bitstream itself. You have to pass (copy or pass by reference) thebitstream
from 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_packer
unless you want to use different policy. You can also declare thebitstream
once 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.