UVM 강의 내용 체계적 요약: OOP부터 RAL까지
본 게시글은 Universal Verification Methodology (UVM)를 주제로 진행된 강의의 핵심 내용을 체계적으로 재구성한 종합 요약 자료입니다. 복잡하고 방대할 수 있는 UVM의 개념들을 보다 명확하고 접근하기 쉬운 형태로 정리하여, 현대 SystemVerilog 기반 설계 검증 기법에 대한 깊이 있는 이해를 돕는 것을 목표로 합니다.
이 요약본은 UVM 학습 여정의 기초부터 고급 주제까지 아우르며, 강의 내용을 11개의 핵심 영역으로 나누어 설명합니다. 먼저 UVM의 기반이 되는 객체 지향 프로그래밍(OOP) 개념 검토를 시작으로, 표준 UVM 테스트벤치 구조의 개요를 살펴봅니다. 이어서 Stimulus 생성의 기본 단위인 UVM 트랜잭션(Transaction)과 이를 구동하는 시퀀스(Sequence)에 대해 상세히 다룹니다.
더 나아가, 유연하고 재사용 가능한 검증 환경 구축의 핵심인 UVM 팩토리(Factory)와 설정 데이터베이스(Configuration Database) 메커니즘을 탐구합니다. 또한, 컴포넌트 간의 효율적인 통신을 위한 TLM(Transaction-Level Modeling) 방법, 설계 정확성 검증의 중추인 스코어보드(Scoreboard) 및 기능 커버리지(Functional Coverage)의 역할, 그리고 기존 코드 수정 없이 동작을 변경하는 UVM 콜백(Callback)의 활용법을 명확하게 설명합니다.
마지막으로 고급 시퀀스 제어 기법, 시뮬레이션 흐름을 관리하는 핵심인 UVM 페이징(Phasing) 및 업젝션(Objection) 메커니즘, 그리고 표준화된 레지스터 접근을 위한 RAL(Register Abstraction Layer) 소개로 마무리됩니다.
UVM 용어의 정확성을 높이고 논리적 흐름에 따라 재구성된 이 자료는, UVM의 필수 구성 요소와 방법론을 체계적으로 학습하고자 하는 모든 이들에게 유용한 기초 학습 자료가 될 것입니다. 이 문서를 통해 UVM 프레임워크에 대한 확실한 이해를 바탕으로 보다 효과적인 설계 검증을 수행할 수 있기를 바랍니다.
1. OOP Inheritance review
Class 기본:
- SystemVerilog는 테스트벤치 구축을 위해 객체 지향 프로그래밍(OOP) 개념, 특히 Class를 사용합니다.
- Class는
class와endclass키워드 사이에 정의하며, 모듈(module)과 유사한 구조를 가집니다. - Class 내부에는 데이터를 저장하는 변수인 Properties (속성)와 특정 동작을 수행하는 서브 프로그램인 Methods (메소드 -
function,task)를 정의합니다. 이들을 합쳐 Class 멤버라고 부릅니다. - OOP의 목적 중 하나는 관련된 데이터와 메소드를 하나의 객체(Object)로 캡슐화하는 것입니다.
Inheritance (상속):
- 상속은 기존 클래스(Base/Super class)의 Property와 Method를 물려받아 새로운 클래스(Derived/Sub class)를 정의하는 기능입니다.
extends키워드를 사용하여 상속 관계를 명시합니다.- 상속받은 클래스는 Base 클래스의 모든 기능을 포함하며, 필요에 따라 새로운 Property나 Method를 추가하거나 기존 Method를 재정의(Override)할 수 있습니다.
- Method 재정의 시
super키워드를 사용하여 Base 클래스의 원본 메소드를 호출하고 추가 기능을 구현할 수 있습니다.
Polymorphism (다형성) 및 Virtual Methods (가상 메소드):
- Polymorphism은 동일한 타입의 변수가 다른 형태의 객체를 참조할 수 있는 능력입니다. Base 클래스 타입의 핸들 변수가 Derived 클래스 객체를 참조할 수 있습니다.
- 어떤 메소드가 호출될지는 핸들 변수의 타입과 메소드가
virtual로 선언되었는지에 따라 결정됩니다. virtual로 선언되지 않은 메소드는 핸들 변수의 타입에 따라 호출됩니다.virtual로 선언된 메소드는 핸들 변수가 실제 가리키는 객체의 타입에 따라 해당 객체의 메소드가 호출됩니다.- 권장사항: UVM 환경에서는 메소드를
virtual로 선언하는 것이 일반적입니다. 이는 Polymorphism을 활용하여 유연성을 높이고 예기치 않은 동작을 방지하는 데 도움이 됩니다.
Casting (형 변환):
- Derived 클래스 핸들을 Base 클래스 핸들에 할당하는 것은 안전하며 별도의 처리가 필요 없습니다.
- Base 클래스 핸들이 가리키는 객체를 Derived 클래스 핸들에 할당하려면, 해당 객체가 실제로 Derived 클래스 타입인지 확인하고 안전하게 형 변환해야 합니다. 이때
$cast시스템 태스크를 사용합니다.$cast는 타입 호환성을 검사하고 안전할 때만 할당을 수행합니다.
기타 OOP 개념:
- Parameterized Class: 클래스 정의 시 타입을 파라미터로 받아 다양한 데이터 타입을 처리할 수 있는 클래스입니다.
typedef: 복잡한 타입(특히 Parameterized Class)에 간단한 별칭을 부여하여 코드 가독성과 사용 편의성을 높입니다.extern: 메소드의 선언과 구현을 분리하여 클래스 정의를 간결하게 유지할 수 있게 합니다. 구현은 클래스 외부에서 스코프 확인 연산자(::)를 사용하여 정의합니다.- Static Members:
static키워드로 선언된 Property나 Method는 클래스의 모든 인스턴스(객체)가 공유합니다. 특정 인스턴스에 종속되지 않고 클래스 자체에 속합니다.
2. UVM structure overview
UVM Framework:
- UVM은 SystemVerilog 기반의 표준화된 검증 방법론이자 프레임워크입니다.
- 정해진 구조와 규칙(프레임) 안에서 필요한 컴포넌트를 구현하고 재사용하는 방식입니다.
- Coverage-Driven Verification (CDV) 방법론을 효과적으로 지원합니다.
Testbench Hierarchy (계층 구조):
- UVM 테스트벤치는 계층적인 컴포넌트 구조를 가집니다.
- Top Module/Program Block: 테스트벤치의 최상위 레벨로, 테스트를 시작(
run_test())하고 DUT와 테스트벤치를 연결합니다.program블록 사용이 권장됩니다. - Test (
uvm_test): 특정 테스트 시나리오를 구성하고 환경(Environment)을 생성 및 설정합니다. 테스트 케이스의 진입점 역할을 합니다. - Environment (
uvm_env): 하나 이상의 에이전트(Agent), 스코어보드(Scoreboard), 기능 커버리지(Functional Coverage) 모델 등을 포함하는 컨테이너입니다. - Agent (
uvm_agent): 특정 인터페이스 프로토콜을 처리하는 단위 컴포넌트입니다. 일반적으로 Sequencer, Driver, Monitor를 포함합니다.- Active Agent: Stimulus를 생성하고 DUT에 전달합니다 (Sequencer, Driver, Monitor 포함).
- Passive Agent: DUT의 동작을 감시만 합니다 (Monitor만 포함).
is_active플래그 (UVM_ACTIVE/UVM_PASSIVE)로 동작 모드를 제어합니다.
- Sequencer (
uvm_sequencer): Sequence로부터 Transaction(Sequence Item)을 받아 Driver에게 전달하는 역할을 합니다. - Driver (
uvm_driver): Sequencer로부터 Transaction을 받아 DUT 인터페이스의 신호 레벨로 변환하여 전달합니다. - Monitor (
uvm_monitor): DUT 인터페이스의 신호 변화를 감지하여 Transaction으로 변환하고, 이를 Analysis Port를 통해 다른 컴포넌트(Scoreboard, Coverage Collector 등)에 전달(Broadcast)합니다. - Scoreboard (
uvm_scoreboard): DUT로부터 나온 실제 결과와 예상 결과를 비교하여 기능적 정확성을 검증합니다. - Coverage Collector: Functional Coverage 정보를 수집합니다.
Base Classes:
- UVM 컴포넌트(Test, Env, Agent, Driver, Monitor, Scoreboard, Sequencer 등)는
uvm_component를 상속받습니다. 이들은 계층 구조를 가지며, Phase를 통해 실행 순서가 제어됩니다. - UVM 객체(Transaction, Sequence, Configuration 등)는
uvm_object를 상속받습니다. 이들은 계층 구조에 포함되지 않으며 데이터를 전달하거나 특정 동작을 수행하는 데 사용됩니다.
Test Execution:
- Top 블록의
initial블록 내에서run_test("test_name")을 호출하여 특정 UVM 테스트를 시작합니다.test_name은 실행하고자 하는 테스트 클래스의 이름입니다.
3. UVM Transaction
정의:
- UVM Transaction은 테스트벤치 컴포넌트 간에 전달되는 데이터의 단위입니다. 일반적으로 DUT 인터페이스를 통해 전달되는 정보(주소, 데이터, 제어 신호 등)를 추상화한 객체입니다.
uvm_sequence_item클래스를 상속받아 정의합니다.uvm_sequence_item은uvm_transaction을 상속하고, 이는 다시uvm_object를 상속합니다.
구성 요소:
- Properties (데이터 멤버): Transaction이 포함하는 데이터 필드(예: 주소, 데이터, 길이, 종류 등)를 정의합니다.
rand또는randc키워드를 사용하여 랜덤화 가능한 변수로 선언하는 것이 일반적입니다.- Meta Data: 실제 DUT로 전달되지 않지만 검증 환경 내부에서 제어나 분석에 사용되는 추가 정보(예: 딜레이, 트랜잭션 종류, 상태 등)를 포함할 수 있습니다.
- Constraints:
constraint블록을 사용하여 랜덤화될 변수들의 조건을 정의합니다. 컨스트레인트 네이밍 규칙(예:class_name_valid,class_name_rule)을 따르면 가독성을 높일 수 있습니다. - Methods: Transaction 데이터를 처리하거나 조작하는 메소드를 포함할 수 있습니다 (예:
print,copy,compare).
Field Automation Macros :
uvm_object_utils또는uvm_object_param_utils(Parameterized Class용) 매크로를 사용하여 클래스를 Factory에 등록합니다.uvm_object_utils_begin과uvm_object_utils_end사이에uvm_field_*매크로를 사용하여 각 멤버 변수(Property)를 등록합니다.- 이는
copy,clone,compare,print,pack,unpack과 같은 내장 메소드들이 해당 변수를 자동으로 처리하도록 합니다. - 다양한 타입에 맞는
uvm_field_*매크로가 제공됩니다 (예:uvm_field_int,uvm_field_object,uvm_field_queue,uvm_field_aa_int_string,uvm_field_sarray_int등). - 매크로에 플래그를 추가하여 내장 메소드의 동작을 제어할 수 있습니다 (예:
UVM_ALL_ON,UVM_NOCOPY,UVM_NOPRINT,UVM_NOCOMPARE,UVM_HEX,UVM_DEC등).
내장 Methods 활용:
print(): Transaction 내용을 출력합니다.copy(): 다른 Transaction 객체의 내용을 현재 객체로 복사합니다.clone(): 현재 객체와 동일한 내용의 새로운 객체를 생성하여 반환합니다.compare(): 다른 Transaction 객체와 내용을 비교합니다.pack()/unpack(): Transaction 객체를 비트 스트림으로 변환하거나 그 반대로 변환합니다.convert2string():print()메소드의 기본 출력 형식을 사용자 정의 문자열로 변경할 수 있습니다. 디버깅 메시지를 간결하게 만드는 데 유용합니다.
Parameterized Transaction:
- Transaction 클래스도 Parameterized Class로 정의하여 다양한 데이터 타입이나 크기를 유연하게 처리할 수 있습니다.
- 이 경우 Factory 등록에는
uvm_object_param_utils매크로를 사용하고, 타입 이름을 명확히 하기 위해get_type_name()메소드를 오버라이드하는 것이 좋습니다.typedef를 사용하는 것이 권장됩니다.
4. UVM Sequence
역할:
- UVM Sequence는 테스트 시나리오를 정의하고 Transaction(Sequence Item)들을 생성하여 Sequencer에게 전달하는 역할을 합니다.
- 검증 환경에서 Stimulus 생성의 핵심적인 부분을 담당하며, 검증 엔지니어가 주로 작성하고 수정하는 부분입니다.
정의:
uvm_sequence클래스를 상속받아 정의합니다.uvm_sequence는uvm_sequence_base를 상속하며, 이는 다시uvm_sequence_item을 상속합니다.- 일반적으로 처리할 Transaction 타입을 파라미터로 받습니다.
uvm_sequence #(type REQ=uvm_sequence_item, type RSP=REQ)형태로 정의되며, 필요시 Request(REQ)와 Response(RSP) 타입을 지정합니다.
body() Task:
- Sequence의 핵심 로직은
virtual task body()안에 구현합니다. body()태스크는 Sequence가 Sequencer에서 실행될 때 자동으로 호출됩니다.body()태스크 안에서 Transaction을 생성하고 Sequencer를 통해 Driver로 전달하는 코드를 작성합니다.
Transaction 생성 및 전달:
- 매크로 사용 (
uvm_do계열):`uvm_do(SEQ_ITEM):SEQ_ITEM(Transaction 객체)을 생성(create), 랜덤화(randomize), 전송(send)하는 과정을 자동으로 처리합니다.`uvm_do_with(SEQ_ITEM, { CONSTRAINTS }):`uvm_do와 유사하지만, Transaction 랜덤화 시 추가적인 인라인 제약 조건(CONSTRAINTS)을 적용할 수 있습니다.- 다른 매크로들 (
`uvm_create,`uvm_send,`uvm_rand_send등)도 존재합니다.
- 수동 방식:
create_item()또는`uvm_create(): Transaction 객체를 생성합니다.start_item(SEQ_ITEM): Sequencer에게 Transaction 전송을 요청하고 Driver가 준비될 때까지 대기합니다. Sequencer arbitration(중재)이 여기서 발생합니다.SEQ_ITEM.randomize(): Transaction 객체의 랜덤 변수들을 랜덤화합니다. 필요시with { ... }를 사용하여 인라인 제약 조건을 추가할 수 있습니다.finish_item(SEQ_ITEM): 랜덤화된 Transaction을 Sequencer를 통해 Driver로 전송합니다. Driver는get_next_item()을 통해 이 Transaction을 받습니다.
Response 처리:
- Driver가 처리 결과를 Response Transaction으로 되돌려주는 경우, Sequence는
get_response(RSP)메소드를 호출하여 Response를 받을 수 있습니다. 이는 Blocking 호출입니다.
실행 흐름:
- Sequence가 시작됩니다 (명시적
start()호출 또는 Default Sequence 설정). body()태스크가 실행됩니다.`uvm_do매크로 또는start_item/finish_item을 통해 Transaction(req)이 생성되고 랜덤화됩니다.start_item호출 시 Sequencer는 Driver에게 Transaction 전송 준비를 알립니다.finish_item호출 시 Transaction이 Sequencer를 거쳐 Driver로 전달됩니다.- Driver는
get_next_item(req)을 호출하여 Transaction을 받습니다. - Driver는 Transaction을 DUT 신호 레벨로 변환하여 전달합니다.
- Driver는 처리가 완료되면
item_done(rsp)을 호출합니다.rsp는 선택적인 Response Transaction입니다. item_done호출은 Sequence의finish_item호출을 완료시키거나,get_response호출에 Response를 전달합니다.body()태스크가 종료되면 Sequence 실행이 완료됩니다.
Sequence 시작 방법:
- 명시적 시작 (Explicit Start): Test나 다른 Sequence 내에서 특정 Sequence 객체를 생성하고,
start(SEQUENCER_HANDLE)메소드를 호출하여 특정 Sequencer에서 실행시킵니다. - 묵시적 시작 (Implicit Start / Default Sequence):
uvm_config_db를 사용하여 특정 Sequencer의 Phase에 실행될 Default Sequence를 설정합니다. 해당 Phase가 시작되면 Sequencer는 자동으로 설정된 Default Sequence를 가져와 실행합니다. 이 방법이 권장됩니다.- 설정 예:
uvm_config_db #(uvm_object_wrapper)::set(this, "env.agent.sequencer.main_phase", "default_sequence", my_sequence::get_type());
- 설정 예:
Pre/Post Body Hooks:
pre_start(),post_start()(UVM 1.1 이전:pre_body(),post_body()) 태스크는body()태스크 실행 전후에 자동으로 호출되는 콜백 메소드입니다. 초기화나 마무리 작업에 사용될 수 있습니다.
5. UVM Configuration & Factory (UVM 설정 및 팩토리)
UVM Factory:
- 개념: UVM Factory는 클래스 타입(문자열 이름 기준)으로 객체(Component 또는 Object)를 생성하고 관리하는 중앙 집중형 메커니즘입니다. UVM의 핵심 기능 중 하나로, 테스트벤치의 유연성과 재사용성을 크게 향상시킵니다. 마치 제품을 만들어 창고에 보관했다가 필요시 꺼내주는 공장(Factory)과 같습니다.
- 등록 (Registration):
- Factory를 사용하려면 생성하려는 클래스를 Factory에 등록해야 합니다.
uvm_component기반 클래스는`uvm_component_utils(CLASS_NAME)또는`uvm_component_param_utils(CLASS_NAME)매크로를 사용합니다.uvm_object기반 클래스는`uvm_object_utils(CLASS_NAME)또는`uvm_object_param_utils(CLASS_NAME)매크로를 사용합니다.- 이 매크로들은 클래스 내부에 선언되어야 하며, 해당 클래스 타입과 생성자 정보를 Factory에 등록하는 역할을 합니다.
- 생성 (Creation):
- 객체를 생성할 때는
new()생성자를 직접 호출하는 대신 Factory의create()메소드를 사용합니다. - Component 생성:
COMPONENT_TYPE::type_id::create(STRING_NAME, this).STRING_NAME은 생성될 컴포넌트의 인스턴스 이름,this는 부모 컴포넌트를 의미합니다. - Object 생성:
OBJECT_TYPE::type_id::create(STRING_NAME). 이름은 선택 사항입니다. type_id는 Factory 등록 매크로에 의해 생성된 프록시(Proxy) 객체로, 실제 생성 로직을 캡슐화합니다.
- 객체를 생성할 때는
- 재정의 (Override):
- Factory의 가장 강력한 기능은 기존 코드를 수정하지 않고 생성될 객체의 타입을 변경(재정의)하는 것입니다.
- Type Override: 특정 타입의 모든 인스턴스를 다른 타입으로 변경합니다.
ORIGINAL_TYPE::type_id::set_type_override(OVERRIDE_TYPE::get_type());uvm_factory::get().set_type_override_by_type(ORIGINAL_TYPE::get_type(), OVERRIDE_TYPE::get_type());
- Instance Override: 특정 경로(Path)에 있는 특정 인스턴스만 다른 타입으로 변경합니다.
ORIGINAL_TYPE::type_id::set_inst_override(OVERRIDE_TYPE::get_type(), "INSTANCE_PATH");uvm_factory::get().set_inst_override_by_type(ORIGINAL_TYPE::get_type(), OVERRIDE_TYPE::get_type(), "INSTANCE_PATH");
- 오버라이드는 주로 Test 클래스의
build_phase에서 설정합니다. - 커맨드 라인 옵션으로도 오버라이드를 설정할 수 있습니다 (
+uvm_set_type_override,+uvm_set_inst_override). - Factory의
print()메소드를 사용하여 현재 설정된 오버라이드를 확인할 수 있습니다.
Configuration Database (uvm_config_db):
- 개념: 테스트벤치 계층 구조를 따라 파라미터, 객체 핸들(특히 Virtual Interface), 설정값 등을 전달하기 위한 중앙 집중형 데이터베이스입니다.
- 동작:
set(uvm_component CONTEXT, string INST_PATH, string FIELD_NAME, T VALUE): 특정 경로(CONTEXT + INST_PATH)의 FIELD_NAME에 VALUE를 설정(Write)합니다.set은 주로 상위 레벨(예: Test, Env)의build_phase에서 호출됩니다.get(uvm_component CONTEXT, string INST_PATH, string FIELD_NAME, ref T VALUE): 특정 경로의 FIELD_NAME 값을 읽어(Read)와서 VALUE 변수에 저장합니다.get은 값을 사용하려는 하위 레벨 컴포넌트의build_phase에서 호출됩니다.CONTEXT는 일반적으로this를 사용하며,INST_PATH는 상대 경로 또는 와일드카드(*)를 사용할 수 있습니다.
- 주요 용도:
- Virtual Interface 핸들을 Driver, Monitor 등에 전달.
- Agent의 동작 모드(Active/Passive) 설정 (
is_active플래그 전달). - Sequencer의 Default Sequence 설정.
- 기타 설정값(타임아웃, ID 등) 전달.
Resource Database (uvm_resource_db):
uvm_config_db의 기반 기술이며, 좀 더 직접적인 리소스 관리 기능을 제공합니다. 일반적으로uvm_config_db사용이 권장되지만, 특정 경우(예: Top 모듈에서 인터페이스 설정)에 사용될 수 있습니다.uvm_resource_db는 범위(Scope)와 이름으로 리소스를 관리하며, 전역적인 설정에 유용할 수 있습니다.- 사용 예:
uvm_resource_db#(virtual my_if)::set("ifs", "dut_if", vif);
6. UVM Component Communication
TLM (Transaction-Level Modeling):
- UVM 컴포넌트 간의 통신은 신호 레벨이 아닌 추상화된 Transaction 레벨에서 이루어집니다. 이를 TLM이라고 합니다.
- TLM은 컴포넌트 간의 결합도를 낮추고 재사용성을 높입니다. 통신 방식(메소드 호출)과 주고받는 데이터(Transaction)만 정의되면 컴포넌트 내부 구현에 상관없이 연결될 수 있습니다.
TLM Ports, Exports, Imps:
- UVM TLM은 표준화된 Port, Export, Implementation(Imp) Port를 사용하여 컴포넌트 간의 연결을 정의합니다.
- Port: 통신을 시작(Initiate)하는 쪽에서 사용됩니다. 다른 컴포넌트의 메소드를 호출합니다.
- Export: 계층 구조를 통해 하위 컴포넌트의 Imp나 다른 Export로 연결을 전달(Pass-through)하는 데 사용됩니다.
- Imp (Implementation Port): 통신 메소드(예:
put,get,write)가 실제 구현된 컴포넌트에서 사용됩니다. Port로부터의 메소드 호출을 받습니다.
TLM 1.0 Communication Models:
- Push Model (
uvm_blocking_put_port/imp): 생산자(Producer)가put()메소드를 호출하여 소비자(Consumer)에게 데이터를 밀어 넣습니다.put()구현은 Consumer 쪽에 있습니다. - Pull Model (
uvm_blocking_get_port/imp): 소비자(Consumer)가get()메소드를 호출하여 생산자(Producer)로부터 데이터를 당겨옵니다.get()구현은 Producer 쪽에 있습니다. - FIFO Model (
uvm_tlm_fifo): 내장된 FIFO 버퍼를 통해 통신합니다. Producer는put()으로 FIFO에 쓰고, Consumer는get()으로 FIFO에서 읽습니다.put/get메소드는 FIFO 자체에 구현되어 있습니다. 데이터 순서 보장이 필요할 때 사용합니다. - Analysis Model (
uvm_analysis_port/imp): 생산자(Monitor)가write()메소드를 호출하여 연결된 모든 구독자(Subscriber: Scoreboard, Coverage Collector 등)에게 데이터를 방송(Broadcast)합니다.write()구현은 각 Subscriber 쪽에 있습니다.
Port/Export Naming Convention:
- Port 변수 이름은 기능과 타입을 명확히 나타내도록 짓는 것이 좋습니다 (예:
ap또는analysis_port,put_port,get_export).
Connecting TLM Interfaces:
- Port, Export, Imp 간의 연결은
connect()메소드를 사용하여connect_phase에서 이루어집니다. - 예:
producer.put_port.connect(consumer.put_imp);
Pass-through Connections:
- 상위 컴포넌트가 하위 컴포넌트의 TLM 인터페이스를 외부에 노출시켜야 할 때 사용됩니다. 상위 컴포넌트에 Export를 선언하고,
connect_phase에서 하위 컴포넌트의 Imp나 Export에 연결합니다. - 예: Monitor의 Analysis Port를 Agent 외부로 노출시키기 위해 Agent에 Analysis Export를 만들고,
monitor.analysis_port.connect(this.analysis_export);와 같이 연결합니다.
TLM 2.0 Sockets:
- TLM 2.0은 좀 더 복잡한 양방향 통신과 타이밍 모델링을 지원하는 소켓(Socket) 기반 통신을 제공합니다.
- Blocking (
b_transport)과 Non-blocking (nb_transport) 전송 방식이 있습니다. Non-blocking 방식은 통신 단계를 나타내는 Phase 정보를 함께 전달합니다. - 딜레이 정보를 포함하여 전송할 수 있습니다.
- 구현이 TLM 1.0보다 복잡하여 특정 경우(예: SystemC 연동, 정교한 타이밍 모델링) 외에는 덜 사용됩니다.
7. UVM Scoreboard & Coverage
Scoreboard 역할:
- DUT의 기능적 정확성을 검증하는 핵심 컴포넌트입니다.
- 일반적으로 DUT 입력과 출력에서 Transaction을 받아, 입력 Transaction을 기반으로 예상 출력(Expected Data)을 계산하고, 이를 실제 DUT 출력(Actual Data)과 비교합니다.
- Testbench의 자동화된 Self-Checking 기능을 제공합니다.
Scoreboard 구현:
uvm_scoreboard클래스를 상속받아 구현합니다.- 입력/출력 Transaction을 받기 위해 Analysis Imp Port(
uvm_analysis_imp)를 사용합니다. Monitor의 Analysis Port와 연결됩니다. - Analysis Imp Port에 연결된
write()메소드를 구현하여 Monitor로부터 Transaction을 수신합니다. 일반적으로 입력 Transaction용write_input()과 출력 Transaction용write_output()등으로 분리하여 구현합니다. - 예상 데이터 생성: 입력 Transaction(
write_input에서 수신)을 기반으로 DUT의 동작을 모델링하여 예상 출력 Transaction을 생성합니다. 이 로직은 검증 대상 설계에 따라 달라지며, 스코어보드 구현의 핵심입니다. - 데이터 저장 및 비교:
- 생성된 예상 출력 Transaction과 DUT에서 나온 실제 출력 Transaction을 저장할 내부 저장소(예: Queue, Associative Array)가 필요합니다.
- 수신된 실제 출력 Transaction(
write_output에서 수신)과 매칭되는 예상 출력 Transaction을 저장소에서 찾아 비교합니다. - UVM은 순서대로 비교하는
uvm_in_order_class_comparator나 순서에 상관없이 비교하는uvm_algorithmic_comparator와 같은 내장 Comparator 컴포넌트를 제공합니다. 이를 활용하면 비교 로직 구현이 간편해집니다.
- 결과 보고: 비교 결과(Match/Mismatch)를 카운트하고 UVM 리포팅 매크로(
uvm_info,uvm_error등)를 사용하여 보고합니다.
Functional Coverage (기능 커버리지):
- 개념: 설계 사양에 정의된 기능 항목들이 검증 과정에서 얼마나 충분히 테스트되었는지를 측정하는 지표입니다. 랜덤 테스트 환경에서 어떤 시나리오들이 실행되었는지 추적하는 데 사용됩니다.
- Coverage Points & Bins: SystemVerilog
covergroup을 사용하여 정의합니다. 특정 변수 값, 변수 값의 조합, 상태 전이 등을 커버리지 포인트(Coverpoint)로 정의하고, 각 포인트 내에서 커버하고자 하는 값이나 범위를 빈(Bin)으로 정의합니다. - Sampling:
covergroup의sample()메소드를 호출하여 현재 시점의 관련 변수 값들을 기반으로 커버리지를 수집합니다. - UVM 연동:
- Coverage Collector 컴포넌트 (Subscriber)를 만들어 Monitor의 Analysis Port에 연결합니다.
- Collector의
write()메소드 내에서 수신된 Transaction 데이터를 기반으로covergroup.sample()을 호출합니다. - Scoreboard 내부에
covergroup을 정의하고, Transaction 처리 과정에서sample()을 호출하여 검증 정확성과 커버리지를 동시에 측정할 수도 있습니다.
- 측정 대상: 검증 목표에 따라 다양한 측면의 커버리지를 측정할 수 있습니다.
- Configuration Coverage: 테스트벤치 구성(예: Agent 개수, 동작 모드)의 다양성.
- Stimulus Coverage: DUT에 인가된 입력 트랜잭션의 값, 순서, 조합 등의 다양성.
- Correctness Coverage (Cross Coverage): 특정 입력 조건과 특정 출력 결과의 조합.
8. UVM Callback
개념:
- UVM Callback은 기존 컴포넌트의 코드를 직접 수정하지 않고 특정 지점의 동작을 변경하거나 확장할 수 있게 해주는 메커니즘입니다.
- 마치 특정 이벤트 발생 시 미리 등록된 함수를 호출(Callback)하는 것과 유사합니다.
- Factory Override가 클래스 전체를 교체하는 방식이라면, Callback은 클래스 내 특정 메소드(Hook)의 동작을 수정하는 데 사용됩니다.
사용 단계:
- Hook 추가 (Embed Hooks): 동작을 변경하고 싶은 지점(주로 메소드 내부)에 Callback Hook 매크로를 추가합니다.
- 예:
`uvm_do_callbacks(CALLBACK_TYPE, METHOD_NAME(ARGS))또는 특정 Callback 객체를 지정하는 방식. 이 매크로는 등록된 모든 Callback 객체의 해당 메소드를 순차적으로 호출합니다.
- 예:
- Facade Class 정의 (Define Facade Class): Callback 메소드들의 인터페이스(껍데기) 역할을 하는 클래스를 정의합니다. 이 클래스는
uvm_callback을 상속받고, Hook에서 호출될 메소드들을virtual(주로 빈 내용)로 선언합니다. 이것이 "퍼사드(Facade)" 클래스입니다. - Callback Class 구현 (Implement Callback Class): Facade 클래스를 상속받아 실제 동작을 구현하는 Callback 클래스를 만듭니다. Hook에서 호출될 메소드를 오버라이드하여 원하는 기능을 구현합니다.
- 등록 (Register Callback): 구현된 Callback 객체를 생성하고, Hook이 있는 컴포넌트 인스턴스에 등록(Add)합니다. 등록은 주로 Test 클래스에서 이루어집니다.
- 등록 매크로:
uvm_callbacks#(COMPONENT_TYPE, CALLBACK_TYPE)::add(TARGET_COMPONENT, CALLBACK_INSTANCE); - 특정 인스턴스가 아닌 해당 타입의 모든 인스턴스에 등록하려면
TARGET_COMPONENT자리에null을 사용합니다. - 등록된 콜백은
delete()메소드로 제거할 수도 있습니다.
- 등록 매크로:
사용 예시:
- Error Injection: Driver의 데이터 전송 직전에 Callback을 사용하여 Transaction 데이터를 의도적으로 변경(오류 주입).
- Functional Coverage: 특정 동작 수행 시점에 Callback을 호출하여 커버리지 그룹을 샘플링.
- Custom Debugging: 특정 메소드 호출 시점에 추가적인 디버그 메시지 출력.
Type-wide Callback Registration:
- Callback을 특정 컴포넌트 타입 전체에 등록하려면
uvm_callback_registry#(COMPONENT_TYPE, CALLBACK_TYPE)::add(CALLBACK_INSTANCE);와 같은 방식을 사용할 수 있습니다.
UVM 내장 Callbacks:
- UVM 자체에도 많은 내장 Callback Hook들이 존재합니다. 예를 들어, Sequence 실행 단계(pre_start, post_start 등)나 Phase 전환 시점 등이 Callback으로 구현되어 있어 사용자가 해당 메소드를 오버라이드하여 원하는 동작을 추가할 수 있습니다.
9. UVM Advanced Sequence/Sequencer (UVM 고급 시퀀스/시퀀서)
필요성:
- 복잡한 테스트 시나리오에서는 여러 인터페이스를 동시에 제어하거나, Sequence 간의 실행 순서나 동기화를 정교하게 관리해야 할 필요가 있습니다.
Virtual Sequence & Virtual Sequencer (가상 시퀀스/시퀀서):
- 개념: Virtual Sequence는 실제 Transaction을 생성하지 않고, 여러 개의 하위 Sequencer(다른 Agent에 속한)를 참조하여 해당 Sequencer에서 실행될 다른 Sequence들을 제어(시작, 동기화)하는 역할을 합니다. Virtual Sequencer는 이러한 Virtual Sequence가 실행될 대상이며, 하위 Sequencer들에 대한 핸들을 가지고 있습니다. 주로 Environment 레벨에 위치합니다.
- Virtual Sequencer 구현:
uvm_sequencer를 상속받아 정의합니다.- 제어할 하위 Sequencer들에 대한 핸들 변수를 선언합니다 (예:
uvm_sequencer #(my_if_a_tx) seqr_a;). `uvm_component_utils로 Factory에 등록합니다.- 일반적으로 내부에 특별한 로직은 필요 없습니다.
- Virtual Sequence 구현:
uvm_sequence를 상속받아 정의합니다. Transaction 타입은 중요하지 않으므로 보통uvm_sequence_item을 사용합니다.- 제어할 하위 Sequencer 핸들을 담을 변수를 선언합니다 (Virtual Sequencer의 핸들과 타입이 일치해야 함).
`uvm_declare_p_sequencer(VIRTUAL_SEQUENCER_TYPE)매크로를 사용하여p_sequencer변수가 Virtual Sequencer를 참조하도록 선언합니다. 이를 통해 Virtual Sequence 내에서p_sequencer.seqr_a와 같이 하위 Sequencer 핸들에 접근할 수 있습니다.body()태스크 내에서 하위 Sequence들을 생성하고,start()메소드를 호출할 때p_sequencer내의 해당 하위 Sequencer 핸들을 지정하여 실행시킵니다.sub_seq_a.start(p_sequencer.seqr_a);sub_seq_b.start(p_sequencer.seqr_b);
- 연결:
- Test나 Env에서 Virtual Sequencer를 생성합니다.
- Test나 Env에서 Virtual Sequencer 내부의 하위 Sequencer 핸들에 실제 Agent의 Sequencer 핸들을 할당(Assign)하거나
uvm_config_db로 전달합니다. - Virtual Sequence를 생성하고
start()메소드를 호출하여 Virtual Sequencer에서 실행시킵니다.
Sequence Synchronization :
- 여러 Sequence의 실행 시점을 맞추기 위해
uvm_event를 사용합니다. uvm_event: 동기화를 위한 기본적인 클래스입니다.trigger(): 이벤트를 발생시킵니다.wait_trigger()또는wait_on(): 이벤트가 발생할 때까지 대기합니다 (레벨 민감).wait_ptrigger(): 이벤트가 발생한 그 순간(엣지)을 기다립니다 (엣지 민감).is_on(),is_off(): 현재 이벤트 상태를 확인합니다.reset(): 이벤트를 초기 상태(Off)로 되돌립니다.
uvm_event_pool: 이름(문자열 키)을 사용하여 이벤트를 공유하는 전역 풀(Global Pool)입니다. 서로 다른 컴포넌트나 시퀀스에서 동일한 이름의 이벤트를 쉽게 참조하고 동기화할 수 있습니다.uvm_event_pool::get_global("EVENT_KEY"): 특정 키에 해당하는 전역uvm_event객체 핸들을 얻습니다. 키가 없으면 새로 생성됩니다.- 얻어온 이벤트 핸들을 사용하여
trigger,wait_*등을 호출합니다.
Sequencer Arbitration (시퀀서 중재 제어):
- Sequencer는 여러 Sequence로부터의 Transaction 요청을 받아 순서대로 처리(Arbitration)합니다. 때로는 특정 Sequence가 Sequencer를 독점해야 할 필요가 있습니다.
lock()/unlock():- Sequence가
lock(SEQUENCER_HANDLE)을 호출하면, 현재 처리 중인 다른 Sequence가 완료된 후 Sequencer 사용 권한을 얻습니다. 일단 Lock을 획득하면,unlock(SEQUENCER_HANDLE)을 호출할 때까지 다른 Sequence는 해당 Sequencer를 사용할 수 없습니다. Lock을 요청한 Sequence는 순서를 기다립니다.
- Sequence가
grab()/ungrab():grab(SEQUENCER_HANDLE)은lock보다 더 강력합니다. 현재 다른 Sequence가 Sequencer를 사용 중이더라도 즉시 (또는 현재 Transaction 처리 완료 후) 사용 권한을 빼앗아 독점합니다. 다른 Sequence들은 Grab이 해제될 때까지 기다려야 합니다. 우선순위가 매우 높은 Sequence에 사용될 수 있습니다.ungrab(SEQUENCER_HANDLE)으로 독점권을 해제합니다.
10. UVM Phasing & Objection
Phasing 개요:
- UVM Phasing은 테스트벤치 실행을 여러 단계(Phase)로 나누어 관리하는 메커니즘입니다.
- 각 Phase는 특정 목적(예: 빌드, 연결, 실행, 정리)을 가지며, 모든 컴포넌트가 현재 Phase를 완료해야 다음 Phase로 진행됩니다.
- Phase는 테스트벤치의 동기화된 실행 흐름을 보장합니다.
Standard Phases:
- Build Phases (Top-down): 컴포넌트 계층 구조를 생성합니다.
build_phase:uvm_component::build_phase(). 주로create()를 호출하여 하위 컴포넌트를 생성하고uvm_config_db::set/get을 사용합니다.
- Connect Phase (Bottom-up): TLM 포트 등을 연결합니다.
connect_phase:uvm_component::connect_phase().connect()메소드를 호출합니다.
- End of Elaboration Phase (Bottom-up): 최종 점검 단계.
end_of_elaboration_phase:uvm_component::end_of_elaboration_phase(). 모든 빌드 및 연결이 완료된 후 호출됩니다. 설정 확인 등에 사용됩니다.
- Start of Simulation Phase (Bottom-up): 시뮬레이션 시작 직전 단계.
start_of_simulation_phase:uvm_component::start_of_simulation_phase(). 테스트벤치 구성 출력, 초기 메시지 표시 등에 사용됩니다.
- Run-Time Phases (Parallel): 실제 시뮬레이션이 진행되는 시간 소모적인 Phase들입니다. UVM Task 기반 Phase와 병렬로 실행됩니다.
run_phase:uvm_component::run_phase(). 시간 소모가 필요한 모든 동작(예: Driver/Monitor 동작)이 여기서 수행됩니다.
- Clean-up Phases (Bottom-up): 시뮬레이션 종료 후 정리 단계.
extract_phase:uvm_component::extract_phase(). 결과 데이터 추출.check_phase:uvm_component::check_phase(). 최종 결과 검사.report_phase:uvm_component::report_phase(). 최종 결과 보고.final_phase:uvm_component::final_phase(). 최종 마무리 작업.
UVM Task-Based Phases (병렬 실행):
run_phase와 병렬로 실행되는 세분화된 Task 기반 Phase입니다. 주로 Sequence 실행을 특정 시점에 맞추는 데 사용됩니다.reset_phase,configure_phase,main_phase,shutdown_phase등이 순서대로 진행됩니다. 각 Phase 전후에pre_*,post_*Phase가 존재합니다 (예:pre_reset_phase,post_reset_phase).
Phase Execution Order:
- Build Phase는 Top-down (상위에서 하위로) 실행됩니다.
- Connect, End of Elaboration, Start of Simulation, Clean-up Phase는 Bottom-up (하위에서 상위로) 실행됩니다.
- Run-time Phase (
run_phase및 UVM Task Phase들)는 병렬로 실행됩니다.
Objection Mechanism (업젝션 메커니즘):
- 목적: Run-time Phase들이 언제 종료되어야 하는지를 제어합니다. 특정 Phase에서 시간 소모적인 작업이 진행 중임을 알리고, 해당 작업이 모두 완료될 때까지 Phase 종료를 보류(Objection)합니다.
- 동작:
raise_objection(this): 현재 Phase에 대한 Objection을 제기합니다. 카운터가 증가합니다.drop_objection(this): 제기했던 Objection을 철회합니다. 카운터가 감소합니다.- 해당 Phase의 모든 컴포넌트에서 제기된 Objection 카운트가 0이 되면, 해당 Phase는 종료되고 다음 Phase로 진행할 준비를 합니다.
- 사용 위치: 시간 소모적인 작업을 시작하기 전에
raise_objection을 호출하고, 작업이 완료되면drop_objection을 호출합니다. - 권장 사항:
run_phase나 Driver/Monitor에서 직접 Objection을 관리하는 것보다, Sequence 내에서 Objection을 관리하는 것이 권장됩니다. 이는 Stimulus 생성과 테스트 종료 시점을 일치시키는 데 도움이 됩니다.
Automatic Objection in Sequences:
- Sequence 실행 시 자동으로 Objection을 관리하는 기능이 제공됩니다.
- UVM 1.1 이전: Sequence의
pre_start()/post_start()(또는pre_body/post_body) 메소드 내에서starting_phase.raise/drop_objection(this)를 호출합니다. - UVM 1.1d/1.2 이후: Sequence의 생성자(
new()) 내에서set_automatic_phase_objection(1)을 호출하면, Sequence가 시작될 때 자동으로raise_objection, 종료될 때drop_objection이 호출됩니다. 이 방법이 가장 간편하고 권장됩니다.
Drain Time :
- 마지막
drop_objection이 호출된 후, DUT 내부 파이프라인 등에 남아있는 동작이 완료되고 최종 결과가 나올 때까지 기다려주는 시간입니다. drop_objection후 즉시 Phase가 종료되면 DUT의 최종 출력을 놓칠 수 있습니다. UVM은 기본 Drain Time을 가지지만, 필요시 조정할 수 있습니다.- 조정 방법:
phase.set_drain_time(this, value): 특정 시간(value)만큼 Drain Time을 설정합니다.- Scoreboard의 예상 데이터 큐가 비워질 때까지 대기 (
wait(exp_q.size() == 0))하는 로직을shutdown_phase등에 추가하여 모든 결과 처리를 보장합니다. phase_ready_to_end()콜백 메소드를 사용하여 모든 Objection이 해제된 후 추가적인 종료 조건을 검사할 수 있습니다.
Phase Timeout:
- 특정 Phase가 비정상적으로 오래 지속될 경우(예: Deadlock), 테스트를 강제 종료하기 위한 타임아웃 시간을 설정할 수 있습니다.
uvm_root::set_timeout(time, hierarchical=1)또는 커맨드 라인 옵션 (+UVM_TIMEOUT)으로 설정합니다.
Advanced Phasing (Domains, Jumps):
- 독립적인 Phase 실행 흐름을 갖는 도메인(Domain)을 생성하거나, 특정 조건에 따라 Phase를 건너뛰거나(Jump) 이전 Phase로 돌아갈 수 있는 고급 기능도 제공됩니다. 하지만 구현이 복잡하고 오류 발생 가능성이 높아 거의 사용되지 않습니다.
11. UVM Register Abstraction Layer (RAL - UVM 레지스터 추상화 계층)
개념:
- RAL(일명 UVM RAL)은 DUT 내부의 레지스터 및 메모리에 접근하기 위한 표준화되고 추상화된 방법을 제공하는 UVM 라이브러리입니다.
- DUT의 레지스터 맵 정보를 기반으로 테스트벤치 내에 해당 레지스터/메모리에 대한 모델(클래스 객체)을 생성합니다.
- 이를 통해 테스트 시퀀스에서 물리적 주소나 비트 필드 위치를 직접 알 필요 없이, 이름 기반으로 레지스터와 필드에 접근할 수 있습니다.
주요 목적:
- 레지스터 접근 코드의 가독성 및 유지보수성 향상.
- 레지스터 접근 방식의 표준화 및 재사용성 증대.
- 레지스터 기능 검증 자동화 (예: 내장된 레지스터 테스트 시퀀스 활용).
- Backdoor 접근 및 예측(Prediction) 기능을 통한 검증 효율성 증대.
RAL 구성 요소:
- RAL Model: DUT의 레지스터와 메모리 구조를 표현하는 클래스들의 집합.
uvm_reg_field: 개별 레지스터 필드.uvm_reg: 개별 레지스터 (하나 이상의 필드 포함).uvm_mem: 메모리 영역.uvm_reg_block: 레지스터, 메모리, 하위 블록들을 그룹화하는 블록. 계층적으로 구성 가능.uvm_reg_map: 레지스터/메모리의 주소 매핑 정보. Default Map (default_map) 등이 사용됨.
- Register Adapter (
uvm_reg_adapter): 추상화된 RAL 읽기/쓰기 동작(uvm_reg_bus_op)을 실제 DUT 버스 프로토콜의 Transaction(예: APB, AHB Transaction)으로 변환하거나 그 반대로 변환하는 역할을 합니다. 사용자는 대상 버스 프로토콜에 맞게 어댑터의reg2bus()와bus2reg()메소드를 구현해야 합니다. - Predictor (
uvm_reg_predictor): 버스 모니터를 통해 감지된 실제 버스 Transaction을 보고 RAL 모델의 레지스터/메모리 값을 자동으로 업데이트(예측)하여 DUT의 실제 값과 동기화하는 역할을 합니다 (Mirroring 기능).
RAL 사용 흐름:
- RAL 모델 생성:
- DUT의 레지스터/메모리 명세(예: IP-XACT, CSV, SystemRDL, 또는 사용자 정의 포맷)를 준비합니다.
- 명세 파일을 입력으로 사용하여 RAL 생성 도구(예: Synopsys의
ralgen또는 타 벤더 도구)를 실행하여 UVM RAL 모델 코드(uvm_reg_block등을 포함하는 .sv 파일)를 자동으로 생성합니다. 수동으로 작성할 수도 있지만 매우 번거롭습니다.
- 어댑터 구현: DUT 버스 프로토콜에 맞는 Register Adapter 클래스(
uvm_reg_adapter상속)를 구현합니다. - RAL 환경 통합:
- 테스트 환경(일반적으로 Env 또는 Test 클래스)에서 생성된 RAL 모델 객체와 구현된 어댑터 객체를 생성(Instantiate)합니다.
build_phase에서 RAL 모델의configure()및lock_model()메소드를 호출하고,default_map을 설정합니다.connect_phase에서 RAL 모델의default_map에 Sequencer와 Adapter를 연결합니다 (set_sequencer,set_adapter).- (선택 사항) Predictor를 생성하고 모니터의 Analysis Port와 연결하여 Mirroring 기능을 활성화합니다.
- RAL 접근 사용:
- 테스트 시퀀스에서 통합된 RAL 모델 핸들을 얻어옵니다 (
uvm_config_db::get등). - RAL 모델의 레지스터/메모리 객체에 대해
read(),write(),peek(),poke(),mirror()등의 메소드를 호출하여 DUT 레지스터/메모리에 접근합니다.
- 테스트 시퀀스에서 통합된 RAL 모델 핸들을 얻어옵니다 (
Frontdoor vs. Backdoor Access:
- Frontdoor: 실제 DUT 버스 인터페이스를 통해 레지스터/메모리에 접근하는 방식입니다. Adapter를 통해 버스 Transaction이 생성되어 Driver로 전달됩니다.
read/write메소드가 기본적으로 Frontdoor 접근을 수행합니다. - Backdoor: 시뮬레이터의 내부 인터페이스(예: DPI, VPI)를 통해 DUT 계층 구조 내부의 레지스터/메모리 값을 직접 읽거나 쓰는 방식입니다. 버스 프로토콜을 거치지 않아 빠르지만, 실제 하드웨어 동작과 다를 수 있습니다.
peek/poke메소드가 Backdoor 접근을 수행합니다.
Register Mirroring:
- RAL 모델 내부에 레지스터/메모리 값을 저장하는 공간(Desired Value, Mirrored Value)을 두어 DUT의 실제 값과 동기화하는 기능입니다.
write/read수행 시 예상되는 값을 Mirrored Value에 업데이트하고,mirror()메소드로 실제 DUT 값과 비교하거나, Predictor를 사용하여 자동으로 업데이트할 수 있습니다.
Built-in RAL Sequences:
- UVM RAL은 기본적인 레지스터 접근성, 리셋 값, 비트 필드 동작 등을 검증하기 위한 내장된 테스트 시퀀스들을 제공합니다 (예:
uvm_reg_hw_reset_seq,uvm_reg_bit_bash_seq,uvm_reg_access_seq,uvm_mem_access_seq). 이를 활용하여 기본적인 레지스터 검증을 쉽게 수행할 수 있습니다.
