Accelerating Hardware Verification Using DVT IDE for Visual Studio Code

Overview

DVT IDE for Visual Studio Code is an integrated development environment that significantly improves productivity of hardware design and verification engineers who are working with languages like Verilog, SystemVerilog, or VHDL. This video focuses on how verification engineers can benefit from the features provided by DVT.

00:00 Introduction
06:35 DVT Compilation & Quick Fixes
13:25 UVM Testbench Exploration
21:51 Code Navigation
27:05 IntelliSense (Content Assist)
33:17 Code Formatting & Refactoring
36:43 Tasks (External Tools)
37:57 Source Code Management
38:59 Contact Us

DVT IDE for Visual Studio Code: https://dvteclipse.com/products/dvt-ide-for-visual-studio-code
Visual Studio Code: https://code.visualstudio.com/

You can install DVT IDE from Visual Studio Marketplace using ext install amiq.dvt command.

Visual Studio Code documentation resources:
Cheatsheet: https://code.visualstudio.com/docs/getstarted/keybindings#_keyboard-shortcuts-reference
Basic Editing: https://code.visualstudio.com/docs/editor/codebasics
Extension Marketplace: https://code.visualstudio.com/docs/editor/extension-marketplace
Version Control: https://code.visualstudio.com/Docs/editor/versioncontrol
Tasks: https://code.visualstudio.com/Docs/editor/tasks

This video was shot using DVT 22.1.20

Details

Introduction

DVT IDE for Visual Studio Code is an integrated development environment, that significantly improves the productivity of hardware design and verification engineers, who are working with languages like Verilog, SystemVerilog, or VHDL. This video shows how verification engineers can be more productive using the features provided by DVT.

First, we'll cover some VS Code basics. Then, we'll see how DVT analyzes code as you type in order to provide instant compilation errors, and also how DVT can help with fixing all sorts of problems. We'll then talk about exploring the UVM test bench using structural views and diagrams, and about seamless navigation around code. Next, we'll cover how context-sensitive autocomplete, code formatting, and refactoring can help you write high-quality code fast. In the end, we'll catch a glimpse of integrating with external tools and source control management.

VS Code Basics

Let's start with a brief overview of the VS Code GUI features. The user interface is divided into five areas: Editor, Sidebar, Activity Bar, Status Bar and the Panels area

The editor is where you spend most of your time coding. On the right there is a Minimap, a high-level overview of the file content useful for quick navigation within the file. Another navigation bar, the breadcrumb, sits above the editor's contents. It shows the current location and allows you to quickly navigate between folders, files, and the elements or symbols defined within the current file. Editors can be freely split and moved around.

Any basic text editing operation you would expect from a text editor is there, including vertical selection, multiple cursors, code folding, find and replace with regex across multiple files, and so on. However, we will not cover such functionalities but rather focus on DVT-specific features. Check the VS Code User Guide section that covers basic editing.

The Sidebar shows different views that assist you while working on your project. The views presented in the Sidebar change any time you use the Activity Bar. When selecting the Explorer activity, the Sidebar presents a file browser and the content outline of the currently edited file. Particularly, for a SystemVerilog verification file, we see the classes, functions, fields, and so on. When selecting the Source Control activity, the Sidebar presents revision control changes. When selecting the Extensions activity, the Sidebar presents the installed and recommended extensions. Now, since we're here, it is worth mentioning that you can search and install extensions, for example, for emulation modes like VI, Emacs, or Eclipse.

In the Activity Bar, you will also find the DVT activity, which provides various compilation-related views, as well as the design and verification hierarchies. We'll talk about them in more detail later.

The Status Bar, located at the bottom of the window, displays information about the project and the currently active file in the editor. Finally, below the editor is the Panels area, where you can find: Problems View, Output, Debug Console and the Integrated Terminal

Using the Command Palette

The Command Palette, which is usually brought up using the Ctrl+Shift+P shortcut, provides quick access to most, if not all, of the tool functionality. It shows all available commands along with their shortcuts, with the most recently used ones at the top of the list. Type a few letters to filter by command name. Matching commands are shown along with their shortcuts. The > sign here means we're looking for commands. If we turn it into a #, we are searching for symbols like packages, classes, enums, macros, and so on. You can even specify what type of symbol you are looking for. Similarly, the @ prefix searches the symbols defined within the editor in focus, classes, fields, functions, and so on, a very convenient way to navigate large files. Simply typing a few letters, without any prefix, is a quick way to open files. Any of these modes of the Command Palette can also be reached by executing commands, such as Go to Symbol in Workspace..., Go to Symbol in Editor..., or Go to File....

To change the UI color, open the Color Theme preference using the Command Palette, and scroll through the entries to get an instant preview. Notice that themes are grouped into light, dark, and high contrast categories. When satisfied with a particular theme, press Enter to use it. You can find plenty of information about VS Code on the web. This was just a quick introduction.

DVT IDE for Visual Studio Code

Now, let's dive into the DVT IDE for VS Code.

DVT Built-in SystemVerilog Compiler

We should first mention that at the heart of it, there is a built-in SystemVerilog compiler that analyzes the source code, reports compilation problems, and builds a complete model that will be used by all other editing, navigation, and visualization features. Furthermore, the compiler works incrementally. Whenever you touch the code, the changes are analyzed and the model gets updated instantly, including, of course, any compilation errors. You don't need to wait for seconds or perhaps even minutes to recompile your entire project, only to reveal a missing semicolon. All this time spent hunting down trivial syntax errors is cut down to zero.

DVT detects a wide variety of common problems on the fly, ranging from simple typos and undeclared elements to tricky syntax and semantic issues. For example, such a type mismatch is a rather common and hard-to-spot error that DVT catches at type time. The same goes for this method prototype and implementation mismatch. Precious debug time is saved by having such problems reported early in the flow.

Errors and warnings are shown to the left side of the editor, as well as in the Minimap, 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. For convenience, you can hide warnings and only focus on errors. Of course, DVT's compiler treats your project as a whole, analyzing not only the changed files, but also any dependencies that might be affected by these changes.

Let's take an example of errors crossing the class inheritance tree. When deleting the argument of this method from the apb_config class, two new errors have been introduced: one where the call to super 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.

Standard Compliance and Non-Standard Warnings

It's also worth mentioning that DVT understands constructs that are accepted by various simulators, although not described by the IEEE standard, and flags them as non-standard warnings. 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 the 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.

Fixing Code Problems with Quick Fix Proposals

Now that we have a couple of errors in our code, it's time to see how DVT can help with fixing them. Place the editor cursor on the non-existing type error and press Ctrl+.. The 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 immediately fixed. Notice that the places where the problems are reported, such as the editor and the Problems View, are instantly updated as well.

Have a look at this extern implementation of the connect_phase function, not matching the prototype's number of arguments. Click the light bulb icon to see the list of quick fix proposals. In this case, we can choose to automatically update the implementation to match the prototype.

When it comes to complex UVM test benches, fixing issues related to inheritance, virtual classes, or classes that are not visible in the current scope may become cumbersome. In the following example, we'll see how DVT can come up to help. Notice this 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.

Assuming you have additional VS Code extensions installed, like Todo Tree for example, all the task tags are collected in a dedicated view. Here, we can notice this high-priority task reminding us of our next example.

Using package encapsulation is a good practice that might sometimes lead to a particular type of compilation problems. Let's remove the ahb_package import. Notice that several new errors are reported in the Problems View. Sometimes you might forget to import the package or simply use a class without realizing that its package is not imported in the current scope. You can immediately fix such issues using the Import quick fix proposal. Especially when dealing with complex UVM test benches, being able to automatically correct such compilation errors can help you focus on the really important tasks and increase efficiency.

Error Resilience

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, as it happens in the early development stages, or incorrect, for example, during major overhauls. Nevertheless, DVT's functionality is largely not affected by errors.

Exploring the Test Bench with Visualization Tools

Now let's take a deep dive into the test bench exploration capabilities of the tool and quickly get familiar with this ref flow project that will be used throughout the presentation.

Verification Hierarchy View

A good entry point is the Verification Hierarchy View, which allows you to see how the UVM components are linked together inside a test. Press the Select Top button and choose a test. Note how the view gets populated with the UVM component instance hierarchy. Select any instance in the tree to see its TLM ports in the Ports panel. Double-click an instance to go to the create call. Double-click on a port to go to its declaration.

Here, by the way, you can easily search for a particular component, say, ahb0. Or for all components containing a port with .seq in its name. Or you can find all components located directly under a particular path, say under the apb_uart_0/monitor/.

UVM Components Diagram

As the saying goes, an image is worth a thousand words, and DVT excels when it comes to visualization techniques. Probably an even better means to visualize the verification environment is the UVM Components Diagram, which you can generate by right-clicking any view entry, for example, the top-level test. It illustrates UVM component containment, TLM port connections between components, as well as virtual interface connections to the design. You can increase the depth to see more details. The search bar helps to quickly find your way around such large diagrams.

Filters allow you to emphasize different UVM components in your diagram. For example, this predefined filter allows you to color distinctively monitors and drivers. To dive into a specific component or jump into the code, use one of the actions available in the context menu. Apart from the visual exploration benefit, you can also use the UVM component diagrams for documentation purposes. Click the Save As toolbar button to export the diagram to a file, which you can share or embed in a document.

Verification Breadcrumb

You might have noticed that the status bar got updated as we navigated the hierarchy in the UVM component diagrams. This is called the Verification Breadcrumb, and it displays the path to the current test bench component starting from the top, usually a test. It shows a couple of parents of the current component, and hovering over it with the mouse pointer reveals the entire path. Click on it to move around using the Command Palette. The Verification Breadcrumb is also available when editing source code. This capital M in the breadcrumb indicates that the component is instantiated on multiple paths throughout the test bench. Now, let's switch to a different path using the Select Other Verification Breadcrumb Instance command.

Speaking of verification hierarchy paths, it's good to know that you can easily copy the current path to the clipboard, for instance, to use it in a configuration file. To quickly jump up the hierarchy, use the Open Verification Breadcrumb Field Declaration or Open Verification Breadcrumb Create Call command.

UML Diagrams for Class Visualization

The Unified Modeling Language, or UML, was devised in the software world to provide a standard way for visualizing classes along with their members, as well as relationships among classes, such as inheritance and associations. To see the UML diagram of a class, simply place the editor cursor on its name and trigger the Show Diagram command, then choose UML Diagram. The selected class is highlighted, its parent classes are connected with a solid arrow, while associations to the types of its fields are shown with a dotted arrow.

To customize the diagram, click on the Preferences toolbar button. For example, you can show all parents and children leaving out associations in order to get a plain inheritance diagram. Right-click on any box, say the uvm_env, and choose Select Type to dynamically expand the diagram. In this example, we obtained the inheritance tree of all UVM environments.

Bit Field Diagrams for Registers

Let's look at another type of diagram which highlights the benefits of choosing an IDE instead of a plain text editor. Simply hover over a register class name to see a bit field diagram in the tooltip. It visually represents the bit fields, alongside a configuration summary table. Even handier, you can get the same for a register instance in a different file, perhaps where it's referenced inside a sequence. This time, let's trigger the Show Diagram command to see it in a separate editor.

Rendering Waveforms with WaveDrom Format

Speaking of diagrams, DVT can also render waveforms using the popular WaveDrom format. The nicest part is that waveform descriptions can be embedded into comments between dedicated pragmas. Hover over the comment to see the waveform in a tooltip.

UVM Sequence Tree

Since we're here, let's talk about another useful test bench exploration feature, the UVM Sequence Tree. Start from a sequence of your choice and run the Show UVM Sequence Tree command. The References view gets populated with the sequence call hierarchy, showing all the sub-sequences and transfers driven by it. For easier understanding, DVT also displays next to each entry the associated comments collected from code. Click an entry to reveal the corresponding source in the editor, for example, the uvm_do macro call.

Method Call Hierarchy

A similar functionality is available for method calls. Let's take the grant_queued_locks function of the uvm_sequencer_base as an example and run the Show Call Hierarchy command. The References view gets populated, allowing you to quickly understand complex execution flows. The header of the view indicates that we are currently seeing all the calls from this function. To see the method calls of the selected function or task, click on the Show Incoming Calls button.

Seamless Navigation Around Code

Now let's also take a quick your of some navigation capabilities.

Navigating Between Extern Methods and Prototypes

In UVM test benches, 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, place the cursor over the connect_phase function name and trigger the Go to Declaration command. Conversely, the Go to Definition command will get you back to the implementation.

You can also jump to the definition of any other language elements, classes, methods, fields, macros, and even included files. Use the Go to Definition option from the context menu or the Command Palette. Or, easier, by holding down the control key and clicking on the identifier.

You can go back and forward between the visited code locations, just like in a web browser. Notice that we also have the Peek option, which, as the name implies, offers a lighter alternative to actually opening another editor, yet allowing you to peek around. Press the Escape key when done.

Exploring Class Hierarchies and Inheritance

Sometimes you will notice this green arrow on the left side of the editor. This indicates an overridden function. The same information is displayed also in the tooltip, together with a hyperlink to the parent function. Another method to reach the parent function is by using the Go to Super Implementation command. This works the other way around too. To see where a specific function is overridden inside your project, use the Find All Implementations command.

Now let's also take a look at the type hierarchy. Place the cursor on a class, interface class, or a function, and invoke the Show Type Hierarchy command. You can choose to see the superclasses or the subclasses of the selected one. Superclass hierarchy is particularly interesting when working with interface classes, since an entire tree of implemented interfaces can unfold above a class.

Finding References and 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 References. Let's take the case of this sequencer from the uvm_sequence_item class. To find where it is used, we trigger the References command. It comes in multiple flavors. Find All References displays results in a dedicated view in the sidebar. Peek References lets you quickly preview the usages with the arrow keys without leaving your current location in the editor. Enter takes you there, and Escape closes the peek. Go to References is like the peek, except that it takes you directly to the usage should there be only one.

Now, further refinements of the references are available. If you want to see only the readers or only the writers, you can choose one of the references subsets.

Variable Randomization and Constraints

Another common operation done when working with UVM test benches is inspecting variable randomization. Place the cursor on the variable and trigger the Show Constraints command. All the constraints in which this variable is involved are displayed in the References view. Like this uvm_do_with macro call, function calls to rand_mode and constraint_mode which affect the variable randomization, or static constraints.

This functionality can also be triggered using CodeLens, links that perform context-sensitive actions conveniently placed between the lines of code. However, since one might find them quite intrusive, CodeLens are not enabled by default. Simply run the Open User Settings command, search for dvt.codelens, and enable them. By the way, it's worth remembering this place. This is where you can find and configure all the tool preferences.

Let's take another look at this rand variable. Above the transmit_delay, there is a new Show Constraints CodeLens, and the constraints are now one click away.

Writing Code Efficiently

When it comes to writing new code, a powerful tool functionality is Autocomplete, also known as Content Assist or IntelliSense in the VS Code terminology.

IntelliSense (Content Assist)

Let's suppose you want to enter a line such as this one. Start by typing a prefix, just a few letters of the identifier you wish to write, then press Ctrl+Space. A list of matching completion proposals shows up: fields, functions, events, types, and so on. Unlike plain text editor string completion, the proposals provided by DVT are context-aware, meaning you only get API which is accessible in this particular context, with the most likely completions promoted to the top of the list. Press the Read More button to the side of an entry to get more details about it. Press Enter to complete a proposal.

After typing the dot for hierarchical access, the proposals list is automatically displayed, and similarly after the equals sign. Let's do the same for this case branch. Notice how DVT only proposes the valid enum items that match the left-hand side of this assignment. Of course, you get similar assistance for any language construct: types, macros, function calls, and so on.

Let's define another TLM port in this collector class. As you type, the list of proposals shrinks to fit the prefix. However, sometimes there are so many types with a common prefix that you need to do quite a lot of typing to reach the desired proposal. That's the reason why you may start typing any of the underscore-separated segments of the word, and even combine such fragments to quickly reach the desired completion.

To sum it up, this feature not only helps you write code faster, but perhaps even more importantly, it helps you accurately write correct-by-construction code.

Using Code Templates and Snippets

Furthermore, you can quickly deploy code templates, snippets of code ranging from a simple for loop to entire test bench components. In order to get template proposals, you need to type in a prefix prior to invoking Content Assist using Ctrl+Space. Press Enter to choose a proposal, then fill in the customizable parts of the code snippet which are called placeholders and enclosed in colored boxes. Press Tab to move to the next box, or Shift+Tab to go to the previous one. Notice how the replacement is made for all occurrences of each placeholder at once.

Notice how fast we got a new agent with all the boilerplate code already in place. Furthermore, reminders are conveniently placed throughout the generated code fragment so you don't forget to fill in the actual functionality.

Overriding Methods Efficiently

Typically, when you develop a UVM component, you need to override some of the UVM phases. This is something you probably do tens of times while building an env. So here's how to quickly do it in DVT. Place the cursor in the component where you plan to insert the phase task. Type a few letters of the phase you wish to override, press Ctrl+Space, pick the method, and hit Enter. A methods stub is inserted in the editor with the full method signature automatically computed.

When you want to override a bunch of methods at once, press Ctrl+Space and pick Override Methods. You can choose any virtual method from across the whole upper inheritance chain of the current class. Next, you can control how the new methods will be inserted in code, within the class body, or as an extern declaration plus out-of-body implementation. As you can see, multiple methods can be overridden at once, making for a very efficient time saver.

Generating UVM Field Registration Macros

Another daunting task you have to deal with when developing a UVM test bench is writing and updating the UVM field registration macros. You have to make sure that you use the proper macro based on the variable type, remember the arguments required by each macro, and other configuration options. Let's take, for example, this apb_transfer and delete all the macros. Press Ctrl+Space and from the list of autocomplete proposals choose Register Class and All Its Fields. DVT generates the registration macros for all class fields and automatically detects the required field registration macro depending on the field type.

By the way, you have the possibility to expand the macro or even apply preprocessing for an arbitrary code section directly from the editor. You can expand either one level or all nested levels of preprocessing, and you can get the expansion either inline or in a separate file. Alternatively, simply click one of the CodeLens we've enabled earlier. When done, simply click the Collapse Expansion CodeLens.

Generating Getters and Setters

Since we're here, let's suppose we also want to generate getters and setters for the fields of this class. Place the editor cursor inside the body of the class where you plan to insert the new methods, press Ctrl+Space, and select Generate Getters and Setters. Check the fields you're interested in using the Spacebar, then press Enter. This is yet another example of how DVT takes care of tedious tasks, allowing you to focus on what really matters.

Code Formatting and Refactoring

Our next topics are code formatting and refactoring, which help you effortlessly improve code readability and maintainability two key aspects of code quality, especially in projects that span across multiple teams.

Code Formatting

This code looks quite messy by any standards. Run the Format Document command and everything falls nicely into place. The DVT code formatter is highly customizable. Search for DVT > Format in the settings and tune the available knobs in accordance with your code styling guidelines. For example, indentation-related preferences, such as consistent alignment of begin-end, parameters, preprocessing, and vertical alignment. Now let's take a look at how the formatter performs.

Refactoring Code

Our next topic is refactoring. In a nutshell, refactoring means modifying the code without changing its functionality in order to improve readability and comprehensibility. For example, a class method can be implemented within the class itself or just prototyped with an out-of-block implementation. DVT can automatically reorganize the code to switch between these two coding styles. Place the cursor on the function name, trigger the Refactor... command, and select Join extern function prototype and implementation, or vice versa, by using the Split function to extern and implementation option.

Test benches grow over time and, without maintenance, code tends to become more and more complex and harder to maintain. Maybe a function grew excessively and it's time to split it up into several new sub-functions. That's where the Extract Functionality can help save some minutes of tedious work. Simply select a fragment of code and run the Refactoring command. Then, pick Extract to function and type in the function name. Notice how the initially selected code was replaced with the new function call.

Also, maybe during development a field name lost its relevance. Naturally, since the tool knows precisely where the name of the field is used throughout the entire codebase, it can accurately perform renames in one shot. From the Command Palette, trigger Rename Symbol. Enter the new name and press Enter to apply or Shift+Enter to preview the changes. Select the file about to be changed from the list to get a before and after comparison. This is a huge time-saver, since manually changing the name of a field, function, class, or macro may involve edits to many files, and text editor searches may require tedious validation because of other elements in the code with similar or even identical names.

Integrating with External Tools and Source Control Management

So far, we've covered lots of visualization, editing, and navigation-related functionalities of the tool, but an IDE does even more than that. It helps you over the entire development process, from writing and reviewing code to simulation and debugging.

If you just want to start the simulation, synthesis, or linting from within VS Code without having to switch to a command line, the most straightforward way is from the built-in Terminal. But going one step further, you can define Tasks that store a particular run configuration. Select Configure Task from the Command Palette. The task configuration includes the command or script to be executed, environment variables, and the way output should be handled. Perhaps most importantly, you can configure problem matchers, which grab the standard output of the external tool and show the errors and warnings in the Problems View alongside other issues. Instead of grepping through the simulator logs, locating the original file in the source tree, going to line, and so on, the source of the error is one click away.

VS Code ships with a Git Source Control Manager extension. Most of the source control UI and workflows are common across other revision control extensions. In the case of Git, the file browser shows various one-letter decorations, for example, modified, untracked, or ignored files. The Timeline view presents the history of commits. Some useful annotations are also available in the editor, in the gutter, and overview ruler.

The Source Control activity provides further functionality. The current branch is shown in the Repositories view, and from here you can easily switch to a different branch or create a tag. All changes are presented in the Source Control view, and from here you can easily stage, view diffs, or even commit.

Conclusion

This brings us to the end of our presentation. Thank you for reading. We hope you've enjoyed learning about the DVT IDE for VS Code. Please don't hesitate to contact us for more information.