C Access example

The files

The VHPIDIRECT method of accessing C-side variables and functions from VHDL requires these variables and functions to be declared with the foreign attribute a certain way. It is set to highlight that the function has a foreign C-side definition, as per foreign_declarations.

The foreign functions can be declared HDL-side in a package as follows, or the declarative part of an architecture (as in VHPIDIRECT Demo). The difference being the scope of the declarations.

This example starts with c_access.vhdl:

library ieee;
use ieee.std_logic_1164.all;

package cAccess is

	type int_ptr is access integer; -- represented C-side with int
	function c_intArrSize_ptr return int_ptr; -- represented C-side with int*
		attribute foreign of c_intArrSize_ptr :
		function is "VHPIDIRECT getIntArrSize"; -- getIntArrSize is the C-side function name

	shared variable c_sizeInt : int_ptr := c_intArrSize_ptr;

	type int_arr is array(0 to c_sizeInt.all-1) of integer;
	type int_arr_ptr is access int_arr; -- represented C-side with int*
	
	
	function c_intArr_ptr return int_arr_ptr;
		attribute foreign of c_intArr_ptr :
		function is "VHPIDIRECT getIntArr_ptr";
	procedure c_promptIndexValue(index: integer);
		attribute foreign of c_promptIndexValue :
		procedure is "VHPIDIRECT promptIndexValue";
		
	shared variable c_intArr : int_arr_ptr := c_intArr_ptr;	
	
	type char_ptr is access std_ulogic; -- represented C-side with char
	function c_finished_ptr return char_ptr;
		attribute foreign of c_finished_ptr :
		function is "VHPIDIRECT getFinished_ptr";
	
	procedure c_promptFinished;
		attribute foreign of c_promptFinished :
		procedure is "VHPIDIRECT promptFinished";

	shared variable c_finished : char_ptr := c_finished_ptr;	
end package cAccess;

package body cAccess is

	function c_intArrSize_ptr return int_ptr is
	begin
		assert false report "c_intArrSize_ptr VHPI" severity failure;
	end c_intArrSize_ptr;


	function c_intArr_ptr return int_arr_ptr is
	begin
		assert false report "c_intArr_ptr VHPI" severity failure;
	end c_intArr_ptr;
	procedure c_promptIndexValue(index: integer) is
	begin
		assert false report "c_promptIndexValue VHPI" severity failure;
	end c_promptIndexValue;

	function c_finished_ptr return char_ptr is
	begin
		assert false report "c_finished_ptr VHPI" severity failure;
	end c_finished_ptr;
	procedure c_promptFinished is
	begin
		assert false report "c_promptFinished VHPI" severity failure;
	end c_promptFinished;
end package body cAccess;

Assuming, for now, that these foreign functions perform as their names indicate they should, the toplevel test bench is defined next (toplevel.vhdl):

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

use work.cAccess.all;

entity toplevel is
end entity toplevel;

architecture RTL of toplevel is

begin
	
	process
	begin		
		report "array length: " & integer'image(c_intArr.all'length);
		
		c_promptIndexValue(0);

		for i in 1 to c_intArr.all'right loop
			report "c_intArr[" & integer'image(i) &"] = " &  integer'image(c_intArr.all(i)) & ". Set to: " & integer'image(2*c_intArr.all(i));
			c_intArr.all(i) := 2*c_intArr.all(i);
		end loop;
		
		c_promptFinished;
		if(c_finished.all = '1') then
			wait;
		end if;
		report "c_finished = " & std_ulogic'image(c_finished.all);
	end process;
		

end architecture RTL;

Perhaps this example’s flow is clearer by now: the testbench will use an integer array from C, the size of which is defined in C. It will use a C-side function to prompt the user to set the value for index zero and then print and adjust all of the other indices. Then the user is prompted to conclude the simulation or repeat the process.

The C functions and variables that GHDL accesses are kept in a separate header/code pair. cSharedVar.h:

#include <stdio.h>

int sizeInt;
int* getIntArrSize();

int* intArray;
int* getintArray_ptr();
void promptIndexValue();

char finishedChar;
char* getFinished_ptr();
void promptFinished();

static const char VHDL_BIT_STATE[] = { 'U', 'X', '0', '1', 'Z', 'W', 'L', 'H', '-'};

enum VHDL_BIT_CHAR {
VHDL_U = 0,
VHDL_X = 1,
VHDL_0 = 2,
VHDL_1 = 3,
VHDL_Z = 4,
VHDL_W = 5,
VHDL_L = 6,
VHDL_H = 7,
VHDL_D = 8,
};

And cSharedVar.c:

#include "cSharedVar.h"

int* getIntArrSize(){
    return &sizeInt;
}

int* getIntArr_ptr(){
    return intArray;
}

void promptIndexValue(int index){
    char strIn[8];

    printf("Enter a number for index %d: ", index);
    fgets(strIn, sizeof strIn, stdin);
    printf("\n");
    sscanf(strIn, "%d", &intArray[index]);
}


char* getFinished_ptr(){
    return &finishedChar;
}

void promptFinished(){
    char strIn[3];

    printf("Conclude simulation? (Y/n): ");
    fgets(strIn, sizeof strIn, stdin);
    printf("\n");
    if(strIn[0] == 'N' || strIn[0] == 'n'){
        finishedChar = VHDL_0;
    }
    else{
        finishedChar = VHDL_1;
    }
}

These are also exposed to the custom entry point in main.c:

#include <malloc.h>

#include "cSharedVar.h"

extern int ghdl_main(char argc, char* argv[]);

int main(int argc, char const *argv[])
{
    char strIn[3];
    printf("Enter the Integer Array length [1-9]: ");
    fgets(strIn, sizeof strIn, stdin);
    printf("\n");
    sscanf(strIn, "%d", &sizeInt);

    if(sizeInt < 1)
        sizeInt = 1;
    
    intArray = malloc(sizeInt*sizeof(int));

    for (int i = 0; i < sizeInt; i++)
    {
        intArray[i] = i*i;
    }
    
    printf("ghdl_main return: %d\n", ghdl_main(0, NULL));
    printf("\n********************************\nghdl simulation completed.\n\n");

    for (int i = 0; i < sizeInt; i++)
    {
       printf("intArray[%d] = %d\n", i, intArray[i]);
    }

    return 0;
}


It is seen that the array’s length is established, and its contents filled with square numbers, before the GHDL simulation happens. After that, the array is read out.

Tip

To pass GHDL runtime options, see the second hint under Wrapping and starting a GHDL simulation from a foreign program.

Compilation

The compilation steps should look something like build.sh:

gcc -c cSharedVar.c -o cSharedVar.o &&
gcc -c main.c -o main.o &&
ghdl-llvm -a cAccess.vhd toplevel.vhd &&
ghdl-llvm -e -Wl,main.o -Wl,cSharedVar.o toplevel
./toplevel
rm *.o work-obj*.cf toplevel