Over a number of projects I've run into many different needs and issues regarding time component definitions. I've concentrated my findings into a paper which gives a general background to the reader on time components, describes many issues which can be encountered and how to avoid pitfalls.
I am sharing this document for the greater good in hope that it may help others avoid many issues on this subject. Enjoy!
Link to paper
Wednesday, July 20, 2016
Wednesday, May 4, 2016
Using uvm_object Hook Methods to Implement User Specific Functionality
It is sometimes necessary to modify or add functionality to existing built-in methods in uvm objects. Numerous methods in the uvm object class have build-in hooks called do_<method name> allowing user to extend existing implementations of the <method name>. These hooks are implemented as empty methods in the uvm_object class type and can be overloaded in a derived class with user specific functionality. Each hook is called automatically by its respective default method call.
But what happens when the default implementation cannot be used due to a corner case or if there is a special implementation requirement? Since the default methods are non-virtual they cannot be overloaded in derived classes.
One way to work around this is to create a new unrelated method, lets call it "my_compare", and call this instead of the default method call. Using this type of workaround should be considered only as a last resort as it can hinder reuse of existing or future code. For example, if the class is an inheritance of a sequence item and factory replaced, a "my_compare" implementation might limit an existing scoreboard receiving and checking these items with the default compare method.
Fortunately, the uvm framework has considered this use case where the default implementation should be circumvented while allowing the user defined code to run from the hooked method.
Using the compare function as our example, class members var1 and var2 in the code snippet above are registered to be ignored when calling the default compare method. The do_compare hook is still executed by the default method call executing the user specific code. This ability allows a call to the default compare method without any need to know that the inherited object has a specialized functionality implemented or that the compare method implementation is circumvented.
function bit uvm_object::compare (uvm_object rhs, uvm_comparer comparer=null); . // UVM implementation here . dc = do_compare(rhs, comparer); // call to hook . .
But what happens when the default implementation cannot be used due to a corner case or if there is a special implementation requirement? Since the default methods are non-virtual they cannot be overloaded in derived classes.
One way to work around this is to create a new unrelated method, lets call it "my_compare", and call this instead of the default method call. Using this type of workaround should be considered only as a last resort as it can hinder reuse of existing or future code. For example, if the class is an inheritance of a sequence item and factory replaced, a "my_compare" implementation might limit an existing scoreboard receiving and checking these items with the default compare method.
Fortunately, the uvm framework has considered this use case where the default implementation should be circumvented while allowing the user defined code to run from the hooked method.
class my_sequence_item extends uvm_sequence_item; rand logic [31:0] var1; rand logic [7:0] var2; `uvm_object_utils_begin (my_sequence_item) `uvm_field_int( var1, UVM_ALL_ON | UVM_NOCOMPARE) `uvm_field_int( var2, UVM_ALL_ON | UVM_NOCOMPARE) `uvm_object_utils_end extern virtual function bit do_compare(uvm_object rhs,uvm_comparer comparer); endclass: my_sequence_item //------------------------------------------------------------------------// function bit my_sequence_item::do_compare(uvm_object rhs,uvm_comparer comparer); . // implementation here . endfunction: do_compare
Using the compare function as our example, class members var1 and var2 in the code snippet above are registered to be ignored when calling the default compare method. The do_compare hook is still executed by the default method call executing the user specific code. This ability allows a call to the default compare method without any need to know that the inherited object has a specialized functionality implemented or that the compare method implementation is circumvented.
Monday, March 7, 2016
Connecting Bi-directional DUT Ports to an Interface
It isn't rare to find SOC top levels designs with bi-directions pins. It also isn't rare to use purchased VIPs to verify protocols requiring bi-directional connectivity.
VIPs come with pre-coded interfaces which are required to be used by the integrator. There are a number of different interface coding methods which include bi-direction ports and require different wire-up connection methods to a DUT. This post presents different interface/DUT connection implementations and to limitations found in each different methodology.
VIPs come with pre-coded interfaces which are required to be used by the integrator. There are a number of different interface coding methods which include bi-direction ports and require different wire-up connection methods to a DUT. This post presents different interface/DUT connection implementations and to limitations found in each different methodology.
Interface Nets Defined in its Port List
When the interface is coded with connection nets in its port list, a connecting wire between the interface and DUT instances is sufficient to create a connection between the two.
An interface with nets defined in its port list gives the most flexibility for wire-up connections. In some cases the connection could be simplified even more if the DUT and interface have the same port names by using the ".name" or ".*" conventions described in sections 23.3.2.3 and 23.3.2.4 of the SV2012 LRM. However, when using third party IPs and VIPs, the probability of design and interface ports having the same naming convention is not high and cannot be expected.
This implementation has a limitation: if the DUT is auto-instantiated, the connection to the DUT can be overridden when modifications are made to the port list.
interface some_interface(inout wire bidir1, inout wire bidir2, input logic in1 ); endinterface: some_interface //----------------------------// module tb(); wire sig1; wire sig2; wire sig3; . . some_interface s_if( .bidir1(sig1), .bidir2(sig2), .in1( sig3) ); dut my_dut(.sig1(sig1), .sig2(sig2), .sig3(sig3), . . ); endmodule: tb
An interface with nets defined in its port list gives the most flexibility for wire-up connections. In some cases the connection could be simplified even more if the DUT and interface have the same port names by using the ".name" or ".*" conventions described in sections 23.3.2.3 and 23.3.2.4 of the SV2012 LRM. However, when using third party IPs and VIPs, the probability of design and interface ports having the same naming convention is not high and cannot be expected.
Interface Nets Defined in the Interface
Another popular method is to define the interface nets within the interface and then create a connection via a cross module reference between the DUT port and the interface signal. A number of different connection implementations for this coding method are detailed in the subsequent sections.
Use Assign Statements in the TB
Connect the nets by assigning either the DUT or interface with the directional continuous assign statement. This method is feasible for directional nets, in or out, but will not suffice for a bi-directional net as only one of the sides can be the driver.
interface some_interface(); wire bidir1; wire bidir2; logic in1; endinterface: some_interface //----------------------------// module tb(); wire sig1; wire sig2; wire sig3; . . some_interface s_if(); dut my_dut(.sig1(sig1), .sig2(sig2), .sig3(sig3), . . ); assign s_if.in1 = sig3; // Can't connect bi-direction ports using this method! //assign bidir1 //assign bidir2 endmodule: tb
Direct Instantiation of interface signals to the DUT Instance
A solution which works for both directional and bi-directional port types is to embed the interface nets to the DUT instance ports using cross module references.
interface some_interface(); wire bidir1; wire bidir2; logic in1; endinterface: some_interface //----------------------------// module tb(); some_interface s_if(); // can be an issue for auto-instatiated DUT dut my_dut(.sig1(s_if.bidir1), .sig2(s_if.bidir2), .sig3(s_if.in1), . . ); endmodule: tb
This implementation has a limitation: if the DUT is auto-instantiated, the connection to the DUT can be overridden when modifications are made to the port list.
Connecting Ports to Interface nets with Verilog Primitives
Verilog provides many primitives to model hardware behavior. These primitives, used in the TB, can bridge between the DUT and interface internal nets including bi-directional ports. The Verilog primitive tran can create connections which allow bi-directional drivers.
interface some_interface(); wire bidir1; wire bidir2; logic in1; endinterface: some_interface //----------------------------// module tb(); wire sig1; wire sig2; wire sig3; some_interface s_if(); dut my_dut(.sig1(sig1), .sig2(sig2), .sig3(sig3), . . ); // create connection with tran primitive tran(s_if.bidir1, sig1); tran(s_if.bidir2, sig2); // directional nets can use continuous assign statements assign s_if.in1 = sig3; endmodule: tb
Controllable Connectivity
Sometimes it is necessary to create different connections for pins with multiple functionality. The Verilog primitives of tranif0 or tranif1 provide a controlled connection to the DUT. These primitives behave the same as the tran primitive with the exception of an enable input connecting or disconnecting the terminals accordingly.interface some_interface(); wire bidir1; wire bidir2; logic in1; endinterface: some_interface //----------------------------// module tb(); wire sig1; wire sig2; wire sig3; wire s_if_control = 1; // default is to short the terminals some_interface s_if(); dut my_dut(.sig1(sig1), .sig2(sig2), .sig3(sig3), . . ); // create connection with tranif1 primitive tranif1(s_if.bidir1, sig1, s_if_control); tranif1(s_if.bidir2, sig2, s_if_control); // directional nets can use continuous assign statements assign s_if.in1 = sig3; endmodule: tb
Thursday, January 28, 2016
Getting the most out of the UVM messages
Messages fill our log file with important information regarding test simulation results. Often this information is used to debug issues and/or report the status of some toggled functionality. Making sure the information added to the log is exact can greatly reduce debug time. I'd like to take a look at the uvm_info macro and suggest a method to receive the most possible information from each reported message.
In both cases the same pointer seq was run on the same agent sequencer but the factory override was reported in the log by the get_type_name()function call. Without any need for review, it is apparent which object type was executed and that the factory was successful in overriding the requested type. Since debugging factory issues can be somewhat hairy, there is an enormous amount of important information provided with this methodology.
Message Macro Format
`uvm_info(ID,MSG,VERBOSITY)
Verbosity
The verbosity selects importance of the message. A verbosity of UVM_NONE is an important message with the highest priority and should always be printed where UVM_DEBUG is the least important message with the lowest priority which should only be printed for deep debugging sessions. If a low priority verbosity is set for default messages, the following can occur:
- Users will run tests with a low verbosity priority causing many unwanted messages to be printed to the log. This setting can potentially make the log extremely hard to to review when debugging a failure.
- Users will leave the verbosity at its higher priority level and many important messages won't be printed to the log. When a failure occurs it can be hard to debug since messages pointing to the failure have been obfuscated.
MSG
This is the heart of your display message, here you insert the message you wish to display. Wrapping the message string in a $sformatf system call allows embedding variables into the message string so highly consider using this system task for all message strings created, even if you don't need to print a variable for a specific message.
ID
This ID field is used to define the type of message printed. I've seen many examples where a simple string is inserted here such as "CFG" or "SEQ". These strings are obscure, misleading and often become irrelevant since messages are copy-pasted from one block to another. Many times the user doesn't update the string for a new block and this field looses its purpose. A much better approach for this field is to use the objects function get_type_name() defined in the uvm_object class. This function reports the object type in use, which becomes extremely useful with factory overrides.
Lets see an example of a log output with a sequence running natively and then with a factory overridden type:
Native sequence message:
UVM_INFO @ 200812.5ns: uvm_test_top.env.agnt.seqr@@seq [my_sequence] msg 1
Factory overridden message:
UVM_INFO @ 200812.5ns: uvm_test_top.env.agnt.seqr@@seq [my_inherited_sequence] msg2
Native sequence message:
UVM_INFO @ 200812.5ns: uvm_test_top.env.agnt.seqr@@seq [my_sequence] msg 1
Factory overridden message:
UVM_INFO @ 200812.5ns: uvm_test_top.env.agnt.seqr@@seq [my_inherited_sequence] msg2
In both cases the same pointer seq was run on the same agent sequencer but the factory override was reported in the log by the get_type_name()function call. Without any need for review, it is apparent which object type was executed and that the factory was successful in overriding the requested type. Since debugging factory issues can be somewhat hairy, there is an enormous amount of important information provided with this methodology.
Other Messaging Macros
So what about the other messaging macros such as uvm_warning, uvm_error and uvm_fatal? These messages are a subset of the uvm_info with the verbosity set to UVM_NONE.
The ID and MSG fields are used in the same format with these macros as with the uvm_info allowing the same implementation as described above.
The ID and MSG fields are used in the same format with these macros as with the uvm_info allowing the same implementation as described above.
Subscribe to:
Posts (Atom)