# 들어가며
Verilog 독학을 하려고 한다. 본인은 Computer Software 전공이지만 베릴로그 공부를 하려고 한다. 나중에 어떻게 될지는 모르지만 공부하려고 한다. 정말 노베이스로 시작하는거라서 아마 이 글을 보는 사람들이 나와 같은 상황이라면 이해 하기 쉬울 것이라고 생각한다.
# Verilog는 무엇인가
베릴로그는 전자 회로 및 시스템에 사용되는 하드웨어 기술 언어(HDL)이다. 디지털 회로 설계(RTL), 검증 구현 등 여러 용도로 사용 된다. 디지털 회로 설계에 최적화 되어 있기에 그 용도가 매우 명확하다. 혹자는 프로그래밍 언어라기 보다는 마크업 언어와 더욱 유사하다고 말하기도 한다. 하지만 그만큼 하드웨어에 대한 이해가 필요하다는 말이기도 하다.
Verilog를 잘 이해하기 위해서는 다음과 같은 선수 지식들이 필요하다고 한다.
- 디지털 논리 회로
- 회로 이론
- 프로그래밍 이론
하지만 나는 CS 전공으로서 저 중에서 초등학생 수준의 1 지식과 평범한 3 지식을 갖고 있다. 그래도 Verilog를 공부한다. 하고싶으니까.
베릴로그는 다음 그림과 같이 전자 회로가 있을 때 이를 제어하는 언어이다. 즉, 베릴로그는 디지털 논리 회로를 코딩하는 언어이다.
# 문법
사실 문법은 굉장히 간단하다. C언어와 약간의 영어를 할줄 안다면 너무너무 쉬울 것이라고 생각한다. 하지만 이 모든것들을 회로 위에서 돌린다고 생각하면 느낌이 다르다. C언어와 같은 Programming Language는 Program을 돌리기 위한 것이지만 Verilog는 전자회로를 돌리기 위한 것이니 말이다.
module Module_Name (A, B, C);
input A, B;
output C;
wire A, B;
reg C;
always@ (A or B)
if (A)
C <= 1'b1;
else if (B)
C <= 1'b0;
endmodule
## Module
Verilog 설계를 위한 기본적인 코드 블록의 단위이다. 코드 블록이라.. C언어 코드 블록의 단위는 무엇인가? {} 일 것이다. Verilog의 Module은 function에 더 가깝다. 객체지향 언어의 class와 유사하다고 생각하면 된다! 문법은 다음과 같다.
module로 시작해서 endmodule로 끝난다.
module <모듈이름> (포트 목록);
// 코드 내용
endmodule
## Port
포트는 모듈과 모듈을 연결할 수 있는 인터페이스이다. 엥? 그게 무슨소리지.. 모듈과 모듈을 연결한다라 그림을 한번 보자. 음 이렇게 보면 Port라는건 뭔가를 연결하는 느낌의 포트인것 같다. 잘 모르겠다. 감이 안온다! 나는 실물 하드웨어를 본적이 없으니까 말이다.
코드를 살펴보자. A, B, C는 각각이 Port이다. 아하! 함수의 매개변수랑 똑같은 말이구나! 하지만 약간의 차이가 있어보인다.
A, B, C가 매개변수로 들어온 것은 서로 다른 모듈이 포트로 연결되는 방식일 것이다. 그말은 즉, 다른 모듈에서 아래의 모듈이 호출되는 상황에서 A, B, C 자리의 인수로 원하는 값을 넣어줄 것이라고 예상할 수 있다.
module Module_Name (A, B, C);
// Port Declaration
input A, B;
output C;
// Data type Declaration
wire A, B;
reg C;
always@ (A or B)
if (A)
C <= 1'b1;
else if (B)
C <= 1'b0;
endmodule
포트 중에서 Input과 Output이 선언되어 있는것을 볼 수 있다. 이말은 즉, input 포트와 output 포트가 나뉘는 것이다. 위의 그림을 참고하면.. 이렇게 그릴 수 있을 것 같다. 오호! 한층 이해가 쉬워졌다. 포트는 그냥 전선이라고 생각하면 될것 같다.
wire A, B와 reg C는 무엇일까? 기본적으로 모든 포트는 wire로 선언되지만 output 포트의 경우에 reg로 선언된다고 한다. 우리는 CS전공이므로 reg가 무엇인지 알고 있다. 바로 Register이다. 아마 reg에는 값을 저장하는 능력이 있고, wire는 단순히 값을 전달하는 전선이라는 느낌이 든다. 이때, input 포트는 reg로 선언될 수 없다. 또한, 당연하게도 서로 연결되는 포트끼리는 비트 수가 같아야 한다.
wire는 assign문을 이용해서 값을 인가할 수 있다. 이런식으로!
assign a = 1;
wire는 bit bandwidth(변수에 담기는 값의 비트 최대 폭이라고 생각하면 됨)를 정의할 수도 있고, wire를 wire로 재정의 할 수도 있다. 8비트 wire는 python의 리스트와 용법이 비슷하게 생겼다. wire[7]에는 0,1이 담길 수 있고, wire[6], wire[5]도 마찬가지 일 것이다. 이렇게 8개가 나란히 모여서 8비트를 구성하게 되는 것이다.
wire c_out; // 1비트 wire
wire [7:0] data; // 8비트 wire
wire msb = data[7]; //data[7]를 msb로 재정의
reg도 마찬가지이다. 똑같다!
reg sum; // 1비트 레지스터
reg [7:0] bus; // 8비트 레지스터
정리하자면, Port는 일종의 변수로 생각할 수 있다. 하지만 코드에서나 그렇지 실제로 모듈 단위에서는 입출력 창구이고, input과 output이 나눠져 있다. 모듈이라는 함수(클래스) 안에서 포트를 통해서 입력을 받아서 출력을 만들고 저장 한 다음에 포트를 통해서 다른 모듈에 전달한다.
## 논리 표현
논리 값은 0, 1, x, z로 나타낸다. 0, 1은 이진수 0, 1이다. x, z는 각각 알수 없는 값 x, 값이 아직 인가되지 않은 상태의 z를 의미한다. 음 무슨말인지 모르겠다.
수치 표현은 다음과 같이 규칙이 정해져있다. 우리는 컴공이므로 다음 문장들을 해석하는데 문제가 없을 것이다!
앞의 숫자는 8비트로 비트폭을 나타낸다. b는 2진수, o는 8진수, d는 10진수, h는 16진수로 기수를 나타내며 뒤의 숫자는 실제 수치이다.
data = 8’b0011_0101; // 53 (2진수)
data = 8’d53; // 53 (10진수)
data = 8’h35; // 53 (16진수)
## 연산자
연산자는 C언어나 Python과 거의 비슷하다. Programming 언어를 배워본 사람이라면 아주 익숙한 녀석들이다. 저런건 필요할 때마다 찾아서 쓰다보면 익숙 해진다. 굳이 전부 다 외우려고 하지 말아보도록 하자.
## 조건문
Branch는 하드웨어 수준에서부터 구현되어 있는 기능이기에 큰 차이가 없다. If, else if, else 전부 다 있다. 특이한 점은, 중괄호를 쓰는게 아니라 특이하게 begin-end로 scope를 표현한다.
if(condition 1) begin
code to execute 1;
code to execute 2;
end
else if(condition 2) begin
code to execute 3;
code to execute 4;
end
else begin
code to execute 5;
code to execute 6;
end
Case도 쓸수 있다. switch-case는 아니고 문법은 조금 다르다. 근데 코딩하면서 보통 case는 잘 안쓰지 않는가? 베릴로그도 마찬가지 일것 같다.
## Combinational Logic & Sequential Logic
Combinational Logic은 언제나 같은 입력을 넣으면 같은 출력이 나오는 회로이다. 무슨말일까?
a에 3, b에 5를 넣으면 out은 8이다. 3초뒤에 a에 3, b에 5를 넣어도 out은 8이다. 이것이 조합 회로이다.
assign out = a + b;
Sequential Logic은 입력이 아니라 현재 회로의 상태에 따라서 값이 다르게 나올 수도 있다. 이게 무슨말이냐? 메모리 소자를 가지고 있기 때문에 과거나 현재의 상태를 기억하고 있다는 말이다. 메모리 소자는 Latch 혹은 FlipFlop이라는 녀석을 사용한다고 한다.
## 하위 모듈 호출
어떤 모듈에서 다른 모듈을 호출 할 때 객체지향 언어의 인스턴스처럼 호출해서 생성할 수 있다. 이때, 두가지 방식이 있는데 이 또한 python과 똑같으므로 잘 살펴보도록 하자.
다음 코드를 보자. 이름에 의한 포트 연결과 위치에 의한 포트 연결이 있다. 그냥 python에서 인수를 매개변수 자리에 맞춰서 넣느냐, 이름으로 불러서 지정해주느냐랑 똑같은 말이다.
module adder(x, y, c_out, c_int, sum);
// ...
endmodule
// 위치에 의한 포트 연결
adder adder1(inp1, inp2, carry_out, carry_in, sum_out);
// 이름에 의한 포트 연결
adder adder2(.sum(sum_out), .c_out(carry_out), .c_in(carry_in), .x(inp1), .y(inp2));
다음 시간에는 Testbench에 대해서 알아보도록 하겠다.
References
UNIST Verilog Tutorial