본문 바로가기

FPGA

[FPGA] 1. Verilog 기본과 문법(4/17)

1. Verilog란?

 

Verilog는 디지털 회로를 모델링하고 설계하기 위한 하드웨어 기술 언어(Hardware Description Language) 중 하나이다. 주로 ASIC(Application-Specific Integrated Circuit)과 FPGA(Field-Programmable Gate Array) 디자인에 사용된다.

베릴로그는 시뮬레이션과 합성을 위한 강력한 도구를 제공하여, 회로 설계자들이 복잡한 디지털 회로를 쉽게 모델링하고 시뮬레이션할 수 있도록 한다. 베릴로그는 기술적으로 높은 수준에서 디지털 회로를 모델링하며, 하드웨어적인 성능을 최적화하기 위해 논리적인 요소들을 조합하는 방법을 사용한다.

 

2. 글리치(glitch)란?

 

이상적인 동기화(클럭을 이용한) 설계에서는 같은 타이밍에 각각의 값이 오르고 내린다. 하지만 회로설계를 하다보면 회로 내 전달시간에 따라 다른 input값이 늦게 도착하는 경우, noise에 의한 경우에 따라 예상치 못한 값이 발생한다. 이를 글리치(glitch)라고 한다.  

 

Verilog에서 글리치를 해결하는 방법은 다양하다. 몇 가지 대표적인 방법을 알아보자

레지스터를 사용하여 시간 지연 추가: 글리치가 발생하는 원인 중 하나는, 조건문이나 다른 논리 연산에 의해 신호가 즉시 반응하는 것이다. 이러한 경우에는 레지스터를 추가하여 시간 지연을 두면 글리치를 줄일 수 있다.

동기화 설계: 명확한 설계를 통해서 정확한 타이밍에 동기화 될 수 있도록 설계한다.

Fan Out을 줄이자: Fan Out은 베릴로그에서 출력 핀에서 구동되는 입력 핀의 개수를 나타낸다. 즉, 출력 신호가 다수의 입력 핀에 공급될 때 해당 출력 핀에서 구동되는 입력 핀의 수를 의미한다.

Fan Out이 높을수록 출력 신호를 공급하는 데 필요한 전류가 증가하므로, 논리 회로의 속도와 전력 소비를 저해할 수 있다. 또한, Fan Out이 높을수록 출력 신호의 왜곡이나 노이즈 등의 문제가 발생할 가능성이 높아진다.

 

따라서 버퍼 또는 D-플립플롭을 통해서 분산처리를 유도하여 이 FanOut을 줄일 수 있다.

 

3. LUT(Look-up Table)

 

LUT은 Look-Up Table 약자이다. 이는  디지털 논리 회로에서 주로 사용되는 기본적인 논리 게이트(AND, OR, NOT, XOR 등)의 조합을 통해서 출력을 하는것이 아닌, 메모리에  입력 신호의 조합에 따라 미리 계산된 출력값을 반환한다. 즉 쉽게 비유하자면, 레지스터로 되어있는 칩 내부에 어떠한 로직이 발생할 경우. 이를 진리표처럼 만들어 출력을 내보낸다고 생각하면 이해가 쉬울 것이다.

 

LUT를 이용하면 복잡한 논리 회로를 쉽게 구현할 수 있으며, FPGA와 같은 프로그래머블 로직 디바이스에서 논리 회로를 구현하는 데에 매우 중요한 역할을 한다 .자일링스(Xilinx)사의 특허이고, 이 방식을 통해서 시스템의 엄청난 속도향상을 이뤄냈다.

 

특히 자일링스의 LUT는 최대 6입력 1출력 LUT (Look-Up Table)을 사용한다. 만약 Input의 개수가 6개를 넘어갈 때에는 두 개의 LUT를 사용해서 구현한다는 특징이 있다.

 

4. 간단한 Verilog 구현 코드

//gate2.v

module gate2(

    input a, b,
    output y_not, y_and, y_or, y_xor, y_nand, y_nor, y_xnor
);

    assign y_not = ~a;
    assign y_and = a & b;
    assign y_or = a | b;
    assign y_xor = a ^ b;
    assign y_nand = ~(a & b);
    assign y_nor = ~(a | b);
    assign y_xnor = ~(a ^ b);
    
endmodule

위 코드는  not, and, or, xor, nand, nor, xnor 총 7가지의 게이트를 a,b를 입력으로 넣는 코드이다. Verilog에는 크게 자료 흐름적 모델링, 동작적 모델링, 게이트레벨 모델링이 있는데, 위 코드는 자료흐름적 모델링으로 구현하였다. 자료흐름적 모델링의 특징은 연속 할당문인 assign 문을 통해 표현되며, 할당 기호 =의 좌변의 신호에 우변의 결과 값을 할당한다. 나머지 모델링은 추후에 설명하겠다.

restart

add_force a -radix hex {0 0ns} {1 50ns} -repeat_every 100ns
add_force b -radix hex {0 0ns} {1 100ns} -repeat_every 200ns

run 400 ns

add_force a -radix hex 30
add_force b -radix hex 5


run 200 ns

위는 파형을 분석하기 위한 테스트벤치 코드이다. run 400 ns를 기준으로 위 코드는 repeat_every를 이용하여 반복하여 값을 변화시키는 것이고, 아래 코드는 값을 한번만 지정해주는 코드이다.

 

간략히 코드를 분석하자면, 

 

-radix hex는 Verilog 코드에서 리터럴 값을 표현하는 방식을 16진수로 설정하는 명령어이다. 여기서 리터럴 값이란 소스 코드 내에서 고정된 값으로 표현되는 것을 말한다. 예를들어 x = 5;를 선언했다고 하면 x의 리터럴 값은 5이다.

 

add_force 명령어는 Verilog 시뮬레이션 도구에서 시뮬레이션을 실행할 때 시뮬레이션 대상 모듈의 포트에 값을 강제로 지정할 때 사용된다. add_force 명령어를 사용하여 시뮬레이션 도중 clk 신호를 강제로 0과 1로 변경할 수 있다.

 

-repeat every 명령어는 Verilog 시뮬레이션 도구에서 add_force 명령어와 함께 사용되어, 일정한 주기로 신호 값을 강제로 변경할 수 있다.

 

5. 크기를 비교할 때, 변수의 비트 개수를 철저하게 확인하자.

module a(

    input  [7:0] a, b,
    output carry,
    output [7:0] y
    );
    
    assign y = a + b;
    
    //1. assign carry = (y > 255) ? 1 : 0;
    //2. assign carry = (a + b > 255) ? 1 : 0;
    
    endmodule

위 코드에서 주석 처리된 1번과 2번 중에 무엇이 정상적으로 작동할까? 정답은 2번이다. 왜냐하면 1번의 경우 이미 output y값을 선언할 때 8비트의 크기로 선언해주었다. 8비트는 Decimal 값으로 최대 255를 가질 수 있기 때문에, 무조건적으로 1번 조건식은 false가 된다. 따라서 비교를 할 때, 변수의 크기를 잘 파악하여 비교하자.

 

6. wire와 reg의 차이

 

wire은 간단하게 말해서 모듈 또는 게이트들을 서로 연결시켜주는 신호선이라고 생각하면 된다. reg는 동기방식에서 자주 쓰이는 자료형이다. 가장 헷갈리는 부분이 wirereg의 차이인데, 둘의 차이는 정말 간단하게 말해서 값을 저장한 채로 값을 읽거나 할당할 수 있는지의 차이다. wire은 신호선이므로 값을 저장하거나 읽기가 불가능하다. 즉 wire은 값만 전달해주는 기능일 뿐이다. reg는 값을 저장하고 값을 읽거나 할당할 수 있다.

 

 

6. always문 사용 방법

 

always 문은 특정 이벤트가 발생할 때마다 항상 실행되는 코드 블록이다. 일반적으로 Verilog에서는 always문을 클럭이 발생할 때 자주 사용하는 것을 알 수 있다. 코드 예시를 보자.

 

module d_ff(
    input D,
    input clk,
    output reg Q
    );

    always @(posedge clk)
    begin
        Q <= D;
    end
endmodule

위 코드는 D-플립플롭의 코드이다. 여기서 always의 전달인자 posedge는 clk이 상승할 때 마다 아래 코드를 실행한다. 또한 begin과 end는 예를 들어 C언어에서의 중괄호 역할을 한다고 생각하면 된다.

 

7. always문 내에서는 assign을 사용할 수 없다!

 

assign 문은 조합 논리 회로에서 사용된다. 여기서 조합 논리 회로는 비동기식이므로 그냥 입력을 넣자마자 입력 값이 출력 값을 결정하게 된다. 이와 달리, 순차 논리 회로는 동기식 방식이므로 클럭에 따라 시간이 지나면서 입력에 대한 변경을 처리하며, 이러한 동작은 always 블록을 사용하여 구현할 수 있다. 그렇다면 클럭을 사용하지 않고 호출되자마자 바로 값을 할당하는 명령어인 assign문을 always 블록 내부에서 사용하는 것은 말이 되지 않는다.

 

이렇게 생각했을 때, always(클럭에 의해 동작이 결정)문 내에 assign(클럭과 상관 없이 바로 레지스터에 값을 저장하는 기능)문을 사용한다는 것이 어불성설임을 알 수 있다.

 

8. 순차 논리 회로에서는 ' = ' 가 아닌 ' <= '를 사용해야 한다!

 

Verilog의 = 연산자blocking assignment, <= 연산자non-blocking assignment를 나타낸다. 순차적인 코드(클럭을 사용하는 코드)에서는 이전 단계에서 계산된 결과를 다음 단계에서 사용해야 한다. 만약 blocking assignment인 = 연산자를 사용한다면, 이전 단계에서 계산된 결과가 다음 단계로 전달되기 전에 먼저 다음 단계의 계산이 실행되어버릴 수 있다. 이러한 문제를 해결하기 위해 순차회로에서는 non-blocking assignment인 <= 연산자를 사용한다. 예시를 보자.

reg a, b, c;

always @(posedge clk) begin
    a <= b;
end

always @(posedge clk) begin
    b <= c;
    c <= a;
end

위 코드에서 보면 클럭을 사용하는 순차회로이기 때문에 첫번째 always문에서 발생한 연산이 아래 always 문의 a 결과에 영향을 주면 안된다. 즉 위 always의 변경되기 전에 저장된 a가 a <= b;에 의해 변경되지 않고, 전 값 그대로 아래 always문의 c <= a; 연산을 수행해야 한다는 것이다. 따라서 이 연산의 방해를 막기 위해 <= 연산자를 사용하는 것이다. 또한 이는 reg 자료형의 중요성을 말해주기도 한다. 순차 회로에서 reg를 쓰지 않는다면 값의 변화가 일어나기에 꼭 전 연산의 값을 보호해주는 reg를 써주어야 한다.

.