The UVM Factory

In the previous post in the Python for Verification Series, we discussed how pyuvm implemented the configuration database as a singleton class named ConfigDB(). In this post, we’ll examine the UVM factory.

The pyuvm implements the UVM factory as it is described in the specification, removing elements that complicated the factory because of SystemVerilog typing.

Instantiating objects using the factory

SystemVerilog UVM developers have a choice of whether they instantiate objects using the new() function, or whether they use the long factory incantation that delivers an object that can be overridden. pyuvm provides the same options.

We have the following component that logs a tiny message:

class TinyComponent(uvm_component):
    async def run_phase(self):
        self.raise_objection()
        self.logger.info("I'm so tiny!")
        self.drop_objection()

Notice that we don’t have a uvm_component_utils() macro that registers the class with the factory. In pyuvm, all classes that extend uvm_void are all registered with the factory.

You can instantiate this component directly like this:

class TinyTest(uvm_test):
    def build_phase(self):
        self.tc = TinyComponent("tc", self)

Or, you can instantiate the component using the create() class method:

class TinyFactoryTest(uvm_test):
    def build_phase(self):
        self.tc = TinyComponent.create("tc", self)
--
1000.00ns INFO     testbench.py(15)[uvm_test_top.tc]: I'm so tiny!

Notice that you do not put parenthesis after the class name when you call a class method. This would create an instance of the object and then call the class method using the instance.

Thanks to the lack of typing in Python, the create() call in Python is much easier to use than the baroque incantation used to call create() in SystemVerilog.

Now we can override our component.

Overriding classes

We override types in pyuvm by using the uvm_factory() singleton object. The uvm_factory() has four overriding methods. Note that what SystemVerilog calls a type, we Python programmers call a class. Still, the specification uses the word type in these methods:

  • set_type_override_by_type(<original class>, <overriding class>)
  • set_type_override_by_name(<"original class name">, <“overriding class name”>)
  • set_inst_override_by_type(<original class>, <overriding class>, <"UVM hierarchy path">)
  • set_inst_override_by_name(<"original class name">, <“overriding class name”>, <"UVM hierarchy path)

Here is an example of creating a component and using it in an override:

class MediumComponent(uvm_component):
    async def run_phase(self):
        self.raise_objection()
        self.logger.info("I'm medium size.")
        self.drop_objection()

Now we create a test that overrides TinyComponent with MediumComponent:

class MediumFactoryTest(TinyFactoryTest):
    def build_phase(self):
        uvm_factory().set_type_override_by_type(
            TinyComponent, MediumComponent)
        super().build_phase()
--
2000.00ns INFO     testbench.py(44)[uvm_test_top.tc]: I'm medium size.

Notice that Python classes are objects just like everything else, so we can pass them to set_type_override_by_type using the simple class names. There is no need for the get_type() method that we see in SystemVerilog.

We can use an environment to demonstrate instance overrides:

class TwoCompEnv(uvm_env):
    def build_phase(self):
        self.tc1 = TinyComponent.create("tc1", self)
        self.tc2 = TinyComponent.create("tc2", self)

Now we have two components, but only want to override tc1. Notice that in this example we use set_inst_override_by_name so we pass strings containing the names of the classes:

class TwoCompTest(uvm_test):
    def build_phase(self):
        uvm_factory().set_inst_override_by_name(
            "TinyComponent", "MediumComponent", "uvm_test_top.env.tc1")
        self.env = TwoCompEnv("env", self)
--
4000.00ns INFO     testbench.py(44)[uvm_test_top.env.tc1]: I'm medium size.
4000.00ns INFO     testbench.py(15)[uvm_test_top.env.tc2]: I'm so tiny!

We’ve now overridden only one instance of the TinyComponent.

Clearing overrides from the factory

Python and pyuvm allow you to run many UVM tests in a single Python file. When you do this, the overrides from one test will persist unless you explicitly clear them in the next test. You do this with the uvm_factory().clear_overrides() method, as we do below:

@cocotb.test()
async def two_comp_test(_):
    uvm_factory().clear_overrides()
    await uvm_root().run_test("TwoCompTest")

Printing the state of the factory

The UVM specification calls for a uvm_factory().print() method that prints the state of the factory. The print() method takes an argument that controls what gets printed. The argument, named all_types, can be set to 0, 1 or 2 with the following results:

  • 0—Prints overrides only
  • 1— (the default) Prints user-defined types and overrides. User-defined types are types whose names don’t start with the string "uvm_"
  • 2—Prints all types registered with the factory and overrides.

Here is an example of printing only overrides:

@cocotb.test()
async def two_comp_test(_):
    uvm_factory().clear_overrides()
    await uvm_root().run_test("TwoCompTest") # from above
    uvm_factory().print(0)
--
# --- overrides ---
# 
# Overrides:
# TinyComponent            : Type Override: None       || Instance Overrides: uvm_test_top.env.tc1 => MediumComponent

The print() method uses the Python print() function to print out the factory status. But you can incorporate the factory status into logging by creating a string from the factory.

Creating a string from the factory

The uvm_factory().debug_level data member can take the same three values as we saw above 0, 1 or 2 with the same behavior. We create a string from the uvm_factory() by passing it to the str() class. For example, we might log the state of the factory in the report phase:

class MediumNameTest(TinyFactoryTest):
    def build_phase(self):
        uvm_factory().set_type_override_by_name(
            "TinyComponent", "MediumComponent")
        super().build_phase()

    def report_phase(self):
        uvm_factory().debug_level = 0
        uvm_factory_str = str(uvm_factory())
        self.logger.info(uvm_factory_str)
--
3000.00ns INFO     testbench.py(69)[uvm_test_top]: --- overrides ---
                   
                   Overrides:
                   TinyComponent            : Type Override: MediumComponent || Instance Overrides: 
                     

cocotb formats the string to fit in the logging system.

Summary

This blog post introduced the UVM factory as implemented in pyuvm. We saw that the lack of typing make the pyuvm factory easier to use than the SystemVerilog one. pyuvm implements the complete UVM factory specification and adds the ability to create a string from the uvm_factory() singleton.

Leave a Reply