3
votes

I have C code (predictor model) that can generate an array of variable length as its result. It is unknown before calling the C code what the size of this array is, and there is some amount of randomization involved (noise modelling)

I need to call this C predictor model from SystemVerilog, and get back the output result array.

As a newbie to DPI-C, I ran into 3 limitations:

  1. The results array on the SV side needs to be allocated prior to calling the C code. Since I do not know what the size will be, I stand the chance of overallocating or underallocating.
  2. I cannot pass an open array from C -> SV in an export function.
  3. An export function cannot be used for class methods(!)

To work around this, I created a hacky juggling between the 2 interfaces and global variables/tasks.

I have posted my solution and it works fine, but I would like to know if anyone has a more elegant solution than this. I especially do not like having to use global variables.

SV:

export "DPI-C" function allocate_mem;
export "DPI-C" function set_item;
import "DPI-C" context function void predictForMe (input int noiseA, input int noiseB);

int result[];

function void allocate_mem(int size);
  result = new[size];
endfunction

function void set_item(int index, int item);
  result[index] = item;
endfunction

class my_class;
  // constructor etc etc - assume is valid

  // my_func
  function void my_func();
    int noiseA = 10; // hardcode to simplify example
    int noiseB = 20; // hardcode to simplify example

    // call imported function
    predictForMe( noiseA, noiseB );
  endfunction
endclass

C:

extern void allocate_mem(int size);
extern void set_item(int index, int item);

void predictForMe(int noiseA, int noiseB)
{
  // do some calcualation based on noiseA and noiseB
  // generates an answer_array with num elements = X

  allocate_mem(X);
  for(i = 0; i < X; i++) set_item(i, answer_array[i]);

}

Any better solutions are welcome.

1
What is the range of X? - meaning-matters
It varies - could be anything between 2^1 and 2^32-1. I use a queue-type structure to dynamically grow and reallocate memory for my array - noobuntu
Ouch, that indeed a very big range. Is there perhaps a heuristic by which you can find the size upper bound (within some margin)? - meaning-matters
The upper bound can only be determined at runtime by randomization in the predictor. Limiting it in any way would degrade our performance. I used a queue structure to dynamically allocate/free memory in the C model but it is hard to predict what an upper bound would be - noobuntu
Split the C part into two separate functions. First one generates the data and returns the amount of data, the second one copies (and frees) the generated data to buffer allocated by the caller (SV). Yes, will need to use globals, though .. If you want more than one operation in flight simultaneously, you can use an array of descriptors, and the first function to grab the first free one; it returns the descriptor index. An additional function then returns the size of the result. The copying function always frees dynamic C memory, and destroys the descriptor. Would that work better? - Nominal Animal

1 Answers

0
votes

You can't pass an open array from C code because C code doesn't know how to allocate such a data structure. I guess this is because it would be problematic if the array allocated in the C code doesn't match the type of the array on the SV side. For example, imagine you have a 100 element fixed size array defined on the SV side and you try to pass it a 10 element array from C. It's also probably difficult to specify a function on the C side that can allocate an SV array data structure. The size and layout doesn't only depend on the number of elements, but on the type of the element as well.

It's possible to pass an already allocated array as an output argument, though. You have to know how many elements to pre-allocate, though. As @NominaAnimal suggested, you have to pass the number of elements you will receive from C via a separate function call:

import "DPI-C" function int get_num_elems(args...);
import "DPI-C" function void get_elems(output int elems, args...);

class some_class;

  function void some_func();
    int result[]; // not a global variable
    result = new[get_num_elems(args...)];
    get_elems(result, args...);
  endfunction

endclass

On the C side you can use svGetArrElemPtr1(...) to obtain a pointer to each element of the passed in result array and update it accordingly. You can do this because the type is int and the representations are identical in both languages:

void get_elems(svOpenArray elems, args...) {
  // compute elems based on 'args...'

  for (int i = 0; i < svSize(elems); i++)
    *(svGetArrElemPtr1(elems, i)) = ...[i];
}