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에 절차 할당과 연속 할당을 사용할 수 있습니다.
먼저 syntax를 알아볼까요?
interface [name] ([port_list]);
[list_of_signals]
endinterfaceInterface에는 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
endmoduleInterface의 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
endmoduleInterface를 사용해 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
endmoduleInterface인 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;
endclockingcb라고 naming 된 clocking block에는 input signal인 from_DUT와 output signal인 to_DUT가 있습니다. 각각 skew가 1ns, 2ns로 지정되어 있는데요, 이 의미는 아래 그림을 보시면 이해할 수 있습니다.
Input skew는 signal이 sampling 되는 timing을 지정하는 것이고, output skew는 signal이 driving 되는 timing을 지정해 주는 것입니다. 만약, default 값이 지정되어 있지 않으면, input skew는 #1 step, output #0 step으로 적용됩니다.
References: chip verify