Introduction: Design patterns
In object oriented programming, we constantly create classes and their instances. Often, we don’t put enough time and efforts in planning these classes especially their roles and relationships. If carefully planned the core principles like inheritance, polymorphism and encapsulation will yield extremely good results to the project but if not planned properly, the same fundamental principles can seriously affect the project in a very bad way. I have seen some test benches that are rewritten as their architecture does not allow scaling, in other cases the components are rewritten as they reach a point where it is almost impossible to manage or fix them.
It is always good to plan out the relationship between the objects ahead of their implementation. However, some of the problems in object oriented programming are very common and they keep repeating across different projects. These common design problems have common solutions too and can be applied once the pattern of the problem is identified. These patterns are known as design patterns. So, always plan, identify common patterns while planning and apply common solution. In this article we will discuss one such design pattern known as singleton.
What exactly is the problem?
Once we declare a class, anybody can take an object of it any number of times. In some special situations we may want control the way the objects are created out of the class. We may want to enforce users to create just one instance. We may wonder what those situations will be and might also think they are extremely rare but there are more than a handful of situations where only single instance of a class is a requirement. For example, If we have a shared resource and many entities are using it then we don’t want to create multiple instances of the resources instead we restrict the entities to always create one resource object and all the entities should access the same resource.
Another example is when we have a set of global variables that all the system entities must have access to. We can create a class with all the global configuration, allow only one object to be created and used among all the entities. Any time we feel like it does not make sense to have more than one object of a class is needed, it is better to enforce that only one object can be created out of that class. This pattern is called as singleton pattern
Some real world analogies
-
- If we want to model earth in object oriented programming then we want only one earth object to be created not multiple of them.
- Let’s say, we created a class to model government to provide different services to citizen entities. We want to have a single instance of the government class.
- If we decided to model a house object then we need to have a single key object to be created to access the house. We don’t want anyone be able to create as many keys as they want to access the house object.
- In a any tree model. We typically want to have only one root object.
One rule of thumb is when we create a class that is too specific in nature and does not have any more generalization left then it is potentially a candidate for singleton pattern.
Some common examples in programming
Some examples of resources that can be managed using the singleton design pattern include:
-
- Logging classes: Singleton can be used to create a single instance of a logging class, ensuring that only one log file or output stream is used, making it easier to manage and maintain
- Printer classes: In systems that handle printing tasks, a Singleton can be used to manage the printer spool, ensuring that only one instance of the spool is used and preventing conflicts or inconsistencies
- Database connection managers: Singleton can be used to manage database connections, ensuring that only one instance of the connection manager is created and used, reducing resource overhead and improving performance
- File managers: In systems that handle file management tasks, a Singleton can be used to manage the file manager, ensuring that only one instance of the file manager is created and used, preventing conflicts or inconsistencies
- Global state management: Singleton can be used to manage global state, such as configuration settings, cache data, or log messages, providing a centralized point of access and control for these shared resources
Implementing Singleton in Systemverilog
To implement singleton pattern we need two steps. First, we need to make the constructor of the class local or protected which prevents anyone calling it directly. In other words, we cannot call new() method to create the object of this class. Second, we need to create a static method to create a single object and return the same object always when called. This static method is the only way to request an object of this class. Here is a simple singleton implementation in Systemverilog.
Example
module SingletonTest;
class GlobalConfiguration;
static local GlobalConfiguration globalConfiguration;
bit enableCoverage;
// Make constructor local so that no one can call new outside this class
local function new();
endfunction : new
// Create a static get function and this function is the only way to get an instance of this object
static function GlobalConfiguration getGlobalConfiguration();
if( globalConfiguration == null)
globalConfiguration = new(); // create an object if it is not created already
return globalConfiguration;
endfunction : getGlobalConfiguration
function void displayConfiguration();
$display("Coverage enable value is %b", enableCoverage);
endfunction : displayConfiguration
endclass : GlobalConfiguration
initial begin
GlobalConfiguration config1;
GlobalConfiguration config2;
// request global configuration object but both handles should be given the same object
config1 = GlobalConfiguration::getGlobalConfiguration();
config2 = GlobalConfiguration::getGlobalConfiguration();
// test and ensure both handles point to the same object, set property with one handle and print with another
config1.enableCoverage = 1;
config2.displayConfiguration();
config2.enableCoverage = 0;
config1.displayConfiguration();
end
endmodule : SingletonTest
Drawbacks and Alternatives
It is possible to abuse singleton pattern in building test benches. For example, we might think that a single configuration object created at the test top can then be shared and accessed by all components in the bench by using the singleton pattern but it may not be a good idea. The singleton pattern has drawback and one should evaluate the situation and make sure that the singleton is indeed the right pattern for the problem at hand. Some drawbacks of the singleton pattern are as follows:
-
- It can lead to tight coupling and hidden dependencies, as any class using the Singleton relies on its specific implementation and state, which can make the code harder to manage. Loose coupling and less dependencies will improve reuse.
- Furthermore, it can violate the single responsibility principle by accumulating multiple responsibilities, thus impacting the maintainability of the code
When we want a single object but not comfortable using singleton pattern then there are couple of alternatives.
-
- Dependency Injection: Instead of using a Singleton, we can create only one instance of a class and pass it as an argument where it’s needed. This raises the awareness of dependencies a method or another class needs in order to function and makes it easier to change the code if multiple instances are needed in the future
- Factory Pattern: The Factory pattern can be used to create instances of classes. This can be an alternative to using the Singleton pattern when there is a need for multiple instances in the future
These alternatives provide different ways to manage the instantiation of classes without relying on the Singleton pattern, addressing some of its drawbacks such as global state and tight coupling.
Conclusion
The singleton pattern that we discussed is more common than we realise. It is used for uvm_config_db, uvm_factory, uvm_command_line_processor, uvm_test_top etc in UVM. While building test benches we might see the need for this pattern frequently. Since we know Singleton design pattern now. Add this new tool to the programming tool kit. We know when to use it and more importantly when not to use. It is always recommended to write down requirements, plan out the classes and their relations, identify the common patterns and apply the solution to it, of-course with careful evaluation.