Repository navigation
Report relative file names used before test_runner_setup and add base_path - #12
Conversation
exec_file and import_module_from_file resolve a relative file name against the testbench path, which test_runner_setup sets. Before that, or without a test runner, the empty path made the name fall back to the working directory of the simulator, giving a misleading file-not-found error. Report it as a failure instead and document how a verification component waits for test_runner_setup to complete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
exec_file in the shared python_pkg now names the operation in its failure, so the VHPI variant of python_ffi_pkg needs it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Also declares |
vunit_python_bridge.set_relative_file_base(directory), called in the run script before add_package, sets a directory for relative file names that must not depend on the testbench. The package setup writes it into the generated python_file_base_pkg, empty when it is not set, for the bridge and the VHPI application alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
base_path(file_name) resolves a relative file name against the directory set by set_relative_file_base() in the run script. Unlike the testbench path it is known at time 0, so a verification component can load its model through exec_file(base_path(...)) without waiting for test_runner_setup. Without a directory set it reports a failure naming the run script call and returns "", which exec_file and import_module_from_file ignore. The failure of a relative file name used before test_runner_setup mentions base_path as an option. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed base_path returns "", which p_file_path now passes through rather than joining it with the testbench path, so exec_file and import_module_from_file execute nothing after a base_path failure, also after test_runner_setup. The tests of a relative file name before test_runner_setup expect the failure message that mentions base_path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/file_base/run.py sets the tests directory as the base directory and runs a testbench where a verification component process at time 0 and the main process after test_runner_setup load files through base_path, while a bare relative file name of exec_file is still taken from the testbench directory. The base directory is fixed for a run, so tests/run.py starts this run as a subprocess with the same simulator and an output path of its own, and fails when it fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cover the empty constant without a base directory, the absolute and working directory relative directories, the rejection of a missing directory and of a call after the setup, the ASCII encoding of quotes and non-ASCII bytes, and the package being added for the bridge and the VHPI application alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Present a base directory set in the run script and base_path as the way for a verification component to load its model at any time, with the wait for test_runner_setup as the alternative, and mention it in the release note of the failure for relative file names before test_runner_setup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An empty file name was passed through by p_file_path and ignored. It is now reported as a failure on the logger of the session and nothing is executed, like it was reported by the runtime before. A base_path that failed therefore gives its own failure followed by the empty file name one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The verification component process at time 0 may run after test_runner_setup has set the testbench path, depending on the simulator. Loading the files through base_path in the main process before test_runner_setup covers that case deterministically. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A run script that cannot be changed, for example a shared one in a git submodule, gets the base directory from the environment variable VUNIT_PYTHON_BRIDGE_FILE_BASE, set by a Makefile or whoever runs it. The variable is read during setup, an empty value counts as unset, a relative value is taken from the working directory, and one that is not an existing directory is an error. It takes precedence over set_relative_file_base(), with a warning when the two directories differ. The base_path failure names both ways of setting the directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pytest covers every combination of the variable and set_relative_file_base(), the warning, an empty, relative and missing directory value, and that the variable is not read at import. tests/run.py runs tests/file_base twice, with the base directory set by the run script and by the variable, which the file_base run script then leaves to the variable. The main run removes the variable from its own environment since its tests need the base directory unset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the variable, a Makefile example and the precedence over set_relative_file_base() to the user guide and the release note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rules for file names were spread over the exec_file and import_module_from_file sections, and the latter still said a relative name works from verification components. The new section lists every way a name is resolved, warns that the working directory of the simulator differs between simulators, gives recipes for a file next to the testbench, the model of a verification component, data files of a model and a shared run script, and describes base_path, set_relative_file_base and VUNIT_PYTHON_BRIDGE_FILE_BASE in full. The examples comment what each file name resolves to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The python_model verification component of the embedded_python example got its model file from the testbench as a name built from tb_path. It now takes a name relative to the base directory, the directory of run.py, which run.py sets with set_relative_file_base, so it works at any time and from any testbench, and the example runs from any working directory. The files the testbench loads itself are given relative to the testbench, and comments say what each file name resolves to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unset PYTHONPATH gave an empty entry, which Python reads as the working directory, the dependence the section warns about. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VUnit's join drops a base directory of / and gave a relative name, which was
then taken from the directory of the testbench. base_path("") returned the
base directory. The run with the environment variable now sets the base to the
root of the file system.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ase_path" This reverts commit 8ceb9dc. base_path becomes a function without arguments, used with join, which needs neither of these cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ectory VHDL joins file names to it with join, like tb_path(runner_cfg): join(base_path, "vc/models/uart_model.py"). Without a base directory it is still reported as a failure on python_logger, naming both ways of setting it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| .. code-block:: python | ||
| # project/vc/models on sys.path of the simulator | ||
| models = Path(__file__).parent / "vc" / "models" | ||
| # Drop empty entries: Python reads an empty PYTHONPATH entry as the working directory | ||
| paths = [str(models)] + [p for p in os.environ.get("PYTHONPATH", "").split(os.pathsep) if p] | ||
| os.environ["PYTHONPATH"] = os.pathsep.join(paths) |
There was a problem hiding this comment.
To Claude: simplify this example
There was a problem hiding this comment.
Done in a8ed48a. The recipe is now one line:
os.environ["PYTHONPATH"] = str(Path(__file__).parent / "vc" / "models")It replaces a PYTHONPATH that is already set instead of extending it, which keeps the example short. Say if you'd rather it kept the existing entries.
| too, and on every simulator. On Riviera-PRO/Active-HDL, where ``exec_file`` is | ||
| not supported, use it with ``import_module_from_file`` or for file names passed | ||
| to a model. Without a base directory ``base_path`` is reported as a failure on |
There was a problem hiding this comment.
write this generally without mentioning specific simulators.
There was a problem hiding this comment.
Done in a8ed48a. The Riviera-PRO/Active-HDL sentence and "and on every simulator" are gone from the base_path section.
Question: the warning about the working directory, further up in the File paths section, still names NVC, GHDL and Questa/ModelSim, since what it warns about is that each uses a different directory. Should that be written generally too?
| - ``sys.path``, which holds the directory of the run script first, like | ||
| ``python run.py`` does (not on Riviera-PRO/Active-HDL), the directories of ``PYTHONPATH`` (the | ||
| simulator inherits the environment of VUnit, so a ``PYTHONPATH`` set | ||
| in the run script before ``vu.main()`` is seen), and, while | ||
| ``exec_file`` executes a file, the directory of that file. |
There was a problem hiding this comment.
remove mentions of riviera pro, it is handled by the global "support on riviera pro"-stuff
There was a problem hiding this comment.
Done in a8ed48a: removed "(not on Riviera-PRO/Active-HDL)" from the table. That and the sentence in the base_path section were the only Riviera-PRO mentions this PR added.
| -- to the base directory, the directory of run.py, set in run.py. | ||
| python_model_inst : entity work.python_model | ||
| generic map(model_file => join(tb_path(runner_cfg), "python_model.py")) | ||
| generic map(model_file => "python_model.py") -- <base directory>/python_model.py |
There was a problem hiding this comment.
Remove the comment, the base path is joined inside the component.
There was a problem hiding this comment.
Done in a8ed48a. The comment is back to -- A component whose behaviour is a Python function, and the trailing comment on the generic map is gone.
| ## Changes | ||
|
|
||
| - `import_module_from_file` resolves a relative path against the directory of the testbench file, like `exec_file`, rather than against the working directory of the simulator. It must be called after `test_runner_setup`. Callers that relied on a path relative to the working directory must pass an absolute path instead. See #7. | ||
| - A relative file name of `exec_file` or `import_module_from_file` used before `test_runner_setup` has completed, or without a test runner, is reported as a failure. It was taken from the working directory of the simulator, which gave a misleading file-not-found error. A verification component can instead load its files with `join(base_path, ...)`, where `base_path` returns a directory set by `vunit_python_bridge.set_relative_file_base()` in the run script or by the environment variable `VUNIT_PYTHON_BRIDGE_FILE_BASE`, which takes precedence, for example `VUNIT_PYTHON_BRIDGE_FILE_BASE=$(CURDIR) python sub/run.py` in a Makefile. This works at any time. A verification component can also wait for `test_runner_setup` as the user guide shows. |
There was a problem hiding this comment.
This is too verbose. Shorten it to describe it in two sentences.
There was a problem hiding this comment.
Done in a8ed48a. It's now two sentences:
A relative file name given to
exec_fileorimport_module_from_filebeforetest_runner_setuphas completed is reported as a failure, instead of being taken from the working directory of the simulator. The newbase_pathreturns a base directory set withvunit_python_bridge.set_relative_file_base()orVUNIT_PYTHON_BRIDGE_FILE_BASE, for file names that work at any time, for example in verification components.
| SUPPORTED_SIMULATORS = "NVC, GHDL, Questa/ModelSim, Riviera-PRO or Active-HDL" | ||
|
|
||
| # The generated package giving base_path of python_pkg its directory | ||
| FILE_BASE_PACKAGE = "python_file_base_pkg" |
There was a problem hiding this comment.
What is this used for?
There was a problem hiding this comment.
It's the name of the generated VHDL package holding the base directory, python_file_base_pkg. It names the generated file and its package declaration, and python_pkg.vhd has use work.python_file_base_pkg.all for base_path. It's in file_base.py now (bcd887b), with a comment saying that.
| _setup_done = False | ||
|
|
||
|
|
||
| def set_relative_file_base(directory): |
There was a problem hiding this comment.
Does this functionality really belong in init.py?
There was a problem hiding this comment.
Same goes for the other new functions in this file
There was a problem hiding this comment.
No. bcd887b moves it to src/vunit_python_bridge/file_base.py, next to bridge.py and foreign_application.py. __init__.py now only calls add_file_base_package in setup. It re-exports set_relative_file_base and FILE_BASE_VARIABLE, because run scripts use vunit_python_bridge.set_relative_file_base().
There was a problem hiding this comment.
Moved as well (bcd887b): _vhdl_string_literal, _file_base_package_text, _file_base and _add_file_base_package (now add_file_base_package) are all in file_base.py.
| BRIDGE_SIMULATORS = ["nvc", "ghdl", "modelsim"] | ||
|
|
||
| # The environment variable setting the base directory of base_path | ||
| FILE_BASE_VARIABLE = "VUNIT_PYTHON_BRIDGE_FILE_BASE" |
There was a problem hiding this comment.
why is this redefined here?
There was a problem hiding this comment.
No good reason. tests/run.py now imports it with from vunit_python_bridge import FILE_BASE_VARIABLE (bcd887b).
| elsif run("Test relative file names before test_runner_setup") then | ||
| check_true(checked_before_setup); | ||
| -- Nothing was executed or imported | ||
| check_false(eval_boolean("'get_call_count' in globals() or 'filters_model' in globals()")); | ||
| elsif run("Test a relative file name from a process waiting for test_runner_setup to complete") then | ||
| wait for 1 ns; | ||
| check_equal(integer'(eval("get_call_count()", session => new_session("vc"))), 1); | ||
| elsif run("Test base_path without a base directory") then | ||
| -- The run script of these tests sets no base directory | ||
| mock(python_logger, failure); | ||
| check_equal(base_path, ""); | ||
| check_only_log( | ||
| python_logger, | ||
| "base_path failed: no base directory is set. " & | ||
| "Call vunit_python_bridge.set_relative_file_base() in the run script before add_package, " & | ||
| "or set the environment variable VUNIT_PYTHON_BRIDGE_FILE_BASE.", | ||
| failure | ||
| ); | ||
| unmock(python_logger); | ||
| elsif run("Test an empty file name") then | ||
| mock(default_logger, failure); | ||
| exec_file(""); | ||
| check_only_log(default_logger, "exec_file("""") failed:" & LF & "Empty file name.", failure); | ||
| import_module_from_file("", "x"); | ||
| check_only_log(default_logger, "import_module_from_file("""", ""x"") failed:" & LF & "Empty file name.", failure); | ||
| unmock(default_logger); | ||
| check_false(eval_boolean("'x' in globals()")); | ||
|
|
There was a problem hiding this comment.
Aren't these tests covered somewhere else? Or should they maybe be part of the specific test bench?
There was a problem hiding this comment.
Nothing else covered them: these were the only tests of relative names before test_runner_setup and of an empty file name. But they belong with the other file name tests, so 5999e3d moves them to tests/file_base/tb_file_base.vhd.
"Test base_path without a base directory" stays in tb_python_pkg_bridge, because only the main run has no base directory.
|
|
||
| -- A verification component waiting for test_runner_setup to complete before it loads its model | ||
| vc : process | ||
| begin | ||
| if find(runner_cfg, "Test a relative file name from a process waiting for test_runner_setup to complete") > 0 then | ||
| if get_phase <= test_runner_setup then | ||
| wait on runner until get_phase > test_runner_setup; | ||
| end if; | ||
| exec_file("models/counter.py", new_session("vc")); | ||
| end if; | ||
| wait; | ||
| end process; |
There was a problem hiding this comment.
This also feels duplicated.
There was a problem hiding this comment.
Yes: tb_file_base already had a vc process. The waiting verification component is now a branch of that process (5999e3d), and tb_python_pkg_bridge no longer has one.
set_relative_file_base, the environment variable and the generated python_file_base_pkg are now in file_base.py rather than __init__.py, which re-exports set_relative_file_base and FILE_BASE_VARIABLE for run scripts. tests/run.py imports FILE_BASE_VARIABLE instead of redefining it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tests of relative file names before test_runner_setup, of a verification component waiting for test_runner_setup and of an empty file name are in tb_file_base with the other file name tests, and the waiting verification component shares its vc process. The test of base_path without a base directory stays in tb_python_pkg_bridge, since only that run has none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The base_path comment says what it is, the release note is two sentences, the PYTHONPATH recipe is one line, the user guide no longer names simulators in the base_path section, and the example drops its comment on the model file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Pushed three commits for the review:
Tested on NVC 1.23-devel, Python 3.14 (Ubuntu):
|
set_relative_file_base and FILE_BASE_VARIABLE are imported into __init__.py for run scripts, not used there, and ruff reported them as unused imports (F401), which fails the lint workflow on main since #12. Import them as X as X, which marks them as re-exports.
exec_fileandimport_module_from_fileresolve a relative file name against the testbench path (tb_path), whichtest_runner_setupsets. Before it has completed, or without a test runner,tb_pathis empty and the name silently fell back to the working directory of the simulator:vunit_out/modelsim/on Questa, the directory the run script was started from on NVC and GHDL. The user got a misleading Python file-not-found error.This typically hits a verification component that loads its model at time 0, since its process runs concurrently with the testbench process. A VC is also reused from testbenches in different directories, so a path relative to the testbench is the wrong anchor for its files anyway.
Changes
Relative names before
test_runner_setupare reported. Without a testbench path, a relative file name is reported as a failure on the session's logger, and nothing is executed or imported:base_path: files of a VC, independent of the testbench and of timing. A base directory is set before the simulation starts.base_path, a function without arguments, returns it, and VHDL joins file names to it with VUnit'sjoin, the same way astb_path(runner_cfg). This works at any time (time 0 included), on every simulator, and also for file names passed to a Python model as arguments. On Riviera-PRO/Active-HDL, whereexec_fileis not supported, it servesimport_module_from_fileand file names passed to a model.exec_fileandimport_module_from_filekeep their meaning, relative to the testbench, so no testbench changes.A run script shared between repos (for example in a git submodule) that can't be changed gets the base from the environment instead:
set_relative_file_base()VUNIT_PYTHON_BRIDGE_FILE_BASEbase_pathfails, naming both ways to set it, and returns""The variable wins so that whoever runs a shared run script can override a default chosen in it. An empty variable counts as unset; a relative value is taken from the working directory at setup; a value that isn't an existing directory is a setup error. The base reaches VHDL through a package generated by the setup (
python_file_base_pkg), for the bridge and the VHPI application alike.Empty file names are reported.
exec_file("")andimport_module_from_file("", ...)report "Empty file name." on the session's logger.Docs: one File paths section. Path handling is gathered in a new user-guide section, which
exec_file,import_module_from_file,import_run_scriptand the sessions caveats link to:exec_file/import_module_from_fileafter and beforetest_runner_setup, absolute names,base_path, relative names opened by Python code, and imports by module name (sys.path: run script directory,PYTHONPATH, the directory of a file whileexec_fileruns it);vunit_out/modelsim), so Python code must not rely on relative names;base_path, or import by module name, or waiting fortest_runner_setup, and whywait_until(runner, test_runner_setup)is not enough), data files of a model, and a run script shared through a git submodule;base_path(including Riviera-PRO/Active-HDL, whereexec_fileis not supported and the run script directory is not onsys.path),set_relative_file_baseandVUNIT_PYTHON_BRIDGE_FILE_BASEin full, with the precedence table.Example.
examples/embedded_pythonshows the recommended layout:run.pysets its own directory as the base directory, thepython_modelVC loads its model withjoin(base_path, model_file), and the testbench's own files are plain names relative to the testbench, each commented with what it resolves to. It passes from the repo root and from another working directory.Release notes in
release_notes/0.2.0.md.Tests
tests/tb_python_pkg_bridge.vhd(logger mocked): relative names beforetest_runner_setup,base_pathwithout a base directory, empty file names, and a VC-style process using the documented wait pattern.tests/file_base: a run with a base directory set, started twice bytests/run.py, once throughset_relative_file_baseand once throughVUNIT_PYTHON_BRIDGE_FILE_BASE. It checks the value ofbase_path, loads files withjoin(base_path, ...)beforetest_runner_setup, from a VC-style process at time 0 and after setup, and checks that a bareexec_filestill resolves next to the testbench.tests/run.pyon NVC, GHDL (mcode) and Questa: 112 of 112, and 4 of 4 in each file_base run;examples/embedded_pythonon NVC, GHDL and Questa: the 17 tests that need no optional package pass, started from the repo root and from another directory. The docs build without warnings. Riviera-PRO and Active-HDL are covered by pytest only.🤖 Generated with Claude Code