VIP Central

 

Parameterized Interfaces and Reusable VIP (2 of 3)

This is the second 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.

In the first part of this series the basic concepts of SystemVerilog interfaces were introduced and the problems that parameterization of those interfaces introduce to testbench code was described.   This post will describe a possible workaround for this problem, but one that comes with a price…

Trojan Horse: The sneak attack method

Virtual interfaces do not support polymorphism because they are associated with static design elements.  However, SystemVerilog classes do support polymorphism and this fact can be utilized to create an interface accessor class.

A virtual class is created that declares pure virtual accessor methods that perform specific operations on a SystemVerilog interface.  Then, a parameterized class extends this class to perform the actual accesses to a strongly typed interface.  The VIP code interacts only with the non-parameterized base accessor class and so the VIP code does not need to be parameterized to work with strongly typed interfaces.  The testbench must set up the strongly typed accessor class and pass this in to the VIP, but after this all interaction with the VIP can be performed without needing to parameterize the VIP.  The following code segments demonstrate how to set this up.

First we define the parameterized virtual interface which is the same interface used in last week’s code segments:

  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 non-parameterized interface accessor class and the extended class that is parameterized to work with a parameterized interface:

  //=======================================================================
  virtual class port_wrapper extends uvm_object;
    pure virtual task do_write(logic[31:0] data);
    pure virtual task do_read(output logic[31:0] data);
    pure virtual task sync_driver_cb();
  endclass

  //=======================================================================
  class port_wrapper_typed#(type vif_t=param_vif) extends port_wrapper;
    vif_t vif;

    function new(vif_t vif);
      this.vif = vif;
    endfunction

    virtual task do_write(logic[31:0] data);
      vif.active_cb.data <= data;
    endtask

    virtual task do_read(output logic[31:0] data);
      data = vif.active_cb.data;
    endtask

    virtual task sync_driver_cb();
      @(vif.active_cb);
    endtask

  endclass

After this the VIP code is implemented to work with this accessor class rather than directly accessing the virtual interface.  The code segment below showcases a driver and an agent class, and demonstrates the various places where the accessor methods must be used.  One thing to note about this VIP code is that it is accepts the non-parameterized accessor class through the config DB rather than the interface:

  //=======================================================================
  class cust_driver extends uvm_driver#(cust_data);
    `uvm_component_utils(cust_driver)

    port_wrapper port;

    task consume_from_seq_item_port();
      forever begin
        seq_item_port.get_next_item(req);
        port.do_write(req.data);
        port.sync_driver_cb();
…
    task sample_signals();
      forever begin
        bit[31:0] sampled_data;

        port.do_read(sampled_data);
        port.sync_driver_cb();
…
    function void build_phase(uvm_phase phase);
      if (!uvm_config_db#(port_wrapper)::get(this, "", "port", port))
        `uvm_fatal("build", "A valid port wrapper was not received.");
…

  //=======================================================================
  class cust_agent extends uvm_agent;
    `uvm_component_utils(cust_agent)

    cust_driver driver;

    function void build_phase(uvm_phase phase);
      port_wrapper port;

      if (!uvm_config_db#(port_wrapper)::get(this, "", "port", port))
        `uvm_fatal("build", "A valid port wrapper was not received.");

      uvm_config_db#(port_wrapper)::set(this, "driver", "port", port);
      driver = cust_driver::type_id::create("driver", this);
…

Finally, the testbench code is presented.  The testcases can access the VIPs without parameterization, but the top level module where the interfaces are instantiated does have to implement a few extra steps.  It must not only instantiate the parameterized interfaces, but it must also create the parameterized interfaces accessor class and pass those to the VIP instances:

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

    cust_agent agent8;
    cust_agent agent16;
    cust_agent agent32;

    virtual function void build_phase(uvm_phase phase);
      agent8 = cust_agent::type_id::create("agent8", this);
      agent16 = cust_agent::type_id::create("agent16", this);
      agent32 = cust_agent::type_id::create("agent32", this);
    endfunction
  endclass

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

    initial begin
      port_wrapper_typed#(virtual param_if#(8)) port_wrapper8;
      port_wrapper_typed#(virtual param_if#(16)) port_wrapper16;
      port_wrapper_typed#(virtual param_if#(32)) port_wrapper32;

      port_wrapper8 = new(if8);
      port_wrapper16 = new(if16);
      port_wrapper32 = new(if32);

      uvm_config_db#(port_wrapper)::set(uvm_root::get(), "uvm_test_top.agent8", "port", port_wrapper8);
      uvm_config_db#(port_wrapper)::set(uvm_root::get(), "uvm_test_top.agent16", "port", port_wrapper16);
      uvm_config_db#(port_wrapper)::set(uvm_root::get(), "uvm_test_top.agent32", "port", port_wrapper32);

      run_test("cust_test");
    end
  endmodule

As you can see, this solution does resolve the use model issues with parameterized interfaces.  There is a bit of extra code needed to create a properly typed port wrapper, but testbench writers can access agents that are working with differently specialized interfaces without needing to know how those interfaces are specialized.

However, this solution imposes severe restrictions on how the VIP can utilize the virtual interface.  The driver and monitor classes cannot interact directly with the interface, and must instead defer these accesses to the strongly typed wrapper class.  This productivity loss can be offset by moving the bulk of the functionality of the driver and monitor classes to the strongly typed wrapper classes; however this more complex structure can be confusing and less flexible.  The code segments above show just a few of the access methods that a driver class might need, but in a real environment this list has the potential to grow many times larger and monitor classes will need a full battery of accessor methods as well.

Upon reflection, neither the brute force approach nor the Trojan Horse method of dealing with parameterized interfaces is ideal.  With the former approach the addition of parameters to classes complicates the use model of the VIP.  With the later approach the access methods that the port wrapper class supports must be planned for in advance and VIP access to the virtual interface is restricted.  What if functionality similar to parameterized interfaces could be enabled by the VIP code without actually using parameterized interfaces?  This leads to the third and final solution, will be described in next week’s post.

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

Authored by Aron Pratt