cocotb (short for "coroutine cosimulation testbench") is a Python library for testing digital hardware designs. Instead of writing testbenches in Verilog/VHDL, you can write them in Python, making hardware verification more accessible and powerful.
- Linux - (https://cloud.google.com/edu/faculty)
- Python 3.8+ with pip and venv
- cocotb[bus] (main library + bus interfaces)
- Icarus Verilog (free HDL simulator)
- GTKWave (waveform viewer)
- Build tools (GCC, make)
sudo apt update
sudo apt install python3-venv python3-pip iverilog git gtkwave# Create the virtual environment
python3 -m venv cocotb-env
# Activate it (you'll need to do this every time you work on cocotb projects)
source cocotb-env/bin/activate
# Your prompt should now show (cocotb-env) at the beginningpip install cocotb[bus]cd dffCreate a file called dff.v:
nano dff.vCopy and paste this code:
// dff.v - Simple D Flip-Flop
module dff (
input clk, // Clock signal
input d, // Data input
input rst, // Reset signal
output reg q // Data output
);
always @(posedge clk or posedge rst) begin
if (rst)
q <= 1'b0; // Reset output to 0
else
q <= d; // Pass input to output on clock edge
end
endmoduleSave and exit (Ctrl+X, then Y, then Enter).
Create a file called test_dff.py:
nano test_dff.pyCopy and paste this code:
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import ClockCycles, RisingEdge
import random
@cocotb.test()
async def test_dff_reset(dut):
"""Test that reset works correctly"""
# Start a clock (1 MHz)
cocotb.start_soon(Clock(dut.clk, 1, units="us").start())
# Apply reset
dut.rst.value = 1
dut.d.value = 1 # Try to set input high
await ClockCycles(dut.clk, 2) # Wait 2 clock cycles
# Check that output is still 0 (reset should override input)
assert dut.q.value == 0, f"Reset failed: expected Q=0, got Q={dut.q.value}"
print("✓ Reset test passed!")
@cocotb.test()
async def test_dff_basic_operation(dut):
"""Test basic D flip-flop functionality"""
# Start the clock
cocotb.start_soon(Clock(dut.clk, 1, units="us").start())
# Release reset
dut.rst.value = 0
dut.d.value = 0
await ClockCycles(dut.clk, 1)
# Test 1: Set D=1, check Q becomes 1 after clock edge
dut.d.value = 1
await RisingEdge(dut.clk) # Wait for rising edge
await ClockCycles(dut.clk, 1) # Wait one more cycle for output to settle
assert dut.q.value == 1, f"Expected Q=1, got Q={dut.q.value}"
print("✓ D=1 → Q=1 test passed!")
# Test 2: Set D=0, check Q becomes 0 after clock edge
dut.d.value = 0
await RisingEdge(dut.clk)
await ClockCycles(dut.clk, 1)
assert dut.q.value == 0, f"Expected Q=0, got Q={dut.q.value}"
print("✓ D=0 → Q=0 test passed!")
@cocotb.test()
async def test_dff_random_data(dut):
"""Test with random data patterns"""
# Start the clock
cocotb.start_soon(Clock(dut.clk, 1, units="us").start())
# Release reset
dut.rst.value = 0
await ClockCycles(dut.clk, 1)
# Test with 10 random values
for i in range(10):
test_value = random.randint(0, 1)
dut.d.value = test_value
await RisingEdge(dut.clk)
await ClockCycles(dut.clk, 1)
assert dut.q.value == test_value, f"Test {i}: Expected Q={test_value}, got Q={dut.q.value}"
print("✓ Random data test passed!")Save and exit.
Create a file called Makefile:
nano MakefileCopy and paste this code:
# Makefile for cocotb DFF example
# Simulator to use (icarus verilog)
SIM ?= icarus
# Language of the top-level module
TOPLEVEL_LANG ?= verilog
# Verilog source files
VERILOG_SOURCES += dff.v
# Top-level module name (must match module name in dff.v)
TOPLEVEL = dff
# Python test module (without .py extension)
MODULE = test_dff
# Include cocotb's makefile
include $(shell cocotb-config --makefiles)/Makefile.simSave and exit.
ls -laYou should see:
dff.v
test_dff.py
Makefile
makeIf everything works correctly, you'll see output like:
Running cocotb tests...
0.00ns INFO gpi ...: Using simulator icarus
0.00ns INFO cocotb ...: Running test test_dff_reset...
✓ Reset test passed!
0.00ns INFO cocotb ...: Test passed
0.00ns INFO cocotb ...: Running test test_dff_basic_operation...
✓ D=1 → Q=1 test passed!
✓ D=0 → Q=0 test passed!
0.00ns INFO cocotb ...: Test passed
0.00ns INFO cocotb ...: Running test test_dff_random_data...
✓ Random data test passed!
0.00ns INFO cocotb ...: Test passed
All tests passed!
- Created a simple D flip-flop that stores the input
don each clock rising edge - Has a reset feature that sets output to 0 when
rstis high
- Clock generation:
Clock(dut.clk, 1, units="us")creates a 1 MHz clock - Async/await: cocotb uses Python's async features for timing
- Signal access:
dut.d.value = 1sets the input signal - Timing control:
await RisingEdge(dut.clk)waits for clock edge - Assertions:
assertstatements verify correct behavior
- Tells cocotb which simulator to use (
icarus) - Specifies the Verilog files and top-level module
- Points to the Python test module
To see signal waveforms, modify your Makefile to add:
# Add this line to generate waveforms
WAVES = 1Then run make again and open the generated .vcd file with GTKWave:
gtkwave dump.vcd- Change the clock frequency
- Add more test cases
- Test edge cases (what happens if you change
dbetween clock edges?)
Try creating a counter, shift register, or simple state machine.
Solution: Make sure your virtual environment is activated:
source cocotb-env/bin/activateSolution: Install cocotb in your virtual environment:
pip install cocotb[bus]Solution: Install Icarus Verilog:
sudo apt install iverilogSolution: Add more ClockCycles delays in your testbench to ensure signals settle.
- Learn more cocotb features: Explore drivers, monitors, and scoreboards
- Try different simulators: Questa, VCS, or GHDL for VHDL
- Build larger designs: Create and test more complex digital circuits
- Explore cocotb-bus: Use pre-built bus interfaces (AXI, Avalon, etc.)
cd ~/cocotb-tutorial/dff_example
source ../cocotb-env/bin/activate
make@cocotb.test(): Marks a function as a testawait: Waits for simulation eventsdut: Device Under Test (your hardware module)ClockCycles(clk, n): Wait n clock cyclesRisingEdge(signal): Wait for signal's rising edge