Examples

Basic counter test

# test_counter.py
def test_counter_basic(test_session):
    test_session.run(test_module="cocotb_counter")
pytest --simulator verilator --hdl-toplevel counter --sources rtl/counter.sv

Parametrised tests

Combine pytest.mark.parametrize with test_session to run the same cocotb testbench against different configurations:

import pytest

@pytest.mark.parametrize("width", [8, 16, 32])
def test_counter_widths(test_session, width):
    test_session.run(
        test_module="cocotb_counter",
        parameters={"WIDTH": width},
    )

Each parametrised variant gets its own output directory automatically.

Overriding test_module in run()

The test_session fixture derives test_module from the test file’s module name by default. Override it to point at a different cocotb module:

def test_with_custom_module(test_session):
    test_session.run(test_module="my_other_cocotb_tests")

Using --waves

Enable waveform dumping for a debugging session:

pytest --simulator verilator --hdl-toplevel counter --sources rtl/counter.sv --waves

When --waves is enabled the build is placed in a separate build_waves/ directory, so you can switch between wave and non-wave runs without triggering a rebuild.

Using --defines

Pass preprocessor defines as name/value pairs:

pytest --simulator verilator --hdl-toplevel counter --sources rtl/counter.sv \
    --defines WIDTH 16 --defines DEPTH 32

Using --parameters

Pass HDL parameters (Verilog generics) as name/value pairs. The plugin passes these directly to runner.build(parameters={...}), so the cocotb runner formats them correctly for the target simulator (e.g. -GNAME=VALUE for Verilator):

pytest --simulator verilator --hdl-toplevel counter --sources rtl/counter.sv \
    --parameters WIDTH 16 --parameters ItcmInitFile firmware.vmem

This is the recommended way to set string parameters. Unlike --build-args, values are not passed through shlex.split, so no quoting issues arise.

Using --filelist

Point at a .f filelist instead of listing source files individually:

pytest --simulator verilator --hdl-toplevel counter --filelist sources.f

The filelist path is resolved to absolute before being passed as -f <path> to the simulator build command, so relative paths in pytest.ini work correctly.

Using --includes

Add include directories for \`include directives:

pytest --simulator verilator --hdl-toplevel counter --sources rtl/counter.sv \
    --includes rtl/includes --includes rtl/common

Using --build-args

Pass extra arguments to the simulator build step. Arguments are shlex-split, so quoting works as expected:

pytest --simulator verilator --hdl-toplevel counter --sources rtl/counter.sv \
    --build-args "--trace-fst" --build-args "-Wno-fatal"

Using --regress

Create a timestamped output directory for each run, useful for regression tracking:

pytest --simulator verilator --hdl-toplevel counter --sources rtl/counter.sv --regress

This produces a layout like sim_build/20250101_120000/build/ instead of sim_build/build/.

Using --modules

Load environment modules before simulation (useful on HPC clusters):

pytest --simulator verilator --hdl-toplevel counter --sources rtl/counter.sv \
    --modules verilator/5.024

See HPC support for more details on HPC integration.

Setting defaults in conftest.py

Rather than repeating CLI flags, you can set defaults in conftest.py:

# conftest.py
def pytest_addoption(parser):
    """Override defaults for this project."""
    parser.addini("simulator", default="verilator")
    parser.addini("hdl_toplevel", default="counter")

Or more commonly, use addopts in pyproject.toml:

[tool.pytest.ini_options]
addopts = "--simulator verilator --hdl-toplevel counter --sources rtl/counter.sv"

Log file behaviour

When pytest output capture is active (the default), the test_session fixture automatically sets log_file to <test_dir>/sim.log. This captures all simulator output to a file.

To disable capture and see simulator output live, run with -s:

pytest -s --simulator verilator --hdl-toplevel counter --sources rtl/counter.sv

You can also provide a custom log file path in run():

def test_counter_basic(test_session):
    test_session.run(
        test_module="cocotb_counter",
        log_file="custom.log",  # relative to the test directory
    )