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


No comments:

Post a Comment