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