Quickstart Guide

In the following sections, we are walking you through creating and running a small but complete cocotb testbench for a fictional Design Under Test (DUT) called my_design.

Please install the prerequisites and cocotb itself (pip install cocotb) now. Run cocotb-config --version in a terminal window to check that cocotb is correctly installed.

The code for the following example is available as examples/doc_examples/quickstart in the cocotb sources. You can also download the files here: my_design.sv, test_my_design.py, Makefile.

Creating a Test

A typical cocotb testbench requires no additional HDL code. The DUT is instantiated as the toplevel in the simulator without any HDL wrapper code.

The test is written in Python.

In cocotb, you can access all internals of your design, e.g. signals, ports, parameters, etc. through an object that is passed to each test. In the following we’ll call this object dut.

Let’s create a test file test_my_design.py containing the following:

# test_my_design.py (simple)

import cocotb
from cocotb.triggers import Timer


@cocotb.test()
async def my_first_test(dut):
    """Try accessing the design."""

    for cycle in range(10):
        dut.clk.value = 0
        await Timer(1, units="ns")
        dut.clk.value = 1
        await Timer(1, units="ns")

    dut._log.info("my_signal_1 is %s", dut.my_signal_1.value)
    assert dut.my_signal_2.value[0] == 0, "my_signal_2[0] is not 0!"


This will first drive 10 periods of a square wave clock onto a port clk of the toplevel. After this, the clock stops, the value of my_signal_1 is printed, and the value of index 0 of my_signal_2 is checked to be 0.

Things to note:

  • Use the @cocotb.test() decorator to mark the test function to be run.

  • Use .value = value to assign a value to a signal.

  • Use .value to get a signal’s current value.

The test shown is running sequentially, from start to end. Each await expression suspends execution of the test until whatever event the test is waiting for occurs and the simulator returns control back to cocotb (see Simulator Triggers).

It’s most likely that you will want to do several things “at the same time” however - think multiple always blocks in Verilog or process statements in VHDL. In cocotb, you might move the clock generation part of the example above into its own async function and start() it (“start it in the background”) from the test:

# test_my_design.py (extended)

import cocotb
from cocotb.triggers import FallingEdge, Timer


async def generate_clock(dut):
    """Generate clock pulses."""

    for cycle in range(10):
        dut.clk.value = 0
        await Timer(1, units="ns")
        dut.clk.value = 1
        await Timer(1, units="ns")


@cocotb.test()
async def my_second_test(dut):
    """Try accessing the design."""

    await cocotb.start(generate_clock(dut))  # run the clock "in the background"

    await Timer(5, units="ns")  # wait a bit
    await FallingEdge(dut.clk)  # wait for falling edge/"negedge"

    dut._log.info("my_signal_1 is %s", dut.my_signal_1.value)
    assert dut.my_signal_2.value[0] == 0, "my_signal_2[0] is not 0!"

Note that the generate_clock() function is not marked with @cocotb.test() since this is not a test on its own, just a helper function.

See the sections Concurrent and sequential execution and Coroutines and Tasks for more information on such concurrent processes.

Note

Since generating a clock is such a common task, cocotb provides a helper for it - cocotb.clock.Clock. No need to write your own clock generator!

You would start Clock with cocotb.start_soon(Clock(dut.clk, 1, units="ns").start()) near the top of your test, after importing it with from cocotb.clock import Clock.

Creating a Makefile

In order to run a test, you create a Makefile that contains information about your project (i.e. the specific DUT and test).

In the Makefile shown below we specify:

  • the default simulator to use (SIM),

  • the default language of the toplevel module or entity (TOPLEVEL_LANG, verilog in our case),

  • the design source files (VERILOG_SOURCES and VHDL_SOURCES),

  • the toplevel module or entity to instantiate (TOPLEVEL, my_design in our case),

  • and a Python module that contains our cocotb tests (MODULE. The file containing the test without the .py extension, test_my_design in our case).

# Makefile

# defaults
SIM ?= icarus
TOPLEVEL_LANG ?= verilog

VERILOG_SOURCES += $(PWD)/my_design.sv
# use VHDL_SOURCES for VHDL files

# TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file
TOPLEVEL = my_design

# MODULE is the basename of the Python test file
MODULE = test_my_design

# include cocotb's make rules to take care of the simulator setup
include $(shell cocotb-config --makefiles)/Makefile.sim

Running a Test

When you now type

make

Icarus Verilog will be used to simulate the Verilog implementation of the DUT because we defined these as the default values.

If you want to simulate the DUT with Siemens Questa instead, all you would need to change is the command line:

make SIM=questa

This concludes our quick introduction to cocotb. You can now look through our Tutorials or check out the Writing Testbenches chapter for more details on the above.