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,645 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
3
|
+
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Runtime of the Python bridge.
|
|
9
|
+
|
|
10
|
+
This module is executed *inside the simulator process* by the embedded
|
|
11
|
+
interpreter of the native bridge (native/*.c). It is loaded by file
|
|
12
|
+
path, not imported from the vunit package, and must therefore not import
|
|
13
|
+
anything from vunit. It is a private implementation detail of the VHDL
|
|
14
|
+
exec/eval operations.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import builtins
|
|
18
|
+
import linecache
|
|
19
|
+
import math
|
|
20
|
+
import numbers
|
|
21
|
+
import os
|
|
22
|
+
import struct
|
|
23
|
+
import sys
|
|
24
|
+
import traceback
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
import __main__
|
|
27
|
+
import numpy # type: ignore[import-not-found]
|
|
28
|
+
|
|
29
|
+
# Result kinds, must match python_ffi_pkg
|
|
30
|
+
KIND_INTEGER = 0
|
|
31
|
+
KIND_REAL = 1
|
|
32
|
+
KIND_BOOLEAN = 2
|
|
33
|
+
KIND_STRING = 3
|
|
34
|
+
KIND_STD_ULOGIC = 4
|
|
35
|
+
KIND_STD_ULOGIC_VECTOR = 5
|
|
36
|
+
KIND_SIGNED = 6
|
|
37
|
+
KIND_UNSIGNED = 7
|
|
38
|
+
KIND_INTEGER_ARRAY = 8
|
|
39
|
+
KIND_INTEGER_VECTOR = 9
|
|
40
|
+
KIND_REAL_VECTOR = 10
|
|
41
|
+
|
|
42
|
+
VHDL_TYPE_NAMES = {
|
|
43
|
+
KIND_INTEGER: "integer",
|
|
44
|
+
KIND_REAL: "real",
|
|
45
|
+
KIND_BOOLEAN: "boolean",
|
|
46
|
+
KIND_STRING: "string",
|
|
47
|
+
KIND_STD_ULOGIC: "std_ulogic",
|
|
48
|
+
KIND_STD_ULOGIC_VECTOR: "std_ulogic_vector",
|
|
49
|
+
KIND_SIGNED: "signed",
|
|
50
|
+
KIND_UNSIGNED: "unsigned",
|
|
51
|
+
KIND_INTEGER_ARRAY: "integer_array_t",
|
|
52
|
+
KIND_INTEGER_VECTOR: "integer_vector",
|
|
53
|
+
KIND_REAL_VECTOR: "real_vector",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
STD_ULOGIC_VALUES = frozenset("UX01ZWLH-")
|
|
57
|
+
INTEGER_LOW = -(2**31)
|
|
58
|
+
INTEGER_HIGH = 2**31 - 1
|
|
59
|
+
TEXT_ENCODING = "utf-8"
|
|
60
|
+
TEXT_ERRORS = "surrogateescape"
|
|
61
|
+
|
|
62
|
+
# Key of the default session namespace. A session is keyed by the full name of
|
|
63
|
+
# its VUnit identity, see default_session in python_ffi_pkg.
|
|
64
|
+
DEFAULT_SESSION = "python_bridge:python:default"
|
|
65
|
+
|
|
66
|
+
# Name of the bridge object injected into every session namespace.
|
|
67
|
+
HANDLE_NAME = "__vunit__"
|
|
68
|
+
|
|
69
|
+
_NO_VALUE = object()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _type_name(value):
|
|
73
|
+
return type(value).__qualname__
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _is_bool(value):
|
|
77
|
+
"""True for Python and NumPy booleans."""
|
|
78
|
+
return isinstance(value, (bool, numpy.bool_))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _is_float(value):
|
|
82
|
+
"""
|
|
83
|
+
True for Python and NumPy floats only.
|
|
84
|
+
|
|
85
|
+
An int is not accepted, matching the PyFloat_Check of the other
|
|
86
|
+
python_ffi_pkg implementations.
|
|
87
|
+
"""
|
|
88
|
+
return isinstance(value, (float, numpy.floating))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _is_integer(value):
|
|
92
|
+
"""True for Python and NumPy integers, but not for booleans."""
|
|
93
|
+
return not _is_bool(value) and isinstance(value, numbers.Integral)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _encode(text):
|
|
97
|
+
return text.encode(TEXT_ENCODING, TEXT_ERRORS)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _type_error(kind, value, expected):
|
|
101
|
+
"""
|
|
102
|
+
The error raised when a Python value cannot become the requested VHDL type.
|
|
103
|
+
|
|
104
|
+
The operation it belongs to (exec, eval, call, ...) is added by VHDL.
|
|
105
|
+
"""
|
|
106
|
+
return TypeError(
|
|
107
|
+
f"Cannot convert Python {_type_name(value)} ({value!r:.200}) "
|
|
108
|
+
f"to VHDL {VHDL_TYPE_NAMES[kind]}; expected {expected}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class BridgeHandle:
|
|
113
|
+
"""
|
|
114
|
+
The ``__vunit__`` object that VHDL-generated Python expressions use.
|
|
115
|
+
|
|
116
|
+
It is the only name the bridge adds to a session namespace.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def __init__(self, runtime):
|
|
120
|
+
self._runtime = runtime
|
|
121
|
+
|
|
122
|
+
def staged(self, staged_id):
|
|
123
|
+
"""
|
|
124
|
+
The value VHDL staged under the given id.
|
|
125
|
+
"""
|
|
126
|
+
return self._runtime.staged(staged_id)
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def error(message):
|
|
130
|
+
"""
|
|
131
|
+
Raise the error VHDL reported for an argument it could not convert.
|
|
132
|
+
|
|
133
|
+
Such an argument becomes __vunit__.error(<message>) in the Python
|
|
134
|
+
source text of the call, so that the call fails with the same message
|
|
135
|
+
instead of silently being made without the argument.
|
|
136
|
+
"""
|
|
137
|
+
raise RuntimeError(message)
|
|
138
|
+
|
|
139
|
+
def __repr__(self):
|
|
140
|
+
return "<VUnit python bridge>"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class Runtime: # pylint: disable=too-many-instance-attributes
|
|
144
|
+
"""
|
|
145
|
+
State of the embedded Python session: one persistent namespace per session,
|
|
146
|
+
shared by all exec and eval operations of a simulation.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, run_script_dir, prefix):
|
|
150
|
+
self._run_script_dir = Path(run_script_dir)
|
|
151
|
+
self._handle = BridgeHandle(self)
|
|
152
|
+
# One namespace per session. The default session uses __main__.
|
|
153
|
+
self._sessions = {DEFAULT_SESSION: __main__.__dict__}
|
|
154
|
+
__main__.__dict__[HANDLE_NAME] = self._handle
|
|
155
|
+
self._session = DEFAULT_SESSION
|
|
156
|
+
self._exec_count = 0
|
|
157
|
+
self._eval_count = 0
|
|
158
|
+
self._result = _NO_VALUE
|
|
159
|
+
# Metadata of the integer_array_t values handed to the current
|
|
160
|
+
# operation, keyed by id() of the NumPy array created for them.
|
|
161
|
+
self._array_meta = {}
|
|
162
|
+
# Values transferred from VHDL, id -> (value, bit_width, is_signed).
|
|
163
|
+
# VHDL transfers an integer_array_t once and then refers to it as
|
|
164
|
+
# __vunit__.staged(<id>) in the Python expressions it evaluates. Staged
|
|
165
|
+
# values live until the simulation ends, or until python_cleanup
|
|
166
|
+
# releases them early, so that one VHDL constant can be used in
|
|
167
|
+
# several calls.
|
|
168
|
+
self._staged = {}
|
|
169
|
+
|
|
170
|
+
self._check_environment(prefix)
|
|
171
|
+
|
|
172
|
+
# Mimic "python run.py": the run script directory is sys.path[0]. It is
|
|
173
|
+
# not used to resolve file names, which VHDL does, but import_run_script
|
|
174
|
+
# imports the run script as a module and it can then import its siblings
|
|
175
|
+
# like it does when the run script is started by python.
|
|
176
|
+
if str(self._run_script_dir) not in sys.path:
|
|
177
|
+
sys.path.insert(0, str(self._run_script_dir))
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def _check_environment(prefix):
|
|
181
|
+
"""
|
|
182
|
+
The embedded interpreter must use the Python environment that launched VUnit.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
def normalize(path):
|
|
186
|
+
return os.path.normcase(os.path.realpath(path))
|
|
187
|
+
|
|
188
|
+
if normalize(sys.prefix) != normalize(prefix):
|
|
189
|
+
raise RuntimeError(
|
|
190
|
+
f"The embedded Python interpreter selected the environment {sys.prefix!r} "
|
|
191
|
+
f"but VUnit was started from {prefix!r}"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def _flush():
|
|
196
|
+
"""
|
|
197
|
+
Flush Python's output so that it is ordered with the simulator's output.
|
|
198
|
+
"""
|
|
199
|
+
for stream in (sys.stdout, sys.stderr):
|
|
200
|
+
try:
|
|
201
|
+
if stream is not None:
|
|
202
|
+
stream.flush()
|
|
203
|
+
except Exception: # pylint: disable=broad-except
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
def format_exception(self, exc):
|
|
207
|
+
"""
|
|
208
|
+
Format an exception with its traceback, hiding frames of this module.
|
|
209
|
+
"""
|
|
210
|
+
this_file = os.path.normcase(os.path.abspath(__file__))
|
|
211
|
+
traceback_ = exc.__traceback__
|
|
212
|
+
while traceback_ is not None and (
|
|
213
|
+
os.path.normcase(os.path.abspath(traceback_.tb_frame.f_code.co_filename)) == this_file
|
|
214
|
+
):
|
|
215
|
+
traceback_ = traceback_.tb_next
|
|
216
|
+
self._flush()
|
|
217
|
+
return "".join(traceback.format_exception(type(exc), exc, traceback_)).rstrip("\n")
|
|
218
|
+
|
|
219
|
+
# ------------------------------------------------------------------
|
|
220
|
+
# Lifecycle
|
|
221
|
+
# ------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
def cleanup(self):
|
|
224
|
+
"""
|
|
225
|
+
Release what the simulation acquired: the last result and the staged
|
|
226
|
+
values. The interpreter and the session namespaces are kept, so
|
|
227
|
+
operations after python_cleanup keep working. Calling it at all is
|
|
228
|
+
optional: it only releases these values earlier than the end of the
|
|
229
|
+
simulator process, and every operation flushes on its own.
|
|
230
|
+
"""
|
|
231
|
+
self._result = _NO_VALUE
|
|
232
|
+
self._staged.clear()
|
|
233
|
+
self._array_meta.clear()
|
|
234
|
+
self._flush()
|
|
235
|
+
|
|
236
|
+
def select_session(self, name):
|
|
237
|
+
"""
|
|
238
|
+
Make the namespace of a session current, creating it on first use. The
|
|
239
|
+
name is the full name of the VUnit identity of the session, which makes
|
|
240
|
+
two sessions with the same name but different identities namespaces of
|
|
241
|
+
their own.
|
|
242
|
+
"""
|
|
243
|
+
if name not in self._sessions:
|
|
244
|
+
self._sessions[name] = {
|
|
245
|
+
"__name__": "__main__",
|
|
246
|
+
"__builtins__": builtins,
|
|
247
|
+
HANDLE_NAME: self._handle,
|
|
248
|
+
}
|
|
249
|
+
self._session = name
|
|
250
|
+
self._result = _NO_VALUE
|
|
251
|
+
self._array_meta = {}
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def _namespace(self):
|
|
255
|
+
"""
|
|
256
|
+
Namespace of the current session.
|
|
257
|
+
"""
|
|
258
|
+
return self._sessions[self._session]
|
|
259
|
+
|
|
260
|
+
def _source_name(self, operation, count):
|
|
261
|
+
"""
|
|
262
|
+
Pseudo file name of inline source code, shown in tracebacks.
|
|
263
|
+
"""
|
|
264
|
+
if self._session == DEFAULT_SESSION:
|
|
265
|
+
return f"<{operation} #{count}>"
|
|
266
|
+
return f"<{operation} {self._session} #{count}>"
|
|
267
|
+
|
|
268
|
+
# ------------------------------------------------------------------
|
|
269
|
+
# exec
|
|
270
|
+
# ------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
def execute(self, text, is_file):
|
|
273
|
+
"""
|
|
274
|
+
Execute inline source code or a Python file in the persistent namespace.
|
|
275
|
+
"""
|
|
276
|
+
try:
|
|
277
|
+
if is_file:
|
|
278
|
+
self._execute_file(text)
|
|
279
|
+
else:
|
|
280
|
+
self._execute_source(text)
|
|
281
|
+
finally:
|
|
282
|
+
self._flush()
|
|
283
|
+
|
|
284
|
+
def _execute_source(self, source):
|
|
285
|
+
"""
|
|
286
|
+
Execute inline source code, registered in linecache for readable tracebacks.
|
|
287
|
+
"""
|
|
288
|
+
self._exec_count += 1
|
|
289
|
+
file_name = self._source_name("exec", self._exec_count)
|
|
290
|
+
# Make tracebacks show the source lines of inline code
|
|
291
|
+
linecache.cache[file_name] = (len(source), None, source.splitlines(True), file_name)
|
|
292
|
+
code = compile(source, file_name, "exec", dont_inherit=True)
|
|
293
|
+
exec(code, self._namespace, self._namespace) # pylint: disable=exec-used
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def _resolve_file(file_name):
|
|
297
|
+
"""
|
|
298
|
+
Absolute path of a Python file. VHDL resolves a relative file name
|
|
299
|
+
against the directory of its testbench before it gets here.
|
|
300
|
+
"""
|
|
301
|
+
if file_name == "":
|
|
302
|
+
raise ValueError("Empty Python file name")
|
|
303
|
+
return Path(os.path.normpath(Path(file_name).absolute()))
|
|
304
|
+
|
|
305
|
+
def _execute_file(self, file_name):
|
|
306
|
+
"""
|
|
307
|
+
Execute a Python file with __file__ set and its directory on sys.path.
|
|
308
|
+
"""
|
|
309
|
+
path = self._resolve_file(file_name)
|
|
310
|
+
with open(path, encoding="utf-8") as fptr:
|
|
311
|
+
source = fptr.read()
|
|
312
|
+
code = compile(source, str(path), "exec", dont_inherit=True)
|
|
313
|
+
|
|
314
|
+
namespace = self._namespace
|
|
315
|
+
previous_file = namespace.get("__file__", _NO_VALUE)
|
|
316
|
+
directory = str(path.parent)
|
|
317
|
+
namespace["__file__"] = str(path)
|
|
318
|
+
# Let the file import its sibling modules
|
|
319
|
+
sys.path.insert(0, directory)
|
|
320
|
+
try:
|
|
321
|
+
exec(code, namespace, namespace) # pylint: disable=exec-used
|
|
322
|
+
finally:
|
|
323
|
+
try:
|
|
324
|
+
sys.path.remove(directory)
|
|
325
|
+
except ValueError:
|
|
326
|
+
pass
|
|
327
|
+
if previous_file is _NO_VALUE:
|
|
328
|
+
namespace.pop("__file__", None)
|
|
329
|
+
else:
|
|
330
|
+
namespace["__file__"] = previous_file
|
|
331
|
+
|
|
332
|
+
# ------------------------------------------------------------------
|
|
333
|
+
# eval
|
|
334
|
+
# ------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
def evaluate(self, text, kind, width):
|
|
337
|
+
"""
|
|
338
|
+
Evaluate a Python expression in the session namespace and convert the
|
|
339
|
+
value to the VHDL type given by kind.
|
|
340
|
+
|
|
341
|
+
:param width: Width of a std_ulogic_vector/signed/unsigned result, -1 if not given by VHDL.
|
|
342
|
+
:returns: (integer, real, data bytes, metadata tuple)
|
|
343
|
+
"""
|
|
344
|
+
self._result = _NO_VALUE
|
|
345
|
+
self._array_meta = {}
|
|
346
|
+
self._eval_count += 1
|
|
347
|
+
file_name = self._source_name("eval", self._eval_count)
|
|
348
|
+
try:
|
|
349
|
+
# Make tracebacks show the source lines of the expression
|
|
350
|
+
linecache.cache[file_name] = (len(text), None, text.splitlines(True), file_name)
|
|
351
|
+
code = compile(text, file_name, "eval", dont_inherit=True)
|
|
352
|
+
# Running the user's own Python code is the purpose of this module
|
|
353
|
+
self._result = eval(code, self._namespace, self._namespace) # pylint: disable=eval-used
|
|
354
|
+
except BaseException:
|
|
355
|
+
self._array_meta = {}
|
|
356
|
+
self._flush()
|
|
357
|
+
raise
|
|
358
|
+
self._flush()
|
|
359
|
+
return self.convert_result(kind, width)
|
|
360
|
+
|
|
361
|
+
# ------------------------------------------------------------------
|
|
362
|
+
# Values transferred from VHDL
|
|
363
|
+
# ------------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
def staged(self, staged_id):
|
|
366
|
+
"""
|
|
367
|
+
The value staged under an id, used from Python as __vunit__.staged(id).
|
|
368
|
+
Every use gets its own copy so that a function modifying it in place
|
|
369
|
+
does not affect the next use.
|
|
370
|
+
"""
|
|
371
|
+
if staged_id not in self._staged:
|
|
372
|
+
raise ValueError(f"There is no value staged under the id {staged_id!r}")
|
|
373
|
+
value, bit_width, is_signed = self._staged[staged_id]
|
|
374
|
+
value = value.copy()
|
|
375
|
+
# An expression returning this very array keeps its word size
|
|
376
|
+
self._array_meta[id(value)] = (value, bit_width, is_signed)
|
|
377
|
+
return value
|
|
378
|
+
|
|
379
|
+
def stage_value(self, value):
|
|
380
|
+
"""
|
|
381
|
+
Stage a value transferred from VHDL and return its id.
|
|
382
|
+
"""
|
|
383
|
+
meta = self._array_meta.get(id(value))
|
|
384
|
+
if meta is not None and meta[0] is value:
|
|
385
|
+
bit_width, is_signed = meta[1], meta[2]
|
|
386
|
+
else:
|
|
387
|
+
bit_width, is_signed = 32, True
|
|
388
|
+
staged_id = len(self._staged) + 1
|
|
389
|
+
self._staged[staged_id] = (value, bit_width, bool(is_signed))
|
|
390
|
+
return staged_id
|
|
391
|
+
|
|
392
|
+
@staticmethod
|
|
393
|
+
def _shape(length, width, height, depth):
|
|
394
|
+
"""
|
|
395
|
+
NumPy shape of an integer_array_t. get(arr, x, y) is a[y, x] and
|
|
396
|
+
get(arr, x, y, z) is a[y, x, z], matching VUnit's storage order.
|
|
397
|
+
"""
|
|
398
|
+
if depth > 1:
|
|
399
|
+
return (height, width, depth)
|
|
400
|
+
if height > 1:
|
|
401
|
+
return (height, width)
|
|
402
|
+
return (length,)
|
|
403
|
+
|
|
404
|
+
def make_array(
|
|
405
|
+
self, storage, length, width, height, depth, bit_width, is_signed
|
|
406
|
+
): # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
407
|
+
"""
|
|
408
|
+
Create the NumPy array for an integer_array_t value. The bridge
|
|
409
|
+
fills the storage (native int32) after this call returns.
|
|
410
|
+
"""
|
|
411
|
+
array = numpy.frombuffer(storage, dtype=numpy.int32).reshape(self._shape(length, width, height, depth))
|
|
412
|
+
self._array_meta[id(array)] = (array, bit_width, bool(is_signed))
|
|
413
|
+
return array
|
|
414
|
+
|
|
415
|
+
# ------------------------------------------------------------------
|
|
416
|
+
# Results
|
|
417
|
+
# ------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
def convert_result(self, kind, width):
|
|
420
|
+
"""
|
|
421
|
+
Convert the value of the last evaluation to the VHDL type given by kind.
|
|
422
|
+
|
|
423
|
+
:param width: Width of a std_ulogic_vector/signed/unsigned result, -1 if not given by VHDL.
|
|
424
|
+
:returns: (integer, real, data bytes, metadata tuple)
|
|
425
|
+
"""
|
|
426
|
+
value = self._result
|
|
427
|
+
self._result = _NO_VALUE
|
|
428
|
+
array_meta = self._array_meta
|
|
429
|
+
self._array_meta = {}
|
|
430
|
+
if value is _NO_VALUE:
|
|
431
|
+
raise RuntimeError("Internal error: no Python result available")
|
|
432
|
+
|
|
433
|
+
if kind == KIND_INTEGER_ARRAY:
|
|
434
|
+
return self._array_result(value, array_meta)
|
|
435
|
+
if kind not in VHDL_TYPE_NAMES:
|
|
436
|
+
raise RuntimeError(f"Internal error: unknown result kind {kind}")
|
|
437
|
+
converter = "bits" if kind in (KIND_SIGNED, KIND_UNSIGNED) else VHDL_TYPE_NAMES[kind]
|
|
438
|
+
return getattr(self, f"_{converter}_result")(kind, value, width)
|
|
439
|
+
|
|
440
|
+
@staticmethod
|
|
441
|
+
def _integer_result(kind, value, _width):
|
|
442
|
+
"""
|
|
443
|
+
Convert an int result.
|
|
444
|
+
"""
|
|
445
|
+
if not _is_integer(value):
|
|
446
|
+
raise _type_error(kind, value, "int")
|
|
447
|
+
value = int(value)
|
|
448
|
+
if not INTEGER_LOW <= value <= INTEGER_HIGH:
|
|
449
|
+
raise OverflowError(f"{value} is outside the range of VHDL integer ({INTEGER_LOW} to {INTEGER_HIGH})")
|
|
450
|
+
return (value, 0.0, b"", ())
|
|
451
|
+
|
|
452
|
+
@staticmethod
|
|
453
|
+
def _real_result(kind, value, _width):
|
|
454
|
+
"""
|
|
455
|
+
Convert a float result. An int is not a float, like in Python's C API.
|
|
456
|
+
"""
|
|
457
|
+
if not _is_float(value):
|
|
458
|
+
raise _type_error(kind, value, "float")
|
|
459
|
+
result = float(value)
|
|
460
|
+
if not math.isfinite(result):
|
|
461
|
+
raise ValueError(f"{result} cannot be represented as VHDL real")
|
|
462
|
+
return (0, result, b"", ())
|
|
463
|
+
|
|
464
|
+
@staticmethod
|
|
465
|
+
def _boolean_result(kind, value, _width):
|
|
466
|
+
"""
|
|
467
|
+
Convert a bool result.
|
|
468
|
+
"""
|
|
469
|
+
if not _is_bool(value):
|
|
470
|
+
raise _type_error(kind, value, "bool")
|
|
471
|
+
return (int(bool(value)), 0.0, b"", ())
|
|
472
|
+
|
|
473
|
+
@staticmethod
|
|
474
|
+
def _string_result(kind, value, _width):
|
|
475
|
+
"""
|
|
476
|
+
Convert a str result to UTF-8.
|
|
477
|
+
"""
|
|
478
|
+
if not isinstance(value, str):
|
|
479
|
+
raise _type_error(kind, value, "str")
|
|
480
|
+
data = _encode(value)
|
|
481
|
+
return (0, 0.0, data, (len(data),))
|
|
482
|
+
|
|
483
|
+
@staticmethod
|
|
484
|
+
def _std_ulogic_result(kind, value, _width):
|
|
485
|
+
"""
|
|
486
|
+
Convert a one character str result.
|
|
487
|
+
"""
|
|
488
|
+
if not isinstance(value, str) or len(value) != 1 or value not in STD_ULOGIC_VALUES:
|
|
489
|
+
raise _type_error(kind, value, "a one character str, one of 'UX01ZWLH-'")
|
|
490
|
+
return (0, 0.0, value.encode("ascii"), (1,))
|
|
491
|
+
|
|
492
|
+
@staticmethod
|
|
493
|
+
def _std_ulogic_vector_result(kind, value, width):
|
|
494
|
+
"""
|
|
495
|
+
Convert a str result, one character per element.
|
|
496
|
+
"""
|
|
497
|
+
if not isinstance(value, str) or not STD_ULOGIC_VALUES.issuperset(value):
|
|
498
|
+
raise _type_error(kind, value, "a str of the characters 'UX01ZWLH-'")
|
|
499
|
+
if width >= 0 and len(value) != width:
|
|
500
|
+
raise ValueError(f"Got {len(value)} std_ulogic values but the VHDL result has length {width}")
|
|
501
|
+
return (0, 0.0, value.encode("ascii"), (len(value),))
|
|
502
|
+
|
|
503
|
+
def _bits_result(self, kind, value, width):
|
|
504
|
+
"""
|
|
505
|
+
Convert an int result to the bits of a signed/unsigned.
|
|
506
|
+
"""
|
|
507
|
+
data = self._int_to_bits(kind, value, width)
|
|
508
|
+
return (0, 0.0, data, (len(data),))
|
|
509
|
+
|
|
510
|
+
@staticmethod
|
|
511
|
+
def _int_to_bits(kind, value, width):
|
|
512
|
+
"""
|
|
513
|
+
Two's complement/binary image of an int, checked to fit width.
|
|
514
|
+
"""
|
|
515
|
+
if not _is_integer(value):
|
|
516
|
+
raise _type_error(kind, value, "int")
|
|
517
|
+
value = int(value)
|
|
518
|
+
is_signed = kind == KIND_SIGNED
|
|
519
|
+
if is_signed:
|
|
520
|
+
low, high = (-(1 << (width - 1)), (1 << (width - 1)) - 1) if width > 0 else (0, -1)
|
|
521
|
+
else:
|
|
522
|
+
low, high = 0, (1 << width) - 1
|
|
523
|
+
if not low <= value <= high:
|
|
524
|
+
raise OverflowError(f"{value} does not fit in a {width} bit {VHDL_TYPE_NAMES[kind]} ({low} to {high})")
|
|
525
|
+
if width == 0:
|
|
526
|
+
return b""
|
|
527
|
+
return format(value & ((1 << width) - 1), f"0{width}b").encode("ascii")
|
|
528
|
+
|
|
529
|
+
@staticmethod
|
|
530
|
+
def _sequence(kind, value):
|
|
531
|
+
"""
|
|
532
|
+
The elements of a list/tuple result. Nothing else is accepted, like in
|
|
533
|
+
the other python_ffi_pkg implementations.
|
|
534
|
+
"""
|
|
535
|
+
if not isinstance(value, (list, tuple)):
|
|
536
|
+
raise _type_error(kind, value, "a list or tuple")
|
|
537
|
+
return value
|
|
538
|
+
|
|
539
|
+
def _integer_vector_result(self, kind, value, _width):
|
|
540
|
+
"""
|
|
541
|
+
Convert a list/tuple of int to integer_vector data (native int32).
|
|
542
|
+
"""
|
|
543
|
+
values = self._sequence(kind, value)
|
|
544
|
+
integers = []
|
|
545
|
+
for index, element in enumerate(values):
|
|
546
|
+
if not _is_integer(element):
|
|
547
|
+
raise TypeError(
|
|
548
|
+
f"Cannot convert element {index} of the Python {_type_name(value)}, "
|
|
549
|
+
f"a {_type_name(element)} ({element!r:.100}), to a VHDL integer; expected int"
|
|
550
|
+
)
|
|
551
|
+
element = int(element)
|
|
552
|
+
if not INTEGER_LOW <= element <= INTEGER_HIGH:
|
|
553
|
+
raise OverflowError(
|
|
554
|
+
f"Element {index} ({element}) is outside the range of VHDL integer "
|
|
555
|
+
f"({INTEGER_LOW} to {INTEGER_HIGH})"
|
|
556
|
+
)
|
|
557
|
+
integers.append(element)
|
|
558
|
+
data = struct.pack(f"={len(integers)}i", *integers)
|
|
559
|
+
return (0, 0.0, data, (len(integers),))
|
|
560
|
+
|
|
561
|
+
def _real_vector_result(self, kind, value, _width):
|
|
562
|
+
"""
|
|
563
|
+
Convert a list/tuple of float to real_vector data (native float64).
|
|
564
|
+
"""
|
|
565
|
+
values = self._sequence(kind, value)
|
|
566
|
+
reals = []
|
|
567
|
+
for index, element in enumerate(values):
|
|
568
|
+
if not _is_float(element):
|
|
569
|
+
raise TypeError(
|
|
570
|
+
f"Cannot convert element {index} of the Python {_type_name(value)}, "
|
|
571
|
+
f"a {_type_name(element)} ({element!r:.100}), to a VHDL real; expected float"
|
|
572
|
+
)
|
|
573
|
+
element = float(element)
|
|
574
|
+
if not math.isfinite(element):
|
|
575
|
+
raise ValueError(f"Element {index} ({element}) cannot be represented as VHDL real")
|
|
576
|
+
reals.append(element)
|
|
577
|
+
data = struct.pack(f"={len(reals)}d", *reals)
|
|
578
|
+
return (0, 0.0, data, (len(reals),))
|
|
579
|
+
|
|
580
|
+
_DTYPE_WORD_SIZE = {
|
|
581
|
+
# dtype.str without byte order: (bit_width, is_signed)
|
|
582
|
+
"b1": (1, False),
|
|
583
|
+
"i1": (8, True),
|
|
584
|
+
"u1": (8, False),
|
|
585
|
+
"i2": (16, True),
|
|
586
|
+
"u2": (16, False),
|
|
587
|
+
"i4": (32, True),
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
def _array_result(self, value, array_meta):
|
|
591
|
+
"""
|
|
592
|
+
Convert a NumPy array (or nested int sequence) to integer_array_t data.
|
|
593
|
+
"""
|
|
594
|
+
original = value
|
|
595
|
+
value = self._integer_ndarray(value)
|
|
596
|
+
bit_width, is_signed = self._word_size(original, value, array_meta)
|
|
597
|
+
|
|
598
|
+
low, high = (-(1 << (bit_width - 1)), (1 << (bit_width - 1)) - 1) if is_signed else (0, (1 << bit_width) - 1)
|
|
599
|
+
if value.size > 0:
|
|
600
|
+
minimum, maximum = int(value.min()), int(value.max())
|
|
601
|
+
if minimum < low or maximum > high:
|
|
602
|
+
raise OverflowError(
|
|
603
|
+
f"The array values ({minimum} to {maximum}) do not fit in "
|
|
604
|
+
f"the integer_array_t word size ({bit_width} bit {'signed' if is_signed else 'unsigned'}, "
|
|
605
|
+
f"{low} to {high})"
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
# get(arr, x, y, z) is value[y, x, z]
|
|
609
|
+
height, width, depth = (value.shape + (1, 1))[:3] if value.ndim > 1 else (1, value.shape[0], 1)
|
|
610
|
+
|
|
611
|
+
data = numpy.ascontiguousarray(value, dtype=numpy.int32).tobytes()
|
|
612
|
+
return (0, 0.0, data, (value.size, width, height, depth, bit_width, int(is_signed)))
|
|
613
|
+
|
|
614
|
+
@staticmethod
|
|
615
|
+
def _integer_ndarray(value):
|
|
616
|
+
"""
|
|
617
|
+
The result as an integer NumPy array with 1 to 3 dimensions.
|
|
618
|
+
"""
|
|
619
|
+
if not isinstance(value, numpy.ndarray):
|
|
620
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, (list, tuple)):
|
|
621
|
+
raise _type_error(KIND_INTEGER_ARRAY, value, "a NumPy array of integers")
|
|
622
|
+
value = numpy.asarray(value)
|
|
623
|
+
|
|
624
|
+
if value.dtype.kind not in "biu":
|
|
625
|
+
raise TypeError(
|
|
626
|
+
f"Cannot convert a NumPy array of dtype {value.dtype} to a VHDL integer_array_t; "
|
|
627
|
+
"expected an integer or boolean dtype"
|
|
628
|
+
)
|
|
629
|
+
if value.ndim not in (1, 2, 3):
|
|
630
|
+
raise ValueError(
|
|
631
|
+
f"Cannot convert a {value.ndim}-dimensional NumPy array to a VHDL integer_array_t; "
|
|
632
|
+
"expected 1, 2 or 3 dimensions"
|
|
633
|
+
)
|
|
634
|
+
return value
|
|
635
|
+
|
|
636
|
+
def _word_size(self, original, value, array_meta):
|
|
637
|
+
"""
|
|
638
|
+
bit_width and is_signed of a returned array.
|
|
639
|
+
"""
|
|
640
|
+
meta = array_meta.get(id(original))
|
|
641
|
+
if meta is not None and meta[0] is original and meta[1] >= 1:
|
|
642
|
+
# The expression returned (possibly modified in place) one of the
|
|
643
|
+
# integer_array_t values VHDL staged: keep its bit width and signedness.
|
|
644
|
+
return meta[1], meta[2]
|
|
645
|
+
return self._DTYPE_WORD_SIZE.get(value.dtype.str[1:], (32, True))
|