UVM에 사용되는 구성 요소들은 모두 UVM 라이브러리에 있는 class를 상속 받아 작성되며, 이것을 base class라고 지칭하겠습니다. 오늘은 이러한 base class에 대해 알아보겠습니다.
(사진 및 내용은 이곳을 참고했습니다: chipverify)
UVM 계층 구조
UVM 계층 구조입니다. 화살표는 화살표 방향의 class를 상속받고 있다는 뜻입니다.
uvm_object
가 uvm_void
를 상속받는 것이죠.Driver
, Monitor
같은 component들은 uvm_component
를 상속받습니다.이제 UVM 라이브러리에 있는 함수들을 뜯어보겠습니다.
virtual class uvm_void;
endclass
UVM 환경의 모든 component
와 object class
는 uvm_object
라는 기본 class에서 파생됩니다.
이 녀석은 void보단 알아볼게 많습니다. 코드를 뜯어보겠습니다.
virtual class uvm_object extends uvm_void;
// Creates a new uvm_object with the given name, empty by default
function new(string name="");
// Utility functions
function void print (uvm_printer printer=null);
function void copy (uvm_object rhs, uvm_copier copier=null);
function bit compare (uvm_object rhs, uvm_comparer comparer=null);
function void record (uvm_recorder recorder=null);
...
// These two functions have to be redefined by child classes
virtual function uvm_object create (string name=""); return null; endfunction
virtual function string get_type_name (); return ""; endfunction
endclass
uvm_object
클래스의 주요 역할은 print
, copy
, compare
, record
와 같은 common utility function과 또 다른 유용한 function들을 정의하는 것입니다. (4개 말고 더 있습니다.)4개의 common utility function말고 밑에 작성된 create
와 get_type_name
은 무엇일까요?
create
: 새로운 객체를 생성하는 method. 이 method는 기존 객체와 동일한 type의 객체를 생성하고 반환합니다.get_type_name
: 객체의 타입 이름을 반환하는 method입니다.
uvm_object
를 상속받는 모든 클래스는 반드시 create
와 get_type_name
이라는 pure virtual method
를 구현해야 합니다. (virtual
이 붙으면 재정의가 가능하다는 것이고 pure
가 붙으면 반드시 해당 function을 받는 class에서 구현해줘야 한다는 뜻입니다.)
create
class my_object extends uvm_object;
...
// Implementation : Create an object of the new class type and return it
virtual function uvm_object create(string name="my_object");
my_object obj = new(name);
return obj;
endfunction
endclass
C++을 해보신 분이라면 생성자함수 new에 대해 아실텐데요, SystemVerilog에도 new
가 있습니다. 하지만 UVM에서는 create
라는 메소드도 많이 사용합니다. 물론 new
도 사용하구요. new
와 create
는 다릅니다.
new와 create의 차이
이 차이는 객체 생성과 관리를 유연하게 설계할 수 있도록 해줍니다. 특히, create
는 UVM 환경에서 동적으로 객체를 생성하고 관리하는 데 중요한 역할을 합니다.
get_type_name
class example_object extends uvm_object;
// This static method is used to access via scope operator ::
// without having to create an object of the class
static function string type_name();
return "example_object";
endfunction
virtual function string get_type_name();
return type_name;
endfunction
endclass
get_type_name
은 class type의 이름을 반환하는 function입니다.
그러면 이제 의문이 생깁니다. 얘네 둘을 어떻게 구현하는데?? 어려운거 아니야?
걱정 안하셔도 됩니다. uvm에는 macro가 존재합니다. macro로 아주 쉽게 구현할 수 있습니다. Macro에 대해서는 다음에 작성해보겠습니다.
아무튼 uvm_object
에 대해서 요약하자면 UVM 환경에서 사용되는 객체들이 더 다양한 동작을 할 수 있도록, 개발자가 편하도록! 유용한 fucntion을 내장하고 있다는 것입니다.
솔직히 이런게 있는 줄 몰랐습니다. Report기능을 사용만 해봤지 알아본건 포스팅하면서 처음이네요.
실제 사용되는 Report개념은 다음 링크를 참조해주세요. 링크
Agent
, driver
와 같은 모든 주요 검증 component는 uvm_component
를 상속받습니다.
특징은 다음과 같습니다.
env0.pci0.master1.drv
와 같은 방식으로 계층 구조를 탐색할 수 있습니다. build
, connect
, run
등.Component
가 생성하거나 소비한 transaction을 transaction database에 기록하는 메서드를 정의합니다.uvm_factory
에 대한 인터페이스를 정의하며, 인스턴스나 타입에 따라 새로운 컴포넌트/객체를 생성하는 데 사용됩니다.아무래도 많은 class를 상속받은 상태이다 보니 기능이 많습니다.
uvm_object
의 확장판으로, timing 및 record 기능을 포함한다고 합니다.
단순 트랜잭션은 uvm_transaction
에서 파생될 수 있지만, sequence-enabled transactions
은 반드시 uvm_sequence_item
에서 파생되어야 합니다.
sequence-enabled transactions
란?
uvm_sequence_item
클래스에서 파생되어야 하며, 시퀀스와 드라이버 간의 상호작용에 필요한 메커니즘을 지원합니다.예를 들어, 아래의 sequence
를 보면.
class gen_item_seq extends uvm_sequence;
`uvm_object_utils(gen_item_seq)
function new(string name="gen_item_seq");
super.new(name);
endfunction
rand int num; // Config total number of items to be sent
constraint c1 { soft num inside {[10:50]}; }
virtual task body();
for (int i = 0; i < num; i ++) begin
Item m_item = Item::type_id::create("m_item");
start_item(m_item);
m_item.randomize();
`uvm_info("SEQ", $sformatf("Generate new item: %s", m_item.convert2str()), UVM_HIGH)
finish_item(m_item);
end
`uvm_info("SEQ", $sformatf("Done generation of %0d items", num), UVM_LOW)
endtask
endclass
Item
이라는 transaction
을 create
해서 이것 저것 처리를 해주고 있습니다. randomize를 하고 그 동작을 반복하고 있죠. 이렇게 sequence
에서 transaction
을 사용하는 경우에는 Item
, 즉 transaction
을 아래처럼
class Item extends uvm_sequence_item;
`uvm_object_utils(Item)
rand bit in;
bit out;
virtual function string convert2str();
return $sformatf("in=%0d, out=%0d", in, out);
endfunction
function new(string name = "Item");
super.new(name);
endfunction
constraint c1 { in dist {0:/20, 1:/80}; }
endclass
uvm_sequence_item
으로 상속받아야 합니다.
시뮬레이션이 실행될 때 자동으로 생성되는 암묵적인 최상위 UVM 컴포넌트이며, 사용자는 전역 변수 uvm_pkg::uvm_top
을 통해 접근할 수 있습니다. uvm_root
는 다음과 같은 특징을 가집니다.
1. 부모가 null
로 설정된 모든 컴포넌트는 uvm_top
의 자식이 됩니다.
즉, 명시적으로 부모가 설정되지 않은 컴포넌트는 기본적으로 uvm_top
에 속하게 됩니다.
2. 모든 컴포넌트의 phasing
를 관리합니다.
예를 들어, build
, connect
, run
과 같은 페이징 단계의 동작을 조율합니다.
3. 계층적 이름을 기반으로 컴포넌트를 검색하는 데 사용됩니다.
uvm_top은 전체 UVM 계층 구조에서 특정 컴포넌트를 찾아내는 데 유용합니다.
4. 전역적으로 Report 세부 강도(verbosity)를 설정할 수 있습니다.
시뮬레이션의 보고 메시지 출력 수준을 조정하는 데 사용됩니다.
5. UVM의 report 메카니즘은 uvm_component 외부에서도 접근할 수 있습니다.
모듈(module)이나 시퀀스(sequence)와 같은 곳에서도 보고 기능을 사용할 수 있습니다.
6. end_of_elaboration phase
에서 오류를 확인하고, UVM_FATAL
오류를 발생시켜 시뮬레이션 시작을 차단합니다.
저는 이걸 보고 나도 모르는 사이에 UVM이 잘 돌아가기 위해 많은 역할을 해주는구나 싶었습니다 ㅎㅎ.
오늘은 UVM의 기본, 뿌리가 되는 Class에 대해 알아보았습니다. 저도 처음엔 마냥 UVM사용법, 기능, 예제를 많이 보았는데, 이런 기본이 되는 코드를 뜯어보고 역할을 알아보는 것이 이해에 도움이 되더라구요. 그래서 도움이 되시라고 작성해보았습니다.
하트 댓글 환영합니다~!!