Verification Martial Arts: A Verification Methodology Blog

Archive for the 'Communication' Category

A generic functional coverage solution based on vmm_notify

Posted by Wei-Hua Han on 15th June 2009

Weihua Han, CAE, Synopsys

Functional coverage plays an essential role in Coverage Driven Verification. In this blog, I’ll explain a modular way of modeling and implementing  functional coverage models.

SystemVerilog users can take the advantage of  the “covergroup” construct to implement functional coverage. However this is not enough. The VMM methodology provides some important design-independent guidelines on how to implement functional coverage in a reusable verification environment.

Here are a few guidelines  from the VMM book that I consider very important for implementing a functional coverage model:

“Stimulus coverage shall be sampled after submission to the design” because in some cases it is possible that not all generated transactions will be applied to DUT.

“Stimulus coverage should be sampled via a passive transactor stack” for the purpose of reuse so that the same implementation can be used in a different stimulus generation structure. For example in another verification environment, the stimulus may be generated by a design block instead of testbench component.

“Functional coverage should be associated with testbench components with a static lifetime” to avoid creating a large number of coverage group instances. So “Coverage groups should not be added to vmm_data class extensions”. These static testbench components include monitors, generators, self-checking structure, etc.

“The comment option in covergroup, coverpoint, and cross should be used to document the corresponding verification requirements”.
You can find some more details on these and other functional coverage related guidelines in the VMM book, pages 263-277.

In an earlier post “Did you notice vmm_notify?”, Janick showed how vmm_notify can be used to connect a transactor to a passive observer like a functional coverage model. Here, let me borrow his idea to implement the following VMM-based functional coverage example.

1.    Transaction class

1. class eth_frame extends vmm_data;
2.    typedef enum {UNTAGGED, TAGGED, CONTROL} frame_formats_e;
3.    rand frame_formats_e format;
4.    rand bit[3:0] src;
5.     …
6. endclass

There is some random property defined in the transaction class.  We would like to collect the coverage information for these properties in our coverage class, for example “format” and “src”.

2.    A generic subscribe class

1.  class subscribe #(type T = int) extends vmm_notify_callbacks;
2.     local T obs;
3.     function new(T obs, vmm_notify ntfy, int id);
4.        this.obs = obs;
5.        ntfy.append_callback(id, this);
6.     endfunction
7.     virtual function void indicated(vmm_data status);
8.          this.obs.observe(status);
9.       endfunction
10. endclass

The generic subscribe class specifies the behavior for “indicated”method which will be called when vmm_notify “indicate” method is called. Then with using this generic subscribe class, functional coverage and other observer models only need to implement the desired behavior with “observe” method. Please note that in line 8 the vmm_data object is passed to “observe” method through vmm notification status information.

3.    Coverage class

1.   class eth_cov;
2.      eth_frame tr;

3.      covergroup cg_eth_frame(string cg_name,string cg_comment,int cg_at_least);
4.         type_option.comment = “eth frame coverage”;
5.         option.at_least = cg_at_least;
6.         option.name=cg_name;
7.         option.comment = cg_comment;
8.         option.per_instance=1;
9.         cp_format:coverpoint tr.format {
10.         type_option.weight=10;
11.       }
12.       cp_src: coverpoint tr.src {
13.          illegal_bins ilg = {4′b0000};
14.         wildcard bins src0[] = {4′b0???};
15.         wildcard bins src1[] = {4′b1???};
16.         wildcard bins src2[] = (4′b0??? => 4′b1???);
17.      }
18.       format_src_crs: cross cp_format, cp_src {
19.          bins c1 = !binsof(cp_src) intersect {4’b0000,4’b1111 };
20.      }
21.    endgroup

22.    function new(string name=”eth_cov”, vmm_notify notify, int id);
23.       subscribe #(eth_cov) cb=new(this,notify,id);
24.       cg_eth_frame = new(“cg_eth_frame”,”eth frame coverage”,1);
25.    endfunction
26.    function void observe (vmm_data tr);
27.       $cast(this.tr,tr);
28.       cg_eth_frame.sample();
29.    endfunction
30. endclass

The coverage group is implemented in coverage class eth_cov. And this coverage class is registered to one vmm_notify service through scubscribe class. The coverage group is sampled in “observe” method so when the notification is indicated the interesting properties will be sampled.

4.    Monitor

1.   class eth_mon extends vmm_xactor;
2.      int OBSERVED;
3.      eth_frame tr; // Contains reassembled eth_frame transaction

4.      function new(…)
5.         OBSERVED = this.notify.configure();
6.         …
7.      endfunction
8.      protected virtual task main();
9.         forever begin
10.          tr = new;
11.          //catch transaction from interface
12.         …
13.         this.notify.indicate(OBSERVED,tr);
14.         …
15.       end
16.    endtask
17. endclass

The monitor extracts the transaction from the interface then it indicates the notification with the transaction as the status information (line 13).

5.    Connect monitor and coverage object

1.   class eth_env extends vmm_env;
2.      eth_mon mon;
3.      eth_cov cov;
4.      …
5.      virtual function void build();
6.         …
7.         mon = new(…);
8.         cov = new(“eth_cov”,mon.notify,mon.OBSERVED);
9.         …
10.    endfunction
11.     …
12. endclass

In the verification environment, the coverage object is created with the monitor notification service interface and the proper notification ID.

There are other ways to implement functional coverage in VMM based environments.  For example a callback-based implementation is used in the example located under sv/examples/std_lib/vmm_test in the VMM package which can be downloaded from www.vmmcentral.org.

I haven’t discussed assertion coverage in this post, which are another important type of “functional coverage”.  If you are interested in using assertions for functional coverage please check out chapter 3 and chapter 6 in the VMM book for its suggestions.

Posted in Communication, Coverage, Metrics, Register Abstraction Model with RAL, Reuse, SystemVerilog, VMM | 4 Comments »

Did you notice vmm_notify?

Posted by Janick Bergeron on 20th May 2009

jb_blog Janick Bergeron, Synopsys Fellow

In the recent past, we’ve had several customers state that VMM did not have a one-to-many passive transaction-level interface akin to Analysis Ports in some other methodology (whose name escapes me right now :-) .

Not true. Always had. From day one. Since March 2006 to be exact.

The VMM Notification Service (vmm_notify) can be used to connect a transactor (or channels or any other testbench component) to a scoreboard or functional coverage collector or any other passive observer. There can be multiple observers, and they will all see the same transaction stream.

There are pre-defined notification in vmm_xactor and vmm_channel: you might want to look them up as they may already provide all the information you are looking for. But if you need to define your own, here’s how you can do it.

First, you need to configure a new indication:

class monitor extends vmm_xactor;
int OBSERVED;

function new(string name);
super.new(“monitor”, name);
this.OBSERVED = this.notify.configure();
endfunction

endclass

then you simply indicate the notification whenever appropriate, supplying a transaction descriptor as status:

class monitor extends vmm_xactor;
int OBSERVED;


task main();
int n = 0;
super.main();
forever begin
trans tr = new;
tr.data_id = n++;

this.notify.indicate(this.OBSERVED, tr);

end
endtask
endclass

Any number of observers can now monitor the transactions through the indication via the vmm_notify::wait_for() method:

class observer;
function new(string name, vmm_notify ntfy, int id);
fork
forever begin
vmm_data tr;
ntfy.wait_for(id);
tr = ntfy.status(id);
$write(“At %0d on %s: %s\n”, $time, name, tr.psdisplay());
end
join_none
endfunction
endclass

program tb_env;
initial
begin
monitor  mon  = new(“tb_env.mon”);
observer sb1  = new(“[sb1] “, mon.notify, mon.OBSERVED);
observer sb2  = new(“[sb2] “, mon.notify, mon.OBSERVED);

end
endprogram

There is only one problem with using the vmm_notify::wait_for() method: it won’t catch multiple indications with no delays between them. If there can be multiple indications in the same simulation cycle, you must use the vmm_notify_callbacks::indicated() method to observe them. This complicates the coding of the observer slightly but provides a more reliable observability:

typedef class observer;
class subscribe extends vmm_notify_callbacks;
local observer obs;
function new(observer obs);
this.obs = obs;
endfunction
virtual function void indicated(vmm_data status);
this.obs.observe(status);
endfunction

endclass

class observer;
string name;
function new(string name, vmm_notify ntfy, int id);
subscribe cb = new(this);
ntfy.append_callback(id, cb);
this.name = name;
endfunction

function void observe(vmm_data tr);
$write(“At %0d on %s: %s\n”, $time, name, tr.psdisplay());
endfunction
endclass

If you are going to have a lot of different observer classes, this additional code can become a burden. It can be greatly simplified by using a template and agreeing on a method name within the observer class to do the observation:

class subscribe #(type T = int) extends vmm_notify_callbacks;
local T obs;
function new(T obs, vmm_notify ntfy, int id);
this.obs = obs;
ntfy.append_callback(id, this);
endfunction
virtual function void indicated(vmm_data status);
this.obs.observe(status);
endfunction
endclass

class observer;
string name;
function new(string name, vmm_notify ntfy, int id);
subscribe#(observer) cb = new(this, ntfy, id);
this.name = name;
endfunction
function void observe(vmm_data tr);
$write(“At %0d on %s: %s\n”, $time, name, tr.psdisplay());
endfunction
endclass

The parameterized class subscribe needs to be written only once, and can then be used with any class that implements an observe() function:

class yet_another_observer;
string name;
function new(string name, vmm_notify ntfy, int id);
subscribe#(yet_another_observer) cb = new(this, ntfy, id);
this.name = name;
endfunction
function void observe(vmm_data tr);
$write(“At %0d on %s: %s\n”, $time, name, tr.psdisplay());
endfunction
endclass

Look for a pre-defined vmm_notify callback subscription class template in the next VMM release…

Posted in Communication | 3 Comments »

How to use VMM callbacks

Posted by Janick Bergeron on 19th November 2008

One of the major benefits of using VMM is that it helps create reusable and extensible transactors and verification environments. One of the key extensibility features of VMM is the “callback”.

The following code sample shows a transactor with its core functionality implemented in the main() task.

   class my_xactor extends vmm_xactor;
      ...
      task main();
         ...
         forever begin
            ...
            in_chan.get(in_tr);    // Get input transaction
            ...                    // Execute transaction
            out_chan.put(out_tr);  // Generate output transaction
            ...
         end
      endtask
   endclass

How would you modify this transactor to accommodate DUT or test-specific features such as saving the output transactions in a scoreboard, injecting errors or adding functional coverage? One option would be to modify the transactor itself and change the functionality in the main() task as shown in the code below:

   class my_xactor extends vmm_xactor;
      ...
      task main();
         ...
         forever begin
            ...
            in_chan.get(in_tr);    // Get input transaction
            if ($urandom % 10 < 2) in_tr.checksum = $random;
            ...                    // Execute transaction
            sb.expect(out_tr);
            out_chan.put(out_tr);  // Generate output transaction
            ...
         end
      endtask
   endclass

However, this makes the transactor not reusable for other DUTs or tests. You must avoid making DUT or test-specific modifications to reusable code. This is a problem that is generic to reusable software, not just verification, and the object-oriented world provides a solution to this problem: virtual methods. Virtual methods can be used to provide extension points at relevant point in the functionality of the transactor:

   class my_xactor extends vmm_xactor;
      virtual task pre_exec(in_trans tr);
      endtask
      virtual task post_exec(out_trans tr);
      endtask
      ...
      task main();
         ...
         forever begin
            ...
            in_chan.get(in_tr);    // Get input transaction
            pre_exec(in_tr);
            ...                    // Execute transaction
            post_exec(out_tr);
            out_chan.put(out_tr);  // Generate output transaction
            ...
         end
      endtask
   endclass

VMM guidelines 4-155 through 4-158 provide recommendations for useful extension points through virtual methods. Virtual methods should also have suitable argument allowing the behavior of the transactor to be observed or modified as necessary. It is now possible to provide DUT or test-specific extensions of the transactor without modifying the reusable code:

   class my_dut_xactor extends my_xactor;
      virtual task post_exec(out_trans tr);
         sb.expect(tr);
      endtask
   endclass

   class my_test_xactor extends my_dut_xactor;
      virtual task pre_exec(in_trans tr);
         if ($urandom % 10 < 2) in_tr.checksum = $random;
      endtask
   endclass

There are two problems though. First, each time you extend the transactor, you create a new class type but the verification environment needs to be written using a class type that is known a priori; if the class type changes for every test, the environment itself will need to be changed for every test. Second, to combine orthogonal extensions, you must extend the previously extended class type, creating a linear chain of new class types; combining N different orthogonal extensions leads to potentially having to create and maintain 2N classes.

Object-oriented programming provide several well-known approaches and solutions to common problem called “patterns“. Patterns are not base classes or libraries. They are techniques and examples on how to structure object-oriented code to solve a specific challenge. Different patterns can be used, alone or in combination.

The first problem can be solve using the “abstract factory” pattern. But it introduces several limitations. For example, it requires an argument-free constructor. This has the consequence of requiring separate methods for transactor configuration and connection. VMM was designed to use the simple and well-known pins-and-wire connectivity model–which has been used to connect Verilog modules for many, many years and is well understood by all design and verification engineers. Just like Verilog modules are instantiated with their configuration parameters and port connections specified at the same time, so are VMM transactors. And this requires that they be passed as arguments via the constructor. Second, the factory pattern is used only at the time the transactor is created. This creates a static lifetime for the transactor and its extensions. It is not possible to dynamically introduce or remove a transactor extension (such as error injection) during the execution of a test.

The better pattern to use for transactor extension is the “facade pattern”. It calls for the separation of the virtual methods into a separate class, called the “facade”. The transactor will then call the virtual methods through that facade class.

   class my_xactor_cbs;
      virtual task pre_exec(in_trans tr);
      endtask
      virtual task post_exec(out_trans tr);
      endtask
   endclass

   class my_xactor extends vmm_xactor;
      my_xactor_cbs cbs;
      ...
      task main();
         ...
         forever begin
            ...
            in_chan.get(in_tr);    // Get input transaction
            if (cbs != null) cbs.pre_exec(in_tr);
            ...                    // Execute transaction
            if (cbs != null) cbs.post_exec(out_tr);
            out_chan.put(out_tr);  // Generate output transaction
            ...
         end
      endtask
   endclass

Using the facade pattern, the environment can be written using the original “my_xactor” class and your extensions can be introduced in the existing transactor instance by assigning to the “cbs” facade instance. Extensions can also be dynamically introduced or removed as the facade instance can be modified at any time.

   class my_dut_cbs extends my_xactor_cbs;
      ...
      virtual task post_exec(out_trans tr);
         sb.expect(tr);
      endtask
   endclass

   class my_test_cbs extends my_dut_cbs;
      ...
      virtual task pre_exec(in_trans tr);
         if ($urandom % 10 < 2) in_tr.checksum = $random;
      endtask
   endclass

   class tb_env extends vmm_env;
      my_xactor xact;
      ...
      virtual function void build();
         ...
         xact = new(...);
         begin
            my_dut_cbs cb = new(...);
            xact.cbs = cb;
         end
         ...
      endfunction
      ...
   endclass

   program test;
   initial begin
      tb_env env = new();
      my_test_cbs err_ext = new(...);
      fork
         #1000 env.xact.cbs = err_ext;
      join_none
      env.run();
   end
   endprogram

The facade pattern solves the new class type issue, but not the exponential number of class extensions. If a “chain of responsibilityobserver” pattern is used in combination with the facade pattern, it is possible to register multiple facade instances with the same transactor, each providing an orthogonal extension.

   class my_dut_cbs extends my_xactor_cbs;
      ...
      virtual task post_exec(out_trans tr);
         sb.expect(tr);
      endtask
   endclass

   class my_test_cbs extends my_xactor_cbs;
      virtual task pre_exec(in_trans tr);
         if ($urandom % 10 < 2) in_tr.checksum = $random;
      endtask
   endclass

   class tb_env extends vmm_env;
      my_xactor xact;
      ...
      virtual function void build();
         ...
         xact = new(...);
         begin
            my_dut_cbs cb = new(...);
            xact.cbs.push_back(cb);
         end
         ...
      endfunction
      ...
   endclass

   program test;
   initial begin
      tb_env env = new();
      my_test_cbs err_ext = new(...);
      fork
         #1000 env.xact.cbs.push_back(err_ext);
      join_none
      env.run();
   end
   endprogram

The combination of the facade and chain-of-responsibilityobserver patterns creates what is known as VMM callbacks. VMM provides some pre-defined functionality to support the callbacks: the “vmm_xactor_callbacks” base class, the vmm_xactor::prepend_callback(), vmm_xactor::append_callback() and vmm_xactor::unregister_callback() and the `vmm_callback macro. Using the provided VMM functionality, a reusable transactor would have the following structure:

   class my_xactor_cbs extends vmm_xactor_callback;
      virtual task pre_exec(in_trans tr);
      endtask
      virtual task post_exec(out_trans tr);
      endtask
   endclass

   class my_xactor extends vmm_xactor;
      ...
      task main();
         ...
         forever begin
            ...
            in_chan.get(in_tr);    // Get input transaction
            `vmm_callback(my_xactor_cbs, pre_exec(in_tr));
            ...                    // Execute transaction
            `vmm_callback(my_xactor_cbs, post_exec(out_tr));
            out_chan.put(out_tr);  // Generate output transaction
            ...
         end
      endtask
   endclass

The environment can provide a callback extension to provide DUT-specific functionality that is shared by all tests:

   class my_dut_cbs extends my_xactor_cbs;
      ...
      virtual task post_exec(out_trans tr);
         sb.expect(tr);
      endtask
   endclass

   class tb_env extends vmm_env;
      my_xactor xact;
      ...
      virtual function void build();
         ...
         xact = new(...);
         begin
            my_dut_cbs cb = new(...);
            xact.append_callback(cb);
         end
         ...
      endfunction
      ...
   endclass

And a test can similarly provide additional callback extensions in addition to the DUT-specific extensions already registered in the environment. Each test can extend a transactor differently according to its need:

   class my_test_cbs extends my_xactor_cbs;
      virtual task pre_exec(in_trans tr);
         if ($urandom % 10 < 2) in_tr.checksum = $random;
      endtask
   endclass

   program test;
   initial begin
      tb_env env = new();
      my_test_cbs err_ext = new(...);
      fork
         #1000 env.xact.append_calback(err_ext);
      join_none
      env.run();
   end
   endprogram

You will find additional guidelines and examples on using and implementing callbacks in the VMM book pp. 198-201 and pp 221-225.

Posted in Callbacks, Communication, Structural Components | 4 Comments »