본문으로 건너뛰기
code0.HDL

RegNext·지연·파형

RegNext로 신호를 에지 하나만큼, 둘을 이어 에지 둘만큼 늦추고, 늦춘 복사본으로 상승 에지 검출기를 만든 뒤, 기록된 파형에서 리셋·poke·레지스터 변화를 읽습니다.

이 장에서 배울 내용

  • RegNext로 신호를 에지 하나 또는 둘만큼 늦추고 지연에 리셋 값을 줍니다
  • 늦춘 복사본으로 상승·하강 에지 검출기를 만들고 펄스가 나오는 사이클을 예측합니다
  • WriteVcdAnnotation으로 VCD를 기록해 dump.vcd로 실습 환경에 전달하고, 기록되는 신호를 가려냅니다
  • 기록된 시간축에서 리셋, 클록 에지, poke, 레지스터 변화를 읽습니다
이 장에서 배우는 개념
  • RegNext·한 단계와 두 단계 지연
먼저 알아야 할 개념

1. RegNext는 인자를 다음 값으로 받는 레지스터입니다

RegNext(x, init)은 레지스터를 만들고 x를 그 다음 값으로 연결한 뒤, 레지스터의 현재 값을 반환합니다. 그 값은 에지 하나 전의 x입니다. 둘째 인자는 리셋 값이며, 없으면 리셋도 정해진 시작 값도 없습니다. 둘을 이어 붙이면 에지 둘만큼 늦어집니다. 둘째 레지스터의 다음 값은 x가 아니라 첫 레지스터의 현재 값입니다. RegNext를 하나 거칠 때마다 지연이 한 사이클씩 늘어납니다.

형식

val d1 = RegNext(io.in, 0.U)   // 에지 하나 전의 in
val d2 = RegNext(d1, 0.U)      // 에지 둘 전의 in

Chisel을 만지기 전에 두 단계 지연을 채워 보세요. in 열은 각 사이클 동안의 입력입니다. 그 사이클을 끝내는 에지 뒤에 s1과 s2가 가질 값을 적으세요.

예측한 뒤 비교 · delay2

비교하기 전에 모든 칸을 채우세요. 빈칸도 불일치로 처리합니다.

상태표
사이클in에지에서 샘플링한 입력s1에지 직후s2에지 직후
01
10
21
31
40
50

2. 상승 에지는 "지금 1, 에지 하나 전에는 0"입니다

조합 식은 현재 사이클만 봅니다. 시간에 따른 변화를 검출하려면 이전 입력을 레지스터에 두고 비교해야 합니다. 상승 에지 검출기는 현재 입력과 한 사이클 전 입력을 비교하고, 한 사이클 전 입력은 RegNext로 얻습니다. 이전 값이 0이고 현재 값이 1인 사이클이 상승 에지입니다.

형식

val 이전_값 = RegNext(신호, 리셋_값)
val 상승 = 신호 && !이전_값

리셋 값은 리셋이 끝나기 전 사이클의 입력을 회로가 무엇으로 가정하는지 정합니다.

3. 예제: 지연 출력과 상승 에지

Edge의 세 신호는 다음과 같습니다.

신호정의의미
lastRegNext(io.in, false.B)이전 사이클의 입력입니다. 리셋 중에는 false입니다
delayedlast한 사이클 지연된 입력입니다
riseio.in && !last이전 사이클 0, 현재 사이클 1일 때만 참입니다
Edge.scala
import chisel3._

class Edge extends Module {
  val io = IO(new Bundle {
    val in      = Input(Bool())
    val delayed = Output(Bool())
    val rise    = Output(Bool())
  })
  // last는 이전 사이클의 입력을 저장한다. 리셋 값은 false.B다.
  val last = RegNext(io.in, false.B)
  io.delayed := last
  // in이 이번 사이클 1이고 이전 사이클 0일 때만 참이다.
  io.rise := io.in && !last
}

테스트벤치는 패턴 0 1 1 1 0 1 0 0을 사이클마다 하나씩 넣고, step 전에 delayed와 rise를 출력한 뒤 step합니다. 따라서 사이클 n에 보이는 레지스터 값은 사이클 n − 1을 끝낸 에지에서 저장된 값입니다.

Tb.scala
import chisel3._
import chiseltest._
import chiseltest.RawTester.test

object Main extends App {
  test(new Edge) { dut =>
    val pattern = Seq(0, 1, 1, 1, 0, 1, 0, 0)
    for ((v, cycle) <- pattern.zipWithIndex) {
      dut.io.in.poke((v == 1).B)
      val d = dut.io.delayed.peek().litValue
      val r = dut.io.rise.peek().litValue
      println(s"cycle=$cycle in=$v delayed=$d rise=$r")
      // 레지스터가 이 클럭 에지에서 in을 저장한다.
      dut.clock.step()
    }
  }
}

이 개념 실습하기

다음 단계로 짧은 Chisel 문제에 이 개념을 적용해 보세요.

실행하기 전에 예측해 보세요. 다음 표를 사이클 7까지 채워 보세요. 처음 세 사이클은 채워 두었습니다.

사이클inlastdelayedrise
000 (리셋 값)00
11001
211 (사이클 1의 입력)10

in은 사이클 1–3 동안 계속 1입니다. 패턴 전체에서 rise는 몇 번 1이 되고, 다시 1이 되는 사이클은 어디일까요?

확인 문제

여덟 사이클 동안 rise가 1인 사이클은 몇 개입니까?

결과 확인. 실행을 누르세요. delayed를 in 열이 한 줄 내려간 것으로 읽고, 0인 줄 다음에 1인 줄이 오는 자리에서만 rise가 1인지 확인하세요.

출력 열확인할 내용
delayedin 열을 한 줄 아래로 옮긴 것과 같습니다. 레지스터 하나가 정확히 한 사이클을 지연합니다
rise0 다음에 1이 오는 사이클 1과 5에서만 1입니다
사이클 2–3in은 1이지만 last도 1이므로 rise는 0입니다. 1이 길게 이어져도 펄스는 한 번만 나옵니다

에지 검출기는 0에서 1로 바뀌는 전이 하나마다 정확히 한 사이클 동안 1을 출력합니다.

4. 비교를 바꿔 보세요

실행 패널에서 rise를 !io.in && last로 바꾸고 다시 실행하세요.

확인 문제

rise = !io.in && last일 때 출력이 1인 사이클은 어디입니까?

답

바꿀 곳예상 결과
rise를 !io.in && last로하강 에지 검출기가 되어 1 다음에 0이 오는 사이클 4와 6에서 1이 됩니다. 레지스터는 같고 주변 조합 논리만 다릅니다
io.rise := RegNext(io.in && !last, false.B)로출력에 레지스터가 하나 더 붙어 펄스가 사이클 2와 6으로 한 사이클씩 늦어집니다

참고. 두 번째 변형처럼 출력을 레지스터로 내보내면 모듈 경계의 타이밍이 뒤의 논리와 분리되지만 지연이 한 사이클 늘어납니다. 출력을 레지스터로 낼지는 인터페이스마다 정하고, 그 지연을 사용하는 쪽이 알 수 있게 기록해 두세요.

5. VCD 파일에 기록되는 것

println이나 printf는 출력하기로 정한 값만 보여 주고, expect는 검사하기로 정한 값만 확인합니다. 파형은 이름 있는 모든 신호를 모든 사이클에 걸쳐 기록하므로 예상하지 못한 문제도 찾을 수 있습니다. chiseltest는 파형을 VCD(Value Change Dump) 파일로 기록하고, 테스트벤치가 그 파일을 실습 환경의 파형 뷰어에 전달합니다.

5.1 신호 선언과 변화 기록

VCD 파일은 두 부분으로 된 텍스트 파일입니다. header는 신호마다 한 번씩 폭, 이름, 그리고 !나 # 같은 짧은 식별자를 선언합니다. 본문은 시간 순서의 기록입니다. #시간 줄이 현재 시간을 정하고, 그 뒤의 줄은 그 시간에 값이 바뀐 신호만 기록합니다. 뷰어는 신호의 마지막 값을 다음 변화까지 유지해 파형을 복원합니다.

형식

$timescale <단위> $end
$var wire <폭> <식별자> <이름> $end
#<시간>
<0 또는 1><식별자>        1비트 신호의 변화
b<2진수 값> <식별자>      여러 비트 신호의 변화

다음은 1비트 clk와 2비트 q를 담은 짧은 VCD를 직접 작성한 예입니다.

$timescale 1ns $end
$var wire 1 ! clk $end
$var wire 2 " q $end
#0
0!
b0 "
#5
1!
b1 "
#10
0!
줄의미
$timescale 1ns $end시간 단위 하나가 1나노초입니다
$var wire 1 ! clk $end폭 1비트의 clk를 선언하고 식별자 !를 붙입니다
#0 뒤의 0!, b0 "시간 0에 clk는 0, q는 0입니다
#5 뒤의 1!, b1 "시간 5에 clk가 상승하고 q가 1이 됩니다
#10 뒤의 0!시간 10에는 clk만 바뀝니다. q는 1을 유지합니다

5.2 기록되는 신호

chiseltest의 VCD에는 시뮬레이션 회로에서 이름을 가진 신호가 기록됩니다.

기록 대상이 장 Blinker의 예
최상위 포트. 경로의 .를 _로 바꿉니다io_en, io_led, io_count
암묵적 클록과 리셋clock, reset
val에 담은 레지스터count, led
val에 담은 조합 값wrap

val에 담은 값은 이름을 유지하고, 이름 없는 식은 생성 이름을 받습니다. 하드웨어에서 출력하기 장에서 보는 _count_T 같은 이름 없는 임시 노드는 시뮬레이션 회로에는 있지만 VCD에는 기록되지 않습니다.

참고. 파형에서 확인할 값은 val에 담으세요. 이름을 붙여도 생성되는 하드웨어는 늘어나지 않습니다.

6. chiseltest로 VCD 기록하기

6.1 WriteVcdAnnotation

test에 전달한 annotation 목록에 WriteVcdAnnotation이 있으면 chiseltest가 VCD를 기록합니다. 이 annotation은 chiseltest.simulator에 있습니다.

형식

import chiseltest.simulator.WriteVcdAnnotation

test(new 최상위_모듈, Seq(WriteVcdAnnotation), "이름") { dut => ... }
test(new Counter, Seq(WriteVcdAnnotation), "run1") { dut =>
  dut.io.en.poke(true.B)
  dut.clock.step(5)
}
// test_run_dir/run1/Counter.vcd가 생성됩니다
코드효과
Seq(WriteVcdAnnotation)이 테스트의 VCD 출력을 켭니다
"run1"작업 디렉터리를 test_run_dir/run1/로 고정합니다
Counter최상위 모듈 이름이 파일 이름이 됩니다. Counter.vcd입니다

이름 인자가 없으면 디렉터리는 실행할 때마다 바뀌는 test_run_dir/chisel_test_<밀리초>/입니다. 고정된 이름을 전달해야 경로를 미리 알 수 있습니다.

6.2 실습 환경에 파일 전달하기

실습 환경은 실행 작업 디렉터리의 dump.vcd 파일만 파형으로 표시합니다. 명령이 끝나면 런타임이 이 파일을 브라우저로 보냅니다. chiseltest는 이 규칙을 모르므로 테스트벤치가 파일을 직접 복사합니다.

형식

import java.nio.file.{Files, Paths, StandardCopyOption}

Files.copy(원본_경로, Paths.get("dump.vcd"), StandardCopyOption.REPLACE_EXISTING)

복사는 test가 반환된 뒤에 해야 합니다. 테스트 본문이 실행되는 동안에는 VCD 파일이 아직 없습니다. 시뮬레이터는 테스트가 끝날 때 파일을 기록하므로 본문 안에서 복사하면 복사할 파일을 찾지 못합니다.

주의. 복사하기 전에 파일이 있는지 확인하고, 없으면 예외를 발생시키세요. 원본 파일이 없으면 Files.copy도 예외를 발생시킵니다. 명시적인 검사는 빠진 annotation이나 잘못된 테스트 이름을 찾기 쉽도록 기대한 VCD 경로를 더 분명한 메시지로 알려 줍니다.

7. 예제: 같은 이야기를 파형으로

Blinker의 3비트 레지스터 count는 io.en이 1인 동안 0부터 5까지 센 뒤 0으로 돌아갑니다. wrap은 count가 5일 때 참인 이름 있는 조합 신호이므로 파형에도 나타납니다. led는 count가 0으로 돌아갈 때마다 반전됩니다. 두 레지스터는 출력 포트도 구동합니다.

Blinker.scala
import chisel3._

class Blinker extends Module {
  val io = IO(new Bundle {
    val en    = Input(Bool())
    val led   = Output(Bool())
    val count = Output(UInt(3.W))
  })
  val count = RegInit(0.U(3.W))
  // 이름 있는 val은 파형의 신호가 된다.
  val wrap = count === 5.U
  when (io.en) {
    count := Mux(wrap, 0.U, count + 1.U)
  }
  val led = RegInit(false.B)
  when (io.en && wrap) {
    led := !led
  }
  io.led := led
  io.count := count
}

Tb.scala는 Seq(WriteVcdAnnotation)과 이름 "tb"를 전달합니다. en을 1로 poke하고 step 여덟 번 동안 count와 led를 출력한 뒤, en을 0으로 poke하고 step 두 번을 더 실행합니다. test가 반환되면 test_run_dir/tb/Blinker.vcd가 있는지 확인하고, 없으면 예외를 발생시키며, 있으면 dump.vcd로 복사합니다.

Tb.scala
import chisel3._
import chiseltest._
import chiseltest.RawTester.test
import chiseltest.simulator.WriteVcdAnnotation
import java.nio.file.{Files, Paths, StandardCopyOption}

object Main extends App {
  // 세 번째 인자가 디렉터리를 test_run_dir/tb/로 고정한다.
  test(new Blinker, Seq(WriteVcdAnnotation), "tb") { dut =>
    dut.io.en.poke(true.B)
    for (cycle <- 1 to 8) {
      dut.clock.step()
      println(s"CYCLE $cycle count=${dut.io.count.peek().litValue} led=${dut.io.led.peek().litValue}")
    }
    dut.io.en.poke(false.B)
    dut.clock.step(2)
  }

  // VCD 이름은 최상위 모듈 이름이다. 실습 환경이 읽는 위치로 복사한다.
  val vcd = Paths.get("test_run_dir", "tb", "Blinker.vcd")
  if (!Files.exists(vcd)) throw new RuntimeException(s"VCD not found: $vcd")
  Files.copy(vcd, Paths.get("dump.vcd"), StandardCopyOption.REPLACE_EXISTING)
  println(s"COPIED $vcd to dump.vcd")
}

이 개념 실습하기

다음 단계로 짧은 Chisel 문제에 이 개념을 적용해 보세요.

처음 여덟 사이클의 값을 따라가 보세요. count는 1, 2, 3, 4, 5로 증가합니다. count가 5인 동안 wrap이 참이므로 다음 에지에서 count는 0이 되고 led는 1로 바뀝니다. 그 뒤 count는 1, 2로 이어집니다. en이 0인 마지막 step 두 번은 아무것도 바꾸지 않습니다.

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

확인 문제

led가 처음 1로 읽히는 CYCLE 줄은 몇 번입니까?

확인 문제

파형에서 reset이 1인 동안 clock의 상승 에지는 몇 번 있습니까?

시간축에서 두 가지를 더 정하세요. io_en은 상승 에지에서 바뀔까요, 에지 사이에서 바뀔까요? count, wrap, led, io_led가 한꺼번에 바뀌는 에지는 언제일까요?

결과 확인. 텍스트 출력은 위 추적과 같습니다. CYCLE 6부터 count가 0으로 돌아가며 led가 1이 되고, 마지막에 COPIED 줄이 출력됩니다. 파형은 출력 아래에 표시됩니다. 클록 주기는 1ns timescale 기준 10 시간 단위이며, 상승 에지는 11, 21, 31, …, 하강 에지는 16, 26, …에 있습니다.

시간변화확인할 내용
0reset = 1chiseltest의 리셋 사이클이 시작됩니다
1clock 상승리셋 중의 유일한 상승 에지입니다
6clock 하강, reset = 0, io_en = 1리셋이 끝나고 첫 poke가 에지 사이에 기록됩니다
11, 21, 31, 41clock 상승, count가 1부터 4까지레지스터는 상승 에지에서만 바뀝니다
51count = 5, wrap = 1조합 val은 자신이 읽는 레지스터와 같은 시간에 바뀝니다
61count = 0, wrap = 0, led = 1, io_led = 1에지 하나에서 여러 신호가 동시에 바뀝니다
86io_en = 0step 여덟 번 뒤의 poke입니다. 이번에도 하강 에지 시간입니다

poke는 step 사이에 실행되고, treadle은 이를 실행 시점의 시간에 기록합니다. 그 시간은 상승 에지와 상승 에지의 한가운데입니다. 뷰어는 header 필드가 아니라 변화 기록에서 시작 시간을 계산하며, 이 VCD의 첫 변화가 시간 0이므로 시간 0이 왼쪽 끝에 놓입니다.

파형은 테스트벤치가 텍스트로 출력한 것과 같은 시뮬레이션을 이름 있는 모든 신호에 걸쳐 보여 줍니다. 텍스트에는 없던 리셋 사이클과, poke와 에지 사이의 반 주기 간격도 파형에서 확인할 수 있습니다. 입력 변화는 그려진 시점에서 읽고 레지스터 변화는 상승 에지에서 읽으세요. 첫 poke 전의 활동은 리셋 사이클입니다.

직접 바꿔 보세요.

  1. val wrap을 지우고, wrap을 쓰던 두 곳에 count === 5.U를 직접 써 보세요. 회로 동작은 같지만 VCD 신호 목록에서 wrap이 사라집니다. 비교 노드는 여전히 있지만 VCD가 생략하는 생성 이름을 받습니다.
  2. Seq(WriteVcdAnnotation) 대신 Seq()를 전달해 보세요. 테스트는 같은 여덟 줄을 출력하지만 VCD가 생성되지 않으므로, 존재 검사가 java.lang.RuntimeException: VCD not found: test_run_dir/tb/Blinker.vcd로 실행을 실패시킵니다.
  3. annotation은 되살리고 "tb" 인자를 지워 보세요. VCD가 test_run_dir/chisel_test_1792139124602/ 같은 디렉터리에 생성되어 고정 경로와 맞지 않으므로 같은 VCD not found 오류가 발생합니다.

8. 자주 하는 실수

8.1 테스트 본문 안에서 VCD 복사하기

// 잘못된 코드: 본문 실행 중에는 파일이 아직 없습니다
test(new Blinker, Seq(WriteVcdAnnotation), "tb") { dut =>
  dut.clock.step(8)
  Files.copy(vcd, Paths.get("dump.vcd"))
}

// 바른 코드: test가 반환된 뒤 복사합니다
test(new Blinker, Seq(WriteVcdAnnotation), "tb") { dut =>
  dut.clock.step(8)
}
Files.copy(vcd, Paths.get("dump.vcd"), StandardCopyOption.REPLACE_EXISTING)

시뮬레이터는 테스트가 끝날 때 VCD를 기록합니다. 본문이 실행되는 동안 test_run_dir/tb/Blinker.vcd 경로에는 파일이 없습니다.

8.2 테스트 이름 빠뜨리기

// 잘못된 코드: 실행할 때마다 디렉터리 이름이 바뀝니다
test(new Blinker, Seq(WriteVcdAnnotation)) { dut => ... }

// 바른 코드: 고정된 디렉터리
test(new Blinker, Seq(WriteVcdAnnotation), "tb") { dut => ... }

이름이 없으면 VCD는 test_run_dir/chisel_test_<밀리초>/에 생성되므로 고정된 복사 경로로 찾을 수 없습니다.

8.3 이름 없는 식이 파형에 나오기를 기대하기

// 잘못된 코드: 비교 결과에 이름이 없어 VCD에 기록되지 않습니다
when (io.en && count === 5.U) { led := !led }

// 바른 코드: val에 담습니다
val toggle = io.en && count === 5.U
when (toggle) { led := !led }

VCD에는 이름 있는 값만 기록됩니다. val에 담으면 toggle이 count, led와 함께 VCD에 나타납니다.

8.4 poke를 레지스터 변화로 읽기

dut.io.en.poke(true.B)   // io_en은 지금, 에지 사이에서 바뀝니다
dut.clock.step()         // count는 이 상승 에지에서 바뀝니다

poke는 실행된 순간에 기록되므로 상승 에지와 반 사이클 떨어져 있습니다. 입력은 바뀐 시점에서, 레지스터 변화는 상승 에지에서 읽으세요. 첫 poke 전의 변화는 chiseltest의 리셋 사이클입니다.

8.5 확인 없이 복사하기

// 잘못된 코드: 파일이 없으면 이유를 알려 주지 않는 NoSuchFileException이 발생합니다
Files.copy(vcd, Paths.get("dump.vcd"))

// 바른 코드: 무엇이 없는지 알립니다
if (!Files.exists(vcd)) throw new RuntimeException(s"VCD not found: $vcd")
Files.copy(vcd, Paths.get("dump.vcd"), StandardCopyOption.REPLACE_EXISTING)

명시적인 검사는 기대한 경로를 메시지에 담으므로 빠진 annotation이나 틀린 테스트 이름을 바로 찾을 수 있습니다.

9. 적용: 에지를 세고, 파형 둘을 읽기

아래 채점 실습 문제의 카운터는 in이 1인 사이클마다 1을 더합니다. 0에서 1로 바뀔 때마다 한 번만 더하게 바꾸세요. rise에는 이전 입력이 필요하고, 그것은 3절과 같이 리셋 값이 false.B인 RegNext입니다. 리셋이 끝날 때 입력이 이미 1이면 에지 하나로 셉니다. 레지스터가 이전 입력을 0이라고 말하기 때문입니다. 실행을 누르기 전에 사이클별 표를 먼저 써 보세요.

vcd 예제 아래의 실습 문제 둘은 파형을 읽어 해결합니다. 첫 문제의 PWM 생성기는 출력이 1이 되지 않습니다. duty 레지스터를 주기가 끝나는 에지에서만 갱신하고, 파형의 count와 level로 그 시점을 확인해 보세요. 두 번째 문제의 tick 분주기는 일시 정지 뒤 tick이 늦게 나옵니다. en이 0인 동안 phase가 어떻게 바뀌는지 파형에서 찾아보세요.

10. 정리

요소뜻
RegNext(x, init)에지 하나 뒤의 x. 리셋 뒤에는 init. init이 없으면 리셋도 정해진 시작 값도 없음
RegNext 둘을 연결에지 둘의 지연
x && !RegNext(x, false.B)상승 에지 펄스. 0에서 1로 바뀔 때마다 한 사이클. !x && 이전_값은 하강 에지
레지스터로 낸 출력지연이 한 사이클 늘어남. 인터페이스마다 정하고 기록
VCD신호 선언 header 뒤에 시간별 값 변화 기록이 이어지는 텍스트 파일
WriteVcdAnnotationtest의 annotation 목록에 전달하면 <최상위 모듈>.vcd를 기록
위치테스트 이름이 있으면 test_run_dir/<이름>/, 없으면 시간값이 붙은 디렉터리
실습 환경 뷰어작업 디렉터리의 dump.vcd를 표시. test 반환 뒤에 복사
기록되는 신호포트, clock, reset, val로 이름 붙인 레지스터와 wire. 이름 없는 임시 노드는 생략
시간축리셋 중 상승 에지 하나, 10 시간 단위마다 상승 에지, 에지 사이의 poke

11. 확인 문제

  1. 에지 예제의 사이클 2에서 in이 여전히 1인데 rise가 0인 이유는 무엇일까요?

    정답 보기

    사이클 1의 끝 에지에서 last가 1을 저장했습니다. 사이클 2에서는 in과 last가 모두 1이므로 io.in && !last가 거짓입니다.

  2. 테스트벤치가 테스트 본문 안이 아니라 test 반환 뒤에 VCD를 복사하는 이유는 무엇일까요?

    정답 보기

    시뮬레이터는 테스트가 끝날 때 VCD 파일을 기록합니다. 본문이 실행되는 동안에는 파일이 아직 없습니다.

  3. 파형에서 리셋 사이클에 속하는 상승 에지는 어느 것이며, 그 에지를 CYCLE 1로 세지 않는 이유는 무엇일까요?

    정답 보기

    reset이 1인 시간 1의 에지입니다. chiseltest가 테스트 본문 실행 전에 그 사이클을 수행하며, CYCLE 번호는 본문의 step만 셉니다.

  4. io_en이 상승 에지가 아니라 시간 6에 바뀌는 이유는 무엇일까요?

    정답 보기

    poke는 step 사이에 실행되고 treadle은 실행 시점의 시간에 기록합니다. 리셋 사이클이 끝난 시점의 시간은 6이며, 시간 1과 11의 상승 에지 한가운데입니다.

  5. 조건 io.en && wrap을 파형에 toggle이라는 이름으로 나타내려면 무엇을 바꿔야 할까요?

    정답 보기

    val toggle = io.en && wrap처럼 val에 담고 when에서 toggle을 사용합니다. 값이 toggle이라는 이름을 얻으므로 VCD에 기록됩니다.

  6. 실행이 성공하고 모든 줄이 출력되었는데 파형이 표시되지 않습니다. 무엇을 먼저 확인해야 할까요?

    정답 보기

    작업 디렉터리에 dump.vcd가 만들어졌는지 확인합니다. 테스트벤치는 test가 반환된 뒤 VCD를 그 위치로 복사해야 합니다. 복사 전에 존재 검사를 두면 파일이 없을 때 명확한 실패로 알 수 있습니다.

확인 지점

실습 문제를 제출해 채점을 받습니다

채점은 서버의 비공개 테스트로 진행되며, 결과는 이 브라우저에만 기록됩니다.

참고 자료

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