본문으로 건너뛰기
code0.HDL

파형 기록

$dumpvars의 scope와 깊이로 VCD에 기록할 인스턴스를 고르고, 중간 계층을 건너뛴 파형을 읽고, 기록을 비우거나 늦추거나 얕게 만들거나 반올림된 지연으로 어긋나게 하거나 이름 없는 값을 빠뜨리는 실수를 알아봅니다.

이 장에서 배울 내용

  • $dumpvars의 scope와 깊이로 VCD에 기록할 인스턴스를 고르고, 한 번의 선택으로 부족하면 호출을 추가할 수 있습니다
  • 중간 계층을 건너뛴 파형을 읽고 어떤 신호가 왜 빠졌는지 설명할 수 있습니다
  • 다섯 가지 기록 실수를 알아볼 수 있습니다. $dumpvars 누락, 늦은 시작, 부족한 깊이, `timescale 없는 소수 지연, 식 안에만 있는 값
이 장에서 배우는 개념
  • 파형 기록
먼저 알아야 할 개념

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를 반전합니다.

Blinker.v
// 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
endmodule

Tb.v는 $dumpvars를 두 번 호출합니다. 첫 호출은 tb를 깊이 1로, 두 번째 호출은 tb.dut.div를 깊이 1로 기록합니다. led가 바뀔 때마다 LED 줄도 출력합니다.

Tb.v
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 문제에 이 개념을 적용해 보세요.

실행하기 전에 예측해 보세요.

  1. 파형에는 어떤 scope가 나타날까요?
  2. blinker 자체의 신호인 tick과 레지스터 led는 tb.dut scope 아래에 기록될까요?
  3. LED 줄은 어느 시간에 출력될까요?

확인 문제

Tb.v는 tb를 깊이 1로, tb.dut.div를 깊이 1로 기록합니다. tick과 레지스터 led를 담은 tb.dut scope가 파형에 나타납니까?

답

결과 확인.

scope기록되는 신호
tbclock, 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. 확인 문제

  1. 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는 기록하지 않으므로 같은 결과를 한 번의 호출로 얻습니다.

확인 지점

예제를 실행해 예측과 비교합니다

참고 자료

참고한 자료의 라이선스와 고지