Skip to main content

Xilinx Test Runner

A test runner for FPGA verification work: build the bitstream, keep the old ones, run the tests on the board, compare against the baseline.

One tool for the whole loop, with a console, a terminal interface and a window over the same run.

tests License: MIT Python 3.10+ Dependencies: standard library Docs: English and Russian

The tests tab

The window follows the desktop theme. The same run on a dark one: window-dark.png.


What it does

Verifying a design on an FPGA prototype is the same loop every day: get the current sources, build a bitstream, run tests in simulation and on the board, compare the behaviour against the documentation, fix something, then prove that nothing which used to work has broken.

Every step of that already has a tool. What is usually missing is the thing that holds them together and remembers what happened. This is that thing.

It is not a simulator, not a replacement for UVM and not a continuous integration system. It sits in front of those and keeps the results.

Installation

Python 3.10 or newer. Nothing else is required; the window needs Qt.

git clone https://github.com/shchuchkin-pkims/xilinx-test-runner
cd xilinx-test-runner
./bin/xtr --help              # runs straight from the checkout

pip install .                 # or install it properly
pip install ".[gui]"          # with PyQt6, for the window

Every release also carries a wheel and a source archive on its releases page.

python3 -m xtr works anywhere the xtr command does.

Quick start

A demo project is included. It builds a fake bitstream with a shell script, so it runs with no vendor tools installed at all:

cd examples/demo_project
xtr list                      # what is here
xtr run                       # run it in the console
xtr artifacts build           # "synthesise" and store a variant
xtr run --accept              # accept these results as the baseline
xtr --gui                     # or the window

For your own project:

cd /path/to/project
xtr init                      # writes xtr.toml and an example test
xtr doctor                    # is everything where the config says it is
xtr run

xtr --gui and xtr --tui also open with no project at all, and can point themselves at one from the File menu.

How a test is written

A test is any executable file. Its exit code is the verdict, and a few prefixed lines carry everything else. A script that has never heard of this runner works as it is.

#!/bin/bash
# TEST: nack_timing
# GROUP: Hardware
# DESC: NACK must start inside the window the standard allows
# NEEDS: board                     # one board: never two of these at once
# REQUIRES: bitstream up to date   # skip me if the build is stale
# RETRY: 1                         # flaky connection; the report will say so
# TARGET: lab_pc                   # run me on the machine with the board
# COVERS: ISO7816-3 8.3

command -v xsdb >/dev/null || { echo "SUMMARY: no debugger here"; exit 2; }

echo "PROGRESS: 30 programming the device"
etu=$(measure_etu)

echo "METRIC: nack_window $etu etu tol=0.5"
echo "CHECK: nack inside the window $( [ "$etu" -le 12 ] && echo pass || echo fail )"

[ "$etu" -le 12 ] || { echo "SUMMARY: NACK at $etu etu, limit is 11.5"; exit 1; }
echo "SUMMARY: NACK at $etu etu, inside the window"

Five verdicts, because two are not enough

Exit code Verdict Meaning
0 PASS checked, behaves as expected
1 FAIL checked, the design is wrong
2 SKIP could not check: no board, no licence, no tool
3 ERROR the test setup broke, not the design
4 KNOWN a known open defect, reproducing exactly as recorded

A missing cable must not paint the run red, and an open defect must not hide under green. KNOWN has its own counter and does not fail the run.

Lines a test can print

Line Purpose
SUMMARY: ... the one line that lands in the results table
PROGRESS: 40 message moves the progress bar
TRACE: ... verbose output, hidden until asked for, always kept
METRIC: etu 66673 cycles tol=50 a number compared between runs
CHECK: name pass a named verdict inside a test
ARTIFACT: /path/file a file to keep with the result
COVERS: ISO7816-3 8.3 which requirement this test checks

The full contract is in docs/writing-tests.md.

What the runner adds

Bitstreams are objects, not a file that happens to be there. Every build is stored as a named variant together with a hash of the sources it was built from. Switching between variants is a file copy, so comparing a fix against the previous build takes seconds instead of another synthesis run. Because the provenance is a hash of the content, copying a variant around cannot make an old build look current. A check based on file dates can, and that mistake is how results from two different designs end up mixed in one report.

Regression means comparison, not a demand for perfection. Tests report measurements and named checks; the runner keeps the accepted values and reports what moved. A test that used to fail and still fails in exactly the same way is reported as a known defect rather than noise. A test that starts passing is pointed out rather than silently absorbed.

The board is usually on another machine. A test can declare where it runs, and the runner drives it over SSH. Stopping a run kills the whole remote process tree, which is what keeps a cancelled run from leaving a debugger attached and the cable locked for the next hour.

Random stimulus stays reproducible. Each run has a seed, and each test gets a stable seed derived from it. Both are recorded, so a failure found with random data can be replayed with --seed.

Reports carry their context. Tool versions, source revision, active bitstream, machine name, seed. A report without those cannot be interpreted a month later.

Also included: dependencies between tests, setup tests that stop a section cleanly, retries that are always visible in the report, exclusive resources that hold even between separate runs of the program, a configuration matrix with a comparison report, and reports in text, JSON, JUnit XML and HTML.

Configuring a project

One file in the project root. TOML is read by the standard library; YAML works too if PyYAML is installed.

[project]
name  = "uart_7816 on EBAZ4205"
tests = "tests"

[tools.vivado]
path = "/opt/Xilinx/2025.1/Vivado/bin/vivado"   # or omit it and let it be found

[targets.lab_pc]                                 # the board is on another machine
type     = "ssh"
host     = "lab@10.0.0.2"
identity = "~/.ssh/id_ed25519_lab"
root     = "/home/lab/project"                  # the same tree, over there

[artifacts.bitstream]
store       = "fpga/bitstreams/variants"
active      = "fpga/bitstreams"
files       = ["design_1_wrapper.bit"]
extra_files = ["design_1_wrapper.xsa"]
sources     = ["rtl/**/*.v"]                     # what the provenance hash covers
build       = "bitstream"

[flows.bitstream]
plugin  = "vivado"
project = "fpga/EBAZ4205/EBAZ4205.xpr"
tcl     = "fpga/scripts/build.tcl"
output  = "fpga/EBAZ4205/EBAZ4205.runs/impl_1/design_1_wrapper.bit"
tail    = ["fpga/EBAZ4205/EBAZ4205.runs/impl_1/runme.log"]

[vcs.rtl]
path = "rtl"

From that, the runner generates the steps a project would otherwise script by hand: check whether the bitstream matches the sources, build a new one, switch between variants, show the source revision, fetch and pull it, and verify that the tools are installed where the configuration says.

Machine specific paths belong in xtr.local.toml next to it, which is not committed. Every key is documented in docs/configuration.md.

The interfaces

All three drive the same run and share the same state.

Command Interface
xtr run console; exits non zero on failure, for scripts and pipelines
xtr --tui terminal interface; needs only a terminal, works over SSH
xtr --gui window: tests, artifacts, run log, history

The window has four tabs. Tests shows the tree, the live output and the measurements next to the accepted values. Artifacts shows the stored bitstreams, which one is active and whether it still matches the sources. Run log collects everything every test printed, verbose lines included, which is where a long synthesis run is watched. History lists previous runs and how a measurement moved between them.

The artifacts tab

Command line

xtr                                     # run everything, print a table
xtr run sim --jobs 4                    # only what matches "sim", four at a time
xtr run --matrix bitstream=fix,old hw   # the same tests on two builds, compared
xtr run --seed 1234                     # replay a random run exactly
xtr run --accept                        # record these results as expected
xtr artifacts list                      # what is stored, what is active
xtr artifacts activate old_build
xtr history trend etu                   # is that measurement drifting
xtr issue                               # a ticket draft from the last failure
xtr doctor                              # check tools, targets and paths
xtr selftest                            # the runner's own tests

A complete example on real hardware

examples/pynq_z1 is a full project for a Digilent PYNQ-Z1: a written specification with seven numbered requirements, the RTL, a simulation testbench, and tests that run on the board. It is programmable logic only, so no software has to be built for the processor and no SD card is involved; the host talks to the design over the same JTAG cable that programs it.

ok    program              31.3 s  device configured, VIO answering, counter running
ok    bitstream up to date  0.0 s  built from the current sources (9eda4dd157f06f4b)
ok    syntax                1.0 s  2 file(s) compile clean, 0 warning(s)
ok    step_counter_tb       5.1 s  16 checks, all as specified
ok    lfsr                 27.7 s  16 values match the model, the seed reload works
ok    steps                27.7 s  reset, edge stepping, up, down, hold and wrap behave as specified

Documentation

Document Contents
Getting started installation, first run, adopting scripts you already have
Writing tests exit codes, output protocol, header fields, environment, seeds
Configuration every key, and which of the four files it belongs in
Artifacts bitstream variants, provenance, the configuration matrix
Remote execution SSH targets, shared trees, stopping remote processes
Baseline and history how a regression is decided, when to accept, trends
Architecture the three layers and how to extend them
Recipes Vivado, XSim, Cadence HAL, a board over SSH, CI
Firmware results self describing results from C running on the target

Russian translations of the main documents are in docs/ru/, and the overview is in README.ru.md.

Requirements

Python 3.11 or newer, standard library only; on 3.10, tomli for TOML
Operating system Linux (POSIX process groups and file locks)
Window PyQt6 or PyQt5, optional
YAML configuration PyYAML, optional; TOML needs nothing
Vendor tools whatever your tests use; the runner only has to find them

Nothing is downloaded at run time and no usage data is collected.

Design decisions

These are deliberate, and they are the reason the tool stays small.

  • The core knows nothing about FPGAs. Everything vendor specific is either configuration or a test. If that stops being true, the tool is no longer portable to the next project, and a shell script would do the job instead.
  • No external test manifest. Test metadata lives in the header of the test file, so adding a test means adding a file, and nothing can drift out of sync with a file that does not exist.
  • Skipping is not failing. Absent hardware, an unlicensed tool and a dead connection are three different situations, and none of them is a defect in the design.
  • A retry that hides instability is worse than no retry. The report always states that a test needed a second attempt.
  • Accepting a baseline is a deliberate act. A baseline that updates itself compares a run against itself.
  • No job queue, no agents, no scheduler. Those products exist. This one runs on the machine in front of the hardware.

Contributing

Bug reports, recipes for tools nobody has tried yet, and patches are all welcome. The suite that guards the behaviour above runs with no dependencies:

xtr selftest        # 94 tests

CONTRIBUTING.md describes the layering rule and what a change is expected to come with.

License and trademarks

MIT. See LICENSE.

This project is independent and is not affiliated with, endorsed by or sponsored by AMD, Cadence, Siemens or Intel. Product names are used only to describe which third party tools the runner can drive. No vendor artwork is included: the icon and the logo are original work. See TRADEMARKS.md.

Metadata

Release files for xilinx-test-runner 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for xilinx-test-runner 0.1.0
File Size Uploaded
xilinx_test_runner-0.1.0.tar.gz 100.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for xilinx-test-runner 0.1.0
File Interpreter ABI Platform
xilinx_test_runner-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 215.6 kB

Release files / xilinx_test_runner-0.1.0.tar.gz

Download URL xilinx_test_runner-0.1.0.tar.gz
Size 100.8 kB
Tags Source
SHA-256 checksum
How to use checksums
036abd46aa1d6465ee3a6bf4318811456035801cee5622783cfa00c2d314baff
BLAKE2b-256 checksum
How to use checksums
5982d0dd61c426980f093ab97ae294f3d46ea3b68dfb9c8a6f472270c1660a21
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.

Transparency log

Release files / xilinx_test_runner-0.1.0-py3-none-any.whl

Download URL xilinx_test_runner-0.1.0-py3-none-any.whl
Size 114.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6d043998e1b626dc91d8fef577ad30c1b375bbf93db36fa5edb70e471bbda645
BLAKE2b-256 checksum
How to use checksums
490b2573893b18826eec7cbe32db3b2e44f9304a15bec9907d1619a3cb33efa4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page