Monday, June 29, 2015

Even More Ideas on Coverage Extendability

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:

class cov_collector #(type POLICY = cg_ignore_bins_policy) extends
  uvm_subscriber #(instruction);

  `uvm_component_param_utils(cov_collector #(POLICY))

  covergroup cg;
  // ...
endclass

We need to instantiate this component inside our environment:

cov_collector #(cg_ignore_bins_policy) cov;

virtual function void build_phase(uvm_phase phase);
  cov = cov_collector #(cg_ignore_bins_policy)::type_id::create("cov", this);
  // ...
endfunction

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:

virtual function void build_phase(uvm_phase phase);
  cov_collector #(cg_ignore_bins_policy)::type_id::set_type_override(
    cov_collector #(no_mul_cg_ignore_bins_policy)::get_type());
  super.build_phase(phase);
endfunction

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:

class cov_collector_base extends uvm_subscriber #(instruction);
  `uvm_component_utils(cov_collector_base)

  // ...
endclass


class cov_collector #(type POLICY = cg_ignore_bins_policy) extends
  cov_collector_base;

  `uvm_component_param_utils(cov_collector #(POLICY))

  // ...
endclass

When declaring the coverage collector, we need to declare it as an object of type cov_collector_base and immediately set an override on it:

cov_collector_base cov;

virtual function void build_phase(uvm_phase phase);
  cov_collector_base::type_id::set_type_override(
    cov_collector #(cg_ignore_bins_policy)::get_type());
  cov = cov_collector_base::type_id::create("cov", this);

  // ...
endfunction

For the restricted CPU we can just replace the previous override:

virtual function void build_phase(uvm_phase phase);
  cov_collector_base::type_id::set_type_override(
    cov_collector #(no_mul_cg_ignore_bins_policy)::get_type());
  super.build_phase(phase);
endfunction

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:

class cg_ignore_bins_policy extends uvm_object;
  `uvm_object_utils(cg_ignore_bins_policy)

  // ...
endclass

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:

class cov_collector extends uvm_subscriber #(instruction);
  `uvm_component_utils(cov_collector)

  protected cg_ignore_bins_policy policy;

  function new(string name, uvm_component parent);
    super.new(name, parent);
    policy = cg_ignore_bins_policy::type_id::create("policy", this);
    // ...
  endfunction

  // ...
endclass

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:

cov_collector no_mul_cov;

virtual function void build_phase(uvm_phase phase);
  cg_ignore_bins_policy::type_id::set_inst_override(
    no_mul_cg_ignore_bins_policy::get_type(), "no_mul_cov.*", this);
  no_mul_cov = cov_collector::type_id::create("no_mul_cov", this);
  // ...
endfunction

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.

5 comments:

  1. Nice series.
    I like your e - view, and the way you're trying to recreate the capabilities you miss.

    I use a lot of macro generated types, where the basic covergroup with coverpoint per item are defined. when I want to add a specific cross I inherit the types, add my cross, and sure, factory override it!
    your post got me thinking to add cross coverage to already defined covergroup using the existing coverpoints,

    Another possibility to use dynamic "Policy" (dynamic is called algorithm pattern, I think) is to put the object in a configuration object. which is also done in build phase. Not that I'm against overrides, I would have done the override straight from the top, without going into the build. Objects/Components are heavy with factory infrastructure that propagates the override into your object, your super.build() is searching this list anyway. So why not use it? declare that override from the test, without touching your build phase.

    ReplyDelete
    Replies
    1. Looking back at the last example I notice that it isn't really that good. In a normal use case, you'd have the coverage collector already created in the base (generic) environment. Somewhere in the testbench we'd need to replace the policy object. That could be done anywhere: in the build of the test, of the testbench env, etc. Like you point out, the policy could also be a part of the config class and the testbench would override it when it's configuring the CPU env.

      Delete
  2. I think your blog suggestions could soon be collected in a series such as

    "effective SystemVerilog" and

    "more effective SystemVerilog"

    like Scott Meyers did for C++, Tudor!

    Keep the great work!
    thx
    Alex Ogheri

    ReplyDelete
  3. How can I delete entries of sampled coverage if a reset is encountered in the middle of simulation and you want to discard previously sampled data and resample?

    ReplyDelete
    Replies
    1. I'd also love to be able to do this, to make transition coverage truly useful, but I don't know any way of doing this and I'm afraid it's not possible.

      Delete