Summary
Xilinx's xsim simulator (UG900) includes the Xilinx Simulator Interface (XSI), a way to embed a RTL simulation kernel inside a C/C++ program. This page further embeds XSI kernels within a Python session, creating a layered simulation environment where Python, C/C++, and RTL coexist. Testbenches can be coded in a mixture of the three languages, removing the hard boundaries that surround an RTL-only testbench environment.
Motivation
The universe is not made of RTL, and RTL-only test frameworks (especially the big ones) pretend it is.
- I want to test RTL using best-of-breed tools from the software world. Automated regression testing; parameterized tests run in parallel; automatic test discovery; standard and tightly integrated CI/CD tooling. RTL tools exist for most of this, but the software tools are orders of magnitude more mature. (How these tests get run on a build server is its own topic: see Remote Vivado Builds.)
- I want to verify my of C and RTL code, together. Driving a wedge between C code and RTL code so I can extract test vectors via file I/O feels like a monumental distraction. Especially for FPGA designs, treating RTL like a stand-alone deliverable is unnecessary and cumbersome. (ASIC folks can and should feel differently.)
- I need to generate or post-process test data in Python. For signal-processing analysis, my go-to environment is Python/Scipy/Matplotlib. For data analysis, there is nothing in C or RTL that offer the same broad reach and high productivity. You can do this by running pre- and post-processing scripts, but it is brittle and frustrating for no justifiable reason.
- I need all of this to work with mixed languages (SystemVerilog/VHDL) and encrypted IP. This, unfortunately, shuts out all of the open-source simulators (Verilator, GHDL, CXXRTL) I would love to be supporting and using. While the open-source tools are creeping closer to mixed-language simulation, it is difficult to see a (legal) path towards supporting encrypted IP in an open simulator. Practically speaking, I'm stuck with either Xilinx's simulator (xsim), or very expensive commercial options.
- I'm allergic to commercial EDA simulator licensing. "Big 3" simulators are better than xsim on whichever axis, but licenses are priced per-seat and cost a significant fraction of a salary. This is flatly incompatible with large, parameterized, parallel CI/CD runs and costs a significant chunk of an engineer's salary.
To date only xsim fits the bill. Fortunately, AMD/Xilinx has (over the past few years) improved xsim's support for VHDL-2008 and VHDL-2019, and there is evidence that the simulator is viewed as a genuine asset rather than a cost centre. However, Vivado still lacks the API surface (VHPI/VPI) to work with open-source cosimulation tools (cocotb, VUnit).
So, I built this.
Demonstration
The following sections show a start-to-finish build, from checking out the source code to completing a test.
Check Out the Source Code
Source code is available at https://github.com/gsmecher/pyxsi.
$ git clone https://github.com/gsmecher/pyxsi.git
Cloning into 'pyxsi'...
remote: Enumerating objects: 221, done.
remote: Counting objects: 100% (112/112), done.
remote: Compressing objects: 100% (58/58), done.
remote: Total 221 (delta 60), reused 88 (delta 47), pack-reused 109 (from 1)
Receiving objects: 100% (221/221), 74.97 KiB | 140.00 KiB/s, done.
Resolving deltas: 100% (101/101), done.
Build the RTL
Now set up Xilinx's environment variables and build the RTL into a simulation library:
$ cd pyxsi
pyxsi$ . /opt/xilinx/2026.1/Vivado/settings64.sh
pyxsi$ make rtl
[snip]
Built XSI simulation shared library xsim.dir/widget/xsimk.so
make: Leaving directory '/root'
Build the C++ Code
Now build the C++ code:
pyxsi$ make
[snip]
g++ -Wall -Werror -g -fPIC -std=c++20 -I/usr/include/python3.12 -I/usr/lib/python3/dist-packages/pybind11/include -I/opt/xilinx/2026.1/Vivado/data/xsim/include -Isrc -DSIMENGINE_SO=\"/opt/xilinx/2026.1/Vivado/lib/lnx64.o/libxv_simulator_kernel.so\" -shared -o pyxsi.so pybind.o xsi_loader.o -ldl
make: Leaving directory '/root'
Execute Tests
Finally, tests are discovered and executed using Python's pytest environment.
pyxsi$ make test
[snip]
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-7.4.4, pluggy-1.4.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /root
plugins: forked-1.6.0
collecting ... collected 8 items
py/test.py::test_counting[VHDL] PASSED
py/test.py::test_counting[Verilog] PASSED
py/test.py::test_counting_wide[VHDL] PASSED
py/test.py::test_counting_wide[Verilog] PASSED
py/test.py::test_hier_signal[VHDL] PASSED
py/test.py::test_hier_signal[Verilog] PASSED
py/test.py::test_random[VHDL] PASSED
py/test.py::test_random[Verilog] PASSED
============================== 8 passed in 31.57s ==============================
Next Steps
This repository contains a skeletal example, suitable for adoption and integration into your existing design trees. (In other words, think of this as a worked example, not a library.)
The Makefile in the repository wraps each step in a Docker container that captures a Vivado-approved OS, with the Vivado installation bind-mounted from the host. That recipe is described in Running Vivado in Docker.