UVM System verilog 문법

Seungyun Lee·2026년 2월 20일

UVM

목록 보기
1/14
post-thumbnail

포트 선언과 함수 호출

포트 선언 (uvm_analysis_port output_port;)

  • 모니터라는 요원 앞에 방송용 마이크를 하나 설치해 준 것입니다.

함수 호출 (output_port.write(item);)

  • 모니터가 마이크의 '방송 시작(write)' 버튼을 꾹 누르고, 자기가 방금 훔쳐본 택배 상자(item)의 내용물을 동네방네 소리치는 과정입니다.

Declare

`ifndef ADDER_ITEM_SV   // ifndef(If Not Defined): "만약  ADDER_ITEM_SV라는 
`define ADDER_ITEM_SV  //이름표가 아직 안 붙어있다면, 지금 붙이고(define) 코드를 읽어라

class adder_item extends uvm_sequence_item; //우리가 설계할 검증 환경의 이름을 adder_item
                                            //으로 지음, uvm_sequence_item 상속

Constructor

// Constructor
  function new(string name = "adder_item");
    super.new(name);
  endfunction
  • 개념: 공장에서 택배 상자가 막 찍혀 나와서 메모리에 처음 생겨날 때(태어날 때), 무조건 가장 먼저 자동으로 실행되는 초기화 함수입니다. OOP에서는 이를 '생성자(Constructor)'라고 부릅니다.

  • function new(string name = "adder_item");
    상자가 태어날 때 이름을 붙여줍니다. 기본값으로 "adder_item"이라는 이름표를 달아주는 것입니다.

  • super.new(name);
    내 부모 클래스(uvm_sequence_item)에게 "부모님, 저 지금 태어났고 이름은 이거예요. UVM 시스템(족보)에 저를 등록해 주세요!" 하고 신고하는 과정입니다. 이 줄이 빠지면 UVM이 이 상자의 존재를 모르게 되어 에러가 납니다.

build_phase(), uvm_config_db()

이 코드는 UVM 전체를 통틀어 가장 많이 쓰이면서도, 소프트웨어와 하드웨어를 연결하는 가장 결정적인 역할을 하는 부분입니다.

이 코드는 "시뮬레이션 시작(build_phase)과 동시에 우체통(uvm_config_db)을 열어서 리모컨(vif)을 꺼내 내 주머니에 넣고, 만약 리모컨이 없으면 즉시 시뮬레이션을 중단시켜라"라는 완벽한 논리 구조를 가지고 있습니다.

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
  
    if(!uvm_config_db#(virtual adder_if)::get(this, "", "vif", vif)) begin
      `uvm_fatal("DRV", "Failed to get virtual interface from config_db!")
    end
  endfunction
  • build_phase: 시뮬레이션 시간이 1나노초라도 흐르기 전, 즉 시간 0초에 모든 검증 장비들을 조립하고 세팅하는 함수입니다.
    하드웨어에 물리적인 클럭이 뛰기 전에, 드라이버가 먼저 "리모컨"을 손에 쥐고 준비를 마쳐야 하므로 반드시 이 Phase 안에서 코드를 작성해야 합니다.

  • super.build_phase(phase);: "부모 클래스(uvm_driver)가 원래 하던 기초 공사도 빼먹지 말고 해라!"라는 객체 지향(OOP)의 기본 규칙입니다.

우체통 뒤지기 (uvm_config_db::get)

#(virtual adder_if): "내가 우체통에서 꺼내려는 물건의 종류(Type)는 virtual adder_if야!"라고 명시합니다.
this 와 "": "내 현재 위치(Driver)를 기준으로 우체통을 뒤져볼게."라는 뜻의 경로 설정입니다.
"vif" (문자열): 누군가(최상위 테스트벤치)가 우체통에 리모컨을 넣을 때 붙여둔 이름표(Key)입니다. 이 이름표가 정확히 일치해야 물건을 찾을 수 있습니다.
vif (변수명): 우체통에서 성공적으로 꺼낸 진짜 리모컨을 내 주머니(Driver 클래스 안에 선언해 둔 vif 변수)에 집어넣어라! 라는 뜻입니다.

방어적 프로그래밍 (if(!...) 와 uvm_fatal)

uvm_config_db::get(...) 함수는 우체통에서 물건을 찾는 데 성공하면 1을, 못 찾으면 0을 반환합니다.

connect_phase()

build_phase, connect_phase, run_phase 등 모든 Phase의 첫 줄에는 무조건 super.xxx_phase(phase);를 습관처럼 박아넣고 시작하는 것입니다.

 virtual function void connect_phase(uvm_phase phase);
 super.connect_phase(phase);

build_phase (부품 생성): "공장에서 Driver, Monitor, Scoreboard 부품을 찍어내라!"

connect_phase (선 연결): "부품이 다 만들어졌으니, 이제 부품 간의 파이프(Port)를 찰칵찰칵 연결해라!"

type_id::create

drv = adder_driver::type_id::create("drv", this);

adder_driver: "공장아, 내가 예전에 도면 넘겨준 'adder_driver' 부품 있지?"

::type_id::create: "그 도면대로 지금 당장 메모리에 부품 하나를 실제 플라스틱으로 찍어내 줘!" (이것이 UVM Factory 메커니즘입니다.)

"drv" (문자열): "그리고 그 부품 겉면에 매직으로 'drv'라고 이름표를 써서 붙여놔." (나중에 에러가 나거나 로그를 띄울 때 이 이름표가 출력됩니다.)

this: "이 부품의 부모(소속)는 바로 나(현재 실행 중인 Agent 상자)야!"


convert2string()

개념: 나중에 시뮬레이션이 돌아갈 때 터미널(콘솔) 창에 에러나 동작 상태를 글자로 띄워야 하잖아요? 그때 "이 상자 안에 지금 무슨 숫자가 들어있는지 사람이 읽기 편한 글자(String)로 예쁘게 번역해 주는 기능"입니다.

virtual function string convert2string();
    return $sformatf("a=%0d, b=%0d | y=%0d", a, b, y);
  endfunction

virtual 키워드:

  • "나중에 누군가 나를 상속받아서 내 기능을 업그레이드(Override)하고 싶다면, 언제든 덮어써도 좋아!"라는 뜻의 유연한 함수 선언 방식입니다. UVM에서는 거의 모든 함수에 습관적으로 붙여줍니다.

$sformatf (String Format):

  • Verilog에서 화면에 바로 글자를 찍는 $display와 달리, 화면에 바로 출력하지 않고 글자(String) 데이터로 포장만 해주는 함수입니다.
  • %0d는 여백 없이 십진수(Decimal)로 숫자를 넣으라는 뜻입니다.
    -나중에 자동 채점기(Scoreboard)가 에러를 발견했을 때 이 함수를 불러서 [UVM_ERROR] 상자 내용물: a=5, b=10 | y=99 (정답 아님!) 이런 식으로 로그를 남길 때 아주 유용하게 쓰입니다.

`uvm_do(req)

// Generate and send 10 random transactions
    repeat(10) begin
      // The `uvm_do macro handles creation, randomization, and sending
      `uvm_do(req)
    end
  • 원래대로라면 상자를 만들고(create), 드라이버에게 보낼 준비를 하고(start_item), 무작위 값을 채우고(randomize), 진짜로 전송하는(finish_item) 긴 코드를 짜야 합니다. 하지만 이 단 한 줄의 매크로가 그 모든 과정을 알아서 해줍니다.
  • req의 정체: 우리가 선언한 적도 없는데 튀어나온 이 req는 uvm_sequence 부모 클래스 안에 이미 기본으로 만들어져 있는 '내 전용 택배 상자 변수'입니다.
  • 즉, repeat(10) 루프를 돌면서 "상자 만들고 무작위 값 채워서 던져!"를 10번 반복하는 아주 강력한 코드입니다.

`uvm_info() == display

`uvm_info는 한마디로 Verilog 하드웨어 설계에서 화면에 글자를 띄울 때 쓰시던 $display의 초강력 업그레이드 버전(스마트 로깅 시스템)입니다.

 `uvm_info("ID (이름표)", "실제 띄울 메시지", 중요도(Verbosity))
 `uvm_info("SEQ", "Finished generating items.", UVM_LOW)
  1. 첫 번째 방: ID (이름표)
    "SEQ": 이 메시지가 어디서 발생했는지 꼬리표를 달아줍니다.
    나중에 시뮬레이션 로그가 수만 줄씩 쏟아질 때, 터미널 검색창에 "SEQ"만 검색하면 대본(Sequence)에서 띄운 메시지만 깔끔하게 모아서 볼 수 있습니다. Driver에서 띄울 때는 "DRV"라고 달아주면 편하겠죠?

  2. 두 번째 방: Message (내용)
    "Starting the generation...": 화면에 띄우고 싶은 실제 텍스트입니다.
    $sformatf와 결합해서 변수값을 띄울 수도 있습니다.

 `uvm_info("DEBUG", $sformatf("[%0d] item: %0s", i, item.convert2string()),UVM_LOW)
  1. 세 번째 방: ★ 가장 중요한 Verbosity (중요도)
    $display를 버리고 UVM 매크로를 쓰는 가장 결정적인 이유입니다! 메시지의 '중요도 등급'을 매겨줍니다.
  • 등급 종류: UVM_NONE (최고 중요) > UVM_LOW > UVM_MEDIUM > UVM_HIGH > UVM_FULL > UVM_DEBUG (가장 사소함)

왜 쓸까요? 시뮬레이션 코드를 다 짜놓고 실행할 때, 터미널 명령어 하나로 "오늘은 UVM_LOW 등급 이상의 중요한 메시지만 화면에 띄워!"라고 지시할 수 있습니다.

uvm_config_db# :: set/get

그냥 최상위 모듈에서 "우체통(config_db)에 넣어둘 테니, 필요한 놈이 알아서 꺼내 가!" 하고 던져버리면 끝입니다.
set(넣기)과 get(꺼내기)

공통 규칙: #() (택배 상자의 규격)
set을 하든 get을 하든, 괄호 앞의 #() 안에는 내가 주고받을 물건의 정확한 데이터 타입(Type)을 적어야 합니다.

: #(virtual adder_if), #(int), #(string)

주의: 넣을 때 #(int)로 넣었는데, 꺼낼 때 #(bit)로 꺼내려고 하면 타입이 안 맞아서 절대 꺼낼 수 없습니다.

1. set : 우체통에 물건 넣기 (보통 tb_top이나 Test에서 씀)

uvm_config_db#(타입)::set( 1.보내는위치, 2.받을사람주소, 3.이름표, 4.진짜물건 );

보내는 위치 (context): 보통 최상위 tb_top(하드웨어)에서 UVM 세상으로 보낼 때는 null을 씁니다. 만약 UVM 클래스 내부(Test 상자 등)에서 보낸다면 this를 씁니다.
받을 사람 주소 (inst_name): 물건을 수령할 권한이 있는 경로를 문자열로 적습니다.
"" : "이 우주에 있는 아무나 다 가져가라!"
"uvm_test_top.env.agent.
" : "Agent 상자 안에 있는 놈들(Driver, Monitor)만 가져가라!"
이름표 (field_name): 우편물 겉면에 적어두는 고유한 송장 번호(문자열)입니다. (예: "vif", "loop_count")
진짜 물건 (value): 실제로 넘겨줄 변수나 인터페이스입니다.

2. get : 우체통에서 물건 꺼내기 (보통 Driver, Monitor에서 씀)

uvm_config_db#(타입)::get( 1.내위치, 2.상세경로, 3.이름표, 4.내주머니 );

내 위치 (context): 물건을 찾는 주체입니다. 클래스 내부이므로 항상 this를 씁니다.

상세 경로 (inst_name): 내 위치(this)를 기준으로 추가 경로가 있는지 적습니다. 보통은 나 자신이 쓸 거니까 빈 문자열인 ""를 씁니다.

이름표 (field_name): set에서 보낸 사람이 적어둔 그 송장 번호와 토씨 하나 안 틀리고 똑같이 적어야 합니다. (예: "vif")

내 주머니 (value): 우체통에서 꺼낸 물건을 저장할 내 클래스 내부의 변수 이름입니다.

uvm.macros.svh

`uvm_component_utils(adder_driver)

결론부터 말씀드리면, uvm_macros.svh는 UVM 라이브러리를 만든 사람들이 제공하는 "수십 줄짜리 복잡한 코드를 단 한 줄로 줄여주는 마법의 단축키(Macro) 모음집"입니다.


test_reg_access

virtual task run_phase(uvm_phase phase);

virtual task run_phase(uvm_phase phase);

시뮬레이션 0초부터 시작되는 메인 동작 무대입니다.

phase.raise_objection(this, "TEST_DONE");

phase.raise_objection(this, "TEST_DONE");
phase.drop_objection(this, "TEST_DONE");

UVM은 아무도 raise_objection을 부르지 않으면 시뮬레이션을 0초 만에 끝내버립니다.

테스트를 시작할 땐 반드시 raise_objection을 부르고, 끝날 땐 drop_objection을 불러야 시간이 제대로 흐릅니다. (시작과 끝의 괄호 같은 존재입니다.)

cfs_apb_item_drv item = cfs_apb_item_drv::type_id::create("item");

cfs_apb_item_drv item = cfs_apb_item_drv::type_id::create("item");

공장(type_id::create)에 지시하여, 비어있는 택배 상자(item)를 1개 새로 찍어냅니다.

Factory Create (공장 주문 방식)
항상 타입::type_id::create("이름") 이라는 마법의 주문을 외워서 부품이나 상자를 찍어내야 한다는 사실 자체를 외워두세요.

void`(std::randomize(item)); or item.randomize()

void`(std::randomize(item));
item.randomize()

Randomization (랜덤화)

item.randomize() 또는 std::randomize(item)을 호출하는 순간, 클래스 안에 rand 라고 선언해둔 모든 변수들에 겹치지 않는 무작위 값들이 알아서 꽉꽉 채워집니다. 이것이 UVM 검증의 핵심 파워입니다.

profile
RTL, FPGA Engineer

0개의 댓글