베릴로그 문법은 아래의 문서를 따른다. 구글에 verilog standard라고 치면 첫번째로 뜨는 문서다.
www.eg.bucknell.edu/~csci320/2016-fall/wp-content/uploads/2015/08/verilog-std-1364-2005.pdf
목차별로 중요한 것들만 알아보자.
3.5 Numbers
3.5.1 Integer constants
Example 1
비트수가 정해지지 않은 상수를 어떻게 표시하느냐를 나타낸다. 8진수 10진수 16진수로 나타낼 수 있다. 비트 수를 표시하지 않고 상수로 표시하는 것은 베릴로그 설계에서 추천하는 바는 아니다.
왜냐하면 10진수의 경우 예시처럼 659라고 표시한다면 합성(synthesis) 툴에서는 32비트짜리 상수 659로 인식한다.
비트가 낭비되는 것이므로 비트수를 항상 명시하여 표시하는 것을 추천한다.
Example 2
비트수가 정해진 상수를 어떻게 표시하느냐를 나타낸다.
2진수 8진수 10진수 16진수가 있다.
4'b1001은 4비트 2진수 1001(10진수로는 9)를 의미한다.
5'd3은 5비트 10진수 3(5'b00011)를 의미한다.
{비트수}'{2/8/10/16진수 (b/o/d/h)}{숫자} 라고 이해하면 된다.
12'hx에서 x는 알 수 없는 수를 의미한다. 회로적으로는 0과 1 중 어느 것인지 파악을 못하는 것이라고 보면 된다. 거의 안쓴다. 설계한 회로에서 에러가 생길때 볼 수 있다.
16'hz에서 z는 high-impedance number를 의미한다. 전자회로에서 배우는 high impedance 맞다. 여러 변수를 한 변수에 wiring 할 경우에 쓰는 경우가 있다. 변수에 아무것도 할당이 안되면 보이는데, 이걸로 본인이 설계한 회로에서 변수에 값이 아예 입력 안된 것을 찾아낼 수도 있다.
Example 3
부호가 있는(양수/음수) 숫자를 어떻게 표시하느냐를 나타낸다.
4'shf 는 f(1111)의 2의 보수, 즉 -0001(-1)을 나타낸다. signed 변수에 할당이 되는 경우 -1를 나타내며, unsigned 변수에 할당이 될 경우 그냥 15를 나타낸다.
Example 4
자동적인 left padding을 나타내고 있다. 비트수가 정해진 변수에 해당 비트수보다 적은 비트의 숫자를 넣을 경우 남는 비트 부분을 0으로 처리하게 된다.
Example 5
언더바(_)를 사용하여 숫자를 나타내는 예시다. "_"를 쓰는 이유는 그냥 가독성 때문이다.
16'h12ab_f001이나 16'h12abf001은 똑같은 숫자다.
4.6 Net types
여러 타입이 있는데 wire을 주로 쓴다. 이때까지 설계하면서 다른 net을 쓴 적이 없다.
표 4-2에서 주목할 점은 multiple drivers가 있을 때인데 0과 z, 1과 z가 동시에 driven되면 결과는 0, 1이 된다. 앞에서 설명한 여러 번수를 한 변수에 wiring (multiple drivers)할 경우에 어떻게 값이 되는지를 나타내고 있다.
4.7 Regs
reg 변수는 값을 저장해두는 역할을 한다. 따라서 하드웨어적인 레지스터를 모델링하는데 쓰인다. 플립플롭 같은걸 나타내는데 주로 쓴다.
4.9 Arrays
배열을 어떻게 나타내느냐를 알려주고 있다. 4.9.3.1.1을 참고하면 될 것 같다.
4.9.3.1.2 처럼 접근하면 된다. 한번에 한 element만 assign 된다는 점만 알고 있으면 된다. mem의 두번째 element에 assign을 하려면 mem[1]에 assign을 하면 되고, 두번째 요소의 n번째 비트에 대해 접근하려면 mem[1][n] 처럼 접근하면 된다.
4.9.3.1.3 에서는 reg의 array 선언에 따른 차이점을 서술하고 있다.
reg [n-1:0] rega [0:2] 의 경우 n비트짜리 레지스터가 3개 있는 것이고,
reg regb [0:2] 의 경우 1비트짜리 레지스터가 3개 있는 것이다.
4.10 Parameters
파라미터는 net이나 reg에 속하는 변수가 아니다. 그냥 상수를 정의할때 쓰는데, 비트수를 유동적으로 선언하기 위해 쓰이기도 한다.
parameter paramA = 7;
reg [paramA-1:0] regA; // 7비트짜리 regA 선언
reg [paramA-1:0] regB; // 7비트짜리 regB 선언
만약 7비트짜리 reg 변수에서 16비트로 바꿔야 한다면 일반적인 선언으로는 두 줄을 수정해야 하지만, parameter를 이용하면 parameter만 수정하면 되는 장점이 있다.
5.1 Operators
연산자이다. modulus(나머지) 및 divider(나눗셈) 연산은 되도록이면 쓰지 않는 게 좋다. 두 연산의 회로적 복잡도가 꽤 높기 때문이다. 꼭 써야할 경우에는 다른 연산자들로 구현을 하는게 좋다.
Concatenation은 여러 변수를 하나의 비트열로 만들 때 사용한다.
{3'b111, 2'b10, 1'b1} // basic
{3'b111, {2{2'b10}}} // nested
{b, {3{a, b}}} // same as {b, a, b, a, b, a, b}
basic의 경우 6'b111_10_1을 나타내고, nested의 경우 7'b111_10_10을 나타낸다. wire나 reg 변수들로도 cncatenation을 구현할 수 있다.
Arithmetic~Bitwise와 Shift는 다른 언어에서도 주로 쓰는 연산자이므로 설명은 생략한다.
Reduction은 해당 변수의 모든 비트를 전부 and/or/xor 등을 취한다는 뜻이다. 아래의 표를 참고하면 쉽게 이해할 수 있다.
? : 는 조건 연산자인데 c언어에서도 쓰이긴 해서 아는 사람들도 있을 것이다.
{0 또는 1이 되는 조건} ? {조건이 1일때} : {조건이 0일때} 로 사용하면 된다.
assign out = select ? in1 : in2;
이 경우 out의 결과는 select가 1일 경우 in1이고 0일 경우 in2다.
조건문의 장점이자 단점은 조건문 내부에 nested 조건문을 만들수 있다는 것이다.
assign out = select ? (select2 ? in1 : in3) : in2;
조건문 안에 조건문 안에 조건문... 이 가능하긴 한데 가독성이 떨어진다. 두 개 이상의 조건문을 넣는건 본인이 보기에는 편하지만 다른 사람이 보기에는 매우 불편하다는 것을 명심하자.
6. Assignments
Continuous와 Procedural assignment가 있다. Continuous는 wire 같은 net에 사용되고 Procedural assignment는 reg와 같은 variable에 사용된다.
Continuous의 경우 아래와 같이 사용한다. 변수를 선언하면서 assign 해줄 수도 있고 선언 후에 따로 assign 해줄 수도 있다. net과 net을 연결하는데 쓰일 수 있고, 연산자를 이용해서 특정 연산을 거친 값을 연결해줄 수도 있다.
wire [3:0] example = 4'b1010;
assign example = 4'b1110;
Procedural assignment는 아래와 같이 사용한다. Blocking assignment가 있고 Nonblocking assignment가 있는데, 차이점은 아래의 링크를 참조하면 될 것 같다.
www.asic-world.com/tidbits/blocking.html
reg [3:0] a, b;
initial begin
a = 4'd5;
b <= 4'd10;
end
나는 주로 nonblocking assignment를 사용하는데, 보통 모듈에서 레지스터 변수(플립플롭)에 값을 assign 해줄때 쓴다. Blocking assignment는 always @ (*) 같은 곳에서 쓰이는데, always @ (*)은 거의 쓰이지 않아서 자주 쓰이는 편은 아니다. always @ (*)에 대한 이슈는 따로 글을 작성할 예정이다.
7.2 and, nand, nor, or, xor, and xnor gates
각 논리연산을 어떻게 구현하는지를 나타내고 있다.
and a1 (out, in1, in2); // {module name} {instance name} ({io ports})
다른 논리연산도 위의 예시처럼 구현할 수 있다. a1과 같은 instance name은 모듈 내부에서 겹치면 안된다는 것을 알아두자. port 순서는 output, input1, input2다.
9.4 Conditional statement
여러 프로그래밍 언어에서 많이 보던 if-else다. 2개 이상의 구문을 한 if 문에 넣기 위해 중괄호 대신 begin-end를 사용한다는게 특징이다. begin-end 및 indent를 쓰면 가독성이 좋아진다.
9.5 Case statement
case문이다. 일반적인 case는 익숙할것이라 생각하고 casez와 casex에 대해 설명한다. casez와 casex는 don't care라는 조건이 있는데 아래와 같다. 각각 x와 ?가 있는 비트의 경우 고려를 안한다는 것을 의미한다.
casex (sel)
4'b1xxx: stat1;
4'b01xx: stat2;
4'b001x: stat3;
4'b0001: stat4;
default: stat5;
endcase
casez (sel)
4'b1???: stat1;
4'b01??: stat2;
4'b001?: stat3;
4'b0001: stat4;
default: stat5;
endcase
casex와 casez의 차이점은 casex는 x, z, ?를 전부다 don't care로 보지만 casez는 z, ?만 don't care로 본다는 것이다.
case 조건에 따라 여러 구문을 넣어야 할 경우 if와 같이 begin-end를 case에 넣어주면 된다.
case (sel)
2'b00: begin
stat1;
stat2;
end
2'b11: stat3;
default: stat4;
endcase
9.6 Looping statements
루프를 만들때 쓰이는 명령어들이다. 회로 설계 시에는 잘못된 합성결과가 나올 수 있기 때문에 사용하지 않고, 테스트벤치 같은 곳에서 복잡한 데이터 i/o를 위해 사용하는 경우가 있다. forever은 while (1)과 같고, repeat은 for(i=0;i<repeat_n;i++)와 같다고 보면 된다.
9.7 Procedural timing controls
delay control과 event control이 있다. delay(#)의 경우 테스트벤치에서 활용하는 경우가 많다. 일반 회로 설계 시에는 합성 오류가 생길 가능성이 있기 때문에 사용하지 않는다. event(@)의 경우 always 문과 함께 사용되는 경우가 많다.
9.9 Structured procedures
initial과 always에 관한 부분이다. initial은 시뮬레이션 시작 시에 수행되도록 하며 always는 시뮬레이션이 진행될 동안 항상 수행되도록 하는 것이다. delay 또는 event와 함께 쓰일 수 있다. 아래와 같은 용도로 사용할 수 있다.
always #5 clk <= ~clk; // 5 timestep마다 clk을 반전시킨다.
always @ (posedge clk) // clk이 posedge일때마다 실행한다.
Task와 Function은 따로 다루겠다.
12.1 Modules
Verilog는 module과 endmodule로 하나의 회로 모듈을 정의한다. 모듈 선언은 아래와 같이 할 수 있다. 티스토리 코드블럭이 input과 output을 인식 못해서 가독성이 조금 떨어진다. VS code 같은 편집 툴에서는 정상적으로 키워드 컬러가 적용된다.
module Example1(input1, input2, output1)
input input1, input2;
output output1;
// 원하는 코드 작성
endmodule
// example 1
module Example2(
input input1, input2,
output output1
);
// 원하는 코드 작성
endmodule
// example 2
모듈과 모듈을 연결하기 위해서는(wiring) 포트를 연결해야 한다. 연결방식은 크게 두 가지로 첫번째는 선언된 포트 순서대로 연결을 하는 경우다. 두번째는 포트 이름으로 연결하는 경우다. 아래와 같이 연결된다.
module top();
wire in1, in2, in3, in4;
wire out1, out2
Example1 Ex1(in1, in2, out1);
Example2 Ex2(.input1(in3), .input2(in4), .output1(out2));
연결할 포트가 적다면 순서대로, 많아서 구별하기 힘들 경우에는 이름으로 연결하면 좋다.
Ex1과 Ex2는 각각 instance name으로, 모듈 내부에서 겹치지 않는 이름이어야 한다. 모듈1-모듈2-모듈3 이 있다고 가정할 경우 모듈2에 있는 인스턴스와 3의 인스턴스 이름은 겹쳐도 상관없다.
'Verilog HDL 설계' 카테고리의 다른 글
8bit Binary to BCD (0) | 2021.04.14 |
---|---|
코드 작성을 위한 몇가지 팁(1) (3) | 2021.04.14 |
MUX (0) | 2021.04.13 |
Clock frequency divider (0) | 2021.04.11 |
Counter (2) | 2021.04.11 |