모듈은 Verilog의 기본 단위
Verilog의 모든 것은 module 안에 살고 있습니다. 모듈은 회로의 한 조각을 감쌉니다. 어떤 신호가 들어오고, 어떤 신호가 나가며, 그 사이에 어떤 wire/register/논리가 있는지를 선언합니다. 여러분이 본 모든 칩은 다른 모듈을 인스턴스화하는 모듈들의 트리이며, 그 끝은 벤더가 제공한 게이트 primitive까지 내려갑니다.
형태는 항상 같습니다.
module name(port_list);
// 선언: wire, reg, parameter
// 본문: assign, 인스턴스화, always 블록
endmodule
단계적으로 완성된 모듈에 다가가 봅시다.
가장 작은 유용한 모듈: 2입력 AND 게이트
뭔가 단순하고 자기 완결적인 게 필요합니다. 2입력 AND 게이트가 딱입니다 - 입력 둘, 출력 하나, 로직 한 줄.
Run을 눌러 보세요. AND 진리표의 네 행을 볼 수 있을 겁니다. 각 부분을 자세히 살펴봅시다.
모듈 선언 읽기
module and_gate(
input wire a,
input wire b,
output wire y
);
module and_gate는 이름이and_gate인 모듈을 선언합니다. 이 이름이 다른 모듈에서 인스턴스화할 때 쓰는 이름입니다.- 괄호 안의 목록은 port list - 외부에서 보이는 신호들입니다.
input wire a-a는 입력 포트이며wire(외부에서 구동됨)입니다.output wire y-y는 모듈 내부의 무언가가 구동하는 출력 포트입니다.
짧게 쓰고 싶다면 input wire a 대신 input a라고 써도 됩니다 - 방향만 적으면 기본 타입이 wire입니다. 하지만 명시적으로 쓰는 습관을 들이는 게 좋습니다. Module Ports에서 포트 형태를 더 다룹니다.
본문
assign y = a & b;
이게 continuous assignment입니다. "언제든 a나 b가 바뀌면 y를 두 값의 비트와이즈 AND로 다시 계산하라"는 뜻입니다. clock도 timing도 없습니다 - 이 관계는 항상 참입니다. 그게 순수한 조합 논리입니다.
endmodule은 블록을 닫습니다. 모듈이 끝났습니다.
testbench
and_gate만으로는 실행할 수 없습니다. 그 입력을 구동하고 출력을 관찰하는 두 번째 모듈이 필요합니다. 그게 testbench이고, 관례상 test, tb, 또는 <design>_tb라고 부릅니다.
module test;
reg a, b;
wire y;
and_gate dut(.a(a), .b(b), .y(y));
...
endmodule
세 가지 주목할 점.
- port list가 없음. testbench는 시뮬레이션의 최상위입니다 - 그 바깥에는 아무것도 없습니다.
reg a, b와wire y. design under test(DUT)의 입력은 testbench에서reg인데, procedural block에서 구동 하기 때문입니다. 출력은 DUT가 구동하므로wire입니다.and_gate dut(.a(a), .b(b), .y(y)). 이게 인스턴스화입니다.and_gate의 사본을 찍어 내고 그것을dut(흔한 이름 - "design under test")라고 부릅니다..a(a)문법은 "이 인스턴스의a라는 포트를 로컬a신호에 연결하라"는 뜻입니다. Module Instantiation에서 더 깊이 다룹니다.
자극(stimulus)
initial begin
a = 0; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 0; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 1; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 1; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
$finish;
end
initial 블록은 시뮬레이션 시작 시 한 번 실행됩니다. 내부에서는
a = 0; b = 0;이 입력을 구동합니다. 이건 blocking assignment입니다 - 순서가 의미가 있고, 각 문장은 다음 문장 전에 일어납니다.#1은 시뮬레이션 시간을 1 단위 진행시킵니다. 입력이 바뀐 뒤y가 안정될 시간이 필요합니다.#1이 없으면$display는y의 옛 값을 출력하게 됩니다.$display(...)는 시뮬레이션 콘솔에 출력합니다. format string은 C의printf처럼 동작합니다:%b는 binary,%d는 decimal,%h는 hex,%t는 시뮬레이션 시간.$finish는 시뮬레이션을 종료합니다. 없으면 시뮬레이터가 영원히 시간을 진행하며 결코 오지 않을 이벤트를 기다립니다.
직접 망가뜨려 보기
모듈을 OR 게이트로 바꿔 보세요(&를 |로). 진리표가 뒤집힙니다. 이제 XOR도 해 보세요.
뼈대는 같고 연산자만 다릅니다. 조합 논리의 게임은 이게 전부입니다 - 포트를 선언하고, assign을 쓰고, 입력을 sweep하고, 출력을 관찰합니다.
출력이 두 개인 모듈
모듈은 출력이 여러 개일 수도 있습니다. half-adder입니다 - 입력 둘, sum과 carry-out.
두 assign 문은 나란히 있지만 병렬로 일어납니다 - "먼저 sum을 계산하고 그다음에 carry"가 아닙니다. 둘 다 항상 참입니다. 그게 하드웨어 vs 소프트웨어에서 이야기한 concurrency를 구체화한 모습입니다.
지금까지 알게 된 것
여러분은 Verilog 파일의 전체 골격을 봤습니다 - 포트가 선언된 설계 모듈과 본문, 그리고 그것을 인스턴스화하고 initial 블록에서 입력을 구동하며 결과를 보고하는 testbench 모듈. 앞으로 보게 될 거의 모든 Verilog 소스 파일이 이 템플릿에 들어맞습니다. 나머지는 더 풍부한 로직, 멀티비트 신호, clocked 동작, 더 큰 testbench로 채워 가는 일입니다.
다음에 볼 것: comment와 코드 스타일. 모듈이 커져도 가독성을 유지하는 방법입니다.
자주 묻는 질문
가장 단순한 Verilog 모듈은 무엇인가요?
가장 작은 합법적인 Verilog 모듈은 module name; endmodule - 포트도 없고 본문도 없는 것입니다. 가장 작은 유용한 모듈은 단일 출력 모듈입니다: module and_gate(input wire a, input wire b, output wire y); assign y = a & b; endmodule. 이건 어떤 큰 설계에도 그대로 넣을 수 있는 실제 조합 논리입니다.
Verilog 모듈은 어떻게 실행하나요?
모듈을 단독으로 실행할 수는 없습니다 - 그것은 회로 기술이지 프로그램이 아닙니다. 설계를 인스턴스화하고 입력을 구동하는 testbench 모듈을 작성한 다음, iverilog -o sim design.v test.v로 둘 다 컴파일하고 vvp sim으로 실행합니다. 이 페이지의 브라우저 에디터는 Run을 누르면 두 단계를 모두 자동으로 처리합니다.
Verilog의 testbench는 무엇인가요?
testbench는 두 번째 모듈입니다 - 보통 포트가 없고 - 여러분의 설계를 동작시키는 것이 임무입니다. 설계를 인스턴스화하고, initial 블록을 통해 입력을 흔들고, $display나 $monitor로 출력을 관찰하고, 끝나면 $finish를 호출합니다. testbench는 synthesis 대상이 아닙니다 - 동작 검증을 위한 것입니다.
Verilog 코드에 왜 $finish가 필요한가요?
하드웨어는 멈추지 않기 때문입니다. 시뮬레이터는 시간이 흐르는 척하므로, 명시적인 $finish가 없으면 새로운 이벤트를 기다리며 영원히 시간을 진행합니다. $finish는 시뮬레이터에게 '끝났으니 깔끔하게 종료하라'고 알립니다. testbench에서는 initial 블록의 마지막 줄입니다 - 테스트를 실행한 다음 finish.