3

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.

noobuntu
  • 903
  • 1
  • 18
  • 42
  • What is the range of `X`? – meaning-matters Dec 22 '17 at 21:39
  • 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 Dec 22 '17 at 21:53
  • 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 Dec 22 '17 at 22:07
  • 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 Dec 22 '17 at 22:18
  • 2
    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 Dec 23 '17 at 01:07
  • Yes I essentially did do this. The `allocate_mem` function allocates buffer space in SV. The `set_item` copies it into this space. I like your idea of descriptors as well. The question however is if there is a method to pass an open array back to SV from the C side using `svOpenArrayHandle` – noobuntu Dec 23 '17 at 11:59

1 Answers1

0

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];
}
Tudor Timi
  • 7,453
  • 1
  • 24
  • 53
  • I initially had the same idea. The issue is my C model has randomization with a stochastically generated seed. So 2 calls to the same C model does not guarantee the same results. (Incase you are wondering how I can reproduce failing testcases, I dump the result array into a file that I can then feed back in without calling the predictor) – noobuntu Dec 23 '17 at 16:46
  • You can store the results in a global variable in C, then. You can make C variables unique to the file they're declared in (I forgot how exactly). @noobuntu Replace the `get_num_elems(...)` function with `allocate_elems_and_get_size(...)` that allocates the elements in a C array you can the access from `get_elems(svOpenArray)` (only one argument - the array to put them in). – Tudor Timi Dec 23 '17 at 17:04
  • It's a ton of copying though. – Tudor Timi Dec 23 '17 at 17:05
  • @noobuntu If copying a second time to SV is prohibitively expensive, you're better off using an extra wrapper class instead of a native SV array. Get a `chandle` to the global C array and the size of the array from C. Whenever you want to access an element, instead of slicing an array, you can call a `get(chandle array, int idx)` function on your wrapper object that can mediate between your code and C. – Tudor Timi Dec 23 '17 at 17:08