Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ the selected simulator by a foreign language interface the package builds itself
called through VHPIDIRECT (NVC, GHDL) or the FLI (Questa/ModelSim), or a VHPI application built with
the simulator's own compiler driver (Riviera-PRO/Active-HDL). Values cross the interface with their
VHDL types: `integer`, `real`, `string`, `boolean`, `std_ulogic`, `unsigned`/`signed`, the vector
types, and `integer_array_t` as a NumPy array.
types, `integer_array_t` as a NumPy array, and VUnit's `dict_t` as a Python `dict`.

## Installation

Expand Down Expand Up @@ -78,7 +78,7 @@ check(call_integer_vector("gain", arg(integer_vector'(1, 2))) = integer_vector'(

## Requirements

- VUnit 5.0.0.dev12 or later (`vunit_hdl` on PyPI), which pip installs with the package.
- VUnit 5.0.0.dev15 or later (`vunit_hdl` on PyPI), which pip installs with the package.
- VHDL-2008 or later.
- CPython 3.10 or later, standard (GIL) build, with a shared `libpython` (`--enable-shared`), which
is what distribution Pythons, `actions/setup-python`, `uv`, `pyenv` and conda-forge provide by default.
Expand Down
4 changes: 2 additions & 2 deletions docs/other_simulators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ simulator installation change, so a run script needs nothing beyond

This application differs from the Python bridge in a few ways: only the
default session exists, the operations implemented by the bridge
(``integer_array_t`` values, the ``boolean``, ``std_ulogic``, vector and
``integer_array_t`` results, ``exec_file``) report that they require NVC, GHDL
(``integer_array_t`` values, the ``boolean``, ``std_ulogic``, vector,
``integer_array_t`` and ``dict_t`` results, ``exec_file``) report that they require NVC, GHDL
or Questa, a Python error stops the simulation with the message printed by the
application rather than through the logger of the session, and ``real`` values
outside the single precision float range are rejected.
Expand Down
4 changes: 3 additions & 1 deletion docs/running_code.rst
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ bridge, ``eval`` has more result types:
* ``eval_std_ulogic_vector``, returning an unconstrained ``std_ulogic_vector``.
* ``eval_integer_array``, returning ``integer_array_t``, see
:ref:`python_bridge:integer_array`.
* ``eval_dict``, returning ``dict_t``, see :ref:`python_bridge:dict`.

``eval_boolean`` and ``eval_std_ulogic`` are aliased ``eval`` like the other
result types. ``eval_std_ulogic_vector`` and ``eval_integer_array`` are not,
Expand Down Expand Up @@ -164,6 +165,7 @@ VHDL Python
``unsigned`` ``int``, passed as ``arg_unsigned``/``kwarg_unsigned``
``signed`` ``int``, passed as ``arg_signed``/``kwarg_signed``
``integer_array_t`` NumPy array, not on Riviera-PRO/Active-HDL
``dict_t`` ``dict`` with ``str`` keys, see :ref:`python_bridge:dict`
========================== ========================================================

An ``unsigned`` or ``signed`` value of any width becomes an exact Python
Expand Down Expand Up @@ -201,7 +203,7 @@ passed as a number or as the string of its characters:

On NVC, GHDL and Questa, ``call`` returns the same types as ``eval``:
``call_boolean``, ``call_std_ulogic``, ``call_std_ulogic_vector``,
``call_integer_array``, ``call_string``, ``call_real_vector`` and
``call_integer_array``, ``call_dict``, ``call_string``, ``call_real_vector`` and
``call_integer_vector_ptr``, plus the procedures ``call_std_ulogic_vector``,
``call_signed`` and ``call_unsigned`` taking the result as an ``out``
parameter. All of the functions but ``call_std_ulogic_vector`` are aliased
Expand Down
43 changes: 43 additions & 0 deletions docs/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Type mapping
* ``signed``/``unsigned`` ↔ ``int`` (procedure results only, Python bridge).
* ``integer_array_t`` ↔ ``numpy.ndarray`` (Python bridge), see
:ref:`python_bridge:integer_array`.
* ``dict_t`` ↔ ``dict``, see
:ref:`python_bridge:dict`.

The types marked as needing the Python bridge are available on NVC, GHDL and
Questa, but not on Riviera-PRO/Active-HDL.
Expand All @@ -24,6 +26,47 @@ Results are strict: a value that does not fit the VHDL type, or is of the
wrong Python type, is an error. Values are never silently truncated or
wrapped.

.. _python_bridge:dict:

dict_t and dict
---------------

A ``dict_t`` from VUnit's ``dict_pkg`` (string keys, values of mixed types) is
a Python ``dict``. As an argument, ``arg(dict)`` and ``kwarg("name", dict)``
write the ``dict`` as the Python literal ``{"key": value, ...}``. The values
are ``integer``, ``real``, ``string``, ``boolean``, ``std_ulogic``,
``integer_vector``, ``real_vector``,
``integer_vector_ptr_t``, ``integer_array_t`` and nested ``dict_t``. Any
other value type is reported like any other argument that cannot be
converted. An ``integer_array_t`` value becomes a NumPy array as described in
:ref:`python_bridge:integer_array`. The keys are
visited in the order of ``get_key``, which VUnit does not specify.

.. code-block:: vhdl

constant cfg : dict_t := new_dict;
set_integer(cfg, "taps", 8);
set_real(cfg, "gain", 0.5);
call("model.configure", arg(cfg));

``eval_dict`` and ``call_dict`` (aliased ``eval`` and ``call``) convert a
Python ``dict`` with ``str`` keys to a new ``dict_t`` that the caller owns and
deallocates. The values are converted strictly: ``int`` to ``integer``,
``float`` to ``real``, ``str`` to ``string``, ``bool`` to ``boolean``, a
``list`` of ``int`` to ``integer_vector_ptr_t``, a NumPy array of integers to
``integer_array_t`` and a ``dict`` to a nested ``dict_t``, the last three
stored with ``set_integer_vector_ptr_t_ref``, ``set_integer_array_t_ref`` and
``set_dict_t_ref``. The bit width and signedness of an ``integer_array_t``
follow the dtype of the array like for ``eval_integer_array``, see
:ref:`python_bridge:integer_array`. ``None``, an ``int`` outside the VHDL integer range, a key
that is not a ``str`` or any other value is an error, and ``eval_dict`` then
returns an empty ``dict_t`` after reporting it.

.. code-block:: vhdl

constant result : dict_t := eval("{'name': 'fir', 'taps': [1, 2, 1], 'gain': 0.5}");
check_equal(get_string(result, "name"), "fir");

.. _python_bridge:integer_array:

integer_array_t and NumPy
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ classifiers = [
dependencies = [
"numpy",
# The first VUnit with packages and simulator hooks, VUnit/vunit#1221
"vunit_hdl>=5.0.0.dev12",
"vunit_hdl>=5.0.0.dev15",
]

[tool.setuptools]
Expand Down
1 change: 1 addition & 0 deletions release_notes/news.d/11.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VUnit's `dict_t` is supported in both directions: `eval_dict` and `call_dict`, aliased `eval` and `call`, return a Python dict as a new `dict_t`, and `arg(dict_t)` and `kwarg(kw, dict_t)` pass a `dict_t` to Python as a dict. Values are integers, reals, strings, booleans, integer vectors, `integer_array_t` (NumPy arrays) and nested dicts. It needs `get_key` and `get_value_type` of `dict_pkg`, so VUnit 5.0.0.dev15 or later is required.
1 change: 1 addition & 0 deletions src/vunit_python_bridge/hdl/src/python_ffi_pkg_bridge.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ package python_ffi_pkg is
constant p_kind_integer_array : integer := 8;
constant p_kind_integer_vector : integer := 9;
constant p_kind_real_vector : integer := 10;
constant p_kind_dict : integer := 11;

-- Names of the operations, used in the error messages
impure function p_exec_operation(session : python_session_t := default_session) return string;
Expand Down
142 changes: 142 additions & 0 deletions src/vunit_python_bridge/hdl/src/python_pkg.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ use vunit_lib.run_pkg.all;
use vunit_lib.runner_pkg.all;
use vunit_lib.integer_vector_ptr_pkg.all;
use vunit_lib.string_ops.all;
use vunit_lib.dict_pkg.all;
use vunit_lib.dict_2008p_pkg.all;
use vunit_lib.data_types_private_pkg.all;

use std.textio.all;

Expand Down Expand Up @@ -82,6 +85,12 @@ package python_pkg is
--
-- H and L are read as 1 and 0. Any other metavalue is an error.
--
-- A dict_t value becomes a Python dict, {"key": value, ...}, whose values
-- are written the same way: integer, real, string, boolean, std_ulogic,
-- integer_vector, real_vector, integer_vector_ptr_t, integer_array_t and
-- dict_t values. Any other value type is an error. An integer_array_t value
-- is transferred as described below.
--
-- An integer_array_t value is transferred to Python by the Python bridge,
-- which is only available for NVC, GHDL and Questa, and is referred to by
-- the expression, which means that it can be used in several calls.
Expand All @@ -97,6 +106,8 @@ package python_pkg is
impure function kwarg_signed(kw : string; value : signed) return arg_t;
impure function arg(value : integer_array_t) return arg_t;
impure function kwarg(kw : string; value : integer_array_t) return arg_t;
impure function arg(value : dict_t) return arg_t;
impure function kwarg(kw : string; value : dict_t) return arg_t;

-- The Python expression calling identifier with the given arguments, for
-- example to embed a call in a larger exec or eval string:
Expand Down Expand Up @@ -168,6 +179,11 @@ package python_pkg is
-----------------------------------------------------------------------------
-- Results of eval: boolean, std_ulogic, vectors and arrays
-----------------------------------------------------------------------------
-- The result of eval_dict and call_dict is a new dict_t that the caller owns
-- and deallocates. Its values are integer, real, string, boolean,
-- integer_vector_ptr_t (a list of int), integer_array_t (a NumPy array, with
-- the word size of an eval_integer_array result) and dict_t (a nested dict).
--
-- std_ulogic_vector and integer_array_t results are only available under
-- their explicit names, not as eval overloads, which keeps
-- check_equal(eval("17"), 17) and length(eval("[1, 2]")) unambiguous.
Expand All @@ -191,6 +207,11 @@ package python_pkg is
expr : string; session : python_session_t := default_session
) return integer_array_t;

impure function eval_dict(
expr : string; session : python_session_t := default_session
) return dict_t;
alias eval is eval_dict[string, python_session_t return dict_t];

procedure eval_std_ulogic_vector(
expr : string; result : out std_ulogic_vector; session : python_session_t := default_session
);
Expand Down Expand Up @@ -251,6 +272,13 @@ package python_pkg is
alias call is call_integer_array[
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];

impure function call_dict(
identifier : string; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 : arg_t := null_arg;
session : python_session_t := default_session
) return dict_t;
alias call is call_dict[
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];

procedure call_std_ulogic_vector(
identifier : string; result : out std_ulogic_vector;
arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 : arg_t := null_arg;
Expand Down Expand Up @@ -622,6 +650,45 @@ package body python_pkg is
return "__vunit__.staged(" & integer'image(staged_id) & ")";
end;

impure function p_arg_value(value : dict_t; operation : string) return string;

-- The Python source text of the value stored for a key of a dict
impure function p_dict_item(value : dict_t; key, operation : string) return string is
begin
case get_value_type(value, key) is
when vhdl_integer => return to_string(get_integer(value, key));
when vhdl_real => return to_string(get_real(value, key), "%.16e");
when vhdl_string => return p_quoted(get_string(value, key));
when vhdl_boolean => return arg(get_boolean(value, key)).value;
when ieee_std_ulogic => return p_arg_value(get_std_ulogic(value, key), operation);
when vhdl_integer_vector => return to_py_list_str(get_integer_vector(value, key));
when vhdl_real_vector => return to_py_list_str(get_real_vector(value, key));
when vunit_integer_vector_ptr_t => return p_arg_value(get_integer_vector_ptr_t_ref(value, key));
when vunit_integer_array_t => return p_arg_value(get_integer_array_t_ref(value, key), operation);
when vunit_dict_t => return p_arg_value(get_dict_t_ref(value, key), operation);
when others =>
return p_failed_value(
operation & " cannot convert the " & to_string(get_value_type(value, key)) &
" value of the dict key """ & key & """"
);
end case;
end;

impure function p_arg_value(value : dict_t; operation : string) return string is
variable result : line;
begin
swrite(result, "{");
for idx in 0 to num_keys(value) - 1 loop
if idx > 0 then
swrite(result, ", ");
end if;
swrite(result, p_quoted(get_key(value, idx)) & ": " & p_dict_item(value, get_key(value, idx), operation));
end loop;
swrite(result, "}");

return result.all;
end;

function arg(value : real_vector) return arg_t is
begin
return (p_positional_arg, p_arg_value(value));
Expand Down Expand Up @@ -682,6 +749,16 @@ package body python_pkg is
return (kw, p_arg_value(value, "kwarg"));
end;

impure function arg(value : dict_t) return arg_t is
begin
return (p_positional_arg, p_arg_value(value, "arg"));
end;

impure function kwarg(kw : string; value : dict_t) return arg_t is
begin
return (kw, p_arg_value(value, "kwarg"));
end;

-----------------------------------------------------------------------------
-- Argument groups
-----------------------------------------------------------------------------
Expand Down Expand Up @@ -896,6 +973,51 @@ package body python_pkg is
return result;
end;

-- The dict_t of a dict result. Python keeps the converted entries of the dict
-- under the handle, see dict_kinds, dict_key and dict_item of runtime.py, and
-- VHDL fetches them one at a time with the eval of the type of each value.
impure function p_to_dict(handle : integer; session : python_session_t) return dict_t;

procedure p_add_dict_item(dict : dict_t; handle, index, kind : integer; session : python_session_t) is
constant entry : string := "(" & integer'image(handle) & ", " & integer'image(index) & ")";
constant key : string := eval_string("__vunit__.dict_key" & entry, session);
constant item : string := "__vunit__.dict_item" & entry;
variable ptr : integer_vector_ptr_t;
variable arr : integer_array_t;
variable nested : dict_t;
begin
case kind is
when p_kind_integer => set_integer(dict, key, eval_integer(item, session));
when p_kind_real => set_real(dict, key, eval_real(item, session));
when p_kind_boolean => set_boolean(dict, key, eval_boolean(item, session));
when p_kind_string => set_string(dict, key, eval_string(item, session));
when p_kind_integer_vector =>
ptr := eval_integer_vector_ptr(item, session);
set_integer_vector_ptr_t_ref(dict, key, ptr);
when p_kind_integer_array =>
arr := eval_integer_array(item, session);
set_integer_array_t_ref(dict, key, arr);
when others =>
-- A nested dict, whose result is its handle
if p_eval(item, p_kind_dict, -1, p_eval_operation(item, session), session) then
nested := p_to_dict(p_result_integer, session);
set_dict_t_ref(dict, key, nested);
end if;
end case;
end;

impure function p_to_dict(handle : integer; session : python_session_t) return dict_t is
constant kinds : integer_vector := eval_integer_vector(
"__vunit__.dict_kinds(" & integer'image(handle) & ")", session
);
constant result : dict_t := new_dict;
begin
for idx in kinds'range loop
p_add_dict_item(result, handle, idx - kinds'low, kinds(idx), session);
end loop;
return result;
end;

-----------------------------------------------------------------------------
-- exec
-----------------------------------------------------------------------------
Expand Down Expand Up @@ -1004,6 +1126,16 @@ package body python_pkg is
return null_integer_array;
end;

impure function eval_dict(
expr : string; session : python_session_t := default_session
) return dict_t is
begin
if p_eval(expr, p_kind_dict, -1, p_eval_operation(expr, session), session) then
return p_to_dict(p_result_integer, session);
end if;
return new_dict;
end;

procedure eval_std_ulogic_vector(
expr : string; result : out std_ulogic_vector; session : python_session_t := default_session
) is
Expand Down Expand Up @@ -1104,6 +1236,16 @@ package body python_pkg is
);
end;

impure function call_dict(
identifier : string; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 : arg_t := null_arg;
session : python_session_t := default_session
) return dict_t is
begin
return eval_dict(
to_call_str(identifier, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10), session
);
end;

procedure call_std_ulogic_vector(
identifier : string; result : out std_ulogic_vector;
arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 : arg_t := null_arg;
Expand Down
1 change: 1 addition & 0 deletions src/vunit_python_bridge/hdl/src/python_pkg_vhpi.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ package python_ffi_pkg is
constant p_kind_integer_array : integer := 8;
constant p_kind_integer_vector : integer := 9;
constant p_kind_real_vector : integer := 10;
constant p_kind_dict : integer := 11;

-- Names of the operations, used in the error messages
impure function p_exec_file_operation(
Expand Down
10 changes: 10 additions & 0 deletions src/vunit_python_bridge/hdl/tools/generate_python_pkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
dict(vhdl="unsigned", impure=True, fails=True, suffix="_unsigned"),
dict(vhdl="signed", impure=True, fails=True, suffix="_signed"),
dict(vhdl="integer_array_t", impure=True, fails=True, suffix=""),
dict(vhdl="dict_t", impure=True, fails=True, suffix=""),
]

# Result types of eval and call.
Expand Down Expand Up @@ -156,6 +157,15 @@
# the integer_array_t and integer_vector_ptr_t results. Use eval_integer_array.
alias_eval=False,
),
dict(
name="dict",
vhdl="dict_t",
kind="p_kind_dict",
value="p_to_dict(p_result_integer, session)",
default="new_dict",
hand_written_eval=False,
hand_written_call=False,
),
]

# Result types whose width is given by the actual of the result parameter
Expand Down
Loading
Loading