[SystemVerilog] Interface 개념 설명(modport, clocking block 포함)

새옹지마·2024년 12월 26일
0

SystemVerilog

목록 보기
1/1
post-thumbnail

Interface란 무엇인가?

Interface는 신호를 하나의 블록으로 캡슐화하는 방법입니다. 관련된 모든 신호를 그룹화하여 인터페이스 블록을 형성함으로써 동일한 인터페이스를 다른 프로젝트에서도 재사용할 수 있습니다. 또한, DUT(설계 대상) 및 기타 검증 컴포넌트와의 연결이 더 쉬워집니다.

Syntax

interface name_of_interface(input clk);
...
interface_items
...
endinterface
  • 괄호 내부에는 내부 신호가 아닌 외부에서 받는 port를 작성하시면 됩니다. 위의 예시에선 clk을 외부에서 받도록 했습니다.
  • Interface는 module 내에서 선언되고 instantiate 될 수 있지만, interface내에서는 module을 선언하거나 intance 할 수 없습니다.

Interface를 왜 사용할까요?

바로 예시를 통해 확인하겠습니다.

Interface 개념을 사용하지 않을 때

어떤 모듈이 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 개념을 사용할 때

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번부터 해결해보겠습니다.

  • logic 타입은 기존 Verilog의 reg와 wire의 제약을 극복하기 위해 도입된 SystemVerilog 데이터 타입입니다.
  • Verilog에서는 reg는 procedure 블록(예: always 블록)에서만 값을 할당할 수 있었고, wire는 assign 문을 통해서만 값을 할당할 수 있었습니다.
  • logic은 두 방식 모두에서 값을 할당할 수 있어 더 유연합니다.
  • 또한 logic은 4-state(0, 1, X, Z)를 지원합니다.
  • DUT에 연결된 신호는 X/Z 값을 감지할 수 있도록 4-state를 지원해야 합니다. 만약 이러한 신호가 bit 타입이었다면, X/Z 값이 0으로 표시되었을 것이고, DUT가 X/Z 값을 가지고 있다는 것을 놓쳤을 것입니다.

2번은 modport를 사용하여 해결합니다.


modport

Interface내부에서 Interface 신호들의 direction을 정해주기 위해 사용됩니다.

Syntax

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

Example

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에서도 inputoutput을 정해주어야하죠. 예시를 보시면 modport를 사용해서 slave와 master의 interface를 direction을 포함해서 정의해주고 있습니다.

위 interface를 적용한 Example

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로 신호를 사용하면 됩니다.

  • 참고로 modport에서 사용하는 list는 interface내부에 선언한 것들만 사용 가능합니다.

예제 코드를 많이 참고하시면 좋을 것 같습니다!


Clocking block

  • 위에서 작성한 interface 블록과 modport 모두 타이밍이나 동기화에 대한 정보는 제공하지 않습니다.
  • 클럭 신호와 다른 신호들 간의 정확한 타이밍을 지정하려면 Clocking block을 사용해야합니다.
  • 현업에서 모든 칩은 clock에 의하여 drive되고 sample 되는 synchronous operation을 하기 때문입니다.
  • Clocking block은 clock 신호와 동기화된 신호 집합을 정의합니다.
  • Interface 내부에서 선언합니다.

Clocking block에 대해 알기 전에 skew에 대해 먼저 공부해야 합니다.

Skew

사진 출처: 링크

우리가 반도체의 이상적인 구동을 기준으로 설계하고있지만, 실제로 물리적인 한계 때문에 delay가 발생하겠죠?? 또 설계자가 신호 인가를 딱 clk에 맞춰서 하지 않고 몇초정도 delay되도록 timing을 설정하고 싶을 때가 있을 겁니다. 이것이 skew의 개념과 맞닿아 있습니다.

input skew: 입력 신호가 clock 이벤트에 비해 언제 샘플링되는지를 결정하는 값입니다.
양수일 때: 몇 초전에 샘플링 할건지(typically use).
음수일 때: 몇 초 뒤에 샘플링 할건지.
output skew: 출력 신호가 클럭 이벤트에 비해 언제 구동(driven)되는지를 결정하는 값입니다.
양수일 때: 몇 초뒤에 driven 할건지(typically use).
음수일 때: 몇 초 전에 driven 할건지.

사실 이게 clocking block의 개념의 전부네요. 코드를 통해 바로 알아보죠.


Clocking block Syntax

[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를 사용하지 않아도 됩니다. 아래 예제를 확인해주세용.


Example

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로 설정되어 있다면, #22ns를 의미하므로 클럭 엣지 이후 2ns에 구동됩니다.
  • 마지막 신호 siginout 타입으로, 클럭 엣지보다 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
  • Interface내부에서 clocking block을 선언하고
  • 사용할 때에는 test_bench에 해당 interface를 인스턴스해서 @(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 타임스케일을 사용하면, #22ns를 의미합니다.
  • #1step이란 신호가 이전 시간 step의 끝, 즉 clock의 posedge 직전에 샘플링되어야 함을 나타냅니다.
  • Interface 내부에 task, function를 작성할 수 있습니다.

오늘도 읽어주셔서 감사합니다 ㅎㅎ

profile
반도체, HW ,SW 탐구생활

0개의 댓글