The cocotb project is proud to announce the immediate release of its new version 1.6.0. cocotb is a COroutine based COsimulation TestBench environment for verifying VHDL/Verilog RTL using Python.
Cocotb can be installed and updated from PyPi through pip:
python3 -m pip install --upgrade cocotb
For full installation instructions refer to the documentation at https://docs.cocotb.org/en/v1.6.0/install.html.
This release concludes a seven month development period. Instead of going directly to cocotb 2.0, we threw in another backwards-compatible release in the 1.x release series with a number of exciting new features, and deprecations for functionality we will change in cocotb 2.0.
All users of cocotb are strongly encouraged to look through the deprecations when running their tests and future-proof their testbenches as time permits!
Read on for some of the highlights in this release.
New ways to schedule coroutines
Coroutines are at the heart of cocotb – it’s even in our name!
Since early on coroutines could be scheduled to run concurrently with
That still works – but now there’s a better way as well, and we encourage you to update your code.
In cocotb 1.6, we’re introducing a new way of scheduling coroutines using
Additionally, we’re introducing two less commonly used functions,
await cocotb.start(), and
Before we go into the details, the quick porting advice is:
In most cases, you can simply replace
To understand the differences between the various ways of scheduling a coroutine we need to first recap the basics behind them.
A coroutine function, in cocotb, is a Python function declared with
Coroutines can be executed in parallel – in theory.
In practice, there’s only one coroutine running at any given time.
So we need to choose which one gets to execute.
Two potential solutions come to mind: we could, option one, introduce a “moderator” which allows, for example, a first task to execute for a bit, and then lets a second one execute, and so on. Or, option two, we could teach tasks (at moments they find suitable) to ask around if other tasks would like to do some work as well, and pass over control to them. Coroutines (not only in cocotb) implement the second concept: be nice to each other, and give up control when no work is left for the moment.
In cocotb, you use the
await statement to yield control, that is, give up control and indicate that another coroutine may now execute.
The execution resumes in the line after the
await statement once the event specified after
await has happened.
Schedule a coroutine using cocotb.start_soon()
That’s a lot of introduction: let’s get back to
What’s common to all these three functions is that they schedule a coroutine, which only means: these functions let cocotb know that a coroutine exists, and that it would like to run at some point.
The cocotb scheduler will then take care of the rest.
What distinguishes the three functions is what they do in addition to scheduling the given coroutine.
cocotb.start_soon() the answer is easy: nothing.
A coroutine is only introduced to the scheduler, which will hand over control to it as soon as another coroutine gives up control.
Let’s have a look at the figure above to understand how the scheduling could look.
coro3() has been scheduled earlier and is ready to execute.
coro2() is scheduled by
Since we’re using
coro2() is only scheduled, but not executed until
coro1() yields control using an
At this point, also
coro3() will execute, and ultimately, control will return to
Schedule and run coroutines using await cocotb.start()
Things looks slightly different when using
cocotb.start() schedules a coroutine, and immediately yields control to let any other coroutine execute.
cocotb.start() yields control, it needs to be used in conjunction with
As shown in the figure, once
coro1() yields control, both
Only then control returns to
You can think of
await cocotb.start() as a combination of
await NullTrigger(), which is a trigger that immediately fires.
In almost all cases you won’t need the immediate trigger that
await cocotb.start() provides, and you can use
The old way: cocotb.fork()
Finally, there’s the third and “old” way of scheduling a coroutine:
cocotb.fork() schedules the coroutine, executes only this coroutine until the next
await statement, and then returns to the caller.
In the figure above,
coro1() forks off
coro2(), which then executes until the next
Then execution returns to
coro1() and it executes until it yields control in an
Only then can all other coroutines, e.g.
coro3() do their work.
cocotb.fork() because it has slightly odd behavior (by today’s standards, of course):
cocotb.fork()gives up control, but isn’t a async function which needs an
awaitin front of it.
cocotb.fork()yields control “only a bit” to let exactly one other coroutine execute, but in a way which is rather unique.
- And finally there are a several other corner cases in error handling which are very hard or impossible to get right with
cocotb.fork(), and which many users have painfully experienced over the years.
cocotb.fork() is deprecated, but it will remain available in cocotb 1.x.
We encourage you to migrate your testbenches to the new scheduling routines, most likely by simply replacing
Have a look at PR #2713 for how we did the conversion in cocotb itself.
If you’d like to know more about coroutines and scheduling in cocotb have a look at the Coroutines and Tasks section in the documentation (which also explains the
cocotb.create_task() function, which we skipped here).
They are your type: HDL datatypes
Datatypes are behind every line of code in your editor.
When you write
logic [3:0] my_signal in SystemVerilog datatypes are clearly visible.
But datatypes are there even if you don’t explicitly write them down, as in
my_signal <= -2 in cocotb.
Right now, cocotb is fairly relaxed about HDL datatypes, and freely converts between them as needed mostly through its
BinaryValue class (which is behind the
my_signal assignment seen before).
However, being relaxed isn’t always helpful, as some cocotb users have found out: conversions can go wrong, and expressiveness can be be lost.
Starting with cocotb 1.6, we’re introducing datatypes which model common HDL data types:
Logic for four-state logic values,
Bit for two-state logic,
LogicArray for HDL-style arrays or vectors.
All these new types have been carefully designed to feel natural for both VHDL and SystemVerilog programmers, and to provide type safety and useful typecasts.
As of now these datatypes can be used in custom testbench code. Going forward, we plan to bake these types more deeply into cocotb, and make signal handles return them.
Assigning values to signals using
<= is deprecated
One of the unique, but also slightly obscure, features of cocotb is on its way out:
Setting values on handles using the
my_signal <= newval syntax is now deprecated.
my_signal.value = newval instead.
We didn’t take this decision lightly (it’s more typing, after all!), but there are good reasons for it.
- First of all, the syntax doesn’t always work (and there’s no way of fixing it).
Quiz: What do you think
a <= b if c else ddoes? (Answer)
- It’s not obvious what
<=does for someone who knows Python. We got asked many times, and every presentation introducing cocotb has to explain it. It’s even hard to google for it!
<=provides more than one way of doing things, which is bad for teaching and learning cocotb.
So if you have a spare moment, open the search-replace dialog of your favorite editor, and get rid of a couple
GPI +- cocotb
Cocotb does a lot of hard work to provide a common interface between simulators and your Python testbench. Within cocotb, this abstraction layer is called GPI. So how about a crazy idea: you want to write your own cocotb-like framework in Python, but don’t want to do the hard work of creating an abstracted interface to all those simulators out there? You can do that now by providing a custom entry point, which is a Python module which implements a couple functions. This feature is experimental and isn’t guaranteed to stay this way going forward, but that shouldn’t discourage you from giving it a try (but don’t get too excited just yet and build your whole infrastructure around it!).
Build system improvements
A staple of cocotb releases are improvements to its testbench build system. This time around, key improvements are:
- ModelSim, Questa, and Xcelium now support compilation into a named VHDL library lib using
- A new
SIM_CMD_PREFIXMakefile variable can be used to prefix simulation commands. This feature is helpful to run commands in GDB, add a library using
LD_PRELOAD, or run commands through a job scheduler.
A much more detailed description of all changes in this first 1.6 release can be found in the release notes.
Cocotb is a community project under the umbrella of the FOSSi Foundation. Thanks to all sponsors of the FOSSi Foundation the cocotb project can make use of a continuous integration system which runs proprietary simulators in a compliant way.
We are very thankful to Aldec for providing a license for Riviera-PRO, and to Siemens EDA for providing a license for Questa, which enables us to continuously test cocotb against these simulators to ensure they continue to be well integrated.
Please reach out to Philipp at firstname.lastname@example.org if you or your company want to support cocotb, either financially to help pay for costs such as running our continuous integration setup, or with in-kind donations, such as simulator licenses.
To close this rather long release announcement, here are some statistics:
326 files changed, 9694 insertions(+), 14361 deletions(-) 153 commits 15 code contributors
These numbers are impressive and showcase the power of the free and open source collaboration model. To be able to sustain this amount of change and the high-quality review process behind it we are happy to have an active group of maintainers caring for cocotb and its users. Thank you, and thanks to the whole cocotb community for coming together to create this unique piece of software.
And now, go and enjoy the best release of cocotb so far!
If you have questions or issues with this release head over to the issue tracker.