Interface and Virtual Interface in SystemVerilog

An interface in SystemVerilog is a construct that bundles together a set of signals and functionality into a single logical entity. This abstraction simplifies the connections between modules by reducing the need for numerous port declarations and wiring in a design. We can also declare task or functions inside the interface.

Key Features of an Interface:

  1. Signal Grouping: Combines related signals into a single construct.
  2. Behavioral Logic: Supports procedural blocks, continuous assignments, and tasks/functions within the interface.
  3. Modularity: Reduces repetitive code and improves readability.

Defining and Using an Interface

Here’s an example of defining an interface:

interface bus_if(input logic clk);
    logic [7:0] data;
    logic valid;
    logic ready;

endinterface
  • Signals (data, valid, ready): Declared inside the interface to group them logically.

Connecting an Interface to a Module

To use the interface in a design:

module dut(bus_if vif);
    always @(posedge vif.clk) begin
    if (vif.valid) begin
      vif.ready <= 1;
    end
    else begin
      vif.ready <= 0;
    end
  end
  
endmodule
  • The module producer uses the bus_if interface, connecting to all its signals through the vif handle.
  • This reduces the complexity of port declarations.

Testbench:

module testbench;
    logic clk;
    bus_if vif(clk);           // Create interface instance

  dut dut_inst(vif);         // Connect interface to DUT

    initial clk = 0;
  always #5 clk = ~clk; // Clock generation
  
  initial begin
    vif.valid = 0;         //Initialize valid to 0
    
    #15 vif.data = 8'hA5;  // Drive data
        vif.valid = 1;     // assert valid
    #10 vif.valid = 0;     // deassert valid
    #10 $finish;
      
    end
  
  initial begin
    $monitor("valid = %0d, ready = %0d, data = %0d",vif.valid, vif.ready,vif.data);
  end 
  
endmodule

Output:

valid = 0, ready = x, data = x
valid = 0, ready = 0, data = x
valid = 1, ready = 1, data = 165
valid = 0, ready = 0, data = 165

What is a Virtual Interface in System Verilog?

A virtual interface is a pointer to an interface instance. It allows dynamic access to interfaces in a testbench, enabling flexibility in modular testbench environments where you may need to connect multiple DUTs or instances.

As classes are dynamic in nature and interface is static, so we use virtual interface which points to an interface instance. A virtual interface must be initialized before using it otherwise it will result a run-time fatal error.

Why Use Virtual Interfaces?

  1. Dynamic Binding: Enables the testbench to connect to specific DUT instances dynamically.
  2. Reusability: A common testbench can interact with different interface instances without recompilation.
  3. Simplified Connections: Reduces the need for passing multiple signals or instances explicitly.

Declaring and Using Virtual Interfaces

Here’s an example to illustrate virtual interfaces:

  1. Interface Declaration and connecting interface to module:
interface bus_if(input logic clk);
    logic [7:0] data;
    logic valid;
    logic ready;
endinterface

module dut(bus_if vif);
    always @(posedge vif.clk) begin
        if (vif.valid) begin
            vif.ready <= 1; // Signal that DUT is ready to accept data
        end
        else begin
            vif.ready <= 0; // Not ready when valid is low
        end
    end
endmodule

2. Virtual Interface in class:

class tb_class;
  virtual bus_if vif;
  
  function new(virtual bus_if vif);
    this.vif = vif;
  endfunction
  

// Drive transactions on the bus
  task run_test();
    vif.valid = 0;
    vif.data = 0;
    #10;

    vif.valid = 1; // Assert valid
    vif.data = 8'hA5; // Send data
    #10;

    vif.valid = 0; // Deassert valid
    vif.data = 0;
    #10;
  endtask
  
endclass

2. Virtual Interface in a Testbench:

`include "test.sv"

module testbench;
    logic clk;
    bus_if vif(clk); // Instantiate the interface and connect to clk

    // Virtual interface
    virtual bus_if v_vif;

    // DUT instantiation
    dut u_dut(.vif(vif));

    // Clock generation
    initial begin
        clk = 0;
        forever #5 clk = ~clk; // 10ns clock period
    end

    // Testbench Initialization
    initial begin
        tb_class tb = new(vif); // Pass interface to testbench class
        tb.run_test();

        // End simulation
        $finish;
    end

    // Monitor the interface signals
    initial begin
        $monitor("Time: %0t | valid: %b | ready: %b | data: %h", 
                 $time, vif.valid, vif.ready, vif.data);
    end
endmodule
  • A virtual keyword is used in the testbench class to reference the bus_if instance (vif).
  • This allows the testbench to interact with the dut indirectly.

Output:

Time: 0 | valid: 0 | ready: x | data: 00
Time: 5 | valid: 0 | ready: 0 | data: 00
Time: 10 | valid: 1 | ready: 0 | data: a5
Time: 15 | valid: 1 | ready: 1 | data: a5
Time: 20 | valid: 0 | ready: 1 | data: 00
Time: 25 | valid: 0 | ready: 0 | data: 00

Differences Between Interface and Virtual Interface

AspectInterfaceVirtual Interface
DefinitionGroups signals and functionality into a bundle.Pointer to an interface instance.
PurposeSimplifies DUT and testbench connections.Enables dynamic access in testbenches.
UsageUsed in both DUTs and testbenches.Primarily used in testbenches.
BindingStatically bound during module instantiation.Dynamically assigned at runtime.
Examplebus_if bvirtual bus_if vif