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.

 Simulation Log
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.