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
33 changes: 25 additions & 8 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: 'docs'

# Builds the documentation with warnings as errors, as Read the Docs does, and keeps the HTML
# as an artifact. A push to main triggers the Read the Docs build of latest, which publishes
# it, see .readthedocs.yaml.
# as an artifact. Read the Docs publishes it, see .readthedocs.yaml: a push to main builds
# latest, and a release tag vX.Y.Z gets a version of its own and updates stable.

on:
push:
Expand Down Expand Up @@ -37,14 +37,31 @@ jobs:

publish:
needs: docs
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'VUnit/vunit-python-bridge'
if: >-
github.event_name == 'push' && github.repository == 'VUnit/vunit-python-bridge' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-24.04
name: '馃殌 Read the Docs'
env:
API: https://app.readthedocs.org/api/v3/projects/vunit-python-bridge
AUTH: 'Authorization: Token ${{ secrets.READTHEDOCS_TOKEN }}'
steps:

- name: '馃殌 Trigger the build of latest'
env:
READTHEDOCS_TOKEN: ${{ secrets.READTHEDOCS_TOKEN }}
- name: '馃殌 Build latest'
if: github.ref == 'refs/heads/main'
run: curl --fail-with-body -X POST -H "$AUTH" "$API/versions/latest/builds/"

# Read the Docs learns about the tag in the background, so wait for its version
- name: '馃殌 Build the release and stable'
if: startsWith(github.ref, 'refs/tags/v')
run: |
curl --fail-with-body -X POST -H "Authorization: Token $READTHEDOCS_TOKEN" \
https://readthedocs.org/api/v3/projects/vunit-python-bridge/versions/latest/builds/
curl --fail-with-body -X POST -H "$AUTH" "$API/sync-versions/"
for attempt in $(seq 30); do
curl --fail -s -o /dev/null -H "$AUTH" "$API/versions/$GITHUB_REF_NAME/" && break
[ "$attempt" = 30 ] && { echo "::error::Read the Docs has no version $GITHUB_REF_NAME"; exit 1; }
sleep 10
done
curl --fail-with-body -X PATCH -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"active": true, "hidden": false}' "$API/versions/$GITHUB_REF_NAME/"
curl --fail-with-body -X POST -H "$AUTH" "$API/versions/$GITHUB_REF_NAME/builds/"
curl --fail-with-body -X POST -H "$AUTH" "$API/versions/stable/builds/"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ __pycache__/
/docs/_build/
/src/vunit_python_bridge/bin/
vunit_out/
/docs/release_notes.rst
/docs/api/
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# vunit-python-bridge

[![Documentation](https://img.shields.io/readthedocs/vunit-python-bridge?label=documentation)](https://vunit-python-bridge.readthedocs.io)
[![Repository](https://img.shields.io/badge/github-VUnit%2Fvunit--python--bridge-blue?logo=github)](https://github.com/VUnit/vunit-python-bridge)
[![PyPI](https://img.shields.io/pypi/v/vunit-python-bridge?label=pip%20install%20vunit-python-bridge&color=blue)](https://pypi.org/project/vunit-python-bridge/)
[![Python versions](https://img.shields.io/badge/python-3.10%20%E2%80%93%203.15-blue?logo=python&logoColor=white)](https://vunit-python-bridge.readthedocs.io/en/latest/requirements.html)
[![License](https://img.shields.io/badge/license-MPL--2.0-blue)](https://github.com/VUnit/vunit-python-bridge/blob/main/LICENSE)
[![CI status](https://github.com/VUnit/vunit-python-bridge/actions/workflows/python_bridge.yml/badge.svg?branch=main)](https://github.com/VUnit/vunit-python-bridge/actions/workflows/python_bridge.yml)

A VUnit package making Python callable from VHDL.

## Overview
Expand Down Expand Up @@ -88,10 +95,7 @@ environment and its installed packages.

## Documentation

The user guide is in [docs/user_guide.rst](docs/user_guide.rst): sessions, `exec`, `eval`, `call`
and its argument forms, `exec_file`, `import_run_script`, the type mapping, `integer_array_t` and
NumPy, error reporting, and how the bridge works. A complete example covering all three simulator
families is in [examples/embedded_python](examples/embedded_python).
The documentation is at [vunit-python-bridge.readthedocs.io](https://vunit-python-bridge.readthedocs.io).

## License

Expand Down
43 changes: 40 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,48 @@
Build with: sphinx-build -b html docs docs/_build
"""

import sys
import tomllib
from pathlib import Path

from packaging.version import Version

ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(ROOT / "docs"))
import vhdl_api # noqa: E402

project = "vunit-python-bridge"
copyright = "2014-2026, Lars Asplund" # pylint: disable=redefined-builtin
author = "Lars Asplund"
release = "0.1.0"
with (ROOT / "pyproject.toml").open("rb") as fptr:
release = tomllib.load(fptr)["project"]["version"]

extensions = []
extensions = ["myst_parser"]
# The release notes start at ## since they are included under a version heading
suppress_warnings = ["myst.header"]
exclude_patterns = ["_build"]
html_theme = "alabaster"

html_theme = "sphinx_rtd_theme"
html_theme_options = {
"prev_next_buttons_location": "both",
"navigation_depth": 3,
}
# The "Edit on GitHub" link of every page
html_context = {
"display_github": True,
"github_user": "VUnit",
"github_repo": "vunit-python-bridge",
"github_version": "main",
"conf_py_path": "/docs/",
}


# The release notes page, generated from release_notes/*.md, newest first
_notes = ["Release notes", "=============", ""]
for _path in sorted((ROOT / "release_notes").glob("*.md"), key=lambda path: Version(path.stem), reverse=True):
_title = _path.stem if Version(_path.stem) <= Version(release) else "Unreleased"
_notes += [_title, "-" * len(_title), "", f".. include:: ../release_notes/{_path.name}"]
_notes += [" :parser: myst_parser.sphinx_", ""]
(ROOT / "docs" / "release_notes.rst").write_text("\n".join(_notes))

vhdl_api.write(ROOT / "docs" / "api")
74 changes: 74 additions & 0 deletions docs/errors.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
Errors and semantics
====================

Errors
------

Python exceptions, syntax errors, type errors and undefined names are
reported as failures on the logger of the :ref:`session
<python_bridge:sessions>` the operation was performed in,
``get_logger(get_id(session))``, including the Python traceback. For the
default session that logger is named ``python_bridge:python:default`` and
``python_logger`` (named ``python_bridge:python``) is its parent. The test
fails and stops like for any other failure. The logger of the session can be
mocked to test error handling; when mocked, ``eval``/``call`` return a default
value (``0``, ``0.0``, ``""`` or an empty vector, depending on the type) after
the failure.

.. code-block:: text

FAILURE - python_bridge:python:default - eval("1 / 0") failed:
Traceback (most recent call last):
File "<eval #3>", line 1, in <module>
1 / 0
~~^~~
ZeroDivisionError: division by zero

The default ``verbose`` format of the VUnit display handler indents the lines
of the traceback to the column where the message starts, after the time, the
logger name and the log level. With the long logger name of a session made from
an instance name and the absolute file paths of a traceback, the lines can be
wider than the terminal, which wraps them back to the first column and breaks
the alignment. The ``level`` format leaves out the time and the logger name and
indents the traceback by the width of the log level only:

.. code-block:: vhdl

test_runner_setup(runner, runner_cfg);
set_format(display_handler, level);

.. code-block:: text

FAILURE - eval("1 / 0") failed:
Traceback (most recent call last):
File "<eval #3>", line 1, in <module>
1 / 0
~~^~~
ZeroDivisionError: division by zero

Output of ``print`` is written to the simulator output and flushed after
every operation.

.. _python_bridge:semantics:

Semantics to be aware of
-------------------------

.. important::

* ``eval``/``eval_<type>`` are declared ``impure`` since they always read
state from the Python interpreter.
* Errors are reported as failures on the logger of the :ref:`session
<python_bridge:sessions>`, including the Python traceback, rather than
aborting the simulation.
* ``python_setup`` and ``python_cleanup`` are optional, see
:ref:`python_bridge:setup_and_cleanup`. ``python_cleanup`` does not
finalize the interpreter.
* ``real`` results have no float32 range limit: VHDL ``real`` is double
precision where the Python bridge implements the API, so any finite
Python ``float`` (also double precision) is in range. A value that is not
finite, such as ``float("inf")`` or ``float("nan")``, has no VHDL ``real``
representation and is reported as a failure.
* ``string`` arguments are passed to Python double-quoted verbatim: a quote
or backslash inside the string is not escaped, so it must be avoided or
already be valid inside a Python double-quoted string.
Loading
Loading