vunit-python-bridge 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vunit_python_bridge/__init__.py +92 -0
- vunit_python_bridge/bin/vunit_python_bridge-cp310-win_amd64.dll +0 -0
- vunit_python_bridge/bin/vunit_python_bridge-cp311-win_amd64.dll +0 -0
- vunit_python_bridge/bin/vunit_python_bridge-cp312-win_amd64.dll +0 -0
- vunit_python_bridge/bin/vunit_python_bridge-cp313-win_amd64.dll +0 -0
- vunit_python_bridge/bin/vunit_python_bridge-cp314-win_amd64.dll +0 -0
- vunit_python_bridge/bridge.py +171 -0
- vunit_python_bridge/foreign_application.py +90 -0
- vunit_python_bridge/hdl/src/python_bridge_pkg.vhd.in +91 -0
- vunit_python_bridge/hdl/src/python_context.vhd +13 -0
- vunit_python_bridge/hdl/src/python_ffi_pkg_bridge.vhd +507 -0
- vunit_python_bridge/hdl/src/python_pkg.vhd +1087 -0
- vunit_python_bridge/hdl/src/python_pkg_vhpi.vhd +270 -0
- vunit_python_bridge/hdl/tools/generate_python_pkg.py +386 -0
- vunit_python_bridge/hdl/tools/python_pkg.vhd.in +765 -0
- vunit_python_bridge/native/arguments.c +191 -0
- vunit_python_bridge/native/bridge.h +117 -0
- vunit_python_bridge/native/config.c +201 -0
- vunit_python_bridge/native/error.c +120 -0
- vunit_python_bridge/native/fli.c +98 -0
- vunit_python_bridge/native/interpreter.c +271 -0
- vunit_python_bridge/native/operations.c +239 -0
- vunit_python_bridge/native/vhpi/python_pkg.c +125 -0
- vunit_python_bridge/native/vhpi/python_pkg.h +31 -0
- vunit_python_bridge/native/vhpi/python_pkg_vhpi.c +366 -0
- vunit_python_bridge/native_library.py +455 -0
- vunit_python_bridge/runtime.py +645 -0
- vunit_python_bridge/simulator_hooks.py +141 -0
- vunit_python_bridge/vunit_pkg.toml +17 -0
- vunit_python_bridge-0.1.0.dist-info/METADATA +116 -0
- vunit_python_bridge-0.1.0.dist-info/RECORD +34 -0
- vunit_python_bridge-0.1.0.dist-info/WHEEL +5 -0
- vunit_python_bridge-0.1.0.dist-info/licenses/LICENSE +373 -0
- vunit_python_bridge-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
4
|
+
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com
|
|
7
|
+
*
|
|
8
|
+
* Data transferred from VHDL: a byte buffer for strings (Python source code,
|
|
9
|
+
* expressions, file and session names) and the integer_array_t value being
|
|
10
|
+
* pushed, which is staged in the runtime and then referred to from the Python
|
|
11
|
+
* source text VHDL builds.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#include "bridge.h"
|
|
15
|
+
|
|
16
|
+
#include <stdlib.h>
|
|
17
|
+
#include <string.h>
|
|
18
|
+
|
|
19
|
+
/* Raw byte buffer used to transfer strings from VHDL, owned. */
|
|
20
|
+
static char *g_buffer = NULL;
|
|
21
|
+
static size_t g_buffer_length = 0;
|
|
22
|
+
static size_t g_buffer_capacity = 0;
|
|
23
|
+
|
|
24
|
+
/* Strong references, only touched with the GIL held. */
|
|
25
|
+
static PyObject *g_array = NULL; /* NumPy array of the integer_array_t being pushed */
|
|
26
|
+
static PyObject *g_pending = NULL; /* bytearray backing it */
|
|
27
|
+
|
|
28
|
+
/* View into g_pending. It is owned by us and never resized while referenced
|
|
29
|
+
* (NumPy holds a buffer export on it), so the pointer stays valid. */
|
|
30
|
+
static char *g_pending_data = NULL;
|
|
31
|
+
static size_t g_pending_size = 0;
|
|
32
|
+
static size_t g_pending_position = 0;
|
|
33
|
+
|
|
34
|
+
/* New reference to the transfer buffer as a str. */
|
|
35
|
+
PyObject *vpy_buffer_as_str(void) {
|
|
36
|
+
return PyUnicode_DecodeUTF8(g_buffer == NULL ? "" : g_buffer, (Py_ssize_t)g_buffer_length, "surrogateescape");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
void vpy_clear_arguments(void) {
|
|
40
|
+
Py_CLEAR(g_array);
|
|
41
|
+
Py_CLEAR(g_pending);
|
|
42
|
+
g_pending_data = NULL;
|
|
43
|
+
g_pending_size = 0;
|
|
44
|
+
g_pending_position = 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* ------------------------------------------------------------------------ */
|
|
48
|
+
/* Entry points */
|
|
49
|
+
/* ------------------------------------------------------------------------ */
|
|
50
|
+
|
|
51
|
+
VPY_EXPORT int32_t vpy_buffer_clear(void) {
|
|
52
|
+
g_buffer_length = 0;
|
|
53
|
+
return VPY_OK;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
VPY_EXPORT int32_t vpy_buffer_append(const char *chunk, int32_t length) {
|
|
57
|
+
size_t required;
|
|
58
|
+
|
|
59
|
+
if (length <= 0) {
|
|
60
|
+
return VPY_OK;
|
|
61
|
+
}
|
|
62
|
+
required = g_buffer_length + (size_t)length;
|
|
63
|
+
if (required > g_buffer_capacity) {
|
|
64
|
+
size_t capacity = g_buffer_capacity == 0 ? 4096 : g_buffer_capacity;
|
|
65
|
+
char *buffer;
|
|
66
|
+
|
|
67
|
+
while (capacity < required) {
|
|
68
|
+
capacity *= 2;
|
|
69
|
+
}
|
|
70
|
+
buffer = (char *)realloc(g_buffer, capacity);
|
|
71
|
+
if (buffer == NULL) {
|
|
72
|
+
vpy_set_error("Out of memory while transferring a string to Python");
|
|
73
|
+
return VPY_ERROR;
|
|
74
|
+
}
|
|
75
|
+
g_buffer = buffer;
|
|
76
|
+
g_buffer_capacity = capacity;
|
|
77
|
+
}
|
|
78
|
+
memcpy(g_buffer + g_buffer_length, chunk, (size_t)length);
|
|
79
|
+
g_buffer_length = required;
|
|
80
|
+
return VPY_OK;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/*
|
|
84
|
+
* Push an integer_array_t value. Allocates the Python-owned storage that the
|
|
85
|
+
* element values are subsequently written to with vpy_array_write. The value
|
|
86
|
+
* is made available to Python source code by vpy_stage.
|
|
87
|
+
*/
|
|
88
|
+
VPY_EXPORT int32_t vpy_push_array(int32_t length, int32_t width, int32_t height, int32_t depth, int32_t bit_width,
|
|
89
|
+
int32_t is_signed) {
|
|
90
|
+
PyGILState_STATE gil;
|
|
91
|
+
size_t size = length > 0 ? (size_t)length * sizeof(int32_t) : 0;
|
|
92
|
+
PyObject *storage = NULL;
|
|
93
|
+
int status = VPY_ERROR;
|
|
94
|
+
|
|
95
|
+
if (vpy_initialize() != VPY_OK || vpy_enter(&gil) != VPY_OK) {
|
|
96
|
+
return VPY_ERROR;
|
|
97
|
+
}
|
|
98
|
+
vpy_clear_arguments();
|
|
99
|
+
if (length < 0) {
|
|
100
|
+
vpy_set_error("Internal error: negative array length");
|
|
101
|
+
goto done;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
storage = PyByteArray_FromStringAndSize(NULL, (Py_ssize_t)size);
|
|
105
|
+
if (storage == NULL) {
|
|
106
|
+
vpy_set_error_from_python();
|
|
107
|
+
goto done;
|
|
108
|
+
}
|
|
109
|
+
if (size > 0) {
|
|
110
|
+
memset(PyByteArray_AsString(storage), 0, size);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
g_array = PyObject_CallMethod(vpy_runtime(), "make_array", "Oiiiiii", storage, (int)length, (int)width, (int)height,
|
|
114
|
+
(int)depth, (int)bit_width, (int)(is_signed != 0));
|
|
115
|
+
if (g_array == NULL) {
|
|
116
|
+
vpy_set_error_from_python();
|
|
117
|
+
goto done;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* The array keeps the storage alive; we keep our own reference too so the
|
|
121
|
+
* cached data pointer is valid until the next vpy_clear_arguments(). */
|
|
122
|
+
g_pending = storage;
|
|
123
|
+
storage = NULL;
|
|
124
|
+
g_pending_data = PyByteArray_AsString(g_pending);
|
|
125
|
+
g_pending_size = size;
|
|
126
|
+
g_pending_position = 0;
|
|
127
|
+
status = VPY_OK;
|
|
128
|
+
|
|
129
|
+
done:
|
|
130
|
+
Py_XDECREF(storage);
|
|
131
|
+
PyGILState_Release(gil);
|
|
132
|
+
return status;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
VPY_EXPORT int32_t vpy_array_write(const int32_t *chunk, int32_t length) {
|
|
136
|
+
size_t bytes;
|
|
137
|
+
|
|
138
|
+
if (length <= 0) {
|
|
139
|
+
return VPY_OK;
|
|
140
|
+
}
|
|
141
|
+
bytes = (size_t)length * sizeof(int32_t);
|
|
142
|
+
if (g_pending_data == NULL || g_pending_position + bytes > g_pending_size) {
|
|
143
|
+
vpy_set_error("Internal error: array data written out of bounds");
|
|
144
|
+
return VPY_ERROR;
|
|
145
|
+
}
|
|
146
|
+
memcpy(g_pending_data + g_pending_position, chunk, bytes);
|
|
147
|
+
g_pending_position += bytes;
|
|
148
|
+
return VPY_OK;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
* Stage the pushed array in the runtime. Python source code built by VHDL
|
|
153
|
+
* refers to it as __vunit__.staged(<id>).
|
|
154
|
+
*
|
|
155
|
+
* Returns the id (>= 1) or -1 on failure, with the error text set.
|
|
156
|
+
*/
|
|
157
|
+
VPY_EXPORT int32_t vpy_stage(void) {
|
|
158
|
+
PyGILState_STATE gil;
|
|
159
|
+
PyObject *id;
|
|
160
|
+
long value;
|
|
161
|
+
int32_t result = -1;
|
|
162
|
+
|
|
163
|
+
if (vpy_initialize() != VPY_OK || vpy_enter(&gil) != VPY_OK) {
|
|
164
|
+
return -1;
|
|
165
|
+
}
|
|
166
|
+
if (g_array == NULL) {
|
|
167
|
+
vpy_set_error("Internal error: no value pushed before vpy_stage");
|
|
168
|
+
goto done;
|
|
169
|
+
}
|
|
170
|
+
id = PyObject_CallMethod(vpy_runtime(), "stage_value", "O", g_array);
|
|
171
|
+
if (id == NULL) {
|
|
172
|
+
vpy_set_error_from_python();
|
|
173
|
+
goto done;
|
|
174
|
+
}
|
|
175
|
+
value = PyLong_AsLong(id);
|
|
176
|
+
Py_DECREF(id);
|
|
177
|
+
if (PyErr_Occurred()) {
|
|
178
|
+
vpy_set_error_from_python();
|
|
179
|
+
goto done;
|
|
180
|
+
}
|
|
181
|
+
if (value < 1 || value > INT32_MAX) {
|
|
182
|
+
vpy_set_error("Internal error: invalid staged value id");
|
|
183
|
+
goto done;
|
|
184
|
+
}
|
|
185
|
+
result = (int32_t)value;
|
|
186
|
+
|
|
187
|
+
done:
|
|
188
|
+
vpy_clear_arguments();
|
|
189
|
+
PyGILState_Release(gil);
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
4
|
+
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com
|
|
7
|
+
*
|
|
8
|
+
* Internal header of the native bridge between VHDL and an embedded CPython
|
|
9
|
+
* interpreter. The bridge is a private implementation detail of
|
|
10
|
+
* python_ffi_pkg: VHDL builds Python source text and the bridge executes or
|
|
11
|
+
* evaluates it. VHDL reaches the entry points below through VHPIDIRECT on NVC
|
|
12
|
+
* and GHDL and through the FLI front end in fli.c on Questa/ModelSim.
|
|
13
|
+
*
|
|
14
|
+
* Modules:
|
|
15
|
+
* error.c error text reported to VHDL
|
|
16
|
+
* config.c configuration file written by VUnit next to the library
|
|
17
|
+
* interpreter.c starting the interpreter and loading ../runtime.py
|
|
18
|
+
* arguments.c strings and integer_array_t values transferred from VHDL
|
|
19
|
+
* operations.c setup, exec, eval and results transferred back to VHDL
|
|
20
|
+
* fli.c Questa/ModelSim front end, only built for that simulator
|
|
21
|
+
*
|
|
22
|
+
* ABI rules, chosen to be identical for NVC and GHDL on all platforms, and
|
|
23
|
+
* translated to the FLI parameter passing conventions by fli.c:
|
|
24
|
+
* - VHDL integer <-> int32_t, VHDL real <-> double.
|
|
25
|
+
* - Booleans are passed as integers (0/1), never as VHDL boolean.
|
|
26
|
+
* - Strings and integer vectors only cross the boundary as *constrained*
|
|
27
|
+
* chunks (plain pointers) with explicit offsets/lengths. Unconstrained
|
|
28
|
+
* arrays (fat pointers) are simulator specific and are never used.
|
|
29
|
+
* - Every operation that can fail returns a status (VPY_OK or VPY_ERROR).
|
|
30
|
+
* The error text is retrieved with vpy_error_length/vpy_error_read.
|
|
31
|
+
*
|
|
32
|
+
* Python objects are only touched with the GIL held. Python data symbols
|
|
33
|
+
* (e.g. Py_None) must not be used: the Windows DLL delay-loads the Python
|
|
34
|
+
* DLL, which only supports functions.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
#ifndef VUNIT_PYTHON_BRIDGE_H
|
|
38
|
+
#define VUNIT_PYTHON_BRIDGE_H
|
|
39
|
+
|
|
40
|
+
#define PY_SSIZE_T_CLEAN
|
|
41
|
+
#include <Python.h>
|
|
42
|
+
|
|
43
|
+
#include <stddef.h>
|
|
44
|
+
#include <stdint.h>
|
|
45
|
+
|
|
46
|
+
#ifdef Py_GIL_DISABLED
|
|
47
|
+
#error "VHDL Python support does not work with free-threaded CPython builds"
|
|
48
|
+
#endif
|
|
49
|
+
|
|
50
|
+
#ifdef _WIN32
|
|
51
|
+
#define VPY_EXPORT __declspec(dllexport)
|
|
52
|
+
#else
|
|
53
|
+
#define VPY_EXPORT __attribute__((visibility("default")))
|
|
54
|
+
#endif
|
|
55
|
+
|
|
56
|
+
#define VPY_OK 0
|
|
57
|
+
#define VPY_ERROR 1
|
|
58
|
+
|
|
59
|
+
/*
|
|
60
|
+
* Entry points called from VHDL (declared in python_bridge_pkg.vhd.in,
|
|
61
|
+
* directly for VHPIDIRECT and through the fli_ prefixed wrappers of fli.c for
|
|
62
|
+
* the FLI). Strings cross the boundary through a buffer; integer_array_t
|
|
63
|
+
* values are pushed and staged, everything else is Python source text built
|
|
64
|
+
* by VHDL.
|
|
65
|
+
*/
|
|
66
|
+
VPY_EXPORT int32_t vpy_setup(void);
|
|
67
|
+
VPY_EXPORT int32_t vpy_cleanup(void);
|
|
68
|
+
VPY_EXPORT int32_t vpy_buffer_clear(void);
|
|
69
|
+
VPY_EXPORT int32_t vpy_buffer_append(const char *chunk, int32_t length);
|
|
70
|
+
VPY_EXPORT int32_t vpy_begin(void);
|
|
71
|
+
VPY_EXPORT int32_t vpy_execute(int32_t is_file);
|
|
72
|
+
VPY_EXPORT int32_t vpy_eval(int32_t kind, int32_t width);
|
|
73
|
+
VPY_EXPORT int32_t vpy_push_array(int32_t length, int32_t width, int32_t height, int32_t depth, int32_t bit_width,
|
|
74
|
+
int32_t is_signed);
|
|
75
|
+
VPY_EXPORT int32_t vpy_array_write(const int32_t *chunk, int32_t length);
|
|
76
|
+
VPY_EXPORT int32_t vpy_stage(void);
|
|
77
|
+
VPY_EXPORT int32_t vpy_result_integer(void);
|
|
78
|
+
VPY_EXPORT double vpy_result_real(void);
|
|
79
|
+
VPY_EXPORT int32_t vpy_result_meta(int32_t index);
|
|
80
|
+
VPY_EXPORT void vpy_result_read_string(char *chunk, int32_t offset, int32_t length);
|
|
81
|
+
VPY_EXPORT void vpy_result_read_integers(int32_t *chunk, int32_t offset, int32_t length);
|
|
82
|
+
VPY_EXPORT void vpy_result_read_reals(double *chunk, int32_t offset, int32_t length);
|
|
83
|
+
VPY_EXPORT int32_t vpy_error_length(void);
|
|
84
|
+
VPY_EXPORT void vpy_error_read(char *chunk, int32_t offset, int32_t length);
|
|
85
|
+
|
|
86
|
+
/* Internal functions shared between the modules */
|
|
87
|
+
|
|
88
|
+
/* error.c */
|
|
89
|
+
void vpy_set_error(const char *text);
|
|
90
|
+
void vpy_set_error_bytes(const char *text, size_t length);
|
|
91
|
+
void vpy_set_error2(const char *prefix, const char *detail);
|
|
92
|
+
void vpy_set_error_from_python(void); /* GIL held */
|
|
93
|
+
int vpy_has_error(void);
|
|
94
|
+
|
|
95
|
+
/* config.c */
|
|
96
|
+
typedef struct {
|
|
97
|
+
char *executable; /* sys.executable of the Python running VUnit */
|
|
98
|
+
char *prefix; /* its sys.prefix, verified by the runtime */
|
|
99
|
+
char *runtime; /* path of runtime.py */
|
|
100
|
+
char *run_script_dir; /* directory of the run script, put on sys.path */
|
|
101
|
+
char *python_dll; /* Windows only: path of the Python DLL */
|
|
102
|
+
} vpy_config_t;
|
|
103
|
+
|
|
104
|
+
int vpy_read_config(vpy_config_t *config);
|
|
105
|
+
|
|
106
|
+
/* interpreter.c */
|
|
107
|
+
int vpy_initialize(void);
|
|
108
|
+
int vpy_is_initialized(void);
|
|
109
|
+
PyObject *vpy_runtime(void); /* borrowed, NULL before initialization */
|
|
110
|
+
int vpy_enter(PyGILState_STATE *gil);
|
|
111
|
+
int vpy_finish_call(PyObject *ret); /* GIL held, steals ret */
|
|
112
|
+
|
|
113
|
+
/* arguments.c (GIL held) */
|
|
114
|
+
PyObject *vpy_buffer_as_str(void);
|
|
115
|
+
void vpy_clear_arguments(void);
|
|
116
|
+
|
|
117
|
+
#endif
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
4
|
+
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com
|
|
7
|
+
*
|
|
8
|
+
* Configuration file written by VUnit next to the bridge library. It tells
|
|
9
|
+
* the bridge which Python to embed, where the runtime is and the base
|
|
10
|
+
* directory of relative Python file names.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
#include "bridge.h"
|
|
14
|
+
|
|
15
|
+
#include <stddef.h>
|
|
16
|
+
#include <stdio.h>
|
|
17
|
+
#include <stdlib.h>
|
|
18
|
+
#include <string.h>
|
|
19
|
+
|
|
20
|
+
#ifdef _WIN32
|
|
21
|
+
#include <windows.h>
|
|
22
|
+
#define DIRECTORY_SEPARATOR '\\'
|
|
23
|
+
#else
|
|
24
|
+
#include <dlfcn.h>
|
|
25
|
+
#define DIRECTORY_SEPARATOR '/'
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
#define CONFIG_FILE_NAME "vunit_python_bridge.cfg"
|
|
29
|
+
|
|
30
|
+
/* Return a malloc'ed UTF-8 path of the directory containing this library. */
|
|
31
|
+
static char *get_library_directory(void) {
|
|
32
|
+
char *path;
|
|
33
|
+
char *separator;
|
|
34
|
+
#ifdef _WIN32
|
|
35
|
+
static wchar_t wpath[32768]; /* static: keep it off the simulator stack */
|
|
36
|
+
HMODULE module = NULL;
|
|
37
|
+
DWORD length;
|
|
38
|
+
int utf8_length;
|
|
39
|
+
|
|
40
|
+
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
|
41
|
+
(LPCWSTR)(void *)&get_library_directory, &module)) {
|
|
42
|
+
return NULL;
|
|
43
|
+
}
|
|
44
|
+
length = GetModuleFileNameW(module, wpath, (DWORD)(sizeof(wpath) / sizeof(wpath[0])));
|
|
45
|
+
if (length == 0 || length >= sizeof(wpath) / sizeof(wpath[0])) {
|
|
46
|
+
return NULL;
|
|
47
|
+
}
|
|
48
|
+
utf8_length = WideCharToMultiByte(CP_UTF8, 0, wpath, (int)length, NULL, 0, NULL, NULL);
|
|
49
|
+
if (utf8_length <= 0) {
|
|
50
|
+
return NULL;
|
|
51
|
+
}
|
|
52
|
+
path = (char *)malloc((size_t)utf8_length + 1);
|
|
53
|
+
if (path == NULL) {
|
|
54
|
+
return NULL;
|
|
55
|
+
}
|
|
56
|
+
WideCharToMultiByte(CP_UTF8, 0, wpath, (int)length, path, utf8_length, NULL, NULL);
|
|
57
|
+
path[utf8_length] = '\0';
|
|
58
|
+
separator = strrchr(path, '\\');
|
|
59
|
+
if (separator == NULL) {
|
|
60
|
+
separator = strrchr(path, '/');
|
|
61
|
+
}
|
|
62
|
+
#else
|
|
63
|
+
Dl_info info;
|
|
64
|
+
|
|
65
|
+
if (dladdr((void *)&get_library_directory, &info) == 0 || info.dli_fname == NULL) {
|
|
66
|
+
return NULL;
|
|
67
|
+
}
|
|
68
|
+
path = strdup(info.dli_fname);
|
|
69
|
+
if (path == NULL) {
|
|
70
|
+
return NULL;
|
|
71
|
+
}
|
|
72
|
+
separator = strrchr(path, '/');
|
|
73
|
+
#endif
|
|
74
|
+
if (separator == NULL) {
|
|
75
|
+
free(path);
|
|
76
|
+
return NULL;
|
|
77
|
+
}
|
|
78
|
+
*separator = '\0';
|
|
79
|
+
return path;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static FILE *open_utf8_path(const char *path) {
|
|
83
|
+
#ifdef _WIN32
|
|
84
|
+
int length = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
|
|
85
|
+
wchar_t *wpath;
|
|
86
|
+
FILE *file;
|
|
87
|
+
|
|
88
|
+
if (length <= 0) {
|
|
89
|
+
return NULL;
|
|
90
|
+
}
|
|
91
|
+
wpath = (wchar_t *)malloc(sizeof(wchar_t) * (size_t)length);
|
|
92
|
+
if (wpath == NULL) {
|
|
93
|
+
return NULL;
|
|
94
|
+
}
|
|
95
|
+
MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, length);
|
|
96
|
+
file = _wfopen(wpath, L"rb");
|
|
97
|
+
free(wpath);
|
|
98
|
+
return file;
|
|
99
|
+
#else
|
|
100
|
+
return fopen(path, "rb");
|
|
101
|
+
#endif
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* The configuration file next to this library, opened for reading. */
|
|
105
|
+
static FILE *open_config_file(void) {
|
|
106
|
+
char *directory = get_library_directory();
|
|
107
|
+
size_t directory_length;
|
|
108
|
+
char *path;
|
|
109
|
+
FILE *file;
|
|
110
|
+
|
|
111
|
+
if (directory == NULL) {
|
|
112
|
+
vpy_set_error("Failed to determine the location of the Python bridge library");
|
|
113
|
+
return NULL;
|
|
114
|
+
}
|
|
115
|
+
directory_length = strlen(directory);
|
|
116
|
+
path = (char *)malloc(directory_length + 1 + sizeof(CONFIG_FILE_NAME));
|
|
117
|
+
if (path == NULL) {
|
|
118
|
+
free(directory);
|
|
119
|
+
vpy_set_error("Out of memory");
|
|
120
|
+
return NULL;
|
|
121
|
+
}
|
|
122
|
+
memcpy(path, directory, directory_length);
|
|
123
|
+
path[directory_length] = DIRECTORY_SEPARATOR;
|
|
124
|
+
memcpy(path + directory_length + 1, CONFIG_FILE_NAME, sizeof(CONFIG_FILE_NAME));
|
|
125
|
+
free(directory);
|
|
126
|
+
|
|
127
|
+
file = open_utf8_path(path);
|
|
128
|
+
if (file == NULL) {
|
|
129
|
+
vpy_set_error2("Failed to open the Python bridge configuration file ", path);
|
|
130
|
+
}
|
|
131
|
+
free(path);
|
|
132
|
+
return file;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* The field of config for a key, NULL for unknown keys. */
|
|
136
|
+
static char **config_field(vpy_config_t *config, const char *key) {
|
|
137
|
+
static const struct {
|
|
138
|
+
const char *key;
|
|
139
|
+
size_t offset;
|
|
140
|
+
} fields[] = {{"executable", offsetof(vpy_config_t, executable)},
|
|
141
|
+
{"prefix", offsetof(vpy_config_t, prefix)},
|
|
142
|
+
{"runtime", offsetof(vpy_config_t, runtime)},
|
|
143
|
+
{"run_script_dir", offsetof(vpy_config_t, run_script_dir)},
|
|
144
|
+
{"python_dll", offsetof(vpy_config_t, python_dll)}};
|
|
145
|
+
size_t index;
|
|
146
|
+
|
|
147
|
+
for (index = 0; index < sizeof(fields) / sizeof(fields[0]); index++) {
|
|
148
|
+
if (strcmp(key, fields[index].key) == 0) {
|
|
149
|
+
return (char **)((char *)config + fields[index].offset);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return NULL;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/*
|
|
156
|
+
* Read the "key=value" lines (UTF-8) of the configuration file into config,
|
|
157
|
+
* whose fields must be NULL or malloc'ed. Returns VPY_ERROR with the error
|
|
158
|
+
* text set on failure.
|
|
159
|
+
*/
|
|
160
|
+
int vpy_read_config(vpy_config_t *config) {
|
|
161
|
+
FILE *file = open_config_file();
|
|
162
|
+
char line[8192];
|
|
163
|
+
|
|
164
|
+
if (file == NULL) {
|
|
165
|
+
return VPY_ERROR;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
while (fgets(line, (int)sizeof(line), file) != NULL) {
|
|
169
|
+
size_t length = strlen(line);
|
|
170
|
+
char *separator;
|
|
171
|
+
char **field;
|
|
172
|
+
|
|
173
|
+
while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) {
|
|
174
|
+
line[--length] = '\0';
|
|
175
|
+
}
|
|
176
|
+
separator = strchr(line, '=');
|
|
177
|
+
if (separator == NULL) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
*separator = '\0';
|
|
181
|
+
field = config_field(config, line);
|
|
182
|
+
if (field != NULL) {
|
|
183
|
+
free(*field);
|
|
184
|
+
*field = strdup(separator + 1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
fclose(file);
|
|
188
|
+
|
|
189
|
+
if (config->executable == NULL || config->prefix == NULL || config->runtime == NULL ||
|
|
190
|
+
config->run_script_dir == NULL) {
|
|
191
|
+
vpy_set_error("Incomplete Python bridge configuration file");
|
|
192
|
+
return VPY_ERROR;
|
|
193
|
+
}
|
|
194
|
+
#ifdef _WIN32
|
|
195
|
+
if (config->python_dll == NULL) {
|
|
196
|
+
vpy_set_error("The Python bridge configuration file lacks the Python DLL path");
|
|
197
|
+
return VPY_ERROR;
|
|
198
|
+
}
|
|
199
|
+
#endif
|
|
200
|
+
return VPY_OK;
|
|
201
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
4
|
+
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com
|
|
7
|
+
*
|
|
8
|
+
* Error text of the last failed operation, reported to VHDL.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include "bridge.h"
|
|
12
|
+
|
|
13
|
+
#include <stdio.h>
|
|
14
|
+
#include <stdlib.h>
|
|
15
|
+
#include <string.h>
|
|
16
|
+
|
|
17
|
+
/* UTF-8, owned. Sticky when initialization failed. */
|
|
18
|
+
static char *g_error = NULL;
|
|
19
|
+
static size_t g_error_length = 0;
|
|
20
|
+
|
|
21
|
+
void vpy_set_error_bytes(const char *text, size_t length) {
|
|
22
|
+
char *copy = (char *)malloc(length + 1);
|
|
23
|
+
|
|
24
|
+
free(g_error);
|
|
25
|
+
g_error = NULL;
|
|
26
|
+
g_error_length = 0;
|
|
27
|
+
if (copy == NULL) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
memcpy(copy, text, length);
|
|
31
|
+
copy[length] = '\0';
|
|
32
|
+
g_error = copy;
|
|
33
|
+
g_error_length = length;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
void vpy_set_error(const char *text) { vpy_set_error_bytes(text, strlen(text)); }
|
|
37
|
+
|
|
38
|
+
/* prefix may be the current error text, it is copied before being replaced. */
|
|
39
|
+
void vpy_set_error2(const char *prefix, const char *detail) {
|
|
40
|
+
char text[4096];
|
|
41
|
+
int length = snprintf(text, sizeof(text), "%s%s", prefix, detail == NULL ? "" : detail);
|
|
42
|
+
|
|
43
|
+
vpy_set_error_bytes(text, length < 0 ? 0 : (length < (int)sizeof(text) ? (size_t)length : sizeof(text) - 1));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
int vpy_has_error(void) { return g_error != NULL; }
|
|
47
|
+
|
|
48
|
+
/* New reference to the raised exception, which is cleared. NULL if none. */
|
|
49
|
+
static PyObject *take_exception(void) {
|
|
50
|
+
#if PY_VERSION_HEX >= 0x030C0000
|
|
51
|
+
return PyErr_GetRaisedException();
|
|
52
|
+
#else
|
|
53
|
+
PyObject *type, *value, *traceback;
|
|
54
|
+
|
|
55
|
+
PyErr_Fetch(&type, &value, &traceback);
|
|
56
|
+
PyErr_NormalizeException(&type, &value, &traceback);
|
|
57
|
+
if (value != NULL && traceback != NULL) {
|
|
58
|
+
PyException_SetTraceback(value, traceback);
|
|
59
|
+
}
|
|
60
|
+
Py_XDECREF(type);
|
|
61
|
+
Py_XDECREF(traceback);
|
|
62
|
+
return value;
|
|
63
|
+
#endif
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/*
|
|
67
|
+
* Convert the currently raised Python exception into the error text and clear
|
|
68
|
+
* it. Formatting is delegated to the runtime, which hides bridge-internal
|
|
69
|
+
* traceback frames; plain str() and a fixed text are the fallbacks.
|
|
70
|
+
*/
|
|
71
|
+
void vpy_set_error_from_python(void) {
|
|
72
|
+
PyObject *exc = take_exception();
|
|
73
|
+
PyObject *text = NULL;
|
|
74
|
+
|
|
75
|
+
if (exc == NULL) {
|
|
76
|
+
vpy_set_error("Unknown Python error (no exception set)");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (vpy_runtime() != NULL) {
|
|
81
|
+
text = PyObject_CallMethod(vpy_runtime(), "format_exception", "O", exc);
|
|
82
|
+
if (text == NULL) {
|
|
83
|
+
PyErr_Clear();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (text == NULL) {
|
|
87
|
+
text = PyObject_Str(exc);
|
|
88
|
+
if (text == NULL) {
|
|
89
|
+
PyErr_Clear();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (text != NULL && PyUnicode_Check(text)) {
|
|
94
|
+
Py_ssize_t length;
|
|
95
|
+
const char *utf8 = PyUnicode_AsUTF8AndSize(text, &length);
|
|
96
|
+
|
|
97
|
+
if (utf8 != NULL) {
|
|
98
|
+
vpy_set_error_bytes(utf8, (size_t)length);
|
|
99
|
+
} else {
|
|
100
|
+
PyErr_Clear();
|
|
101
|
+
vpy_set_error("Python error (message could not be encoded)");
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
vpy_set_error("Python error (message could not be formatted)");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Py_XDECREF(text);
|
|
108
|
+
Py_DECREF(exc);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
VPY_EXPORT int32_t vpy_error_length(void) {
|
|
112
|
+
return g_error_length > INT32_MAX ? INT32_MAX : (int32_t)g_error_length;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
VPY_EXPORT void vpy_error_read(char *chunk, int32_t offset, int32_t length) {
|
|
116
|
+
if (g_error == NULL || offset < 0 || length <= 0 || (size_t)offset + (size_t)length > g_error_length) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
memcpy(chunk, g_error + offset, (size_t)length);
|
|
120
|
+
}
|