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.
Files changed (34) hide show
  1. vunit_python_bridge/__init__.py +92 -0
  2. vunit_python_bridge/bin/vunit_python_bridge-cp310-win_amd64.dll +0 -0
  3. vunit_python_bridge/bin/vunit_python_bridge-cp311-win_amd64.dll +0 -0
  4. vunit_python_bridge/bin/vunit_python_bridge-cp312-win_amd64.dll +0 -0
  5. vunit_python_bridge/bin/vunit_python_bridge-cp313-win_amd64.dll +0 -0
  6. vunit_python_bridge/bin/vunit_python_bridge-cp314-win_amd64.dll +0 -0
  7. vunit_python_bridge/bridge.py +171 -0
  8. vunit_python_bridge/foreign_application.py +90 -0
  9. vunit_python_bridge/hdl/src/python_bridge_pkg.vhd.in +91 -0
  10. vunit_python_bridge/hdl/src/python_context.vhd +13 -0
  11. vunit_python_bridge/hdl/src/python_ffi_pkg_bridge.vhd +507 -0
  12. vunit_python_bridge/hdl/src/python_pkg.vhd +1087 -0
  13. vunit_python_bridge/hdl/src/python_pkg_vhpi.vhd +270 -0
  14. vunit_python_bridge/hdl/tools/generate_python_pkg.py +386 -0
  15. vunit_python_bridge/hdl/tools/python_pkg.vhd.in +765 -0
  16. vunit_python_bridge/native/arguments.c +191 -0
  17. vunit_python_bridge/native/bridge.h +117 -0
  18. vunit_python_bridge/native/config.c +201 -0
  19. vunit_python_bridge/native/error.c +120 -0
  20. vunit_python_bridge/native/fli.c +98 -0
  21. vunit_python_bridge/native/interpreter.c +271 -0
  22. vunit_python_bridge/native/operations.c +239 -0
  23. vunit_python_bridge/native/vhpi/python_pkg.c +125 -0
  24. vunit_python_bridge/native/vhpi/python_pkg.h +31 -0
  25. vunit_python_bridge/native/vhpi/python_pkg_vhpi.c +366 -0
  26. vunit_python_bridge/native_library.py +455 -0
  27. vunit_python_bridge/runtime.py +645 -0
  28. vunit_python_bridge/simulator_hooks.py +141 -0
  29. vunit_python_bridge/vunit_pkg.toml +17 -0
  30. vunit_python_bridge-0.1.0.dist-info/METADATA +116 -0
  31. vunit_python_bridge-0.1.0.dist-info/RECORD +34 -0
  32. vunit_python_bridge-0.1.0.dist-info/WHEEL +5 -0
  33. vunit_python_bridge-0.1.0.dist-info/licenses/LICENSE +373 -0
  34. 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))