Menu

Verilog Module Instantiation: 서브모듈 연결하기

모듈을 다른 모듈 안에서 인스턴스화하는 방법, named와 positional 포트 연결의 차이, 그리고 실제 설계를 만들 때 쓰는 다중 인스턴스 패턴들.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

모듈 안의 모듈

Verilog 설계는 모듈의 트리입니다. 최상위 모듈(여러분의 testbench나 칩의 최상위 wrapper)이 하위 모듈을 인스턴스화하고, 그것이 더 하위 모듈을 인스턴스화하는 식으로, 벤더가 제공하는 게이트 primitive까지 내려갑니다. 인스턴스화 가 그 중첩을 위한 문법입니다.

이미 첫 모듈 만들기에서 그 모양을 봤습니다.

and_gate dut(.a(a), .b(b), .y(y));

이 줄은 and_gate의 인스턴스 하나를 찍어 내 dut라고 이름 짓고, 그 포트를 로컬 신호에 연결합니다. 각 조각을 살펴봅시다.

인스턴스화의 형태

module_name instance_name (port_connections);
  • module_name 은 프로젝트 어딘가의 module 선언과 일치해야 합니다. Verilog은 대소문자를 구분합니다.
  • instance_name 은 여러분이 정하는 라벨입니다 - 보통 이 인스턴스의 역할을 설명합니다. 계층 경로와 파형 뷰에서 사용됩니다.
  • port_connections 은 인스턴스의 포트를 로컬 신호에 연결합니다. 두 가지 방식이 있습니다.

Named 포트 연결 (이걸 쓰세요)

named 형태는 이렇게 생겼습니다.

my_module instance_name(
    .clk    (clk),
    .reset  (reset_n),
    .data_in(in_bus),
    .data_out(out_bus),
    .valid  (out_valid)
);

.port(signal) 쌍은 "이 인스턴스의 port라는 포트를 로컬 신호 signal에 연결한다"는 뜻입니다. 순서는 중요하지 않습니다. 모듈 선언에 새 포트를 추가해도 기존 인스턴스화가 깨지지 않으며, 새 포트에 기본값을 주거나 각 호출 지점을 갱신하면 됩니다.

실용적인 두 가지 주의점.

  • 포트 이름(괄호 왼쪽)은 모듈 선언과 정확히 일치해야 합니다.
  • 신호 이름(괄호 안)은 인스턴스화가 위치한 곳의 로컬 - 보통 상위 모듈 - 입니다.

포트가 연결되지 않았다면 안쪽을 비워 두세요: .optional_port(). 인스턴스 내부에서는 신호가 floating(z)이 됩니다. 일부 synthesis 도구는 경고하고, 대부분은 받아들입니다.

Positional 포트 연결 (피하세요)

더 간결한 형태는 port list 순서대로 신호를 나열합니다.

my_module instance_name(clk, reset_n, in_bus, out_bus, out_valid);

이건 짧지만 깨지기 쉽습니다. 모듈의 port list 순서를 바꾸면(흔히 일어나는 실제 리팩터링) 모든 positional 인스턴스가 조용히 잘못 연결됩니다. port list가 정확히 한두 개고 변할 가능성이 거의 없는 경우가 아니라면 positional을 쓰지 마세요.

여전히 받아들일 만한 곳은 포트 순서가 API의 일부인 작은 유틸리티 모듈입니다. 2입력 게이트는 positional도 괜찮습니다. 30포트 메모리 컨트롤러는 문제를 자초하는 격입니다.

완전한 계층 예시

이건 실제 3단 계층입니다: testfull_adder → 두 개의 half_adder 인스턴스. 각 인스턴스는 half_adder 안의 게이트들의 자기 사본을 가지며, synthesis 도구는 인스턴스화마다 회로 하나를 생성합니다.

같은 모듈의 다중 인스턴스

같은 모듈을 여러 번 인스턴스화하면, 각 인스턴스는 독립적인 하드웨어 입니다. 상태를 공유하지 않습니다. 게이트를 공유하지 않습니다. 각 인스턴스를 설계에서 찍어낸 새 사본이라고 생각하세요.

adder add0(.a(a0), .b(b0), .sum(s0));
adder add1(.a(a1), .b(b1), .sum(s1));
adder add2(.a(a2), .b(b2), .sum(s2));
adder add3(.a(a3), .b(b3), .sum(s3));

이건 병렬로 동작하는 네 개의 독립된 가산기입니다. adder가 register를 가지고 있었다면, 각 인스턴스는 자기 상태를 가진 register 사본을 가지게 됩니다.

generate 루프: 반복 하드웨어 찍어 내기

손으로 네 인스턴스 적기는 괜찮습니다. 64개를 적기는 지루합니다. generate 블록은 elaborator가 타이핑을 대신 해 줍니다.

새 문법 세 가지.

  • genvar igenerate에서 쓸 수 있는 루프 변수를 선언합니다. 런타임 신호가 아니라 elaboration 시점에만 존재합니다.
  • generate ... endgenerate 가 루프를 감쌉니다. 일부 도구는 명시적인 generate 키워드 없이도 generate 루프를 받아들이지만, 적어 두면 의도가 분명해집니다.
  • begin : invert_loop 가 generate 스코프에 라벨을 붙입니다. 라벨은 생성된 각 인스턴스의 계층 이름의 일부가 됩니다(dut.invert_loop[0].u_inv, dut.invert_loop[1].u_inv 등).

synthesizer는 루프를 unroll해 WIDTH개의 bit_inverter 사본을 만들어 냅니다. 각 사본은 독립적인 하드웨어입니다.

인스턴스화에서의 parameter 오버라이드

모듈에 parameter가 있다면, 모듈 이름과 인스턴스 이름 사이에 #(.PARAM(value))로 덮어쓸 수 있습니다.

counter #(.WIDTH(16)) c16 (.clk(clk), .count(out16));
counter #(.WIDTH(32)) c32 (.clk(clk), .count(out32));

두 인스턴스 모두 같은 counter 소스를 쓰지만 서로 다른 너비를 가집니다. 문법은 Parameter에서 다뤘으며, 인스턴스화에 깔끔하게 끼워 맞춰집니다.

계층 이름

계층이 생기면 모든 신호는 hierarchical path 를 가집니다.

test.dut.ha0.sum

읽으면 "test 모듈 안, dut 인스턴스 안, ha0 인스턴스 안의 sum이라는 신호"입니다. 이 경로는 파형 뷰어, 오류 메시지, 그리고 testbench에서 가끔 서브모듈 깊숙이 들여다보는 $display에서 보입니다.

$display("internal carry1 = %b", dut.carry1);

이런 계층 참조는 testbench와 디버그용일 뿐입니다 - synthesis 가능한 RTL은 다른 모듈 내부에 손을 뻗지 않습니다.

흔한 실수

포트 이름 불일치. .clk_in(clk)은 로컬 clkclk_in이라는 포트에 연결합니다. 모듈의 실제 포트가 clk라면 파서가 알려 줍니다(도구에 따라 메시지가 더 분명하기도, 덜 분명하기도 합니다).

포트의 너비 불일치. 4비트 신호를 8비트 포트에 연결하면 조용히 zero-extension되고, 반대는 조용히 잘립니다. 대부분의 도구가 경고를 띄우는데 안 보인다면 더 자세히 살펴보세요.

parameter #을 잊는 것. counter (.WIDTH(8)) c(.clk(clk))는 오버라이드처럼 보이지만 아닙니다 - 파서가 (.WIDTH(8))을 포트 연결로 해석하다가 실패합니다. 올바른 형태: counter #(.WIDTH(8)) c(.clk(clk)).

같은 인스턴스 이름 재사용. 같은 스코프에서 두 인스턴스가 같은 이름을 가질 수 없습니다. 오류 메시지는 보통 분명한데, 복붙의 유혹이 함정입니다.

다음에 볼 내용

이제 모듈들을 실제 계층으로 엮을 수 있습니다. 다음 문서는 Verilog의 구조적 측면을 마무리합니다 - Continuous Assignment - 그리고 첫 장부터 자유롭게 써 온 assign 문을 더 깊이 파헤칩니다.

자주 묻는 질문

Verilog에서 모듈은 어떻게 인스턴스화하나요?

모듈 이름, 인스턴스 이름, 괄호로 둘러싼 포트 연결 목록 순으로 적습니다: my_module instance_name(.port(signal), ...);. 가장 흔한 스타일은 named 연결(.port(signal))이며, 순서와 무관하게 포트 이름으로 매칭합니다. 더 간결한 positional 스타일(my_module instance(signal1, signal2))은 port list 순서에 의존하므로 유지보수가 위험합니다.

named 연결과 positional 연결의 차이는?

positional 연결은 모듈의 port list와 같은 순서로 신호를 나열합니다 - 첫 신호가 첫 포트에, 두 번째가 두 번째에 연결되는 식. named 연결은 .port_name(signal_name)으로 이름을 통해 매칭합니다. named가 더 장황하지만 port 재정렬에 면역이고 호출 지점에서 의도가 드러납니다. 포트가 두세 개를 넘는다면 named를 사용하세요.

같은 Verilog 모듈을 여러 번 인스턴스화할 수 있나요?

네 - 그게 핵심입니다. 각 인스턴스는 자신의 상태를 가진 독립적인 하드웨어입니다. adder 모듈이 있다면 SIMD 유닛에서 64번 인스턴스화하면서 각각 다른 입력을 줄 수 있습니다. 인덱스로 구분되는 유사한 인스턴스들에는 generate 루프가 정석 문법입니다.

Verilog의 generate block이란?

generate ... endgenerate는 반복 하드웨어를 찍어 내는 컴파일 시점 구성요소입니다. generate 안의 for 루프는 본문에 있는 것을 N번 인스턴스화합니다. generate는 시뮬레이션이 시작되기 전 elaboration 시점에 실행됩니다 - 런타임 루프가 아니라 synthesizer를 위한 코드 생성기입니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기