RTL to GDS: Design Verification

RTL to GDS: Design Verification
Photo by Agence Olloweb / Unsplash

1. 서론: 설계의 의도를 수학적, 논리적으로 증명하는 엔지니어링

반도체 설계의 거대한 흐름, 즉 RTL to GDSII Flow의 관점에서 볼 때, 우리는 지난 단계에서 Verilog Coding Style과 Linting을 통해 코드의 구조적 건전성과 문법적 오류를 바로잡는 과정을 거쳤습니다.

이제 우리는 설계의 심장이자 가장 많은 시간과 자원이 투입되는 단계인 Design Verification의 영역으로 진입합니다.

Verification은 단순히 RTL 코드가 "돌아가는지" 확인하는 테스트 과정이 아닙니다. 그것은 설계자가 의도한 Architecture Specification이 RTL이라는 구현체로 정확하게 변환되었는지, Logic Synthesis 등 뒷 단계에 논리적 문제가 없을지 를 증명하는 과정입니다.

현대의 SoC(System on Chip) 설계에서 Verification은 RTL 설계에 들어가는 시간보다 커질 정도로 비중이 크며, 이는 칩이 제작된 후 발견되는 버그(Silicon Bug)를 수정하는 비용이 RTL 단계에서 수정하는 비용의 수천 배에 달하기에, 매우 중요한 단계이고, 정말 많은 채용이 있습니다.

따라서 Verification Engineer의 목표는 단순히 버그를 찾는 것을 넘어, "버그가 없다"는 것을 통계적이고 논리적인 지표를 통해 확신시키는 데 있습니다.


2. 검증 방법론의 패러다임 전환: Directed에서 Constrained Random으로

수십 년 전, 수백개의 게이트 수준의 디자인을 검증하던 시절에는 엔지니어가 예상 가능한 시나리오를 하나하나 코딩하는 Directed Testing 방식이 주를 이루었습니다. 하지만 VLSI 규모의 복잡성을 가진 현대의 디자인에서 인간의 예측 능력에만 의존하는 것은 불가능에 가깝습니다. 이러한 한계는 검증 방법론의 근본적인 변화를 가져왔습니다.

2.1 Directed Testing: 직관적이지만 한계가 명확한 접근

Directed Testing은 검증 엔지니어가 Feature의 동작 여부를 확인하기 위해,

검증자가 직접 Stimulus input과 Expected Output을 명시적으로 작성하는 방식입니다. 예를 들어, 프로세서 검증에서 "A 명령어 다음에 B 명령어를 실행하고 레지스터 값이 C인지 확인하라"는 식의 테스트가 이에 해당합니다.

이 방식의 장점은 명확합니다. 테스트의 의도가 분명하므로 디버깅이 용이하고, 초기 Bring-up 단계에서 기본적인 기능 동작 여부를 빠르게 확인하는 데 유리합니다.

그러나 치명적인 단점은 "엔지니어가 생각하지 못한 시나리오"는 절대 검증할 수 없다는 점입니다. 복잡한 Pipeline Stall, 비동기 인터페이스 간의 Race Condition, 예외적인 에러 처리 등은 인간의 머리로 모든 조합을 나열하기 어렵습니다. 또한 디자인 사양이 변경될 때마다 수동으로 작성된 테스트 케이스들을 일일이 수정해야 하므로 유지보수 비용이 기하급수적으로 증가합니다.

2.2 Constrained Random Verification (CRV)

이러한 Directed Testing의 한계를 극복하기 위해 등장한 것이 Constrained Random Verification (CRV)입니다.

CRV는 SystemVerilog의 객체 지향 프로그래밍 기능과 Randomization 기능을 활용하여, State Space을 무작위로 탐색하되 유효한 범위 내에서 Constraint를 두어 testbench를 생성하여 검증하는 방식입니다.

CRV의 핵심은 랜덤, 자동화, 탐색입니다.

엔지니어는 개별 테스트 벡터를 작성하는 대신, Data packet의 구조와 프로토콜 규칙(Constraints)만을 정의합니다. 시뮬레이터는 randomize() 함수를 호출할 때마다 매번 다른 데이터, 주소, 딜레이, 제어 신호 조합을 생성해냅니다. 예를 들어, constraint valid_addr { addr < 1024; }와 같은 제약 조건을 통해 유효한 주소 공간 내에서 무작위 접근을 시도하게 합니다.

사람이 미처 생각하지 못한 Corner Case, 예를 들어 "FIFO가 가득 찬 상태에서 갑자기 Reset이 들어오고 동시에 인터럽트가 발생하는 상황"과 같은 복잡한 시나리오를 시뮬레이터가 생성해냅니다.


3. UVM (Universal Verification Methodology)

CRV를 효과적으로 구현하기 위해서는 견고하고 재사용 가능한 프레임워크가 필요합니다. 이것이 바로 UVM (Universal Verification Methodology)이 탄생한 배경이며, 현재 업계 표준으로 자리 잡은 이유입니다.

많은 주니어 엔지니어들이 UVM의 복잡한 클래스 계층 구조와 방대한 매크로 앞에서 좌절하곤 합니다. 하지만 각 컴포넌트의 존재 이유와 역할(Role)을 이해하면, UVM은 매우 논리적인 구조임을 알 수 있습니다.

3.1 UVM Testbench의 철학: 관심사의 분리 (Separation of Concerns)

UVM의 핵심 철학은 검증 환경을 기능별로 철저히 모듈화하는 것입니다. 즉, 자극을 생성하는 역할(Generation), 신호를 구동하는 역할(Driving), 신호를 관찰하는 역할(Monitoring), 그리고 정답을 판별하는 역할(Checking)을 서로 다른 컴포넌트로 분리합니다. 이렇게 함으로써 특정 프로토콜(예: PCIe)을 위한 검증 환경을 만들었을 때, 이를 다른 프로젝트에서도 쉽게 떼어다 쓸 수 있는 재사용성(Reusability)을 확보하게 됩니다.

3.2 핵심 구성 요소 상세 분석

3.2.1 Sequence Item (Transaction): 추상화의 시작

검증의 가장 기초 단위는 uvm_sequence_item입니다. 이는 핀(Pin) 레벨의 신호(0과 1)들을 의미 있는 정보의 덩어리인 'Transaction'으로 추상화한 객체입니다. 예를 들어, AXI 버스에서 수많은 제어 신호가 오고 가지만, Transaction 레벨에서는 Address, Data, Read/Write, Burst Length 등의 속성만으로 표현됩니다. 이를 통해 검증 엔지니어는 복잡한 타이밍에서 벗어나 데이터의 흐름 자체에 집중할 수 있습니다.

3.2.2 Sequencer: 데이터 흐름의 관제탑

uvm_sequencer는 생성된 Sequence Item들을 Driver에게 전달하는 중개자 역할을 합니다. 단순히 데이터를 넘겨주는 것을 넘어, 여러 Sequence가 동시에 실행될 때 어떤 Sequence의 Item을 먼저 보낼지 결정하는 중재(Arbitration) 역할을 수행합니다. 예를 들어, 일반 데이터 전송 Sequence와 긴급 인터럽트 Sequence가 동시에 돌아갈 때, Sequencer는 우선순위(Priority) 설정에 따라 인터럽트 관련 Item을 먼저 Driver에게 보낼 수 있습니다. 이는 실제 하드웨어의 버스 아비터(Arbiter)와 유사한 개념입니다.

3.2.3 Driver: 추상과 실재의 교량

uvm_driver는 Sequencer로부터 받은 추상적인 Transaction 패킷을 실제 DUT(Design Under Test)가 이해할 수 있는 핀 레벨 신호로 변환하는 역할을 합니다. 이 과정에서 TLM (Transaction Level Modeling) 인터페이스가 사용됩니다. Driver는 seq_item_port.get_next_item(req)을 통해 Item을 요청하고, 내부 로직을 통해 virtual interface를 제어하여 클럭 엣지에 맞춰 신호를 토글링(Toggling)합니다. Driver는 오직 '입력'에만 관여하며, DUT로부터 나오는 출력 데이터를 판단하거나 검사하지 않습니다. 이는 철저히 Monitor와 Scoreboard의 몫입니다.

3.2.4 Monitor: 검증의 눈 (Observer)

uvm_monitor는 DUT의 인터페이스 신호를 수동적으로 관찰(Passive Monitoring)하는 컴포넌트입니다. DUT에 어떠한 신호도 인가하지 않으며, 오직 virtual interface를 통해 신호 변화를 감지합니다. Monitor는 핀 레벨의 신호 변화를 감지하여 다시 Transaction 형태(uvm_sequence_item)로 재구성(Reassembly)합니다. 이렇게 재구성된 정보는 analysis_port를 통해 Scoreboard나 Coverage Collector와 같은 가입자(Subscriber)들에게 브로드캐스팅됩니다. 또한, Monitor 내부에는 기본적인 프로토콜 위반 여부를 검사하는 Assertion Checker가 포함되기도 합니다.

3.2.5 Agent: 프로토콜의 캡슐화

uvm_agent는 위에서 설명한 Sequencer, Driver, Monitor를 하나로 묶는 컨테이너입니다. Agent는 특정 인터페이스 프로토콜(예: AHB, AXI, UART)을 처리하는 검증 IP(VIP)의 단위가 됩니다. Agent는 is_active라는 설정 변수에 따라 두 가지 모드로 동작합니다 :

  • Active Mode: Sequencer와 Driver를 모두 생성하여 DUT에 적극적으로 Stimulus를 인가합니다.
  • Passive Mode: 오직 Monitor만 생성하여 버스 신호를 관찰하기만 합니다. 이는 칩 내부의 서브 시스템을 검증하거나, 이미 다른 마스터가 버스를 드라이빙하고 있을 때 시스템 모니터링 용도로 사용됩니다.

3.2.6 Scoreboard: 판관 (Checker)

uvm_scoreboard는 검증의 핵심인 Pass/Fail을 판정하는 곳입니다. Monitor로부터 수집된 실제 DUT의 출력(Actual Output)과, 내부적으로 구현된 Reference Model(Golden Model)의 예상 출력(Expected Output)을 비교합니다. Reference Model은 보통 C/C++이나 SystemVerilog로 작성되며, RTL과 달리 타이밍 정보 없이 기능적인 동작만을 빠르게 수행하도록 모델링됩니다. 비교 방식은 순서가 중요한 In-order 체크와 순서가 뒤바뀔 수 있는 Out-of-order 체크로 나뉘며, 이는 DUT의 특성에 따라 결정됩니다.


4. SystemVerilog Assertions (SVA): 실시간 안전장치

UVM 테스트벤치가 칩의 전반적인 기능과 데이터 흐름을 검증한다면, SystemVerilog Assertions (SVA)는 RTL 내부의 신호 레벨에서 미세한 동작 규칙과 타이밍을 실시간으로 감시하는 CCTV와 같습니다. SVA는 버그가 발생한 순간, 즉시 시뮬레이션을 중단하거나 에러 메시지를 출력함으로써 디버깅 시간을 획기적으로 단축시켜 줍니다.

4.1 Immediate vs. Concurrent Assertions: 언제 검사하는가?

SVA는 평가 시점에 따라 크게 두 가지 유형으로 나뉩니다.

4.1.1 Immediate Assertions (즉시 어서션)

Immediate Assertion은 Verilog의 절차적 블록(always, initial, task, function) 내부에서 사용되며, if-else 문과 유사한 실행 Semantics을 가집니다. 시뮬레이션 이벤트가 발생하여 해당 구문이 실행되는 즉시 Expression을 평가합니다.

  • 활용: 변수의 초기화 상태 확인, 함수 입력 Argument의 유효성 검사, Combinational Logic의 상태 검사 등에 주로 사용됩니다.
  • 예시: assert (packet_len > 0) else $error("Packet length must be positive");

4.1.2 Concurrent Assertions

Concurrent Assertion은 Clock Edge에 동기화되어 평가됩니다. 이는 시간의 흐름에 따른 시퀀스Sequence of events를 검증하는 데 매우 강력합니다. "Request 신호가 뜨면 3 사이클 뒤에 반드시 Grant가 떠야 한다"와 같은 Temporal Logic을 표현하는 데 최적화되어 있습니다.

  • 문법: propertysequence 키워드를 사용하여 복잡한 타이밍 관계를 정의합니다. 연산자 |-> (Overlapping implication)와 |=> (Non-overlapping implication)를 통해 전제 조건(Antecedent)과 결과(Consequent)의 관계를 명시합니다.
  • 예시: property req_gnt_prop; @(posedge clk) req |-> ##[1:3] gnt; endproperty (Request가 High이면 1~3 사이클 내에 Grant가 High여야 함을 검증).

4.2 SVA의 배치 전략: Interface vs. Bind

SVA를 어디에 구현할 것인가는 코드의 가독성과 재사용성, 그리고 합성(Synthesis) 영향도에 큰 영향을 미칩니다.

4.2.1 In-line SVA (RTL 내부 삽입)

RTL 디자이너가 코드 작성 시점에 직접 모듈 내부에 Assertion을 삽입하는 방식입니다. 설계 의도를 가장 잘 아는 디자이너가 즉각적으로 제약을 걸 수 있다는 장점이 있지만, 검증 코드가 RTL 코드와 섞여 가독성을 해칠 수 있고, 합성 시에는 synthesis translate_off 프라그마 등을 통해 제외시켜야 하는 번거로움이 있습니다.

4.2.2 Interface SVA

SystemVerilog의 interface 구문 내에 어서션을 포함시키는 방식입니다. 버스 프로토콜과 같이 여러 모듈에서 공통적으로 사용되는 인터페이스 규칙을 검증할 때 매우 효율적입니다. 인터페이스만 연결하면 해당 버스를 사용하는 모든 모듈에 대해 자동으로 프로토콜 검증이 수행되므로 재사용성이 뛰어납니다.

4.2.3 Bind Construct

검증 엔지니어가 RTL 코드를 전혀 수정하지 않고, 별도의 SVA 모듈을 작성하여 RTL 인스턴스에 동적으로 갖다 붙이는 방식입니다. bind 키워드를 사용하며, 이는 RTL(Design)과 Verification의 관심사를 완벽하게 분리할 수 있게 해줍니다. 특히 타사 IP나 수정 권한이 없는 레거시 코드를 검증할 때(Black-box Verification) 필수적인 기법입니다.


5. Coverage-Driven Verification (CDV)

"언제 검증을 끝낼 것인가?"는 모든 프로젝트 매니저와 엔지니어의 난제입니다. 감(Gut feeling)에 의존하던 과거와 달리, 현대의 검증은 Coverage-Driven Verification (CDV) 방법론을 따릅니다. 즉, 수치화된 Coverage Metric가 목표치에 도달했을 때를 검증 완료 시점으로 봅니다.

5.1 Coverage Metrics: 검증의 척도

커버리지는 크게 툴이 자동으로 추출해 주는 Code Coverage와 사용자가 정의하는 Functional Coverage로 나뉩니다.

5.1.1 Code Coverage (코드 커버리지)

RTL 코드가 시뮬레이션 중에 얼마나 실행되었는지를 나타냅니다. 100% 달성이 기본 목표입니다.

  • Line/Block Coverage: 모든 코드 줄과 블록이 최소 한 번 이상 실행되었는가?
  • Toggle Coverage: 모든 Signal와 레지스터 비트가 0에서 1로, 1에서 0으로 변환되었는가? 이는 연결이 끊긴(Stuck-at) 신호를 찾는 데 유용합니다.
  • FSM Coverage: FSM의 모든 State를 방문했고, 가능한 모든 Transition 경로를 거쳤는가?
  • Expression/Condition Coverage: if (A && B)와 같은 조건문에서 A와 B의 모든 True/False 조합이 테스트되었는가?

5.1.2 Functional Coverage (기능 커버리지)

Code Coverage가 100%라도 기능이 완벽하다고 볼 수 없습니다. 코드는 실행되었지만, 의미 있는 기능적 시나리오가 빠져 있을 수 있기 때문입니다. Functional Coverage는 엔지니어가 covergroupcoverpoint를 통해 명시적으로 정의한 시나리오의 달성 여부를 측정합니다.

  • Cross Coverage: 두 개 이상의 변수 조합을 확인합니다. 예를 들어, "패킷 타입이 Video이면서, 동시에 버퍼가 Full인 상태"와 같은 복합적인 상황을 정의합니다.
  • Bins: 변수가 가질 수 있는 값의 범위를 나누어(Binning), 특정 범위의 값들이 모두 발생했는지 확인합니다.

5.2 Waiver Strategy: 불가능과 무의미의 배제

현실적으로 모든 디자인 코드의 커버리지 100%를 달성하는 것은 불가능하거나 비효율적일 때가 있습니다. 이때 Waiver 프로세스가 필요합니다. Waiver는 단순히 커버리지 수치를 높이기 위한 속임수가 아니라, 검증되지 않은 부분에 대한 엔지니어링적 Justification을 부여하는 과정입니다.

Waiver 유형설명 및 예시
Unreachable Code하드웨어적으로 도달 불가능한 로직. 예) 안전 모드에서만 활성화되는 방어 코드(Dead Code), 파라미터 설정에 의해 합성 시 제거되는 코드.
Unused FeaturesIP에는 존재하지만 현재 SoC 프로젝트에서는 사용하지 않기로 결정된 기능(Tie-off 된 포트 등).
Low Priority일정상 검증 우선순위가 낮고 리스크가 극히 적다고 판단되어 PM 및 아키텍트의 승인 하에 제외된 기능.

Waiver는 텍스트 파일이나 툴의 GUI를 통해 관리되며, RTL 코드가 수정될 때마다 기존 Waiver가 여전히 유효한지 반드시 재검토해야 합니다. 잘못된 Waiver는 치명적인 버그를 숨기는 구멍이 될 수 있습니다.

CDC, RDC는 다른 글에서 따로 다루겠습니다.

결론: Synthesis를 향한 관문, Sign-off criteria for Design Verification

우리는 긴 여정을 통해 RTL 코드가 설계 의도대로 동작함을 증명했습니다. Verification 단계가 공식적으로 완료되기 위해서는 다음의 세 가지 Sign-off Criteria가 충족되어야 합니다.

  1. Regression Clean: 정의된 모든 Testsuite가 실패 없이 통과(Pass)했습니다.
  2. Coverage Closure: Code Coverage와 Functional Coverage가 목표치(통상 100% 또는 합의된 수치)에 도달했으며, 미달성 항목에 대해서는 합당한 Waiver 승인이 완료되었습니다.
  3. CDC/Lint Clean: 정적 분석 툴을 통해 Clock Domain Crossing 문제와 RTL 코딩 스타일 위반 사항이 모두 해결되었습니다.

이 모든 조건이 만족되었을 때 비로소 RTL 코드는 Freeze 선언이 내려집니다.

이제 이 RTL 코드는 더 이상 수정되지 않으며, 다음 연재에서 다룰 Logic Synthesis 단계로 넘어가 게이트 레벨의 Netlist로 변환될 준비를 마칩니다.

실제로 ASIC 프로젝트에서 가장 중요한 단계 Top3 중 첫번째가 RTL Freeze를 언제 하는가 입니다. 이걸 빨리 해야, 뒷단 설계가 빨리 끝나고, 예정된 제조공정 날짜에 반도체 설계도를 Tape-out 할 수 있습니다.

Verification은 단순한 테스트가 아닌, 칩의 품질과 신뢰성을 담보하는 최후의 보루이자 가장 중요한 엔지니어링 과정임을 기억하십시오.

Enjoyed this article?

Get deep-dive semiconductor analysis and career insights delivered weekly. Free forever — no paywall, no upsell. Funded by sponsorships with a strict editorial firewall (Editorial Standards).

Work with me

Consulting · Collaboration · Support

Paid 1:1 technical consulting, speaker invitations, collaboration proposals, or just want to say thanks — all welcome.

View options →
VLSI Korea Free forever · No paywall · Weekly semiconductor insights from practicing engineers
Support