Wednesday, 8 November 2017

Timeunit and Timeprecision in SystemVerilog

Within a design element, such as a module, program or interface, the time precision specifies how delay values are rounded before being used in simulation.

The time precision is relative to the time units. If the precision is the same as the time units, then delay values are rounded off to whole numbers (integers). If the precision is one order of magnitude smaller than the time units, then delay values are rounded off to one decimal place.

For example, if the time unit specified is 1ns and the precision is 100ps, then delay values are rounded off to one decimal place (100ps is equivalent to 0.1ns). Thus, a delay of 2.75ns would be rounded off to 2.8ns.

The time unit and time precision can be specified in the following two ways:
— Using the compiler directive `timescale
— Using the keywords timeunit and timeprecision

Here in this post, we will go in details of second way (timeunit and timeprecision).

The time precision may also be declared using an optional second argument to the timeunit keyword using the slash separator. For example:

------------------------------------------------------------------------------
------------------------------------------------------------------------------

Defining the timeunit and timeprecision constructs within the design element removes the file order dependency problems with compiler directives.

There shall be at most one time-unit and one time-precision for any module, program, package, or interface definition or in any compilation-unit scope. This shall define a time scope.

The timeunit and timeprecision declarations shall precede any other items in the current time scope. i.e. timeunit and timeprecision shall be declared either inside module, program, package, interface or after any item in any compilation-unit scope.

The timeunit and timeprecision declarations can be repeated as later items, but must match the previous declaration within the current time scope.

If a timeunit is not specified within a module, program, package, or interface definition, then the time unit shall be determined using the following rules of precedence:
  1. If the module or interface definition is nested, then the time unit shall be inherited from the enclosing module or interface (programs and packages cannot be nested).
  2. Else, if a `timescale directive has been previously specified (within the compilation unit), then the time unit shall be set to the units of the last `timescale directive.
  3. Else, if the compilation-unit scope specifies a time unit (outside all other declarations), then the time unit shall be set to the time units of the compilation unit.
  4. Else, the default time unit shall be used.


The global time precision, also called the simulation time unit, is the minimum of,
  • All the timeprecision statements,
  • All the time precision arguments to timeunit declarations, and
  • The smallest time precision argument of all the `timescale compiler directives in the design.

Let's go through one example,
------------------------------------------------------------------------------
------------------------------------------------------------------------------

Reference:
1) SystemVerilog LRM (1800-2012)

Tuesday, 26 September 2017

Array Manipulation Methods in SystemVerilog with example

SV provides build in methods to facilitate searching from array, array ordering and reduction.

Array locator methods:

Array locator methods operate on any unpacked array, including queues, but their return type is a queue.

Element locator methods (with clause is mandatory):
find() returns all the elements satisfying the given expression.
find_first() returns the first element satisfying the given expression.
find_last() returns the last element satisfying the given expression.

Index locator methods (with clause is mandatory):
find_index() returns the indices of all the elements satisfying the given expression.
find_first_index() returns the index of the first element satisfying the given expression.
find_last_index() returns the index of the last element satisfying the given expression.

Index locator methods return a queue of int for all arrays except associative arrays, which return a queue of the same type as the associative index type.
arrays that specify a wildcard index type shall not be allowed.

If no elements satisfy the given expression or the array is empty (in the case of a queue or dynamic array), then an empty queue is returned.

Index locator methods return a queue with the indices of all items that satisfy the expression.

The optional expression specified by the with clause shall evaluate to a Boolean value.

Let's go through below example,
---------------------------------------------------------------------
---------------------------------------------------------------------

Element locator methods (with clause is optional):
min() returns the element with the minimum value or whose expression evaluates to a minimum.
max() returns the element with the maximum value or whose expression evaluates to a maximum.
unique() returns all elements with unique values or whose expression evaluates to a unique value.
unique_index() returns the indices of all elements with unique values or whose expression evaluates to a unique value.

Let's go through below example,
---------------------------------------------------------------------
---------------------------------------------------------------------

Array reduction methods:


Array reduction methods may be applied to any unpacked array of integral values to reduce the array to a single value

sum() returns the sum of all the array elements or, if a with clause is specified, returns the sum of the values yielded by evaluating the expression for each array element.

product() returns the product of all the array elements or, if a with clause is specified, returns the product of the values yielded by evaluating the expression for each array element.

and() returns the bitwise AND ( & ) of all the array elements or, if a with clause is specified, returns the bitwise AND of the values yielded by evaluating the expression for each array element.

or() returns the bitwise OR ( | ) of all the array elements or, if a with clause is specified, returns the bitwise OR of the values yielded by evaluating the expression for each array element.

xor() returns the bitwise XOR ( ^ ) of all the array elements or, if a with clause is specified, returns the bitwise XOR of the values yielded by evaluating the expression for each array element.

Let's go through below example,
---------------------------------------------------------------------
---------------------------------------------------------------------

Array ordering methods:

Array ordering methods reorder the elements of any unpacked array (fixed or dynamically sized) except for associative arrays.

reverse() reverses the order of the elements in the array. Specifying a with clause shall be a compiler error.

sort() sorts the array in ascending order, optionally using the expression in the with clause.

rsort() sorts the array in descending order, optionally using the expression in the with clause.

shuffle() randomizes the order of the elements in the array. Specifying a with clause shall be a compiler error.

Let's go through below example,
---------------------------------------------------------------------
---------------------------------------------------------------------


Now let's see couple of practical examples, let's say we want to find number of 1s from bit array or bit queue or we want to find out number of non-zero elements in any array or queue.
---------------------------------------------------------------------
---------------------------------------------------------------------

Sunday, 23 July 2017

Advantage of uvm_event over SV event and how to disable uvm_event using callback

The uvm_event#(type T = uvm_object) class is an extension of the abstract uvm_event_base class. The optional parameter T allows the user to define a data type which can be passed during an event trigger. uvm_event class is an abstract wrapper class around SystemVerilog event construct.

It provides additional services such as over traditional SystemVerilog event like,
1) pass data when event is triggered,
A traditional Systemverilog event does not have functionality to pass data when event is triggered. While uvm_event adds this functionality. So, you can pass the transaction class handle when some event is triggered.
By calling trigger task we can trigger the event and optionally give the data which we want to pass using uvm_event.
By calling wait_trigger()/wait_ptrigger() task of uvm_event we can wait for event to be trigger and then by calling get_trigger_data() function we can get data.
or we can directly use only one task wait_trigger_data()/wait_ptrigger_data() of uvm_event to wait for event to be triggered and to get the data.

2) setting callbacks,
We can also add callbacks whenever an event is triggered. This is done by registering a callback class with particular event.

3) maintaining the number of waiters,
We can get the number of processes waiting on the event (using get_num_waiters() function).

4) maintaining the time when event was triggered,
We can get the time that this event was last triggered (using get_trigger_time() function)

Like SystemVerilog event has trigger (@event) and persistent trigger (wait(event.triggered)) mode, uvm_event also has trigger (wait_trigger task) and persistent trigger (wait_ptrigger task).


Let's go through below example and see how we can transfer data using uvm_event and how we can disable uvm_event from triggering using callbacks of uvm_event.

-------------------------------------------------------------------
-------------------------------------------------------------------

-------------------------------------------------------------------
-------------------------------------------------------------------
As shown in above code, one uvm_event named PKT_TX_CMPLT_EV is taken in driver.
In build phase of driver we get global handle of event pool using static method get_event_pool of uvm_event_pool class.
Then PKT_TX_CMPLT_EV is added into associative array of uvm_event_pool using get/add method of uvm_event_pool. Note that here PKT_TX_CMPLT_EV event is added in associative array of uvm_event_pool using key (in string format) DRV_EVENT.
In run phase of driver when stimulus is driven, trigger method of uvm_event is called and transaction class is passed in argument of trigger method.

uvm_event also provides facility of callback when event is triggered.
In code my_event_callback (callback for uvm_event) class which extended from uvm_event_callback.
uvm_event_callback provides two hookups, 1) pre_trigger, 2) post_trigger.

pre_trigger:
Called just before triggering the associated event. If this function returns 1, then the event will not trigger and the post-trigger callback is not called.

post_trigger:
Called after triggering the associated event.

-------------------------------------------------------------------
-------------------------------------------------------------------
As shown in above code, in sequence, event callback is registered with associated event using add_callback method of uvm_event and also deleted using delete_callback method of uvm_event

-------------------------------------------------------------------
-------------------------------------------------------------------


FAQ:
Through uvm_event we can pass data(transaction class) when event is triggered, then why do we need TLM/Analysis ports in UVM?
Ans:
If event is triggered again before receiver gets the data then data will be overwritten.

Reset Testing using Phase Jump in UVM

Reset testing is a crucial element of functional sign-off for any chip. The architectural components of the entire verification environment need to be correctly synchronized to be made aware of the reset condition. Scoreboards, drivers and monitors need to be tidied up, and the complex stimulus generation needs to be killed gracefully.

As we know, in UVM, there are twelve phases parallel to run_phase:
  1. pre_reset_phase(), reset_phase(), post_reset_phase(): Phases involved in reset activity.
  2. pre_configure_phase(), configure_phase(), post_configure_phase(): Phases involved in configuring DUT.
  3. pre_main_phase(), main_phase(), post_main_phase(): Phases involved in driving main stimulus to the DUT.
  4. pre_shutdown_phase(), shutdown_phase and post_shutdown_phase(): Phases involved in settling down the DUT after driving main stimulus.

Using these phases instead of using only run_phase, we can achieve synchronization between all components of verification environment also easily test reset functionality.

In reset testing, user drives random sequence to the DUT and in between data transmission, reset is applied followed by driving restart sequence. We will see how the reset functionality could be easily tested using phases parallel to run_phase and phase jump feature of UVM.

Let’s go through complete example to understand how it is achieved using UVM phases and Phase jump feature.
----------------------------------------------------------------------
----------------------------------------------------------------------
----------------------------------------------------------------------
----------------------------------------------------------------------
As shown in above code,
Driver is waiting for Reset to be asserted (in reset_phase) by raising objection and then perform action which user want on assertion of Reset signal and at last drop the objection and move to post_reset_phase. In post_reset_phase driver is waiting for Reset to de-assert and then move to main_phase. In main_phase driver drives stimulus on interface and in parallel to that wait for indication from agent about assertion of reset.

Components such as monitors that attach to signaling interfaces should be designed to be phase independent because they are intended to mimic other real devices in the system. These components should watch the reset signal associated with their interface and reset themselves accordingly.

You may find that the driver, the sequencer, and their currently running sequences will squawk with errors if they are not synchronized properly. UVM requires that the sequencer first stop its sequences and then the driver must be certain to not call item_done on any outstanding sequences.  However, the order that a simulator executes threads in the various components is indeterminate. To synchronize these operations, the containing agent has a pre_reset_phase such as the above.

----------------------------------------------------------------------
----------------------------------------------------------------------
When test enters main_phase initially first time at that time run_count is 0, so on assertion of Reset test will do phase.jump method and move to pre_reset_phase from main_phase.
When test enters main_phase second time at that time run_count is 1, so at that time it will not do phase jumping.

Note: It is not good to use a phase jumping feature in case any of the components of testbench don’t use the sub-phases of UVM.

Reference:

Saturday, 22 July 2017

Extend run_phase using set_drain_time and all_dropped callback after all objection drops

It is more often that two or more components in verification environment are not in sync with each other. And there may be a case where driver/transmitter finish it's job first and call item_done. After item_done is called from driver and if there is no action pending in sequence then start method of sequence finishes and objection is also dropped.
But we want that simulation (more precisely we can say run_phase) should extend to some more time after all objection are dropped, so that other components which are late with respect to driver can finish it's job. We can achieve this by using set_drain_time in UVM.

Let's go through example and see how we can achieve this using set_drain_time in UVM.
--------------------------------------------------------------------

--------------------------------------------------------------------


UVM also provides callback hookups when every objection raises and drops and when all objection drops.
Let's go through example and see how it works,

--------------------------------------------------------------------

--------------------------------------------------------------------
--------------------------------------------------------------------

--------------------------------------------------------------------

Once all the objections are dropped, Drain time takes effect. After Drain time is finished, all_dropped callback takes effect.