Note: The cocotb BFM is a recent addition to the FrontPanel toolchain, and we are actively interested in how teams are putting it to work. If something you need is missing, or anything does not behave the way you would expect, reach out to us at [email protected].

cocotb support is best-effort, since the Python, cocotb, and Icarus Verilog versions drift easily. Pin the versions you validate against; the example’s requirements.txt pins the exact set we have verified.

Quickstart

A minimal cocotb test creates the container, gets an interface, resets it, and issues a transfer. Each interface has its own reset() driving its own AXI reset signal, so reset every interface you intend to use. For a runnable project, see Run the PerfTest Example.

import cocotb

from cocotbext.frontpanel import FPGADataportAXI

@cocotb.test()
async def example(dut):
    axi = FPGADataportAXI(dut.okHost_i)   # wraps okHost + starts the simulation clock
    full = axi.axi_full

    await full.reset()

    # Transfers raise on failure and return their result directly. A successful write
    # returns an AXIOperationStatistics, carrying .transfer_byte_count and
    # .transaction_count. A failing write raises AXIOperationError, carrying the same
    # record as .statistics; a failing read raises AXITransferError, carrying
    # .transfer_byte_count. Both derive from FrontPanelError, whose .code is the
    # ErrorCode for the failure, or a plain int if the code has no name in ErrorCode.
    stats = await full.write(0x0000, bytes(256))

    data = await full.read(0x0000, 256)Code language: Python (python)

The concept is on the parent’s Introduction to Host Simulation, how closely it matches hardware on Fidelity and Limitations, and the shared behaviors on The BFM Behavioral Contract. The sections below cover only what is specific to cocotb.

Using the BFM

The cocotb API mirrors the FrontPanel Python host API: the same class names, the same axi_lite / axi_full / axi_stream accessors, and the same method names, arguments, and return values. See the FrontPanel Python API reference for that shared surface. Four things differ in simulation and are covered below: every operation is a coroutine you await; you construct FPGADataportAXI directly from the okHost sim entity, where on hardware you obtain the container from a Device; timeouts are a float rather than whole milliseconds; and stall injection is a simulation-only test hook with no host counterpart.

One Operation at a Time

cocotb is a coroutine-based simulation library, so each transfer method is a coroutine you await. The host API it mirrors is blocking, and the BFM holds to that contract. Only one operation runs at a time across all three interfaces, guarded by one shared busy flag. Await each call before you start the next. Starting a second operation before the first finishes raises FrontPanelError with ErrorCode.UnsupportedFeature instead of driving the bus concurrently.

ErrorCode.UnsupportedFeature is also what a misaligned address raises, which in practice is the more common cause. Every AXI-Full and AXI-Lite address, and the length of every AXI-Full and AXI-Stream transfer, must be a multiple of the datapath width, or the call raises FrontPanelError with that code before it drives the bus. Read the width rather than assuming it: full.datapath_width_byte_count, and stream.write_datapath_width_byte_count / stream.read_datapath_width_byte_count.

ErrorCode has six members, and knowing which you got is how you tell the failures apart: NoError, UnsupportedFeature (the two cases above), InvalidParameter (an argument out of range, such as a burst length), AXISlaveError and AXIDecodeError (the slave answered with SLVERR or DECERR), and GatewareTimeout (the transfer ran out of time). Catching FrontPanelError alone tells you something failed; reading .code tells you what.

AXI-Full transfers also take a burst argument, the AXI burst length in beats. It is optional, defaults to 16, and must be 1 to 256; outside that range the call raises FrontPanelError with ErrorCode.InvalidParameter before it drives the bus.

AXI-Lite has no width accessor, in simulation or on hardware, and needs none: AXI4-Lite registers are 32 bits (4 bytes) by specification, so a test defines that constant itself, as the example’s test_perftest.py does with AXIL_REGISTER_BYTES = 4. Its length check is separate too. write_bulk and read_bulk_into raise a plain ValueError, before the bus is touched, when the buffer’s byte length is not a multiple of 4. That is not a FrontPanelError, so catching FrontPanelError alone will not catch it.

Timeouts

The hardware FrontPanel API takes a timeout in whole milliseconds. The cocotb BFM takes the same timeout_ms argument as a float, so you can set sub-millisecond timeouts and cut the simulated wait, for example 0.005 for 5 microseconds. Every transfer method defaults to timeout_ms=5.0, measured in simulated time rather than wall-clock time.

On a timeout a read raises AXITransferError with .code set to ErrorCode.GatewareTimeout, which is how you tell a timeout from a slave or decode error, and, like the host API, read/read_bulk discard the partial data. To inspect what arrived before the timeout, use the caller-buffer forms read_into (AXI-Full, AXI-Stream) or read_bulk_into (AXI-Lite): they fill your buffer in place and return the transferred byte count, which AXITransferError.transfer_byte_count also carries.

The returned count is always in bytes, so mind the units. For the byte-oriented read_into the valid region is buffer[:err.transfer_byte_count]. For read_bulk_into into an array('I') of 32-bit words it is buffer[:err.transfer_byte_count // 4]. Note that this applies to what comes back: read_bulk‘s own count argument goes the other way and is a number of 32-bit words, not bytes. Anything past the valid region is whatever you pre-filled the buffer with: the BFM does not clear or mark the tail, so reusing a buffer across transfers leaves stale data behind it.

Stall Injection

The BFM’s stall injection is configured per channel with methods on the AXI-Full and AXI-Stream BFMs. It is simulation-only and has no counterpart in the host API, so it is documented here rather than in the host API reference.

set_*_stall_percent(percent) takes a stall density from 0 to 100 and raises ValueError outside that range. Its stalled cycles come from a fixed-seed generator, one per channel, so re-running the same test reproduces the same placement.

set_*_stall_pattern(bits, length) takes two arguments: an explicit bitmap and the number of bits to use, from 1 to 256. The bitmap is consumed least-significant bit first, and each 0 inserts one idle cycle, so 0b1110 stalls one cycle and then runs three. Like the percent form, a length outside 1 to 256 raises ValueError.

Both are plain calls, not coroutines, so do not await them.

ChannelPercent methodPattern method
AXI-Full write (WVALID)full.set_write_stall_percentfull.set_write_stall_pattern
AXI-Full read (RREADY)full.set_read_stall_percentfull.set_read_stall_pattern
AXI-Stream write (TVALID)stream.set_write_stall_percentstream.set_write_stall_pattern
AXI-Stream read (TREADY)stream.set_read_stall_percentstream.set_read_stall_pattern

AXI-Lite has no stall injection.

Set Up the PerfTest Example

The cocotb PerfTest example lives in the cocotbext-frontpanel repository and runs on Icarus Verilog, the simulator these steps use; cocotb also works with other simulators.

This section is where every difference between operating systems lives: which packages to install, which command runs Python, and how to create and activate the virtual environment. Follow the one toolchain section for your system and skip the other two. It ends in the same place on every system: an activated virtual environment in which python runs the interpreter you installed, which is what Run the PerfTest Example assumes.

You need Python 3.11 or newer, Icarus Verilog 12 or newer, and git. The upper end is set by cocotb rather than by us: cocotb is a hard dependency, so your Python has to be a version cocotb publishes wheels for, and a freshly released CPython typically waits several months for them. In practice the newest Python release is not usable for the first few months after it ships.

If yours is too new, pip install falls back to building cocotb from source and the last line reads Getting requirements to build wheel did not run successfully. Scroll up a few lines for the message that actually tells you what to do, which names the ceiling directly:

RuntimeError: cocotb 2.0.1 only supports a maximum Python version of 3.13.Code language: CSS (css)

Install the previous Python version instead. cocotb’s files listing on PyPI shows which Python versions it currently publishes wheels for.

Install the Toolchain: Linux, or Windows via WSL

Ubuntu 24.04 or newer, Debian 13 or newer:

sudo apt update
sudo apt install make git python3 python3-pip python3-venv iverilog

Confirm with iverilog -V and python3 --version. Your interpreter is python3. If that python3 turns out to be newer than the versions cocotb ships wheels for, install a version cocotb supports and use it in place of python3 below.

Ubuntu 22.04 and Debian 12 package Icarus Verilog 11 and Python 3.10 and are not sufficient; upgrade the distribution (on WSL, wsl --install -d Ubuntu-24.04 gives you a second distribution side by side) or build Icarus Verilog 12 yourself.

Install the Toolchain: macOS

git comes from the Xcode Command Line Tools. Install Python by version, not as python3:

xcode-select --install
brew install python@3.13 icarus-verilogCode language: CSS (css)

Confirm with iverilog -V and python3.13 --version. Your interpreter is python3.13. Homebrew’s Icarus prints its version followed by a couple of Unable to get version from ... lines; those are harmless and do not mean the install is broken.

Note that brew install python3 installs the newest Python, which is normally too new for cocotb to use. That is why the command names a version. Homebrew’s versioned formulae also install only python3.13, so plain python3 still refers to the Python that shipped with macOS (3.9.6). Checking python3 --version here will mislead you in both directions; check python3.13 --version instead.

The command names 3.13 because cocotb publishes wheels for it. Any version cocotb supports works: substitute it in the brew install line and wherever python3.13 appears below.

Install the Toolchain: Native Windows

WSL is the smoother path and the section above is written for it, but the example runs natively too. You need three things: Python 3.13, a Windows build of Icarus Verilog with iverilog on your PATH, and Git for Windows for the git clone in the next section.

Do not use the big download button on python.org. It gives the newest release, which is normally too new for cocotb, exactly as described above. Follow “Looking for a specific release?” and pick 3.13.

Confirm with iverilog -V and py -3.13 --version. Your interpreter is py -3.13. If that prints No suitable Python runtime found, Python 3.13 is not installed; py --list shows which versions you do have. Python 3.13 is named because cocotb publishes wheels for it; any version cocotb supports works, with the py -3.13 commands changed to match.

make is not part of a standard Windows install, and even if you have one from MSYS2, Cygwin or a vendor toolchain such as ModusToolbox, the Makefile flow does not work on native Windows. Nothing in Run the PerfTest Example needs it.

Get the Example

Clone the repository and change into the example directory:

git clone https://github.com/opalkelly-opensource/cocotbext-frontpanel.git
cd cocotbext-frontpanel/exampleCode language: PHP (php)

Every command from here on runs in that directory.

Create and Activate the Virtual Environment

Use the interpreter your toolchain section gave you. The activation command differs by shell rather than by system.

Linux, or Windows via WSL:

python3 -m venv .venv
source .venv/bin/activate

macOS:

python3.13 -m venv .venv
source .venv/bin/activate

Windows PowerShell:

py -3.13 -m venv .venv
.venv\Scripts\Activate.ps1Code language: CSS (css)

On Windows, if activation fails and the message says the script cannot be loaded, that is a Windows setting, not the example. Python’s documentation recommends:

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSignedCode language: JavaScript (javascript)

Then activate again. This covers both of the usual messages, running scripts is disabled on this system and is not digitally signed.

If the setting is managed by your organisation, -Scope CurrentUser cannot override it and the command appears to do nothing. You do not need activation at all in that case: call the environment’s executables directly, from the same example directory, and skip the activation step wherever the rest of this page assumes it.

.venv\Scripts\python.exe test_perftest.py 3
.venv\Scripts\pytest.exe -sCode language: CSS (css)

Install the Packages

pip install -r requirements.txtCode language: CSS (css)

requirements.txt ships with the example you just cloned. It pins the exact package versions that release was tested with, including cocotbext-frontpanel itself, so the example code and the BFM always match. It also carries the --extra-index-url line pip needs to find the package.

cocotbext-frontpanel is a pre-built wheel served from our own package index, not from PyPI, which is what --extra-index-url points pip at. cocotb and pytest come from PyPI as usual. A name-reservation placeholder holds cocotbext-frontpanel on PyPI, so installing without --extra-index-url will not fetch a working package; the pinned requirements.txt above always resolves the real wheel from our index. We plan to publish the wheels to PyPI once the API has settled, at which point the extra index URL will no longer be needed.

Setup is done. The environment is active, and python runs the interpreter you installed. That is true on every system, which is why the next section needs no per-system commands, and it is also why the environment has to be active for python to exist at all on Linux.

Activate the environment in every new terminal. Without it the commands in the next section fail, and how they fail depends on your system. On Linux and WSL, python is not found at all, because Ubuntu and Debian provide only python3; running python3 test_perftest.py 3 instead reaches your system Python and fails with ModuleNotFoundError: No module named 'cocotb'. On Windows you get that ModuleNotFoundError directly. pytest behaves the same way: either the command is not found, or it starts up and then fails to import cocotb.

Optional: the Makefile

On Linux, WSL, and macOS, the example’s Makefile wraps the run commands: make 3 for one bandwidth mode, make for all seven, which takes a minute or two with no progress output beyond a banner per mode. It builds in a different directory than the commands in the next section do: the Makefile writes to sim_build_makefile_MODE3, with no underscore before the digit, while python test_perftest.py 3 and pytest write to sim_build_pytest_MODE_3. Look for the waveform under whichever one you ran. make clean removes both, so it also cleans up after the next section’s runs.

Activate the environment before running make. It finds cocotb through your PATH, so without activation it can pick up a different cocotb installed elsewhere on your machine and fail with a Verilog error, Unknown module type: okHost, which looks nothing like an environment problem.

The Makefile does not work on native Windows. Depending on which make you have, it either fails partway through with path errors, or exits reporting success while having done nothing at all. Use the commands in the next section instead; they work on every system. On Windows they leave their build directories behind, and Remove-Item -Recurse -Force sim_build_pytest_MODE_* clears them.

Optional: a Waveform Viewer

Each run writes a .fst, and surfer opens it. surfer is not packaged for any of these systems, so download it from https://surfer-project.org/ and use the build for your platform. On Windows and macOS it runs as an ordinary desktop application with no further setup. On WSL it needs a display, so use WSLg or an X server, or open the .fst from surfer running on Windows instead.

Run the PerfTest Example

Every command in this section is the same on Linux, WSL, macOS, and native Windows. Run them from the example directory with the virtual environment active, which is where Set Up the PerfTest Example leaves you. The design, its data patterns, and the behaviors the suite verifies are on the parent’s PerfTest Example and the PerfTest example page.

Step 1: Run a single bandwidth mode. The example runs in one of seven bandwidth modes, numbered 1 to 7, each a different okHost datapath width and clock frequency; the number on the command line picks one. A single mode is the quickest check:

python test_perftest.py 3Code language: CSS (css)

A passing run prints cocotb’s summary table near the end, whose last row reads:

** TESTS=14 PASS=14 FAIL=0 SKIP=0    <sim time>    <real time>    <ratio>  **Code language: HTML, XML (xml)

cocotb indents that table under its log column, so it appears far to the right rather than at the left margin. The run also prints the waveform file it wrote, as an FST info: dumpfile <path> opened for output. line; that <path> is what Step 3 opens.

Step 2: Run all seven bandwidth modes.

pytest -s

This takes roughly a minute, less on fast hardware. -s lets the simulation logs through instead of capturing them, so several thousand lines scroll past; that is the run working, not failing. The result is the last line:

========================= 7 passed in <elapsed> =========================Code language: HTML, XML (xml)

Use pytest -q instead if you only want the verdict.

Step 3 (optional): View the waveform. python test_perftest.py 3 puts its waveform at sim_build_pytest_MODE_3/perftest_top.fst, and prints that path in an FST info: dumpfile <path> opened for output. line. Open it with surfer, which the setup section covers installing; on WSL this needs a display, see Optional: a Waveform Viewer:

surfer sim_build_pytest_MODE_3/perftest_top.fst

See Also