
Interface는 신호를 하나의 블록으로 캡슐화하는 방법입니다. 관련된 모든 신호를 그룹화하여 인터페이스 블록을 형성함으로써 동일한 인터페이스를 다른 프로젝트에서도 재사용할 수 있습니다. 또한, DUT(설계 대상) 및 기타 검증 컴포넌트와의 연결이 더 쉬워집니다.
interface name_of_interface(input clk);
...
interface_items
...
endinterface
바로 예시를 통해 확인하겠습니다.
어떤 모듈이 APB 버스를 포트로 설정하고자 할 때, 모듈 내부에 input, output을 하나하나 작성합니다. 만약 AHB 버스도 사용한다면 또 하나하나 작성해야합니다. 사실 모듈이 하나일 땐 그냥 작성해도 오랜 시간이 걸리지 않습니다.
module example_slave();
input [31:0] paddr; //APB Bus
input [31:0] pwdata;
output [31:0] prdata;
input penable;
input pwrite;
input psel;
...
하지만 아래와 같이 APB Bus에 대해 Interface 블록을 만들어뒀다면, 가져다 쓰기만 하면 됩니다.
interface apb_if ();
logic [31:0] paddr;
logic [31:0] pwdata;
logic [31:0] prdata;
logic penable;
logic pwrite;
logic psel;
endinterface
(Interface 블록 선언)
module example_module();
...
apb_if apb_if_1; //interface 인스턴스
...
endmodule
APB BUS를 쓰는 module이 한 두개가 아니라 굉장히 많다면, 신호를 하나하나 쓰는 것 보다 interface 블록을 인스턴스하는게 훨씬 편하겠죠?
하지만 여기서 의문이 생깁니다.
1. logic type은 뭐지?
2. Interface 블록엔 input, output 포트 방향이 정해져있지 않은데 괜찮나?
1번부터 해결해보겠습니다.
bit 타입이었다면, X/Z 값이 0으로 표시되었을 것이고, DUT가 X/Z 값을 가지고 있다는 것을 놓쳤을 것입니다.2번은 modport를 사용하여 해결합니다.
Interface내부에서 Interface 신호들의 direction을 정해주기 위해 사용됩니다.
modport [identifier] (
input [port_list],
output [port_list]
);
interface my_if (input clk);
logic sready; // Indicates if slave is ready to accept data
logic rstn; // Active low reset
logic [1:0] addr; // Address
logic [7:0] data; // Data
modport slave ( input addr, data, rstn, clk,
output sready);
modport master ( output addr, data,
input clk, sready, rstn);
endinterface
보통 master와 slave간의 주고받는 packet은 똑같잖아요? 하지만 그 방향은 정반대입니다. 따라서 interface에서도 input과 output을 정해주어야하죠. 예시를 보시면 modport를 사용해서 slave와 master의 interface를 direction을 포함해서 정의해주고 있습니다.
module master0 (my_if.master mif);
always @ (posedge mif.clk) begin
// If reset is applied, set addr and data to default values
if (! mif.rstn) begin
mif.addr <= 0;
mif.data <= 0;
end else begin
...
...
end
end
endmodule
module top (my_if my_intf);
// Pass the "master" modport to master
master0 m0 (my_intf.master);
endmodule
이렇게 사용할 module에서 포트에 사용할 인터페이스를 작성해주시면 됩니다.
my_if.master mif; : my_if 인터페이스에 있는 master 모드포트를 사용할건데 걔 이름을 mif라고 할거야.
사용할 땐, mif.xxx로 신호를 사용하면 됩니다.
예제 코드를 많이 참고하시면 좋을 것 같습니다!
Clocking block에 대해 알기 전에 skew에 대해 먼저 공부해야 합니다.

사진 출처: 링크
우리가 반도체의 이상적인 구동을 기준으로 설계하고있지만, 실제로 물리적인 한계 때문에 delay가 발생하겠죠?? 또 설계자가 신호 인가를 딱 clk에 맞춰서 하지 않고 몇초정도 delay되도록 timing을 설정하고 싶을 때가 있을 겁니다. 이것이 skew의 개념과 맞닿아 있습니다.
input skew: 입력 신호가 clock 이벤트에 비해 언제 샘플링되는지를 결정하는 값입니다.
양수일 때: 몇 초전에 샘플링 할건지(typically use).
음수일 때: 몇 초 뒤에 샘플링 할건지.
output skew: 출력 신호가 클럭 이벤트에 비해 언제 구동(driven)되는지를 결정하는 값입니다.
양수일 때: 몇 초뒤에 driven 할건지(typically use).
음수일 때: 몇 초 전에 driven 할건지.
사실 이게 clocking block의 개념의 전부네요. 코드를 통해 바로 알아보죠.
[default] clocking [identifier_name] @ [event_or_identifier]
default input #[delay_or_edge] output #[delay_or_edge]
[list of signals]
endclocking
delay_or_edge: clk의 몇 초전에 샘플링을 할 것인지, 즉 input skew값
delay_or_edge: clk의 몇 초 뒤에 반영할 것인지, 즉 output skew값
이 Syntax처럼 default를 사용하지 않아도 됩니다. 아래 예제를 확인해주세용.
1.
clocking cb @(clk);
input #1ps req;
output #2 gnt;
input #1 output #3 sig;
endclocking
req는 1ps의 skew를 가지며, 클럭 엣지(clk)보다 1ps 이전에 샘플링됩니다.gnt는 2 타임 유닛의 출력 스큐를 가지며, 현재 스코프에서 설정된 타임스케일을 따릅니다. 만약 타임스케일이 1ns/1ps로 설정되어 있다면, #2는 2ns를 의미하므로 클럭 엣지 이후 2ns에 구동됩니다.sig는 inout 타입으로, 클럭 엣지보다 1ns 이전에 샘플링되고, 클럭 엣지의 3ns 이후에 구동됩니다.2.
clocking cb0 @(posedge clk);
default input #1ns output #2ns;
output a;
output b;
input c;
input d;
endclocking: cb0
이렇게 모든 signal에 적용되는 default값을 정해줄 수도 있습니다.
a와 b 는 2ns뒤에 drive되고 c, d는 1ns 전에 sampling되겠죠?
3. waveform를 포함한 예제
코드 및 파형 출처: 링크
module des (output reg[3:0] gnt);
always #1 gnt <= $random;
endmodule
//////////////////////////////////////////////////////
interface _if (input bit clk);
logic [3:0] gnt;
clocking cb_0 @(posedge clk);
input #0 gnt;
endclocking
clocking cb_1 @(posedge clk);
input #1step gnt;
endclocking
clocking cb_2 @(posedge clk);
input #1 gnt;
endclocking
clocking cb_3 @(posedge clk);
input #2 gnt;
endclocking
endinterface
//////////////////////////////////////////////////////
module tb;
bit clk;
always #5 clk = ~clk;
initial clk <= 0;
_if if0 (.clk (clk));
des d0 (.gnt (if0.gnt));
initial begin
fork
begin
@(if0.cb_0);
$display ("cb_0.gnt = 0x%0h", if0.cb_0.gnt);
end
begin
@(if0.cb_1);
$display ("cb_1.gnt = 0x%0h", if0.cb_1.gnt);
end
begin
@(if0.cb_2);
$display ("cb_2.gnt = 0x%0h", if0.cb_2.gnt);
end
begin
@(if0.cb_3);
$display ("cb_3.gnt = 0x%0h", if0.cb_3.gnt);
end
join
#10 $finish;
end
endmodule
@(intf.cb)로 사용하네요.결과

log
ncsim> run
cb_3.gnt = 0x9
cb_2.gnt = 0x3
cb_1.gnt = 0x3
cb_0.gnt = 0xd
Simulation complete via $finish(1) at time 15 NS + 0
waveform을 보시면 언제 샘플링되는지 알 수 있습니다.
참고
#0 스큐를 가진 입력은 해당 클로킹 이벤트와 동시에 샘플링되지만, 경쟁 상태(race condition)를 피하기 위해 Observed 영역에서 수행됩니다.1ns/1ps 타임스케일을 사용하면, #2는 2ns를 의미합니다.#1step이란 신호가 이전 시간 step의 끝, 즉 clock의 posedge 직전에 샘플링되어야 함을 나타냅니다.오늘도 읽어주셔서 감사합니다 ㅎㅎ