[Verilog] Reusable RTL을 위한 Parameter와 Generate 정복하기

RTL 설계를 하다 보면 비슷한 기능을 하는데 비트 폭(Bit Width)만 다르거나, 파이프라인 단수(Depth)만 다른 모듈이 필요할 때가 많습니다.

이때 가장 나쁜 습관은 기존 코드를 복사해서 파일 이름만 바꾸고 숫자만 고치는 것(Hard-coding)입니다. 이런 방식은 프로젝트가 커질수록 유지보수를 지옥으로 만듭니다. 하나를 수정하면 복사한 10개의 파일을 다 찾아가며 수정해야 하기 때문입니다.

이번 글에서는 “코드를 짜는 코드”라고 불리는 Verilog의 강력한 기능, parametergenerate 블록을 활용하여 재사용 가능한(Reusable) IP를 설계하는 방법을 알아봅니다.

1. 상수의 종류: parameter vs `define vs localparam

변하지 않는 값(상수)을 정의하는 방법은 여러 가지가 있지만, 재사용성을 위해서는 용도(Scope)를 명확히 구분해야 합니다.

`define (Global Macro)

  • 특징: C언어의 #define과 같습니다. 컴파일 시점의 전처리(Preprocessor)입니다.
  • 단점: 전역(Global)으로 적용됩니다. project_A에서 정의한 이름이 project_B와 겹치면 충돌이 발생합니다.
  • 용도: 칩 전체에서 공유하는 상수(예: 칩 ID, 글로벌 주소맵)에만 제한적으로 써야 합니다. 모듈 내부 설정용으로는 절대 금지입니다.

parameter (Interface Constant)

  • 특징: 모듈을 인스턴싱(Instantiation) 할 때 외부에서 값을 바꿀 수 있습니다.
  • 용도: 재사용성의 핵심입니다. 비트 폭, FIFO 깊이 등 모듈의 ‘성격’을 외부에서 결정할 때 사용합니다.

localparam (Internal Constant)

  • 특징: 모듈 내부에서만 사용되며, 외부에서 변경할 수 없습니다.
  • 용도: parameter를 기반으로 계산된 2차 상수(예: State Machine의 상태 값)를 정의할 때 사용합니다.
// 좋은 예: 파라미터를 통한 유연한 설계
module generic_fifo #(
    parameter DATA_WIDTH = 32,          // 외부 설정 가능
    parameter FIFO_DEPTH = 1024,        // 외부 설정 가능
    // localparam은 parameter에 의해 자동 계산됨 (외부 수정 불가)
    localparam ADDR_WIDTH = $clog2(FIFO_DEPTH) 
) (
    input wire [DATA_WIDTH-1:0] wdata,
    ...
);

2. RTL Generate 문: 하드웨어를 ‘생성’하다

generate 블록은 Elaboration Time (합성 전 단계)에 코드를 동적으로 생성하는 기능입니다. 소프트웨어의 iffor와 달리, 실행 중에 도는 것이 아니라 회로를 복사하거나 지우는 역할을 합니다.

generate for: 반복 구조 생성

똑같은 회로를 N개 복사할 때 사용합니다. 손으로 100줄 짤 코드를 3줄로 줄여줍니다.

generate if / case: 조건부 회로 생성

파라미터 값에 따라 특정 회로를 아예 넣거나 빼버릴 수 있습니다. 불필요한 로직을 합성 단계에서 제거하여 면적(Area)을 최적화합니다.

3. 실전 패턴 ①: N-Stage Pipeline (Generate For)

데이터가 멀리 이동해야 해서 파이프라인 레지스터를 3개 넣어야 할 때가 있고, 5개 넣어야 할 때가 있습니다. 이를 parameter로 조절하는 코드입니다.

module delay_line #(
    parameter WIDTH = 8,
    parameter DEPTH = 3  // 파이프라인 단수
) (
    input  wire clk,
    input  wire [WIDTH-1:0] d_in,
    output wire [WIDTH-1:0] d_out
);

    // 2차원 배열로 레지스터 선언
    reg [WIDTH-1:0] pipeline [0:DEPTH-1];

    genvar i; // generate용 루프 변수 선언
    generate
        for (i = 0; i < DEPTH; i = i + 1) begin : gen_pipe
            always @(posedge clk) begin
                if (i == 0) 
                    pipeline[i] <= d_in;      // 첫 번째 스테이지
                else 
                    pipeline[i] <= pipeline[i-1]; // 나머지 스테이지
            end
        end
    endgenerate

    assign d_out = pipeline[DEPTH-1];

endmodule
  • 해석: DEPTH를 5로 설정하면, 합성 툴은 gen_pipe[0] ~ gen_pipe[4]라는 이름으로 5개의 레지스터 블록을 자동으로 풀어헤쳐서(Unroll) 구현합니다.

4. 실전 패턴 ②: Feature Toggle (Generate If)

IP를 설계할 때, 어떤 고객사는 CRC 체크 기능을 원하고, 어떤 고객사는 면적이 작아야 한다며 빼달라고 합니다. 코드를 두 개 만들 필요 없이 generate if로 해결합니다.

module uart_tx #(
    parameter ENABLE_CRC = 1 // 1: CRC 포함, 0: CRC 제거
) (
    input wire clk,
    input wire [7:0] tx_data,
    output wire tx_out
);

    wire [7:0] final_data;

    generate
        if (ENABLE_CRC == 1) begin : gen_crc_logic
            // CRC 모듈 인스턴싱
            wire [7:0] crc_val;
            crc_calc u_crc ( .data(tx_data), .crc(crc_val) );
            
            // 데이터 뒤에 CRC를 붙임 (예시)
            assign final_data = tx_data ^ crc_val; 
        end else begin : gen_no_crc
            // CRC 로직은 아예 합성되지 않음 (면적 0)
            assign final_data = tx_data;
        end
    endgenerate

    // ... UART Transmit Logic using final_data ...

endmodule
  • 핵심: ENABLE_CRC가 0이면, u_crc 모듈은 아예 넷리스트(Netlist)에서 사라집니다. 게이트 카운트(Gate Count)를 효율적으로 관리할 수 있습니다.

5. 꿀팁: $clog2 시스템 함수 활용

파라미터화된 설계를 할 때 가장 골치 아픈 것이 주소 비트 폭(Address Width) 계산입니다. 깊이가 1024면 10비트, 2048이면 11비트가 필요합니다. 이를 사람이 계산하지 않고 수식으로 처리해야 진정한 자동화입니다.

// Verilog-2005 이상 지원
parameter DEPTH = 512;
localparam ADDR_WIDTH = $clog2(DEPTH); // log2(512) = 9 자동 계산

주의: Vivado 등 최신 툴은 잘 지원하지만, 아주 오래된 레거시 툴에서는 지원하지 않을 수 있으니 확인이 필요합니다.

6. 결론: 게으른 엔지니어가 훌륭한 엔지니어다

훌륭한 RTL 엔지니어는 ‘게으릅니다’. 똑같은 코드를 두 번 짜기 싫어서, 처음 짤 때 조금 고생하더라도 완벽하게 파라미터화된(Parameterized) 모듈을 만듭니다.

  1. parameter로 모듈의 크기와 깊이를 유연하게 만듭니다.
  2. generate for로 반복되는 노가다 코딩을 자동화합니다.
  3. generate if로 필요 없는 로직을 깔끔하게 제거합니다.

이렇게 만든 모듈은 프로젝트 A에서 쓰고, 프로젝트 B에서도 쓰고, 10년 뒤에도 쓸 수 있는 여러분만의 강력한 자산이 될 것입니다.

참고: chipverify

Similar Posts