diff --git a/doc/examples/README.rst b/doc/examples/README.rst new file mode 100644 index 00000000000..be4d7310290 --- /dev/null +++ b/doc/examples/README.rst @@ -0,0 +1,12 @@ +.. _USING:Examples: + +Examples +######## + +This sections contains advanced examples using specific features of the language, the tool, +or interaction with third-party projects. It is suggested for users who are new to either +`GHDL` or `VHDL` to read :ref:`USING:QuickStart` first. + +.. toctree:: + + ./vhpidirect/VHPIDIRECT diff --git a/doc/examples/vhpidirect/VHPIDIRECT.rst b/doc/examples/vhpidirect/VHPIDIRECT.rst new file mode 100644 index 00000000000..6524e0fa080 --- /dev/null +++ b/doc/examples/vhpidirect/VHPIDIRECT.rst @@ -0,0 +1,25 @@ +.. _Examples:VHPIDIRECT: + +Data exchange through VHPIDIRECT +################################ + +.. toctree:: + :hidden: + + demo/README + +See :ref:`Examples:VHPIDIRECT:Demo` for a demo about how to pass different types of data to/from VHDL and C through VHPIDIRECT. + +VUnit +===== + +`VUnit `_ is an open source unit testing framework for VHDL/SystemVerilog. Sharing memory buffers between foreign C or Python applications and VHDL testbenches is supported through GHDL's VHPIDIRECT features. Buffers are accessed from VHDL as either strings, arrays of bytes or arrays of 32 bit integers. See VUnit example `external buffer `_ for details about the API. + +ghdlex and netpp +================ + +`netpp (network property protocol) `_ is a communication library allowing to expose variables or other properties of an application to the network as abstract 'Properties'. Its basic philosophy is that a device always knows its capabilities. netpp capable devices can be explored by command line, Python scripts or GUI applications. Properties of a device - be it virtual or real - are typically described by a static description in an XML device description language, but they can also be constructed on the fly. + +`ghdlex `_ is a set of C extensions to facilitate data exchange between a GHDL simulation and external applications. VHPIDIRECT mechanisms are used to wrap GHDL data types into structures usable from a C library. `ghdlex` uses the `netpp `_ library to expose virtual entities (such as pins or RAM) to the network. It also demonstrates simple data I/O through unix pipes. A few VHDL example entities are provided, such as a virtual console, FIFOs, RAM. + +The author of `netpp` and `ghdlex` is also working on `MaSoCist `_, a linux'ish build system for System on Chip designs, based on GHDL. It allows to handle more complex setup, e.g. how a RISC-V architecture (for example) is regress-tested using a virtual debug interface. diff --git a/doc/examples/vhpidirect/demo/README.rst b/doc/examples/vhpidirect/demo/README.rst new file mode 100644 index 00000000000..5e4a21cdf2f --- /dev/null +++ b/doc/examples/vhpidirect/demo/README.rst @@ -0,0 +1,21 @@ +.. program:: ghdl +.. _Examples:VHPIDIRECT:Demo: + +VHPIDIRECT Demo +=============== + +The following sources show how to pass different types of data to/from VHDL and C through VHPIDIRECT. + +.. NOTE:: :file:`ghdl.h` is a reference of GHDL's ABI, which can be imported to easily convert data types. However, the ABI is not settled, so it might change without prior notice. + +.. literalinclude:: run.sh + :language: bash + +.. literalinclude:: tb.vhd + :language: vhdl + +.. literalinclude:: main.c + :language: c + +.. literalinclude:: ghdl.h + :language: c diff --git a/doc/examples/vhpidirect/demo/ghdl.h b/doc/examples/vhpidirect/demo/ghdl.h new file mode 100644 index 00000000000..23e06fb95f3 --- /dev/null +++ b/doc/examples/vhpidirect/demo/ghdl.h @@ -0,0 +1,224 @@ +#ifndef GHDL_TYPES_H +#define GHDL_TYPES_H + +#include +#include +#include +#include + +// Range/bounds of a dimension of an unconstrained array with dimensions of type 'natural' +typedef struct { + int32_t left; + int32_t right; + int32_t dir; + int32_t len; +} range_t; + +// Range/bounds of an unconstrained array with 1, 2 or 3 dimensions of type 'natural' +typedef struct { + range_t dim_1; +} bounds_t; +typedef struct { + range_t dim_1; + range_t dim_2; +} bounds2D_t; +typedef struct { + range_t dim_1; + range_t dim_2; + range_t dim_3; +} bounds3D_t; + +// Unconstrained array with dimensions of type 'natural' +typedef struct { + void* array; + bounds_t* bounds; +} ghdl_NaturalDimArr_t; + +// Access to an unconstrained array with 1 dimension of type 'natural' +typedef struct { + range_t range; + uint8_t array[]; +} ghdl_AccNaturalDimArr_t; + +/* +* Print custom types +*/ + +void print(ghdl_NaturalDimArr_t* ptr) { + printf("array: %p\n", ptr->array); + printf("bounds: %p\n", ptr->bounds); + printf("bounds.left: %d\n", ptr->bounds->dim_1.left); + printf("bounds.right: %d\n", ptr->bounds->dim_1.right); + printf("bounds.dir: %d\n", ptr->bounds->dim_1.dir); + printf("bounds.len: %d\n", ptr->bounds->dim_1.len); +} + +/* +* Convert a fat pointer of an unconstrained string, to a (null terminated) C string +*/ + +// @umarcor +char* ghdlToString(ghdl_NaturalDimArr_t* ptr) { + assert(ptr != NULL); + assert(ptr->bounds != NULL); + int len = ptr->bounds->dim_1.len; + char* str = malloc(sizeof(char) * len + 1); + strncpy(str, ptr->array, len); + str[len] = '\0'; + return str; +} + +// In the prototype, Bradley declares a value instead of a reference. Why? + +// @bradleyharden +/* +char* ghdl_array_to_string(array_t array) { + // Add a null character, because GHDL strings are not null-terminated + char *string = malloc(array.range->len + 1); + strncpy(string, array.array, array.range->len); + string[array.range->len] = '\0'; + return string; +} +*/ + +/* +* Convert a fat pointer of an uncontrained array with (up to 3) dimensions of type 'natural', to C types +*/ + +void ghdlToArray(ghdl_NaturalDimArr_t* ptr, void** vec, int* len, int num) { + assert(ptr != NULL); + assert(ptr->bounds != NULL); + *vec = ptr->array; + + void* b = ptr->bounds; + switch (num) { + case 1: + len[0] = ((bounds_t*)b)->dim_1.len; + break; + case 2: + len[0] = ((bounds2D_t*)b)->dim_2.len; + len[1] = ((bounds2D_t*)b)->dim_1.len; + break; + case 3: + len[0] = ((bounds3D_t*)b)->dim_3.len; + len[1] = ((bounds3D_t*)b)->dim_2.len; + len[2] = ((bounds3D_t*)b)->dim_1.len; + break; + } +} + +/* +* Convert a (null terminated) C string, to a fat pointer of an unconstrained string +*/ + +// @umarcor +/* +ghdl_NaturalDimArr_t* ghdlFromString(char* str) { + uint32_t len = strlen(str); + ghdl_NaturalDimArr_t* ptr = malloc(sizeof(ghdl_NaturalDimArr_t)); + ptr->array = malloc(sizeof(char) * len); + strncpy((char*)(ptr->array), str, len); + ptr->bounds = malloc(sizeof(bounds_t)); + bounds_t* b = ptr->bounds; + b->dim_1.left = 1; + b->dim_1.right = len; + b->dim_1.dir = 0; + b->dim_1.len = len; + return ptr; +} +*/ + +// Again, the prototype I had (above) returns a reference instead of a value (Bradley's below) + +// @bradleyharden +ghdl_NaturalDimArr_t ghdlFromString(char *string) { + range_t *range = malloc(sizeof(range_t)); + assert(range != NULL); + uint32_t len = strlen(string); + range->left = 1; + range->right = len; + range->dir = 0; + range->len = len; + // Don't bother copying the string, because GHDL will do that anyway + return (ghdl_NaturalDimArr_t){.array=string, .bounds=(bounds_t*)range}; +} + +/* +* Convert C types representing an unconstrained array with a dimension of type 'natural', to a fat pointer +*/ + +ghdl_NaturalDimArr_t ghdlFromArray(void* vec, int* len, int num) { + bounds_t* b = malloc(sizeof(bounds_t)); + assert(b != NULL); + switch (num) { + case 3: + // TODO + case 2: + // TODO + case 1: + b->dim_1.left = 0; + b->dim_1.right = len[0]-1; + b->dim_1.dir = 0; + b->dim_1.len = len[0]; + } + return (ghdl_NaturalDimArr_t){.array=vec, .bounds=b}; +} + +/* +* Convert an access to an unconstrained string, to a (null terminated) C string +*/ + +char* ghdlAccToString(ghdl_AccNaturalDimArr_t *line) { + // Add a null character, because GHDL strings are not null-terminated + char *string = malloc(line->range.len + 1); + strncpy(string, line->array, line->range.len); + string[line->range.len] = '\0'; +} + +/* +* Convert C types representing an unconstrained array with a dimension of type 'natural', to an access +*/ + +// TODO: support 2 and 3 dimensions +ghdl_AccNaturalDimArr_t* ghdlAccFromArray(uint32_t length, size_t bytes) { + ghdl_AccNaturalDimArr_t *access = malloc(sizeof(ghdl_AccNaturalDimArr_t) + length * bytes); + assert(access != NULL); + access->range.left = 0; + access->range.right = length - 1; + access->range.dir = 0; + access->range.len = length; + return access; +} + +/* +* Convert a (null terminated) C string, to an access to an unconstrained string +*/ + +/* +// @umarcor +ghdl_AccNaturalDimArr_t* ghdlLineFromString(char *str) { + uint32_t len = strlen(str); + ghdl_AccNaturalDimArr_t *line = malloc(sizeof(ghdl_AccNaturalDimArr_t) + sizeof(char) * len); + line->bounds.left = 1; + line->bounds.right = len; + line->bounds.dir = 0; + line->bounds.len = len; + strncpy(line->array, str, len); + return line; +} +*/ + +// @bradleyharden +ghdl_AccNaturalDimArr_t* ghdlAccFromString(char *string) { + uint32_t length = strlen(string); + ghdl_AccNaturalDimArr_t *line = ghdlAccFromArray(length, 1); + // New access objects default to numbering from 0, + // but VHDL strings must be numbered from 1 + line->range.left++; + line->range.right++; + // Don't copy the null termination + strncpy(line->array, string, length); + return line; +} + +#endif diff --git a/doc/examples/vhpidirect/demo/main.c b/doc/examples/vhpidirect/demo/main.c new file mode 100644 index 00000000000..b4e0c478d70 --- /dev/null +++ b/doc/examples/vhpidirect/demo/main.c @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include + +#include + +typedef struct rec_t { + char r_char; + int32_t r_int; +} rec_t; + +typedef enum {standby, start, busy, done} enum_t; + +void testCinterface( + char v_char, + int32_t v_int, + uint32_t v_nat, + uint32_t v_pos, + double v_real, + bool v_bool, + bool v_bit, + int64_t v_time, + rec_t* v_rec, + uint8_t v_enum, + ghdl_NaturalDimArr_t* v_str, + ghdl_NaturalDimArr_t* v_vec_int, + ghdl_NaturalDimArr_t* v_vec_real, + ghdl_NaturalDimArr_t* v_vec_bool, + ghdl_NaturalDimArr_t* v_vec_bit, + ghdl_NaturalDimArr_t* v_vec_phy, + ghdl_NaturalDimArr_t* v_vec_rec, + ghdl_NaturalDimArr_t* v_vec_enum, + ghdl_NaturalDimArr_t* v_2vec_real +) { + assert(v_char == 'k'); + printf("v_char : %c\n", v_char); + + assert(v_int == -6); + printf("v_int : %d\n", v_int); + + assert(v_nat == 9); + printf("v_nat : %d\n", v_nat); + + assert(v_pos == 3); + printf("v_pos : %d\n", v_pos); + + assert(v_real == 3.34); + printf("v_real : %f\n", v_real); + + assert(v_bool == true); + printf("v_bool : %d\n", v_bool); + + assert(v_bit == true); + printf("v_bit : %d\n", v_bit); + + assert(v_time == 20e6); + printf("v_time : %d\n", v_time); + + assert(v_rec != NULL); + assert(v_rec->r_char == 'y'); + assert(v_rec->r_int == 5); + printf("v_rec : %p %c %d\n", v_rec, v_rec->r_char, v_rec->r_int); + + assert(v_enum == busy); + printf("v_enum : %d %d\n", v_enum, busy); + + char* str = ghdlToString(v_str); + printf("v_str : %p '%s' [%d]\n", v_str->array, str, strlen(str)); + + int* len = malloc(2 * sizeof(int)); + + int32_t* vec_int; + ghdlToArray(v_vec_int, (void**)&vec_int, len, 1); + assert(vec_int[0] == 11); + assert(vec_int[1] == 22); + assert(vec_int[2] == 33); + assert(vec_int[3] == 44); + assert(vec_int[4] == 55); + printf("v_vec_int : %p [%d]\n", vec_int, len[0]); + + double* vec_real; + ghdlToArray(v_vec_real, (void**)&vec_real, len, 1); + assert(vec_real[0] == 0.5); + assert(vec_real[1] == 1.75); + assert(vec_real[2] == 3.33); + assert(vec_real[3] == -0.125); + assert(vec_real[4] == -0.67); + assert(vec_real[5] == -2.21); + printf("v_vec_real : %p [%d]\n", vec_real, len[0]); + + bool* vec_bool; + ghdlToArray(v_vec_bool, (void**)&vec_bool, len, 1); + assert(vec_bool[0] == 0); + assert(vec_bool[1] == 1); + assert(vec_bool[2] == 1); + assert(vec_bool[3] == 0); + printf("v_vec_bool : %p [%d]\n", vec_bool, len[0]); + + bool* vec_bit; + ghdlToArray(v_vec_bit, (void**)&vec_bit, len, 1); + assert(vec_bit[0] == 1); + assert(vec_bit[1] == 0); + assert(vec_bit[2] == 1); + assert(vec_bit[3] == 0); + printf("v_vec_bit : %p [%d]\n", vec_bit, len[0]); + + int64_t* vec_phy; + ghdlToArray(v_vec_phy, (void**)&vec_phy, len, 1); + assert(vec_phy[0] == 1e6); + assert(vec_phy[1] == 50e3); + assert(vec_phy[2] == 1.34e9); + printf("v_vec_phy : %p [%d]\n", vec_phy, len[0]); + + rec_t* vec_rec; + ghdlToArray(v_vec_rec, (void**)&vec_rec, len, 1); + assert(vec_rec[0].r_char == 'x'); + assert(vec_rec[0].r_int == 17); + assert(vec_rec[1].r_char == 'y'); + assert(vec_rec[1].r_int == 25); + printf("v_vec_rec : %p [%d]\n", vec_rec, len[0]); + + uint8_t* vec_enum; + ghdlToArray(v_vec_enum, (void**)&vec_enum, len, 1); + assert(vec_enum[0] == start); + assert(vec_enum[1] == busy); + assert(vec_enum[2] == standby); + printf("v_vec_enum : %p [%d]\n", vec_enum, len[0]); + + double* vec2_real_base; + ghdlToArray(v_2vec_real, (void**)&vec2_real_base, len, 2); + double (*vec2_real)[len[0]] = (double(*)[len[0]])vec2_real_base; + assert(vec2_real[0][0] == 0.1); + assert(vec2_real[0][1] == 0.25); + assert(vec2_real[0][2] == 0.5); + assert(vec2_real[1][0] == 3.33); + assert(vec2_real[1][1] == 4.25); + assert(vec2_real[1][2] == 5.0); + printf("v_2vec_real : %p [%d, %d]\n", vec_enum, len[1], len[0]); +} + +void getString(ghdl_NaturalDimArr_t* ptr) { + *ptr = ghdlFromString("HELLO WORLD"); +} + +void getIntVec(ghdl_NaturalDimArr_t* ptr) { + int32_t vec[6] = {11, 22, 33, 44, 55}; + int32_t len[1] = {5}; + int x; + for ( x=0 ; x) of integer; + type real_vec_t is array(natural range <>) of real; + type bool_vec_t is array(natural range <>) of boolean; + type time_vec_t is array(natural range <>) of time; + type rec_vec_t is array(natural range <>) of rec_t; + type enum_vec_t is array(natural range <>) of enum_t; + + type real_2vec_t is array (natural range <>, natural range <>) of real; + +begin + process + + procedure testCinterface( + v_char : character := 'k'; + v_int : integer := -6; + v_nat : natural := 9; + v_pos : positive := 3; + v_real : real := 3.34; + v_bool : boolean := true; + v_bit : bit := '1'; + v_time : time := 20 ns; + v_rec : rec_t := ('y', 5); + v_enum : enum_t := busy; + v_str : string := "hellostr"; + v_vec_int : int_vec_t := (11, 22, 33, 44, 55); + v_vec_real : real_vec_t := (0.5, 1.75, 3.33, -0.125, -0.67, -2.21); + v_vec_bool : bool_vec_t := (false, true, true, false); + v_vec_bit : bit_vector := ('1', '0', '1', '0'); + v_vec_time : time_vec_t := (1 ns, 50 ps, 1.34 us); + v_vec_rec : rec_vec_t := (('x', 17),('y', 25)); + v_vec_enum : enum_vec_t := (start, busy, standby); + v_2vec_real : real_2vec_t := ((0.1, 0.25, 0.5),(3.33, 4.25, 5.0)) + ) is + begin assert false report "VHPIDIRECT testCinterface" severity failure; end; + attribute foreign of testCinterface : procedure is "VHPIDIRECT testCinterface"; + + function getString return string is + begin assert false report "VHPIDIRECT getString" severity failure; end; + attribute foreign of getString : function is "VHPIDIRECT getString"; + + function getIntVec return int_vec_t is + begin assert false report "VHPIDIRECT getIntVec" severity failure; end; + attribute foreign of getIntVec : function is "VHPIDIRECT getIntVec"; + + function getLine return line is + begin assert false report "VHPIDIRECT getLine" severity failure; end; + attribute foreign of getLine : function is "VHPIDIRECT getLine"; + + constant g_str: string := getString; + constant g_int_vec: int_vec_t := getIntVec; + + variable g_line: line := getLine; + + begin + + testCinterface( + v_char => 'k', + v_int => -6, + v_nat => 9, + v_pos => 3, + v_real => 3.34, + v_bool => true, + v_bit => '1', + v_time => 20 ns, + v_rec => ('y', 5), + v_enum => busy, + v_str => "hellostr", + v_vec_int => (11, 22, 33, 44, 55), + v_vec_real => (0.5, 1.75, 3.33, -0.125, -0.67, -2.21), + v_vec_bool => (false, true, true, false), + v_vec_bit => ('1', '0', '1', '0'), + v_vec_time => (1 ns, 50 ps, 1.34 us), + v_vec_rec => (('x', 17),('y', 25)), + v_vec_enum => (start, busy, standby), + v_2vec_real => ((0.1, 0.25, 0.5),(3.33, 4.25, 5.0)) + ); + + report "g_str'length: " & integer'image(g_str'length) severity note; + if g_str'length /= 0 then + report "g_str: " & g_str severity note; + end if; + report "string: " & getString severity note; + + report "g_int_vec'length: " & integer'image(g_int_vec'length) severity note; + for x in g_int_vec'range loop + report integer'image(x) & ": " & integer'image(g_int_vec(x)) severity note; + assert g_int_vec(x) = 11*(x+1) severity warning; + end loop; + + report "g_line: " & g_line.all severity note; + report "getLine: " & getLine.all severity note; + assert getLine.all = "HELLO WORLD" severity failure; + + wait; + end process; +end;