Understanding UVM

(Basic familiarity with SystemVerilog and OOP concepts are assumed)

Let's talk about UVM.

Entry-level engineers in the ASIC design industry usually start their careers from verification and the very first thing they come across in their job is something called the Universal Verification Methodology (UVM) framework. That is also the start of sleepless nights for mostly EE graduates that are trained in neither verification nor software design principles.

It is not just about OOP. In fact, UVM is mostly based on software design patterns such as the "Factory Pattern", "Singleton Pattern", "Decorator Pattern" and "Observer Pattern". See Gang of Four Design Patterns

The UVM framework is a collection of libraries packages that already integrated some of these design principles. As a user, we simply need to be aware of their existence and when to use them.

To understand why the UVM framework is widely used in digital verification, we need to first understand what are the issues with traditional tests and go from there.

In college, the test benches being taught are mostly directed tests using test vectors. For example:

No alt text provided for this image

There is one glaring issue with these types of directed tests - scalability.

For n bits of input, we get 2^n possible input space. Due to the exponential growth in input space, most companies in the industry use a combination of both directed tests and constrained random tests (CRT) in order to cover as many test scenarios as they can. In CRT, the input vectors are randomized. You can think of directed tests as the specific instance of random tests.

Natively, SystemVerilog supports CRT via random variables and constraints. A random input can be represented as a class

No alt text provided for this image

Without implementing any checkers and reference models, we can then connect the random inputs as such

No alt text provided for this image

The result is a testbench that generates constrained random inputs every time .randomize() is called. If we need to, we can even write coverage classes and bind them to the random testbench.

Why UVM

There is still another glaring issue - Sequencing the inputs of the tests.

The testbench above is good enough for combinational logic, but what if we need to test sequential logic? Ideally, we would need a way of sequencing without having to care too much about the details of clock cycles and simulation ticks (if we don't want to). What if we also want the possibility to randomize/constrain the number and order of the sequences?

It is obvious we need a much higher abstraction to deal with the aforementioned issues.

There's another scalability issue as well. Suppose we have a huge design, and we need several different test scenarios to target different features. For example, we might want a test targeting just opcodes and another test targeting the reset sequences.

Some naive approaches would be to

  • Have a class for each test scenario and a monolithic testbench that runs one after another.??

No alt text provided for this image

  • Have one testbench for each test scenario, possibly with a duplicated codebase. Compiled models might also not share between each testbench.

No alt text provided for this image

Ideally, if one test fails, the rest of the test still continues so we can analyze and prioritize each test failure at our discretion. But for #1, if one test fails, we are left with nothing.

As for #2, since each testbench has to recompile to a different model, it might not be very efficient when your design is large, especially when you are trying to minimize compilation + elaboration time (and hence verification time).

Also, both methods run into maintainability issues. For example, when you add new test scenarios, changes would have to be made at the testbench to instantiate it.

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

Why can't we use polymorphism as is? Well, it doesn't work. You still have to instantiate the derived class elsewhere and point the base test to it. The instance name is hardcoded so you can't modify it dynamically at runtime.

No alt text provided for this image

What we really want is the ability to run specific tests scenarios dynamically. Compile once and runs a subset. This is where the design patterns come in.

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

Design Patterns

The Factory Pattern in UVM makes it so that we can instantiate a factory item, in this case, our test scenarios (and others) classes dynamically. For example:

No alt text provided for this image

compared to without the UVM Factory Pattern.

No alt text provided for this image

Notice that in the case of UVM, you can just use the string name, while in the latter the instance name is hardcoded.

This allows the verification engineer to compile the testbench once, then pass in strings as parameters to choose which test scenarios to run.

The Observer Pattern

The observer pattern is very simple and can be explained by the terms broadcaster and subscriber. The subscribers register themselves to the broadcaster and when the broadcasters update (via notify), the subscribers also get the update subsequently (notify calls the observer's update).

No alt text provided for this image

Rings a bell? uvm_subscriber and subsequently the monitors use this Observer Design Pattern. The broadcaster here is the analysis_port. See this tutorial for basic usage of uvm_subscriber. An example of what the Observer pattern looks like in SystemVerilog:

No alt text provided for this image
No alt text provided for this image

The Decorator Pattern

It is difficult to describe in a short paragraph how decorator patterns are used in UVM. Simply put, the decorator itself is a derived instance/extended from the same component and it wraps components of the same type so that you can "decorate" it with new features.

The powerful notion about it is that it allows you to add functionality to your components without having to do major changes (if at all) to any legacy codebase. You can simply add new classes without being afraid of breaking anything.

Decorator patterns are seen a lot in python codes due to the native support.

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

Now that we have seen why UVM is utilized, in the next article we can discuss what is the usual methodology for the bring-up of a UVM environment.

Do we start with the driver first? The stimulus sequence? Or the test environment?

How should we think about the monitors and checkers?

要查看或添加评论,请登录

Alan Saw的更多文章

社区洞察

其他会员也浏览了