With any large software project, you need to share information and control across widely separated blocks. In the bad old days, this would be done with global variables and routines. In OOP and SystemVerilog, you can do this with classes that have static properties and methods. UVM’s Configuration Database is a great example. But did you know that the DB has a global space inside its pseudo-global space?
You may be familiar with the RTL passing a virtual handle pointer to the test class through the DB as follows.
module hdl_top; // Holds RTL code import uvm_pkg::*; xbus_ifc xbus_if(...); // Instantiate the xbus interface initial uvm_config_db #(virtual xbus_ifc):: // Pass a virtual ifc set(null, "uvm_test_top", // to the test object "xbus_if", xbus_if); // Name: "xbus_if" ... // RTL code endmodule
The uvm_config_db call has the 3 parts. Read the statement from right to to left.
- The “name”, value pair of what you are accessing in the DB. Your code is easier to understand and understand if you try to make the name string and the variable the same. Here they are both xbus_if.
- The scope, which is a uvm_component handle plus a string. Since the RTL code does not know anything about the test class, it uses a null handle, and “uvm_test_top“, the instance name of the test object.
- Specialization #(virtual xbus_ifc) the data type of the value, which here is a virtual Xbus interface handle.
A whole new world
Most calls to the uvm_config_db use scopes made from the UVM testbench hierarchy, such as “uvm_test_top.env.agt”, which is an agent’s instance name. But you can make your own scope and it does not have to start with “uvm_test_top”. Under the hood, the uvm_config_db is just an associative array indexed by a string, so make your own global scope with any string.
Maybe a test wants to pass an address to the X-bus agent. It could use the scope “uvm_test_top.env.agt” or it could make up its own scope, such as “XBUS”.
class xbus_test extends uvm_test; virtual function void build_phase(...); uvm_config_db#(int):: // Pass an int value set(null, "XBUS", // in the XBUS global scope "addr", 42); // name, value pair endfunction endclass
The agent, or even a sequence can get this value with a nearly identical call.
class xbus_agent extends uvm_agent; int addr = 0; virtual function void build_phase(...); void'(uvm_config_db#(int):: // Get an int value get(null, "XBUS", // in the XBUS global scope "addr", addr)); // name, value pair endfunction endclass
These small code examples work great, passing single values to single destinations. What about the real world? One possible problem with this style is expandability: what happens when there are multiple agents? You could replace the “XBUS” string with an instance number, such as “XBUS”, or pass an array of addresses. Here is the set() call with the $sformatf() call to create the indexed scope.
uvm_config_db#(int)::set(null, $sformatf("XBUS[%0d]", inst), "addr", addr);
A more complex case is when making a testbench for a deeply layered system with 3 blocks, each which has an X-bus buried deep inside. The X-bus agent might not know if it is instance 0, 1, or 2, so adding an instance number to the scope string is more difficult.
With the UVM Config DB, you can pass values around a testbench, following the instance names, or you can make your own global scopes. Make sure you plan ahead. How are you going to reuse those components and sequences if they are surrounded by several layers and instantiated multiple times?
Enjoy your verification journey!