Logo

Virtual Interface in System Verilog

15 Sep 2023
4 mins

We have discussed classes and interface in our previous articles. In this article we will see about the virtual interface and what is the need for virtual interface. Having idea about classes and interface will help appreciate need of virtual interface and how to use it in test bench to make test bench more versatile and re-usable.

Introduction

What is a virtual interface?

A virtual interface is a variable that represents an interface instance. Just like an object is a pointer to a memory address containing the class members, a virtual interface points to the interface.

Virtual interface being used inside class to connect with DUT
An interface is a bundle of signals or nets that communicate between a testbench and a design under test (DUT).

Why do we need a virtual interface?

So, the most obvious question would be “What’s the need for virtual interface when we have interface?”. Let us try to understand this.

A virtual interface is needed because System Verilog interfaces are static in nature, whereas classes are dynamic in nature. This means that you cannot declare an interface inside a class. This is because in OOPs, we cannot refer static object from a dynamic object. But we can refer to or point to a static object through a pointer. Virtual interface acts as this pointer which helps us access interface and all its member from classes.

But why we need to access an interface from class?? We already have a module (which is static in nature), and we can use interface in module to connect with DUT. In verification, we create lot of dynamic objects like driver, monitor, scoreboard, etc. These objects need access to interface to drive signals or monitor signals. This is why virtual interface is needed specially in TB codes.

A virtual interface also allows you to pass an interface as an argument to a task, function, or method. This way, you can reuse the same code for different interfaces without changing the code.

Syntax

A virtual interface is declared with the keyword virtual followed by the name of the interface type.

For example:
virtual apb_if vif;

Here, vif is a virtual interface of type apb_ifc, which is an interface that contains various signals needed to have connection with DUT.

How to use a virtual interface?

To use a virtual interface, you need to do the following steps:

  1. Declare an interface with the signals and methods that you want to use for connection with DUT.
  2. Declare a virtual interface of the same type as the interface in your class or module from which interface needs to be accessed.
  3. Connect the virtual interface to the actual interface using an assignment or a constructor argument.
  4. Access the signals and methods of the interface using the virtual interface handle.
We should always assign the virtual interface to an interface before using it. If we do not assign virtual interface before using it, we will get null object access fatal error.

Example

In this example, we are creating an interface apb_ifc and using virtual interface to access the interface signals. In this example, we are not connecting any DUT to the interface as main motive is to see how we can access interface through dynamic objects.

interface apb_ifc(input logic clk);
    logic [31:0] addr;
    logic [31:0] wdata;
    logic pvalid;
endinterface //apb_ifc

class driver;
    //defining a virtual interface and assigning through constructor
    virtual apb_ifc vif;
    function new(virtual apb_ifc vif);
        this.vif = vif;
    endfunction

    task drive(bit [31:0] addr, wdata);
        $display("[%0t] Driving signal with addr = %0h, data = %0d", $time, addr, wdata);
        @(posedge vif.clk);
        vif.addr <= addr;
        vif.wdata <= wdata;
        vif.pvalid <= 1;
        $display("[%0t] Signal driven with addr = %0h, data = %0d", $time, addr, wdata);
        #1;
        @(posedge vif.clk);
        vif.pvalid <= 0;
    endtask
endclass

class monitor;
    virtual apb_ifc vif;
    function new(virtual apb_ifc vif);
        this.vif = vif;
    endfunction

    task monitor_sig;
        forever @(posedge vif.clk) begin
            #1;
            if(vif.pvalid) begin
                $display("[%0t] Sampled packet sent to DUT with addr = %0h, data = %0d", $time, vif.addr, vif.wdata);
            end
        end
    endtask
endclass


module tb;
    bit clk;
    apb_ifc ifc(clk);
    driver drv;
    monitor mon;

    always #5 clk = ~clk;

    initial begin
        mon = new(ifc); 
        mon.monitor_sig();
    end

    initial begin
        drv = new(ifc); 
        #34;
        drv.drive('h4, 454);
        #32;
        drv.drive('h3a, 6658);
    end
endmodule
Output
# [34] Driving signal with addr = 4, data = 454
# [35] Signal driven with addr = 4, data = 454
# [36] Sampled packet sent to DUT with addr = 4, data = 454
# [77] Driving signal with addr = 3a, data = 6658
# [85] Signal driven with addr = 3a, data = 6658
# [86] Sampled packet sent to DUT with addr = 3a, data = 6658
Try this code in EDA Playground

In the output we can observe that the virtual interface is changing the values of the signal and thus we are able to sample it through monitor. If we connect a DUT with the interface in this example, then we can drive and monitor the DUT through our virtual interface.

Also, observe that we are passing the interface to the constructor of driver and monitor. Compiler automatically coverts it to virtual interface and there is no need to pass virtual interface in constructor.

Conclusion

In this article we discussed about the virtual interface and tried to appreciate the need of virtual interface in verification. We hope that this article helped understand virtual interface and the learnings can be incorporated into test bench codes.