Verification Martial Arts: A Verification Methodology Blog

Archive for November, 2008

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 | 6 Comments »

Where did this blog go?

Posted by Janick Bergeron on 6th November 2008

Darn. Haven’t posted in over three months!

My appologies to readers: these past few months have been crazy! Two trips to India, one to Japan and many to various parts of North America. Made my 1K status with United in mid-october this year. Lots of customer visits, working on lots of new VMM stuff.

I’ll be back on a regular schedule soon.

Posted in VMM | No Comments »