There are times when a signal within the design has to be probed or monitored for certain tasks in the testbench. Typically these are accessed via hierarchical references and tend to break when the same code is ported to different projects because of changes in design hierarchy. UVM has a set of DPI implementation tasks for backdoor accesses that has a similar effect and achieves much better code reusability.
UVM sequence macros are a great way of reducing code and hiding away some details.
`uvm_do macros enable a sequence item to be created, randomized and executed on a sequencer all from a single line of code.
`uvm_create is another macro which simply creates an object of a sequence item so that it can be handled later on. Let's see what the name of an object created by
`uvm_create would look like. Unlike a typical
type_id::create() method where you get to specify a required name,
`uvm_create does not have any, not that it matters, but just for trivia.
One of the most commonly asked questions in System Verilog for interviews is how to uniquely constrain any given array, or any two variables. There are a couple of workarounds for this, but we didn't have a clean shortcut to achieve this very useful functionality until the 2012 IEEE revision.
Constrained randomization is a powerful feature of System Verilog that are generally applied to members of class objects which can be later extended, inherited and overridden. Sometimes we do not need the full blown feature set provided by classes to perform simple variable randomizations and would probably hesitate to create a class structure just to hold such variables. A simpler mechanism to randomize data that do not belong to a class is provided by the scope randomization function
Many a time I have written functions that end up having only a single line of code in it and wished for a better alternative. System Verilog (1800-2009) has a construct called
let that defines a template expression that can be used for customization and text replacement.
In the UVM world there exists a function to reset a register block within a model. This is a step that many beginners often overlook because the need to invoke this might not be very clear until errors crop up. The register model primarily holds three different kinds of values each for a different purpose. Let's see how the
reset() method affects each of them.
UVM register model is quite extensive and has many useful API that help query registers and fields based on their names. Typically, registers are accessed by hierarchical references and there may be a better alternative in cases where there is a consistent naming scheme applied to all registers in the model.
Creating a global singleton object that can be referenced from elsewhere in the testbench is sometimes a good thing. This is very similar to the way
static variables in a class work - only one variable is created and made accessible for all class objects. In this case, a single class object is created that can be accessed from other testbench components. As an example, you could have such a class object to contain all the design or testbench specification features like number of masters and slaves, or clock frequency requirements for each interface, etc. Let's see how to effectively create a singleton object.
Yes. UVM has a class-based dynamic queue that can be allocated on demand, passed and stored by reference. Eventhough
uvm_queue is a parameterized class extended from
uvm_object, it is not registered with the factory and hence invocation of
new() function is the correct way to create a queue object.
At times we might need to accept values from the command line to make our testbench and testcases more flexible. UVM provides this support via the
uvm_cmdline_processor singleton class. Generation of the data structures which hold the command line arguments happen during construction of the class object. A global variable called
uvm_cmdline_proc is created at initialization time which can be used to access command line options. Let's see more on how this feature can be used.
There are primarily two ways to start a sequence : use a
`uvm_do macro, or use the
start() method. If you have read How to execute sequences via `uvm_do macros ?, you might already know that
`uvm_do macros eventually call the
start() method, and the macros act as a wrapper to execute both data items and sequences on the default sequencer "m_sequencer".
UVM has this nice feature of being able to print the line number and file name from where a reporting task is called. This is very helpful during the early days of testbench debug, but it can soon clutter the log reports. Just imagine having the file name occupy most of the screen space, true in most projects because of the long path to a file, only to make it difficult for you to find the actual report message. Good News ! There's a way to disable this.
One of the main features of UVM is the factory mechanism, and we already know how to use
`uvm_component_utils () and
`uvm_object_utils () within user-defined component and object classes. It's a way of registering our new component with the factory so that we can request the factory to return an object of some other type later on via
type_id::create () method. Let's see what happens behind the scene when the code is elaborated and compiled for the example that follows.
An agent is a hierarchical block which puts together other verification components that are dealing with a specific DUT interface. It usually contains a sequencer to generate data transactions, a driver to drive these transactions to the DUT, and a monitor that sits on the interface and tries to capture the pin wiggling that happens on the DUT interface. So, in a typical UVM environment there'll be multiple agents connected to various interfaces of the DUT. Sometimes, we do not want to drive anything to the DUT, but simply monitor the data produced by DUT. It would be nice to have a feature to turn the sequencer and driver of an agent ON and OFF when required.
There are ocassions when some components in the testbench keep running forever and cause the simulation to hang. Another case is when performing SoC level C tests, where you could have written a
while (1) code expecting an interrupt to cause the loop to break but, instead not get the interrupt at all. Not a good place to be in, especially if you tried running it in your local machine instead of an LSF farm. Let's look at what UVM has to offer to get around this.
Tests can be run in a UVM environment by either specifying the testname as an argument to
run_test() or as a command-line argument using
+UVM_TESTNAME="[test_name]". This can be considered an entry point to how UVM starts each component, configures and runs a simulation. There are a set of UVM core services within the structure, capable of providing instances to the factory and the root object. We'll see how the general flow looks like in the short explanation that follows.