Skip to content

Document the design pattern for a verification component with a Python model - #14

Closed
ru551n wants to merge 1 commit into
VUnit:mainfrom
ru551n:docs/vc-pattern
Closed

ru551n wants to merge 1 commit into
VUnit:mainfrom
ru551n:docs/vc-pattern

Conversation

@ru551n

@ru551n ru551n commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A verification component (VC) is used by many testbenches, often with several instances in each, and lives in a folder of its own. A relative file name given to exec_file or import_module_from_file resolves against tb_path of whichever testbench is running, and only after test_runner_setup, while a VC process usually starts at time 0. A VC therefore can't find its model by a relative file name, and the user guide doesn't say what to do instead.

The class example in "Independent models of component instances" also calls import_module_from_file(join(tb_path(runner_cfg), "accumulator.py"), ...) inside a component, which has no runner_cfg.

Changes

All in docs/user_guide.rst:

  • New section "Verification components with a Python model" (python_bridge:verification_components) under "Sessions", between "Independent models of component instances" and "Caveats". It describes:
    • The pattern: the model is a Python package next to the VC (my_vc/python/my_vc_model/__init__.py), imported by module name. Mutable state lives on one model object per instance. Each instance makes its own session with new_session(my_vc'instance_name) in the statement part of the process.
    • A Python Model class and a VHDL my_vc that imports and creates the model at time 0, without test_runner_setup.
    • A run.py snippet that prepends the VC's Python folder to PYTHONPATH, and pip install -e as an alternative.
    • Things to keep in mind: module and class level state is shared by all instances even with a session per instance, module names are global, errors are reported on the session's logger named after the instance path (with an example), and a model given as a file needs an absolute path.
  • The class example in "Independent models of component instances" now uses the component's model_file generic, import_module_from_file(model_file, "accumulator", session), and links to the new section.

Verification

I built a test project outside the repo. It has the my_vc VC and the my_vc_model package copied from the new section, checked by a script to be identical to the docs. It also has a testbench with two instances (gain 1 and gain 10) and a run.py that sets PYTHONPATH as the docs show. I started run.py from a different folder than the testbench.

  • GHDL 6.0.0 (mcode, ucrt64) on Windows 11, CPython 3.14.2, VUnit 5.0.0.dev13: passes.
    • With x1 driven 1 then 3 and x2 driven 2 then 4, y1 = 4 and y2 = 60.
    • The testbench waits 1 ns before calling test_runner_setup. At that point it checks that my_vc_model is in sys.modules and that two model objects exist. So both are imported and created at time 0.
    • The bridge library was built with MinGW-w64 gcc 13.2.0 (MSYS2), since a development checkout has no prebuilt DLLs.
  • Error message: I made one instance temporarily call a misspelled method. The failure was reported on python_bridge:python:tb_vc(tb):vc2@my_vc(python), which matches the logger name in the docs. The example now shows that exact call and traceback.
  • Docs: python -m sphinx -b html docs docs/_build (Sphinx 9.1.0) builds without warnings.
  • Repository tests: run the way CI runs them, with the same GHDL, Python and VUnit.
    • python -m pytest tests/test_python_bridge.py: 70 passed, 15 skipped.
    • python tests/run.py -p 4: 108 of 108 passed. The output path has to be short on Windows. With a long one, 23 tb_python_pkg_bridge tests fail because GHDL can't open vunit_results, as the path goes over the 260-character limit.
    • python examples/embedded_python/run.py -p 4 --without-attributes .optional_deps --without-attributes .expected_failure: 18 of 18 passed.

Not verified:

  • NVC: not installed on this machine.
  • Questa, Riviera-PRO/Active-HDL: not available.
  • Other GHDL backends (llvm, gcc), Linux and macOS: not run.

🤖 Generated with Claude Code

…n model

A verification component is used by many testbenches and lives in a folder
of its own, while a relative file name given to exec_file or
import_module_from_file resolves against tb_path of the running testbench,
and only after test_runner_setup. Describe how a VC imports its model as a
package by module name at time 0 instead, with a session and a model object
per instance.

Also make the class example of "Independent models of component instances"
use the model_file generic of the component, which has no runner_cfg.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ru551n added a commit to ru551n/vunit-python-bridge that referenced this pull request Oct 8, 2026
From the verification component pattern of VUnit#14: the model of a verification
component as a package next to it, put on PYTHONPATH by the run script or
installed with pip install -e. The class example of the independent models
section gets its file from the model_file generic of the component instead
of runner_cfg, which a component does not have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ru551n

ru551n commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #15, which turns this pattern into an API: new_python_object/get_python_object import a class by module name and give each object a session and identity of its own. The parts that still apply moved to #15: making the model importable (package layout, PYTHONPATH in the run script, pip install -e) and the fix to the class example that used runner_cfg inside a component.

@ru551n ru551n closed this Oct 8, 2026
ru551n added a commit to ru551n/vunit-python-bridge that referenced this pull request Oct 13, 2026
From the verification component pattern of VUnit#14: the model of a verification
component as a package next to it, put on PYTHONPATH by the run script or
installed with pip install -e. The class example of the independent models
section gets its file from the model_file generic of the component instead
of runner_cfg, which a component does not have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ru551n added a commit to ru551n/vunit-python-bridge that referenced this pull request Oct 14, 2026
From the verification component pattern of VUnit#14: the model of a verification
component as a package next to it, put on PYTHONPATH by the run script or
installed with pip install -e. The class example of the independent models
section gets its file from the model_file generic of the component instead
of runner_cfg, which a component does not have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ru551n added a commit to ru551n/vunit-python-bridge that referenced this pull request Oct 16, 2026
From the verification component pattern of VUnit#14: the model of a verification
component as a package next to it, put on PYTHONPATH by the run script or
installed with pip install -e. The class example of the independent models
section gets its file from the model_file generic of the component instead
of runner_cfg, which a component does not have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ru551n added a commit that referenced this pull request Oct 21, 2026
* Support Riviera-PRO through the bridge with VHPI

Riviera-PRO has no direct binding (VHPIDIRECT), and was
served by a separate VHPI application with a subset of the API. It now
calls the bridge like the other simulators, through a VHPI front end,
native/vhpi.c, that registers a foreign subprogram per entry point of
bridge.h. The front end is compiled into a variant of the library, like the
FLI one for Questa, against the vhpi_user.h of the simulator, and the
generated attributes name the library by absolute path without its suffix.
Strings cross as vectors of character positions, since Riviera-PRO ends a
string value at a line feed.

The VUnit interface of Riviera-PRO has no run environment hook,
so the setup sets VUNIT_PYTHON_BRIDGE_CONFIG in the environment of the VUnit
process, which vsim inherits.

The VHPI application (python_pkg_vhpi.vhd, native/vhpi/,
foreign_application.py) and the expected failures of the tests on VHPI are
removed. Two test names lose their brackets, which VUnit's Tcl scripts for
Riviera-PRO evaluate as commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review: Riviera-PRO only, explicit front ends

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Make the release note a news fragment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Prototype Python objects owned by VHDL

python_object_t is an instance of a Python class living in a session of its
own, created on first use and called with call(object, "method", args).
Every VC instance can get an object of its own without namespace or path
handling, and a bench can create one and pass it to a VC through a generic.

Hand-written prototype for review of the API; the full set of result types
would be generated once it is agreed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Generate the call and eval overloads of python_object_t

python_object_pkg.vhd is generated from python_object_pkg.vhd.in by
generate_python_pkg.py, from the same result tables as python_pkg, so an
object has every result type of call and eval, the procedure results and
the same aliases. A test checks that both generated packages are up to date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say that python_object_pkg.vhd is generated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rename object_or_new to get

Like get_logger and get_id, it gives the object it is given or a new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document Python objects and report objects that cannot be created

An object whose class cannot be imported or constructed reports that error
once and is then bound to a stand-in raising the reason on every use,
rather than leaving self undefined. The user guide gets a Python objects
section, and the README and the release notes mention them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name the constructor new_python_object and enumerate anonymous objects

new_object is now new_python_object and get is get_python_object, as
VUnit names the constructors and getters of its types. An object without
an id is python_bridge:python:object:<n>, enumerated like VUnit's
verification components. The user guide makes the handle constructor of a
VUnit style verification component the place to create its object, which
tests/counter_vc_pkg.vhd now shows; the handle-less component taking its
instance path is tests/path_counter_vc.vhd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add exec and get_logger for objects and report objects on VHPI

exec(object, code) executes code with the object as self, and
get_logger(object) is the logger reporting its errors. On
Riviera-PRO/Active-HDL, which have no sessions, the first use of an object
says that objects require NVC, GHDL or Questa. The user guide notes that an
integer_array_t cannot be a constructor argument of an object made during
elaboration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Show Python objects as backends in the embedded_python example

filter_vc is a verification component whose handle, made by
new_filter_vc, holds the Python object that is its behaviour. The new
test case drives three instances: two with objects of their own and one
given an object of the testbench, which the testbench queries too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Show how a model class is made importable, fix the class example

From the verification component pattern of #14: the model of a verification
component as a package next to it, put on PYTHONPATH by the run script or
installed with pip install -e. The class example of the independent models
section gets its file from the model_file generic of the component instead
of runner_cfg, which a component does not have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Polish python_object_pkg

Name the failures of objects after what failed: new_python_object("<class>")
for a failed creation and call("<method>") for the procedure form of a
method call, rather than an exec of their session. p_create and p_self are
private to the package body, and the PROTOTYPE marker is gone. The user
guide shows a component calling its object in a process and how errors are
named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Cover create and get_session of objects

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Lay out the instantiations like standard ones

One association per line in the generic and port maps of the tests, the
example and the user guide, the layout speja gives them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address the review of Python objects

- A class is named by a plain dotted name, resolved from the longest prefix
  that is a module, so nested classes work without the module:Class form.
- __vunit__.create is __vunit__.instantiate_as_self, after what it does.
- p_supports_sessions is gone: the FFI of Riviera-PRO/Active-HDL already
  reports the first use of a session other than the default one.
- filter_vc of the example takes a clock and a valid input.
- The user guide gets a "Where Python code is found" chapter on the run
  script directory, the Python environment, PYTHONPATH, file names and
  global module names, and clearer paragraphs on backends of verification
  components and shared objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say plainly when Python code is not found without setup

The Where Python code is found chapter gets a subsection per topic, and
the verification component example states that its folder is not
importable until the run script adds it to PYTHONPATH, before showing how.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Simplify python_object_pkg and the runtime

- pydoc.locate resolves dotted class names instead of a hand-written loop.
- The identity check is in VHDL only, which also catches a duplicate at
  elaboration; the Python check of the session was a second copy of it.
- Runtime.namespace is the property itself rather than a wrapper of it.
- create is the procedure the generated code calls, without p_create
  behind it, p_self is inlined and the operation of a failed creation is
  stored rather than rebuilt from the class name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Find classes of objects more clearly and report a failed object on every use

A class name now starts in the default session, so a class reached
through a name defined there, like "np.ndarray", is found, and is
otherwise imported with importlib rather than pydoc, which is no longer
imported at every simulation start. A name that is not found, or not a
class, is reported in one line rather than with the traceback of the
bridge. The stand-in of an object that could not be created also fails
on assignments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fail early on objects that cannot work and name the call in its errors

- new_python_object reports, on the logger of the identity, an identity
  already used by an object or by a session made with new_session, and
  a simulator without sessions (Riviera-PRO/Active-HDL), and then
  returns null_python_object rather than carrying on. Using
  null_python_object is reported on python_logger.
- The VHPI p_exec stub reports and executes nothing, rather than
  running the code in the default session.
- Every form of call on an object reports its errors as
  call("<method>"), like the procedure did: the call overloads evaluate
  with p_eval and the result getters, which the FFI packages now all
  have.
- create is named create_now, which is what it does.
- Tests of the error paths: a used identity, an object that cannot be
  created, the naming of call errors, separate sessions and numbering,
  exec and get_logger before creation, null_python_object, the eval
  procedures and the identities of create_std_cfg.
- The generated lines are at most 120 characters, and the docstring of
  the generator describes both packages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Merge where Python code is found into File paths and update the objects

The section on how modules are found is now the "Python modules" part
of "File paths", whose table already covered file names, with one
recipe for PYTHONPATH. The objects chapter describes the new identity
rule, the failure on Riviera-PRO/Active-HDL, the class lookup and the
error names of call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Apply the learnings of trying objects in a real project

Converting a stream-width model kept in module globals to an object per
source, in a project that used exec_file models before, showed:
- A misspelled class in a module that does import said only "No class
  named", which reads like a missing module. It now names the module
  and the missing attribute.
- The user guide gets a recipe for moving from a model with module
  state to an object, says that the id is optional, and recommends
  create_now to report wrong constructor arguments at the start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Pair new_python_object with create_python_object

new_python_object makes an object that Python constructs on first use;
create_python_object makes one that is constructed by the call, so a
wrong class or constructor argument is reported there. They replace
new_python_object with create_now, and the step that constructs an
object on first use is private.

create_python_object only works in a process: NVC cannot find the
foreign functions of the bridge during elaboration ("foreign function
vpy_buffer_clear not found") and GHDL cannot call them then. The user
guide has a table of when to use each, and errors name the one called.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give Python objects a page of their own in the documentation

The Python objects chapter moves from Running Python code to its own page, after
Sessions, and python_object_pkg gets a page in the VHDL API reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review: drop the simulator check of objects, new docs example

Remove p_sessions_supported: on Riviera-PRO/Active-HDL, the first use of an
object already fails in the VHPI primitives, and whether Riviera-PRO is
supported is a question of its own. The shared object example of the docs
is a memory model instead of a scoreboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Get Python objects by identity or full name

get_python_object(id) and get_python_object(name) return the object made
with that identity, like get_logger, but report an identity without an
object as a failure instead of making one. new_python_object and
create_python_object take the identity by its full name as well. The
get_python_object giving a component a default backend is replaced by an
if in the constructor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Bind a Python object to obj instead of self in its session

self is also the first parameter of the methods of classes defined in
the session, which made code mixing the two hard to read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name a Python object made without an id after its class

Objects without an id are enumerated per class, the dotted class name
giving the identity hierarchy under python_bridge:python:object, e.g.
python_bridge:python:object:models:counter_model:Counter:1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Return null_python_object for an identity without an object

get_python_object no longer reports a failure for an identity without
an object, so a testbench can check whether the object is made and make
it otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give verification components a constructor per use case

A component making its own object and a component given one have
constructors of their own instead of an optional model parameter. The
user guide describes the identities of components and their objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give path_counter_vc a handle instead of several generics

The handle holds the step and the id. Without an id the component still
takes its instance path as the identity, in its process, and the
testbench reaches the object by that path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Use the named eval and call forms, adding the alias call_integer

The tests and the user guide use eval_integer, call_real and the like
rather than qualified expressions. The call returning integer,
call_integer_w_arg, gets the alias call_integer in python_pkg and
python_object_pkg, named like the other results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add function forms of the vector results sized like to_unsigned

eval and call of std_ulogic_vector, signed and unsigned results, in
python_pkg and for objects, are also functions taking the width as a
length or as size_res. They call the procedure forms, so an error
returns a vector of 'U'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Leave the result of a failed sized function out of the docs

A failure stops the test before the function returns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Align the docs and comments of Python objects with the latest changes

- The python_pkg comment and the example no longer say that signed and
  unsigned results are procedures only; the example shows the sized
  function too.
- The aliasing of call is described for the functions without a width.
- The new features of the release notes are listed under New features.
- One class name in the identity examples of the user guide, and a class
  name given to a constructor rather than as a generic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Use the model of a handle directly and drop a python_pkg caveat

- counter_vc and filter_vc read p_model of their handle, like the user
  guide and VUnit's own verification components, instead of a get_model
  accessor.
- The Python objects page no longer repeats that string arguments are
  passed verbatim, a semantic of python_pkg that #16 changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Make the release notes news fragments

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten the news fragment of Python objects, drop the call_integer one

The details are in the user guide, and the call_integer alias is nothing
a user needs to know about.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name objects without an id python_object:<class>:<n>

The default identity was python_bridge:python:object:package:module:Class:<n>.
python_object:package.module.Class:<n> is shorter and as unique: the
counter separates the objects of a class and the dotted class name the
classes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant