2025. 4. 4. 00:29ㆍUVM
UVM을 실무에서 사용하다 보면 문서에서 나오는 표준적인 구조 외에, 실제 동작에서 헷갈리거나 예상과 다른 결과를 마주할 때가 많다. 나도 그랬다. 이 글은 내가 경험한 시행착오와 그 과정에서 정리된 내용을 바탕으로, UVM 시퀀스 실행 구조, 랜덤 변수 처리, config_db를 통한 값 공유 방식에 대해 설명한다.
시퀀스는 언제, 어떻게 실행되는가?
UVM에서 시퀀스는 .start()를 호출해야만 실제로 실행된다. 실행되면 내부적으로는 다음과 같은 순서로 진행된다.
- randomize() 자동 호출
- pre_body() → body() → post_body() 순서로 실행
이 전체 흐름은 run_phase() 안에서 이루어져야 하며, build_phase()에서는 시퀀스를 create()할 수는 있지만, randomize나 실행은 권장되지 않는다. 환경이 아직 완전히 세팅되지 않았기 때문이다.
시퀀스가 실행되는 위치 (run_phase vs build_phase)
- run_phase(): 실제 시퀀스를 실행 (.start())하는 곳
- 시퀀스가 실행되려면 .start()가 호출되어야 함
- randomize()는 run_phase에서 수행하는 것이 안정적임
- build_phase(): 시퀀스나 컴포넌트 생성 (create())만 수행
- 아직 환경이 완전히 준비되지 않았기 때문에 randomize() 사용은 위험함
나는 .start()를 안 했는데도 시퀀스가 실행되던데?
실제로 내가 처음 UVM을 사용할 때 가장 헷갈렸던 부분이 이거다. 테스트에서 명시적으로 .start()를 한 시퀀스는 하나였지만, 그 외에도 다른 시퀀스들이 실행되고 있었던 것이다.
알고 보니 아래와 같은 경우에 시퀀스가 자동 실행될 수 있었다.
- 상위 시퀀스 내부에서 하위 시퀀스를 create()하고 .start()한 경우
- 시퀀서에 default_sequence로 등록되어 자동 실행되는 경우
- uvm_do(), uvm_rand_send() 같은 매크로를 사용하는 경우
- 또는 트랜잭션(item)을 직접 start_item() / finish_item()으로 처리한 경우
이런 상황에서는 .start()가 코드에 없더라도 실행 흐름이 자연스럽게 이어지기 때문에 실행 로그를 찍어보지 않으면 헷갈릴 수 있다.
랜덤 변수는 언제, 어떻게 처리하는 게 좋은가?
처음에는 모든 rand 변수를 randomize()로 한꺼번에 처리했는데, 내부 constraint가 꼬여서 에러가 나는 일이 많았다. 그러다 알게 된 게 randomize() with {} 구문이다. 이 방식은 필요한 변수만 랜덤화할 수 있어 유용하다.
rand bit mode;
rand int id;
randomize() with { mode == 1; } // id는 랜덤화되지 않음
- randomize(): 클래스 내 모든 rand 변수에 대해 랜덤화 시도
- randomize() with { ... }: 명시한 변수만 랜덤화하고 나머지는 무시
- 내부 함수나 constraint에서 다른 rand 변수를 참조하면 에러 가능성 있음
동일 랜덤 변수를 여러 시퀀스에서 공유하고 싶을 때
- 상위 control 시퀀스에서 rand bit shared_var; 선언하고
- randomize() 후 config_db에 set()
- 하위 시퀀스들 (class_1_1, class_2_3 등)은 get()으로 받아서 동일한 값 사용
// 상위에서
rand bit shared_var;
randomize();
uvm_config_db#(bit)::set(null, "*", "shared_var", shared_var);
// 하위에서
bit shared_var;
if (!uvm_config_db#(bit)::get(this, "", "shared_var", shared_var))
`uvm_fatal("CFG", "shared_var not found");
uvm_config_db::set()의 파라미터
uvm_config_db#(T)::set(
uvm_component context, // 보통 this 또는 null
string inst_name, // 경로: "*", "env.agent", 등
string field_name, // 키 이름
T value // 전달할 값
);
config_db를 사용할 때 꼭 알아야 할 것들
처음엔 단순히 set과 get만 알면 될 줄 알았지만, 실제로는 경로, 시점, 타입이 매우 중요하다.
- set()은 값이 확정된 이후 (보통 run_phase)에서 수행
- get()은 해당 값을 실제로 사용하는 곳(보통 body())에서 수행
- set()보다 get()이 먼저 실행되면 get() 실패함 (fatal 발생)
- 경로(inst_name)는 set과 get이 동일하거나, \"*\"로 매칭이 가능해야 함
- 타입도 정확히 일치해야 하고, rand bit를 set한 경우 get 쪽에서도 bit로 선언해야 함
예상보다 이런 작은 차이에서 get 실패가 발생하는 경우가 많았고, 그로 인해 fatal 에러가 나는 경우도 있었다.
'UVM' 카테고리의 다른 글
[UVM] uvm 자주사용하는 매크로 정리 | uvm_do_on_with, start_item, uvm_object_utils ... (0) | 2025.04.07 |
---|---|
[UVM] new 생성자와 void 함수의 차이점 | 객체 초기값 설정 (1) | 2024.10.26 |
[UVM] super란? | 상위 클래스 메소드 호출, 상위 클래스의 생성자 호출 (0) | 2024.10.26 |
[UVM] Factory란? | 오버라이드 차이 (0) | 2024.10.26 |