Testbenches and cocotb
What a testbench is, how a cocotb test is built, and the pattern every test in this lab follows.
On this page
cocotbThe shape of a testLine by lineWhy the falling edgeSigned valuesA good assert messageWhat the lab allows in a testA testbench is code that drives a design's inputs and checks its outputs. It is not part of the chip. It exists to find mistakes before the design becomes hardware.
cocotb#
In this lab a testbench is a Python file, run by cocotb. Your design runs inside the Icarus Verilog simulator; cocotb lets Python set its inputs, wait for clock edges and read its outputs.
Python is a good fit: working out the expected answer is ordinary programming.
The shape of a test#
This is the test from the Blank starter, complete.
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import FallingEdge
@cocotb.test()
async def test_top(dut):
"""q follows d one clock later, and reset clears it."""
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
dut.d.value = 0xFF
dut.rst_n.value = 0
await FallingEdge(dut.clk)
await FallingEdge(dut.clk)
assert int(dut.q.value) == 0, f"q expected 0 in reset, got {int(dut.q.value)}"
dut.rst_n.value = 1
for value in (0x01, 0x80, 0x5A, 0x00, 0xFF):
dut.d.value = value
await FallingEdge(dut.clk)
assert int(dut.q.value) == value, f"q expected {value}, got {int(dut.q.value)}"Line by line#
| Line | What it does |
|---|---|
@cocotb.test() | Marks the function as a test |
async def test_top(dut) | dut is your design. dut.q is its port q |
Clock(dut.clk, 10, unit="ns") | A 10 ns clock on clk |
dut.d.value = 0xFF | Drive an input |
await FallingEdge(dut.clk) | Wait for the clock to fall |
int(dut.q.value) | Read an output as a number |
assert ..., "message" | A check. If it is false the test fails and prints the message |
Why the falling edge#
The design changes its outputs on the rising edge. If the test drove inputs and read outputs at that same instant, it would race the design. Driving and reading on the falling edge puts the test half a period away from the action.
Signed values#
int(dut.acc.value) reads the bits as an unsigned number. For a signed output use dut.acc.value.to_signed().
A good assert message#
Write the message for the person who will see it fail: the inputs, what was expected, what was seen.
assert got == want, f"subtract {a}, {b}: expected {want}, saw {got}"What the lab allows in a test#
A test may import cocotb, cocotb.clock, cocotb.triggers and random, and nothing else. It may not open files, start processes or use the network.
Something unclear or missing? Send feedbackSign in and use Feedback to tell us.