Showing posts with label enums. Show all posts
Showing posts with label enums. 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


Tuesday, June 4, 2019

Using Enumeration Types for Testbench Definitions & Connectivity

It is very popular for environments to have multiple agents of the same type. One common method to maintain a list of multiple agents is to define an enumeration for each agent type. Using this method has a number of positive features such as unique naming of agents, simplicity in maintaining lists of agents from project to project and readability. This post will focus on the use of enums for testbench needs such as interface definition and wire-up, environment implementations will have a separate post so stay tuned.
Lets dive right into it and start off with what would seem to be an intuitive and simple implementation and then dissect issues that crop up.

 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
package my_pkg;
  typedef enum {
    ENUM1,
    ENUM2,
    ENUM3
  } my_enum_e;
  
  // `include files ....
endpackage: my_pkg
//-----------------------------
interface my_if();
  logic [31:0] sig1;
endinterface: my_if
//-----------------------------
module tb();
  import my_pkg::*;
  my_enum_e e;
  
  generate for( genvar i=0; i<e.num();i++) begin : my_genvar
    my_if my_if_i();

    assign my_if_i.sig1 = i+4;// different per interface
  end endgenerate
  
endmodule: tb

When trying to simulate this code some vendors allow this implementation while others reported a compile/elaboration failure on line 19. In theory there shouldn't be a limitation for this implementation, the enum size value is constant and can't be dynamically updated effectively making the "num()" method return value a constant. I can theorize why some vendors are creating this failure but don't want to speculate on that here.
So if the vendor you're using allows this syntax and you have no need to support multiple vendors, everything is great. For those of you with failures, lets present a different solution.

 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
package my_pkg;
  typedef enum {
    ENUM1,
    ENUM2,
    ENUM3,
    MY_ENUM_MAX
  } my_enum_e;
  
  // `include files ....
endpackage: my_pkg
//-----------------------------
interface my_if();
  logic [31:0] sig1;
endinterface: my_if
//-----------------------------
module tb();
  import my_pkg::*;
  my_enum_e e;
  
  generate for( genvar i=0; i<my_pkg::MY_ENUM_MAX;i++) begin : my_genvar
    my_if my_if_i();

    assign my_if_i.sig1 = i+4;// different per interface
  end endgenerate
  
endmodule: tb

In the implementation above a value was added to the enum definition on line 6. How does this addition help solve our compilation/elaboration issue? The answer is in understanding how enumerations are  implementation as stated in the LRM section 6.19. You are welcome to read this section in depth, here are a couple of guidelines taken directly from the LRM which will help explain the solution above.

1. "In the absence of a data type declaration, the default data type shall be int."
2. "A name without a value is automatically assigned an increment of the value of the previous name. ... If the first name is not assigned a value, it is given the initial value of 0."

In the current solution, values assigned to the enum definitions start at zero and increment per definition. Appending the end of the enum with an additional value inexplicitly makes the last value hold the number of defined enumerations.
Great! A solution for all is in sight. Well... there is one possible pitfall that should be addressed:
when and if the enum needs to have values added to it there is a risk that the new values will be added after the MY_ENUM_MAX. Now if you are sure that you or your team can never make such a mistake, all the more power to you. And even though such an issue will eventually wash out, debugging this sort of failure can be frustrating and time consuming. So how can we have our cake and eat it too? Lets present yet another solution which reports erroneous coding when incorrect modifications is done to the enum.

 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
package my_pkg;
  typedef enum {
    ENUM1,
    ENUM2,
    ENUM3
  } my_enum_e;
  
  parameter MY_ENUM_E_SIZE = ENUM3+1; // update to last value when enum is modified

  // `include files ....
endpackage: my_pkg
//-----------------------------
interface my_if();
  logic [31:0] sig1;
endinterface: my_if
//-----------------------------
module tb();
  import my_pkg::*;
  my_enum_e e;
  
  generate for( genvar i=0; i<my_pkg::MY_ENUM_E_SIZE;i++) begin : my_genvar
    my_if my_if_i();

    assign my_if_i.sig1 = i+4;// different per interface
  end endgenerate
  // ------------------
  // in an UVM env, this check can be done in one of the build phases (i.e. env or base test)
  initial begin 
    e = e.last();

    if(e.num() !== my_pkg::MY_ENUM_E_SIZE) 
      $fatal("enum size %0d which differs from parameter MY_ENUM_E_SIZE value of %0d, last value is %s ", e.num(), 
                                                                                                          my_pkg::MY_ENUM_E_SIZE, 
                                                                                                          e.name() );
  end
endmodule: tb

In the last example the enum definition no longer has the max enum size where on line 8 a parameter was added to the package holding the current enum size. This parameter is used in a run time checker shown on lines 28 to 35 verifying the enum size is as expected. If your environment is UVM based, such a checker can be coded into the build phase of the env or base test.