[UVM] run_phase, build_phase | 시퀀스 구조와 랜덤 변수 공유 | config_db 사용법 등

2025. 4. 4. 00:29UVM

반응형

UVM을 실무에서 사용하다 보면 문서에서 나오는 표준적인 구조 외에, 실제 동작에서 헷갈리거나 예상과 다른 결과를 마주할 때가 많다. 나도 그랬다. 이 글은 내가 경험한 시행착오와 그 과정에서 정리된 내용을 바탕으로, UVM 시퀀스 실행 구조랜덤 변수 처리config_db를 통한 값 공유 방식에 대해 설명한다.


시퀀스는 언제, 어떻게 실행되는가?

UVM에서 시퀀스는 .start()를 호출해야만 실제로 실행된다. 실행되면 내부적으로는 다음과 같은 순서로 진행된다.

  1. randomize() 자동 호출
  2. 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 에러가 나는 경우도 있었다.

 

반응형