Breaking Free from uvm_config_db: Embracing the Full Power of UVM Resources

Introduction

Throughout my verification career, I’ve consistently used uvm_config_db as my go-to mechanism for sharing data between components in UVM testbenches. It’s ubiquitous in training materials, example code, and verification environments. However, after reading Cliff Cummings’ illuminating paper “The Untapped Power of UVM Resources and Why Engineers Should Use the uvm_resource_db API,” I’ve come to realize I’ve been limiting myself unnecessarily.

In this post, I’ll share what I’ve learned about the differences between uvm_resource_db and uvm_config_db, and why I’m planning to make the switch in my future projects.

The Historical Context

First, let’s understand why we have these two APIs in the first place. According to Cummings, the resource database was designed with several goals:

  1. Enable virtual interfaces to be treated like other configuration items
  2. Remove restrictions on what types can be stored
  3. Detach configuration from the component hierarchy
  4. Provide a general-purpose mechanism for sharing data between entities

The uvm_resource_db API was created to meet these goals. However, to ease the transition from OVM to UVM, a “convenience” layer was added called uvm_config_db that more closely mimicked the semantics of the older OVM set_config_* facility.

Crucially, uvm_config_db was intended as a transitional API rather than the primary interface. Yet today, Cummings estimates that over 90% of verification engineers primarily use uvm_config_dbmyself included! Why? “Because early UVM books and examples gave this flawed recommendation”. I can’t agree more.

Key Differences: What I’ve Been Missing

1. Simpler Syntax

The uvm_config_db API requires a component context and a string-based path, which can be confusing:

uvm_config_db#(virtual dut_if)::set(null, "*agnt*", "vif", dif);

(<font size=0.01>My idea was to always use cntxt=null for modules/interfaces and this for components</font>)

But uvm_resource_db uses a straightforward string for scoping:

uvm_resource_db#(virtual dut_if)::set("*agnt*", "vif", dif);

2. Access from Anywhere

One of the most limiting aspects of uvm_config_db is that it’s designed to work primarily with components. This creates complications when trying to access configuration from:

  • Sequences (requiring p_sequencer patterns)
  • Modules (requiring workarounds)
  • Non-component classes

The uvm_resource_db API eliminates these limitations, allowing any entity to store and retrieve data from the resource database.

3. Multiple Access Methods

uvm_config_db only provides a get() method that accesses resources by name. In contrast, uvm_resource_db offers:

  • read_by_name() – similar to uvm_config_db::get()
  • read_by_type() – retrieve resources based on type, which can simplify access patterns

4. Pseudo-Scopes

Perhaps the most powerful feature I’ve been missing is the ability to use “pseudo-scopes” – non-component-based scopes that allow for flexible data sharing patterns:

// Setting a resource with a pseudo-scope
uvm_resource_db#(int)::set("LCNT::*", "loop_count", 10);

// Retrieving it from anywhere
int loop_count;
uvm_resource_db#(int)::read_by_name("LCNT::*", "loop_count", loop_count);

This eliminates many of the awkward patterns I’ve had to use to share data between components and sequences.

Practical Implications: No More p_sequencer!

One of the most common patterns in UVM testbenches is the use of a “p_sequencer” to access configuration data from sequences (right? RIGHT?):

class my_sequence extends uvm_sequence;
  `uvm_declare_p_sequencer(my_sequencer)
  
  task body();
    // Access config through p_sequencer
    if (!uvm_config_db#(int)::get(p_sequencer, "", "param", param))
      `uvm_error(...)
  endtask
endclass

With uvm_resource_db, this pattern becomes unnecessary:

class my_sequence extends uvm_sequence;
  task body();
    // Direct access from the sequence
    if (!uvm_resource_db#(int)::read_by_name("SEQ::*", "param", param))
      `uvm_error(...)
  endtask
endclass

No more p_sequencer, no more virtual sequencers just for configuration passing!

Making the Transition

So, Chen, What do you say? I need to rewrite my whole testbench and use uvm_resource_db?

The good news is that we don’t have to completely rewrite our existing testbenches. Since both APIs access the same underlying resource database, we can:

  1. Continue using existing uvm_config_db code
  2. Start using uvm_resource_db for new components
  3. Gradually replace uvm_config_db calls as we refactor

Any resource stored with uvm_config_db can be accessed with uvm_resource_db, though the reverse isn't always true (particularly for pseudo-scopes).

Conclusion

After learning about the capabilities of uvm_resource_db, I’m convinced it’s time to make the switch. The simpler syntax, flexible access patterns, and elimination of workarounds like p_sequencer make it a superior choice for most use cases.

As Cummings puts it, the mistake wasn’t adding uvm_config_db to UVM—it was needed for backward compatibility. The mistake was “using and promoting the uvm_config_db API as the primary interface to the resource database.”

I’m excited to start using uvm_resource_db in my next project and to gradually refactor existing code to take advantage of its capabilities. Have you used uvm_resource_db in your projects? I’d love to hear about your experiences in the comments!

Newsletter Updates

Enter your email address below and subscribe to my newsletter

Leave a Reply

Your email address will not be published. Required fields are marked *