[UVM] UVM 완전 정리 | 구조, Sequence, Driver, Monitor, Scoreboard까지 한 번에

2026. 4. 10. 20:02UVM

반응형

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_phaseconnect_phaserun_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

randconstraint를 활용하면 랜덤 자극을 자동으로 생성하면서도 원하는 조건을 강제할 수 있다.


5. uvm_sequence (시나리오)

📌 심화 글: uvm_sequence 심화 — start(), body(), 서브 시퀀스, mid_do/post_do

uvm_sequenceuvm_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 환경도 읽고 수정할 수 있다.