티스토리 뷰

엔지니어라면, 누구나 한 번쯤 해봤을 법한 생각이 있다.
입력 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 문 안에서 중복돼서 사용하는 것은 문제없다.

샘플 코드로 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 == 0) begin
assign result = a + b;
end
else if (OP_TYPE == 1) begin
assign result = a - b;
end
else if (OP_TYPE == 2) begin
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 == 0) begin
ref_result = ref_a + ref_b;
end
else if (_OP_TYPE == 1) begin
ref_result = ref_a - ref_b;
end
else if (_OP_TYPE == 2) begin
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 |
어떠한 차이가 있는지 알고 사용하는 것은 고수의 길로 나아가기 위한 필수 기본기 중에 하나라고 생각한다. 크게 중요하지 않게 생각한 것에서 발생하는 사고는 현업을 하다 보면 흔하게 마주하는 일이다. 디테일의 힘, 제대로 알고 있는 지식은 중요한 순간에 빛을 발하는 법이다.
'Verilog & SystemVerilog' 카테고리의 다른 글
[Verilog 문법] 검증의 기본 Assertion 살펴보기 (7) | 2021.02.07 |
---|---|
[SystemVerilog 문법] randomization에 대하여 (1) | 2020.10.02 |
[Verilog 문법] 침묵의 암살자, X(unknown value)-propagation (6) | 2019.12.25 |
[Verilog 문법] wire, reg 차이에 대하여 (9) | 2019.04.17 |
[Verilog 문법] Blocking & Non Blocking에 대해 알아보자 (7) | 2019.04.16 |