The crooked ways of UVM's register model's coverage methods

"Welcome to the world of Coverage"

I have been dabbling now for a couple of weeks with coverage. Working my way through the SV LRM, looking at some examples and running a few simple cases.
My first case was some protocol coverage. We wanted to verify certain types of traffic are getting through the design. I extended a uvm_subscriber for that purpose, added a covergroup as part of the class, instantiated the covergroup in the new function of the class and called the sample function in the write function of the subscriber:

class my_coverage_collector extends uvm_subscriber #(trans_type);

     ...
     covergroup my_covergroup;
          ....
     endgroup

     function new(string name = "my_coverage_collector",
                  uvm_component parent = null); 
         super.new(name, parent);
         my_covergroup = new;
     endfunction

     function write(trans_type t);
         my_covergroup.sample();
     endfunction
endclass

Simple enough.
Want to disable the coverage? Just turn off a bit in the "env_config", and hocus-pocus the env won't build the coverage collector, and won't hook it up to the analysis port providing the transactions.

UVM register model (or "Here comes the cavalry")

UVM wants to help you to disable/enable coverage for every possible coverage type in the register model at every possible level. 
You get the set_coverage and get_coverage functions which are supposed to be used for sample controlling, the build_coverage, has_coverage and add_coverage which are supposed to help you decide whether or not to build the coverage model, and then a static method called include_coverage to be called from the test/env which should allow controlling the building of the coverage model but at a higher level.

A nice example can be found on Mentor's verification academy site (direct link, note: you have to register to be able to see the article).


The way it is depicted in the example and in some other examples on the net:
  1. You call uvm_reg::include_coverage("*", UVM_CVR_ALL) in the env. This sets a variable in the uvm_resource_db, defining what to cover (in case you didn't set * or UVM_CVR_ALL). 
  2. When the register is created, the build_coverage should be called with as argument a variable that defines which coverage should be built.
  3. build_coverage goes to the uvm_resource_db, looks up what has been included in the coverage and based on that and the requested coverage returns the actual coverage to be built.
  4. Note: The coverage is not being build directly, the class instantiating the coverage is the actual one being created. We'll get back to that in a minute.
  5. You should then query the coverage by calling has_coverage, and if it returns a 1 you go ahead and instantiate the covergroup.
  6. Now you can go ahead and set_coverage to a certain value, which allows you when overriding uvm_reg's sample method to call get_coverage to verify you're allowed to call the sample function of the covergroup.

 Why wrap the covergroup in another class?

It seems like a nice and very modular approach once you get the hang of it, and very fitting of the UVM mindset.

The question that bothered me is why bother wrapping the covergroup in another class. As with a lot of these questions, there are some good reasons for them but a lot of times they get lost over time, or the reason is not clear enough for others. I think I have uncovered one of those.
The reason quoted in the aforementioned link: 
The covergroup has been wrapped in a class included in the register model package, this makes it easier to work with.
How so? As I was working on a small example, I figured, I'll go ahead and cut the cruft. I put the covergroup straight into my register class. Everything was great.

Then I figured let's go ahead and make use of all those coverage guards as I decided to call them. So I added a call to build_coverage, and a query to has_coverage. That's when I discovered things stopped working.

Debugging

You see, I decided to be smart and filter the coverage in the test by calling include_coverage as such: include_coverage("my_reg_block*", UVM_CVR_ALL). But for some reason no coverage would be built even though the my_register is part of my_reg_block. The reason for that: although build_coverage is smart enough to call get_full_name() so as to get the register's hierarchy, when creating the register, the name hasn't been set yet. So unless you set the coverage for all ("*"), you can forget about calling build_coverage with some actual functionality to it.

Well, you might say, let's call build_coverage during the build method. The problem with that is that you can't call new on covergroups defined in a class if not during the class's new function. And now you have the real answer to why Mentor's example of using a wrapper class for the covergroup makes some sense. Although they still didn't get it right, as the build_coverage needs to be called in the build method to be able to lookup the register's name in the uvm_resource_db as mentioned above.

Lessons learned

  • Don't be a smartass (Wow, this takes a lot of lessons) :-)
  • If you want to use include_coverage, build_coverage, add_coverage or has_coverage to decide on building of the covergroup:
    • build_coverage should be called in the build function of the register (I'm pointing a finger at you Synopsys - ralgen does it wrong)
    • wrap your covergroup in a wrapper class.
Your comments as always are welcome,

Tsvi

Comments

Popular posts from this blog

uvm_config_db vs uvm_resource_db, where they come from and what are they good for

Don't use null as a sequencer argument to uvm_reg_sequence