From e72cc777777f2e5f5eca4b1b5acbc1d0f0277dc9 Mon Sep 17 00:00:00 2001
From: umarcor <unai.martinezcorral@ehu.eus>
Date: Sat, 11 Apr 2020 08:19:42 +0200
Subject: [PATCH] vhpidirect: add header file and 'demo' (ghdl/ghdl#1059)

---
 .github/workflows/test.yml             |   1 +
 doc/vhpidirect/examples/cinterface.rst |  10 ++
 doc/vhpidirect/examples/index.rst      |   2 +
 vhpidirect/cinterface/demo/main.c      | 160 ++++++++++++++++++
 vhpidirect/cinterface/demo/run.sh      |  14 ++
 vhpidirect/cinterface/demo/tb.vhd      | 110 ++++++++++++
 vhpidirect/ghdl.h                      | 224 +++++++++++++++++++++++++
 7 files changed, 521 insertions(+)
 create mode 100644 doc/vhpidirect/examples/cinterface.rst
 create mode 100644 vhpidirect/cinterface/demo/main.c
 create mode 100755 vhpidirect/cinterface/demo/run.sh
 create mode 100644 vhpidirect/cinterface/demo/tb.vhd
 create mode 100644 vhpidirect/ghdl.h

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 130efa92..8b951fdb 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,6 +27,7 @@ jobs:
             shared/shlib,
             shared/dlopen,
             shared/shghdl,
+            cinterface/demo,
           ]
     steps:
     - uses: actions/checkout@v2
diff --git a/doc/vhpidirect/examples/cinterface.rst b/doc/vhpidirect/examples/cinterface.rst
new file mode 100644
index 00000000..7cd21226
--- /dev/null
+++ b/doc/vhpidirect/examples/cinterface.rst
@@ -0,0 +1,10 @@
+.. program:: ghdl
+.. _COSIM:VHPIDIRECT:Examples:cinterface:
+
+C interface
+###########
+
+:cosimtree:`demo <vhpidirect/cinterface/demo>`
+**********************************************
+
+This example is a reference and a test suite for the helper C headers provided in :cosimtree:`vhpidirect`. These headers are a reference of GHDL's ABI, and can be imported to easily convert non-trivial data types between C and VHDL. However, the ABI is not settled, so it might change without prior notice.
diff --git a/doc/vhpidirect/examples/index.rst b/doc/vhpidirect/examples/index.rst
index 256809d1..e39a1d53 100644
--- a/doc/vhpidirect/examples/index.rst
+++ b/doc/vhpidirect/examples/index.rst
@@ -8,5 +8,7 @@ Examples
    quickstart
    wrapping
    linking
+   demo
    shared
+   cinterface
    other
diff --git a/vhpidirect/cinterface/demo/main.c b/vhpidirect/cinterface/demo/main.c
new file mode 100644
index 00000000..b4e0c478
--- /dev/null
+++ b/vhpidirect/cinterface/demo/main.c
@@ -0,0 +1,160 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <string.h>
+
+#include <ghdl.h>
+
+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<len[0] ; x++ ) {
+    printf("%d: %d\n", x, vec[x]);
+  }
+  *ptr = ghdlFromArray(vec, len, 1);
+}
+
+ghdl_AccNaturalDimArr_t* getLine() {
+  return ghdlAccFromString("HELLO WORLD");
+}
diff --git a/vhpidirect/cinterface/demo/run.sh b/vhpidirect/cinterface/demo/run.sh
new file mode 100755
index 00000000..10ca7856
--- /dev/null
+++ b/vhpidirect/cinterface/demo/run.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env sh
+
+set -e
+
+cd $(dirname "$0")
+
+echo "Analyze tb.vhd"
+ghdl -a -O0 -g tb.vhd
+
+echo "Build tb (with main.c and headers)"
+ghdl -e -O0 -g -Wl,-I../../ -Wl,main.c tb
+
+echo "Execute tb"
+./tb
diff --git a/vhpidirect/cinterface/demo/tb.vhd b/vhpidirect/cinterface/demo/tb.vhd
new file mode 100644
index 00000000..10cc9322
--- /dev/null
+++ b/vhpidirect/cinterface/demo/tb.vhd
@@ -0,0 +1,110 @@
+use std.textio.line;
+
+entity tb is
+end;
+
+architecture arch of tb is
+
+  type rec_t is record
+    r_char: character;
+    r_int : integer;
+  end record;
+
+  type enum_t is (standby, start, busy, done);
+
+  type int_vec_t is array(natural range <>) 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;
diff --git a/vhpidirect/ghdl.h b/vhpidirect/ghdl.h
new file mode 100644
index 00000000..23e06fb9
--- /dev/null
+++ b/vhpidirect/ghdl.h
@@ -0,0 +1,224 @@
+#ifndef GHDL_TYPES_H
+#define GHDL_TYPES_H
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+// 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