Monday, June 29, 2015

In parts one and two of this series we looked at how to use policy classes to implement an extendable coverage model, where ignore bins can be tweaked. The first post looked at how to use these policies as parameters for a parameterizable coverage collector (the so called static flavor), while the second post focused on using them as constructor arguments for the collector (the so called dynamic flavor).

Since SystemVerilog and UVM have become almost synonymous terms, let's look at how these two approaches for implementing coverage extendability interact with UVM features such as the factory.

Typically, coverage collectors are UVM subscribers that are connected to monitors. Let's start as before with the static implementation, that relies on a parameterizable class:

We had the foresight to create our coverage collector using the factory. Now, when we want to verify the CPU variant without a multiplier, we just need to make sure that we create an instance of the appropriate type by setting a factory override:

While this code would compile, it will fail at run time. The reason is because even though no_mul_cg_ignore_bins_policy is a sub-class of cg_ignore_bins_policy, this relationship doesn't trickle down through the parameterization of cov_collector. The two different variants of the coverage collector are totally unrelated to each other from an OOP point of view and cannot be swapped for each other.

The classic solution to this problem is to create a common base class from which the parameterizations will inherit:

This will work, but it's not terribly elegant in my opinion. We needed to create the extra level of class hierarchy just so we could get the compiler to do our bidding. I'm also not particularly thrilled with directly starting out with a factory override in the base environment.

The dynamic version of the code is much more straightforward. This time we need to make sure we can switch out policy class instances for each other, so we'll need to make them UVM objects:

In the vanilla SystemVerilog example from the previous post we passed an instance of the policy class as the coverage collector's constructor argument. This isn't possible in UVM because a component's constructor has a fixed signature, taking only its name and its parent. However, there isn't anything stopping us from creating a policy object internally:

You may have noticed something slightly off about the previous code snippet. Even though policy is a uvm_object at heart, we pass a uvm_component parent to the create(...) call. What that does is create the policy object inside the coverage collector's scope. This makes it possible to override the policy class type using the following instance override:

A type override would have also worked, but this is a nifty little trick to know. The dynamic variant ended up being far shorter than the static one. I also like this one more because it feels closer to the Gang of Four's "favor object composition over class inheritance" principle. The main disadvantage is that, as we saw last time, it might suffer from more simulator limitations.

As always, you can download the code for this and the previous two posts from SourceForge.

I hope this series of posts convinced you that a certain degree of coverage extendability is also possible in SystemVerilog. As with all other things, as opposed to e, this requires pre-planning. Because of the overhead required, this approach might not catch traction for many testbenches, but it is ideal when developing UVCs. There are still some kinks that need ironing: the syntax for coverpoints isn't fully described and simulator support might be limited for now. While this approach currently resembles a dangerous trek through uncharted waters of the LRM, once the seas of SystemVerilog vendor interoperability calm down it's sure to really start to shine.

In the previous post, we looked at how to use policy classes as parameters for a highly configurable coverage collector. This allows us to easily implement different variations of what bins to ignore. If you haven't read that one yet, I'd encourage you to do so before continuing with this post.

Truthfully, using policy classes wasn't the first approach I tried. I had to go down a wrong path and fail before coming to that idea. Let's have a look at how I started out. You may remember the following code snippet showing the use of a function to specify what values are supposed to belong to a certain bin:

Since I had defined the covergroup for the CPU instructions inside a coverage collector class, it made the most sense to have the ignore bin function definition also part of that class. At the same time, class member function are overridable... What if we defined the ignore expression inside a virtual function? Inside the base class we don't want to ignore anything yet, so the function would be empty. We could then extend this class and override this function to ignore MUL and DIV. The code for the generic coverage collector would look like this:

Unfortunately, this wasn't supported by the simulator. After a bit more fiddling I gave up on trying to use functions with the with syntax, since that didn't seem to be supported. I wasn't about to give up yet. I figured I could pass the list of ignore bins as a constructor parameter to the covergroup. Instead of having a function that tells us whether a certain value should be ignored we could write a function that returns a list of values that should be ignored:

That also gave some cryptic error. If it didn't like passing the list of ignore bins as a constructor argument, maybe it would go for having it as a class member. After some more trial and error, I eventually got it to work by declaring it as a static field:

For some whatever reason this worked, but only if the ignore list was static. Ignoring the fact that it's a hack, now we're back in business! We can declare our coverage collector sub-class that ignores MULs and DIVs:

After firing up the simulator GUI and having a look at the generated bins, we'll get a big surprise. We'll see that MUL and DIV are, in fact, not being ignored. What's the reason for this? Well, remember that we defined the get_operation_ignore_bins() function as virtual and that we are calling this function from inside the constructor. It turns out that calling virtual functions from constructors is generally frowned upon inside the programming community, for reasons that I'm not going to list here. What's happening in our case is that the covergroup is being constructed inside the base class's constructor. The version of the bin generation function that's getting called is the one from inside that same class. Conceptually, at that point, the object being constructed is still of type cov_collector, not no_mul_cov_collector. The same behavior is also specified in the C++ standard.

Bonus points go to whoever noticed from the get-go that this whole thing was a bad idea!

In any case, we should ignore calling a class's virtual methods from its constructor altogether. This is because I've seen other simulators that will behave differently from what I've described above. I seem to remember reading that SystemVerilog should work like C++ in this respect, but I couldn't find any reference inside the LRM. If anyone could point out the appropriate section in the comments, that would be great!

I guess we'll have to scratch that idea... Not necessarily. We can still make use of polymorphism, but we just need to take the method that generates the bins out of the coverage collector class and put it into some other class. We'll call this a policy class:

We can now subclass the ignore bin generation policy as we like and pass different types of objects to the coverage collector. The simulator will take care at run time that the appropriate get_operation_ignore_bins() function gets called. For example, to create a coverage collector that ignores MULs and DIVs we can create a policy that specifies those as ignore bins:

You may notice that this approach is remarkably similar to the one we used in the previous post. Before, the type of policy that the coverage collector used was specified at compile time, by passing the policy class as a parameter and relying on static methods. Now, the coverage collector has to wait until run time to get the policy via a constructor argument and it makes use of virtual methods and polymorphism. We could describe the former approach as static and the latter as dynamic.

The dynamic approach seems to suffer from worse tool support, though. We've only got it running by employing various hacks. Barring that we could say that the two are, for all intents and purposes, equivalent.

Or are they? Make sure to read the next post where we'll explore how the two styles fare in the context of a UVM environment.

The biggest advantage of e regarding coverage is, in my opinion, the ability to tweak the definitions of existing coverage groups by extending them from anywhere inside the verification environment. This is particularly useful when dealing with coverage groups defined inside eVCs. For example, let's say that we have an AHB eVC that provides some very extensive coverage definitions to make sure that we rigorously verify our DUT. If that DUT is a very simple slave that can only process read transactions, we'll never reach 100% coverage unless we ignore all references to write transactions. This is easily done with just a few lines of code:

Even for such a basic CPU this means covering 4 * 8 * 8 * 8 = 2048 cross bins. Adding just one more instruction would raise that number to 2560. Adding just one more register would raise that number to 2916. Adding just two more registers would raise that number to 4000. I guess it's clear that this approach won't scale when our design grows.

Maybe it isn't necessary to cover all combinations. After all, we've had to give up on the dream of traversing the entire state space of a DUT many years ago, when they started getting way too big. We could make an educated guess that it isn't important to make sure that we executed an ADD with all combinations of operands and destinations. What would be important, though, is that we've made sure that each register can be multiplexed to each of the three operation arguments:

Some other interesting corner cases might be to make sure that we've tried to use the same register for both operands or for one of the operands and the destination, regardless of what the operation was:

Now that we've cut down the problem space to a more manageable size and are merrily going about verifying our design, our colleagues in marketing notice that there might be some profit to be made if we could offer a version of our CPU that only supports addition and subtraction. Since SystemVerilog coverage groups aren't extendable we can't leverage the coverage definitions from above for this other project. We'd need to define the covergroup again and specify that we want to ignore multiplications and divisions:

Those same colleagues from marketing also figure out that we could sell a slightly slower variant of our CPU that only has four registers. As before, we'd need to create another copy of the covergroup where we ignore all registers from R4 onward:

Now we've got three copies of essentially the same covergroup, with very small differences. If after a review we notice that we need to add a new coverage item because we missed some important aspect, we'll need to make sure that we update all three of those coverage groups. If marketing finds even more potential for stripped down CPUs (for example, without multiplication and with less registers at the same time), those new variants will only increase the number of files we need to maintain in sync.

I couldn't accept that there isn't any elegant solution to this problem, so I went digging through the LRM. The new 2012 standard added some cool new features to the coverage chapter. The one that caught my eye in particular was the with syntax for specifying coverpoints:

What this code snippet would do is ignore all values of the coverpoint for which the is_ignore_bin(...) function returns a 1. Let's imagine that we write the definition of the operationcoverpoint in this manner. We need to find a way to switch out the implementation of the function, so that it always returns a 0 (in the general case where we don't want to ignore anything) or sometimes returns a 1 (to ignore MULs and DIVs).

There is a lot of literature on this topic (switching out method implementations) in the world of software programming. Generic programming and, more specifically, policy-based design give us the answer. This programming paradigm is based on C++ templates, which are analogous to SystemVerilog parameterized classes. We could use parameter classes to define different policies whether a bin is supposed to be ignored or not.

Using policy classes allows us to separate our coverage definitions from their parameterization. The result is that we have less code, which is also much easier to maintain because we did away with the redundancy the old-school method suffered from.

Have a look at the next post for an alternative way of using policy classes.

About

I am a Verification Engineer at Infineon Technologies, where I get the chance to work with both e and SystemVerilog.
I started the Verification Gentleman blog to store solutions to small (and big) problems I've faced in my day to day work. I want to share them with the community in the hope that they may be useful to someone else.