vunit-python-bridge 0.1.0__py3-none-any.whl → 0.2.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 (28) hide show
  1. vunit_python_bridge/__init__.py +13 -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/bin/vunit_python_bridge-cp315-win_amd64.dll +0 -0
  8. vunit_python_bridge/bridge.py +25 -2
  9. vunit_python_bridge/file_base.py +128 -0
  10. vunit_python_bridge/hdl/src/python_ffi_pkg_bridge.vhd +11 -0
  11. vunit_python_bridge/hdl/src/python_pkg.vhd +224 -30
  12. vunit_python_bridge/hdl/src/python_pkg_vhpi.vhd +15 -1
  13. vunit_python_bridge/hdl/tools/generate_python_pkg.py +10 -0
  14. vunit_python_bridge/hdl/tools/python_pkg.vhd.in +180 -30
  15. vunit_python_bridge/native/config.c +42 -7
  16. vunit_python_bridge/native/vhpi/python_pkg.c +1 -1
  17. vunit_python_bridge/native/vhpi/python_pkg.h +1 -1
  18. vunit_python_bridge/native/vhpi/python_pkg_vhpi.c +1 -1
  19. vunit_python_bridge/native_library.py +89 -4
  20. vunit_python_bridge/runtime.py +145 -8
  21. vunit_python_bridge/simulator_hooks.py +28 -6
  22. vunit_python_bridge/vunit_pkg.toml +1 -3
  23. {vunit_python_bridge-0.1.0.dist-info → vunit_python_bridge-0.2.0.dist-info}/METADATA +16 -11
  24. vunit_python_bridge-0.2.0.dist-info/RECORD +36 -0
  25. {vunit_python_bridge-0.1.0.dist-info → vunit_python_bridge-0.2.0.dist-info}/WHEEL +1 -1
  26. vunit_python_bridge-0.1.0.dist-info/RECORD +0 -34
  27. {vunit_python_bridge-0.1.0.dist-info → vunit_python_bridge-0.2.0.dist-info}/licenses/LICENSE +0 -0
  28. {vunit_python_bridge-0.1.0.dist-info → vunit_python_bridge-0.2.0.dist-info}/top_level.txt +0 -0
@@ -25,10 +25,21 @@ Modules:
25
25
  * foreign_application: builds the VHPI application.
26
26
  * simulator_hooks: makes NVC and GHDL find the library.
27
27
  * runtime: runs inside the simulator's embedded interpreter.
28
+ * file_base: the base directory of ``base_path``, ``set_relative_file_base``.
29
+
30
+ ``set_relative_file_base(directory)``, called in the run script before ``add_package``, sets the
31
+ directory that ``base_path`` of ``python_pkg`` returns, for VHDL to join file names to. The
32
+ environment variable ``VUNIT_PYTHON_BRIDGE_FILE_BASE`` sets it too, and takes precedence.
28
33
  """
29
34
 
30
35
  from pathlib import Path
31
36
 
37
+ from .file_base import add_file_base_package
38
+
39
+ # Public: run scripts use them
40
+ from .file_base import FILE_BASE_VARIABLE as FILE_BASE_VARIABLE
41
+ from .file_base import set_relative_file_base as set_relative_file_base
42
+
32
43
  VHDL_PATH = Path(__file__).parent.resolve() / "hdl" / "src"
33
44
 
34
45
  # The foreign language interface implementing the package for a simulator. VUnit says which
@@ -73,6 +84,8 @@ def setup(context):
73
84
  f"it has no foreign language interface for {simulator_name}"
74
85
  )
75
86
 
87
+ add_file_base_package(context)
88
+
76
89
  if interface == "VHPI":
77
90
  # Riviera-PRO/Active-HDL, the simulators served by the VHPI application
78
91
  context.add_source_files(context.library.name, [VHDL_PATH / "python_pkg_vhpi.vhd"])
@@ -16,10 +16,12 @@ from pathlib import Path
16
16
  from typing import List, NamedTuple, Optional
17
17
 
18
18
  from .native_library import (
19
+ LIBRARY_DIR_VARIABLE,
19
20
  PACKAGE_PATH,
20
21
  PythonBridgeError,
21
22
  check_python_build,
22
23
  check_windows_64bit_simulator,
24
+ library_directory,
23
25
  prepare_library,
24
26
  windows_python_dll,
25
27
  )
@@ -51,6 +53,7 @@ class PythonBridge(NamedTuple):
51
53
  """
52
54
 
53
55
  library_file: Path
56
+ config_file: Path
54
57
  vhdl_files: List[Path]
55
58
 
56
59
 
@@ -88,9 +91,12 @@ def setup(
88
91
  )
89
92
 
90
93
  root = Path(output_path) / "python_bridge"
91
- library_file = prepare_library(root, Path(simulator_prefix) if is_fli else None)
94
+ library_file = _prepare_library(Path(simulator_prefix) if is_fli else None)
92
95
  run_script_dir = str(Path.cwd() if run_script_path is None else Path(run_script_path).resolve().parent)
93
- _write_if_changed(library_file.parent / CONFIG_FILE_NAME, _config_text(run_script_dir))
96
+ # The configuration belongs to the project, the library may be shared. The simulator
97
+ # hooks tell the library where the configuration is.
98
+ config_file = root / CONFIG_FILE_NAME
99
+ _write_if_changed(config_file, _config_text(run_script_dir))
94
100
 
95
101
  # The foreign attribute string of an entry point: the name of its wrapper in native/fli.c
96
102
  # and the library, by absolute path since Questa accepts it, for the FLI; the VHPIDIRECT
@@ -105,6 +111,7 @@ def setup(
105
111
 
106
112
  return PythonBridge(
107
113
  library_file,
114
+ config_file,
108
115
  [
109
116
  bridge_package,
110
117
  VHDL_SOURCE_PATH / "python_ffi_pkg_bridge.vhd",
@@ -112,6 +119,22 @@ def setup(
112
119
  )
113
120
 
114
121
 
122
+ def _prepare_library(fli_prefix: Optional[Path]) -> Path:
123
+ """
124
+ Prepare the library in the library directory, failing with a pointer to the variable
125
+ selecting another one when it cannot be written, e.g. a read-only home directory.
126
+ """
127
+ directory = library_directory()
128
+ try:
129
+ return prepare_library(directory, fli_prefix)
130
+ except OSError as exc:
131
+ source = f"set by {LIBRARY_DIR_VARIABLE}" if os.environ.get(LIBRARY_DIR_VARIABLE) else "the default"
132
+ raise PythonBridgeError(
133
+ f"Cannot build the Python bridge library in {directory!s} ({source}): {exc}. "
134
+ f"Set {LIBRARY_DIR_VARIABLE} to a writable directory."
135
+ ) from exc
136
+
137
+
115
138
  def _vhpidirect_token(simulator_name, simulator_backend: Optional[str], library_file: Path) -> str:
116
139
  """
117
140
  The library token of the VHPIDIRECT attributes: the file name the simulator dlopen()s,
@@ -0,0 +1,128 @@
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
+ The base directory that ``base_path`` of ``python_pkg`` returns: set by ``set_relative_file_base``
9
+ in the run script or by the environment variable ``VUNIT_PYTHON_BRIDGE_FILE_BASE``, and given to
10
+ VHDL in a generated package.
11
+ """
12
+
13
+ import logging
14
+ import os
15
+ from pathlib import Path
16
+
17
+ from .bridge import _write_if_changed
18
+
19
+ # Name of the generated VHDL package holding the base directory, used by python_pkg (base_path)
20
+ FILE_BASE_PACKAGE = "python_file_base_pkg"
21
+
22
+ # The environment variable setting the directory of base_path when the run script cannot. It takes
23
+ # precedence over set_relative_file_base so that whoever runs a shared run script can override it.
24
+ FILE_BASE_VARIABLE = "VUNIT_PYTHON_BRIDGE_FILE_BASE"
25
+
26
+ LOGGER = logging.getLogger(__name__)
27
+
28
+ # The directory set by set_relative_file_base, None when unset, and whether setup has run, after
29
+ # which the directory is in the generated package and can no longer be changed
30
+ _relative_file_base = None
31
+ _setup_done = False
32
+
33
+
34
+ def set_relative_file_base(directory):
35
+ """
36
+ Set the directory that ``base_path`` of ``python_pkg`` returns, for VHDL to join file names to.
37
+
38
+ Call it in the run script before ``vu.add_package("vunit-python-bridge", ...)``. A relative
39
+ directory is taken from the current working directory at the time of the call.
40
+
41
+ :param directory: An existing directory, a ``str`` or a ``Path``.
42
+ """
43
+ global _relative_file_base # pylint: disable=global-statement
44
+
45
+ if _setup_done:
46
+ raise RuntimeError(
47
+ 'set_relative_file_base() must be called before vu.add_package("vunit-python-bridge", ...), '
48
+ "the package has already been set up"
49
+ )
50
+ path = Path(directory).resolve()
51
+ if not path.is_dir():
52
+ raise ValueError(f"The relative file base {str(directory)!r} is not an existing directory")
53
+ _relative_file_base = path
54
+
55
+
56
+ def _vhdl_string_literal(text):
57
+ """
58
+ A VHDL expression of type string for the UTF-8 bytes of a text, in ASCII: printable characters
59
+ in a literal with its quotes doubled, other bytes as character'val(n).
60
+ """
61
+ parts = []
62
+ literal = ""
63
+ for byte in text.encode("utf-8"):
64
+ if 0x20 <= byte <= 0x7E:
65
+ literal += '""' if byte == ord('"') else chr(byte)
66
+ else:
67
+ if literal:
68
+ parts.append(f'"{literal}"')
69
+ literal = ""
70
+ parts.append(f"character'val({byte})")
71
+ if literal or not parts:
72
+ parts.append(f'"{literal}"')
73
+ if not parts[0].startswith('"'):
74
+ # A string expression rather than a lone character
75
+ parts.insert(0, '""')
76
+ return " & ".join(parts)
77
+
78
+
79
+ def _file_base_package_text(directory):
80
+ """
81
+ Content of the generated python_file_base_pkg.vhd.
82
+ """
83
+ value = "" if directory is None else directory.as_posix()
84
+ return f"""\
85
+ -- Generated by vunit_python_bridge.setup. Do not edit.
86
+ --
87
+ -- The directory set by the environment variable {FILE_BASE_VARIABLE} or by
88
+ -- vunit_python_bridge.set_relative_file_base() in the run script, empty when neither is set.
89
+ -- base_path of python_pkg returns it.
90
+ package {FILE_BASE_PACKAGE} is
91
+ constant p_relative_file_base : string := {_vhdl_string_literal(value)};
92
+ end package;
93
+ """
94
+
95
+
96
+ def _file_base():
97
+ """
98
+ The directory of base_path: that of the environment variable if it is set and not empty,
99
+ otherwise that of set_relative_file_base, None when neither is set.
100
+ """
101
+ value = os.environ.get(FILE_BASE_VARIABLE, "")
102
+ if value == "":
103
+ return _relative_file_base
104
+
105
+ directory = Path(value).resolve()
106
+ if not directory.is_dir():
107
+ raise ValueError(f"The environment variable {FILE_BASE_VARIABLE}={value!r} is not an existing directory")
108
+ if _relative_file_base is not None and _relative_file_base != directory:
109
+ LOGGER.warning(
110
+ "The environment variable %s=%s takes precedence over set_relative_file_base(%r)",
111
+ FILE_BASE_VARIABLE,
112
+ value,
113
+ str(_relative_file_base),
114
+ )
115
+ return directory
116
+
117
+
118
+ def add_file_base_package(context):
119
+ """
120
+ Generate python_file_base_pkg and add it to the library of the package.
121
+ """
122
+ global _setup_done # pylint: disable=global-statement
123
+
124
+ path = Path(context.output_path) / "python_bridge" / "vhdl" / f"{FILE_BASE_PACKAGE}.vhd"
125
+ _write_if_changed(path, _file_base_package_text(_file_base()))
126
+ context.add_source_files(context.library.name, [path])
127
+ _setup_done = True
128
+
@@ -26,6 +26,8 @@ use vunit_lib.id_pkg.all;
26
26
  use vunit_lib.integer_array_pkg.all;
27
27
  use vunit_lib.integer_vector_ptr_pkg.all;
28
28
  use vunit_lib.logger_pkg.all;
29
+ use vunit_lib.run_pkg.get_phase;
30
+ use vunit_lib.run_types_pkg.all;
29
31
  use work.python_bridge_pkg.all;
30
32
 
31
33
  package python_ffi_pkg is
@@ -121,6 +123,7 @@ package python_ffi_pkg is
121
123
  constant p_kind_integer_array : integer := 8;
122
124
  constant p_kind_integer_vector : integer := 9;
123
125
  constant p_kind_real_vector : integer := 10;
126
+ constant p_kind_dict : integer := 11;
124
127
 
125
128
  -- Names of the operations, used in the error messages
126
129
  impure function p_exec_operation(session : python_session_t := default_session) return string;
@@ -412,6 +415,14 @@ package body python_ffi_pkg is
412
415
  variable offset, n : natural := 0;
413
416
  variable staged_id : integer;
414
417
  begin
418
+ if get_phase < test_runner_setup then
419
+ failure(
420
+ python_logger,
421
+ operation & " failed:" & LF &
422
+ "An integer_array_t argument cannot be made before test_runner_setup."
423
+ );
424
+ return -1;
425
+ end if;
415
426
  if not p_succeeded(
416
427
  vpy_push_array(len, width(arr), height(arr), depth(arr), bit_width(arr), boolean'pos(is_signed(arr))),
417
428
  operation
@@ -7,12 +7,16 @@
7
7
  -- Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com
8
8
 
9
9
  use work.python_ffi_pkg.all;
10
+ use work.python_file_base_pkg.all;
10
11
  library vunit_lib;
11
12
  use vunit_lib.path.all;
12
13
  use vunit_lib.run_pkg.all;
13
14
  use vunit_lib.runner_pkg.all;
14
15
  use vunit_lib.integer_vector_ptr_pkg.all;
15
16
  use vunit_lib.string_ops.all;
17
+ use vunit_lib.dict_pkg.all;
18
+ use vunit_lib.dict_2008p_pkg.all;
19
+ use vunit_lib.data_types_private_pkg.all;
16
20
 
17
21
  use std.textio.all;
18
22
 
@@ -28,6 +32,9 @@ use vunit_lib.logger_pkg.all;
28
32
  ------------------------------------------------------------------------------
29
33
 
30
34
  package python_pkg is
35
+ -- Import a Python file as a module. A relative path is relative to the
36
+ -- directory of the testbench file, tb_path, so test_runner_setup must have
37
+ -- been called. An absolute path is used as it is given.
31
38
  procedure import_module_from_file(
32
39
  module_path, as_module_name : string; session : python_session_t := default_session
33
40
  );
@@ -78,6 +85,12 @@ package python_pkg is
78
85
  --
79
86
  -- H and L are read as 1 and 0. Any other metavalue is an error.
80
87
  --
88
+ -- A dict_t value becomes a Python dict, {"key": value, ...}, whose values
89
+ -- are written the same way: integer, real, string, boolean, std_ulogic,
90
+ -- integer_vector, real_vector, integer_vector_ptr_t, integer_array_t and
91
+ -- dict_t values. Any other value type is an error. An integer_array_t value
92
+ -- is transferred as described below.
93
+ --
81
94
  -- An integer_array_t value is transferred to Python by the Python bridge,
82
95
  -- which is only available for NVC, GHDL and Questa, and is referred to by
83
96
  -- the expression, which means that it can be used in several calls.
@@ -93,6 +106,8 @@ package python_pkg is
93
106
  impure function kwarg_signed(kw : string; value : signed) return arg_t;
94
107
  impure function arg(value : integer_array_t) return arg_t;
95
108
  impure function kwarg(kw : string; value : integer_array_t) return arg_t;
109
+ impure function arg(value : dict_t) return arg_t;
110
+ impure function kwarg(kw : string; value : dict_t) return arg_t;
96
111
 
97
112
  -- The Python expression calling identifier with the given arguments, for
98
113
  -- example to embed a call in a larger exec or eval string:
@@ -164,6 +179,11 @@ package python_pkg is
164
179
  -----------------------------------------------------------------------------
165
180
  -- Results of eval: boolean, std_ulogic, vectors and arrays
166
181
  -----------------------------------------------------------------------------
182
+ -- The result of eval_dict and call_dict is a new dict_t that the caller owns
183
+ -- and deallocates. Its values are integer, real, string, boolean,
184
+ -- integer_vector_ptr_t (a list of int), integer_array_t (a NumPy array, with
185
+ -- the word size of an eval_integer_array result) and dict_t (a nested dict).
186
+ --
167
187
  -- std_ulogic_vector and integer_array_t results are only available under
168
188
  -- their explicit names, not as eval overloads, which keeps
169
189
  -- check_equal(eval("17"), 17) and length(eval("[1, 2]")) unambiguous.
@@ -187,6 +207,11 @@ package python_pkg is
187
207
  expr : string; session : python_session_t := default_session
188
208
  ) return integer_array_t;
189
209
 
210
+ impure function eval_dict(
211
+ expr : string; session : python_session_t := default_session
212
+ ) return dict_t;
213
+ alias eval is eval_dict[string, python_session_t return dict_t];
214
+
190
215
  procedure eval_std_ulogic_vector(
191
216
  expr : string; result : out std_ulogic_vector; session : python_session_t := default_session
192
217
  );
@@ -247,6 +272,13 @@ package python_pkg is
247
272
  alias call is call_integer_array[
248
273
  string, arg_t, arg_t, arg_t, arg_t, arg_t, arg_t, arg_t, arg_t, arg_t, arg_t, python_session_t return integer_array_t];
249
274
 
275
+ impure function call_dict(
276
+ identifier : string; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 : arg_t := null_arg;
277
+ session : python_session_t := default_session
278
+ ) return dict_t;
279
+ alias call is call_dict[
280
+ string, arg_t, arg_t, arg_t, arg_t, arg_t, arg_t, arg_t, arg_t, arg_t, arg_t, python_session_t return dict_t];
281
+
250
282
  procedure call_std_ulogic_vector(
251
283
  identifier : string; result : out std_ulogic_vector;
252
284
  arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 : arg_t := null_arg;
@@ -271,24 +303,36 @@ package python_pkg is
271
303
  -- is given. The file is executed with __file__ set and its own directory on
272
304
  -- sys.path so that it can import its siblings.
273
305
  procedure exec_file(file_name : string; session : python_session_t := default_session);
306
+
307
+ -- The absolute base directory, set by vunit_python_bridge.set_relative_file_base()
308
+ -- or VUNIT_PYTHON_BRIDGE_FILE_BASE. Without one, a failure is reported on
309
+ -- python_logger and "" is returned.
310
+ impure function base_path return string;
274
311
  end package;
275
312
 
276
313
  package body python_pkg is
314
+ impure function p_file_path(file_name, operation : string; session : python_session_t) return string;
315
+
277
316
  -- @formatter:off
278
317
  procedure import_module_from_file(
279
318
  module_path, as_module_name : string; session : python_session_t := default_session
280
319
  ) is
281
320
  constant spec_name : string := "__" & as_module_name & "_spec";
321
+ constant path : string := p_file_path(
322
+ module_path, "import_module_from_file(""" & module_path & """, """ & as_module_name & """)", session
323
+ );
282
324
  constant code : string :=
283
325
  "from importlib.util import spec_from_file_location, module_from_spec" & LF &
284
326
  "from pathlib import Path" & LF &
285
327
  "import sys" & LF &
286
- spec_name & " = spec_from_file_location('" & as_module_name & "', str(Path('" & module_path & "')))" & LF &
328
+ spec_name & " = spec_from_file_location('" & as_module_name & "', str(Path('" & path & "')))" & LF &
287
329
  as_module_name & " = module_from_spec(" & spec_name & ")" & LF &
288
330
  "sys.modules['" & as_module_name & "'] = " & as_module_name & LF &
289
331
  spec_name & ".loader.exec_module(" & as_module_name & ")";
290
332
  begin
291
- exec(code, session);
333
+ if path /= "" then
334
+ exec(code, session);
335
+ end if;
292
336
  end;
293
337
  -- @formatter:on
294
338
 
@@ -408,6 +452,31 @@ package body python_pkg is
408
452
  return result;
409
453
  end;
410
454
 
455
+ -- The Python source text of a message, escaped for a double quoted Python
456
+ -- string. Quote, backslash and control characters are escaped; bytes >= 128
457
+ -- are kept so that Python decodes UTF-8 text.
458
+ function p_quoted(message : string) return string is
459
+ variable result : line;
460
+ begin
461
+ swrite(result, """");
462
+ for idx in message'range loop
463
+ case message(idx) is
464
+ when '"' | '\' => swrite(result, "\" & message(idx));
465
+ when LF => swrite(result, "\n");
466
+ when CR => swrite(result, "\r");
467
+ when others =>
468
+ if character'pos(message(idx)) < 32 or message(idx) = DEL then
469
+ swrite(result, "\x" & to_hstring(to_unsigned(character'pos(message(idx)), 8)));
470
+ else
471
+ swrite(result, string'(1 => message(idx)));
472
+ end if;
473
+ end case;
474
+ end loop;
475
+ swrite(result, """");
476
+
477
+ return result.all;
478
+ end;
479
+
411
480
  function arg(value : integer) return arg_t is
412
481
  begin
413
482
  return (p_positional_arg, to_string(value));
@@ -420,12 +489,12 @@ package body python_pkg is
420
489
 
421
490
  function arg(value : string) return arg_t is
422
491
  begin
423
- return (p_positional_arg, '"' & value & '"');
492
+ return (p_positional_arg, p_quoted(value));
424
493
  end;
425
494
 
426
495
  function kwarg(kw : string; value : string) return arg_t is
427
496
  begin
428
- return (kw, '"' & value & '"');
497
+ return (kw, p_quoted(value));
429
498
  end;
430
499
 
431
500
  function arg(value : integer_vector) return arg_t is
@@ -466,26 +535,6 @@ package body python_pkg is
466
535
  end if;
467
536
  end;
468
537
 
469
- -- The Python source text of a message, escaped for a double quoted Python
470
- -- string. A quote, a backslash and a line break are the characters that
471
- -- cannot be written as they are.
472
- function p_quoted(message : string) return string is
473
- variable result : line;
474
- begin
475
- swrite(result, """");
476
- for idx in message'range loop
477
- case message(idx) is
478
- when '"' | '\' => swrite(result, "\" & message(idx));
479
- when LF => swrite(result, "\n");
480
- when CR => swrite(result, "\r");
481
- when others => swrite(result, string'(1 => message(idx)));
482
- end case;
483
- end loop;
484
- swrite(result, """");
485
-
486
- return result.all;
487
- end;
488
-
489
538
  -- Report a value that could not be converted and give it a Python source
490
539
  -- text raising the same message, so that the call the argument is used in
491
540
  -- fails the same way rather than being made without the argument.
@@ -601,6 +650,45 @@ package body python_pkg is
601
650
  return "__vunit__.staged(" & integer'image(staged_id) & ")";
602
651
  end;
603
652
 
653
+ impure function p_arg_value(value : dict_t; operation : string) return string;
654
+
655
+ -- The Python source text of the value stored for a key of a dict
656
+ impure function p_dict_item(value : dict_t; key, operation : string) return string is
657
+ begin
658
+ case get_value_type(value, key) is
659
+ when vhdl_integer => return to_string(get_integer(value, key));
660
+ when vhdl_real => return to_string(get_real(value, key), "%.16e");
661
+ when vhdl_string => return p_quoted(get_string(value, key));
662
+ when vhdl_boolean => return arg(get_boolean(value, key)).value;
663
+ when ieee_std_ulogic => return p_arg_value(get_std_ulogic(value, key), operation);
664
+ when vhdl_integer_vector => return to_py_list_str(get_integer_vector(value, key));
665
+ when vhdl_real_vector => return to_py_list_str(get_real_vector(value, key));
666
+ when vunit_integer_vector_ptr_t => return p_arg_value(get_integer_vector_ptr_t_ref(value, key));
667
+ when vunit_integer_array_t => return p_arg_value(get_integer_array_t_ref(value, key), operation);
668
+ when vunit_dict_t => return p_arg_value(get_dict_t_ref(value, key), operation);
669
+ when others =>
670
+ return p_failed_value(
671
+ operation & " cannot convert the " & to_string(get_value_type(value, key)) &
672
+ " value of the dict key """ & key & """"
673
+ );
674
+ end case;
675
+ end;
676
+
677
+ impure function p_arg_value(value : dict_t; operation : string) return string is
678
+ variable result : line;
679
+ begin
680
+ swrite(result, "{");
681
+ for idx in 0 to num_keys(value) - 1 loop
682
+ if idx > 0 then
683
+ swrite(result, ", ");
684
+ end if;
685
+ swrite(result, p_quoted(get_key(value, idx)) & ": " & p_dict_item(value, get_key(value, idx), operation));
686
+ end loop;
687
+ swrite(result, "}");
688
+
689
+ return result.all;
690
+ end;
691
+
604
692
  function arg(value : real_vector) return arg_t is
605
693
  begin
606
694
  return (p_positional_arg, p_arg_value(value));
@@ -661,6 +749,16 @@ package body python_pkg is
661
749
  return (kw, p_arg_value(value, "kwarg"));
662
750
  end;
663
751
 
752
+ impure function arg(value : dict_t) return arg_t is
753
+ begin
754
+ return (p_positional_arg, p_arg_value(value, "arg"));
755
+ end;
756
+
757
+ impure function kwarg(kw : string; value : dict_t) return arg_t is
758
+ begin
759
+ return (kw, p_arg_value(value, "kwarg"));
760
+ end;
761
+
664
762
  -----------------------------------------------------------------------------
665
763
  -- Argument groups
666
764
  -----------------------------------------------------------------------------
@@ -875,6 +973,51 @@ package body python_pkg is
875
973
  return result;
876
974
  end;
877
975
 
976
+ -- The dict_t of a dict result. Python keeps the converted entries of the dict
977
+ -- under the handle, see dict_kinds, dict_key and dict_item of runtime.py, and
978
+ -- VHDL fetches them one at a time with the eval of the type of each value.
979
+ impure function p_to_dict(handle : integer; session : python_session_t) return dict_t;
980
+
981
+ procedure p_add_dict_item(dict : dict_t; handle, index, kind : integer; session : python_session_t) is
982
+ constant entry : string := "(" & integer'image(handle) & ", " & integer'image(index) & ")";
983
+ constant key : string := eval_string("__vunit__.dict_key" & entry, session);
984
+ constant item : string := "__vunit__.dict_item" & entry;
985
+ variable ptr : integer_vector_ptr_t;
986
+ variable arr : integer_array_t;
987
+ variable nested : dict_t;
988
+ begin
989
+ case kind is
990
+ when p_kind_integer => set_integer(dict, key, eval_integer(item, session));
991
+ when p_kind_real => set_real(dict, key, eval_real(item, session));
992
+ when p_kind_boolean => set_boolean(dict, key, eval_boolean(item, session));
993
+ when p_kind_string => set_string(dict, key, eval_string(item, session));
994
+ when p_kind_integer_vector =>
995
+ ptr := eval_integer_vector_ptr(item, session);
996
+ set_integer_vector_ptr_t_ref(dict, key, ptr);
997
+ when p_kind_integer_array =>
998
+ arr := eval_integer_array(item, session);
999
+ set_integer_array_t_ref(dict, key, arr);
1000
+ when others =>
1001
+ -- A nested dict, whose result is its handle
1002
+ if p_eval(item, p_kind_dict, -1, p_eval_operation(item, session), session) then
1003
+ nested := p_to_dict(p_result_integer, session);
1004
+ set_dict_t_ref(dict, key, nested);
1005
+ end if;
1006
+ end case;
1007
+ end;
1008
+
1009
+ impure function p_to_dict(handle : integer; session : python_session_t) return dict_t is
1010
+ constant kinds : integer_vector := eval_integer_vector(
1011
+ "__vunit__.dict_kinds(" & integer'image(handle) & ")", session
1012
+ );
1013
+ constant result : dict_t := new_dict;
1014
+ begin
1015
+ for idx in kinds'range loop
1016
+ p_add_dict_item(result, handle, idx - kinds'low, kinds(idx), session);
1017
+ end loop;
1018
+ return result;
1019
+ end;
1020
+
878
1021
  -----------------------------------------------------------------------------
879
1022
  -- exec
880
1023
  -----------------------------------------------------------------------------
@@ -893,20 +1036,51 @@ package body python_pkg is
893
1036
  return false;
894
1037
  end;
895
1038
 
896
- -- The file name of exec_file, a relative one taken from the directory of the
897
- -- testbench file like the file names of the other VUnit subprograms
898
- impure function p_file_path(file_name : string) return string is
1039
+ -- The file name of exec_file and import_module_from_file, a relative one taken
1040
+ -- from the directory of the testbench file like the file names of the other
1041
+ -- VUnit subprograms
1042
+ impure function p_file_path(file_name, operation : string; session : python_session_t) return string is
1043
+ constant tb_dir : string := tb_path(get_cfg(runner_state));
899
1044
  begin
900
- if p_is_absolute(file_name) then
1045
+ if file_name = "" then
1046
+ failure(get_logger(get_id(session)), operation & " failed:" & LF & "Empty file name.");
1047
+ return "";
1048
+ elsif p_is_absolute(file_name) then
901
1049
  return file_name;
1050
+ elsif tb_dir = "" then
1051
+ -- Without a testbench path the file would silently be taken from the working directory
1052
+ failure(
1053
+ get_logger(get_id(session)),
1054
+ operation & " failed:" & LF &
1055
+ "A relative file name needs the testbench path set by test_runner_setup. " &
1056
+ "Call it after test_runner_setup, give an absolute path, " &
1057
+ "or join it to base_path with a base directory set in the run script."
1058
+ );
1059
+ return "";
902
1060
  end if;
903
- return join(tb_path(get_cfg(runner_state)), file_name);
1061
+ return join(tb_dir, file_name);
1062
+ end;
1063
+
1064
+ impure function base_path return string is
1065
+ begin
1066
+ if p_relative_file_base = "" then
1067
+ failure(
1068
+ python_logger,
1069
+ "base_path failed: no base directory is set. " &
1070
+ "Call vunit_python_bridge.set_relative_file_base() in the run script before add_package, " &
1071
+ "or set the environment variable VUNIT_PYTHON_BRIDGE_FILE_BASE."
1072
+ );
1073
+ end if;
1074
+ return p_relative_file_base;
904
1075
  end;
905
1076
 
906
1077
  procedure exec_file(file_name : string; session : python_session_t := default_session) is
1078
+ constant path : string := p_file_path(file_name, p_exec_file_operation(file_name, session), session);
907
1079
  variable ok : boolean;
908
1080
  begin
909
- ok := p_exec_file(p_file_path(file_name), session);
1081
+ if path /= "" then
1082
+ ok := p_exec_file(path, session);
1083
+ end if;
910
1084
  end;
911
1085
 
912
1086
  -----------------------------------------------------------------------------
@@ -952,6 +1126,16 @@ package body python_pkg is
952
1126
  return null_integer_array;
953
1127
  end;
954
1128
 
1129
+ impure function eval_dict(
1130
+ expr : string; session : python_session_t := default_session
1131
+ ) return dict_t is
1132
+ begin
1133
+ if p_eval(expr, p_kind_dict, -1, p_eval_operation(expr, session), session) then
1134
+ return p_to_dict(p_result_integer, session);
1135
+ end if;
1136
+ return new_dict;
1137
+ end;
1138
+
955
1139
  procedure eval_std_ulogic_vector(
956
1140
  expr : string; result : out std_ulogic_vector; session : python_session_t := default_session
957
1141
  ) is
@@ -1052,6 +1236,16 @@ package body python_pkg is
1052
1236
  );
1053
1237
  end;
1054
1238
 
1239
+ impure function call_dict(
1240
+ identifier : string; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 : arg_t := null_arg;
1241
+ session : python_session_t := default_session
1242
+ ) return dict_t is
1243
+ begin
1244
+ return eval_dict(
1245
+ to_call_str(identifier, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10), session
1246
+ );
1247
+ end;
1248
+
1055
1249
  procedure call_std_ulogic_vector(
1056
1250
  identifier : string; result : out std_ulogic_vector;
1057
1251
  arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 : arg_t := null_arg;