Verification Central

 

Parameterized Interfaces and Reusable VIP (1 of 3)

This is the first part of a three part series discussing SystemVerilog interfaces and strategies for dealing with parameterization.

You can get more information at Synopsys Verification IP and VC Verification IP Datasheet.

Background

SystemVerilog based verification introduces the concept of interfaces to represent communication between design blocks.  In its most elemental form a SystemVerilog interface is just a named bundle of signals that can be communicated through a module port as a single item.  Design modules that receive this interface can then access signals through this interface reference.  However, higher level functions of interfaces can also provide a more strongly typed communication to better represent the design intent.  The following is a subset of the higher order functions that are available in SystemVerilog Interfaces:

  • Access rules can be enforced on the signal list through the use of clocking blocks and modports
  • Functions and tasks can be used to encapsulate higher order sequencing or access control
  • Processes such as initial blocks and always blocks can add functionality
  • Continuous assignment statements can also add functionality
  • Assertions can ensure proper integration

One very important use of SystemVerilog interfaces is to connect static design elements to dynamic testbench elements.  Dynamic testbench elements need access to static design elements in order to sample and drive signals, but reusable testbench elements cannot access static design elements except through a special construct named a virtual interface.  Virtual interfaces are interface handles within testbench code that can be assigned with an interface instance.  Virtual interfaces are dynamic properties and can be assigned to different interface instances in different testbenches which promotes re-usability.

A common technique for designing reusable design blocks is to use parameters to enable different instances of the design block with unique characteristics.  For example, a module could be parameterized to allow the data bus width to be defined when the module is declared and the parameter value is provided.  SystemVerilog interfaces also support parameterization, but the use of parameterized interfaces introduces unforeseen complications on the testbench side.  In order to be assignment compatible a parameterized virtual interface must be specialized with the same values that the interface instance is specialized with.  Unless precautions are taken, this can make for some very ugly testbench code with even uglier use models.

Parameter Proliferation: The brute force method

The problem that parameterized virtual interfaces introduce is that the strongly typed interface must be known to the testbench elements that access it.  Therefore a generic class cannot be written to use a parameterized virtual interface when the interface specialization is not yet known.  One solution to this problem is to parameterize the class that accesses the parameterized virtual interface.  For example, a UVM driver could be parameterized with the type of virtual interface that it must utilize.  This just moves the problem up a layer however, as now the agent that instantiates that parameterized driver must also be parameterized so that it can create a correctly specialized instance of the driver.  This keeps moving up the layers until you get to the top layer testbench component that “knows” about the specific specializations that are being tested exists.  The following code segments demonstrate the issue.

First we define the parameterized virtual interface:

  interface param_if#(int width = 8);
    logic clk;
    logic[width-1:0] data;

    clocking active_cb @(posedge clk);
      default input #1 output #1;
      output data;
    endclocking

    modport active_mp (clocking active_cb);
  endinterface

  typedef virtual param_if param_vif

Next, we define the reusable VIP code. This testbench code must be designed to be reusable in any environment that the parameterized interface could be used in, and so the VIP code itself must also be parameterized so that the proper interface can be accessed.  The following code segment shows how a UVM driver class and the agent that contains the driver must be parameterized:

  //=======================================================================
  class param_driver#(type vif_t=param_vif) extends uvm_driver#(cust_data);
    `uvm_component_param_utils(param_driver#(vif_t))

    vif_t vif;

    function void build_phase(uvm_phase phase);
      if (!uvm_config_db#(vif_t)::get(this, "", "vif", vif))
        `uvm_fatal("build", "A valid interface was not received.");
    endfunction
  endclass

  //=======================================================================
  class cust_agent#(type vif_t=param_vif) extends uvm_agent;
    `uvm_component_param_utils(param_agent#(vif_t))

    vif_t vif;
    param_driver#(vif_t) param_driver;

    function void build_phase(uvm_phase phase);
      if (!uvm_config_db#(vif_t)::get(this, "", "vif", vif))
        `uvm_fatal("build", "A valid interface was not received.");

      uvm_config_db#(vif_t)::set(this, "param_driver", "vif", vif);
      param_driver = param_driver#(vif_t)::type_id::create("param_driver", this);
    endfunction
  endclass

This doesn’t look so bad so far! It adds a little complication to the class definitions, but not too much.  The problems however don’t become apparent until you examine how the testbench must access these parameterized classes.  The following segment shows how a test has to access every instance of the VIP uniquely based on how the interface is parameterized:

  //=======================================================================
  class cust_test extends uvm_test;
    `uvm_component_utils(cust_test)

    param_agent#(virtual param_if#(8)) param_agent8;
    param_agent#(virtual param_if#(16)) param_agent16;
    param_agent#(virtual param_if#(32)) param_agent32;

    virtual function void build_phase(uvm_phase phase);
      param_agent8 = param_agent#(virtual param_if#(8))::type_id::create("param_agent8", this);
      param_agent16 = param_agent#(virtual param_if#(16))::type_id::create("param_agent16", this);
      param_agent32 = param_agent#(virtual param_if#(32))::type_id::create("param_agent32", this);
    endfunction
  endclass

  //=======================================================================
  module test_top;
    param_if#(8) if8();
    param_if#(16) if16();
    param_if#(32) if32();

    initial begin
      uvm_config_db#(virtual param_if#(8))::set(uvm_root::get(), "uvm_test_top.param_agent8", "vif", if8);
      uvm_config_db#(virtual param_if#(16))::set(uvm_root::get(), "uvm_test_top.param_agent16", "vif", if16);
      uvm_config_db#(virtual param_if#(32))::set(uvm_root::get(), "uvm_test_top.param_agent32", "vif", if32);

      run_test("cust_test");
    end
  endmodule

As you can see, every reference to the VIP must be parameterized with the proper interface type that it is to work with.  This affects not only the VIP construction, but will also affect callback registration, factory overrides, etc.  This presents a large burden on the testbench developer, and limits the reusability of these environments.

Adding parameters to the verification components is a valid technical solution for reusable VIP, but it complicates the use model considerably and limits testbench reusability.  In the next post in this series we’ll examine a possible workaround to this problem, but this comes with a price!

You can get more information at Synopsys Verification IP and VC Verification IP Datasheet.

Authored by Aron Pratt