In this article, we will explore how to apply the SOLID principles within the context of SystemVerilog for verification purposes. Originally introduced by Robert C. Martin in the early 2000s, the SOLID principles are a set of five guidelines aimed at improving the design and maintainability of object-oriented software. The acronym SOLID was subsequently coined by Michael Feathers to represent these key principles. The five principles are:
The Single Responsibility Principle states that a software entity (class, method, module, etc.) should have one, and only one, reason to change. In other words, a software entity should have a single, well-defined responsibility.
Key aspects of SRP:
Below is a simple example of packet generation with CRC that violates the Single Responsibility Principle (SRP), as both packet assembly and CRC calculation are performed in the same function.
gen_pkt_with_crc()
has two major responsibilities: assembling the packet and calculating the CRC, giving it two reasons to change.
In the following example, we refactor the previous code to adhere to SRP.
In this latest version:
packet_assembler
is responsible only for assembling the packet.CRC_calculator
is responsible only for calculating the CRC.packet_generator
coordinates the process using the other two classes.If the CRC calculation changes, we only need to modify the CRC_calculator
method. By isolating the CRC method, we can easily perform unit tests to verify its correctness. This separation of responsibilities makes the code more modular, easier to maintain, and easier to test. A clear example of SRP can be seen in the UVM verification library, where the framework provides objects and components with a well-defined function (uvm_driver, uvm_monitor, uvm_sequencer, etc.).
The Open/Closed Principle (OCP) is the second principle of SOLID and is crucial for creating flexible and extensible software systems. Let’s dive deeper into this principle. The Open/Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
Key aspects of OCP:
In the context of SystemVerilog, OCP can be applied in several ways:
Interfaces: SystemVerilog allows defining interfaces that can be implemented by different modules. This allows extending functionality without modifying existing modules.
Parameters: Using parameters in modules allows extending their functionality without modifying the internal code.
Classes and Inheritance: In SV for verification, base classes and inheritance can be used to extend functionalities.
Component Generation: Using generate to create configurable structures.
Using Abstract Classes:
In this example:
math_operation
is an abstract class that defines the interface for math operations.addition and multiplication
are concrete implementations of math_operation
.calculator
uses math_operation
without knowing the specific implementations.This design is:
calculator
to add new operations.math_operation.
For example, we could add a new operation without modifying existing code:
This approach allows easily extending functionality without modifying existing classes, thus adhering to the Open/Closed Principle. When we use the UVM library in our verification environment, we often work with UVM base classes that come with a set of predefined properties and methods. These base classes are 'closed for modification' as we shouldn't alter their core functionality.
However, these classes are simultaneously 'open for extension'. We can extend these base classes to add more functionalities or customize behavior for our specific needs without modifying the original code. For instance, we might extend uvm_sequence_item to create our custom transaction types, or extend uvm_driver to implement a specific protocol.
This approach allows us to build upon the robust foundation provided by UVM while tailoring components to our particular verification requirements. It ensures that the core UVM library remains stable and untouched, while still allowing for the flexibility needed in diverse verification scenarios.
Applying OCP in SystemVerilog can lead to more modular and adaptable designs, which is especially valuable in projects that evolve over time or need to be reused in different contexts.
The Liskov Substitution Principle (LSP) is the third principle of SOLID, named after Barbara Liskov, who introduced it in 1987. This principle is fundamental for designing robust class hierarchies and creating coherent object-oriented software. Let's delve deeper into this principle.
The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. Although SystemVerilog is not a pure object-oriented language like Java or C++, it does support object-oriented programming concepts in its verification layer. LSP can be applied in this context:
Classes and inheritance:
In this example, read_transaction
and write_transaction
extend transaction in a way that respects the LSP. They can be used anywhere a transaction is expected.
Interfaces and modules:
Here, both base_memory
and cache_memory
implement the same memory_if interface,
allowing them to be substitutable for each other without affecting the system's functioning.
Polymorphism in verification:
In this case, cache_memory_model
can be used anywhere a memory_model
is expected, maintaining the coherence of the expected behavior.
If we use UVM, the Factory respects this principle by allowing subclasses to be used in place of their base classes, which is fundamental to the functioning of type overrides.
Facilitates the creation of interchangeable components in design and verification.
Applying LSP in SystemVerilog can lead to more robust and flexible verification designs, allowing greater component reuse and facilitating testbench extension as hardware design evolves.
The Interface Segregation Principle (ISP) is the fourth principle of SOLID. This principle focuses on creating more specific and cohesive interfaces. This principle states that clients should not be forced to depend on interfaces they do not use. In other words, it is better to have many specific interfaces than a general one.
Although SystemVerilog does not have interfaces exactly like traditional object-oriented programming languages, we can apply ISP in several ways:
SystemVerilog Interfaces:
In this example, we have segregated the big interface into smaller, more specific interfaces. This allows modules to implement only the interfaces they need.
Abstract Classes:
Here, we have segregated the functionalities into separate abstract classes. This allows different types of memory to implement only the interfaces they need.
Modports in Interfaces:
In this case, we use modports
to define different views of the same interface, allowing modules to access only the signals they need.
Applying ISP in SystemVerilog can lead to cleaner and more maintainable designs, especially in complex systems with multiple interacting components.
The Dependency Inversion Principle (DIP) is the fifth and final principle of SOLID. This principle is crucial for creating decoupled and easily maintainable systems.
The Dependency Inversion Principle states that:
In the context of SystemVerilog and hardware design, DIP can be applied in several ways:
Interfaces: Interfaces in SystemVerilog provide a way to define abstractions that can be implemented by different modules.
In this example, the memory_controller depends on the abstraction bus_if, not on a specific memory implementation. This allows easy switching between ram and cache without modifying the controller.
Abstract Classes and Inheritance:
Here, TestBench depends on the abstraction memory_model, not on concrete implementations like ram_model or cache_model.
Module Parameterization:
In this case, generic_memory is parameterizable and can work with different data types, inverting the typical dependency where the high-level module would depend on a specific
This parameterized approach is exemplified in the UVM library itself, which extensively uses parameterization for creating flexible and reusable components. This design philosophy allows high-level modules to depend on abstractions rather than concrete implementations, aligning perfectly with the Dependency Inversion Principle (DIP). A prime example of this is the uvm_driver base class:
In this definition, uvm_driver is parameterized with REQ and RSP types, defaulting to uvm_sequence_item if not specified. This allows users to create drivers for specific transaction types without modifying the base class, adhering to both the Open-Closed Principle and the Dependency Inversion Principle. High-level testbench components can now work with this abstraction, while specific implementations can be easily swapped or extended as needed.
The UVM Factory is another example, it allows high-level components to depend on abstractions (base classes or interfaces) rather than concrete implementations.
Here, my_test doesn't directly instantiate my_env
, but uses the Factory to create the object. This allows the concrete implementation of my_env
to be substituted without changing my_test.
Applying the DIP in SystemVerilog can lead to more flexible and maintainable designs, especially in complex systems where modularity and the ability to evolve are crucial.
Final Thoughts
In conclusion, SOLID principles applied to SystemVerilog for verification are not just theoretical concepts, but practical guidelines that can transform the quality and efficiency of our verification environments.
Identifying and applying these principles in our designs is crucial. While we may sometimes apply them unconsciously, we often realize their importance only when faced with the need to refactor code due to a seemingly simple change. The SOLID principles, along with software design patterns that we'll explore in future articles, enable us to create modular, maintainable, and scalable verification environments.
By adopting these principles, we not only improve the quality of our current code but also lay the foundation for more efficient and robust development in the future. In the dynamic field of hardware verification, where design complexity constantly increases, the application of SOLID principles can make the difference between a successful project and one that collapses under its own weight.
Marcelo A. Pouso
Design Verification Engineer at Emtech S.A.
Thanks, Maia Desamo, Joaquin Lutri, and Leonel Campos for their valuable feedback and insightful reviews.
Any Comments or questions, please feel free to contact us: info@emtech.com.ar