티스토리 뷰

 

처음에 이 글을 쓰려고 기획했을 때, 카테고리를 Testbench로 두어야 할지, SystemVerilog 문법으로 해야 할지 고민했다. 그러다가 어찌 보면 지엽적인 내용일 수도 있는 randomization에 대한 글을 문법 카테고리에서 작성하기로 결정했다. Testbench 연재에는 아키텍처 측면의 접근이나 Top view에서 쓴 글이 더욱 어울리다고 보고, 앞으로 이러한 기조로 글을 써 내려갈 예정이다.

 

어떻게 생각해보면 무작위(random)라는 개념은 세상에 존재하는 매우 친숙한 개념이다. 굳이 오늘 점심 메뉴를 룰렛을 돌려서 결정하거나 게임 캐릭터의 스텟이 난수 값으로 결정되는 등의 특수한 상황이 아니더라도 말이다.

 

 

오늘 뭐 먹을지 고민될 때, 우리는 랜덤이라는 좋은 대안을 찾고는 한다.

 

 

Digital design의 세계에서도 random이라는 것은 매우 중요한 개념인데, 내가 설계한 IP를 검증하기 위해서는 무작위로 생성된 vector를 통해 최대한 많은 case를 검증해야 하기 때문이다. 앞서 말한, 랜덤 입력을 사용하여 다양한 케이스를 만드는 검증 방법을 regression이라고 하며, 이는 아주 오래전부터 사용되고 있는 보편적인 테스팅 기법이다.

 

 

 

 

 

 

이러한 regression test가 성립되기 위해서는 configuration의 난수화가 반드시 필요하다. 여기서 말하는 configuration이란, DUT의 Specification(Spec)을 뜻하며, 유저 시나리오나 입력에 따라 변할 수 있는 값들을 말한다. 이해를 돕기 위해 2가지의 큰 범주로 나눠보면 아래와 같다. 

 

(1) IP protocol 관련된 부분 ex) Input size, HSYNC, VSYNC, address 등    

(2) IP의 SFR(Special Function Register)에 관련된 부분 ex) mode, tuning register, option register 등

 

Randomization은 environment 부분에서 수행하고, Testbench로 넣어줄 수도 있으며, SystemVerilog 문법을 사용하여 Testbench에서 바로 생성할 수도 있다. 검증 엔지니어의 선택에 따라 randomizatoin을 수행하는 위치를 정하게 되며, 어느 방법이 낫냐를 논하기에 어려우므로 구현 스타일의 차이라고 이해하면 좋겠다.

 

 

Regression 환경의 일반적인 구조. 물론, 다양한 변형이 있을 수 있다.

 

 

다양한 Input vector를 생성하여 test를 진행하기 위해서는 randomization이 필요하다고 했다. SystemVerilog에서는 이를 위한 다양한 방법을 제공하고 있다. 다음 장에서는 regression의 꽃이라 볼 수 있는 randomization 관련 system task와 내장 함수 등에 대해 소개하고자 한다.

 

 

 

 

 

 

SystemVerilog의 난수 생성은 일반적인 프로그래밍 언어에서 제공하는 방법과 크게 다르지 않다. SystemVerilog라는 언어가 Design을 위한 부분보다는 검증에 조금 더 포커스를 맞춘 언어이기 때문에, 기존의 S/W 공학에서 사용되는 많은 요소들을 차용했다. 이러한 특성은 상당수의 H/W 엔지니어들이 SystemVerilog라는 언어에 어려움을 느끼게 되는 일종의 장애물이 되는 이유이기도 하다.

 

난수 생성은 의사 난수 생성 알고리즘(Pseudo Random Number Generation, PRNG)에 의해 이루어지는데, 여기서 의사 난수(pseudo random)란 컴퓨터에서 알고리즘으로 생성한 난수이다. 실제로는 완전한 무작위가 아니라 주기적으로 값이 반복되지만, 그 주기가 크기때문에 무작위로 봐도 무방하므로 이러한 의사 난수를 사용한다. SystemVerilog에서 생성되는 random 값들도 모두 이러한 방식의 의사 난수로 만들어진다.

 

 

의사 난수 생성 방법 중 하나인 폰노이만이 제안한 중앙제곱법

 

 

SystemVerilog에서 제공하는 randomization 관련 기능은 크게 3가지 범주로 분류할 수 있다. Constraint(제약 조건)을 줄 수 있는 지 여부에 따라 또한 확률 분포 함수 모델 적용 여부에 따라 아래와 같이 나뉜다.

 

 

 

여기서는 실제로 많이 사용되는 Constrained PRNG와 Non-Constrained System Calls에 대해 설명하고, $random이 왜 잘 사용되지 않는지에 대해 설명하려고 한다. 그리고, 확률 분포(dist_*) 관련 함수들은 이러한 경향을 따르는 난수를 생성할 때는 유용하겠지만, 특정 확률 분포가 필요한 상황이 많지 않아 개인적으로 사용한 경험이 없다.  

 

1. $urandom()

앞서 $random은 잘 사용하지 않는다고 했는데, 그렇다면 어떠한 이유에서 $random을 사용하지 않는 것일까?

 

$random은 IEEE std 1800-2012에 명시되어 있는 specification이고, $urandom은 EDA tool vender에서 제공하는 function이다. $urandom이 $random에 비해 randomization 성능이 좋고, random stabilization 측면에서도 뛰어나고 한다. 개인적인 생각으로 EDA tool 간의 자연스러운 경쟁 가운데, 계속해서 성능을 발전시켜 간 것이 아닌가 싶다.

 

대표적으로 random stabilization 측면에서 $random을 사용할 시, multiple thread에서 결과의 일관성을 항상 보장할 수 없다는 단점이 있다. 그러므로, $urandom을 사용하는 것을 권장한다. 이를 통해 32bit의 크기를 갖는 unsigned random값을 생성할 수 있으며, 만약, 이보다 큰 값을 만들어야 한다면, concatenation(ex. {$urandom(), $urandom()})을 사용해서 더 큰 값도 만들 수 있다.

2. $urandom_range()

$urandom 기능과 동일하지만, 범위를 고정하고 싶을 때 사용한다. min, max 범위 안에서 unsigned 32bit random 값을 생성하는 것이 가능하다. 반면, $random은 음수 값까지 생성 가능하고, 일반적으로 알려져 있는 방법인 %(modulo)를 사용해서 범위를 고정 할 수 있다. $urandom_range가 범위를 인자로 넘길 수 있어 편리한 반면 $random의 경우는 offset과 %(modulo)를 일일이 넣어줘야 하므로 불편하다. 

 

아래 코드와 결과 값을 통해 어떻게 동작하는 지 이해해보자.

 

▼ 소스코드

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
// Non-Constrained System Calls
// urnadom, urandom_range
program test;
    class packet;
  
      logic [7:0] addr;
      logic [9:0] data;
  
      function void do_rand();
      addr = $urandom_range(0,10);
      data = $urandom();
      endfunction
  
    endclass
 
  initial begin
    packet p1;
   packet p2;
    
    p1 = new();
    p2 = new();
             
    #5;
   p1.do_rand();
    p2.do_rand();
 
    $display($time, " ns --> [%m:p1] addr = %0x, data = %0x", p1.addr, p1.data);
    $display($time, " ns --> [%m:p2] addr = %0x, data = %0x", p2.addr, p2.data);
  end
  
endprogram
cs

 

▼ 출력

1
2
3
4
5
6
7
8
9
10
[2020-10-01 22:01:53 EDT] xrun --unbuffered '-timescale' '1ns/10ps' '-sysv' '-svseed' '0'   
SVSEED set from command line: 0
xcelium> run
                   5 ns --> [test.unmblk1:p1] addr = 6, data = 3d1
                   5 ns --> [test.unmblk1:p2] addr = 4, data = 33c
Simulation complete via implicit call to $finish(1) at time 5 NS + 1
./testbench.sv:3 program test;
xcelium> exit
TOOL:    xrun    19.09-s012: Exiting on Oct 012020 at 22:01:55 EDT  (total: 00:00:02)
Done
cs

 

 

3. randomize()

class 내부의 변수들을 randomize하고 싶을 때 사용하는 built-in 함수이다. class 내부 멤버 변수를 rand로 지정하여, randomize를 손쉽게 할 수 있다. post_randomize(), pre_randomize()도 제공하니 사전 작업이나 이후에 어떠한 처리가 필요할 때 유용하게 사용 가능하다. rand type과 randc type으로 지정이 가능한데, 아래와 같은 차이가 있으니 용도에 따라 사용하면 되겠다.

 

- rand

: random으로 생성하고 싶은 datatype에 붙여서 사용한다.

- randc

: cyclic random type으로 한 번 생성된 값은 반복되지 않는다. 생성된 데이터가 각각 배타적이어야 하는 경우에 사용한다.

 

randomization() with {}는with안에 constraints(제약) 사항을 추가하여, 원하는 값이 생성되도록 할 수 있다. 즉, 필요로 하지 않는 값은 생성되지 않게 할 수 있으며, 의미 있는 변수를 생성하고자 할 때 쓸 수 있다. 이러한 방법 외에도 constraint로 조건을 걸어주는 것도 가능한데, 이 번 포스팅에서는 이 내용에 대해서 말하기엔 양이 너무 방대해질 것 같아, 다음 포스팅으로 미루어두려 한다.

 

4. std::randomize()

function이나 task 내부에서도 사용할 수 있으며, standard library에서 제공하는 기본 함수이다. 반드시 class 값 내부에서만 사용 가능한 randomize()와 다르게 어디서도 사용할 수 있으며, 반드시 rand형 datatype만 randomize 할 수 있는 것이 아니라, 일반 변수도 난수로 생성 가능하다. 

 

아래 코드와 결과 값을 통해 어떻게 동작하는 지 이해해보자.

 

▼ 소스코드

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
50
// Constrained PRNG
// randomize(), std::randomize()
program test;
  
    class packet;
  
    logic    [9:0] size;
   rand bit [7:0] addr;
   rand bit [9:0] data;
  
      function void initialize();
        size = 32;
      endfunction : initialize
  
    endclass : packet
    
  logic [9:0] queue_size;
  
  initial begin
 
    packet p1;
   packet p2;
    
    p1 = new();
    p2 = new();
    
    #5
    p1.initialize();
    p2.initialize();
             
    #5;
    if (!(p1.randomize() with { p1.addr inside {[32:48]}; })) begin
      $display($time, " ns ---> [%m:p1] Randomization is failed");
      $finish;
    end
    if (!p2.randomize()) begin
      $display($time, " ns ---> [%m:p2] Randomization is failed");
      $finish;
    end
    if (!std::randomize(queue_size))begin
      $display($time, " ns ---> [%m:local] Randomization is failed");
      $finish;
    end
 
    $display($time, " ns --> [%m:p1] addr = %03x, data = %03x, size = %03d", p1.addr, p1.data, p1.size);
    $display($time, " ns --> [%m:p2] addr = %03x, data = %03x, size = %03d", p2.addr, p2.data, p2.size);
    $display($time, " ns --> [%m:local] queue_size = %03x", queue_size);
  end
  
endprogram
cs

 

▼ 출력

1
2
3
4
5
6
7
8
9
10
11
[2020-10-02 01:29:01 EDT] xrun --unbuffered '-timescale' '1ns/10ps' '-sysv' '-svseed' '2'   
SVSEED set from command line: 2
xcelium> run
                  10 ns --> [test.unmblk1:p1] addr = 02d, data = 38c, size = 032
                  10 ns --> [test.unmblk1:p2] addr = 0d5, data = 06c, size = 032
                  10 ns --> [test.unmblk1:local] queue_size = 1dd
Simulation complete via implicit call to $finish(1) at time 10 NS + 1
./testbench.sv:6 program test;
xcelium> exit
TOOL:    xrun    19.09-s012: Exiting on Oct 022020 at 01:29:03 EDT  (total: 00:00:01)
Done
cs

 

결과를 보면, packet class 내부의 rand type의 변수는 난수 값이 들어갔고, 일반 변수인 size는 처음에 초기화해준 값인 32가 그대로 있는 것을 확인할 수 있다. std::randomize()를 통해 class 외부의 일반 변수인 queue_size 가 제대로 randmoization 된 것도 결과를 통해 확인할 수 있다.

 

 

 

 

 

 

아무래도 random simulation에서는 재현성 측면에서의 고려를 하지 않을 수 없다. 검증이란 bug가 존재하는 케이스를 검출하고자 하는 것인데, 랜덤 요소를 다시 재현할 수 없다면 무슨 의미가 있겠는가? 그리하여 다양한 EDA tool 회사에서는 초기 seed를 설정할 수 있는 방법을 제공하고 있다.

 

커맨드 라인에 실행 구문의 argument로 해당 명령어를 함께 넣어주면, 해당 seed가 초기 값으로 들어가게 된다. 이러한 경우, log에 해당 seed를 기록해 둔다면 추후 bug가 발생했을 때 동일 seed로 simulation을 재현할 수 있다. seed 값은 앞에서 설명한 environment 부분에서 random으로 생성하는 방식이 통상적으로 사용된다. 아래에 각 EDA tool에서 제공하는 seeding 명령어를 표로 정리하였다.

 

 

 

 

 

 

 

 

Randomize는 검증에 있어서 기본 중에 기본인 개념이다. 제대로 알고 사용하는 것이 중요하며, 경우에 따라 의도하지 않은 결과를 초래하는 경우가 왕왕 있어서 주의가 필요하다. 다음 포스팅으로는 Constrained Random Verification(CRV)의 개념과 constraint를 명시하는 대표적인 예시들을 다뤄보고자 한다. 이와 더불어 DVCON2014에서 발표된 논문인 The Top Most Common System Verilog Constrained Random Gotchas에서 소개한 실수하기 쉬운 사례와 이를 회피하기 위한 방법에 대해 간단히 다뤄보고자 한다.

 

 

댓글
공지사항