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.

No comments:

Post a Comment