Showing posts with label coverbins. Show all posts
Showing posts with label coverbins. Show all posts

Thursday, June 23, 2022

Utilizing Enumerations in Functional Coverage for Parameterized Buses & Address Spaces

I am a fan of utilizing enumerations in functional coverage. One such use was presented in a previous post, lets take a look at another use that can simplify both implementation and maintenance for parameterized bus widths. 

The example design for this post supports a parameterized address bus, each instance has the potential of a different address width, a maximum of up to thirty two bits is never exceeded. Lets also define the coverage bins for the bus as bit based where the highest set MSB is collected per sampled value. 

What would be the most basic functional coverage implementation? Coding coverpoint bins to the maximum bus width of thirty two bits guarantees support for all bus widths, instances utilizing narrow widths will require coverage exclusions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class block_cov;
  covergroup block_cg with function sample(bit[31:0] address);
    address_ranges_c : coverpoint address {
      bins BIT0  = {[('h0)   :( ('h1<<1)-1 )]};
      bins BIT1  = {[('h1<<1):( ('h1<<2)-1 )]};
      bins BIT2  = {[('h1<<2):( ('h1<<3)-1 )]};
      bins BIT3  = {[('h1<<3):( ('h1<<4)-1 )]};
      bins BIT4  = {[('h1<<4):( ('h1<<5)-1 )]};
      bins BIT5  = {[('h1<<5):( ('h1<<6)-1 )]};
      //..
      //..
      bins BIT31 = {['h1<<31:$]};

    } 
  endgroup: block_cg
  //-----------------
  function new();
    block_cg = new;
  endfunction
endclass: block_cov

Since this post is about enumerations and functional coverage, the first step toward the sought solution is to covert the coverpoint from a bit based variable to an enumeration type. This conversion will require populating an enumeration variable before calling the covergroup sample method, a wrapper method to the sample call provides this ability. While this change may initially appear cumbersome the full picture will be clear shortly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
typedef enum {
  RANGE_BIT_0 = 0,
  RANGE_BIT_1,
  RANGE_BIT_2,
  RANGE_BIT_3,
  RANGE_BIT_4,
  
  //...
  //...
  
  RANGE_BIT_30,
  RANGE_BIT_31
} address_range_e;

//----------------------------------------------------------------------
class block_cov;
  covergroup block_cg with function sample(address_range_e address_range);
    address_ranges_c : coverpoint address_range;  
  endgroup: block_cg
  //-----------------
  function new();
    block_cg = new;
  endfunction
  //-----------------
  function void do_sample(bit[31:0] address);
    address_range_e range;
    
    case(address) inside 
      [('h0)    :( ('h1<<1) -1 )]: range = RANGE_BIT_0; 
      [('h1<<1) :( ('h1<<2) -1 )]: range = RANGE_BIT_1; 
      [('h1<<2) :( ('h1<<3) -1 )]: range = RANGE_BIT_2; 
      [('h1<<3) :( ('h1<<4) -1 )]: range = RANGE_BIT_3; 
      [('h1<<4) :( ('h1<<5) -1 )]: range = RANGE_BIT_4; 
      
      //...
      //...

      [('h1<<30):( ('h1<<31)-1 )]: range = RANGE_BIT_30; 
      [('h1<<31):( $           )]: range = RANGE_BIT_31; 
    endcase

    block_cg.sample( .address_range(range) );

  endfunction: do_sample
endclass: block_cov

Now that some groundwork has been laid, lets begin optimizing this code one section at a time. First up would be to recode the enumeration definition be taking advantage of enumeration type ranges as described in the LRM 1800-2017 section 6.19.2.

1
2
3
4
5
`define ADR_WIDTH 10 
//----------------------------------------------------------------------
typedef enum {
  RANGE_BIT_[`ADR_WIDTH] = 0
} address_range_e;

Now lets take another look at the population of the enumeration variable. It doesn't require more that a quick glance to recognize that this code section is both bug and maintenance prone. The same result can be produced by searching for the highest set MSB with a loop. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  function void do_sample(bit[31:0] address);
    address_range_e range;
    
    range = RANGE_BIT_0; // will default to this if address is zero
    for(int bit_num=`ADR_WIDTH; bit_num; bit_num--) begin
      if (address[bit_num-1] == 1) begin
        range = address_range_e'(bit_num-1);
        break;
      end
    end

    block_cg.sample( .address_range(range) );

  endfunction: do_sample

By taking advantage of the bit based range definition, the enumeration population can go through another optimization step by utilizing system tasks that further simplify this code section.

1
2
3
4
5
6
7
8
9
  function void do_sample(bit[31:0] address);
    address_range_e range;
    
    if(address == 0) range  = RANGE_BIT_0;
    else             range  = address_range_e'($onehot(address)? $clog2(address) : $clog2(address)-1);
    
    block_cg.sample( .address_range(range) );

  endfunction: do_sample

Now that all the building blocks are ready lets put them all together creating elegant, clear and easily maintainable code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//----------------------------------------------------------------------
`define ADR_WIDTH 10 
//----------------------------------------------------------------------
typedef enum {
  RANGE_BIT_[`ADR_WIDTH] = 0
} address_range_e;
//----------------------------------------------------------------------
class block_cov;
  covergroup block_cg with function sample(address_range_e address_range);
    address_ranges_c : coverpoint address_range;  
  endgroup: block_cg
  //-----------------
  function new();
    block_cg = new;
  endfunction
  //-----------------
  function void do_sample(bit[31:0] address);
    address_range_e range;
    
    if(address == 0) range  = RANGE_BIT_0;
    else             range  = address_range_e'($onehot(address)? $clog2(address) : $clog2(address)-1);
    
    block_cg.sample( .address_range(range) );

  endfunction: do_sample
endclass: block_cov

Lets take this implementation another step by adapting it to a different design construct, one that could easily support variable address spaces. 

In this new design, the address bus is always defined as thirty two bits however the actual supported address space can be lower than the maximum range. Functional coverage is still defined as bit based. With a few minor tweaks the solution presented above is viable for these requirements and has an added bonus of identifying out or range requests. 

First off, lets expand the enumeration definition to have both valid values and a single out of range value, the preprocessing define of ADR_WIDTH now represents the valid address range.

1
2
3
4
typedef enum {
  RANGE_BIT_[`ADR_WIDTH] = 0
  RANGE_OUT_OF_RANGE
} address_range_e;

The sample wrapper method recognizes invalid ranges by dynamically casting the received MSB value on to the enumeration, a failed casting indicates the request is indeed out of range. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  function void do_sample(bit[31:0] address);
    logic [31:0]    bit_num = '0;
    address_range_e range   = RANGE_OUT_OF_RANGE;

    // address of zero get special attention, all other values are evaluated with the same line
    case(1) 
      (address == 0): bit_num = 0;
      default:        bit_num = $onehot(address)? $clog2(address) : $clog2(address)-1;
    endcase

    if(!$cast(range, bit_num)) range = RANGE_OUT_OF_RANGE; // default to OUT_OF_RANGE if casting fails 
    
    block_cg.sample( .address_range(range) );

  endfunction: do_sample

Now lets aggregate the previous solution with these updates into a full code snippet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//----------------------------------------------------------------------
`define ADR_WIDTH 8 
//----------------------------------------------------------------------
typedef enum {
  RANGE_BIT_[`ADR_WIDTH] = 0,
  RANGE_OUT_OF_RANGE
} address_range_e;
//----------------------------------------------------------------------
class block_cov;
  covergroup block_cg with function sample(address_range_e address_range);
    address_ranges_c : coverpoint address_range;  
  endgroup: block_cg
  //-----------------
  function new();
    block_cg = new;
  endfunction
  //-----------------
  function void do_sample(bit[31:0] address);
    logic [31:0]    bit_num = '0;
    address_range_e range   = RANGE_OUT_OF_RANGE;

    // address of zero get special attention, all other values are evaluated with the same line
    case(1) 
      (address == 0): bit_num = 0;
      default:        bit_num = $onehot(address)? $clog2(address) : $clog2(address)-1;
    endcase

    if(!$cast(range, bit_num)) range = RANGE_OUT_OF_RANGE; // default to OUT_OF_RANGE if casting fails 
    
    block_cg.sample( .address_range(range) );

  endfunction: do_sample
endclass: block_cov


Thursday, March 15, 2018

Using Object Members in Functional Coverage Coverbins

While implementing a functional coverage model one of the requirements was to create coverbins whose values are a function of the sampled objects members. Below is a simplified example of the initial implementation with a sampled class object and a coverpoint meeting these requirements.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class my_item;
  rand bit [7:0] data_size;
  rand bit [7:0] inside_data_size;

  constraint my_c { 
    inside_data_size <= data_size;
    inside_data_size >= 0;
  }
endclass
//-----------------------------------------//
class my_cov;
my_item itm_p;
//---------//
covergroup my_grp;
  data_size_cp:        coverpoint itm_p.data_size;
  
  inside_data_size_cp: coverpoint itm_p.inside_data_size {
    bins  zero        = {0};
    bins  lower_half  = {[1:(itm_p.data_size/2)-1]};
    bins  half        = {itm_p.data_size/2};
    bins  upper_half  = {[(itm_p.data_size/2)+1:itm_p.data_size-1]};
    bins  max         = {itm_p.data_size};
  }
endgroup
//----------//
function new();
  my_grp = new;
endfunction: new
//----------//
task do_sample();
  my_grp.sample();
endtask: do_sample
endclass
//-----------------------------------------//
module tb();
initial begin
  my_item itm_p;
  my_cov  cov_p;
  
  cov_p = new();
  itm_p = new();

  for(int i = 0; i < 10; i++) begin
    itm_p.randomize();
    cov_p.itm_p = itm_p;
    cov_p.do_sample();
  end
end
endmodule: tb

Needless to say, this coverpoint implementation would not work. If you don't see the problem here, I will point it out; When the covergroup is created, there is an attempt to access the object member "itm_p.data_size" for the coverbin "inside_data_size_cp". Since an object has not been created yet and the current object pointer is null, a null pointer access is triggered terminating the simulation. So such an implementation is obviously not the way to go however the dependency for our coverage model still exists. Lets go for a different coverbin implementation, one that will work. 
What is needed is a variable which is a function of the data size but not a member of the sampled object. This new variable can be a member is the coverage class wrapper and updated right before the covergroup sampling. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class my_cov;

typedef enum {
  ZERO = 0,
  LOWER_HALF,
  HALF,
  UPPER_HALF,
  MAX
} e_inside_data_size;

my_item             itm_p;
e_inside_data_size  inside_data_size_e;
//---------//
covergroup my_grp;
  data_size_cp:        coverpoint itm_p.data_size;
  inside_data_size_cp: coverpoint inside_data_size_e;
endgroup
//----------//
function new();
  my_grp = new;
endfunction: new
//----------//
task do_sample();
  case(1)
    (itm_p.inside_data_size == 0):                  inside_data_size_e = ZERO;
    (itm_p.inside_data_size > 0 && 
     itm_p.inside_data_size < itm_p.data_size/2):   inside_data_size_e = LOWER_HALF;
    (itm_p.inside_data_size == itm_p.data_size/2):  inside_data_size_e = HALF;
    (itm_p.inside_data_size > itm_p.data_size/2 && 
     itm_p.inside_data_size < itm_p.data_size ):    inside_data_size_e = UPPER_HALF;
    (itm_p.inside_data_size == itm_p.data_size):    inside_data_size_e = MAX;
    default: $error("inside_data_size=%x is not within data_size of %x", itm_p.inside_data_size, itm_p.data_size);
  endcase

  my_grp.sample();
endtask: do_sample
endclass

In the last example an enum type was created defining different value ranges for our coverbin. A class member is defined in the coverage wrapper class using this enum as the type. Right before the covergroup is sampled, this member is updated as a function of the received item using the case(1) statement. So now the coverbin no longer creates a null pointer access and the required dependency for the functional coverage is fulfilled.