When you first learn UVM, most of the concepts make sense, even if you are new to Object-Oriented Programming. Except one, the UVM Factory. Why do you need all that extra code,
class::type_id::create(), just to make an object? What’s wrong with just calling
new()? The answer is teamwork!
The Small Problem
Imagine you are trying to verify a design with the X bus protocol. You write UVM testbench components that speak that protocol, including the agent, driver, and monitor. Here is agent code that constructs the driver.
class x_agent extends uvm_agent; x_driver drv; function void build_phase(…); drv = new("drv", this); // Construct the X bus driver endfunction endclass
In the build phase you call
new(). SystemVerilog says that this constructs the object based on the type of the handle on the left side, and so you end up with an
x_driver object. Job done and you didn’t have to type all that
But then halfway through the project, your manager says the design must work with the next generation of the protocol, called the Xplus bus. She even gave you the
xplus_driver class that extends
No problem, just add a flag variable and some code and your agent constructs both types. Since the new class extends the original one, you can reuse the
drv handle. You can even get fancy with the typed constructor.
class x_agent extends uvm_agent; x_driver drv; bit use_xbus; // True: use X bus, False: XPLUS-bus function void build_phase(…); if (use_xbus) drv = new(…); // Contruct the X bus driver else drv = xplus_driver::new(…); // Typed constructor builds xplus_driver object endfunction endclass
With this new agent and driver, you write a Xplus test, set the flag
use_xbus, and it passes. Who needs the factory when you can make a change this easily? Time to commit your changes and release them to the team.
When you come in the next morning, there are several angry people outside your cubicle. There is a bug in your code – the default value of
use_xbus is 0, so the agent always constructs
xplus_driver. Everyone who tried to use your revised agent got the wrong driver and now all their tests fail.
The Big Problem
The issue here is not the code bug. You are working in a team, so changes you make affect everyone. Every time you change shared code, adding more control variables,
case-statement for all the new variants, you potentially cause bugs. How can your tests inject the new
xplus_driver class without changing the
Many UVM techniques involve polymorphism. Sounds tricky, but it just means you define a base class with virtual methods. These become “hooks” to inject new behavior. How? Define a derived class and replace the base object with the derived one. Now when you call
handle.action(), you can get the base action or the derived, depending on the object type.
The factory pattern is a little more subtle. Imagine that the factory is a printing press. If you load a drum with a base image, it will print base text. If you load one with a derived image, it prints the derived text.
The UVM Factory prints objects. In the base
x_driver and derived
xplus_driver, use the
`uvm_component_utils(x_driver) macro to define
type_id. This is a “proxy class”, which means it is a “helper” of these classes, building their objects. Each
type_id is a unique class that, together, are the UVM Factory. The key is that all these proxy classes share a static array which is lookup table of base and derived types. By default, the following call builds an
drv = x_driver::type_id::create("drv", this);
The key concept is that from the test class, you can decide which object is built by the agent, without having to modify the agent’s code. In the test’s
build_phase(), add the following call.
You can read this as follows. Tell the
x_driver’s factory that when it is asked to create an object, use the type defined in the
xplus_driver class, where
get_type() returns the type
The OOP hook here to inject new behavior is the virtual method
create(). The subtle difference is that the method is in the
type_id proxy class, not in any of the driver classes.
If you write your UVM testbench with the factory create pattern, instead of directly calling the new() constructor, you can inject new behavior by overriding a base class with a derived. Not only can you override components, but you can also extend a sequence item class, override the base type, and now all your sequences will get this new behavior, without changing their code. The factory pattern enables all this, plus a stable code base, so you can inject new features, without affecting your team.
Look for a more technical post on the UVM Factory next week.
To learn more
You can learn more about these topics including Oriented Programming with the Siemens SystemVerilog for Verification course. It is offered in instructor led format by our industry expert instructors, or in a self-paced on-demand format. It can also be tailored to address your specific design goals and show you how to set up an environment for reuse for additional designs. Also, you can now earn a digital badge/level 1 certificate by taking our Advanced Topics Badging Exam. This will enable you to showcase your knowledge of this topic by displaying the badge in your social media and email signature.