-
cocotb is a coroutine-based co-simulation library for writing HDL test benches in Python. All you need is Python, gnu make and a hdl simulator.
-
A coroutine is a special type of function that can pause its execution (using await) and resume later. It allows for asynchronous programming, meaning the program can perform non-blocking waits and handle other tasks in the meantime, making it more efficient.
-
In Cocotb, coroutines are used to simulate hardware behavior over time. Hardware tests typically involve waiting for certain signals or clocks, and coroutines allow the test to wait for these events in an efficient way without blocking the entire program.
Line 2 and 3 will get clarified here-Or Verification Demystifed
Environment Set Up
Installing prerequisites & iverilog:
sudo apt-get install make python3 python3-pip libpython3-dev
sudo apt-get install iverilog
Creating Virtual Environment:
Possible Error & Fix:
python3 -m venv venv
This makes sure that the global Python installation isn't corrupted.
sudo apt-get install python3-venv
which python3
source venv/bin/activate
Installing necessary packages:
Possible Error & Fix: Install missing wheel package
pip3 install pytest cocotb cocotb-bus cocotb-coverage
pip3 install wheel
ls venv/lib/python3.6/site-packages/
packages sucessfully installed
Or Verification Example
Using Local Simulation:
git clone https://github.com/learn-cocotb/tutorial.git
cd tests/
make or
vi Makefile
SIM ?= icarus #simulator used,?= means "set the variable only if it is not defined."
TOPLEVEL_LANG ?= verilog
#declaring source files that will be compiled and simulated.
VERILOG_SOURCES += $(PWD)/../hdl/or_gate.v
VERILOG_SOURCES += $(PWD)/wrappers/or_test.v
#define make target
or:
rm -rf sim_build #clean build
$(MAKE) sim MODULE=or_test TOPLEVEL=or_test #python test file and top-level Verilog module
include $(shell cocotb-config --makefiles)/Makefile.sim #This line includes the default Cocotb Makefile,It is always declared at last as it has its make target which can run instead of 'or' as default 'make' takes the first target.
-
$(shell cocotb-config --makefiles): This calls the cocotb-config command, which knows where the Cocotb installation is located, and fetches the Makefile.sim file.
-
Makefile.sim: This is a core Cocotb file that contains all the common rules for running the simulation, compiling, and linking your test with Cocotb.
To exit vim editor - : -> q! enter , for more type vimtutor on terminal
Using Github actions:
Go to the actions tab and create a new workflow.
name: learning-cocotb
run-name: ${{ github.actor }} is learning Cocotb
on:
workflow_dispatch:
jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 3
permissions:
contents: write
checks: write
actions: write
steps:
- uses: actions/checkout@v3
- run: sudo apt install -y --no-install-recommends iverilog
- run: pip3 install cocotb cocotb-bus
- run: make -C tests or
- uses: actions/upload-artifact@v4
with:
name: waveform
path: tests/*.vcd
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always()
with:
report_paths: '**/tests/results.xml'
on: [push] - run every time you push
To run it manually use
on:
workflow_dispatch:
Waveform:
Download the artifact from Summary.
Gtkwave
gtkwave waveform.vcd
git clone https://github.com/learn-cocotb/assignment-xor.git
cd assignment-xor/
cd tests
vi dut_test.py
-
assert 0: Always fails, triggering an assertion failure. It can be used to deliberately fail a test.
-
assert 1: Always passes, so it doesn't trigger any assertion failure.
make
Or Verification Demystifed
Truth table-Expected Result/Functional Behaviour:
-
OR gate design here would be the DUT, and you'd write Python code to drive the OR gate inputs and check the outputs against expected results.
-
DUT here stands for Device Under Test. It refers to the specific piece of hardware that is being tested or simulated.
gedit hdl/or_gate.v
module or_gate(
input wire a,
input wire b,
output wire y
);
assign y=a|b; //DUT
endmodule
gedit tests/or_test.py
import cocotb
from cocotb.triggers import Timer, RisingEdge
@cocotb.test() #this decorator pulls test-related features
async def or_test(dut):
a = (0, 0, 1, 1) #tuples are immutable-here values are predefined and won't change during the test
b = (0, 1, 0, 1)
y = (0, 1, 1, 1)
for i in range(4): #0 to 3
dut.a.value = a[i]
dut.b.value = b[i]
await Timer(1, 'ns')
assert dut.y.value == y[i], f"Error at iteration {i}"
-
The Timer trigger pauses the execution for a specified amount of time, simulating delays or waiting for a certain duration within your test.
-
RisingEdge waits for a signal to transition from low to high (0 to 1). It’s useful when synchronizing your test with clock edges or specific events in the DUT. We are not using it here.Similarly FallingEdge and Edge can also be used.
-
A decorator is defined as a function that takes another function as an argument and returns a new function that usually extends or alters the behavior of the original function.
def check(func):
def inside(a, b):
if b == 0:
print("Can't divide by 0")
return
func(a,b)
return inside
@check
def div(a, b):
return a / b
#div=check(div)
print(div(10, 0))
-
@cocotb.test(): This is a decorator that marks the function or_test as a Cocotb test. Cocotb will automatically recognize this function and execute it as part of the test suite.
-
async def or_test(dut): This defines the test function as an asynchronous coroutine (async), which allows you to use await inside the function for non-blocking waits.
-
dut.a.value = a[i]: This sets the value of the a input of the DUT to the corresponding value in the a tuple for each iteration of the loop.In python even dut.a <= a[i] works.
-
Delta delay is a concept in digital simulation that represents an infinitesimally small unit of time. It is a way to model events that happen at the same simulation time but need to be evaluated in a specific order. In hardware simulations, even though two events might appear to happen "simultaneously" in real-time, the simulator needs to process them in some order. The simulator introduces delta cycles to sequence events that happen at the same time step.
-
await Timer(1, 'ns'): This pauses the execution of the test for 1 nanosecond. It allows time for the DUT to process the input changes and update the output. The await keyword ensures the test waits for the specified time in a non-blocking way.Similarly await Edge(dut.clk) can also be used.
-
Non-blocking operations allow the testbench to simulate these events efficiently, where multiple parts of the hardware can be tested or driven independently without being blocked by one another.
async def test1(dut):
await Timer(10, 'ns')
print("Test 1 done")
async def test2(dut):
await Timer(5, 'ns')
print("Test 2 done")
-
Both test1 and test2 will start running, and while test1 is waiting for 10 ns, test2 can complete after 5 ns without waiting for test1 to finish. This is non-blocking behavior, as the simulation doesn’t get held up by one test waiting.
-
assert dut.y.value == y[i]: This checks if the actual output of the DUT (dut.y.value) matches the expected output from the y tuple for the current iteration. If the assertion fails, the message is displayed, showing which iteration of the test failed.
cd tests/
gedit Makefile
SIM ?= icarus
TOPLEVEL_LANG ?= verilog
VERILOG_SOURCES += $(PWD)/../hdl/or_gate.v
or:
rm -rf sim_build
$(MAKE) sim MODULE=or_test TOPLEVEL=or_test
include $(shell cocotb-config --makefiles)/Makefile.sim
Check for assertion failure:
gedit ../hdl/or_gate.v
module or_gate(
input wire a,
input wire b,
output wire y
);
assign y=a^b; //Xor - must show an error for 1 1 - 0 3rd iteration
endmodule
Create a wrapper for RTL code:
- A wrapper is used to add functionality or integrate RTL code without modifying it directly
- Change xor to or in hdl/or_gate.v
gedit wrappers/or_test.v
module or_test(
input wire a,
input wire b,
output wire y
);
or_gate or_gate(
.a(a),
.b(b),
.y(y)
); //call the instance
initial begin
$dumpfile("orwaves.vcd"); // specifies the name of the VCD (Value Change Dump) file where simulation waveforms will be saved.
$dumpvars; // dumping all variables to the VCD file.
end
endmodule
Other variants of dumpvars:
- $dumpvars: Dumps all variables in the current scope.
- $dumpvars(level): Dumps variables with the specified level of hierarchy. For example, $dumpvars(0) dumps all variables at the top level, while $dumpvars(1) includes variables from one level below, and so on.
- $dumpvars(level, instance): Dumps variables from a specific instance up to the specified level. For example, - $dumpvars(1, or_gate) would dump variables within the or_gate module and one level below.
- $dumpvars(level, instance, var): Dumps specific variables from a particular instance and level. This allows for fine-grained control over which signals are dumped.
gedit Makefile
SIM ?= icarus
TOPLEVEL_LANG ?= verilog
VERILOG_SOURCES += $(PWD)/../hdl/or_gate.v
VERILOG_SOURCES += $(PWD)/wrappers/or_test.v
or:
rm -rf sim_build
$(MAKE) sim MODULE=or_test TOPLEVEL=or_test #modified top level
include $(shell cocotb-config --makefiles)/Makefile.sim
Waveform:
gtkwave orwaves.vcd
Xor Verification
module xor_gate(
input wire a,
input wire b,
output wire y
);
assign y=a^b;
endmodule
module xor_test(
input wire a,
input wire b,
output wire y
);
xor_gate xg(.a(a),.b(b),.y(y));
initial begin
$dumpfile("xorwaves.vcd");
$dumpvars;
end
endmodule
import cocotb
from cocotb.triggers import Timer,RisingEdge
@cocotb.test()
async def xor_test(dut):
a = (0,0,1,1)
b = (0,1,0,1)
y = (0,1,1,0)
for i in range(4):
dut.a.value = a[i]
dut.b.value = b[i]
await Timer(1,'ns')
assert dut.y.value==y[i],f"Error at iteration {i}"
SIM ?= icarus
TOPLEVEL_LANG ?= verilog
TARGET ?= xor
VERILOG_SOURCES += $(PWD)/../hdl/or_gate.v
VERILOG_SOURCES += $(PWD)/wrappers/or_test.v
VERILOG_SOURCES += $(PWD)/../hdl/xor_gate.v
VERILOG_SOURCES += $(PWD)/wrappers/xor_test.v
or:
rm -rf sim_build
$(MAKE) sim MODULE=or_test TOPLEVEL=or_test #modified top level
xor:
rm -rf sim_build
$(MAKE) sim MODULE=xor_test TOPLEVEL=xor_test
run: $(TARGET)
include $(shell cocotb-config --makefiles)/Makefile.sim
make run
Waveform: