Odds and Ends

Lego Blocks

I hope that the Python for Verification Series has demonstrated that Python is a new tool in the verification team’s toolbox. We can now write complete UVM testbenches in Python using pyuvm and connect them to the simulator using cocotb. Python as a verification language explodes the ecosystem at a verification engineer’s fingertips while making it easier to hire engineers who know the team’s language (Python) immediately.

This final blog post in the series discusses odds and ends that may have gotten little attention in the blog posts or may not have been invented when the blog post was written.

Python and SystemVerilog

Verification falls into a deeply committed relationship with every language it meets. You can look across the verification landscape and find old friends such as VHDL, Verilog, SystemC, e, and C++. Our industry will likely add Python to that list, using it on projects where it does the most good.

SystemVerilog is still the best way to define complicated randomization constraints and functional coverage bins. Python needs to take advantage of what SystemVerilog has to offer.

Siemens EDA has a skunkworks project named SVConduit that allows engineers to define objects using YAML and then pass those objects between SystemVerilog and Python using put() and get() operations. Python uses get() to retrieve a randomized transaction and put() to store functional coverage.

SVConduit is an example of how SystemVerilog will remain relevant in the future. Contact your Siemens EDA AE if you’d like the SVConduit source code.

pyuvm changes to run_test()

pyuvm changes the run_test() task in several ways, starting with the fact that you use the await keyword to start your test.

await uvm_root().run_test("MyTest")

Passing classes to run_test()

The string in the above call raises another change. pyuvm slavishly copied the SystemVerilog UVM’s use of a string to hold the test name. There is no need to do that in Python as we can pass the class directly. This also enables error checking for misspellings. So now we can do this without the string:

await uvm_root().run_test(MyTest)

pyuvm does not need uvm_test

Python has no typing, so pyuvm does not need uvm_test, though it implements it to follow the standard. Here is the uvm_test implementation. It is an empty extension:

# 13.2 
class uvm_test(uvm_component):
    """
        The base class for all tests
    """

This means we could pass a uvm_component class to run_test(), and it would work with no problem.

Keeping singletons

The SystemVerilog UVM built and ran a single UVM testbench per simulation run. cocotb allows you to store many tests in one Python file and run a different pyuvm test in each of them.

This feature was broken before version 2.6 because the singletons in one test carried over to the next test. For example, the ConfigDB() would keep data from the previous test.

Starting in 2.6, run_test() clears all singletons except the uvm_factory(). It clears the overrides in uvm_factory(). You can keep the singletons by using the keep_singletonsargument:

uvm_root().run_test(keep_singletons=True)

This argument will also keep the existing factory overrides. You can clear them explicitly using uvm_factory().clear_overrides().

If you want to keep data between calls to run_test() you store it in one or more singletons, and tell run_test() to keep them using keep_set. For example, you may keep persistent data in a singleton class named GlobalData

uvm_root().run_test(MyTest, keep_set=set([GlobalData])

This will clear all the singletons but GlobalData

Enabling deep UVM log messages

The Python logging module defines logging levels. A message is more likely to be logged if its logging level is high. The default logging levels range from CRITICAL (50) to DEBUG (10).

pyuvm adds two logging levels below DEBUG: FIFO_DEBUG (5) and PYUVM_DEBUG (4). Setting a FIFO’s logging level to PYUVM will log all the activity in the FIFO. It is useful if your testbench is hanging.

The PYUVM_DEBUG level will become more verbose over time as there is more need to print UVM debug messages.

Changing the default logging level

pyuvm sets logger objects to the INFO logging level by default. You can change that default before awaiting run_test():

uvm_report_object.set_default_logging_level(DEBUG)
uvm_root().run_test(MyTest)

pyuvm does not need uvm_subscriber

The SystemVerilog UVM provides the uvm_subscriber class as a convenience class. uvm_subscriber creates an analysis_export with the correct parameterized type and links it to the write() function. One could code this manually, and one does to have multiple analysis_export objects in a single subscriber, such as a scoreboard.

Python doesn’t have typing issues, so a programmer can create a subscriber by directly extending uvm_analysis_export and providing the write() function.

This is the same procedure as using uvm_subscriber, except you can pass an object that extends analysis_export directly to the connect() function.

Here is an example. We define Adder by extending uvm_analysis_export and instantiate it in the test. Then we pass the instantiated adder (self.sum) directly to the ap.connect() function:

class Adder(uvm_analysis_export):
    def start_of_simulation_phase(self):
        self.sum = 0

    def write(self, nn):
        self.sum += nn

    def report_phase(self):
        self.logger.info(f"Sum: {self.sum}")


class AdderTest(uvm_test):
    def build_phase(self):
        self.num_gen = NumberGenerator("num_gen", self)
        self.sum = Adder("sum", self)

    def connect_phase(self):
        self.num_gen.ap.connect(self.sum)

Summary

This blog post examined how pyuvm is different from the SystemVerilog UVM. I hope it becomes a valuable tool in the verification engineer’s toolbox.

Leave a Reply