UVM Factory의 핵심 목적은 "사용자가 만든 UVM 클래스들을 상속 받아 새로운 기능을 추가하거나 변경할 수 있도록 하면서, 기존 테스트벤치 코드는 수정하지 않도록 하는 것"이다.
[uvm_component] or [uvm_object]
↓ 상속
[사용자 정의 클래스] ex) my_monitor, my_seq, my_scoreboard
↓ 상속
[확장 클래스] ex) my_monitor_v2, my_seq_v2
Factory를 사용하는 이유에 대해 자세히 알아보자. SystemVerilog에서 new()는 클래스 객체를 생성할 때 사용된다.
기존의 테스트벤치가 Wishbone v1.0 프로토콜에 기반한 데이터 패킷 클래스를 사용하고 있다고 가정해 보자. 이 클래스는 드라이버, 모니터, 스코어보드, 다양한 시퀀스 등 테스트벤치의 여러 컴포넌트에서 폭넓게 사용된다.
만약 Wishbone v2.0이 출시되어 테스트벤치가 새로운 프로토콜에 맞는 패킷 정의를 사용해야 한다면, 테스트벤치의 여러 곳에서 코드를 수정해야 할 필요가 생기고, 이는 매우 비효율적이다.
class wb_seq extends uvm_sequence_item; // UVM 시퀀스 아이템을 상속받아 사용자 정의 시퀀스를 구현
...
virtual task body(); // 시퀀스 동작의 핵심 로직이 정의되는 메서드
// new() function allocates space for the new class object
// and assigns the handle 'm_wb_pkt' to new object
wb_pkt m_wb_pkt = new(); // Wishbone 패킷 객체를 직접 생성함 (factory 사용 안함 → override 불가능)
// This new object may be used everywhere in the sequence
start_item(m_wb_pkt); // 시퀀서에게 트랜잭션 요청 시작 알림 (driver가 받아 처리함)
m_wb_pkt.randomize(); // 패킷 내부 필드를 제약 조건에 따라 랜덤화
... // 이후 추가적인 시퀀스 로직 (예: finish_item, 에러체크 등)
endtask
endclass
이를 해결하기 위해 UVM에는 Factory(팩토리) 라는 기능이 있어서, 사용자가 테스트벤치 코드에서 이미 사용 중인 클래스 인스턴스들을 직접 수정하지 않고도 그 타입을 다른 타입(자식 클래스 등)으로 교체하거나 수정할 수 있도록 해준다.
즉, 기존에 사용하던 데이터 패킷 클래스 이름을 직접 코드에서 바꾸는 대신, 필요한 변경 사항을 포함한 자식 클래스 객체를 새로 정의하고, UVM Factory를 통해 해당 자식 클래스 객체를 테스트벤치 전반에 걸쳐 자동으로 반환받을 수 있다.
이러한 이유로, UVM 테스트벤치에서는 객체를 생성할 때 new()가 아닌 create() 메서드를 사용하는 것이 권장되는 방식이다.
class wb_seq extends uvm_sequence_item; // UVM 시퀀스 아이템을 상속받은 사용자 정의 시퀀스 클래스
...
virtual task body(); // 시퀀스의 주요 동작을 정의하는 메서드 (시뮬레이션 중 실행됨)
// Factory의 create() 메서드를 호출하여 원하는 타입(wb_pkt)의 인스턴스를 생성
// 이 방식은 추후 테스트 클래스 등 상위 계층에서 자식 클래스로 오버라이드가 가능함
wb_pkt m_wb_pkt = wb_pkt::type_id::create("wb_pkt", this); // Factory를 통해 wb_pkt 객체 생성
// 생성된 객체는 시퀀스 내 어디서든 사용할 수 있으며,
// 아래는 시퀀서와 드라이버 간 트랜잭션을 시작하는 동작
start_item(m_wb_pkt); // 드라이버에게 트랜잭션 시작 알림 (seq_item_port 통해 연결됨)
m_wb_pkt.randomize(); // 필드 값 랜덤화 (제약조건이 있다면 그에 따라 값 생성)
... // 이후 로직 (예: finish_item, 체크, 응답 처리 등)
endtask
endclass
C 언어 관점에서 factory를 설명하자면, "원래 코드에 직접 박혀 있는 변수 선언(=new)을, 함수처럼 한 곳에서 제어 가능한 생성기(generator) 로 바꾸는 개념"이라고 생각하면 된다.
또는, "변수 선언을 함수화해서 나중에 어떤 구조체를 쓸지 바꾸는 방식"이라고 이해하면 될듯하다.
class pkt_v1 extends uvm_sequence_item;
`uvm_object_utils(pkt_v1)
function new(string name = "pkt_v1");
super.new(name);
endfunction
function void display();
$display("This is V1 Packet");
endfunction
endclass
class pkt_v2 extends pkt_v1;
`uvm_object_utils(pkt_v2)
function new(string name = "pkt_v2");
super.new(name);
endfunction
function void display();
$display("This is V2 Packet");
endfunction
endclass
class my_seq extends uvm_sequence;
`uvm_object_utils(my_seq)
virtual task body();
pkt_v1 p = new(); // ❌ 하드코딩 – 항상 V1으로 고정됨
p.display(); // 출력: "This is V1 Packet"
endtask
endclass
만약에 Factory를 사용하지 않는다면, 패킷 버전이 바뀔 때마다, 해당 클래스를 사용하는 모든 코드에서 직접 수정이 필요하다.
class my_seq extends uvm_sequence;
`uvm_object_utils(my_seq)
virtual task body();
pkt_v1 p = pkt_v1::type_id::create("p", this); // ✅ factory 기반 생성
p.display(); // 출력은 override 여부에 따라 달라짐
endtask
endclass
set_type_override_by_type(pkt_v1::get_type(), pkt_v2::get_type());
Factory를 사용하게 되면 새로운 패킷 버전을 상속받아서 만들고, 그걸 Factory에 등록해서 기존 코드에 주입하면 된다.
'이론 공부 > UVM (Universal Verification Methodology)' 카테고리의 다른 글
UVM Reporting Mechanism - `uvm_info (1) (0) | 2025.04.21 |
---|---|
UVM Testbench Structure - Constraint (0) | 2025.04.19 |
UVM Testbench Structure - 06. Scoreboard (0) | 2025.04.19 |
UVM Testbench Structure - 05. Monitor (0) | 2025.04.19 |
UVM Testbench Structure - 04. Driver (0) | 2025.04.19 |