2026. 4. 10. 20:02ㆍUVM
UVM(Universal Verification Methodology)은 SystemVerilog 기반의 검증 표준 방법론이다. 반도체 설계 검증에서 사실상의 업계 표준으로, 이를 모르면 SoC 검증 엔지니어로 일하기 어렵다. 이 글에서는 UVM의 핵심 구조와 주요 클래스, 그리고 실전에서 쓰이는 패턴까지 한 번에 정리한다.
1. UVM이란?
UVM은 Accellera가 표준화한 검증 방법론으로, SystemVerilog 클래스 기반으로 구성된다. 이전에는 OVM(Open Verification Methodology), VMM(Verification Methodology Manual) 등이 경쟁했지만 현재는 UVM이 완전히 표준으로 자리잡았다.
UVM의 핵심 목표는 다음과 같다.
- 재사용성: 한 번 만든 검증 환경을 다른 프로젝트에도 쓸 수 있다
- 표준화: 팀 간, 회사 간 협업이 가능하다
- 자동화: 랜덤 자극 생성, 커버리지 수집, 비교 자동화
// UVM 사용을 위한 기본 import
import uvm_pkg::*;
`include "uvm_macros.svh"
2. UVM 계층 구조 (Hierarchy)
UVM 환경은 계층적으로 구성된다. 아래가 전형적인 구조다.
uvm_test
└── uvm_env
├── uvm_agent (Active)
│ ├── uvm_sequencer
│ ├── uvm_driver
│ └── uvm_monitor
├── uvm_agent (Passive)
│ └── uvm_monitor
└── uvm_scoreboard
각 컴포넌트는 uvm_component를 상속받고, build_phase → connect_phase → run_phase 순서로 동작한다.
3. UVM Phase
📌 심화 글: UVM Phase 완전 정리 — build_phase 실행 순서, 컴포넌트 간 동작 흐름, run_phase, objection
UVM은 시뮬레이션을 phase라는 단계로 관리한다. 주요 phase는 다음과 같다.
| Phase | 역할 |
|---|---|
build_phase |
컴포넌트 생성 및 설정 |
connect_phase |
TLM 포트 연결 |
start_of_simulation_phase |
시뮬레이션 시작 전 초기화 |
run_phase |
실제 시뮬레이션 (time 소비) |
extract_phase |
결과 추출 |
check_phase |
에러 체크 |
report_phase |
결과 리포트 |
class my_driver extends uvm_driver #(my_seq_item);
`uvm_component_utils(my_driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
// build_phase에서는 time이 소비되지 않음
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("DRV", "build_phase 시작", UVM_MEDIUM)
endfunction
// run_phase에서만 @(posedge clk) 같은 time 소비 가능
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
drive_item(req);
seq_item_port.item_done();
end
endtask
endclass
4. uvm_sequence_item (트랜잭션)
📌 심화 글: uvm_component vs uvm_object 완전 정리 — 차이점, 생성자, 팩토리 매크로
uvm_sequence_item은 검증에서 주고받는 데이터 패킷이다. AXI 트랜잭션, AHB 트랜잭션 등 DUT에 넣을 자극을 클래스로 정의한다.
class my_seq_item extends uvm_sequence_item;
`uvm_object_utils_begin(my_seq_item)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_int(wr_en, UVM_ALL_ON)
`uvm_object_utils_end
// 랜덤 변수 선언
rand bit [31:0] addr;
rand bit [31:0] data;
rand bit wr_en;
// 제약 조건 (constraint)
constraint addr_range_c {
addr inside {[32'h0000_0000 : 32'h0FFF_FFFF]};
}
constraint wr_ratio_c {
wr_en dist {1 := 70, 0 := 30}; // 쓰기 70%, 읽기 30%
}
function new(string name = "my_seq_item");
super.new(name);
endfunction
endclass
rand와 constraint를 활용하면 랜덤 자극을 자동으로 생성하면서도 원하는 조건을 강제할 수 있다.
5. uvm_sequence (시나리오)
📌 심화 글: uvm_sequence 심화 — start(), body(), 서브 시퀀스, mid_do/post_do
uvm_sequence는 uvm_sequence_item을 만들어서 sequencer에 보내는 시나리오 클래스다.
class write_seq extends uvm_sequence #(my_seq_item);
`uvm_object_utils(write_seq)
function new(string name = "write_seq");
super.new(name);
endfunction
task body();
my_seq_item item;
repeat(10) begin
item = my_seq_item::type_id::create("item");
start_item(item); // sequencer에게 item 요청
if (!item.randomize() with {wr_en == 1;})
`uvm_fatal("SEQ", "randomize 실패")
finish_item(item); // driver가 처리할 때까지 대기
end
endtask
endclass
start_item / finish_item 쌍이 핵심이다. finish_item은 driver가 item_done()을 부를 때까지 블로킹된다.
6. uvm_sequencer
📌 심화 글: Virtual Sequence 완전 정리 — 멀티 에이전트 환경 시퀀스 제어
uvm_sequencer는 sequence에서 받은 아이템을 driver에게 전달하는 중간 관리자다. 대부분 직접 코드를 작성하지 않고 파라미터화된 기본 클래스를 그대로 사용한다.
class my_sequencer extends uvm_sequencer #(my_seq_item);
`uvm_component_utils(my_sequencer)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
Sequencer와 Driver는 TLM(Transaction Level Modeling) 포트로 연결된다.
sequence → [seq_item_export] sequencer [seq_item_port] → driver
7. uvm_driver
uvm_driver는 sequencer로부터 트랜잭션을 받아서 DUT의 핀 레벨 신호로 변환하는 역할이다.
class my_driver extends uvm_driver #(my_seq_item);
`uvm_component_utils(my_driver)
virtual my_if vif; // DUT 인터페이스
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// config_db에서 인터페이스 가져오기
if (!uvm_config_db #(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("DRV", "vif를 config_db에서 찾을 수 없음")
endfunction
task run_phase(uvm_phase phase);
my_seq_item req;
@(posedge vif.clk); // 초기 동기화
forever begin
seq_item_port.get_next_item(req); // 아이템 수신
// 핀 드라이브
@(posedge vif.clk);
vif.addr <= req.addr;
vif.data <= req.data;
vif.wr_en <= req.wr_en;
vif.valid <= 1'b1;
@(posedge vif.clk);
vif.valid <= 1'b0;
seq_item_port.item_done(); // 처리 완료 통보
end
endtask
endclass
8. uvm_monitor
uvm_monitor는 DUT의 인터페이스를 패시브하게 관찰해서 트랜잭션으로 변환한다. DUT를 건드리지 않고 핀만 읽는다. Scoreboard나 Coverage collector에게 데이터를 전달한다.
class my_monitor extends uvm_monitor;
`uvm_component_utils(my_monitor)
virtual my_if vif;
uvm_analysis_port #(my_seq_item) ap; // 외부로 데이터 전송
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
ap = new("ap", this);
if (!uvm_config_db #(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("MON", "vif를 config_db에서 찾을 수 없음")
endfunction
task run_phase(uvm_phase phase);
my_seq_item item;
forever begin
@(posedge vif.clk);
if (vif.valid) begin
item = my_seq_item::type_id::create("item");
item.addr = vif.addr;
item.data = vif.data;
item.wr_en = vif.wr_en;
ap.write(item); // analysis port로 전송
end
end
endtask
endclass
9. uvm_scoreboard
uvm_scoreboard는 예측값과 실제값을 비교해서 DUT가 정상 동작하는지 검증한다.
class my_scoreboard extends uvm_scoreboard;
`uvm_component_utils(my_scoreboard)
// 입력(expected)과 출력(actual) 포트
uvm_analysis_imp #(my_seq_item, my_scoreboard) exp_port;
uvm_analysis_imp #(my_seq_item, my_scoreboard) act_port;
my_seq_item exp_q[$]; // expected queue
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
exp_port = new("exp_port", this);
act_port = new("act_port", this);
endfunction
// expected 아이템 수신
function void write_exp(my_seq_item item);
exp_q.push_back(item);
endfunction
// actual 아이템 수신 → 비교
function void write_act(my_seq_item item);
my_seq_item exp;
if (exp_q.size() == 0) begin
`uvm_error("SB", "예상 아이템이 없는데 실제 아이템 수신")
return;
end
exp = exp_q.pop_front();
if (item.data !== exp.data)
`uvm_error("SB", $sformatf("불일치! exp=0x%0h act=0x%0h", exp.data, item.data))
else
`uvm_info("SB", $sformatf("일치 확인 data=0x%0h", item.data), UVM_HIGH)
endfunction
endclass
10. uvm_agent
uvm_agent는 sequencer, driver, monitor를 묶는 컨테이너다. Active 모드(driver 포함)와 Passive 모드(monitor만)를 is_active 설정으로 선택한다.
class my_agent extends uvm_agent;
`uvm_component_utils(my_agent)
my_sequencer sqr;
my_driver drv;
my_monitor mon;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = my_monitor::type_id::create("mon", this);
if (get_is_active() == UVM_ACTIVE) begin
sqr = my_sequencer::type_id::create("sqr", this);
drv = my_driver::type_id::create("drv", this);
end
endfunction
function void connect_phase(uvm_phase phase);
if (get_is_active() == UVM_ACTIVE)
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
endclass
11. uvm_config_db
📌 심화 글: uvm_config_db 완전 정리 — Virtual Interface 전달, 와일드카드, 디버깅
uvm_config_db는 UVM의 전역 설정 데이터베이스다. 인터페이스, 설정값 등을 계층 간에 전달할 때 사용한다.
// 상위(testbench)에서 set
uvm_config_db #(virtual my_if)::set(null, "uvm_test_top.*", "vif", vif);
// 하위(driver/monitor)에서 get
uvm_config_db #(virtual my_if)::get(this, "", "vif", vif)
12. uvm_test
uvm_test는 검증 시나리오의 최상위 진입점이다. 어떤 환경(env)을 쓸지, 어떤 sequence를 실행할지를 결정한다.
class my_base_test extends uvm_test;
`uvm_component_utils(my_base_test)
my_env env;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create("env", this);
endfunction
task run_phase(uvm_phase phase);
write_seq seq;
phase.raise_objection(this); // 시뮬레이션 종료 방지
seq = write_seq::type_id::create("seq");
seq.start(env.agent.sqr);
#100;
phase.drop_objection(this); // 시뮬레이션 종료 허용
endtask
endclass
raise_objection / drop_objection이 없으면 시뮬레이션이 즉시 종료되므로 반드시 쌍으로 써야 한다.
13. `uvm_info / uvm_error / uvm_fatal 메시지
📌 심화 글: UVM Reporting 완전 정리 — Verbosity, 에러 제어, uvm_report_server
UVM은 자체 로깅 시스템을 제공한다.
| 매크로 | 설명 |
|---|---|
`uvm_info(ID, MSG, VERBOSITY) |
일반 정보 출력 |
`uvm_warning(ID, MSG) |
경고 (시뮬레이션 계속) |
`uvm_error(ID, MSG) |
에러 (에러 카운트 증가) |
`uvm_fatal(ID, MSG) |
치명적 오류 (즉시 종료) |
`uvm_info("DRV", $sformatf("addr=0x%0h data=0x%0h 드라이브", req.addr, req.data), UVM_MEDIUM)
`uvm_error("SB", "데이터 불일치 발생!")
`uvm_fatal("CFG", "인터페이스를 찾을 수 없음 — testbench 설정 확인")
Verbosity 레벨: UVM_NONE > UVM_LOW > UVM_MEDIUM > UVM_HIGH > UVM_FULL > UVM_DEBUG
14. Factory와 Override
📌 심화 글: UVM Factory 완전 정리 — type_id::create, Override, 커맨드라인 제어
UVM factory는 클래스를 런타임에 교체할 수 있게 해준다. 테스트마다 다른 driver나 sequence를 쓰고 싶을 때 유용하다.
// 기본 등록 (모든 uvm_object/component에 필요)
`uvm_component_utils(my_driver)
`uvm_object_utils(my_seq_item)
// 런타임 override
my_driver::type_id::set_type_override(my_err_driver::get_type());
// 또는 특정 경로만 override
my_driver::type_id::set_inst_override(
my_err_driver::get_type(), "uvm_test_top.env.agent.drv");
15. UVM 전체 흐름 요약
[Testbench Top]
↓ config_db::set(vif)
[uvm_test]
↓ build env
[uvm_env]
↓ build agent, scoreboard
[uvm_agent]
↓ build sequencer, driver, monitor
[run_phase 시작]
test → seq.start(sqr)
sequence: start_item → randomize → finish_item
sequencer: 아이템 받아서 driver에 전달
driver: 핀 드라이브 → DUT
DUT: 처리
monitor: 결과 관찰 → ap.write(item)
scoreboard: 비교 → pass/fail
정리
UVM은 처음 보면 복잡하지만 구조를 이해하면 패턴이 반복된다. 핵심은 이것이다.
- sequence → sequencer → driver 경로로 자극이 흐른다
- monitor → scoreboard 경로로 결과가 검증된다
- phase가 빌드, 연결, 실행을 순서대로 관리한다
- config_db가 계층 간 데이터를 전달한다
- factory가 런타임 클래스 교체를 가능하게 한다
실전에서는 이 기본 구조 위에 coverage collector, virtual sequence, register model(RAL) 등이 추가되지만, 위 구조만 확실히 이해하면 어떤 UVM 환경도 읽고 수정할 수 있다.
📚 UVM 심화 시리즈
▸ Phase 완전 정리 · TLM 완전 정리 · uvm_config_db
▸ uvm_sequence 심화 · Virtual Sequence
▸ Factory & Override · uvm_component vs uvm_object
▸ UVM RAL · Reporting 시스템