Developing UVM Verification Environments Using DVT Eclipse IDE

Overview

This video walks through a broad range of DVT Eclipse IDE features withing the context of UVM testbench development: compilation problems reported on the fly, applying quick fixes, jumping to declaration, finding usages, browsing documentation in tooltips, expanding macros to name just a few.

Explore the design and verification tools: https://www.dvteclipse.com
Or request a license: https://www.dvteclipse.com/request-license

Details

Introduction

In the previous videos, we've walked through various UVM-specific functionalities, like the UVM Browser or the UVM Components Diagrams, and through a set of object-oriented capabilities, like the Type Hierarchy or the UML Diagrams. In this video, we'll focus mainly on writing, fixing, and navigating UVM testbench code with ease.

Real-Time Error Detection with the Built-In Incremental Compiler

Let's start from the very basics. The built-in incremental compiler analyzes source code on the fly, so any errors that might creep into your code are reported back immediately. You don't waste time waiting for the simulator to compile all your code just to find out a missing semicolon, and you don't waste time grepping through logs to find the source of the problem. This type mismatch is a good example taken from a UVM testbench — a rather common and hard-to-spot error that DVT catches at type time. The same goes for this method prototype and implementation mismatch, this undeclared function call, or this typo. Precious debug time is saved by having such problems reported so early in the flow.

Errors and warnings are shown to the left side of the editor, while the affected section of code is underlined for maximum readability. All the errors and warnings from the entire project are gathered in the Problems View. From here, you can easily navigate to any problem in your code.

Of course, DVT's compiler treats your project as a whole, analyzing not only the changed files, but also the dependencies that might be affected by these changes. Let's take the example of an error propagation within the inheritance tree. When deleting the argument of this constructor from the apb_config class, two new errors have been introduced: one where the super constructor is expecting a string argument but receives an undeclared identifier when invoked, and another error in the default_apb_config class, which extends the class where we've made the change. Some apparently harmless edits in a file may lead to instant feedback via the Problems View and the file decorations that something got broken elsewhere as a side effect.

Non-Standard Constructs Detection

It's also worth mentioning that DVT understands constructs which are accepted by various simulators, although not described by the IEEE standard, and flags them as non-standard. For example, let's remove the return type of this build_phase() function. DVT's internal on-the-fly compiler immediately reports a NON_STANDARD warning on this line, since omission of the return type in an extern function declaration is not allowed by the IEEE standard. Keeping your codebase standard compliant is good practice and will save a lot of time in case you ever need to add another SystemVerilog tool to your flows or switch to a different vendor.

Quick Fix Proposals and Error Corrections

Now that we have a couple of errors in our code, it's time to see how DVT can help with fixing. For example, if we use the Ctrl+1 keyboard shortcut on the line with the non-existing type error, a list of Did you mean quick fix proposals pops up. You can navigate through the list of proposals using the keyboard Up and Down arrows. As soon as you hit Enter while on the convenient proposal, the error gets fixed. Notice that the places where the problems are reported, such as the editor and the Problems View, are instantly updated as well.

You can also trigger the quick fix proposals from tooltips. To do so, simply hover over the problematic identifier.

Now have a look at this extern implementation of the connect_phase() function, not matching the prototype's number of arguments. Here we can choose to automatically update the implementation to match the prototype.

Especially when dealing with complex UVM testbenches, being able to automatically correct such compilation errors can help you focus on the really important tasks and increase overall efficiency.

Error Resilience in DVT Eclipse IDE

Another very important aspect of DVT's built-in compiler is the ability to survive in projects affected by errors. The tool was designed from the ground up with error resilience in mind. If you think about it, help is most needed when the code is either incomplete, like it happens in early development stages, or incorrect, for example during major overhauls. Nevertheless, DVT's functionality is largely not affected by errors, and actually we'll leave some of the errors in place during the remainder of this presentation.

Navigating and Refactoring Extern Methods

In UVM testbenches, it is quite a common practice to use extern methods. Even UVM base classes make extensive use of this encapsulation technique. You can easily navigate between the implementation of an extern function and its prototype by using hyperlinks. To do so, let's hover over the connect_phase() function name while keeping the Ctrl key pressed. We can also use the hyperlinks to get back to the implementation.

Should we ever need to switch between the two coding styles, we can use the corresponding refactoring operations that DVT provides. To do so, we place the cursor on the function name, right-click, go to the Refactoring menu, and select the Join Extern and Implementation entry. Or, vice versa, by using the Split to Extern and Implementation refactoring option.

Implementing Pure Virtual Methods

When it comes to complex UVM testbenches, fixing issues related to inheritance and virtual classes may become cumbersome. In the following example, we'll see how DVT can come up to help. Notice the NOT_IMPLEMENTED_PURE VIRTUAL error in the Problems View. Let's fix this issue by triggering the Implement Missing Pure Virtual Methods quick fix proposal. The stubs for the methods get instantly generated in the editor along with some TODO task tags in order to stress that an actual implementation is to be done here. All the task tags are collected in the Tasks View. Here we can notice this high-priority task.

Resolving Missing Imports

Sometimes you might come across classes which are not visible in the current scope. For example, let's remove the ahb_pkg import. Notice that several new errors are reported in the Problems View. We can easily get rid of them by using the import quick fix proposal.

Using Tooltips for In-line Documentation

We've previously seen that you can easily trigger the quick fix proposals from the tooltips by simply hovering over a problematic identifier. But the tooltips are also very useful when you want to explore the documentation of your code. DVT supports both Javadocs and Naturaldocs syntaxes, giving the possibility to create nicely rendered rich HTML documentation.

Now, let's take a look at the ahb_master_driver from our project. If we hover over the uvm_driver, we'll get information about this UVM library class in place, and we stay focused without any context switching. By the way, the information presented by the tooltip is automatically extracted from code, in this case from the sources of the UVM library itself. In fact, DVT conveniently associates any element defined in your code with the comment above or to the side of its declaration. No pragmas, no special coding style is needed, so no effort to have inline documentation in tooltips. It will just work with what you have.

Using the tooltip hyperlinks can also be very useful when exploring documentation. You can easily navigate through the documentation of classes or functions.

Working with Macros

Tooltips are also very useful when you need to find out fast the value of a macro. Let's take the example of the UVM_REG_DATA_WIDTH. To see its value, simply hover over the macro call in the editor. By the way, you can also use the Macros View and the powerful DVT filtering capabilities to get more information about a specific define.

When working with macros, the Inspect View is also a good candidate for coming up with help. Notice that from here we can easily navigate to the macro definition or to see the value of the macro in various bases.

Now let's take one more example, this time a macro with parameters and a hefty expansion. Have a look at this UVM field registration. In such a case, the Inspect View is very useful to quickly have a look at the macro definition, the macro call, and the macro expansion with all preprocessing applied.

You also have the possibility to expand the macro or even apply preprocessing for an arbitrary code section directly from the editor. To do so, select a piece of code containing preprocessing macros, right-click, and go to the Macros submenu. From here, you have various expansion options. Let's choose to Expand in-line all levels of preprocessing. When done, you can use the indicator from the left side of the editor to collapse the macro expansion.

Inspecting Random Variable Constraints

A common operation done when working with UVM testbenches is inspecting variables randomization. If we keep the Ctrl key pressed while hovering over the transmit_delay random variable, we can use the Show Constraints hyperlink for a better understanding of the variable randomization. All the constraints from our entire UVM testbench in which this particular variable is involved are displayed in the Search View.

We can expand all the entries and navigate through the matches using the Up and Down arrow toolbar buttons. All the places where this variable is constrained are shown, like the uvm_do_with macro call, static constraints, and even function calls to rand_mode() and constraint_mode(), which affect the variable randomization.

Finding Usages

Many times you wonder, where is this function called? Where is this variable written? Or where is this UVM component created? And the answer is Show Usages. Let's take the case of the sequencer from the uvm_sequence_item class. Keep the Ctrl key pressed while hovering over the m_sequencer and then select the Show Usages hyperlink. From the Search View, you can now navigate through all the usages of this field.

If you want to see only the readers or only the writers of the field, you can choose one of the according usages subsets.

Using Favorite Searches

The Favorite Searches engine can help you quickly answer some questions which come up when debugging complex UVM testbenches. Are there any leftover debug printouts that use $display() instead of UVM reporting macros? Where are all the config_db getter and setter calls? Where are all the UVM reporting macro calls? Or where are all the UVM factory overrides?

If you want to focus on some specific matches, you can further filter the search results.

You can also define your own favorite searches. Let's create one for calls to raise_objection() and drop_objection(). To do so, go to Customize, New, fill in the name, select the language, press OK, and then enter the fully qualified names of the elements you'd like to search. Then simply click the Save and Run button.

By the way, if you want to quickly grab the fully qualified name of an element, you can use the scope breadcrumb navigation bar. Right-click on an element, and then simply select the Copy Qualified Name option.

Navigating with Breadcrumbs

The Breadcrumb Navigation Bars are useful especially when the editor is maximized and the full-blown hierarchical views are not at your fingertips. Particularly, the Scope Breadcrumb always shows the current cursor context, such as the enclosing package, class, and function. It also helps you instantly navigate to a particular construct that you are interested in.