2026. 4. 10. 20:02ㆍUVM
uvm_sequence의 실행 흐름
시퀀스가 실행될 때 내부적으로 다음 순서로 메서드가 호출됩니다. 이 흐름을 정확히 이해해야 복잡한 시나리오를 제대로 제어할 수 있습니다.
// 실행 순서
start()
-> pre_start()
-> pre_body() // is_relevant() 체크 후
-> body() // 실제 시나리오 구현 위치
-> post_body()
-> post_start()
대부분의 경우 body()만 오버라이드하면 충분합니다. 나머지 훅은 공통 전처리/후처리가 필요한 기반 클래스(base sequence)를 만들 때 활용합니다.
start() — 시퀀스 실행의 진입점
start()는 시퀀서를 연결하고 시퀀스를 실행시키는 메서드입니다. Test나 상위 시퀀스에서 호출합니다.
task start(
uvm_sequencer_base sequencer, // 실행할 시퀀서
uvm_sequence_base parent_sequence = null,
int this_priority = -1,
bit call_pre_post = 1 // pre/post_body 호출 여부
);
// 사용 예
task run_phase(uvm_phase phase);
my_sequence seq;
seq = my_sequence::type_id::create("seq");
seq.start(env.agent.sequencer);
endtask
call_pre_post = 0으로 설정하면 pre_body()와 post_body()가 호출되지 않습니다. 하위 시퀀스(sub-sequence)를 start할 때 주로 사용합니다.
body() — 시나리오 구현
실제 트랜잭션을 생성하고 드라이버로 전송하는 핵심 메서드입니다.
class write_sequence extends uvm_sequence #(my_item);
`uvm_object_utils(write_sequence)
int unsigned num_trans = 10;
task body();
my_item req;
repeat (num_trans) begin
req = my_item::type_id::create("req");
start_item(req); // 시퀀서에 아이템 전송 요청
if (!req.randomize() with { addr inside {[0:255]}; })
`uvm_fatal("RAND", "randomization failed")
finish_item(req); // 드라이버가 item_done() 할 때까지 대기
end
endtask
endclass
start_item()과 finish_item()은 항상 쌍으로 써야 합니다. start_item() 이후 finish_item() 이전에 randomize를 호출해야 합니다.
pre_body() / post_body() — 전후 처리 훅
시퀀스 계층 구조에서 기반 클래스에 공통 로직을 넣을 때 사용합니다. 예를 들어, 모든 시퀀스 실행 전에 초기화 트랜잭션을 보내거나, 실행 후 상태를 확인하는 로직을 여기에 넣습니다.
class base_sequence extends uvm_sequence #(my_item);
`uvm_object_utils(base_sequence)
task pre_body();
// 모든 자식 시퀀스 실행 전 공통 처리
if (starting_phase != null)
starting_phase.raise_objection(this);
endtask
task post_body();
// 모든 자식 시퀀스 실행 후 공통 처리
if (starting_phase != null)
starting_phase.drop_objection(this);
endtask
endclass
class my_sequence extends base_sequence;
`uvm_object_utils(my_sequence)
task body();
// pre_body/post_body는 base_sequence에서 처리됨
// 여기서는 실제 시나리오만 구현
my_item req;
req = my_item::type_id::create("req");
start_item(req);
void'(req.randomize());
finish_item(req);
endtask
endclass
Objection과 시퀀스 — starting_phase
테스트에서 직접 seq.start()를 호출하는 경우는 test의 run_phase에서 objection을 관리합니다. 하지만 시퀀서의 default_sequence로 등록된 경우에는 시퀀스가 직접 objection을 raise/drop해야 합니다.
// default_sequence 방식 (구형, UVM 1.1)
// sequencer에서 자동으로 starting_phase 설정됨
// 권장 방식 — test에서 명시적으로 start + objection 관리
task run_phase(uvm_phase phase);
my_sequence seq = my_sequence::type_id::create("seq");
phase.raise_objection(this);
seq.start(env.agent.sequencer);
phase.drop_objection(this);
endtask
서브 시퀀스 — 시퀀스 안에서 시퀀스 호출
시퀀스 안에서 다른 시퀀스를 호출할 수 있습니다. 복잡한 시나리오를 작은 단위로 분리하는 핵심 기법입니다.
class config_then_write_seq extends uvm_sequence #(my_item);
`uvm_object_utils(config_then_write_seq)
task body();
config_sequence cfg_seq;
write_sequence wr_seq;
// 1단계: 설정 시퀀스 실행
cfg_seq = config_sequence::type_id::create("cfg_seq");
cfg_seq.start(m_sequencer, this, -1, 0); // call_pre_post=0
// 2단계: 쓰기 시퀀스 실행
wr_seq = write_sequence::type_id::create("wr_seq");
wr_seq.num_trans = 20;
wr_seq.start(m_sequencer, this, -1, 0);
endtask
endclass
m_sequencer는 현재 시퀀스가 실행 중인 시퀀서의 핸들입니다. 서브 시퀀스 호출 시 동일한 시퀀서를 전달합니다.
mid_do() / post_do() — 아이템 레벨 훅
트랜잭션 하나하나에 개입할 때 씁니다. mid_do()는 start_item()과 finish_item() 사이에, post_do()는 finish_item() 이후에 호출됩니다.
class monitored_sequence extends uvm_sequence #(my_item);
`uvm_object_utils(monitored_sequence)
// start_item 직후 (randomize 전)
function void mid_do(uvm_sequence_item this_item);
my_item req;
if ($cast(req, this_item))
`uvm_info("SEQ", $sformatf("about to send addr=0x%0h", req.addr), UVM_LOW)
endfunction
// finish_item 직후 (driver 처리 완료 후)
function void post_do(uvm_sequence_item this_item);
my_item req;
if ($cast(req, this_item))
`uvm_info("SEQ", $sformatf("sent addr=0x%0h", req.addr), UVM_LOW)
endfunction
task body();
my_item req;
repeat (5) begin
req = my_item::type_id::create("req");
start_item(req);
void'(req.randomize());
finish_item(req);
// post_do가 여기서 자동 호출됨
end
endtask
endclass
`uvm_do 매크로 시리즈 — create → randomize → start 자동화
앞서 start_item() / finish_item()을 직접 호출하는 방법을 살펴보았습니다. UVM은 이 과정을 한 줄로 단축하는 `uvm_do 매크로 시리즈를 제공합니다.
| 매크로 | 설명 |
|---|---|
`uvm_do(seq_or_item) | create → randomize → start (또는 start_item/finish_item) 자동 처리 |
`uvm_do_with(seq, { constraint }) | constraint 조건으로 randomize 후 실행 |
`uvm_do_on(seq, sequencer) | 특정 시퀀서 위에서 실행 (Virtual Sequence에서 활용) |
`uvm_do_on_with(seq, sqr, { ... }) | 시퀀서 지정 + constraint 지정 |
// 직접 호출 방식
req = my_item::type_id::create("req");
start_item(req);
if (!req.randomize() with { addr < 256; })
`uvm_fatal("RAND", "randomization failed")
finish_item(req);
// uvm_do_with 매크로로 동일한 동작
`uvm_do_with(req, { addr < 256; })
`uvm_do 계열 매크로는 내부적으로 create() → randomize() → start() (시퀀스) 또는 start_item()/finish_item() (아이템)을 자동으로 처리합니다. 아이템 전송을 위한 별도 헬퍼 매크로도 있습니다.
| 매크로 | 설명 |
|---|---|
`uvm_create(item) | 아이템 생성만 (randomize, start 없음) |
`uvm_rand_send(item) | randomize → start_item → finish_item |
`uvm_send(item) | start_item → finish_item (randomize 없이) |
// 수동 제어가 필요할 때: create + 직접 설정 + send
`uvm_create(req)
req.addr = 32'h1000; // 직접 값 할당
req.data = 32'hDEAD;
`uvm_send(req) // randomize 없이 그대로 전송
randomize() with {} — 선택적 랜덤화와 변수 공유
시퀀스에서 트랜잭션을 생성할 때, 모든 rand 변수를 한꺼번에 랜덤화하면 내부 constraint가 꼬여서 에러가 날 수 있습니다. randomize() with {} 구문을 사용하면 특정 변수만 조건을 걸어 랜덤화할 수 있습니다.
// 모든 rand 변수 랜덤화
req.randomize();
// addr만 조건 부여, 나머지는 자유 랜덤
req.randomize() with { addr inside {[0:255]}; };
// 특정 필드를 고정하고 나머지만 랜덤
req.randomize() with { mode == 1; id == 5; };
여러 시퀀스에서 동일한 랜덤 변수를 공유하려면 상위 시퀀스에서 randomize() 후 uvm_config_db에 set()하고, 하위 시퀀스에서 get()으로 받아 사용합니다.
// 상위 제어 시퀀스
class control_seq extends uvm_sequence;
rand bit shared_mode;
task body();
randomize();
uvm_config_db#(bit)::set(null, "*", "shared_mode", shared_mode);
// 하위 시퀀스 실행
sub_seq_a seq_a = sub_seq_a::type_id::create("seq_a");
seq_a.start(m_sequencer);
endtask
endclass
// 하위 시퀀스
class sub_seq_a extends uvm_sequence;
bit shared_mode;
task body();
uvm_config_db#(bit)::get(null, "*", "shared_mode", shared_mode);
`uvm_do_with(req, { mode == shared_mode; })
endtask
endclass
📌 관련 글: uvm_config_db 완전 정리 · Virtual Sequence 완전 정리 · UVM Phase 완전 정리
핵심 정리
시퀀스를 올바르게 설계하기 위한 포인트입니다. 첫째, body()는 반드시 task로 선언해야 합니다 — function으로는 start_item()/finish_item() 같은 blocking call을 쓸 수 없습니다. 둘째, start_item()과 finish_item()은 항상 쌍으로, 그 사이에서 randomize를 해야 합니다. 셋째, 서브 시퀀스 호출 시 call_pre_post=0으로 objection 중복 raise를 방지하세요. 넷째, 복잡한 시나리오는 작은 시퀀스 단위로 쪼개고 상위 시퀀스에서 조합하는 방식이 재사용성과 가독성 모두에서 유리합니다.