Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
4443d3b3aThis file is pinned to the selected release. Download the original to open it in your design tool.
name: ci
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-22.04
# Four-way test split: each shard installs LiteX once and runs a
# quarter of the test suite with pytest-xdist `-n auto` on top. On a
# 4-core runner that means 16 effective workers across the four shards.
# test_integration's ~30 parametrized boot tests — which dominate
# wall-clock — end up ~7-8 per shard instead of ~30, roughly quartering
# the test phase. No cross-job orchestration needed (no artifact,
# shared state only via the Verilator / OSS CAD Suite caches).
strategy:
fail-fast: false
matrix:
group: [1, 2, 3, 4]
steps:
# Checkout Repository
- name: Checkout
uses: actions/checkout@v4
- name: Setup CCache
uses: hendrikmuhs/ccache-action@v1.2
# Install Tools
- name: Install Tools
run: |
sudo apt-get install wget build-essential ninja-build help2man
sudo apt-get install pkg-config python3-venv libglib2.0-dev libpixman-1-dev libfdt-dev device-tree-compiler
sudo apt-get install libevent-dev libjson-c-dev flex bison
sudo apt-get install libfl-dev libfl2 zlib1g-dev
# Coreblocks generation currently requires Python 3.13. Install it first
# so python3.13 remains available to the pythondata wrapper, then restore
# Python 3.9 as the default LiteX/test interpreter below.
- name: Set up Python 3.13 for Coreblocks
uses: actions/setup-python@v4
with:
python-version: "3.13"
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.9"
cache: "pip"
cache-dependency-path: "setup.py"
- name: Install Python dependencies
run: |
python3 -m pip install setuptools wheel requests pexpect meson distlib pytest pytest-xdist pytest-split
# Install (n)Migen / LiteX / Cores
- name: Install LiteX
run: |
python3 litex_setup.py --config=full --init --install --user --dev --clone-depth=1
# Install GCC Toolchains (cached). The packages below are what
# `litex_setup.py --gcc={riscv,openrisc,powerpc,mips}` resolves to on
# Ubuntu — bypass litex_setup here so the cache action can see the
# concrete package list and handle restore. On cache hit the .debs
# are replayed from cache; on miss apt install runs and repopulates.
- name: Install GCC Toolchains
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: gcc-riscv64-unknown-elf gcc-or1k-elf gcc-powerpc64le-linux-gnu gcc-mipsel-linux-gnu binutils-multiarch
version: 1.0
# Install LM32 GCC Toolchain. Ubuntu does not currently package
# lm32-elf-gcc, so use the LiteX-Hub Conda package and expose only
# the lm32-elf-* tools to the regular test environment.
- name: Setup Conda for LM32 Toolchain
uses: conda-incubator/setup-miniconda@v4
with:
auto-update-conda: false
activate-environment: litex-toolchains
python-version: "3.9"
channels: litex-hub,conda-forge
channel-priority: flexible
- name: Install LM32 GCC Toolchain
shell: bash -el {0}
run: |
python3 litex_setup.py --dev --gcc=lm32
sudo ln -sf "$CONDA_PREFIX/bin"/lm32-elf-* /usr/local/bin/
# Cache OSS CAD Suite (keyed on pinned release version — cache hit
# skips the ~500 MB download from the YosysHQ release bucket).
- name: Cache OSS CAD Suite
id: oss-cache
uses: actions/cache@v4
with:
path: ~/oss-cad-suite
key: oss-cad-suite-2025-05-30-ubuntu22
# Install OSS CAD Suite (only on cache miss). Direct curl to $HOME —
# deterministic install path, no tool-cache indirection.
- name: Install OSS CAD Suite
if: steps.oss-cache.outputs.cache-hit != 'true'
run: |
URL="https://github.com/YosysHQ/oss-cad-suite-build/releases/download/2025-05-30/oss-cad-suite-linux-x64-20250530.tgz"
curl -fsSL "$URL" | tar xz -C "$HOME"
# Forward OSS CAD Suite env (PATH / LD_LIBRARY_PATH / GHDL_PREFIX / etc.)
# to subsequent steps. Runs whether cache hit or miss.
- name: Export OSS CAD Suite env
run: |
source "$HOME/oss-cad-suite/environment"
{
echo "PATH=$PATH"
for v in LD_LIBRARY_PATH PKG_CONFIG_PATH GHDL_PREFIX PY_VCD_PATH YOSYSHQ_ROOT; do
if [ -n "${!v:-}" ]; then
echo "$v=${!v}"
fi
done
} >> "$GITHUB_ENV"
# # Build / Install GHDL
# - name: Build GHDL
# run: |
# sudo apt-get install gnat llvm
# git clone https://github.com/ghdl/ghdl.git
# cd ghdl
# git checkout 1fc1bccd1a7c256c531bdd6dc8f9641f90eb8ea0
# ./configure --with-llvm-config
# make
# sudo make install
# Cache Verilator install (keyed on pinned commit SHA — cache hit
# skips the ~15 min build. Bumping the SHA invalidates the cache).
- name: Cache Verilator install
id: verilator-cache
uses: actions/cache@v4
with:
path: /tmp/verilator-install
key: verilator-ubuntu22-7d2d32420a630befa4097170ecbf227e04e32522
# Build / Install Verilator (only on cache miss). `--prefix` under /tmp
# so the install tree is a self-contained cacheable directory.
- name: Build Verilator
if: steps.verilator-cache.outputs.cache-hit != 'true'
run: |
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
git clone https://github.com/verilator/verilator
cd verilator
git checkout 7d2d32420a630befa4097170ecbf227e04e32522
autoconf
./configure --prefix=/tmp/verilator-install
make -j$(nproc)
make install
# Copy verilator (cached or freshly built) into the standard location.
- name: Install Verilator into /usr/local
run: sudo cp -a /tmp/verilator-install/. /usr/local/
# Install Project
- name: Install Project
run: python3 setup.py develop --user
# Build the patched QEMU once for the dedicated co-simulation
# integration test. The regular pytest shards skip this test unless
# LITEX_QEMU_COSIM_TEST is set by the step below.
- name: Build patched QEMU
if: matrix.group == 1
env:
LD_LIBRARY_PATH: ""
PKG_CONFIG_PATH: ""
run: |
export PATH="${pythonLocation}/bin:${HOME}/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
qemu_root="${RUNNER_TEMP}/qemu-litex"
# Use a mirrored 8.2.x archive so CI is not dependent on download.qemu.org.
# Keep parallelism low: this build only feeds one smoke test and can
# otherwise hit memory pressure on hosted runners.
qemu_build_log="${RUNNER_TEMP}/qemu-litex-build.log"
set +e
python3 litex/build/sim/qemu/build_qemu_litex.py \
--source=archive \
--qemu-ref=v8.2.2 \
--archive-url=https://sources.cdn.openwrt.org/qemu-8.2.2.tar.xz \
--src-dir="${qemu_root}/src" \
--install-dir="${qemu_root}/install" \
--targets=riscv32-softmmu \
--minimal-devices \
--system-fdt \
--disable-download \
--jobs=2 2>&1 | tee "${qemu_build_log}"
qemu_build_status=${PIPESTATUS[0]}
set -e
if [ "${qemu_build_status}" -ne 0 ]; then
qemu_build_tail="$(python3 -c 'import pathlib, sys; s = pathlib.Path(sys.argv[1]).read_text(errors="replace").replace("\r", "\n"); s = "\n".join(s.splitlines()[-80:]); print(s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A"))' "${qemu_build_log}")"
echo "::error title=Patched QEMU build failed::${qemu_build_tail}"
exit "${qemu_build_status}"
fi
- name: Check patched QEMU
if: matrix.group == 1
env:
LD_LIBRARY_PATH: ""
PKG_CONFIG_PATH: ""
run: |
export PATH="${pythonLocation}/bin:${HOME}/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
qemu_root="${RUNNER_TEMP}/qemu-litex"
qemu_check_log="${RUNNER_TEMP}/qemu-litex-check.log"
set +e
python3 litex/build/sim/qemu/check_qemu_litex.py --variants=rv32 --qemu-dir="${qemu_root}/install/bin" 2>&1 | tee "${qemu_check_log}"
qemu_check_status=${PIPESTATUS[0]}
set -e
if [ "${qemu_check_status}" -ne 0 ]; then
qemu_check_tail="$(python3 -c 'import pathlib, sys; s = pathlib.Path(sys.argv[1]).read_text(errors="replace").replace("\r", "\n"); s = "\n".join(s.splitlines()[-80:]); print(s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A"))' "${qemu_check_log}")"
echo "::error title=Patched QEMU check failed::${qemu_check_tail}"
exit "${qemu_check_status}"
fi
- name: Run QEMU Co-Simulation Integration Test
if: matrix.group == 1
env:
LITEX_QEMU_COSIM_TEST: "1"
LD_LIBRARY_PATH: ""
PKG_CONFIG_PATH: ""
run: |
export PATH="${RUNNER_TEMP}/qemu-litex/install/bin:${pythonLocation}/bin:${HOME}/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
python3 -m pytest -v test/soc/test_integration.py::test_qemu_cpu
# Generate .test_durations so pytest-split's least_duration algorithm
# can pack shards optimally (longest tests first, fill remaining
# capacity with small tests). The alternative was a committed JSON
# with one entry per test — it would rot every time a test was added
# or renamed. Instead we emit it fresh each run from a tiny per-CPU
# duration table. Unknown CPUs default to a medium cost so new
# additions to TESTED_CPUS don't break balancing — they just aren't
# optimally placed until added here.
- name: Generate .test_durations
run: |
python3 - <<'PY'
import json, subprocess
out = subprocess.check_output(
["python3", "-m", "pytest", "--collect-only", "-q"]
).decode()
tests = [l.strip() for l in out.splitlines()
if l.startswith("test/") and "::" in l]
# Rough per-CPU boot-time estimates (seconds). Order of magnitude
# matters more than precision — least_duration only uses these
# to rank. Refine from `--durations=0` log output if desired.
BOOT_TIMES = {
"blackparrot": 180,
"cdim": 180,
"cv32e40p": 120, "cv32e41p": 120,
"cva6": 480,
"femtorv": 60, "firev": 60, "serv": 45,
"gs232": 180,
"sentinel": 120, "minerva": 120, "lm32": 120,
"marocchino": 120, "mor1kx": 60, "microwatt": 300,
"vexriscv": 120, "neorv32": 150, "ibex": 150,
"picorv32": 60,
"naxriscv": 240, "vexiiriscv": 240, "vexriscv_smp":360,
}
d = {}
for t in tests:
if "::test_cpu[" in t:
cpu = t.split("[", 1)[1].rstrip("]")
d[t] = float(BOOT_TIMES.get(cpu, 120))
elif "::test_buses[" in t:
d[t] = 100.0 # same default CPU, different bus cfg
elif "::test_csr_data_width_8" in t:
d[t] = 120.0 # same default CPU, 8-bit CSR cfg
elif any(name in t for name in (
"::test_linux_on_litex_rocket_ecpix5_generation",
"::test_linux_on_litex_rocket_nexys_video_generation",
)):
d[t] = 30.0 # software compile + no-gateware-compile generation smoke test
else:
d[t] = 1.0 # in-process Migen sims are near-instant
with open(".test_durations", "w") as f:
json.dump(d, f, indent=2, sort_keys=True)
print(f"generated .test_durations for {len(d)} tests")
PY
# Test: pytest-xdist `-n auto` distributes across the runner's 4
# cores inside each shard; pytest-split `--splits 4 --group N`
# distributes across the four matrix shards using the .test_durations
# generated above. `--durations=0` logs every test's runtime — use
# it to refine the BOOT_TIMES table above if the shards are uneven.
- name: Run Tests (shard ${{ matrix.group }}/4)
run: |
python3 -m pytest -v -n auto --durations=0 \
--splits 4 --group ${{ matrix.group }} \
--splitting-algorithm least_duration