본문 바로가기

SystemVerilog

[SystemVerilog] DPI의 심화(3) - data exchange

SystemVerilog와 C 사이의 data exchange는 DPI-C interface를 통해 이루어진다.

이번 게시글에서는 SV가 C 함수를 호출한다는 것 자체보다, SV에서 C로 함수 호출시 argument를 건네줌으로써

data를 교환할 수 있다는 데에 초점을 맞추어서 본다!!

 

 

Data Mapping between SV and C

대부분의 SystemVerilog Data Type들은 C언어와 직접 대응되는 data type이 있는 반면, 

그렇지 않은 데이터 타입들(4 state variable이나 array)들은 DPI-C나 API에 define된 특정 type을 필요로 한다..

( 기존의 SystemVerilog Data Type이 아니라, user-defined? DPI에서 새롭게 정의된?)

SystemVerilog Type 과 C data type 대응
SV C
byte  char
shortint short int
int int
longint long int
real double
string char*
string[n] char*
chandle char*

 

위 sv 데이터 타입들은 c 데이터 타입과 호환이 간단하다.

 

 

SystemVerilog Type 과 DPI-defined type 대응
SV C
bit svBit
bit[n:0] svBitVecVal
logic svLogic
reg svLogic
logic[n:0] svLogicVecVal*
reg[n:0] svLogicVecVal*
int[] svOpenArrayHandle
byte[] svOpenArrayHandle
shortint[] svOpenArrayHandle
longint[] svOpenArrayHandle
real[] svOpenArrayHandle

위 sv data type들은 C에서 따로 정의된 data type을 사용한다. 아마 이부분이 제일 생소할 것이다. 

 

함수 호출시 argument(인자)를 전달하는 방법 두 가지.

SystemVerilog와 C에서는 함수 호출시 argument를 전달하는 방법엔 2 가지가 있다!

1. pass by value (call by value)

caller(함수 호출한 애)가 값을 전달하면, callee(호출받은 함수)는 값을 함수 내 variable에 copy하여 사용한다.

따라서 그 variable의 값을 변경시켜도, 함수 범위 밖에서의 값은 변하지 않는다. 

 

2. pass by reference (call by reference)

caller(함수 호출한 애)가 전달하려는 변수의 주소값(reference)을 전달하고 , callee(호출 받은 함수)는 값을 함수 내 포인터 variable에 그 data의 reference를 담는다. 결과적으로 함수 내 arg variable은 caller가 전달한 data에(바로 그 주소에) 직접 접근하게 된다.  따라서 함수에서 포인터 변수가 참조하는 값을 변경하면, 함수 범위 밖에서도 값이 변해있다.

 

 

SystemVerilog에서 DPI호출 시 pass by value인지, pass by reference인지는 argument direction에 의해서 결정된다.

 

 

1. pass by value (call by value)

// SV에서 call by value로 C함수 import할 때, 

function void f1(int a);                               // a는 input이지만 direction이 implicit.
function void f2(input int a);                     // explit direction mentioned

이처럼 system verilog는 argument의 direction이 input 이면, call by value이다. (값의 사본(copy)이 전달된다.)

 

// C에서 pass by value로 선언된 함수

void f1 (int a);

그냥 C 문법과 사실 별 다를 바 없음. 

 

2. pass by reference (call by reference)

// SV에서 Pass By Reference  C함수를 import할 때! 

function void f1(output int a);
// direction of a is output!!

이처럼 system verilog는 argument의 direction이 output 이면, call by reference이다. (값의 주솟값(reference)이 직접 전달된다.)

 

// C에서 Pass By Reference 함수를 선언할 때! 

void f1(int * a);

일반적인 C에서 Call by reference함수를 선언할 때와 동일하다.

a라는 pointer변수로 받은 reference를 담겠다. 

즉 caller가 전해준 변수를 직접 가리키겠다! 

 

 

 

 

백문이 불여일견 예제!

i) SV 의 int[] (int type array)를 C함수에서 svOpenArrayHandle과 맵핑하기.

// SV
import "DPI-C" function void comput_unsized_int_array ( input int i_value[], output int result[];

// C
void compute_unsized_int_array(const svOpenArrayHandle i_value, svOpenArrayHandle result);

- SV에서 compute_unsized_int_array라는 c 함수를 import 하여 사용한다.

input int i_value[]로 선언된 argument는 sv에서 호출 시 pass by value로 동작한다.

output int result[] 로 선언된 argument는 pass by reference로서 전달된다.

 

또한,

SV에서 모든 array타입은 C에서 svOpenArrayHandle 타입(DPI-defined)와 호환된다.

c함수에서 선언된 array arg는 pass by value, pass by reference  모두 svOpenArrayHandle타입으로 선언된다. 포인터처럼 (*) 붙일 필요 X ( 이름에서 예측할 수 있듯이 이미 array handle이다.)

 

 

 

ii) SV의 struct 를 C함수에서 struct 타입과 맵핑하기.

// SV
`define BIT_ARRAY_SIZE 16
typedef struct {
	byte aByte;
    int anInt;
    bit aBit;
    longint aLongInt;
    bit[`BIT_ARRAY_SIZE-1:0] aBitVector;
} dpi_c_ex_s;

import "DPI-C" function void compute_struct (
	input dpi_c_ex_s i_value,
    output dpi_c_ex_s result
);

 

// C
typedef struct dpi_c_ex_s {
	char aChar;
	int anInt;
	svBit aBit;
	long int aLongInt;
	svBitVecVal aBitVector;
} dpi_c_ex_s;

void compute_struct(
	const dpi_c_ex_s* i_value, 
    dpi_c_ex_s* output
);

아주 아주 아주 충격적인 사실은 struct type은 SV에서 output 뿐만 아니라 input으로 선언된 struct변수 (즉 call by value)에 대해서도 pointer 형태로 받는다는 사실이다. 

 

** SystemVerilog LRM (시스템베릴로그 IEEE표준 공식 문서)에 따르면...

packed data(예를 들면 struct type, 여러 변수가 하나의 변수로 표현/참조되는 것을 말함)는 DPI 함수 호출 시, 

argument로서 전달될 때, data의 reference가 전달된다.

즉, SV가 C함수를 호출하면서 struct타입을 그냥(input으로, 즉 call by value로) 넘겨줘도, 

default가 struct의 reference가 넘겨진다는 뜻.

 

나의 추측은 struct type은 변수명이 struct 변수가 위치한 시작 주소를 뜻하지않을까 싶네. 마치 C에서 array 이름이 array시작 주소를 가리키는 것처럼. 사실상 struct 타입도 array처럼 연속된 주소 공간에 각 엘리먼트를 저장하는 꼴이니까.

 

svGetArrElemPtr1 

systemverilog에서 array type이 import C함수에서는 svOpenArrayHandle 라는 어레이 핸들이 된다는 것을 알았다.

그럼 요 타입으로 포인팅하는 변수들을 실제로는 어떻게 다룰까?

svGetArrElemPtr1 이라는 내장함수를 이용하면 array handle의 n번째 element를 가리키는 포인터(주소) 리턴한다.

(주소를 return하기 때문에 svGetArrElemPtr1( var, i) 사용시 * 라는 dereferencing operator를 사용해야함

 

예시 

 void func1 (svOpenArrayHandle var)
 {
     int i = 0 //element index
     int elem0 = *(uint8_t *)(svGetArrElemPtr1(var, i)); //read
     *((uint8_t *)var + i) = elem0 * 2; //multiply byte i with 2
 }