[System Verilog] Overview – 4 interface

In Verilog, ports are connected with wires to facilitate inter-module communication. In System Verilog, inter-module communication is achieved through interfaces, which are collections of signals.

System Verilog interface intro

So what are the benefits of using interfaces?

  • In Verilog, adding a signal requires tracking down and editing every instance of the module. However, with System Verilog interfaces, you only need to modify the interface block.
  • It is reusable throughout the project.
  • Interface blocks allow you to use features such as parameters, variables, functional coverage, assertions, tasks, and functions.
  • You can use procedural assignment and continuous assignment in the Interface block.
글 설명 이미지, Port VS Interface
Port VS Interface

Let's learn the syntax first.

interface [name] ([port_list]);
  [list_of_signals]
endinterface

The interface can describe direction information through modport and timing information through clocking block.

Connect interface with design

With Verilog design

Let's look at an example of integrating a DUT designed in Verilog.

//DUT
module counter_ud #(
  parameter WIDTH = 4
) (
   input  wire             clk
  ,input  wire             rstn
  ,input  wire [WIDTH-1:0] load
  ,input  wire             load_en
  ,input  wire             down
  ,output wire             rollover
  ,output reg  [WIDTH-1:0] count
);
  
  always @(posedge clk or negedge rstn) begin
    if (!rstn)        count <= 4'b0;
    else if (load_en) count <= load;
    else begin
      if (down)       count <= count - 1;
      else            count <= count + 1;
    end
  end
  
  assign rollover = andcount;
  
endmodule

//Interface
interface cnt_if #(
  parameter WIDTH = 4
) (
  input bit clk
);
  logic             rstn;
  logic             load_en;
  logic [WIDTH-1:0] load;
  logic [WIDTH-1:0] count;
  logic             down;
  logic             rollover;
  
  //initialize
  initial begin
    rstn    = 0;
    load_en = 0;
    load    = 4'b0;
    down    = 0;
  end
  
endinterface

module tb;
  
  //clk gen
  localparam period_clk = 10;
  reg clk; initial clk = 0;
  
  always #(period_clk*0.5) clk = ~clk;
  
  //instance
  cnt_if     cnt_if0 (clk);
  counter_ud c0 (
     .clk      (cnt_if0.clk     )
    ,.rstn     (cnt_if0.rstn    )
    ,.load     (cnt_if0.load    )
    ,.load_en  (cnt_if0.load_en )
    ,.down     (cnt_if0.down    )
    ,.rollover (cnt_if0.rollover)
    ,.count    (cnt_if0.count   )
  );
  
  initial begin
    bit       load_en;
    bit       down;
    bit [3:0] load;
    
    @(posedge clk);
    cnt_if0.rstn = 1;
    
    //Stimulus
    cnt_if0.load_en <= 1;
    cnt_if0.load    <= 4'h3;
    cnt_if0.down    <= 1;
    
    @(posedge clk);
    cnt_if0.load_en <= 0;
    
    repeat (2) @(posedge clk);
    cnt_if0.down    <= 0;
    
    //Sim. run time
    repeat (7) @(posedge clk);
    $finish;
  end
  
  //wave dump
  initial begin
    $dumpfile("dump.vcd");
    $dumpvars;
  end
  
endmodule

You can connect each signal of the interface one by one, but you can make the code simpler by using the method below.

With System Verilog design

//DUT
module counter_ud #(
  parameter WIDTH = 4
) (cnt_if _if);
  
  always @(posedge _if.clk or negedge _if.rstn) begin
    if (!_if.rstn)        _if.count <= 4'b0;
    else if (_if.load_en) _if.count <= _if.load;
    else begin
      if (_if.down)       _if.count <= _if.count - 1;
      else                _if.count <= _if.count + 1;
    end
  end
  
  assign _if.rollover = and_if.count;
  
endmodule

//Interface
interface cnt_if #(
  parameter WIDTH = 4
) (
  input bit clk
);
  logic             rstn;
  logic             load_en;
  logic [WIDTH-1:0] load;
  logic [WIDTH-1:0] count;
  logic             down;
  logic             rollover;
  
  //initialize
  initial begin
    rstn    = 0;
    load_en = 0;
    load    = 4'b0;
    down    = 0;
  end
  
endinterface

module tb;
  
  //clk gen
  localparam period_clk = 10;
  reg clk; initial clk = 0;
  
  always #(period_clk*0.5) clk = ~clk;
  
  //instance
  cnt_if     cnt_if0 (clk    );
  counter_ud c0      (cnt_if0);
  
  initial begin
    bit       load_en;
    bit       down;
    bit [3:0] load;
    
    @(posedge clk);
    cnt_if0.rstn = 1;
    
    //Stimulus
    cnt_if0.load_en <= 1;
    cnt_if0.load    <= 4'h3;
    cnt_if0.down    <= 1;
    
    @(posedge clk);
    cnt_if0.load_en <= 0;
    
    repeat (2) @(posedge clk);
    cnt_if0.down    <= 0;
    
    //Sim. run time
    repeat (7) @(posedge clk);
    $finish;
  end
  
  //wave dump
  initial begin
    $dumpfile("dump.vcd");
    $dumpvars;
  end
  
endmodule

You can simplify your code by integrating using an interface.

Modport

You can set specific constraints on an interface to control its direction. Let's look at an example.

//Syntax
modport  [identifier]  (
	input  [port_list],
	output [port_list]
);

interface ms_if (input clk);
  logic       sready;
  logic       rstn;
  logic [1:0] addr;
  logic [7:0] data;
  
  initial rstn = 0;
    
  modport slave (
     input  clk
    ,input  rstn
    ,input  addr
    ,input  data
    ,output sready
  );
  
  modport master (
     input  clk
    ,input  rstn
    ,input  sready
    ,output addr
    ,output data
  );
  
endinterface

module master (ms_if.master mif);
  
  always @(posedge mif.clk or negedge mif.rstn) begin
    if (!mif.rstn) begin
      mif.addr <= 2'b0;
      mif.data <= 8'b0;
    end
    else begin
      if (mif.sready) begin
        mif.addr <= mif.addr + 1;
        mif.data <= (mif.addr * 4);
      end
      else begin
        mif.addr <= mif.addr;
        mif.data <= mif.addr;
      end
    end
  end
  
endmodule

module slave (ms_if.slave sif);
  
  reg [7:0] reg_a;
  reg [7:0] reg_b;
  reg       reg_c;
  reg [3:0] reg_d;
  
  reg       dly;
  reg [3:0] addr_dly;
  
  always @(posedge sif.clk or negedge sif.rstn) begin
    if (!sif.rstn) addr_dly <= 4'b0;
    else           addr_dly <= sif.addr;
  end
  
  always @(posedge sif.clk or negedge sif.rstn) begin
    if (!sif.rstn) begin
      reg_a <= 8'b0;
      reg_b <= 8'b0;
      reg_c <= 1'b0;
      reg_d <= 4'b0;
    end
    else begin
      case (addr_dly)
        0 : reg_a <= sif.data;
        1 : reg_b <= sif.data;
        2 : reg_c <= sif.data;
        3 : reg_d <= sif.data;
      endcase
    end
  end
  
  assign sif.sready = ~(sif.addr[1] and sif.addr[0]) | ~dly;
  
  always @(posedge sif.clk or negedge sif.rstn) begin
    if (!sif.rstn) dly <= 1;
    else           dly <= sif.sready;
  end
  
endmodule

module dut (ms_if tif);
  
  master m0 (tif.master);
  slave  s0 (tif.slave );
  
endmodule

module tb;
  
  localparam period_clk = 10;
  reg clk; initial clk = 0;
  
  always #(period_clk * 0.5) clk = ~clk;
  
  ms_if if0   (clk);
  dut   u_dut (if0);
  
  initial begin
    
    @(posedge clk)
    if0.rstn = 1;
    
    repeat (20) @(posedge clk);
    $finish;
  end
  
  initial begin
    $dumpfile("dump.vcd");
    $dumpvars;
  end
  
endmodule

The ms_if interface module has a "master" modport and a "slave" modport. Clock and reset are inputs on both modports, but addr, data, and sready have opposite directions. The master and slave DUTs each use their own modports.

The reason modport is used like this is because the logic declared in the interface is basically inout, so there may be a situation where input and output conflicts due to human error in the testbench or module. In this case, an X (unknown) is displayed for the relevant net. By using modport, such conflicts can be prevented in advance.

Clocking block

The Clocking block specifies the timing and synchronization of a group of signals. Let's first look at a simple example.

clocking cb @(posedge clk);
  default input #1ns output #2ns;
  input  from_Dut;
  output to_Dut;
endclocking

The clocking block, named cb, contains an input signal, from_DUT, and an output signal, to_DUT. Skews are set to 1 ns and 2 ns, respectively, as shown in the figure below.

Skew 설명
Skew Description

Input skew specifies the timing at which the signal is sampled, and output skew specifies the timing at which the signal is driven. If no default value is specified, input skew is applied at step #1 and output #0.

References: chip verify

Similar Posts