UVM을 잘 모르고 사용할 때, 가장 많이 드는 의문점
- uvm 클래스 정의할 때 왜 `uvm_component_utils나 `uvm_object_utils 같은 매크로를 사용하는거지?
- uvm 클래스의 객체를 생성할 때 왜 create 와 같은 메소드를 써야하는 거지? ( 일반 시스템베릴로그 클래스의 객체랑 다르지 왜 ? )
정답은 uvm 이란 프레임워크는 팩토리 패턴이란 디자인 패턴을 사용하기 때문이다.
팩토리패턴에 대해서 감을 잡고 싶다면?
[SystemVerilog/UVM] Factory Pattern (팩토리 패턴)에 관하여. (tistory.com)
이 글을 읽고와 주세요~ ^^
UVM factory란 무엇인가?
이름에서 유추 가능하듯이 uvm_factory는 uvm object와 component들을 생성(create)하기 위해 사용됩니다.
현재 진행중인 시뮬레이션에 대해서 uvm factory라는 인스턴스는 단 한 개만 존재합니다. (전문 용어로는 singleton이라고 한다.) (oop 프로그램에선 클래스를 정의하고 인스턴스를 마구잡이로 찍어낼 수 있었는데, 예는 특별한 용도로 한 개 인스턴스만 두는 거예요.)
UVM 클래스로 정의된 object나 component type은 '실제' object나 component에 대한 프록시를 사용하여 factory에 등록이됩니다.
uvm_object_registry#(T, Tname)나 uvm_component_registry#(T,Tname) 클래스들이 프록시를 위해 사용되는 클래스들입니다.
그런데 저런 uvm_object_registry#(T, Tname)나 uvm_component_registry#(T,Tname) 클래스들은 직접 사용할 일이 거의 없어요.
왜냐하면 저희는 UVM 매크로를 사용할거기 때문이에요. `uvm_component_utils 같은 애들 말입니다.
* 프록시란 무엇인가?
프록시란 용어가 가장 많이 쓰이는 곳은 네트워크 분야데, 외부 서버와 통신하는 역할을 이 프록시가 대신해줍니다. 따라서 클라이언트 입장에서는 프록시가 가장 가까운 서버가 되겠죠..? 서버란 뭘까요? 요청한 것을 주는 놈 정도로만 이해하면 됩니다.
프록시라는 영어 단어를 직역하면 대리인이란 뜻인데요. 객체 생성을 대리로 해주는 놈 정도로만 짚고 넘어가겠습니다.
UVM Factory는 name-based와 type-based 이렇게 두 가지 인터페이스 타입을 갖습니다. ( 이에 대한 설명은 나중에, 여기선 생략하겠습니다.)
-->> 자 여기까지 잠깐 정리하자면
UVM Factory는 UVM component 나 object의 생성을 대리로 해주는 녀석입니다.
component나 object 정의할 때 factory에다가 registration을 해주어야 합니다.
이놈이 왜 필요하냐면, UVM environment는 한 번만 구성을 해놓고, 여러 scenario를 시뮬레이션 하고 싶을 때,
scenario에 따라서 달라지는 object들을 동적으로 factory에서 받아올 수 있습니다.
즉 UVM Factory는 필요에 따라 원하는 type의 object를 동적으로 생성해줍니다.
UVM Factory Registration
위에서 배웠다시피,
UVM이 제공하는 팩토리패턴을 이용하면 내가 원하는 test scenario에 따라서 동적으로 오브젝트를 생성할 수 있다는 장점이 있다.
이를 위해서는 uvm내에서 component와 object를 정의할 때 따라야 할 규칙이 있다.
바로..
모든 uvm_component 와 uvm_object는 factory에 registered(등록)되어야 한다는 것이다.
이과정은 uvm에 pre-define된(미리 작성되어 UVM 프레임워크에서 제공하는) factory registration macro를 통해 이뤄집니다.
UVM utility & Field Macro
UVM utility macro는 UVM object나 component를 factory에 registration하는 과정을 아주 쉽고 간편하게 만들어줍니다.
사용법은 아래 처럼, class 정의시 `uvm_object_utils나 `uvm_component_utils와 같은 매크로를 한 줄 추가해주면 된다.
UVM Object Utility
class reg_seq extends uvm_sequence #(seq_item);
// factory registration for sequence
`uvm_object_utils(reg_seq)
...
...
endclass
// For parameterized class
class param_obj #(int WIDTH = 16, ID = 0) extends uvm_object;
typedef param_obj #(int WIDTH, ID) obj_p;
`uvm_object_param_utils(obj_p)
...
...
endclass
`uvm_object_utils 는 사실 다른 매크로들로 구성된 또 하나의 매크로인데, 어떻게 정의되어 있는지 더 자세히 보고 싶으면 구글 검색을 추천한다.
하지만 저거 한 줄로도 UVM 사용에 문제는 없다. ㅎㅎ
그리고 만약 uvm클래스 정의시 parameterized class를 정의하고 싶다면, `uvm_object_param_utils 를 사용하면 된다.
UVM Component Utility
class reg_driver extends uvm_driver;
// factory registration for driver component
`uvm_component_utils(reg_driver)
...
...
endclass
// For parameterized class
class param_env #(int WIDTH = 32, ID = 0) extends uvm_env;
typedef param_env #(int WIDTH, ID) env_p;
`uvm_component_param_utils(env_p)
...
...
endclass
마찬가지로 parameterized class를 정의하고 싶다면, `uvm_component_param_utils 를 사용하면 된다.
constructor new()
팩토리 생성을 위해서는 constructor를 정의할 때에도 정형화된 방식을 따라야 한다.
이 new()는 우리가 호출하는 게 아니다.
팩토리가 우리 대신 호출할것이라서 정형화된 패턴을 따르는 것이다.
1. object의 new()
// default constructor for sequence
function new(string name = "reg_seq")
super.new(name);
endclass
오브젝트의 디폴트 인자는 name 하나이다.
2. component의 new()
class env extends uvm_env;
`uvm_component_utils(reg_driver)
// default constructor for sequence
function new(string name = "env", uvm_component parent = null)
super.new(name, parent);
endclass
...
...
endclass
컴포넌트의 디폴트 인자는 name과 parent이다.
name은 object의 이름이고, parent는 이 객체가 생성되는 부모객체의 hande이다.
지켜져야 할 것은 super.new를 호출해야한다.
객체의 초기값에서는 default argument가 사용되며, uvm_component_registry라는 wrapper class의 메소드인 create()가 호출되면서 값이 override된다.
Component and Object Creation
이렇게 factory register 한 클래스의 객체를 생성할 때는...
우리가 배운대로 factory라는 싱글턴 오브젝트가 객체 생성의 대리인이 된다.
팩토리는 내가 create를 호출한 클래스가 팩토리에 register되어 있다면, 해당 타입의 인스턴스를 생성해서 리턴한다.
따라서, 우리는 객체를 생성할때 객체에 정의된 constructor인 new를 직접 호출하는 것이 아니라
Factory를 통해 생성된 객체를 받게 된다.
따라서 우리는 wrapper class의 create() 메소드를 호출하는 것이다.
component creation 문법
<instance_name> = <type>::<type_id>::create("<name>", <parent>)
object creation 문법
<instance_name> = <type>::<type_id>::create("<name>")
정리
여기까지 배운걸 정리해보자.
UVM component와 object의 인스턴스 생성은 팩토리라는 애가 대신한다.
팩토리는 다양한 시나리오에서 객체 생성을 동적으로 할 수 있도록 도와준다. ( 작성된 객체가 test 에 따라서 type이 다르게 instantiation 될 수 있다는 말씀.)
이를 위해서는 object나 component 정의 시에 factory registration을 해야 하고
new() 작성도 마음대로 하면안되고 어떤 규칙을 따라야 하며
실제 객체 생성부분을 작성시에는 new()가 아니라 wrapper class의 create()메소드를 호출한다.
예시
다음은 env에서 uvm component를 선언하고 생성하는 예시 코드이다.
class env extends uvm_env;
`uvm_component_utils(env)
mycomp compA;
param_comp #(.WIDTH(16), .ID(1)) compB;
// default constructor for sequence.. 얘는 env의 constructor이다.
function new(string name = "env", uvm_component parent = null)
super.new(name, parent);
endclass
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// component creation
compA = mycomp::type_id::create("compA", this); // env에서 compA를 생성
compB = param_comp #(16, 1)::type_id::create("compB", this); //env에서 compB를 생성
endfunction
task run_phase(uvm_phase phase);
myseq seqA;
param_seq #(.WIDTH(16), .ID(1)) seqB;
// object creation
seqA = myseq::type_id::create("seqA");
seqB = param_seq #(16,1)::type_id::create("seqB");
...
...
endtask
endclass
눈여겨 볼 것은
uvm_component, 즉 uvm testbench를 구성하는 기본 골조들은 build_phase에서 생성된다는 것이다.
그리고 uvm test시나리오를 실행하는 동적 요소들인 시퀀스는 run_phase에서 생성되어 시작된다는 것이다.
설명은 대충 여기서 무리 하겠지만..
그래도 팩토리를 왜쓰는지 감이 안잡히는 분들이 많을것 같다.
위에 걸어둔 링크 '팩토리패턴' 게시글을 꼭 읽어보시길 바랍니다.
내가 다른 테스트로 검증을 하고 싶을때, uvm env의 구성이 살짝씩 바뀌길 원한다면
test 정의하는 부분에서 uvm_type override를 통해서 uvm_env 내부 component를 다르게 생성할 수 있어요.
장점은.. test에 따라서 uvm_env를 새로 작성할 필요가 없다는 것입니다.
override에 관해서는 다음 게시글에서 다시 다룹니다.
'UVM' 카테고리의 다른 글
UVM 이란 무엇인가? (3) | 2024.09.28 |
---|---|
[UVM] UVM subscriber의 개념 잡기 (1) | 2024.04.28 |
[SystemVerilog/UVM] Factory Pattern (팩토리 패턴)에 관하여. (0) | 2022.10.30 |