My previous blog posts were on static and parameterized classes to get you ready for the big game – the UVM Configuration Database or uvm_config_db. When used properly, this is a great way for one component to share a value with another. If the test or environment knows the path to the agent, the DB is efficient. Used improperly and it will bring your simulation to its knees.
Too Many Choices
The DB is that it is based on an associative array with a string index. So each entry is a name-value pair. If you store 100,000 values, the DB has to search these to find the particular value. If the array index values are organized as a tree, searching may require up to 20 string comparisons. Here is the DB with 100,000 entries.
Since this is a parameterized class, each specialization with a different type divides the size of the database. Perhaps half your configuration values are 32-bit integers, and the other half 64-bit values. Each DB access is now looking through half as many values.
The Wild Problem
The DB further organizes the name-value pairs by adding a scope to each name. The “speed” value can take a very different meaning for a memory compared to a processor.
How does a component share values across the entire testbench? I saw a memory component that wanted to share the memory speed to EVERY component, regardless of location. So it made the following call.
uvm_config_db#(int)::set(null, “*”, “mem_speed”, mem_speed);
The problem is that when you call get() , and the DB contains entries with wildcard scopes, the DB has to perform regular expression matching, which is much less efficient than a straight string match. Worse yet, the DB can’t do a tree-search, and instead has to compare every entry. If your DB only contains a few hundred entries, no problem, but if there are hundreds of thousands, all with wildcards, the runs very slowly. How bad? These wildcard scopes for the memory speed caused the UVM build_phase() to take 24 hours, even though the rest of the simulation took less than an hour. If your testbench is getting large, keep an eye out for this problem!
As mentioned in the webinar, one solution is to group configuration variables together into a “config object”. For example, an agent config has its local parameters such as the active/passive enum, various address and data values, and the virtual interface. If each config object holds just 10 values, the DB size drops by 10x. An agent’s build_phase has a single DB call to get the handle to its config object. When the agent wants an individual value, it just uses the handle to get the value in its local object. Caching values in a config object is much faster than another DB access.
Another solution for the wildcard problem is a “global scope”. Remember, the scopes in the DB do NOT have to match your testbench hierarchy. The memory component could write its value in a unique name space at the top of the DB, such as “mem” shown here.
uvm_config_db#(int)::set(null, “mem”, “speed”, memory_speed);
What if you have multiple memory components? In this global scope of “mem”, you could store a separate config object handle for each instance, assuming “speed” is a property in the mem_cfg class.
foreach (mem_cfg[i]) uvm_config_db#(mem_cfg)::set(null, “mem”, $sformatf(“mem[%0d]”, i), mem_cfgs[i]);
Don’t call super.build_phase() for components directly derived from a UVM component class such as uvm_component, uvm_test, uvm_env, uvm_agent, etc. This avoids the expensive apply_config_settings().
A wildcard at the end of a scope string, such as “agt*” has fewer matches and better performance than wildcards at the front, such as “*”.
A flat uvm_config_db with wildcard scopes and many entries can be a performance hog. Divide the DB into smaller domains by grouping values into config objects. You can use wildcards in the scope strings, but limit them to the end of the string to help performance.
Enjoy your verification journey!