Mastering the fundamentals of this verification standard.
One of the reasons I like using UVM is its tendency toward an organized structure and uniformity. Some may find it annoying to adhere to such a strict format in UVM, but I think it’s a good way to keep the basics of UVM engrained in your brain. You always want a good foundation and development of strong fundamentals in any endeavor. Verification is no different and UVM hammers the fundamentals home.
UVM has a great structure and organization paradigm. I consider there to be two distinct and fundamental elements in the UVM structure: Components and Objects. Now this characterization isn’t strictly correct because uvm_components are extended from uvm_objects, but I think they are used in such a way that warrants the distinction. I consider it similar to the idea of trucks and cars. In my view, trucks are also cars, but it’s useful to note the difference.
Generally, the components make up the static parts of the UVM structure. I like to think about components as the walls and rooms of a house. They divide the home into different parts with different purposes. Similarly, components divide the verification process into different roles. Doors allow passage from one room to the next just as the ports of components allow for information transfer between components.
Components consist of common methods similar to how many different rooms in a house contain similar features: lights, windows, or outlets. Many of the shared major parts or methods among UVM components are:
Factory Registration and handler instantiations
When you register something with the factory using the `uvm_component_utils() macro, you are entering it into a lookup table where you will be able to access it later. Using this registration functionality makes it possible to override components in the future when developing advanced tests, but that is beyond the scope of what I am discussing here. Handlers are needed to hold the instantiations of components and are typically created after the factory registration.
A staple of object-oriented programming, these are methods to create instantiations of classes.
The build phase is where the walls and rooms of the house are erected. In this phase, the components are being built and the house is taking shape.
This is where you start to install the doors to connect different rooms together. This method deals with linking components together in order to allow the passage of transactions. TLM concepts are heavily used in UVM and it is most evident here. TLM allows the abstraction of communication between components, thus facilitating reuse of component code and easy rerouting between components.
This is where the fun starts. I can’t think of a great equivalent to the house analogy, but basically the run_phase is where movement starts to take place. Transactions are passed around and time starts to elapse. Information is taken from or put into transactions as they make their way through different components.
A basic travel plan for a transaction might include:
An important feature of UVM that exists in the run_phase is objections. Objections are the brilliant ways in which UVM decides when tests are finished. I think of objections as flags that are raised when the component wants to do something. When the component is done with whatever it was doing, it drops the objection and the test is then finished when all objections are dropped.
Objects are the moving parts of the UVM verification structure. They carry information around the structure to and from different components. When I think of objects, I think mainly of transactions and sequences. Although configuration and register objects may also fit into the category, they are more advanced concepts and not what I am referring to. Sequences and transactions consist of common parts and methods amongst each other:
Similar to component factory registration, sequences and transactions are also registered with the factory using the `uvm_object_utils() macro.
Variable and constraint declarations
The stimulus variables are developed here. Generally the test stimulus variables are declared, then constraints are put on them in order to push the design in interesting scenarios.
There is nothing too special about object constructors. They’re purpose is similar to component constructors, but object constructors don’t have a parent argument in the function.
Body functions are usually found in sequences and are similar to the run_phase of a component given that both consume time. The body of a sequence might be used to create multiple transactions while varying the value of variables in those transactions.
Suffice it to say that this is not an exhaustive list of all UVM has to offer, but I hope it gives some clarity into the basic functionality behind UVM and allows new users to get started quickly. For more information on UVM, users can visit our website and take the Fast Track To UVM Training.