RTL log #1 : Half Adder 설계부터 검증까지 — iverilog 실습

YOUNGWOONG HAN·2026년 2월 23일

RTL log

목록 보기
1/3
post-thumbnail

이번 포스팅에서는 가장 기초적인 Half Adder를 통해 RTL 설계의 기본기와 일반적인 검증 워크플로우를 정리해 보겠습니다.

Half Adder를 첫 예제로 고른 이유는 간단합니다. 로직 자체가 단순해서 설계보다 "내가 만든 게 정말 맞게 동작하는지 어떻게 확인하지?" 라는 검증의 흐름에 집중하기 좋기 때문입니다.
처음 RTL을 접했을 때 Verilog 문법이나 시뮬레이터 사용법부터 막막하게 느껴졌습니다.
이 글이 같은 고민을 하고 있는 분들께 조금이나마 도움이 됐으면 좋겠습니다.

RTL 설계 및 검증 프로세스

RTL(Register Transfer Level) 설계는 단순히 코드를 짜는 것에 그치지 않고, 의도한 대로 동작하는지 증명하는 과정이 핵심입니다. 일반적인 순서는 다음과 같습니다.

1. RTL 작성 (Design): 하드웨어 로직 구현

2. 테스트벤치 작성 (Testbench): 입력 시나리오 및 검증 로직 작성

3. 컴파일 (Compilation): 설계 및 테스트벤치 코드의 문법 체크 및 실행 파일 생성

4. 검증 (Verification): 파형(Waveform) 분석 또는 로그 확인

1. RTL 작성 (Design): Half Adder

반가산기는 가장 기본적인 산술 연산 블록으로, 2진수 1비트 두 개를 더해 합(Sum)과 올림수(C_out)를 출력합니다.

`timescale 1ns / 1ps

module half_adder (
    input  a, b,
    output c_out, sum
);
  // XOR 게이트를 이용한 Sum 구현
  xor g1 (sum, a, b);
  // AND 게이트를 이용한 Carry 구현
  and g2 (c_out, a, b);
endmodule

2. 테스트벤치(Testbench) 작성

테스트벤치는 설계한 모듈(DUT, Design Under Test)에 가상의 입력을 넣어주는 실험 환경입니다. 여기서는 두 가지 방식을 비교합니다.

A. Directed Testbench (수동 검증)

입력값을 하나하나 직접 인가하고, 터미널의 로그나 파형을 보고 눈으로 직접 결과를 확인하는 방식입니다.

  • 장점: 직관적이며 소규모 회로의 동작을 빠르게 확인할 때 유리합니다.

  • 단점: 입력 조합이 많아질수록 누락되는 케이스가 생기기 쉽고, 하나하나 확인하기 번거롭습니다.

// directed testbench for half adder
`timescale 1ns / 1ps

module test_half_adder ();
  reg t_a, t_b;
  wire t_c_out, t_sum;
  half_adder uut (
      .a(t_a),
      .b(t_b),
      .c_out(t_c_out),
      .sum(t_sum)
  );

  initial #100 $finish;
  initial begin
    $dumpfile("sim/build/output/iverilog/test_half_adder.vcd");
    $dumpvars(0, test_half_adder);
    $display("a b | c_out sum");
    $display("-------------");
    t_a = 0;
    t_b = 0;
    #10;
    $display("%b %b | %b     %b", t_a, t_b, t_c_out, t_sum);
    t_a = 0;
    t_b = 1;
    #10;
    $display("%b %b | %b     %b", t_a, t_b, t_c_out, t_sum);
    t_a = 1;
    t_b = 0;
    #10;
    $display("%b %b | %b     %b", t_a, t_b, t_c_out, t_sum);
    t_a = 1;
    t_b = 1;
    #10;
    $display("%b %b | %b     %b", t_a, t_b, t_c_out, t_sum);

  end
endmodule

B. Self-checking Testbench (자동 검증)

입력값을 랜덤으로 인가하거나 모든 조합을 돌리면서, Reference Model(정답지)과 실제 출력값을 비교하여 자동으로 PASS/FAIL을 판정하는 방식입니다.

  • 눈으로 직접 확인하는 대신, 코드 내부의 if문과 ref_model이 결과를 실시간으로 대조합니다.
  • repeat 문을 통해 수천 번의 랜덤 테스트를 수행할 수 있어, 설계자가 예상치 못한 코너 케이스(Corner Case)를 잡기에 유리합니다.
  • seen 변수 등을 활용해 모든 입력 조합이 한 번씩은 들어갔는지(Coverage)를 확인할 수 있습니다.

보통 6개의 블럭으로 구성됩니다.

  1. DUT 연결 (u_dut)
  2. 입력 인가 (apply stimulus)
  3. 기대값 계산 (reference model)
  4. 자동 비교 (if (got !== exp))
  5. 에러 카운트/최종 PASS-FAIL
  6. 타임아웃(TIMEOUT) 종료시스템 베릴로그 문법을 사용하여 작성했습니다.
// self-checking testbench for half_adder
`timescale 1ns / 1ps
module test_half_adder;
  reg t_a, t_b;
  reg [1:0] ref_out;
  wire t_c_out, t_sum;
  integer i;
  integer err_cnt;
  reg [3:0] seen;  // 입력 조합 관측 체크용
// 1. DUT 연결
  half_adder u_dut (
      .a(t_a),
      .b(t_b),
      .c_out(t_c_out),
      .sum(t_sum)
  );

  // 3. reference model
  function [1:0] ref_half_adder;
    input a_i, b_i;
    begin
      ref_half_adder = a_i + b_i;  // {c_out, sum}
    end
  endfunction

  initial begin
    err_cnt = 0;
    seen    = 4'b0000;

    // ── 2. 입력 인가 ──
    repeat (50) begin
      t_a = $urandom_range(0, 1);
      t_b = $urandom_range(0, 1);
      #1;
      seen[{t_a, t_b}] = 1'b1;
      // ── 4. 자동 비교: 랜덤 50회 ──
      ref_out = ref_half_adder(t_a, t_b);
      if ({t_c_out, t_sum} !== ref_out) begin
        $error("Mismatch: a=%0b b=%0b got=%0b%0b exp=%0b%0b", t_a, t_b, t_c_out, t_sum, ref_out[1],
               ref_out[0]);
        err_cnt = err_cnt + 1;
      end
    end

    // ── 미커버 조합 강제 인가 (전수 커버리지 보장) ─────────
    for (i = 0; i < 4; i = i + 1) begin
      if (!seen[i]) begin
        {t_a, t_b} = i[1:0];
        #1;
        seen[{t_a, t_b}] = 1'b1;
        ref_out = ref_half_adder(t_a, t_b);
        if ({t_c_out, t_sum} !== ref_out) begin
          $error("Mismatch: a=%0b b=%0b got=%0b%0b exp=%0b%0b", t_a, t_b, t_c_out, t_sum,
                 ref_out[1], ref_out[0]);
          err_cnt = err_cnt + 1;
        end
      end
    end

    // ── 5. 최종 판정 ──
    if (seen !== 4'b1111) $fatal(1, "FAIL: not all input combos covered, seen=%b", seen);

    if (err_cnt == 0) $display("PASS: self-checking random TB");
    else $fatal(1, "FAIL: err_cnt=%0d", err_cnt);

    $finish;
  end

  initial begin
    #1000;
    $fatal(1, "TIMEOUT");
  end

endmodule

3. 컴파일 (Compilation): iverilog 가이드

컴파일 단계에서는 iverilog를 사용합니다. 각 옵션의 의미를 정확히 아는 것이 중요합니다.

// iverilog 컴파일 명령어 예시
iverilog -g2012 -Wall -s test_half_adder -o sim/build/output/iverilog/test_half_adder.vvp \
rtl/half_adder.v sim/test/test_half_adder.v

옵션 상세 설명
-g2012: iverilog가 사용할 Verilog 표준을 지정합니다.
g2005, g2001 등 여러 버전이 있는데, -g2012는 IEEE 1800-2012(SystemVerilog) 표준으로 $urandom_range 같은 최신 시스템 함수를 사용하려면 이 옵션이 필요합니다.
명시하지 않으면 iverilog 기본값으로 컴파일되어 최신 문법에서 에러가 날 수 있습니다.

-Wall: 모든 경고(Warning) 메시지를 출력합니다. 사소한 문법 실수나 포트 연결 누락처럼 에러는 아니지만 잠재적으로 문제가 될 수 있는 부분을 알려줍니다.

-s test_half_adder: 시뮬레이션의 최상위 모듈(Top-level module)을 지정합니다. 보통 테스트벤치 모듈 이름인 test_half_adder가 들어갑니다.

-o [출력파일경로]: 컴파일 결과로 생성될 실행 파일의 경로와 이름을 지정합니다. iverilog의 출력물은 .vvp 확장자를 사용하며, 이후 vvp 명령어로 실행합니다. 경로를 지정하지 않으면 현재 디렉토리에 a.out으로 생성됩니다.

소스 파일 나열: 마지막에는 컴파일에 필요한 모든 .v 파일을 나열합니다. 설계 파일(half_adder.v)을 먼저, 테스트벤치 파일(test_half_adder.v)을 그 뒤에 적는 것이 일반적입니다. 파일을 빠뜨리면 "module not found" 에러가 발생하니 주의하세요.

4. 검증 (Verification)

컴파일이 성공했다면 vvp 명령어로 시뮬레이션을 실행합니다.

// 시뮬레이션 실행
vvp sim/build/output/iverilog/test_half_adder.vvp

// 출력 예시
PASS: self-checking random TB

결과 분석 방법

1. Directed Testbench 방식

  • 터미널에 출력된 진리표로 직접 확인합니다.
  • 혹은 아래 명령어로 파형을 직접 확인할 수도 있습니다.

// gtkwave를 이용하여 파형 확인
gtkwave sim/build/output/iverilog/test_half_adder.vcd

2. Self-checking Testbench 방식

아래 메시지가 뜨면 모든 테스트를 통과한 것입니다.

PASS: self-checking random TB


만약 실패했다면 error와 $fatal 메시지가 함께 출력됩니다. error는 어떤 입력값에서 출력이 틀렸는지 알려주고, $fatal은 최종적으로 몇 개의 오류가 발생했는지 요약해줍니다. 로그를 위에서부터 읽으면서 첫 번째 ERROR가 찍힌 시점을 찾는 것이 디버깅의 시작입니다.

ERROR: Mismatch: a=1 b=1 got=01 exp=10
FAIL: err_cnt=1

마무리하며

이번 포스팅에서는 Half Adder를 통해 RTL 설계부터 iverilog를 이용한 컴파일, 검증까지 전체 흐름을 정리해봤습니다. 단순한 회로지만 막상 직접 테스트벤치를 짜고 시뮬레이션을 돌려보면 생각보다 알아야 할 것이 많다고 느꼈습니다.

다음 포스팅에서는 베릴레이터를 이용하여 64bit adder를 검증해 볼 예정입니다.

profile
electronic engineering student

0개의 댓글