Showing posts with label Interface. Show all posts
Showing posts with label Interface. Show all posts

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.

Monday, March 7, 2016

Connecting Bi-directional DUT Ports to an Interface

It isn't rare to find SOC top levels designs with bi-directions pins. It also isn't rare to use purchased VIPs to verify protocols requiring bi-directional connectivity.
VIPs come with pre-coded interfaces which are required to be used by the integrator. There are a number of different interface coding methods which include bi-direction ports and require different wire-up connection methods to a DUT. This post presents different interface/DUT connection implementations and to limitations found in each different methodology.

Interface Nets Defined in its Port List

When the interface is coded with connection nets in its port list, a connecting wire between the interface and DUT instances is sufficient to create a connection between the two.

interface some_interface(inout wire  bidir1, 
                         inout wire  bidir2,
                         input logic in1
                        );
endinterface: some_interface
//----------------------------//
module tb();

wire sig1;
wire sig2;
wire sig3;
.
.
some_interface s_if( .bidir1(sig1),
                     .bidir2(sig2),
                     .in1(   sig3)
                    );

dut my_dut(.sig1(sig1),
           .sig2(sig2),
           .sig3(sig3),
           .
           .
           );

endmodule: tb

An interface with nets defined in its port list gives the most flexibility for wire-up connections. In some cases the connection could be simplified even more if the DUT and interface have the same port names  by using the ".name" or ".*" conventions described in sections 23.3.2.3 and 23.3.2.4 of the SV2012 LRM. However, when using third party IPs and VIPs, the probability of design and interface ports having the same naming convention is not high and cannot be expected.

Interface Nets Defined in the Interface

Another popular method is to define the interface nets within the interface and then create a connection via a cross module reference between the DUT port and the interface signal. A number of different connection implementations for this coding method are detailed in the subsequent sections.

Use Assign Statements in the TB 

Connect the nets by assigning either the DUT or interface with the directional continuous assign statement. This method is feasible for directional nets, in or out, but will not suffice for a bi-directional net as only one of the sides can be the driver.

interface some_interface();
wire  bidir1; 
wire  bidir2;
logic in1;
endinterface: some_interface
//----------------------------//
module tb();

wire sig1;
wire sig2;
wire sig3;
.
.
some_interface s_if();

dut my_dut(.sig1(sig1),
           .sig2(sig2),
           .sig3(sig3),
           .
           .
           );

assign s_if.in1 = sig3;
// Can't connect bi-direction ports using this method!
//assign bidir1 
//assign bidir2

endmodule: tb

Direct Instantiation of interface signals to the DUT Instance

A solution which works for both directional and bi-directional port types is to embed the interface nets to the DUT instance ports using cross module references.

interface some_interface();
wire  bidir1; 
wire  bidir2;
logic in1;
endinterface: some_interface
//----------------------------//
module tb();

some_interface s_if();

// can be an issue for auto-instatiated DUT
dut my_dut(.sig1(s_if.bidir1),
           .sig2(s_if.bidir2),
           .sig3(s_if.in1),
           .
           .
           );

endmodule: tb

This implementation has a limitation: if the DUT is auto-instantiated, the connection to the DUT can be overridden when modifications are made to the port list.

Connecting Ports to Interface nets with Verilog Primitives

Verilog provides many primitives to model hardware behavior. These primitives, used in the TB, can bridge between the DUT and interface internal nets including bi-directional ports. The Verilog primitive tran can create connections which allow bi-directional drivers.

interface some_interface();
wire  bidir1; 
wire  bidir2;
logic in1;
endinterface: some_interface
//----------------------------//
module tb();

wire sig1;
wire sig2;
wire sig3;

some_interface s_if();

dut my_dut(.sig1(sig1),
           .sig2(sig2),
           .sig3(sig3),
           .
           .
           );

// create connection with tran primitive 
tran(s_if.bidir1, sig1);
tran(s_if.bidir2, sig2);

// directional nets can use continuous assign statements
assign s_if.in1 = sig3;

endmodule: tb

Controllable Connectivity 

Sometimes it is necessary to create different connections for pins with multiple functionality. The Verilog primitives of tranif0 or tranif1 provide a controlled connection to the DUT. These primitives behave the same as the tran primitive with the exception of an enable input connecting or disconnecting the terminals accordingly.

interface some_interface();
wire  bidir1; 
wire  bidir2;
logic in1;
endinterface: some_interface
//----------------------------//
module tb();

wire sig1;
wire sig2;
wire sig3;

wire s_if_control = 1; // default is to short the terminals

some_interface    s_if();

dut my_dut(.sig1(sig1),
           .sig2(sig2),
           .sig3(sig3),
           .
           .
           );

// create connection with tranif1 primitive 
tranif1(s_if.bidir1, sig1, s_if_control);
tranif1(s_if.bidir2, sig2, s_if_control);

// directional nets can use continuous assign statements
assign s_if.in1 = sig3;

endmodule: tb