A singleton object refers to an instance of a class that is designed to have only one instance throughout the entire simulation runtime. In other words, a singleton object is a class instance that is shared and accessible from different parts of your verification environment, ensuring that there is always a single instance of that object.
Singleton objects are often used for managing global settings, configurations, and resources that need to be accessible from various components and modules in your verification environment. It can also be used to store design RTL parameters to influence testbench configurations.
Singleton Object Example
// Define a class that you want to make a singleton
class mySingleton;
function new (string name = "mySingleton");
...
endfunction
static local mySingleton m_sg; // Singleton object
string name; // My test variable
// Has a static method that returns the instance of the class
static function mySingleton get();
if (m_sg == null)
m_sg = new ();
return m_sg;
endfunction
endclass
Note that a static
and local
variable of the same class type is created. In the static get() method, check whether an instance of the class has already been created. If an instance exists, return that instance; otherwise, create a new instance and return it.
class my_env extends uvm_env;
`uvm_component_utils (my_env)
function new (string name="my_env", uvm_component parent);
super.new (name, parent);
endfunction
virtual function void build_phase (uvm_phase phase);
mySingleton m_sg = mySingleton::get();
super.build_phase (phase);
`uvm_info ("ENV", $sformatf ("%s", m_sg.name), UVM_MEDIUM)
endfunction
endclass
class my_test extends uvm_test;
`uvm_component_utils (my_test)
function new (string name = "my_test", uvm_component parent);
super.new (name, parent);
endfunction
my_env m_env;
virtual function void build_phase (uvm_phase phase);
mySingleton m_sg = mySingleton::get();
super.build_phase (phase);
m_sg.name = "Hey, this works well !";
`uvm_info ("TEST", $sformatf ("%s", m_sg.name), UVM_MEDIUM)
m_env = my_env::type_id::create ("m_env", this);
endfunction
endclass
Note that a local m_sg class object has been created and its get() function is called to get the singleton object. The first invocation will create a new m_sg object within the class and all further calls will return the same m_sg instance.
CDNS-UVM-1.1d (15.10-s004) (C) 2007-2013 Mentor Graphics Corporation (C) 2007-2013 Cadence Design Systems, Inc. (C) 2006-2013 Synopsys, Inc. (C) 2011-2013 Cypress Semiconductor Corp. ---------------------------------------------------------------- UVM_INFO @ 0: reporter [RNTST] Running test my_test... UVM_INFO tb_top.sv(45) @ 0: uvm_test_top [TEST] Hey, this works well ! UVM_INFO tb_top.sv(29) @ 0: uvm_test_top.m_env [ENV] Hey, this works well ! --- UVM Report catcher Summary ---
By using the get() method, you ensure that you always work with the same instance of the MySingleton class throughout your UVM-based verification environment. Remember that while singletons can be useful for sharing common resources, they should be used judiciously to avoid creating tightly coupled code and potential issues with testbench scalability and maintainability.
Usecase Example
An interconnect is the backbone of an SoC as many cores, IPs and memory blocks are connected to it and hence is usually an important part of the verification plan. Each master and slave may have different data bus-widths and hence different requirements. From a testbench perspective, it would be interesting to think about how to organize all these design parameters so that any change in the design results in a lower effort to reflect that change in the testbench.
A singleton object can be used as a central database to store design parameters that can be queried by custom functions from the same class object. Imagine masters and slaves are cars of different types and makes stored in a SV structure format which has a definition as given below.
typedef struct {
string brand;
e_type type;
e_engine engine;
bit [15:0] length;
bit has_lcd;
...
} st_car;
A structure list can be created to store all the details of different cars that can later be fed into the database. This list can be generated from design files using perl or python scripts.
st_car car_list [10] = '{
'{"honda", 4DOOR, V6, 450, ...},
'{"bmw", COUPE, V8, 300, ... }
...
};
Next task is to create the central database.
class base_car;
string brand;
e_type type;
...
endclass
class car_db;
static local car_db m_inst;
base_car car_list [$];
static function noc_spec get();
if (m_inst == null) begin
m_inst = new();
end
return m_inst;
endfunction
// functions to query the list for different usage
endclass
An instance of the database can be obtained by calling the function get() and can be placed inside within the top environment.
class top_env;
...
car_db m_car_db;
virtual function void build_env ();
// Get database instance
m_car_db = car_db::get();
// For each row in the structure, create a class to store the row
// and insert into the database queue
foreach (car_list[i]) begin
base_car bc = new ("bc");
bc.brand = car_list[i].brand;
bc.type = car_list[i].type;
...
m_car_db.car_list.push_back (bc);
end
endfunction
endclass
The main advantage of doing this is the ability for any component to access the central database via a local instance. Another advantage is that all related custom functions to query the database can be encapsulated. Moreover, any change in design parameter for any of the above "cars", can easily be made in the structure list that was created earlier. You can also create new variables in the base_car class to store computed results from existing values.