티스토리 뷰

 

Testbench의 기본이란 무엇일까? 검증하고자 하는 target에 spec에 맞는 입력을 제대로 넣어줄 수 있어야 한다. 랜덤 한 입력을 넣어주게 되지만, 검증력을 높이기 위해서는 적절한 constraints를 설정하여, 유의미한 값들이 생성되어야 한다. 검증 환경의 구현에 대해 이야기해보자면, 말마따나 정답이 없는 세계라고 할 수 있다. 검증 엔지니어의 취향이나 본인만의 신념대로 환경이 꾸려지기 때문에 같은 IP를 검증한다 해도 다양한 형태의 testbench가 가능하다.

 

어떤 것이 효율적인가에 대한 물음은 항상 검증 엔지니어들을 따라다니는 숙제이다. 좋은 검증 환경이란 유연하고 확장성이 좋아야하며, 재사용성도 보장되어야 한다. 그리고 언제나 검증에 있어서 빠른 처리 시간의 충족이라는 현실적인 문제가 항상 따라다니게 된다. 검증할 때마다 컴파일을 하지 않고, 기존에 만들어 둔 스냅샷을 사용하는 방법은 이미 업계에서는 기본적인 내용이 되었다. 또한 레퍼런스 값과의 비교를 위해 사용되는 버퍼를 미리 모두 만들어두지 않는 것도 시간을 절약하기 위해 지켜야 하는 부분이다. 되도록이면 저장 공간을 효율적으로 사용하는 것이 결과적으로 시간 싸움인 이 세계에서 현명하게 살아남는 해법이라 할 수 있다. 이렇듯 끊임없는 고민과 노력이 필요한 분야가 검증이며, 그래서 매력 있는 분야가 아닌가 싶다. 

 

앞서 말한 컴파일시, 스냅샷을 미리 찍어두고 활용하는 방법을 잘 설명한 블로그가 있어 아래에 링크를 달아두었다.

▼스냅샷 사용하는 방법에 대한 링크 (Donny님의 블로그)

http://donny.co.kr/wp/?p=231

 

[Verilog] 새로 컴파일하지 않고 테스트 입력/조건을 바꾸는 방법

Donny | Design, Simulation, Verification, Verilog

donny.co.kr

 

서론이 길었다. 

 

저번 시간에 이어 오늘은 testbench의 핵심적인 부분이라 할 수 있는 Driver에 대해 소개하려 한다. Driver는 DUT에서 필요로 하는 입력을 생성하여 DUT로 밀어 넣는 부분이다. 사용자가 원하는 입력을 넣어줄 수도 있어야 하지만, 검증력을 높이기 위해서는 무작위 한 케이스들을 생성해야 한다. 이렇게 랜덤 한 입력을 충분한 회차 동안 DUT에 인가하고 reference 모델과 비교하는 작업을 regression이라 한다.

 

DUT로 원하는 입력을 넣어주는 역할을 하는 Driver

spec에 맞는 무작위 입력을 넣어주기 위한 stimulus 생성에 앞서, 기본적인 신호들을 만들어주는 모듈을 살펴보자. 

 

 

 

clock과 reset은 기본 중에 기본. clock이 없는 디지털 시스템은 존재하지 않는다고 해도 과언이 아니다. 보통 clock은 PLL이라고 하는 회로에 의해서 생성되고 해당 clock 주파수에서 원하는 동작을 제대로 해야 제품으로써의 가치가 있다. 상황에 따라 검증 환경에 실제 PLL 모델 등을 구현해서 넣는 경우도 있겠지만, 대부분의 경우에는 간단한 clock 모델을 넣어서 검증을 진행한다. reset도 testbench 내부에서 active low의 H/W reset을 구현하여 전체 시스템이 정확히 reset이 될 수 있도록 신호를 넣어준다. timescale에 따라서 시뮬레이션에 생성되는 타이밍이 정해진다.

 

다음의 예제에서는 기본적으로 1ns/10ps의 해상력을 가지는 timescale로 clock과 reset을 생성하는 모델을 첨부하였다. clock은 기본적으로는 100 Mhz를 갖는 clock이 생성되지만, 사용자가 값을 지정하여 원하는 주파수로 동작할 수 있도록 parameter를 통해 원하는 동작 주파수를 받을 수 있도록 하였다.

 

▼ clock/reset_n generator 예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
`timescale 1ns/10ps
module tb();
  logic clk;
  logic rst_n;
  logic freq_set;
  event eCLK, eRSTN;
      
  parameter freq = 100// 100Mhz
  realtime delay = (freq_set) ? 1000/(freq*2) : 1000/(100*2);
  integer reset_cyc = 2;
  
  
// tb logging 
initial begin
   #5
    ->eCLK;
    #5
    ->eRSTN;
  for(int i=0; i<40; i++begin
     #2.5 $display($time, "ns --> [%m] clk = %b, reset = %b",clk,rst_n); 
   end
  #10 $finish; // simulation end
end
  
  // clk & rst_n gen
initial begin
  clk <= 'b0;
  rst_n <= 'b1;
  fork
  // thread 01 - clk gen
      begin
          @ eCLK
        $display($time, "ns --> [Info] clk is generated.");
          forever    #delay clk <= ~clk;
      end
  // thread 02 - rst_n gen
      begin
          @ eRSTN
        $display($time, "ns --> [Info] rst_n is generated.");
          #30 rst_n <= 'd0;
      repeat(reset_cyc+1) @ (posedge clk);
          rst_n <= 'd1;
      end
  join
  end
endmodule
  
 
 
cs

▼ simulation 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  5ns --> [Info] clk is generated.
 10ns --> [Info] rst_n is generated.
 13ns --> [tb.unmblk1] clk = 1, reset = 1
 15ns --> [tb.unmblk1] clk = 1, reset = 1
 18ns --> [tb.unmblk1] clk = 0, reset = 1
 20ns --> [tb.unmblk1] clk = 0, reset = 1
 23ns --> [tb.unmblk1] clk = 1, reset = 1
 25ns --> [tb.unmblk1] clk = 1, reset = 1
 28ns --> [tb.unmblk1] clk = 0, reset = 1
 30ns --> [tb.unmblk1] clk = 0, reset = 1
 33ns --> [tb.unmblk1] clk = 1, reset = 1
 35ns --> [tb.unmblk1] clk = 1, reset = 1
 38ns --> [tb.unmblk1] clk = 0, reset = 1
 40ns --> [tb.unmblk1] clk = 0, reset = 1
 43ns --> [tb.unmblk1] clk = 1, reset = 0
 45ns --> [tb.unmblk1] clk = 1, reset = 0
 48ns --> [tb.unmblk1] clk = 0, reset = 0
 50ns --> [tb.unmblk1] clk = 0, reset = 0
 53ns --> [tb.unmblk1] clk = 1, reset = 0
 55ns --> [tb.unmblk1] clk = 1, reset = 0
 58ns --> [tb.unmblk1] clk = 0, reset = 0
 60ns --> [tb.unmblk1] clk = 0, reset = 0
 63ns --> [tb.unmblk1] clk = 1, reset = 1
 65ns --> [tb.unmblk1] clk = 1, reset = 1
 68ns --> [tb.unmblk1] clk = 0, reset = 1
 70ns --> [tb.unmblk1] clk = 0, reset = 1
 73ns --> [tb.unmblk1] clk = 1, reset = 1
 75ns --> [tb.unmblk1] clk = 1, reset = 1
 78ns --> [tb.unmblk1] clk = 0, reset = 1
 80ns --> [tb.unmblk1] clk = 0, reset = 1
 83ns --> [tb.unmblk1] clk = 1, reset = 1
 85ns --> [tb.unmblk1] clk = 1, reset = 1
 88ns --> [tb.unmblk1] clk = 0, reset = 1
 90ns --> [tb.unmblk1] clk = 0, reset = 1
 93ns --> [tb.unmblk1] clk = 1, reset = 1
 95ns --> [tb.unmblk1] clk = 1, reset = 1
 98ns --> [tb.unmblk1] clk = 0, reset = 1
100ns --> [tb.unmblk1] clk = 0, reset = 1
103ns --> [tb.unmblk1] clk = 1, reset = 1
105ns --> [tb.unmblk1] clk = 1, reset = 1
108ns --> [tb.unmblk1] clk = 0, reset = 1
110ns --> [tb.unmblk1] clk = 0, reset = 1
Simulation complete via $finish(1) at time 120 NS + 0
cs

 

코드에서 100ns 후에 reset이 low로 떨어지는 것을 확인할 수 있고, 2 cycle의 clock만큼 유지하다가 다시 high로 올라가는 것을 확인할 수 있다. clock은 100Mhz의 주파수로 튀기고 있다. 아래 timing diagram과 같은 출력이 생성되고 있는 것이다. 

 

clk, rst_n의 timing diagram. event가 발생한 위치를 표시하였다.

 

 

 

systemverilog에는 event라는 문법이 있다. 여러 개의 thread로 돌아가는 각각의 process들이 event를 통해 서로 시작과 끝을 알릴 수 있도록 간단하게 구현해보았다. 이 방법이 정답이라고는 볼 수 없지만, 비교적 간단하게 testbench를 꾸릴 수 있는 방법이라고 생각한다. 앞서 설명한 코드를 보면, clock을 생성하는 부분을 thread1, reset을 생성하는 부분을 thread2로 할당한 것을 확인할 수 있다. 해당 부분은 begin~end로 묶여 있으며, 각 부분은 eCLK과 eRSTN이라는 event 변수를 통해 제어되고 있다.

 

event 문법

-> : event를 trigger 시킨다.

@ : event를 기다리다가 trigger되면, 아래 process를 수행한다.

->> : non-blocking event를 trigger시키는 구문이라 하지만, 실제로 사용해본 적은 없다.

 

 

 

여기서는 다양한 fork-join 문법을 소개하고, multithread를 구현하는 방법을 첨부하였다. 본 예제에서는 기본적인 fork join 문법을 사용했으나, 다양한 케이스에 적용할 수 있는 변형이 존재한다. fork-join으로 묶여있는 process는 두 thread가 모두 완료되어야 다음 구문으로 넘어갈 수 있다. 즉 각각의 process들을 병렬로 처리할 수 있는 문법으로 testbench를 작성할 때 매우 유용하게 사용할 수 있다. clock과 reset은 각각 독립적으로 생성되어야 하기 때문에 병렬로 처리할 수 있도록 fork-join구문을 사용하였다. 아래에 여러 변형을 소개하는 그림을 첨부하였다.

 

 

fork-join의 다양한 변형

 

fork-join_any의 경우 병렬 처리되는 thread 중 먼저 끝나는 것이 있으면, 바로 다음 구문이 실행된다.

fork-join_none은 실행되고 있는 thread의 종료 여부와 관계 없이 다음 구문이 실행된다.

 

예제 코드와 설명이 자세히 나와있는 블로그를 발견하여, 아래에 링크를 첨부한다.

▼fork-join 구문에 대한 링크 (IKSciting님의 블로그)

https://iksciting.com/fork-join-and-disable-fork/

 

fork-join and disable fork - IKSciting

BFM(Bus Functional Model), scoreboard 등 testbench를 개발하면서 두 개 이상의 process를 동시에 실행하도록 구성해야 하는 경우가 있다. 간단한 예로, 특정 task를 수행하되 일정 시간이 지나면 timeout이 발생

iksciting.com

에필로그...

 

바쁘다는 핑계로 업로드가 늦었다.

"네 처음은 미약하였으나 끝은 창대하리라"와 같은 마음으로 주 1회 업로드를 꿈꿨건만

현실은 그렇지 못했다.

반성하고 반성하며, 더욱 노력할 것을 다짐한다.

 

댓글
공지사항