| 하드웨어 기술 언어(HDL) | |
| Verilog | VHDL |
1. 개요
베릴로그(Verilog)는 ASIC이나 FPGA 등을 포함한 디지털 회로 및 시스템의 설계, 검증 그리고 구현에 쓰이는 하드웨어 기술 언어(HDL; Hardware Description Language)다. Verilog HDL라고도 부르지만, 이 경우 VHDL과 헷갈리기 때문에 짧게 Verilog라고만 부른다. [1]과거에는 VHDL이 많이 사용되었으나, 업계에서 Verilog를 주로 사용함에 따라 2020년대 중반 기준 많은 학교가 강의에 사용하는 언어를 VHDL에서 Verilog로 전환하였다.
통계적으로는 유럽을 제외하면 Verilog의 점유율이 VHDL을 압도하였다.
2. 특징
Ada 기반인 VHDL과 달리 C언어와 비슷한 문법을 가진 것이 특징이다. 'if'나 'for', 'while' 같은 제어 구조나 연산자 등이 거의 유사하여, C언어에 익숙한 사용자들이 쉽게 접근할 수 있다. [2]하지만 베릴로그가 C언어와 문법이 비슷하다고 C언어 프로그래밍 하듯이 베릴로그 코드를 작성하면 엉뚱한 코드가 나오게 되며 실제 하드웨어 상에서는 동작을 안할 가능성이 있다. 베릴로그는 실제적인 하드웨어를 기술하고 만드는 것에 목적을 둔 언어이기에, 현실의 하드웨어로는 도저히 구현이 어려운 구문이라면 논리적으로는 문제가 없더라도 합성이 되지 않는다.
테스트벤치와 시뮬레이션은 잘 돌아가도 합성이 안 되는 경우도 있다. 시뮬레이션은 작성한 코드를 합성한 뒤 하드웨어로 구현한 뒤 수행되는 것이 아니라, 소프트웨어 위에서만 해석하여 수행되는 것이기 때문이다. 따라서 어떤 문법이 합성이 가능하고 어떤 문법이 테스트벤치 및 시뮬레이션에서만 사용되는지 숙지해야 한다.[3]
예를 들어서, for 문 같은 반복문은 하드웨어적인 구현이 힘들거나 합성 툴에 따라서 완전히 다른 결과물이 나올수도 있으며, 비트 수가 큰 가산기 등 조합회로가 과다하게 쓰이는 회로인 경우에는 FPGA 내부의 자원(논리게이트, 메모리 등)을 너무 많이 차지해버려서 하드웨어적으로 구현하는 것이 비효율적일 수 있다.
HDL에 입문하는 사람들은 하드웨어에 대한 충분한 지식을 갖고, 작성한 베릴로그 코드가 어떻게 하드웨어로 합성될지에 대한 감각을 키워나가야 한다.
3. 역사
1983년 Gateway Design Automation사에서 근무하던 Phil Moorby가 Verilog-XL이라는 논리 시뮬레이터와 함께 모의시험용 언어로 개발한 것이 시초이다. 이후 회사가 Cadence Design Systems[4]에 인수합병되어 업계표준 HDL로 자리를 잡았다.이후 1995년 IEEE 1364-1995 표준이 만들어졌고, 2001년 기존 단점을 보완하고 개선하여 IEEE 1364-2001 표준(Verilog-2001)으로 개정되었다.
Verilog-2001은 당시 벤더사들이 비표준으로 지원하던 확장 기능들을 통합하고 VHDL의 강점을 수용하며 대규모로 개편된 표준이다. ANSI C 스타일의 포트 선언이나
@(*) sensitivity list, 반복 구조 기술을 위한 generate 문이 이때 도입되었으며, signed 연산 및 다차원 배열 지원 등을 통해 설계 편의성을 크게 개선했다.3.1. SystemVerilog
디지털 시스템 설계에서 검증의 중요성이 증가함에 따라 이를 지원하기 위해 Verilog의 확장 형태인 SystemVerilog가 IEEE 1800-2005 표준으로 제정되었다.2020년대 이전까지는 SystemVerilog는 검증용 테스트벤치 작성시 외엔 실제 업계에서는 잘 쓰이지 않았다. C에서 쓰이는 enum, class, struct, union등의 data type은 실제 하드웨어 합성시 결과물로 나오게 될 netlist[5]를 직관적으로 예측하기 어렵게 하고 툴 간에 문법 해석 차이가 있는 경우 netlist 기반의 시뮬레이션을 토대로 RTL[6] 디버깅을 하기 상당히 까다로워졌기 때문. 물론 베릴로그의 wire, reg data type도 소스코드에 선언된 그대로 wire와 register로 합성되는 것은 아니지만 SystemVerilog의 추상화에 비하면 사실상 하드웨어에 매우 가까운 편이라 소스코드에 존재하는 버그를 찾더라도 netlist를 직접 고치는 것이 가능할 정도였다. 또한 SystemVerilog는 툴체인에서 지원을 하지 않는 경우가 있기 때문에 더더욱 사용도가 떨어진 점도 있었다.[7]
2020년대에 돌입하면서 공정이 고도화됨에 따라 front-end 디자인을 real 값과 유사하게 구현하기 위한 방법으로 SystemVerilog format이 채택되었고, 이를 보다 고도화 한 UVM 등의 방법론과 프레임워크도 지속적으로 개발되고 있다. UVM Wikipedia OVM Wikipedia IEEE UVM Reference Manual
4. 문법
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[Verilog/문법#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[Verilog/문법#|]] 부분을 참고하십시오.4.1. 코드 구조
대다수 프로그래밍 언어에서처럼 코드의 기본 단위가 함수(function)인 것과 달리 베릴로그의 기본 단위는 모듈(module)이다. 모듈이 시작하는 위치에 module이라는 키워드를, 모듈이 끝나는 위치에 endmodule 키워드를 작성한다.모듈은 크게 모듈 선언부, 신호 선언부, 동작 선언부 3가지로 구성된다.
- 모듈 선언부: 모듈의 이름, input 및 output 포트 등을 선언함
- 신호 선언부: 모듈 내부에서 사용될 자료형인 wire, reg 등을 선언함
- 동작 선언부: always, assign 등을 사용하여 모듈의 기능, 동작, 구조 등을 선언함
/* 모듈 선언부 */
// 모듈의 이름, 입출력 포트, parameter 등을 선언함 //
module my_and_module (a, b, c);
/* 신호 선언부 */
// wire, reg, parameter 등 선언함
input wire a, b;
output wire c;
/* 동작 선언부 */
// assign, always, initial, 하위 모듈 인스턴스 등 선언함
assign c = a & b;
endmodule4.2. 병렬 동작
프로그래밍 언어와 구분되는 Verilog의 특징은 코드 라인들이 순서대로 실행되는 것이 아니라, 조건이 맞으면 동시에 실행된다는 점이다.assign c = a & b; // (1)
assign e = d | f; // (2)위 코드에서 (1)번 줄과 (2)번 줄은 위아래 순서와 상관없이, 입력 신호(a, b, d, f)가 변하는 즉시 동시에 결과가 업데이트된다. 이는 실제 회로 기판에서 칩들이 동시에 전기를 먹고 동작하는 것과 같다.
4.3. 자료형 및 논리값
베릴로그는 일반적인 프로그래밍 언어의 '변수' 개념 외에도 실제 회로의 전선을 모델링한 'wire'라는 독특한 자료형을 가진다. 또한 0과 1의 이진수로 데이터를 컴퓨터와는 달리 실제 전기 회로에서 발생할 수 있는 물리적 상태를 반영하여 총 4가지 논리값을 사용한다.베릴로그의 논리값에는 0과 1 외에 회로가 끊어져 전기가 통하지 않는 상태인 z(high impedance)와, 0과 1 신호가 충돌했거나 초기화되지 않아 값을 알 수 없는 x(unknown) 상태가 존재한다. 특히 x값은 시뮬레이션 과정에서 의도치 않은 레이스 컨디션(race condition)이나 타이밍 에러를 찾아내는 데 중요한 지표가 된다.
자료형은 크게 wire와 reg로 구분된다.
- wire: 하드웨어적인 연결을 의미한다. 값을 스스로 저장하지 못하며,
assign문을 통해 모듈이나 논리 게이트를 연결할 때 사용한다. - reg: 값을 저장하는 변수와 유사하다.
always블록 내부에서 값을 대입할 때 사용된다. 이름은 레지스터(register)지만, 코드를 어떻게 작성하느냐에 따라 실제 하드웨어에서는 값을 저장하는 플립플롭 또는 래치(latch)가 될 수도 있고, 단순히 조합 회로로 합성될 수도 있다.
구체적으로 always 블록의 sensitivity list 및 사용된 조건문 등에 따라 실제 합성 시 특정한 reg 변수에 대해 flip flop이 생성되거나, latch가 생성되거나, 어떠한 메모리 요소도 생성되지 않고 조합회로만 만들어질 수 있으므로 주의가 필요하다.
wire와 reg 자료형은 대괄호 기호를 사용하여 범위 지정을 하면 다중 bit의 벡터 또는 배열을 선언할 수 있다.
wire w1; // 1 bit wire
reg reg_a, reg_b; // 1 bit reg
reg [7:0] reg_c; // 8 bit reg (벡터)
wire [15:0] d_in; // 16 bit wire (벡터)
reg [7:0] reg_d [0:255]; // 8*256 bit reg (2차원 배열)
4.4. 합성 가능성 (Synthesizability)
베릴로그 문법이 문법적으로 맞다고 해서 모두 실제 칩으로 만들 수 있는 것은 아니다. 베릴로그는 시뮬레이션용 언어에서 출발했기 때문에, 하드웨어로 구현 불가능한 문법도 다수 포함하고 있다.예를 들어
#10 (10단위 시간 지연) 같은 구문은 시뮬레이션에서는 동작하지만, 실제 회로에서는 "정확히 10ns 뒤에 신호를 보내라"는 물리적 구현이 불가능하므로 합성 툴이 무시하거나 에러를 뱉는다. 따라서 하드웨어 설계자는 '합성 가능한(synthesizable) 코드'와 '시뮬레이션용 코드'를 명확히 구분해서 작성해야 한다.5. 예제
5.1. 조합 회로(combinational circuit)
인코더와 같이 조합 회로를 설계할 때는 always 문을 사용하는 방법과 assign 문을 사용하는 방법 2가지가 있다. 여기서 always 문에서 값을 할당할 때는 reg 자료형을, assign 문에서 값을 할당할 때는 wire 자료형을 사용하는 것에 유의해야 한다.5.1.1. Behavioral modeling (always 문 사용)
always 문 스타일로 4:2 인코더를 모델링한 코드이다.always 문으로 조합회로를 기술할 때는 sensitivity list에 *(모든 신호)를 사용한다. *를 사용하지 않을 시, 합성 전 시뮬레이션 결과와 합성 후의 결과가 다를 수 있기 때문이다.
래치(latch) 생성을 피하기 위해서, 조건문에서 else를 사용한다. 모든 경우에 대해서 조건문을 작성하지 않으면 래치가 생성된다. 래치는 조합 회로가 아닐뿐더러 글리치에 취약하고, 정적 타이밍 분석(STA; Static Timing Analysis)에도 불리하게 작용되기 때문이다.
module encoder (
input wire [3:0] x,
output reg [1:0] y // output y의 자료형이 wire가 아니라 reg임에 유의
);
always @(*) begin
if (x == 4'b0001) y = 2'b00; // non-blocking 할당문(<=)이 아닌, blocking 할당문(=)을 사용함에 유의
else if(x == 4'b0010) y = 2'b01;
else if(x == 4'b0100) y = 2'b10;
else if(x == 4'b1000) y = 2'b11;
else y = 2'bxx; // latch 생성을 피하기 위해서 대신 else 사용함
end
endmodule
5.1.2. Dataflow modeling (assign 문 사용)
assign 문 스타일로 4:2 인코더를 모델링한 코드이다.module encoder (
input wire [3:0] x,
output wire [1:0] y // output y의 자료형이 reg가 아니라 wire임에 유의
);
assign y[0] = (!x[3] && !x[2] && x[1] && !x[0]) || (x[3] && !x[2] && !x[1] && !x[0]);
assign y[1] = (!x[3] && x[2] && !x[1] && !x[0]) || (x[3] && !x[2] && !x[1] && !x[0]);
endmodule
5.2. 순차 회로(sequential logic)
5.2.1. D 플립플롭
클럭의 상승에지에 동작하는 D 플립플롭을 모델링한 코드이다. 순차 회로를 구현할 때는 non-blockinig 할당문인<=를 사용해야 한다. [8]module d_flip_flop (
input wire clk,
input wire d,
output reg q,
output reg q_bar
);
always @(posedge clk) begin
q <= d;
q_bar <= ~d;
end
endmodule
5.2.2. 동기식 다운 카운터
8 bit 동기식 다운 카운터를 모델링한 코드이다. 입력 신호 load가 1이면 카운트 값 cnt는 8'b1111_1111(십진수로 255)로 값이 할당되며, load가 0이면 카운트 값이 1씩 작아지도록 설계되었다. 그리고 카운트 값 cnt는 출력 port인 Q와 연결되어 모듈 바깥으로 값이 출력된다.module down_counter #(
parameter WIDTH = 8
)
(
input wire clk,
input wire load,
output wire [WIDTH-1:0] Q
);
reg [WIDTH-1:0] cnt;
always @(posedge clk) begin
if(load) cnt <= {WIDTH{1’b1}}; // 8'b1111_1111
else cnt <= cnt - 1’b1;
end
assign Q = cnt;
endmodule
5.3. Testbench
테스트벤치(Testbench)는 작성한 설계 모듈(DUT; Design Under Test)이 의도대로 동작하는지 검증하기 위한 시뮬레이션 코드다. 실제 FPGA/ASIC 핀에 신호를 넣는 것이 아니라, 소프트웨어 상에서 가상의 입력 신호를 생성하여 DUT에 주입하고 출력을 관찰한다.테스트벤치 파일은 합성을 목적으로 하지 않으므로 `initial` 구문, `#` 지연(delay) 연산자, 시스템 태스크(`$display` 등)를 자유롭게 사용할 수 있다.
일반적으로 다음과 같은 구조를 가진다.
- 시간 단위 설정:
`timescale지시어를 사용. - 입출력 선언: DUT의 입력은
reg로, 출력은wire로 선언한다. (입력은 값을 바꿔줘야 하므로 변수 속성인 reg, 출력은 모듈에서 나오는 값을 받아야 하므로 연결선 속성인 wire를 쓴다.) - DUT 인스턴스화: 설계한 모듈을 불러와서 테스트벤치의 신호와 연결한다.
- 클럭 생성:
always구문을 이용해 주기적인 클럭 신호를 만든다. - 입력 자극(Stimulus) 인가:
initial블록 안에서 시간 흐름에 따라 입력값을 변경한다.
`timescale 1ns / 1ps // 단위 시간 1ns, 정밀도 1ps
module tb_down_counter;
// 1. 신호 선언
reg clk;
reg load;
wire [7:0] Q;
// 2. DUT(Design Under Test) 인스턴스화
// 설계한 down_counter 모듈을 불러옴
down_counter u_dut (
.clk (clk),
.load (load),
.Q (Q)
);
// 3. 클럭 생성 (주기 10ns -> 100MHz)
always #5 clk = ~clk;
// 4. 입력 자극(Stimulus)
initial begin
// 초기화
clk = 0;
load = 0;
// 시뮬레이션 시작 후 20ns 대기 후 load 신호 인가
#20;
load = 1;
// 10ns 후 load 해제 (카운트 시작)
#10;
load = 0;
// 200ns 동안 동작 확인
#200;
// 시뮬레이션 종료
$finish;
end
// 5. 모니터링 (선택 사항)
initial begin
$monitor("Time: %t, Load: %b, Q: %d", $time, load, Q);
end
endmodule6. 자주 사용되는 용어
- 시뮬레이션: 디자인을 실제 하드웨어를 제작하지 않고, 소프트웨어 환경에서 설계된 하드웨어의 동작을 가상으로 구현하고 분석하는 과정.
- 테스트벤치(testbench): 디자인의 기능 및 성능을 검증하기 위해 사용되는 시뮬레이션 환경. 테스트벤치에서 입력 신호를 생성하고, 출력 결과를 관찰하거나 비교하여 동작을 확인함. 디자인과 마찬가지 테스트벤치 또한 HDL로 작성함.
- DUT(Design Under Test): 테스트 대상이 되는 디자인.
- 합성(synthesis): HDL로 작성된 코드를 실제 논리 게이트 수준(AND, OR, NOT 등)의 회로로 변환하는 과정. 타겟 하드웨어(FPGA나 ASIC)에 적합한 라이브러리 셀로 매핑하여, 최종적으로 게이트 수준으로 기술된 회로[9]가 생성됨.
- 배치 및 배선(place and route): 합성된 회로를 물리적 레이아웃에 매핑하고 연결하는 과정. Floorplan이라고도 불린다.
- STA(Static Timing Analysis): 회로에 가상의 입력 신호를 줘서 내부 및 출력 신호의 타이밍이나 기능을 확인하는 시뮬레이션과는 다르게, STA는 입력 신호를 주지 않고서 타이밍을 분석하는 방법이다.
7. 여담
- 프로그래밍 언어에 'Hello World' 출력이 있다면, 하드웨어 언어에는 'LED Blinking'(LED 깜빡이기)이 있다. FPGA 보드에 코드를 올렸을 때 LED가 1초마다 깜빡거리면 입문에 성공한 것이다.
8. 관련 책과 자료
- Verilog, Formal Verification and Verilator Beginner's Tutorial - Daniel E. Gisselquist
- Verilog Quick Reference Guide - Sutherland HDL (PDF)
- Verilog Tutorial - Deepak Kumar Tala
- Verilog Tutorials and Examples - Russell Merrick(nandland)
- Verilog Projects - Van Loi Le(FPGA4student)
- Verilog Tutorial (ChipVerify)
- HDLBits - Henry Wong
- Iverilog (온라인 시뮬레이터)
9. 관련 문서
[1] 여담으로 Verilog는 verfication과 logic의 합성어다.[2] 다만, case문의 형태와 블록의 시작과 끝을 중괄호 대신 begin과 end를 사용하는 것은 파스칼(Pascal)언어와 유사하다.[3] 예를 들어서, initial 블록이라던지 지연(#; delay) 같은 문법은 원래 시뮬레이션에서 쓰라고 만든 기능이기 때문에 합성이 되지 않는다. 시뮬레이션에서만 사용되고 합성이 되지 않는 코드를 주의해서 사용해야 한다.[4] OrCad와 Allegro 등 각종 전자 설계 EDA를 개발하는 회사다.[5] 쉽게 말해서 gate-level[6] 물론 이 경우 SystemVerilog 소스코드에 해당[7] 칩을 제대로 하나 내보내려면, 단순 설계만 아니라 각종 합성부터 해서 backend작업까지 Pass를 시켜야 되는데, 이 backend 작업들 중에서 SystemVerilog를 지원하지 않거나 추가 라이센스비용을 내게하는 경우가 많다.[8] blocking 할당문인
=을 사용해도 합성되지만, 시뮬레이션 시 레이스 컨디션(race condition) 문제를 방지하기 위해 <= 사용을 강력하게 권장한다.[9] 주로 netlist라고 부름