Creating a simple Cocotb Testbench


SiliconNotes

Read on web →

In this edition of the newsletter, I will continue discussing the use of Python for Design Verification.

In the last newsletter, we created a Cocotb-based testbench to verify a simple D flip-flop RTL, as shown:

// _Dff_
module dff (
input logic clk,
input logic d,
output logic q
);

always_ff @(posedge clk) begin
q <= d;
end

endmodule

The above RTL implements a standard D-ff using the d pin as the input port and the q pin as the output. This is implemented using the always_ff block using the non-blocking assignments.

And this is what we had for our python based testbench using Cocotb:

# test_dff.py
import random

import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge

@cocotb.test()
asyncdef dff_simple_test(dut):
"""Test that d propagates to q"""# Set initial input value to prevent it from floating
dut.d.value = 0

clock = Clock(dut.clk, 10, units="us") # Create a 10us period clock on port clk# Start the clock. Start it low to avoid issues on the first RisingEdge
cocotb.start_soon(clock.start(start_high=False))

# Synchronize with the clock. This will regisiter the initial `d` valueawait RisingEdge(dut.clk)
expected_val = 0 # Matches initial input valuefor i in range(10):
val = random.randint(0, 1)

Alright, so there are multiple things which have happened above. We've imported bunch of cocotb classes and the python random class and have also created a async def called dff_simple_test decorated with @cocotb.test(). While using cocotb, a simple python function can be treated as a test. In order to do so, we just need to add the @cocotb.test() decorator for the function. That would allow cocotb to treat that function as a test and would be automatically run when the simulation starts. The next thing to note is that the function takes dut object as an argument. In cocotb the dut object basically consists the handle to the top module of the design (or testbench) and could be used to either sample signals from the design or drive signals. That is what is achieved when we drive the d input pin to a value of 0. Since we have already imported the cocotb.clock python class, we create the clock with 10us period. However, this just creates the clock but doesn't start it and hence needs to be called using the following:

clock = Clock(dut.clk, 10, units="us") # Create a 10us period clock on port clk
# Start the clock. Start it low to avoid issues on the first RisingEdge
cocotb.start_soon(clock.start(start_high=False))

Having set the initial value to the d pin and starting the clock, we could wait for the rising edge of the clock and then start driving inputs. Since we have already imported the random library, we use the randint between 0 and 1 to drive random input values to the d pin (as the d pin is only a single bit signal). This is done for 10 clock cycles by using a for loop. Note that once the input is driven the await statement is added to allow the clock to tick to the next rising edge of the cycle. This makes sure that the for loop drives all the input on the rising edge of the clock. Here's the look at the complete testbench code:

# test_dff.py
import random
import cocotb from cocotb.clock import Clock from cocotb.triggers import RisingEdge
@cocotb.test()
async def dff_simple_test(dut):
“"”Test that d propagates to q”””
# Set initial input value to prevent it from floating
dut.d.value = 0

clock = Clock(dut.clk, 10, units="us") # Create a 10us period clock on port clk
# Start the clock. Start it low to avoid issues on the first RisingEdge
cocotb.start_soon(clock.start(start_high=False))

# Synchronize with the clock. This will regisiter the initial `d` value
await RisingEdge(dut.clk)

for i in range(10):
val = random.randint(0, 1)
dut.d.value = val # Assign the random value val to the input port d
await RisingEdge(dut.clk)

# Check the final input on the next clock
await RisingEdge(dut.clk)

Great, we now have the RTL design and the Testbench coded. The next thing is to compile both together and let cocotb simulate the design along with the testbench. For this, cocotb already provides a set of makefiles which are created for various simulators and can be used for managing the infra on how the design and testbench are simulated.

We will take a look at the Makefile structure for simulating this in next week’s edition.


Well, that's it for this edition of the SiliconNotes.

Have a great week!
Rahul

#103 Sector D, Jammu, 180011
Update your email preferences or unsubscribe here