본문 바로가기

SystemVerilog

[SystemVerilog/UVM] Factory Pattern (팩토리 패턴)에 관하여.

Factory Pattern은 디자인 패턴이다.

 

0. 잡소리 ( 바쁘면 읽지 마세요 TMI 대잔치)

요즘은  UVM에 관해 구글링 하면 익사이팅(?)이라는 한 개인 블로그를 찾을 수 있어 자주 참고하지만 그때는 한국어로 작성된 UVM자료가 지금보다 더, 더, 더 희박했기 때문에.. 열심히 번역해도 모르는 개념이 있으면 알지 못한 채로 넘어가거나, 추측하는 수밖에 없었다.

UVM 관련 웹사이트에 기재된 포스팅을 공부하면서 컴포넌트, 시퀀스 등에 관해서는 약간의 감을 잡을 수 있었지만, 

UVM factory에 관해서는 정말 오리무중이었다. 뭐하자는 건지 알 수 없었다. 대충 'new를 직접 호출하지 않고, UVM에서 제공되는 어떤 특별한 메서드를 호출해야 한다.' 정도로만 이해했었다.

 

2020년 봄.. 이전 직장에 다닐 때, 종종 개발직군 친구랑 카페에서 공부를 하곤 했는데, 나는 그날도 UVM factory를 이해하기 위해 고군분투하고 있었다. 그런데 마침 옆에 있는 친구가 나한테 무슨 공부를 하고 있냐고 물어왔다.

 

UVM이란 건데... 하드웨어 설계 검증.. 어쩌고저쩌고... 설명을 해주고

이게 그친구가 하는 일반적인 sw 프로그래밍이랑 systemverilog 코딩/ UVM을 방법론을 차용한 코딩 어떻게 다른 코딩인지 설명하느라 진을 뺐다. (왜냐하면 나도 잘 모르고, 아직 공부하는 단계니까 ㅎㅎ)

그리고 혹시나 해서 팩토리 패턴에 관해 그 친구에게 물어봤다.

팩토리 패턴..? 그 친구는 한 내가 읽고 있는 문서를 한 번 읽어보고 1~2분간 생각하더니 대답했다

"이거 디자인 패턴이야~ 구글에서 디자인 패턴 한 번 검색해봐"

 

이렇게 해서 프로그래밍에서 디자인패턴이란 개념을 처음 접하게 됐지만 여전히 당시에는 팩토리 개념을 이해할 수 없었다. 

그렇게 그 시기를 그냥 넘기고, 작년엔 파이썬 공부를 좀 하면서 싱글톤 패턴, 데코레이터 패턴이란 예제를 통해 디자인 패턴의 개념을 익혔다. 문제 해결 과정을 담은 예제를 통해 디자인 패턴을 공부하니 개념을 확실하게 이해할 수 있었다. 

그러고 나서 올해 다시 UVM을 공부하면서 Factory Pattern을 공부하니 이제 조금 알 것 같다. 

 

여러 분야를 공부해보고 연관성을 지어보면서 돌고 돌아 여기까지 왔다.

처음 한번에 이해할 수 없었던 Factory Pattern을 드디어 이해하게 되어 매우 뿌듯하고 기쁘다. 

 

 

1. 디자인 패턴 

쉽게 말해 Programming Trick이다.

아래는 디자인패턴에 관해 위키피디아에서 발췌한 문장이다. 

- 특정 문맥에서 공통적으로 발생하는 문제에 대해 재사용 가능한 해결책이다.

아래는 나무위키에서 발췌한 문장이다. 

- 알고리즘은 아니며 상황에 따라 자주 쓰이는 설계 방법을 정리한 코딩 방법론이다.

 

디자인 패턴이란, 개발에 관한 문제들을 유형별로 나누어서 해결 방법을 제시한다. 

즉, 디자인 패턴은 개발의 유연성/확장성과 같은 문제를 풀기 위한 공식이다.

공식을 몰라도 문제를 풀 수는 있지만, 공실을 모른다면 풀이에 시간이 배로 걸리고 비효율적이다. 

 

그럼 팩토리 패턴은 어떤 디자인 문제를 해결하기 위한 패턴일까?

- 정답을 미리 말하자면, object instantiation을 dynamic 하게 만들어, 확장성과 유연성을 제공하기 위한 디자인 패턴이다. 

 

2. Hard-Coding 

Hard Coding이란, 데이터를 프로그램의 소스코드에 직접적으로 기록하여 작성하는 코딩 방식이다. 

하드코딩의 반대는, 데이터를 외부 소스로부터 얻어오거나 run-time동안 생성하는 방식이다.

 

따라서 Hard-coded data는 소스코드를 편집해야만 수정이 되며, 편집된 코드를 다시 recompilation 하여 executable을 만들어야만 한다. 

 

다음 hard-coding 예제를 보자... 

 

- 팩토리를 모르는 우리는.. class object 생성을 위해 new()메소드를 사용한다. 

( animal object는 new() constructor에서 age와 name을 argument로 받는다. )

lion_h = new(15, "Mustafa")
chicken_h = new(1, "Little Red Hen")

위와 같은 방식을 hard coding이라고 부른다. 

새로운 object를 만들고 싶으면 위 코드를 수정해야 한다. 

이러한 방식을 고수 할 때, animal list를 파일로부터 읽어오고 싶으면? 

소스 코드 수정을 하는 스크립트가 필요할 것이다..  안 그럼 불가능.

 

 

Factory Pattern이 해결책이 될 수 있다. 

3.  Factory Pattern

factory에 argument를 건네주고 object를 돌려받도록 하는 디자인 패턴이다. (new 메서드를 직접 호출하지 않음)

다음 예제에서, string을 argument로 사용해 animal 오브젝트를 생성하느 팩토리를 작성해보자.

먼저, Animal Class Hierarchy에 관한 전제이다. 

 

- lion과 chicken은 animal class를 extend한다. 

- lion과 chicken은 new() constructor와 make_sound라는 pure virtual method를 override 한다.

 

위와 같은 lion과 chicken 타입의 오브젝트를 리턴하는 factory를 작성해본다. 

한 가지 유의 점은, factory의 return type이 animal ( base class )이다. 

 왜냐하면 좀 더 generic 한 base class핸들로 오브젝트를 return 함으로써, 모든 타입에 관해 일관된 리턴 타입으로 구현하기 위함이다. 

실제 object는 child(lion이나 chicken) 일 지라도... base 핸들로 전달하고, 이를 다시 child class handle이 받아가면 된다.

 

class animal_factory;
	static function animal make_animal(
    		string species,
        	int age,
        	string name );
        
    	chicken chicken_h;
    	lion	lion_h;
    
    	case(species)
    		"lion" : begin
        		lion_h = new(age, name)
            	return lion_h;
        	end
        
        	"chicken" : begin 
        		chicken_h = new(age, name)
            	return chicken_h;
        
        	end
        
        	default : 
        		$fatal(1, {"No such animal : ", species})
    	
    
    	endcase
    
    endfunction
    
endclass

1) make_animal은 static function이다. testbench 어디서든 사용 가능

2) make_animal은 animal species를 string으로 받아 animal object handle로 return 한다. 

실제 object는 child class object이지만, return type은 이들의  base class로서 코드 수정 없이  구현한다. 

(부모 handle로 자식 object를 가리킨다? 이것이 바로 polymorphism... )

 

3) 다시 말해 factory의 핵심은 class의 polymorphsim에 있다.

animal을 return 하게 작성된 function이므로, lion이나 chicken 중 아무거나 return해도 된다. 

 

 

4. 시스템베릴로그 $cast와 polymorphsim

polymorphism

polymorphism이란 parent class handle로 child class object를 가리킬 수 있으며, 실제 인스턴스를 가리키고 있는 핸들의 타입에 따라 호출되는 함수나 접근하는 데이터 멤버가 달라짐을 의미한다.

(polymorphism을 직영하면 다형성인데, 가리키는 핸들에 따라 인스턴스의 속성이 다양해지므로 이러한 개념에 다양한 형태를 지녔다는 뜻의 다형성이란 이름을 붙인다.)

아래 예제를 살펴보면, 

initial begin 
	animal 	animal_h;
    lion	lion_h;
    chicken	chicken_h;
    
    animal_h = animal_factory::make_animal("lion",15,"Mustafa");
    
    animal_h.make_sound();
end

 

- 위 코드에서 make_animal()이란 static method를 호출함으로써, 15살 Mustafa라고 불리는 lion인스턴스를 생성하고 animal_h라는 핸들에 return 받았다. (실제 lion 인스턴스가 생성됐지만, 리턴할 때 타입과 리턴 받은 핸들 타입 모두 animal_h로 lion의 base class handle이다. )

- base class인 animal class에서 make_sound가 virtual로 선언되었었다. 

따라서  base_class 핸들이 아닌 실제 인스턴스 타입인 lion class의 make sound가 호출된다.

- 그러나 lion class의 data member인 thorn_in_paw 멤버를 animal_h핸들로 접근하고자 하면 compile error가 뜸

make_sound는 animal과 lion클래스 모두에 정의돼 있으나, thorn_in_paw는 lion에만 정의된 object이기 때문이다.

 

만약에 thorn_in_paw와 같은 lion에게만 있는 data member에 접근하고 싶다면, animal object handle에서 lion object handle에 assign 하고 접근해야 함.

 

------------------------------------------------------------------------------------------

 ** SystemVerilog에서.. 

 child class handle에서 parent class handle로의 assign은 그냥 "="operator를 사용한다.

parent class handle에서 child class handle로의 assign은 $cast라는 system task를 사용한다.

단, parent class handle이 본디 가리키고 있는 인스턴스는 child class의 타입이어야만 한다.

안 그러면 cast fail 

------------------------------------------------------------------------------------------

 

 

initial begin 
	animal 	animal_h;
    lion	lion_h;
    chicken	chicken_h;
    int cast_ok;
    
    animal_h = animal_factory::make_animal("lion",15,"Mustafa");
    
    animal_h.make_sound();
    
    cast_ok = $cast(lion_h, animal_h);
    // lion_h = animal_h라고 간주하자.
    
    
end

- $cast는 2nd arg에서 1st arg로 copy.

- $cast는 targe class가 child class여야 가능하다. 

 예를 들면 $cast(chicken, lion) 이런 거 안됨. 

- $cast system call은 cast가 성공하면 1을 return 한다. 

위 예제에서는 cast 성공 여부를 cast_ok라는 변수에 담았지만, 아래 코드와 같이 한 줄로 표현하는 것이 관습이다. 

if(!$cast(어쩌구))
	$fatal(어쩌구)
    //혹은 `uvm_fatal

 

 

 

5.  Factory Pattern 정리를 마치며... 

- source code의 수정 없이 서로 다른 type의 object를 생성하는 방법.

    - string argument를 받아 run-time동안 dynamic 하게 object를 return 

- string argument를 hard-coding 한 것처럼 보이지만, string 이야말로 file로 읽어오기 매우 간편한 인자이다. 

- UVM testbench를 구성할 때도 testbench component를 dynamically 생성하기 위해 factory pattern을 사용한다. 

- UVM factory override를 배우면, generic lion으로 요청된 모든 부분을 eastern mountain lion과 같은 override type으로 return 하도록 할 수 있음.