[System Verilog] Overview – 4 interface

Verilog에서는 모듈 간 통신을 위해 port를 wire로 연결했습니다. System Verilog에서는 signal의 집합인 interface를 통해 모듈 간 통신을 진행합니다.

System Verilog interface intro

그렇다면 interface를 사용함으로써 얻는 이점이 무엇일까요?

  • Verilog에서 신호를 추가하기 위해서는 해당 모듈이 인스턴스화된 모든 곳을 찾아 일일이 수정해 줘야 합니다. 하지만 System Verilog의 interface를 사용하면 interface block만 수정하면 됩니다.
  • Project 전반적으로 reusable 합니다.
  • Interface block에 parameters, variables, functional coverage, assertions, tasks and functions 같은 기능을 사용할 수 있습니다.
  • Interface block에 절차 할당과 연속 할당을 사용할 수 있습니다.
글 설명 이미지, Port VS Interface
Port VS Interface

먼저 syntax를 알아볼까요?

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

Interface에는 modport를 통해 direction 정보, clocking block을 통해 timing 정보를 기술할 수 있습니다.

Connect interface with design

With Verilog design

먼저 일반적으 Verilog로 design 된 DUT를 integrate 한 예시를 보겠습니다.

//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 = &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 (
     .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

Interface의 signal을 일일이 연결해 줄 수 있지만 아래의 방법을 사용하면 code를 간결하게 만들 수 있습니다.

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 = &_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

Interface를 사용해 integrate를 하면 코드를 간결하게 만들 수 있습니다.

Modport

Interface에 특정 제약조건을 걸어서 interface의 방향을 설정할 수 있습니다. 예시를 볼까요?

//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] & 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

Interface인 ms_if 내부 모듈로 ‘master’ modport와 ‘slave’ modport가 있습니다. clock, reset은 두 modport에서 모두 input이지만 addr, data, sready는 상반된 방향을 가진 것을 알 수 있습니다. 그리고 master, slave DUT에서 각각의 모듈에 맞는 modport를 사용했습니다.

이렇게 modport를 사용하는 이유는, interface에 선언된 logic은 기본적으로 inout이기 때문에 testbench나 module에서 human error로 인해 input과 output이 충돌되는 상황이 있을 수 있습니다. 이때는 해당 net에 X(unknown)가 표시되는데, modport를 사용하면 이러한 충돌을 사전에 방지할 수 있습니다.

Clocking block

Clocking block은 signal group의 timing과 동기화를 지정하는 것입니다. 먼저 간단한 예시를 확인해볼까요?

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

cb라고 naming 된 clocking block에는 input signal인 from_DUT와 output signal인 to_DUT가 있습니다. 각각 skew가 1ns, 2ns로 지정되어 있는데요, 이 의미는 아래 그림을 보시면 이해할 수 있습니다.

Skew 설명
Skew 설명

Input skew는 signal이 sampling 되는 timing을 지정하는 것이고, output skew는 signal이 driving 되는 timing을 지정해 주는 것입니다. 만약, default 값이 지정되어 있지 않으면, input skew는 #1 step, output #0 step으로 적용됩니다.

References: chip verify

Similar Posts