C/RTL Cosimulation with Vivado and Python

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.

  1. 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.)
  2. 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.)
  3. 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.
  4. 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.
  5. 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.