Tudor timisescu also known as the verification gentleman in verification community posted this question on twitter.

His question was, can we create transition coverage using cross between two different types of objects? He named it as heterogeneous cross.

His requirement has very useful application in CPU verification to cover transitions of different instructions. For RISC-V (and basically all other ISAs), different instructions have different formats, so you end up with cases where you get such heterogeneous transitions.

So, let’s jump into understanding the question further. I know it’s not easy to understand it on first impression. So let’s do bit of deep dive into question. Followed by that we will take a look in to one of the proposed solution and scalable automation using the code generation approach.

Question:

Can we do heterogeneous cross coverage in SystemVerilog?

Partial screenshot of the question on twitter.

Tudor clarifies the question in his own words.

Heterogeneous cross coverage is cross between two different object types.

Let me clarify by what I mean with heterogeneous. First, I’m trying to model some more involved form of transition coverage. I imagine the best way to do this is using cross coverage between the current operation and the previous operation.

Assuming you only have one category of operations, O, each with a set of properties P0, P1, P2, … it’s pretty easy to write this transition coverage. Let the tilde (‘) denote previous. The cross would be between the values of P0, P1, P2, … and P0′, P1′, P2′, …

If you have two categories of operations, Oa and Ob, each with different sets of properties: Pa0, Pa1, …, Pam for Oa and Pb0, Pb1, …, Pbn (with m and n possibly different), the cross gets a bit more involved.

If the current operation is of type Oa and the previous is of type Oa, then you want to cover like in the case where all operations are the same (i.e. Pa0, Pa1, …, Pa0′, Pa1′). This also goes for when both are of type Ob.

If the current operation is of type Oa and the previous is of type Ob, then what you want to cross is something like Pa0, Pa1, Pa2, …, Pb0′, Pb1′, Pb2’, … The complementary case with the operation types switched is analogous to this one.

I don’t see any way of writing this in #SystemVerilog without having 4 distinct covergroups (one for each type transition).

Imagine you add a third operation type, Oc, and suddenly you need 9 covergroups you need to write.

The more you add, the more code you need and it’s all pretty much boilerplate.

The only thing  that the test bench writer needs to provide are definitions for the cross of all properties of each operation. Since it’s not possible to define covergroup items (coverpoints and crosses) in such a way that they can be reused inside multiple covergroup definitions, the only solution I see is using macros.

Code generation would be a more robust solution, but that might be more difficult to set up.

Solution code snippet:

He was kind enough to provide the solution for it as well. So what was he looking for? He was looking for, is there any easier and scalable ways to solve it?

Following are the two different data types that we want to cross.

When you create all 4 possible combinations of transition crosses, it would look as following:

I thought we could follow the precedence of scientific community and refer the heterogeneous cross as “Tudor cross” for formulating the problem and defining the solution.

Real life use cases for Tudor cross

Okay, before we invest our valuable time understanding automation are there any real life use cases?

Tudor was facing this real problem for project he worked on related to critical pieces of security. For confidentiality reasons he could not provide any more details about it. He was kind enough to share another example where this type of problem would be faced again and hence the solution would be useful.

In Tudor’s own words, an example from the top of my head (completely unrelated to the one I was working on) where this might be useful is if you have to cover transitions of different instructions. For RISC-V (and basically all other ISAs), different instructions have different formats, so you end up with cases where you get such heterogeneous transitions.

The same CPU will be executing all of those instructions and you can get into situations that the previous instruction busted something that will cause the current instruction to lock up, which is why you want to at least test all transitions.

One step even further is if you also add the state of the CPU to the cross. Different parts of the state are relevant to different instructions. It could be that transition a -> b is fine in state Sa0, but is buggy in state Sa1.

The CPU example is maybe even better than my concrete use case where only a history of 1 is needed. A fancy CPU has a deep pipeline and it may well be the case that the depth of the pipeline should be the length of the history for which you collect these transitions. Even for lengths of 2 it becomes a huge problem, too laborious to write by hand.

Automation using code generation

Here we apply two concepts:

High-level modeling of operation is done in very limited way to keep the solution simple. We have done it for USB power delivery protocol layer over here, which is much more involved than this problem.

What are the benefits of this approach?

Solution can easily scale for:

Why shouldn’t I write a custom SystemVerilog covergroup code generator as well?

Yes, you can write it. It’s not such a big effort for specific problem. But if you are embarking in this direction, you will need more and more capabilities. That’s where the well thought out and tested library will save you lot of time allowing you to focus on the real problem.

Implementation:

Steps involved in automation are following:

If you are interested in programmable number of stages solution, just drop me an email: anand@verifsudha.com

Following code shows both the python input and generated SystemVerilog code output.


#!/home/utils/Python-2.7.5/bin/python
########################################################################################
#
#  Copyright (C) VerifSudha Technologies Pvt. Ltd.  - All Rights Reserved 2016-18
#  Unauthorized copying of this file, via any medium is strictly prohibited
#  Proprietary and confidential
#  Written by Anand Shirahatti ,
# 
#  Title: Heterogeneous transition coverage using cross 
#  Description: @TudorTimi posed this question on twitter
#  Let me clarify by what I mean with heterogeneous. First, I'm trying to model some more 
#  involved form of transition coverage. I imagine the best way to do this is using cross 
#  coverage between the current operation and the previous operation. 
#  
#  Assuming you only have one category of operations, O, each with a set of properties 
#  P0, P1, P2, ..., it's pretty easy to write this transition coverage. Let the tilde (') 
#  denote previous. The cross would be between the values of P0, P1, P2, ... and P0', P1', P2', ... 
# 
#  If you have two categories of operations, Oa and Ob, each with different sets of properties: 
#  Pa0, Pa1, ..., Pam for Oa and Pb0, Pb1, ..., Pbn (with m and n possibly different), the cross 
#  gets a bit more involved.
#
#  If the current operation is of type Oa and the previous is of type Oa, then you want to cover 
#  like in the case where all operations are the same (i.e. Pa0, Pa1, ..., Pa0', Pa1'). This also 
#  goes for when both are of type Ob.
#
#
#  If the current operation is of type Oa and the previous is of type Ob, then what you want to cross 
#  is something like Pa0, Pa1, Pa2, ..., Pb0', Pb1', Pb2', ... The complementary case with the operation 
#  types switched is analogous to this one.
#
#  I don't see any way of writing this in #SystemVerilog without having 4 distinct covergroups 
#  (one for each type transition).
#
#  Imagine you add a third operation type, Oc, and suddenly you need 9 covergroups you need to write.
#
#  The more you add, the more code you need and it's all pretty much boilerplate. The only thing that the 
#  TB writer needs to provide are definitions for the cross of all properties of each operation.
#  Since it's not possible to define covergroup items (coverpoints and crosses) in such a way that they can be 
#  reused inside multiple covergroup definitions, the only solution I see is using macros.

#  Code generation would be a more robust solution, but that might be more difficult to set up.
#  
#  Category: REALLIFE 
#  Status: PUBLISH 
#
########################################################################################

from curiosity_user_common_utils import *

import itertools

class curiosity_heterogeneous_cross(curiosity_user_lib):

    def set_dut_spec(self, **dut_spec_dict):
         self.dut_spec =  copy.deepcopy(dut_spec_dict)
    
         # Set default value for all entries
         self.set_default_value_wbv_entry(
            WBV_CLOCK = self.dut_spec['__DUT_CLK__'], 
            WBV_RESET = self.dut_spec['__DUT_RSTN__'],
            WBV_USR_META_VAR2VAL = self.dut_spec,
            WBV_LOOP_LIST        = [''],)

    # Helper routine that creates the cross from properties of operations 
    def add_cross_for_property(self, wbv_entry, wbv_name_str, prop_list, prefix_str, cross_name):

       with add_cross_entry(wbv_entry,
           CROSS_NAME = cross_name,
       )as cross_entry:

        for prop_entry in prop_list:
           cp_name_str  = wbv_name_str + '.cp_' + prefix_str + '_' + prop_entry 
           print cp_name_str  
           cross_entry.add_coverpoint(
              COVERPOINT_NAME = cp_name_str)  

    # Helper routine to create cross of crosses from list 
    def add_cross_of_cross(self, wbv_entry, wbv_name_str, cross_list, cross_name):

       with add_cross_entry(wbv_entry,
           CROSS_NAME = cross_name,
       )as cross_entry:

        for cross_name_str in cross_list:
           cr_name_str  = wbv_name_str + '.' + cross_name_str 
           cross_entry.add_cross(
              CROSS_NAME = cr_name_str)  


 
    # Helper routine that adds the properties of operations to create a coverpoint 
    def add_coverpoints_for_property(self, wbv_entry, property_list, prefix_str):
        for prop_name_str in property_list:
           si_name_str  = prefix_str + '_' + prop_name_str
           print si_name_str   
           obj_prop_str = prefix_str + '.' + prop_name_str     
           wbv_entry.add_wbv_si_entry(
             SI_NAME         = si_name_str,
             SIGNAL_OR_EXPR  = obj_prop_str)


    # Creates a covergroup of transitions of specified depth defined the number of elements in operations_tuple
    # For each element in the argument operations_tuple the operations_dict provides the information about its
    # object data type and name of the property. Property data type and values can be added in needed 
    # Using this information it creates the covergroup, coverpoint for each of properties and then
    # transition of defined depth using cross of all individual stage coverpoints   
    def add_covergroup_operation_transitions(self, operations_tuple, operations_dict): 

        # Create the CG name like a_after_a or a_after_b of programmable depth
        # Create the object to be passed as arguments to CG 
        sample_arguments = ''
        cg_name_str      = '' 

        operation_prev = operations_tuple[0] # Ex: 0c 
        operation_curr = operations_tuple[1] # Ex: 0a

        data_type_prev = operations_dict[operation_prev]['DATA_TYPE'] + ' ' + 'prev' 
        data_type_curr = operations_dict[operation_curr]['DATA_TYPE'] + ' ' + 'curr' 
      
        # Creates ex: operation_c prev , operation_a curr  
        sample_arguments = data_type_prev + ' ' + 'prev' + ' , ' +  data_type_curr + ' ' + 'curr'
        sample_event = 'with function sample' + '(' + sample_arguments + ')'  
        
        # Creates ex: Oc_after_Oa 
        cg_name_str  = operation_prev + '_after_' +  operation_curr 


        # Curiosity Library object: Create the CG, coverpoints and crosses  
        with add_wbv_entry(self,
             WBV_NAME             = cg_name_str,
             WBV_CLOCK            = sample_event,
             WBV_TYPE             = 'WBV_SIGNAL_VALUE',
             WBV_FUNCTIONAL_COV   = '1',
             WBV_DESCRIPTION      = 'Multistage transition coverage') as wbv_entry:

             # Add properties of prev object type as coverpoints  
             self.add_coverpoints_for_property(wbv_entry, operations_dict[operation_prev]['PROPERTY_LIST'], 'prev')
             # Add properties of curr object type as coverpoints
             self.add_coverpoints_for_property(wbv_entry, operations_dict[operation_curr]['PROPERTY_LIST'], 'curr') 

             # Add a cross from all the coverpoints of prev object
             self.add_cross_for_property(wbv_entry, cg_name_str, operations_dict[operation_prev]['PROPERTY_LIST'], 'prev', 'prev_cross') 
             # Add a cross from all the coverpoints of curr object
             self.add_cross_for_property(wbv_entry, cg_name_str, operations_dict[operation_curr]['PROPERTY_LIST'], 'curr', 'curr_cross') 

             # Add the cross of curr properties and prev properties
             self.add_cross_of_cross(wbv_entry, cg_name_str, ['prev_cross', 'curr_cross'], 'all_cur_after_all_prev') 

 
    # This is the top level function where all the top level actions takes place 
    def gen_coverage(self):

        # Number of repeated permutations (cartesian product) of operations required  
        transition_depth_int = 2 
       
        # High level specification of operations, their data type and property names
        # Values of properties or data types of properties can be added here if required in future   
        operations_dict = {
           'Oa' : {
               'DATA_TYPE'        : 'operation_a',
               'PROPERTY_LIST'    : ['prop_0_a', 'prop_1_a'],
           },

           'Ob' : {
               'DATA_TYPE'        : 'operation_b',
               'PROPERTY_LIST'    : ['prop_0_b', 'prop_1_b', 'prop_2_b'],
           },

           'Oc' : {
               'DATA_TYPE'        : 'operation_c',
               'PROPERTY_LIST'    : ['prop_0_c', 'prop_1_c', 'prop_2_c', 'prop_3_c'],
           },


        }
 
        # Get the names of all operations 
        operations_list = operations_dict.keys() 

        # permutation with repetitions for among all operations types for defined depth of transitions called as stages
        operations_cartesian_product = list(itertools.product(operations_list, repeat=transition_depth_int)) 

        # For each combinations [( Oa, Oa), (Ob, Ob), (Oa, Ob), (Ob, Oa)] crate a covregroup with
        # with transition crosses  
        for operation_combo_tuple in operations_cartesian_product:
           self.add_covergroup_operation_transitions(operation_combo_tuple, operations_dict) 



def main():
    curiosity_heterogeneous_cross_obj = curiosity_heterogeneous_cross() # Create the user lib for easing the wb_input entries creation

    # Setup DUT attributes  
    curiosity_heterogeneous_cross_obj.set_dut_spec(
        __DUT_CLK__      = 'dut.clk',
        __DUT_RSTN__     = '!dut.resetn', 

    )  

    curiosity_heterogeneous_cross_obj.gen_coverage()


## Call the main ##

if __name__ == '__main__':
    main()


//File Name: curiosity_wb_fcov_cg.sv
//##################################################################################################################
//
//  Copyright (C) VerifSudha Technologies Pvt. Ltd.  - All Rights Reserved 2018
//  Unauthorized copying of this file, via any medium is strictly prohibited
//  Proprietary and confidential
//  Written by curiosity ,
//
//  Generated file - Donot edit manually. It will be overwritten !
//
//  File generated with the command:
//  ./curiosity -ips ../../examples/curiosity_ex_heterogeneous_prev_curr.py
//
//##################################################################################################################

covergroup Oc_after_Oc with function sample(operation_c prev prev , operation_c curr curr);
	option.comment = "Multistage transition coverage";
	option.name = "Oc_after_Oc";
	option.per_instance = 1;

	// Cover points 
	cp_prev_prop_0_c : coverpoint prev.prop_0_c iff (!dut.resetn);
	cp_prev_prop_1_c : coverpoint prev.prop_1_c iff (!dut.resetn);
	cp_prev_prop_2_c : coverpoint prev.prop_2_c iff (!dut.resetn);
	cp_prev_prop_3_c : coverpoint prev.prop_3_c iff (!dut.resetn);

	cp_curr_prop_0_c : coverpoint curr.prop_0_c iff (!dut.resetn);
	cp_curr_prop_1_c : coverpoint curr.prop_1_c iff (!dut.resetn);
	cp_curr_prop_2_c : coverpoint curr.prop_2_c iff (!dut.resetn);
	cp_curr_prop_3_c : coverpoint curr.prop_3_c iff (!dut.resetn);
	
        // Cross coverage 
	prev_cross : cross cp_prev_prop_0_c, cp_prev_prop_1_c, cp_prev_prop_2_c, cp_prev_prop_3_c;
	curr_cross : cross cp_curr_prop_0_c, cp_curr_prop_1_c, cp_curr_prop_2_c, cp_curr_prop_3_c;

	all_cur_after_all_prev : cross prev_cross, curr_cross;

endgroup : Oc_after_Oc




covergroup Oc_after_Ob with function sample(operation_c prev prev , operation_b curr curr);
	option.comment = "Multistage transition coverage";
	option.name = "Oc_after_Ob";
	option.per_instance = 1;

	// Cover points 
	cp_prev_prop_0_c : coverpoint prev.prop_0_c iff (!dut.resetn);
	cp_prev_prop_1_c : coverpoint prev.prop_1_c iff (!dut.resetn);
	cp_prev_prop_2_c : coverpoint prev.prop_2_c iff (!dut.resetn);
	cp_prev_prop_3_c : coverpoint prev.prop_3_c iff (!dut.resetn);

	cp_curr_prop_0_b : coverpoint curr.prop_0_b iff (!dut.resetn);
	cp_curr_prop_1_b : coverpoint curr.prop_1_b iff (!dut.resetn);
	cp_curr_prop_2_b : coverpoint curr.prop_2_b iff (!dut.resetn);

	// Cross coverage 
	prev_cross : cross cp_prev_prop_0_c, cp_prev_prop_1_c, cp_prev_prop_2_c, cp_prev_prop_3_c;
	curr_cross : cross cp_curr_prop_0_b, cp_curr_prop_1_b, cp_curr_prop_2_b;

	all_cur_after_all_prev : cross prev_cross, curr_cross;

endgroup : Oc_after_Ob




covergroup Oc_after_Oa with function sample(operation_c prev prev , operation_a curr curr);
	option.comment = "Multistage transition coverage";
	option.name = "Oc_after_Oa";
	option.per_instance = 1;

	// Cover points 
	cp_prev_prop_0_c : coverpoint prev.prop_0_c iff (!dut.resetn);
	cp_prev_prop_1_c : coverpoint prev.prop_1_c iff (!dut.resetn);
	cp_prev_prop_2_c : coverpoint prev.prop_2_c iff (!dut.resetn);
	cp_prev_prop_3_c : coverpoint prev.prop_3_c iff (!dut.resetn);

	cp_curr_prop_0_a : coverpoint curr.prop_0_a iff (!dut.resetn);
	cp_curr_prop_1_a : coverpoint curr.prop_1_a iff (!dut.resetn);

	// Cross coverage 
	prev_cross : cross cp_prev_prop_0_c, cp_prev_prop_1_c, cp_prev_prop_2_c, cp_prev_prop_3_c;
	curr_cross : cross cp_curr_prop_0_a, cp_curr_prop_1_a;

	all_cur_after_all_prev : cross prev_cross, curr_cross;

endgroup : Oc_after_Oa




covergroup Ob_after_Oc with function sample(operation_b prev prev , operation_c curr curr);
	option.comment = "Multistage transition coverage";
	option.name = "Ob_after_Oc";
	option.per_instance = 1;

	// Cover points 
	cp_prev_prop_0_b : coverpoint prev.prop_0_b iff (!dut.resetn);
	cp_prev_prop_1_b : coverpoint prev.prop_1_b iff (!dut.resetn);
	cp_prev_prop_2_b : coverpoint prev.prop_2_b iff (!dut.resetn);

	cp_curr_prop_0_c : coverpoint curr.prop_0_c iff (!dut.resetn);
	cp_curr_prop_1_c : coverpoint curr.prop_1_c iff (!dut.resetn);
	cp_curr_prop_2_c : coverpoint curr.prop_2_c iff (!dut.resetn);
	cp_curr_prop_3_c : coverpoint curr.prop_3_c iff (!dut.resetn);

	// Cross coverage 
	prev_cross : cross cp_prev_prop_0_b, cp_prev_prop_1_b, cp_prev_prop_2_b;
	curr_cross : cross cp_curr_prop_0_c, cp_curr_prop_1_c, cp_curr_prop_2_c, cp_curr_prop_3_c;

	all_cur_after_all_prev : cross prev_cross, curr_cross;

endgroup : Ob_after_Oc




covergroup Ob_after_Ob with function sample(operation_b prev prev , operation_b curr curr);
	option.comment = "Multistage transition coverage";
	option.name = "Ob_after_Ob";
	option.per_instance = 1;

	// Cover points 
	cp_prev_prop_0_b : coverpoint prev.prop_0_b iff (!dut.resetn);
	cp_prev_prop_1_b : coverpoint prev.prop_1_b iff (!dut.resetn);
	cp_prev_prop_2_b : coverpoint prev.prop_2_b iff (!dut.resetn);

	cp_curr_prop_0_b : coverpoint curr.prop_0_b iff (!dut.resetn);
	cp_curr_prop_1_b : coverpoint curr.prop_1_b iff (!dut.resetn);
	cp_curr_prop_2_b : coverpoint curr.prop_2_b iff (!dut.resetn);

	// Cross coverage 
	prev_cross : cross cp_prev_prop_0_b, cp_prev_prop_1_b, cp_prev_prop_2_b;
	curr_cross : cross cp_curr_prop_0_b, cp_curr_prop_1_b, cp_curr_prop_2_b;

	all_cur_after_all_prev : cross prev_cross, curr_cross;

endgroup : Ob_after_Ob




covergroup Ob_after_Oa with function sample(operation_b prev prev , operation_a curr curr);
	option.comment = "Multistage transition coverage";
	option.name = "Ob_after_Oa";
	option.per_instance = 1;

	// Cover points 
	cp_prev_prop_0_b : coverpoint prev.prop_0_b iff (!dut.resetn);
	cp_prev_prop_1_b : coverpoint prev.prop_1_b iff (!dut.resetn);
	cp_prev_prop_2_b : coverpoint prev.prop_2_b iff (!dut.resetn);

	cp_curr_prop_0_a : coverpoint curr.prop_0_a iff (!dut.resetn);
	cp_curr_prop_1_a : coverpoint curr.prop_1_a iff (!dut.resetn);

	// Cross coverage 
	prev_cross : cross cp_prev_prop_0_b, cp_prev_prop_1_b, cp_prev_prop_2_b;
	curr_cross : cross cp_curr_prop_0_a, cp_curr_prop_1_a;

	all_cur_after_all_prev : cross prev_cross, curr_cross;

endgroup : Ob_after_Oa




covergroup Oa_after_Oc with function sample(operation_a prev prev , operation_c curr curr);
	option.comment = "Multistage transition coverage";
	option.name = "Oa_after_Oc";
	option.per_instance = 1;

	// Cover points 
	cp_prev_prop_0_a : coverpoint prev.prop_0_a iff (!dut.resetn);
	cp_prev_prop_1_a : coverpoint prev.prop_1_a iff (!dut.resetn);

	cp_curr_prop_0_c : coverpoint curr.prop_0_c iff (!dut.resetn);
	cp_curr_prop_1_c : coverpoint curr.prop_1_c iff (!dut.resetn);
	cp_curr_prop_2_c : coverpoint curr.prop_2_c iff (!dut.resetn);
	cp_curr_prop_3_c : coverpoint curr.prop_3_c iff (!dut.resetn);

	// Cross coverage 
	prev_cross : cross cp_prev_prop_0_a, cp_prev_prop_1_a;
	curr_cross : cross cp_curr_prop_0_c, cp_curr_prop_1_c, cp_curr_prop_2_c, cp_curr_prop_3_c;

	all_cur_after_all_prev : cross prev_cross, curr_cross;

endgroup : Oa_after_Oc




covergroup Oa_after_Ob with function sample(operation_a prev prev , operation_b curr curr);
	option.comment = "Multistage transition coverage";
	option.name = "Oa_after_Ob";
	option.per_instance = 1;

	// Cover points 
	cp_prev_prop_0_a : coverpoint prev.prop_0_a iff (!dut.resetn);
	cp_prev_prop_1_a : coverpoint prev.prop_1_a iff (!dut.resetn);

	cp_curr_prop_0_b : coverpoint curr.prop_0_b iff (!dut.resetn);
	cp_curr_prop_1_b : coverpoint curr.prop_1_b iff (!dut.resetn);
	cp_curr_prop_2_b : coverpoint curr.prop_2_b iff (!dut.resetn);

	// Cross coverage 
	prev_cross : cross cp_prev_prop_0_a, cp_prev_prop_1_a;
	curr_cross : cross cp_curr_prop_0_b, cp_curr_prop_1_b, cp_curr_prop_2_b;

	all_cur_after_all_prev : cross prev_cross, curr_cross;

endgroup : Oa_after_Ob




covergroup Oa_after_Oa with function sample(operation_a prev prev , operation_a curr curr);
	option.comment = "Multistage transition coverage";
	option.name = "Oa_after_Oa";
	option.per_instance = 1;

	// Cover points 
	cp_prev_prop_0_a : coverpoint prev.prop_0_a iff (!dut.resetn);
	cp_prev_prop_1_a : coverpoint prev.prop_1_a iff (!dut.resetn);

	cp_curr_prop_0_a : coverpoint curr.prop_0_a iff (!dut.resetn);
	cp_curr_prop_1_a : coverpoint curr.prop_1_a iff (!dut.resetn);

	// Cross coverage 
	prev_cross : cross cp_prev_prop_0_a, cp_prev_prop_1_a;
	curr_cross : cross cp_curr_prop_0_a, cp_curr_prop_1_a;

	all_cur_after_all_prev : cross prev_cross, curr_cross;

endgroup : Oa_after_Oa