티스토리 뷰

 

엔지니어라면, 누구나 한 번쯤 해봤을 법한 생각이 있다.

 

입력 spec을 넣어주면 자동으로 H/W를 뱉어주는 마법 같은 시스템

상상 속에서만 존재하는 이야기라고 생각할지도 모르지만, 이를 위한 다양한 시도들은 아직도 현재 진행 중이다.

 

상상 속의 존재는 여자친구 하나로 충분해

 

자동화에 대한 열망은 VPP(Verilog Pre-Processor)와 C++/perl 등의 library를 사용하여 코드를 뽑아주는 것부터해서 emacs, vim 등의 edit 도구를 이용한 반복 작업의 최소화, IPXACT 등의 xml을 사용하여 packaging, integration과 문서까지 만들어주는 등의 다양한 형태로 실현되고 있다.

 

주 52시간 근무 제도, 워라벨 중시 등의 시대적 흐름으로 인해 우리나라의 기업문화도 더 이상 예전처럼 공돌이를 갈아서 제품을 만들어서는 살아남을 수 없게 되었다. 반복적인 작업을 최소화하고, 업무 자동화를 통해서 효율적으로 일을 하지 않고서는 시장에서 도태될 수밖에 없는 것이다.

 

이젠 안녕

 

Verilog를 사용하면서 `define, `ifdef나 parameter, localparam 등의 문법을 사용해본 경험은 흔히 있을 것이다. 이를 통해서도 Bit width를 조절하거나 특정 조건에서만 활성화되는 일부 configurable 한 RTL을 만들 수 있다. 다만 오늘 다룰 generate 같은 문법은 사용하지 않고서는 구현할 수 있는 자동화의 범위가 다소 제한된다. 그러나 잘 알지 못하는 문법을 무턱대고 사용하자니 뭔가 찜찜한 것이 사실이다. 

 

물론 나도 그랬던 사람 중에 하나였다.

 

해당 문법을 사용했던 경험이라곤 ISP 블럭을 설계하면서, 코드 길이를 줄이고자 kernel단에서 mux를 구현할 때 썼던 것이 전부였다. 이번 블로그 포스팅을 준비하면서 generate문을 공부하고 나니, 지금까지는 제대로 몰라서 잘 사용하지 못했던 것은 아닐까라는 생각이 든다. 특히 반복적인 구조나 특정 조건에 따라 처음부터 결정 되는 H/W를 만들고 싶다면 이보다 쉬운 대안을 찾기는 어렵다고 본다.

 

 


 

 

 

자동화는 정말 마법일까?

Generate 문법에 대해 자세히 알아보기 전에 간단히 자동화라는 주제에 대해서 이야기해보려 한다. 자동화가 필요한 영역을 크게 두 가지로 분류해보면 아래와 같다.

 

1. 반복 작업이 필요한 경우
2. 재사용이 요구되는 경우

 

일상 생활에서도 반복 작업이 필요한 경우는 흔하게 찾아볼 수 있다. 대학교 수강 신청 기간에 광클 신공 대신 마우스 자동 클릭 프로그램을 사용해 편하게 목적을 달성하는 친구들도 있었고, 게임도 자동 사냥이라는 개념이 생기기 전엔 불법 매크로를 통해 반복적이고 지루한 사냥을 편법으로 스킵하는 유저들이 존재했다.

 

설계의 경우에도 똑같이 상황을 적용할 수 있다. 반복적인 단순 코드를 100~200줄 계속 써내려 가는 것보다 for문과 같은 반복문을 사용해 몇 줄 만에 끝내는 것이 좋은 판단이다. 그러니 자동화를 통해 귀찮은 일들은 기계에게 맡기고 조금 더 생산적인 곳에 에너지를 쏟아붓도록 하자.

 

그래도 불법 프로그램은 쓰지 맙시다.

 

재사용률이 높거나 이미 대략적인 포맷이 잡혀있고, 변화가 크지 않은 모듈의 경우에는 매번 설계하는 것만큼 비효율적인 일은 없다. 이럴 때에는 메타데이터나 파라미터를 입력으로 받아 재사용되는 부분을 그대로 뽑아주는 방식으로 설계를 자동화하는 것이 좋은 방법이다. 

 

대부분의 팹리스 업체에서는 메모리나 레지스터 뱅크처럼 구조가 잘 바뀌지 않는 것들에 대해서는 이러한 방식으로 설계를 진행하고 있을 것이다. 

 

 

단순하게 생각하면 이렇게 심플하다. 하지만 명(明)이 있으면 암(暗)도 있는 법.

 

자동화라는 마법이라고 소제목을 정했지만, 정말 마법같이 환상적인 부분만 있는 것은 아니다. 신규 기능이나 수정 사항이 있을 때마다 자동화 시스템의 수정과 이력 관리가 수반되어야 하며, 기계가 추출한 부분이 항상 옳다는 보장이 없으니 검증을 게을리해서는 안된다.

 

"그냥 스크립트 돌린 거 썼는데요?"

 

이런 식의 대답은 곤란하다. 문제가 생겼을 때 기계가 대신 책임져주는 법은 없다. 자동화 시스템을 사용하기로 결정한 설계자에게 일차적인 책임이 있으니, 반드시 미리 체크를 하는 습관을 들이자. 

 

 


 

 

 

반복적인 일은 따분하면서 시간도 많이 소요된다.

모듈이나 코드를 여러 번 instantiation하거나 신호선을 대량으로 연결해줘야 할 때, generate loop를 사용하면 코드 작성에 들어가는 시간을 많이 줄일 수 있다. generate와 endgenerate 사이에 반복문을 기술해주면 되며, integer 데이터 타입인 genvar를 사용하여 반복문에서 사용되는 index 등을 정의할 수 있다.

 

genvar는 elaboration time에 사용되고, simulation time에는 사라진다. nested loop에서 동일한 변수를 사용하는 것은 불가능하며, generate 문 안에서 중복돼서 사용하는 것은 문제없다.

 

generate문의 기본 구조

샘플 코드로 16개의 입력 신호 중에 하나를 선택하여 출력하는 mux를 generate문을 사용하여 구현하였다. 개별적으로 일일이 연결할 필요 없이 for loop을 통해서 sel 신호에 따라 각각의 값이 할당된다.

 

▼ 16to1 mux 소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module mux_16to1
(
    input  [9:0] data_in[0:15],
    input  [3:0] sel,
    output [9:0] data_out
);
 
    wire [9:0] temp[0:15];
    
    // for-loop creates 16 assign statements
    genvar i;
    generate
        for (i=0; i<16; i++begin : gen_mux
            assign temp[i] = (sel==i) ? data_in[i] : 'h0;
        end
    endgenerate
 
    assign data_out = temp[ 0| temp[ 1| temp[ 2| temp[ 3| 
                      temp[ 4| temp[ 5| temp[ 6| temp[ 6| 
                      temp[ 8| temp[ 9| temp[10| temp[11| 
                      temp[12| temp[13| temp[14| temp[15];
 
endmodule
 
cs

 

▼ 16to1 mux 테스트벤치 소스코드

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
`timescale 1ns/10ps
module tb_mux_16to1();
 
logic [9:0] data_in[0:15];
logic [3:0] sel;
logic [9:0] data_out;
integer i;
    
mux_16to1 _mux_16to1(.*);
 
initial begin
      $display($time, "ns --> [Info] Testbench Start!");
    #10
    for (i=0; i<16; i++begin    
        data_in[i] <= i;
        #10
        $display($time, "ns --> [Info] data_in[%02d] = %03x", i, data_in[i]);
    end
 
    for (i=0; i<16; i++begin    
        sel <= i;
        #10
        $display($time, "ns --> [Info] sel = %03d, data_out = %03x", sel, data_out);
    end
 
    #100
    $display($time, "ns --> [Info] Testbench End!");
    $finish;
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
xcelium> run
                   0ns --> [Info] Testbench Start!
                  20ns --> [Info] data_in[00= 000
                  30ns --> [Info] data_in[01= 001
                  40ns --> [Info] data_in[02= 002
                  50ns --> [Info] data_in[03= 003
                  60ns --> [Info] data_in[04= 004
                  70ns --> [Info] data_in[05= 005
                  80ns --> [Info] data_in[06= 006
                  90ns --> [Info] data_in[07= 007
                 100ns --> [Info] data_in[08= 008
                 110ns --> [Info] data_in[09= 009
                 120ns --> [Info] data_in[10= 00a
                 130ns --> [Info] data_in[11= 00b
                 140ns --> [Info] data_in[12= 00c
                 150ns --> [Info] data_in[13= 00d
                 160ns --> [Info] data_in[14= 00e
                 170ns --> [Info] data_in[15= 00f
                 180ns --> [Info] sel = 000, data_out = 000
                 190ns --> [Info] sel = 001, data_out = 001
                 200ns --> [Info] sel = 002, data_out = 002
                 210ns --> [Info] sel = 003, data_out = 003
                 220ns --> [Info] sel = 004, data_out = 004
                 230ns --> [Info] sel = 005, data_out = 005
                 240ns --> [Info] sel = 006, data_out = 006
                 250ns --> [Info] sel = 007, data_out = 000
                 260ns --> [Info] sel = 008, data_out = 008
                 270ns --> [Info] sel = 009, data_out = 009
                 280ns --> [Info] sel = 010, data_out = 00a
                 290ns --> [Info] sel = 011, data_out = 00b
                 300ns --> [Info] sel = 012, data_out = 00c
                 310ns --> [Info] sel = 013, data_out = 00d
                 320ns --> [Info] sel = 014, data_out = 00e
                 330ns --> [Info] sel = 015, data_out = 00f
                 430ns --> [Info] Testbench End!
Simulation complete via $finish(1) at time time time time time time time time 430 NS + 0
./testbench.sv:30     $finish;
xcelium> exit
cs

 

 


 

 

모듈화 설계에 딱 맞는 문법

앞서 설명했듯이 generate문은 elaboration time에 각 신호선을 연결하게 된다. 합성할 때도 parameter의 조건에 따라 net이 고정적으로 생성되기 때문에 on-the-fly로 H/W가 더해지거나 사라진다고 생각해서는 안된다. 모든 기능을 넣어두고 선택적으로 쓰고 싶다면 이 문법의 사용은 적절치 않으며, 처음부터 선택적으로 회로를 구현하고 싶을 때에 사용해야 한다.

 

조건에 따라 configurable 한 회로를 구현할 때 parameter와 조건문(if, case)을 사용하게 된다. 이때, 단순히 assign과 같은 연속 할당문 외에도 always와 같은 절차형 할당문으로도 구현 가능하다. 즉, 실제 Flip-Flop으로 생성되는 순차 회로도 generate를 통해 만들 수 있다는 것이다. 

 

예제 코드를 통해 실제 사용되는 예시를 알아보자. parameter 값에 따라 생성되는 회로가 달라지도록 구현하였다. 여기서는 간단한 조합회로를 만들었지만, 순차 회로의 경우도 크게 다르지 않다.

 

아래는 간단한 예제로 입력은 a > b로 제한하며, 이에 음수 처리 등은 고려하지 않았다.
OP_TYPE = 0 인 경우, a + b
OP_TYPE = 1인 경우, a - b
OP_TYPE = 2인 경우, (a + b) >> 1
DEFAULT는 (a - b) >> 1

 

▼ Configurable RTL 소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module configurable_rtl
#(parameter OP_TYPE = 0) // default value
(
    input  [9:0] a,
    input  [9:0] b,
    output [10:0] result
);
 
    generate
        if (OP_TYPE == 0begin
            assign result = a + b;
        end
        else if (OP_TYPE == 1begin
            assign result = a - b;
        end
        else if (OP_TYPE == 2begin
            assign result = (a + b) >> 1;
        end
        else begin
            assign result = (a - b) >> 1;
        end
    endgenerate
 
endmodule
cs

 

▼ Configurable RTL 테스트벤치 소스코드 <- reference function과 비교할 수 있도록 구현

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
`timescale 1ns/10ps
module tb_configurable_rtl();
 
logic [9:0] a;
logic [9:0] b;
logic [10:0] result;
 
parameter _OP_TYPE = 0;    
 
configurable_rtl #(.OP_TYPE(_OP_TYPE))
_configurable_rtl(.*);
 
function [10:0] ref_result;
    input [9:0] ref_a, ref_b;
    begin
        if (_OP_TYPE == 0begin
            ref_result = ref_a + ref_b;
        end
        else if (_OP_TYPE == 1begin
            ref_result = ref_a - ref_b;
        end
        else if (_OP_TYPE == 2begin
            ref_result = (ref_a + ref_b) >> 1;
        end
        else begin
            ref_result = (ref_a - ref_b) >> 1;
        end
    end
endfunction
 
initial begin
      $display($time, "ns --> [Info] Testbench Start!");
    a <= 78;
    b <= 32;
    #10
    $display($time, "ns --> [Info] a = %03d, b = %03d", a, b);
    #10
    $display($time, "ns --> [Info] result = %03d, ref_result = %03d", result, ref_result(a,b));
    #100
    $display($time, "ns --> [Info] Testbench End!");
    $finish;
end
 
endmodule
cs

 

▼ simulation 결과 화면

(1) OP_TYPE = 0 (a + b)

1
2
3
4
5
6
7
8
xcelium> run
                   0ns --> [Info] Testbench Start!
                  10ns --> [Info] a = 078, b = 032
                  20ns --> [Info] result = 110, ref_result = 110
                 120ns --> [Info] Testbench End!
Simulation complete via $finish(1) at time 120 NS + 0
./testbench.sv:41     $finish;
xcelium> exit
cs

(2) OP_TYPE = 1 (a - b)

1
2
3
4
5
6
7
8
xcelium> run
                   0ns --> [Info] Testbench Start!
                  10ns --> [Info] a = 078, b = 032
                  20ns --> [Info] result = 046, ref_result = 046
                 120ns --> [Info] Testbench End!
Simulation complete via $finish(1) at ti me 120 NS + 0
./testbench.sv:41     $finish;
xcelium> exit
cs

(3) OP_TYPE = 2 (a + b) >> 1

1
2
3
4
5
6
7
8
celium> run
                   0ns --> [Info] Testbench Start!
                  10ns --> [Info] a = 078, b = 032
                  20ns --> [Info] result = 055, ref_result = 055
                 120ns --> [Info] Testbench End!
Simulation complete via $finish(1) at ti me 120 NS + 0
./testbench.sv:41     $finish;
xcelium> exit
cs      

(4) OP_TYPE = 3 (a - b) >> 1

1
2
3
4
5
6
7
8
xcelium> run
                   0ns --> [Info] Testbench Start!
                  10ns --> [Info] a = 078, b = 032
                  20ns --> [Info] result = 023, ref_result = 023
                 120ns --> [Info] Testbench End!
Simulation complete via $finish(1) at ti me 120 NS + 0
./testbench.sv:41     $finish;
xcelium> exit
cs

 

 

 


 

 

맺음말

추가로 몇 가지 쓸만한 Tip과 지식을 나누며 글을 맺으려 한다.

 

1. 항상 이름을 넣어줄 것
2. 기존 문법인 for loop와 generate를 사용한 for loop의 차이

 

generate block에 이름을 넣지 않으면 genblk[0], genblk[1]... 등의 이름을 EDA tool이 자동으로 설정하게 되는데, 디버깅 측면에서 가독성이 좋지 않다. 항상 이름을 붙여주는 습관을 들일 것을 추천한다.

 

다음으로 verilog에서 지원하는 두 가지 형태의 for loop의 차이점에 대해서 마지막으로 짚고 넘어가자.

 

(1) 기존 for loop은 verilog compiler에게 하나의 code block을 여러 번 실행하도록 지시한다.

1
2
3
4
5
6
7
8
9
10
11
12
// normal for loop
always @(posedge clock) begin
    for(i=0;i<2;i++begin
        a[i] = b[i];
    end
end
 
// create single instance & execute it multiple times
always @(posedge clock) begin
    a[0= b[0];
    a[1= b[1];
end
cs

 

(2) generate for loop은 여러 code block을 생성하도록 지시한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// generate for loop
generate    
    for(i=0;i<2;i++begin
        always @(posedge clock) begin
            a[i] = b[i];
        end
    end
endgenerate
 
// create multiple instances
always @(posedge clock) begin
    a[0= b[0];
end
always @(posedge clock) begin
    a[1= b[1];
end
cs

 

어떠한 차이가 있는지 알고 사용하는 것은 고수의 길로 나아가기 위한 필수 기본기 중에 하나라고 생각한다. 크게 중요하지 않게 생각한 것에서 발생하는 사고는 현업을 하다 보면 흔하게 마주하는 일이다. 디테일의 힘, 제대로 알고 있는 지식은 중요한 순간에 빛을 발하는 법이다.

 

 

 

 

 

댓글
공지사항