Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to call C functions with opaque pointers (VHPIDIRECT) #30

Open
augustofg opened this issue Sep 3, 2022 · 3 comments
Open

How to call C functions with opaque pointers (VHPIDIRECT) #30

augustofg opened this issue Sep 3, 2022 · 3 comments

Comments

@augustofg
Copy link

augustofg commented Sep 3, 2022

Hi,

I'm developing a library to allow my VHDL testbenches to be able to listen to TCP connections and send / receive data. The library is written in a object oriented sytle, in which each function receives a pointer to a struct that stores the object internal state, for example:

struct TcpObject {
  int a, b;
};
typedef struct TcpObject TcpObject;

TcpObject* new_tcp_listener();
int tcp_read_data(TcpObject* obj, char* data);
void delete_tcp_listener(TcpObject* obj);

Now, I couldn't find in the documentation and examples what VHDL type to use for storing an opaque pointer that comes from the C code. The best I could think was using an 'access type', but you can't call functions with an 'access type' argument:

entity tb is
  generic (
    g_TCP_PORT : natural := 14000
  );
end tb;

architecture arch of tb is
  type t_tcp_listener is access integer;

  -- Works
  impure function new_tcp_listener (tcp_port : natural) return t_tcp_listener is
  begin report "VHPIDIRECT new_tcp_listener" severity failure; end;
  attribute foreign of new_tcp_listener : function is "VHPIDIRECT new_tcp_listener";

  -- This will fail with: type of constant interface "listener" cannot be access type "t_tcp_listener"
  impure function delete_tcp_listener (listener : t_tcp_listener) return natural is
  begin report "VHPIDIRECT delete_tcp_listener" severity failure; end;
  attribute foreign of delete_tcp_listener : function is "VHPIDIRECT delete_tcp_listener";

begin

  process
    variable net: t_tcp_listener;
  begin
    net := new_tcp_listener(g_TCP_PORT);
    wait;
  end process;
end;

Is there some way of invoking C functions with opaque pointers?

Thanks,
Augusto.

@augustofg
Copy link
Author

Well, as a workaround you can use a procedure instead of a function if you don't need the return value:

entity tb is
  generic (
    g_TCP_PORT : natural := 14000
  );
end tb;

architecture arch of tb is
  type t_tcp_listener is access integer;

  impure function new_tcp_listener (tcp_port : natural) return t_tcp_listener is
  begin report "VHPIDIRECT new_tcp_listener" severity failure; end;
  attribute foreign of new_tcp_listener : function is "VHPIDIRECT new_tcp_listener";

  procedure delete_tcp_listener (variable listener : t_tcp_listener) is
  begin report "VHPIDIRECT delete_tcp_listener" severity failure; end;
  attribute foreign of delete_tcp_listener : procedure is "VHPIDIRECT delete_tcp_listener";

begin

  process
    variable net: t_tcp_listener;
  begin
    net := new_tcp_listener(g_TCP_PORT);
    delete_tcp_listener(net);
    wait;
  end process;
end;

This works, if you don't touch the variable net, so you don't corrupt the underlying object, but it seems not ideal.

@tgingold
Copy link
Member

tgingold commented Sep 4, 2022 via email

@augustofg
Copy link
Author

augustofg commented Sep 6, 2022

After some experimentation I've found few caveats:

  • You should not deallocate from the C code, GHDL seems to automatically call free on the pointers used in access types, deallocating explicitly causes double free errors;
  • Using a procedure to be able to pass the object pointer as an access variable is tricky when you need get data back. As procedures can not return values like functions, the only way to 'return' something is by specifying arguments with out, and by reading the documentation this is discouraged:
    Non-composite types are passed by value. For the in mode (default), this corresponds to the C or Ada mechanism. out and inout interfaces are gathered in a record and this record is passed by reference as the first argument to the subprogram. As a consequence, it is not suggested to use out and/or inout modes in foreign subprograms, since they are not portable. Restrictions on type declarations

Nevertheless, I succeed in writing to a constrained std_logic_vector variable via a procedure:

library ieee;
use ieee.std_logic_1164.all;

entity obj_tb is
end obj_tb;

architecture arch of obj_tb is
  type t_my_obj is access integer;

  impure function new_obj (arg : natural) return t_my_obj is
  begin report "VHPIDIRECT new_obj" severity failure; end;
  attribute foreign of new_obj : function is "VHPIDIRECT new_obj";

  procedure read_data_obj(variable obj  : in t_my_obj;
                          variable data : out std_logic_vector(0 to 31))  is
  begin report "VHPIDIRECT read_data_obj" severity failure; end;
  attribute foreign of read_data_obj: procedure is "VHPIDIRECT read_data_obj";

begin
  process
    variable obj: t_my_obj;
    variable data: std_logic_vector(31 downto 0);
  begin
    obj := new_obj(512);
    read_data_obj(obj, data);
    report "Data read: " & to_string(data) severity note;
    wait;
  end process;
end;
#include <stddef.h>
#include <stdlib.h>

typedef struct {
	int data;
} obj;

obj* new_obj(int arg) {
	obj* myobj = malloc(sizeof(obj));
	myobj->data = arg;
	return myobj;
}

typedef struct {
	char data[32];
} read_data_obj_record;

void read_data_obj(obj* myobj, read_data_obj_record* rec) {
	int mydata = myobj->data;
	for (size_t i = 0; i < 32; i++) {
		rec->data[i] = mydata & 0x80000000 ? 0x03 : 0x02;
		mydata = mydata << 1;
	}
}
$ ghdl -a --std=08 test_arguments.vhd
$ ghdl -e -Wl,testobj.c --std=08 obj_tb
./obj_tb 
test_arguments.vhd:26:5:@0ms:(report note): Data read: 00000000000000000000001000000000

Though this seems to contradict the documentation: this record is passed by reference as the first argument to the subprogram. Maybe this is not true when passing access types?

I couldn't get working anything more complex than a single out argument, the record layout seems to be wildly different from what I expected, but I think it is good enough for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants