2026. 4. 10. 20:02ㆍUVM
UVM을 처음 쓸 때 가장 헷갈리는 것 중 하나가 Phase다. build_phase에서 왜 하위 컴포넌트가 안 만들어졌다고 에러가 나는지, connect_phase와 run_phase는 어떤 순서로 실행되는지, objection은 왜 써야 하는지 — 이 글에서는 UVM Phase의 종류와 실행 순서를 컴포넌트 계층 구조와 함께 완벽하게 정리한다.
1. UVM Phase 전체 목록
UVM Phase는 크게 Build-Time Phase와 Run-Time Phase로 나뉜다.
1-1. Build-Time Phase (함수형, 순차 실행)
Phase역할실행 방향
| build_phase | 컴포넌트 생성 (new), uvm_config_db get | Top-Down (상위 → 하위) |
| connect_phase | TLM 포트 연결, 핸들 연결 | Bottom-Up (하위 → 상위) |
| end_of_elaboration_phase | 토폴로지 확정 후 설정 조정, 계층 출력 | Bottom-Up |
| start_of_simulation_phase | 시뮬레이션 시작 직전 초기화, 배너 출력 | Bottom-Up |
1-2. Run-Time Phase (태스크형, 병렬 실행)
Phase역할특징
| run_phase | 실제 시뮬레이션 실행 (시퀀스 실행, 모니터링) | 모든 컴포넌트 병렬 실행 |
| extract_phase | 결과 데이터 수집, 통계 계산 | Bottom-Up |
| check_phase | 오류 체크, 예상값 비교 | Bottom-Up |
| report_phase | 결과 보고서 출력 | Bottom-Up |
| final_phase | 종료 처리, 파일 닫기 | Top-Down |
1-3. Run-Time Sub-Phase (12개 세부 단계)
run_phase와 병렬로 실행되는 세부 phase들이다. 복잡한 테스트 시나리오에서 단계별 제어가 필요할 때 사용한다.
Sub-Phase 그룹포함 Phase
| Reset | pre_reset_phase → reset_phase → post_reset_phase |
| Configure | pre_configure_phase → configure_phase → post_configure_phase |
| Main | pre_main_phase → main_phase → post_main_phase |
| Shutdown | pre_shutdown_phase → shutdown_phase → post_shutdown_phase |
일반적인 블록 레벨 검증에서는 sub-phase를 잘 쓰지 않고 run_phase 하나로 모든 시나리오를 처리하는 경우가 많다. 복잡한 SoC 레벨에서 reset 시퀀스와 메인 트래픽을 분리해야 할 때 유용하다.
2. 핵심 — 컴포넌트 간 Phase 실행 순서
이것이 가장 중요한 부분이다. 같은 Phase라도 컴포넌트 종류에 따라 실행 순서가 다르다.
2-1. build_phase: Top-Down (상위 → 하위)
build_phase는 UVM에서 유일하게 Top-Down으로 실행되는 phase다. 상위 컴포넌트(test)가 먼저 build_phase를 실행하고, 그 안에서 하위 컴포넌트(env)를 create한 후, env의 build_phase가 실행되는 방식이다.
실행 순서 (Top-Down):
1. my_test::build_phase() ← 최상위부터
2. my_env::build_phase()
3. my_axi_agent::build_phase()
4. my_axi_driver::build_phase()
5. my_axi_monitor::build_phase()
6. my_axi_sequencer::build_phase()
7. my_apb_agent::build_phase()
8. my_apb_driver::build_phase()
9. my_apb_monitor::build_phase()
10. my_apb_sequencer::build_phase()
왜 Top-Down인가? 하위 컴포넌트를 create하는 것은 상위 컴포넌트의 build_phase 책임이기 때문이다. 상위가 먼저 실행되어야 하위 컴포넌트가 존재할 수 있다. 만약 Bottom-Up이었다면 아직 생성되지 않은 컴포넌트의 build_phase를 호출하는 모순이 생긴다.
2-2. connect_phase 이후: Bottom-Up (하위 → 상위)
build_phase가 끝나면 이후 모든 phase는 Bottom-Up으로 실행된다. 가장 아래 leaf 컴포넌트(driver, monitor)부터 실행하고, 위로 올라오는 방식이다.
connect_phase 실행 순서 (Bottom-Up):
1. my_axi_driver::connect_phase()
2. my_axi_monitor::connect_phase()
3. my_axi_sequencer::connect_phase()
4. my_axi_agent::connect_phase() ← 여기서 포트 연결
5. my_apb_driver::connect_phase()
6. my_apb_monitor::connect_phase()
7. my_apb_sequencer::connect_phase()
8. my_apb_agent::connect_phase()
9. my_env::connect_phase() ← 여기서 scoreboard 연결
10. my_test::connect_phase()
왜 Bottom-Up인가? connect_phase에서는 하위 컴포넌트의 포트(port)를 상위 컴포넌트가 연결한다. 하위의 포트가 먼저 준비되어 있어야 상위에서 연결할 수 있기 때문이다. TLM 포트 연결, virtual sequencer 핸들 연결이 대표적이다.
2-3. run_phase: 모든 컴포넌트가 동시에 병렬 실행
run_phase는 Build-Time Phase와 완전히 다르다. Top-Down도 Bottom-Up도 아니라 모든 컴포넌트의 run_phase가 동시에(fork-join_none) 시작된다.
run_phase — 모든 컴포넌트 동시 시작:
fork
my_test::run_phase() ← 시퀀스 실행
my_axi_driver::run_phase() ← 항상 대기하며 item 처리
my_axi_monitor::run_phase() ← 항상 대기하며 트랜잭션 캡처
my_apb_driver::run_phase()
my_apb_monitor::run_phase()
my_scoreboard::run_phase()
join ← 모두 끝날 때까지 대기 (objection이 0이 되면 종료)
Driver와 Monitor는 보통 forever 루프로 동작하며, 시뮬레이션이 끝날 때까지 계속 실행된다. run_phase의 종료는 objection 메커니즘으로 제어한다.
3. Objection 메커니즘 — run_phase를 끝내는 방법
run_phase는 영원히 실행되지 않는다. Objection 카운터가 0이 되는 순간 run_phase가 종료되고, 이후 extract → check → report → final phase로 넘어간다.
task run_phase(uvm_phase phase);
phase.raise_objection(this); // 카운터 +1 → run_phase 유지
// ... 시퀀스 실행, 검증 작업 ...
my_seq.start(env.v_sqr);
phase.drop_objection(this); // 카운터 -1 → 0이 되면 종료
endtask
Objection은 보통 Test 클래스 하나에서만 raise/drop한다. 여러 컴포넌트에서 raise하면 모든 objection이 drop되어야 종료되므로 관리가 복잡해진다.
상황Objection 카운터run_phase 상태
| run_phase 시작 | 0 | 즉시 종료될 수 있음 (주의!) |
| raise_objection 호출 | +1 (이상) | 유지됨 |
| drop_objection 호출 후 0 | 0 | 종료 → extract_phase로 |
⚠️ 주의: raise_objection 없이 run_phase를 시작하면 시뮬레이션이 즉시 종료될 수 있다. Driver와 Monitor는 보통 objection을 raise하지 않는다 — forever 루프로 돌기 때문에 자연스럽게 Test의 drop을 기다린다.
4. 전체 실행 흐름 — 실전 예제로 트레이스하기
test → env → agent(axi, apb) → driver/monitor/sequencer 계층 구조에서 전체 phase 실행 순서를 처음부터 끝까지 트레이스해보자.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1] build_phase (Top-Down)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
my_test::build_phase
→ env 생성
my_env::build_phase
→ axi_agent, apb_agent, v_sqr 생성
my_axi_agent::build_phase
→ axi_driver, axi_monitor, axi_sequencer 생성
my_apb_agent::build_phase
→ apb_driver, apb_monitor, apb_sequencer 생성
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[2] connect_phase (Bottom-Up)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
axi_driver, axi_monitor, axi_sequencer::connect_phase
my_axi_agent::connect_phase
→ driver.seq_item_port.connect(sequencer.seq_item_export)
apb_driver, apb_monitor, apb_sequencer::connect_phase
my_apb_agent::connect_phase
my_env::connect_phase
→ monitor.ap.connect(scoreboard.imp)
→ v_sqr.axi_sqr = axi_agent.sequencer
my_test::connect_phase
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[3] end_of_elaboration / start_of_simulation (Bottom-Up)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
계층 확인, 초기화 메시지 출력
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[4] run_phase (모든 컴포넌트 동시 시작)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌─ test::run_phase → raise_objection → 시퀀스 실행 → drop_objection
├─ axi_driver::run_phase (forever: seq_item 받아 DUT 구동)
├─ axi_monitor::run_phase (forever: 인터페이스 샘플링)
├─ apb_driver::run_phase (forever)
└─ apb_monitor::run_phase (forever)
→ drop_objection() 후 카운터 0 → run_phase 전체 종료
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[5] extract → check → report → final (Bottom-Up)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
scoreboard::check_phase → 결과 비교
scoreboard::report_phase → Pass/Fail 출력
5. Phase 핵심 정리
Phase타입실행 방향핵심 역할
| build_phase | 함수 | Top-Down ⬇ | 컴포넌트 생성, config_db get |
| connect_phase | 함수 | Bottom-Up ⬆ | TLM 포트 연결, 핸들 연결 |
| end_of_elaboration | 함수 | Bottom-Up ⬆ | 토폴로지 확정 후 보정 |
| start_of_simulation | 함수 | Bottom-Up ⬆ | 시뮬레이션 직전 초기화 |
| run_phase | 태스크 | 전체 병렬 ↔ | 실제 트래픽 실행 (objection으로 종료 제어) |
| extract_phase | 함수 | Bottom-Up ⬆ | 결과 수집 |
| check_phase | 함수 | Bottom-Up ⬆ | Pass/Fail 판정 |
| report_phase | 함수 | Bottom-Up ⬆ | 결과 출력 |
| final_phase | 함수 | Top-Down ⬇ | 종료 처리 |
한 줄 요약: build는 위에서 아래로 컴포넌트를 만들고, connect부터는 아래에서 위로 연결하며, run_phase에서 모두 동시에 실행된다. 시뮬레이션 종료는 objection 카운터가 0이 될 때다.