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
Please install the prerequisites
and cocotb itself (
pip install cocotb) now.
cocotb-config --version in a terminal window to check that cocotb is correctly installed.
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
Let’s create a test file
test_my_design.py containing the following:
# test_my_design.py import cocotb from cocotb.triggers import Timer @cocotb.test() async def my_first_test(dut): """Try accessing the design.""" dut._log.info("Running test...") for cycle in range(10): dut.clk <= 0 await Timer(1, units="ns") dut.clk <= 1 await Timer(1, units="ns") dut._log.info("my_signal_1 is", dut.my_signal_1.value) assert dut.my_signal_2.value == 0, "my_signal_2 is not 0!" dut._log.info("Running test...done")
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
my_signal_2 is checked to be
Things to note:
@cocotb.test()decorator to mark the test function to be run.
<=to assign a value to a signal (alternatively, use
.valueto get a signal’s current value.
The test shown is running sequentially, from start to end.
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 -
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
fork() it (“start it in the background”)
from the test:
# test_my_design.py (extended) import cocotb from cocotb.triggers import Timer from cocotb.triggers import FallingEdge async def generate_clock(dut): """Generate clock pulses.""" for cycle in range(10): dut.clk <= 0 await Timer(1, units="ns") dut.clk <= 1 await Timer(1, units="ns") @cocotb.test() async def my_second_test(dut): """Try accessing the design.""" dut._log.info("Running test...") cocotb.fork(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", dut.my_signal_1.value) assert dut.my_signal_2.value == 0, "my_signal_2 is not 0!" dut._log.info("Running test...done")
Note that the
generate_clock() function is not marked with
since this is not a test on its own, just a helper function.
See the sections Concurrent and sequential execution and Coroutines for more information on such concurrent processes.
Since generating a clock is such a common task, cocotb provides a helper for it -
No need to write your own clock generator!
You would start
cocotb.fork(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).
Makefile shown below we specify:
the default simulator to use (
the default language of the toplevel module or entity (
verilogin our case),
the design source files (
the toplevel module or entity to instantiate (
my_designin our case),
and a Python module that contains our cocotb tests (
MODULE. The file containing the test without the .py extension,
test_my_designin 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
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:
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.