1. 기록 범위 선택하기
$dumpfile은 VCD 파일 이름을 정하고 $dumpvars(0, tb)는 호출된 시점부터 tb 아래 전체를
기록합니다. 두 호출은 레지스터
단계·관찰 시점·파형 장에서 처음 썼습니다. 이 장은 전체를 기록하기에는 너무 많을 때
기록 범위를 고르는 방법과, 파형을 비우거나 늦추거나 불완전하게 만드는 실수를 다룹니다.
1.1 scope와 깊이
$dumpvars의 scope 인자는 어느 인스턴스부터 기록할지 고르고, 깊이는 그 아래로 몇 계층까지
기록할지 정합니다. 여러 번 호출할 수 있으며 선택한 범위가 합쳐집니다.
형식
$dumpvars(깊이, scope);
$dumpvars(깊이, scope); // 두 번째 호출은 범위를 추가합니다| 호출 | 기록 대상 |
|---|---|
$dumpvars(0, tb) | tb와 그 아래의 모든 인스턴스 |
$dumpvars(1, tb) | tb에 직접 선언한 신호만 |
$dumpvars(2, tb) | tb와 tb.dut 같은 직속 하위 인스턴스 |
$dumpvars(0, tb.dut) | tb.dut와 그 아래 전체. tb는 제외 |
인자 없는 $dumpvars | 시뮬레이션 전체 |
scope는 tb.dut.div처럼 인스턴스 이름을 점으로 이어 쓴 계층 이름입니다.
참고. 큰 설계는 VCD 파일도 크고, 신호가 수백 개인 파형은 읽기 어렵습니다. 모든 변화가 파일에 기록되고 실습 환경에서는 브라우저로 전송되므로, 조사하려는 범위만 기록하세요.
1.2 예제: 중간 계층 건너뛰기
Blinker.v는 두 계층입니다. divider는 2비트 phase를 세고 네 사이클마다 한 사이클 동안
tick을 1로 만듭니다. blinker는 divider를 div라는 인스턴스로 만들고 tick마다 led를
반전합니다.
// tick은 네 사이클마다 한 사이클 동안 1이다.
module divider (
input clock,
input reset,
output tick
);
reg [1:0] phase;
always @(posedge clock) begin
if (reset) phase <= 2'd0;
else phase <= phase + 2'd1;
end
assign tick = (phase == 2'd3);
endmodule
// LED는 divider의 tick마다 반전된다.
module blinker (
input clock,
input reset,
output reg led
);
wire tick;
divider div (.clock(clock), .reset(reset), .tick(tick));
always @(posedge clock) begin
if (reset) led <= 1'b0;
else if (tick) led <= ~led;
end
endmoduleTb.v는 $dumpvars를 두 번 호출합니다. 첫 호출은 tb를 깊이 1로, 두 번째 호출은
tb.dut.div를 깊이 1로 기록합니다. led가 바뀔 때마다 LED 줄도 출력합니다.
module tb;
reg clock = 0;
reg reset = 1;
wire led;
blinker dut (.clock(clock), .reset(reset), .led(led));
always #5 clock = ~clock;
always @(led) $display("LED t=%0t led=%b", $time, led);
initial begin
$dumpfile("dump.vcd");
// 깊이 1: tb에 직접 선언한 신호만 기록한다.
$dumpvars(1, tb);
// 두 번째 호출은 scope 하나를 추가한다. 여기서는 두 단계 아래의 divider다.
$dumpvars(1, tb.dut.div);
@(negedge clock) reset = 0;
repeat (12) @(posedge clock);
#1 $finish;
end
endmodule이 개념 실습하기
다음 단계로 짧은 Verilog 문제에 이 개념을 적용해 보세요.
실행하기 전에 예측해 보세요.
- 파형에는 어떤 scope가 나타날까요?
blinker자체의 신호인tick과 레지스터led는tb.dutscope 아래에 기록될까요?LED줄은 어느 시간에 출력될까요?
확인 문제
Tb.v는 tb를 깊이 1로, tb.dut.div를 깊이 1로 기록합니다. tick과 레지스터 led를 담은 tb.dut scope가 파형에 나타납니까?
결과 확인.
| scope | 기록되는 신호 |
|---|---|
tb | clock, reset, led |
tb.dut | 없습니다. 어느 호출도 이 scope를 선택하지 않았습니다 |
tb.dut.div | 포트 clock, reset, tick과 레지스터 phase |
LED 줄은 리셋 값이 정해지는 시간 5와, phase가 3이 된 다음 상승 에지인 45, 85, 125에
출력됩니다. 파형에서 phase는 상승 에지마다 0, 1, 2, 3으로 바뀌고, tick은 phase가 3인
동안만 1이며, led는 각 tick이 끝나는 에지에서 반전됩니다. divider의 clock 포트는
tb의 clock과 같은 net이므로 클록은 tb 아래에 한 번만 표시됩니다.
직접 바꿔 보세요. 두 번째 호출을 $dumpvars(0, tb.dut)로 바꿔 보세요. tb.dut scope가
tick과 레지스터 led를 담아 나타나고, 그 아래에 tb.dut.div가 이어집니다. LED 출력은
전혀 바뀌지 않습니다. 기록 범위는 시뮬레이션 결과에 영향을 주지 않습니다.
2. 자주 하는 실수
2.1 $dumpvars를 호출하지 않음
// 잘못된 코드: 파일 이름만 정하고 기록 대상을 고르지 않았습니다
initial begin
$dumpfile("dump.vcd");
// ...
end
// 바른 코드
initial begin
$dumpfile("dump.vcd");
$dumpvars(0, tb);
// ...
end$dumpvars가 없으면 Icarus는 VCD 파일을 만들지 않습니다. 실행은 성공하지만 VCD info:
줄도 파형도 나타나지 않습니다.
2.2 기록을 늦게 시작함
// 잘못된 코드: 리셋이 끝난 뒤에 기록을 시작합니다
repeat (2) @(posedge clock);
$dumpvars(0, tb);
// 바른 코드: 시간 0에 기록을 시작합니다
$dumpvars(0, tb);
repeat (2) @(posedge clock);$dumpvars는 호출된 시점부터 기록합니다. 늦게 호출하면 초기 x와 리셋 구간처럼 문제를
찾는 데 가장 중요한 구간이 파일에 없습니다.
확인 문제
클록은 0에서 시작하는 always #5 clock = ~clock이고, 위의 잘못된 코드는 repeat (2) @(posedge clock) 뒤에 $dumpvars를 호출합니다. 기록은 몇 시에 시작됩니까?
2.3 깊이가 부족해 신호가 빠짐
// 잘못된 코드: tb의 신호만 기록되어 tb.dut 내부가 보이지 않습니다
$dumpvars(1, tb);
// 바른 코드: 그 아래 계층까지 모두 기록합니다
$dumpvars(0, tb);필요한 신호가 파형에 없으면 그 신호가 속한 scope가 선택 범위 안에 있는지, 깊이가 그 계층까지 닿는지 확인하세요.
확인 문제
Blinker 예제에서 phase는 tb.dut.div에 선언되어 있습니다. $dumpvars(d, tb) 한 번으로 phase를 기록하려면 양수인 깊이 d는 최소 얼마여야 합니까?
2.4 `timescale 없이 소수 지연 쓰기
// 잘못된 코드: 정밀도가 한 단위이므로 10.5가 11로 반올림됩니다
#10.5 b = 0;
// 바른 코드: 모든 파일 첫머리에 지시어를 둡니다
`timescale 1ns / 1ps
// ...
#10.5 b = 0;`timescale이 없으면 소수 지연은 경고 없이 정수 시간으로 옮겨집니다. 파형의 시간이
예상과 조금씩 어긋나면 지시어부터 확인하세요. 지시어가 정하는 단위와 정밀도는
레지스터 단계·관찰 시점·파형
장에 있습니다.
2.5 식 안에만 있는 값을 파형에서 찾음
// 관찰하기 어려운 코드: a & b는 이름이 없어 VCD에 기록되지 않습니다
always @(posedge clock) y_reg <= a & b;
// 관찰하기 쉬운 코드: 중간 값에 wire 이름을 줍니다
wire both = a & b;
always @(posedge clock) y_reg <= both;VCD는 선언된 신호만 기록합니다. 식의 중간 결과를 파형에서 보고 싶으면 wire로 선언하세요.
하드웨어는 그대로이고 파형에 신호 하나가 추가됩니다.
3. 정리
| 주제 | 핵심 |
|---|---|
| 기록 범위 | 깊이 0은 모든 하위 계층, 1은 그 scope만입니다. 여러 호출의 범위는 합쳐집니다 |
| scope 이름 | tb.dut.div처럼 인스턴스 이름을 점으로 잇습니다. 어느 호출도 이름 붙이지 않은 scope는 기록되는 두 scope 사이에 있어도 기록되지 않습니다 |
$dumpvars 누락 | 파일이 전혀 만들어지지 않습니다. 실행은 성공하지만 VCD info: 줄도 파형도 없습니다 |
늦은 $dumpvars | 파일은 호출된 시점에서 시작합니다. 초기 x와 리셋 구간이 빠집니다 |
`timescale 없는 소수 지연 | 한 단위로 반올림되어 시간이 경고 없이 어긋납니다 |
| 이름 없는 식 | VCD에 없습니다. wire로 이름을 주면 보입니다 |
이 장 아래의 실습 문제 delay-line의 2단 지연선은 첫 단과 출력 단이 모든 줄에서 같은 값을
보여 한 단처럼 동작합니다. 파형에서 두 단이 어느 에지에서 바뀌는지 비교해 원인을 찾고, 두
단이 모두 보이도록 충분한 깊이로 기록하세요.
4. 확인 문제
-
tb.dut는 기록하고tb.dut.div는 기록하지 않으려면$dumpvars를 어떻게 호출해야 할까요?정답 보기
$dumpvars(1, tb.dut)처럼tb.dut를 깊이 1로 기록합니다.tb의 신호도 필요하면$dumpvars(1, tb)를 함께 호출합니다.$dumpvars(2, tb)는tb와tb.dut를 기록하고tb.dut.div는 기록하지 않으므로 같은 결과를 한 번의 호출로 얻습니다.