OOP Python & SystemVerilog
Cheatsheet Content
### Python OOP: Introduction Object-Oriented Programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes), and code in the form of procedures (often known as methods). Python is a multi-paradigm language, supporting OOP, procedural, and functional programming. Key principles of OOP: - **Encapsulation:** Bundling data and methods that operate on the data within a single unit or object. - **Inheritance:** A mechanism where a new class (subclass) derives properties and behavior from an existing class (superclass). - **Polymorphism:** The ability of an object to take on many forms. In OOP, it refers to the ability of different classes to respond to the same method call in different ways. - **Abstraction:** Hiding complex implementation details and showing only the necessary features of an object. ### Python OOP: Classes and Objects - **Class:** A blueprint or a template for creating objects. It defines a set of attributes and methods that the created objects will have. - **Object (Instance):** An individual instance of a class. Each object has its own state (attribute values) and can perform actions (methods). #### Defining a Class ```python class MyClass: # Class attribute (shared by all instances) class_variable = "I am a class variable" def __init__(self, name, age): # Instance attributes (unique to each instance) self.name = name self.age = age def greet(self): return f"Hello, my name is {self.name} and I am {self.age} years old." def get_class_variable(self): return MyClass.class_variable # Accessing class variable via class name ``` #### Creating Objects ```python # Creating instances (objects) of MyClass obj1 = MyClass("Alice", 30) obj2 = MyClass("Bob", 25) print(obj1.greet()) # Output: Hello, my name is Alice and I am 30 years old. print(obj2.greet()) # Output: Hello, my name is Bob and I am 25 years old. print(obj1.class_variable) # Output: I am a class variable print(obj2.class_variable) # Output: I am a class variable ``` ### Python OOP: Encapsulation Encapsulation in Python is achieved through conventions, as Python does not have strict access modifiers like `private` or `protected`. #### Public Attributes By default, all attributes and methods are public. ```python class MyClass: def __init__(self, public_data): self.public_data = public_data # Public attribute obj = MyClass("Accessible Data") print(obj.public_data) # Accessible directly ``` #### Protected Attributes (Convention) Prefixing an attribute with a single underscore `_` indicates it's "protected" and should not be accessed directly from outside the class. It's a convention, not enforced. ```python class MyClass: def __init__(self, protected_data): self._protected_data = protected_data # Protected attribute (convention) obj = MyClass("Sensitive Data") print(obj._protected_data) # Still accessible, but discouraged ``` #### Private Attributes (Name Mangling) Prefixing an attribute with double underscores `__` triggers "name mangling", making it harder to access from outside. Python renames `__attribute` to `_ClassName__attribute`. ```python class MyClass: def __init__(self, private_data): self.__private_data = private_data # Private attribute (name mangling) def get_private_data(self): return self.__private_data obj = MyClass("Secret Data") # print(obj.__private_data) # This would raise an AttributeError print(obj.get_private_data()) # Accessible via method print(obj._MyClass__private_data) # Still accessible via mangled name, but highly discouraged ``` ### Python OOP: Inheritance Inheritance allows a class (child/derived/subclass) to inherit attributes and methods from another class (parent/base/superclass). #### Single Inheritance ```python class Animal: def __init__(self, name): self.name = name def speak(self): raise NotImplementedError("Subclass must implement abstract method") class Dog(Animal): # Dog inherits from Animal def __init__(self, name, breed): super().__init__(name) # Call parent class constructor self.breed = breed def speak(self): return f"{self.name} says Woof!" class Cat(Animal): # Cat inherits from Animal def __init__(self, name, color): super().__init__(name) self.color = color def speak(self): return f"{self.name} says Meow!" dog = Dog("Buddy", "Golden Retriever") cat = Cat("Whiskers", "Tabby") print(dog.speak()) # Output: Buddy says Woof! print(cat.speak()) # Output: Whiskers says Meow! ``` #### Multiple Inheritance A class can inherit from multiple parent classes. ```python class Flyer: def fly(self): return "I can fly!" class Swimmer: def swim(self): return "I can swim!" class Duck(Flyer, Swimmer): # Duck inherits from Flyer and Swimmer def quack(self): return "Quack!" duck = Duck() print(duck.fly()) # Output: I can fly! print(duck.swim()) # Output: I can swim! print(duck.quack()) # Output: Quack! ``` **Method Resolution Order (MRO):** Python uses C3 linearization to determine the order in which base classes are searched for methods. You can check it using `ClassName.__mro__` or `help(ClassName)`. ### Python OOP: Polymorphism Polymorphism means "many forms". In OOP, it allows objects of different classes to be treated as objects of a common type, often a superclass or an interface. #### Method Overriding Subclasses can provide their own implementation of a method that is already defined in their superclass. ```python class Animal: def speak(self): return "Animal makes a sound" class Dog(Animal): def speak(self): # Overrides speak() from Animal return "Woof!" class Cat(Animal): def speak(self): # Overrides speak() from Animal return "Meow!" def make_animal_speak(animal): print(animal.speak()) dog = Dog() cat = Cat() animal = Animal() make_animal_speak(dog) # Output: Woof! make_animal_speak(cat) # Output: Meow! make_animal_speak(animal) # Output: Animal makes a sound ``` #### Duck Typing Python's polymorphism is often described as "duck typing": "If it walks like a duck and quacks like a duck, then it must be a duck." It focuses on what an object *can do* rather than what its *type is*. ```python class Car: def drive(self): print("Driving a car") class Bicycle: def drive(self): print("Riding a bicycle") def start_journey(vehicle): vehicle.drive() my_car = Car() my_bike = Bicycle() start_journey(my_car) # Works because Car has a drive method start_journey(my_bike) # Works because Bicycle has a drive method ``` ### Python OOP: Abstraction Abstraction involves hiding the complex implementation details and showing only the essential features of an object. In Python, this is typically achieved using abstract classes and methods from the `abc` (Abstract Base Classes) module. #### Abstract Base Classes ```python from abc import ABC, abstractmethod class Shape(ABC): # Inherit from ABC to make it an abstract class @abstractmethod def area(self): pass @abstractmethod def perimeter(self): pass def description(self): # Concrete method in abstract class return "This is a generic shape." # shape = Shape() # This would raise a TypeError: Can't instantiate abstract class Shape class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): # Must implement abstract method area return 3.14 * self.radius * self.radius def perimeter(self): # Must implement abstract method perimeter return 2 * 3.14 * self.radius class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height def area(self): # Must implement abstract method area return self.width * self.height def perimeter(self): # Must implement abstract method perimeter return 2 * (self.width + self.height) circle = Circle(5) rectangle = Rectangle(4, 6) print(f"Circle area: {circle.area()}") # Output: Circle area: 78.5 print(f"Rectangle perimeter: {rectangle.perimeter()}") # Output: Rectangle perimeter: 20 print(circle.description()) # Output: This is a generic shape. ``` Any concrete class inheriting from `Shape` *must* implement `area()` and `perimeter()`, otherwise, it will also be an abstract class. ### Python OOP: Special (Magic/Dunder) Methods Special methods (e.g., `__init__`, `__str__`, `__add__`) allow you to define how objects of your class behave with built-in functions and operators. | Method Name | Description | Example Usage | | :------------- | :------------------------------------------------------------------------------ | :------------------------------------------------- | | `__init__(self, ...)` | Constructor: Initializes a new object. | `obj = MyClass(args)` | | `__str__(self)` | String representation for humans (called by `str()`, `print()`). | `print(obj)` | | `__repr__(self)` | Official string representation for developers (called by `repr()`). | `obj` in interactive console | | `__len__(self)` | Returns the length of an object (called by `len()`). | `len(obj)` | | `__add__(self, other)` | Defines behavior for the `+` operator. | `obj1 + obj2` | | `__getitem__(self, key)` | Defines behavior for accessing an item by key (e.g., `obj[key]`). | `obj[0]` | | `__setitem__(self, key, value)` | Defines behavior for setting an item by key (e.g., `obj[key] = value`). | `obj[0] = 'new'` | | `__call__(self, ...)` | Makes an object callable like a function. | `obj(arg1, arg2)` | | `__eq__(self, other)` | Defines behavior for the `==` operator. | `obj1 == obj2` | | `__lt__(self, other)` | Defines behavior for the ` ### Python OOP: Class Methods and Static Methods These are alternative ways to define methods within a class, differing in how they receive the instance or class as their first argument. #### Instance Methods - Take `self` as the first argument, referring to the instance of the class. - Can access and modify instance-specific data. - Most common type of method. ```python class MyClass: def instance_method(self): return f"This is an instance method called by {self}" ``` #### Class Methods - Marked with `@classmethod` decorator. - Take `cls` (conventionally) as the first argument, referring to the class itself. - Can access and modify class-specific data (class variables). - Often used as alternative constructors. ```python class Employee: company = "ABC Corp" # Class variable def __init__(self, name, salary): self.name = name self.salary = salary @classmethod def from_string(cls, emp_str): name, salary = emp_str.split('-') return cls(name, int(salary)) # cls() acts as a constructor emp1 = Employee("Alice", 50000) emp_str_data = "Bob-60000" emp2 = Employee.from_string(emp_str_data) # Using class method as alternative constructor print(f"{emp1.name} works at {emp1.company}") print(f"{emp2.name} works at {emp2.company} and earns {emp2.salary}") ``` #### Static Methods - Marked with `@staticmethod` decorator. - Do not take `self` or `cls` as the first argument. - Behave like regular functions but are logically grouped within a class. - Cannot access or modify instance or class-specific data. ```python class MathOperations: @staticmethod def add(x, y): return x + y @staticmethod def multiply(x, y): return x * y print(MathOperations.add(5, 3)) # Output: 8 print(MathOperations.multiply(5, 3)) # Output: 15 ``` ### Python OOP: Interview Questions #### Q1: What are the four pillars of OOP? Explain each briefly. **A1:** The four pillars are: 1. **Encapsulation:** Bundling data and methods that operate on that data into a single unit (object), and restricting direct access to some of an object's components. 2. **Inheritance:** A mechanism for a new class to derive attributes and behavior from an existing class, promoting code reusability. 3. **Polymorphism:** The ability of an object to take on many forms, allowing objects of different classes to be treated as objects of a common type. 4. **Abstraction:** Hiding complex implementation details and showing only the necessary features of an object, simplifying interaction. #### Q2: Explain the difference between `__str__` and `__repr__` in Python. **A2:** - `__str__` is used to provide a "human-readable" string representation of an object. It's called by `str()` and `print()`. Its goal is readability. - `__repr__` is used to provide an "official" string representation, primarily for developers. It should ideally return a string that, if passed to `eval()`, would recreate the object. It's called by `repr()` and when an object is displayed in an interactive console. Its goal is unambiguity. #### Q3: How is encapsulation achieved in Python, given it doesn't have `private` keywords? **A3:** Python uses conventions for encapsulation: - **Public:** Attributes/methods without any prefix are public and can be accessed directly. - **Protected (convention):** Prefixing with a single underscore (`_attribute`) suggests it's for internal use and should not be accessed from outside the class, but it's not enforced. - **Private (name mangling):** Prefixing with double underscores (`__attribute`) triggers name mangling, where the attribute name is changed to `_ClassName__attribute`. This makes it harder (but not impossible) to access directly from outside the class, providing a stronger form of "privacy." #### Q4: When would you use a class method versus a static method? **A4:** - **Class Methods (`@classmethod`):** - Used when the method needs access to the class itself (e.g., to create an instance of the class with different parameters, like an alternative constructor). - Takes `cls` as the first argument. - Can modify class-level state. - **Static Methods (`@staticmethod`):** - Used when a method has a logical connection to the class but doesn't need to access class-specific or instance-specific data. - Behaves like a regular function defined inside the class. - Takes no special first argument (`self` or `cls`). - Cannot modify class-level or instance-level state. #### Q5: What is "Duck Typing" in Python? **A5:** Duck typing is a concept related to dynamic typing where the type or the class of an object is less important than the methods it defines. If an object has the methods and properties that are expected for a certain operation, then it can be used for that operation. "If it walks like a duck and quacks like a duck, then it must be a duck." It promotes polymorphism by focusing on interfaces (what an object *can do*) rather than strict class hierarchies (what an object *is*). ### SystemVerilog OOP: Introduction SystemVerilog, primarily a hardware description and verification language, includes robust Object-Oriented Programming features, especially critical for building powerful and reusable testbenches. While its OOP features share concepts with general-purpose languages like Python, their application often differs due to the verification context. Key OOP principles in SystemVerilog: - **Encapsulation:** Achieved through `local` and `protected` keywords. - **Inheritance:** Allows creating specialized verification components from general ones. - **Polymorphism:** Through virtual methods and handles. - **Abstraction:** Using `virtual` classes and methods. OOP in SV is extensively used in Universal Verification Methodology (UVM) for building structured and reusable verification environments. ### SystemVerilog OOP: Classes and Objects - **Class:** A user-defined type that encapsulates data (properties) and behavior (methods). - **Object (Instance):** A dynamically allocated instance of a class, accessed via a *class handle*. #### Defining a Class ```systemverilog class MyClass; // Properties (data members) int id; string name; // Constructor (new() method) function new(int id_p, string name_p); this.id = id_p; this.name = name_p; $display("MyClass object created: ID=%0d, Name=%s", this.id, this.name); endfunction // Method (function) function void display_info(); $display("Object Info: ID=%0d, Name=%s", id, name); endfunction // Method (task) - can contain delays task do_something_timed(); #10ns; // Simulate some time-consuming operation $display("Doing something timed for object ID=%0d", id); endtask endclass ``` #### Creating Objects Objects in SystemVerilog are created on the heap using the `new()` constructor and accessed via class handles. ```systemverilog module test; MyClass obj_handle1; // Declare a class handle MyClass obj_handle2; initial begin // Create an object and assign its handle to obj_handle1 obj_handle1 = new(101, "FirstObject"); // Calls the constructor // Create another object obj_handle2 = new(102, "SecondObject"); // Access properties and call methods obj_handle1.display_info(); obj_handle2.display_info(); obj_handle1.id = 200; // Can modify public properties $display("Modified obj_handle1 ID: %0d", obj_handle1.id); obj_handle2.do_something_timed(); // Call a task end endmodule ``` **Important:** A class handle is just a pointer. If a handle is declared but not assigned an object (e.g., `MyClass my_obj;`), it will be `null`. Attempting to access properties or methods via a `null` handle results in a run-time error. ### SystemVerilog OOP: Encapsulation SystemVerilog provides explicit keywords for access control: `local` and `protected`. By default, class members are public. #### Public Members (Default) Any property or method declared without `local` or `protected` is public. ```systemverilog class PublicExample; int public_var; // Public by default function void public_method(); $display("This is a public method."); endfunction endclass module test; PublicExample ex = new(); initial begin ex.public_var = 5; ex.public_method(); end endmodule ``` #### Local Members `local` members are accessible only within the class itself. They are not visible to subclasses or outside modules. ```systemverilog class LocalExample; local int secret_data; // Accessible only within LocalExample function new(int data); this.secret_data = data; endfunction function void print_secret(); $display("Secret data: %0d", secret_data); endfunction endclass module test; LocalExample ex = new(123); initial begin // ex.secret_data = 456; // ERROR: secret_data is local ex.print_secret(); // OK: Method within the class can access it end endmodule ``` #### Protected Members `protected` members are accessible within the class itself and by any derived (sub)classes. They are not visible to outside modules. ```systemverilog class BaseProtected; protected int protected_id; // Accessible in BaseProtected and its subclasses function new(int id_p); this.protected_id = id_p; endfunction function void display_protected(); $display("Base Protected ID: %0d", protected_id); endfunction endclass class DerivedProtected extends BaseProtected; function new(int id_p); super.new(id_p); // Call base class constructor endfunction function void access_protected(); $display("Derived class accessing protected ID: %0d", protected_id); // OK endfunction endclass module test; BaseProtected base_obj = new(10); DerivedProtected derived_obj = new(20); initial begin base_obj.display_protected(); derived_obj.access_protected(); // base_obj.protected_id = 15; // ERROR: protected_id is protected end endmodule ``` ### SystemVerilog OOP: Inheritance Inheritance allows a class to derive properties and methods from another class. This promotes code reuse and hierarchical organization. #### Syntax ```systemverilog class BaseClass; int base_var; function new(int val); this.base_var = val; endfunction function void display_base(); $display("Base var: %0d", base_var); endfunction endclass class DerivedClass extends BaseClass; // DerivedClass inherits from BaseClass int derived_var; function new(int base_val, int derived_val); super.new(base_val); // Call base class constructor this.derived_var = derived_val; endfunction function void display_derived(); $display("Derived var: %0d, Base var from derived: %0d", derived_var, base_var); endfunction endclass module test; DerivedClass d_obj = new(10, 20); initial begin d_obj.display_base(); // Method inherited from BaseClass d_obj.display_derived(); // Method from DerivedClass $display("Accessing inherited property: %0d", d_obj.base_var); end endmodule ``` - `extends` keyword is used for inheritance. - `super.new()` is crucial for calling the parent constructor from the child constructor. If not called, the parent's `new()` is not executed, and its members might not be properly initialized. - SystemVerilog supports only **single inheritance**. A class can inherit from only one direct base class. ### SystemVerilog OOP: Polymorphism Polymorphism in SystemVerilog is primarily achieved through **virtual methods** and **class handles**. It allows a base class handle to refer to objects of derived classes, and when a virtual method is called, the method of the actual object type is executed. #### Virtual Methods - A method declared with the `virtual` keyword in the base class. - Allows a derived class to override the base class's implementation. - Crucial for dynamic method dispatch. ```systemverilog class BasePacket; virtual function string get_packet_type(); // Declare as virtual return "Generic Packet"; endfunction endclass class EthernetPacket extends BasePacket; function string get_packet_type(); // Overrides the base method return "Ethernet Packet"; endfunction endclass class USBPacket extends BasePacket; function string get_packet_type(); // Overrides the base method return "USB Packet"; endfunction endclass module test; BasePacket pkt_handle; // Base class handle EthernetPacket eth_pkt = new(); USBPacket usb_pkt = new(); initial begin pkt_handle = eth_pkt; // Base handle points to derived object $display("Packet Type 1: %s", pkt_handle.get_packet_type()); // Calls EthernetPacket's method pkt_handle = usb_pkt; // Base handle points to another derived object $display("Packet Type 2: %s", pkt_handle.get_packet_type()); // Calls USBPacket's method pkt_handle = new(); // Base handle points to base object $display("Packet Type 3: %s", pkt_handle.get_packet_type()); // Calls BasePacket's method end endmodule ``` Without `virtual`, `pkt_handle.get_packet_type()` would always call `BasePacket::get_packet_type()`, even when pointing to `EthernetPacket` or `USBPacket` objects. ### SystemVerilog OOP: Abstraction Abstraction involves defining a common interface without providing the full implementation. SystemVerilog uses `virtual` classes and `pure virtual` methods for this. #### Virtual Classes - A class declared with the `virtual` keyword. - Cannot be instantiated directly (e.g., `pkt = new();` would be an error if `Packet` is virtual). - Must be extended by a concrete (non-virtual) class for instantiation. - Can contain both concrete and virtual methods. #### Pure Virtual Methods - A `virtual` method declared with `= 0;` in a `virtual` class. - Has no implementation in the `virtual` class. - Any non-virtual class extending a `virtual` class *must* implement all its `pure virtual` methods. ```systemverilog virtual class Transaction; // Virtual class int start_addr; int data_length; function new(int addr, int len); this.start_addr = addr; this.data_length = len; endfunction // Pure virtual method - must be implemented by concrete subclasses pure virtual function void print_transaction(); // Concrete virtual method - can be overridden, but has a default impl virtual function void display_details(); $display("Transaction: Addr=0x%0h, Length=%0d", start_addr, data_length); endfunction endclass class WriteTransaction extends Transaction; int write_data; function new(int addr, int len, int data); super.new(addr, len); this.write_data = data; endfunction // Implementation of pure virtual method function void print_transaction(); $display("WRITE Transaction: Addr=0x%0h, Length=%0d, Data=0x%0h", start_addr, data_length, write_data); endfunction // Overriding a concrete virtual method (optional) virtual function void display_details(); super.display_details(); // Call base implementation $display(" (Specific to Write: Data=0x%0h)", write_data); endfunction endclass class ReadTransaction extends Transaction; function new(int addr, int len); super.new(addr, len); endfunction // Implementation of pure virtual method function void print_transaction(); $display("READ Transaction: Addr=0x%0h, Length=%0d", start_addr, data_length); endfunction endclass module test; Transaction tr; // Base class handle initial begin // tr = new(10, 20); // ERROR: Cannot instantiate virtual class WriteTransaction wt = new(100, 4, 'hABCD); ReadTransaction rt = new(200, 8); tr = wt; // Polymorphism: Base handle points to derived object tr.print_transaction(); // Calls WriteTransaction's implementation tr.display_details(); // Calls WriteTransaction's overridden implementation tr = rt; tr.print_transaction(); // Calls ReadTransaction's implementation tr.display_details(); // Calls Transaction's default implementation end endmodule ``` ### SystemVerilog OOP: Factories Factory patterns are crucial in SystemVerilog verification environments (especially UVM) for creating objects dynamically and allowing easy modification of component types without changing the code that instantiates them. #### Basic Factory Mechanism A simple factory involves a static method in a base class (or a separate factory class) that creates an object based on some input. ```systemverilog class BaseComponent; string type_name; function new(string name="BaseComponent"); this.type_name = name; $display("Creating %s", type_name); endfunction virtual function void build(); $display("%s: Building...", type_name); endfunction endclass class Driver extends BaseComponent; function new(string name="Driver"); super.new(name); endfunction function void build(); $display("%s: Building driver logic.", type_name); endfunction endclass class Monitor extends BaseComponent; function new(string name="Monitor"); super.new(name); endfunction function void build(); $display("%s: Building monitor logic.", type_name); endfunction endclass // Simple Factory Class class ComponentFactory; static function BaseComponent create_component(string component_type); case(component_type) "driver": return new Driver(); "monitor": return new Monitor(); default: begin $error("Unknown component type: %s", component_type); return null; end endcase endfunction endclass module test; BaseComponent comp; initial begin comp = ComponentFactory.create_component("driver"); if (comp != null) comp.build(); comp = ComponentFactory.create_component("monitor"); if (comp != null) comp.build(); comp = ComponentFactory.create_component("sequencer"); // Will error if (comp != null) comp.build(); end endmodule ``` This basic factory needs modification if new component types are added. UVM provides a more sophisticated, type-based factory that uses `uvm_component::type_id::create()` and `set_type_override()` for dynamic object creation and substitution at runtime. ### SystemVerilog OOP: Randomization and Constraints SystemVerilog classes are extensively used for creating randomized test stimuli. #### Random Variables - Declared with the `rand` keyword. - Values are assigned randomly when `randomize()` method is called on an object. #### Constraints - Defined within a `constraint` block. - Used to specify legal relationships and ranges for `rand` variables. ```systemverilog class Transaction; rand int addr; rand int data; rand bit [2:0] command; // Constraint block constraint addr_range { addr >= 0; addr 50; };` ### SystemVerilog Data Structures: Associative Arrays and Queues SystemVerilog provides powerful built-in data structures, often used with OOP. #### Associative Arrays (Dynamic, Sparse Memory) - Similar to Python dictionaries or hash maps. - Stores elements based on a key. Key can be any integral type or `string`. - Memory is allocated only for elements that are assigned. ```systemverilog module assoc_array_example; string name_map[int]; // Associative array with int key, string value int score_map[string]; // Associative array with string key, int value initial begin // Assigning values name_map[10] = "Alice"; name_map[20] = "Bob"; name_map[5] = "Charlie"; score_map["Alice"] = 95; score_map["Bob"] = 88; // Accessing values $display("ID 10: %s", name_map[10]); // Output: ID 10: Alice $display("Alice's score: %0d", score_map["Alice"]); // Output: Alice's score: 95 // Checking if a key exists if (name_map.exists(15)) begin $display("ID 15 exists."); end else begin $display("ID 15 does not exist."); // Output: ID 15 does not exist. end // Iterating over keys $display("All names:"); foreach (name_map[key]) begin $display(" Key %0d: %s", key, name_map[key]); end // Deleting an entry name_map.delete(20); $display("Size of name_map after delete: %0d", name_map.num()); // Output: Size of name_map after delete: 2 end endmodule ``` #### Queues (Dynamic, Ordered Lists) - Similar to Python lists or C++ `std::deque`. - Can grow or shrink dynamically. - Elements can be added/removed from either end or anywhere in between. ```systemverilog module queue_example; int my_queue[$]; // Declare an integer queue string fifo_data[$]; initial begin // Adding elements my_queue.push_back(10); // Add to end my_queue.push_front(5); // Add to front my_queue.push_back(20); my_queue.insert(1, 7); // Insert 7 at index 1 $display("Queue after insertions: %p", my_queue); // Output: Queue after insertions: '{5, 7, 10, 20}' // Accessing elements $display("Element at index 0: %0d", my_queue[0]); // Output: Element at index 0: 5 $display("Last element: %0d", my_queue[$]); // Access last element // Removing elements int front_val = my_queue.pop_front(); // Remove from front int back_val = my_queue.pop_back(); // Remove from back $display("Popped front: %0d, Popped back: %0d", front_val, back_val); $display("Queue after pops: %p", my_queue); // Output: Queue after pops: '{7, 10}' my_queue.delete(0); // Delete element at index 0 $display("Queue after delete index 0: %p", my_queue); // Output: Queue after delete index 0: '{10}' my_queue.delete(); // Clear the entire queue $display("Queue after clear: %p", my_queue); // Output: Queue after clear: '{}' end endmodule ``` ### SystemVerilog Data Types: `enum`, `typedef`, `struct`, `union` These features help create more readable, robust, and organized code. #### `enum` (Enumerated Types) - Defines a set of named integer constants. - Improves readability and type safety compared to using raw numbers. ```systemverilog module enum_example; typedef enum { IDLE, // Default 0 READ = 2, // Explicitly set to 2 WRITE, // Automatically 3 ERROR = 10 // Explicitly set to 10 } state_e; // Define a new type 'state_e' state_e current_state; initial begin current_state = IDLE; $display("Initial state: %s (%0d)", current_state.name(), current_state); // IDLE (0) current_state = READ; $display("Current state: %s (%0d)", current_state.name(), current_state); // READ (2) current_state = state_e'(3); // Cast integer to enum type $display("Current state: %s (%0d)", current_state.name(), current_state); // WRITE (3) end endmodule ``` - `state_e.name()` is a built-in method to get the string name of an enum value. - Enums can be cast to and from integers. #### `typedef` (Type Definition) - Creates an alias for an existing data type. - Improves code readability and maintainability. ```systemverilog module typedef_example; typedef bit [7:0] byte_t; // Define 'byte_t' as an 8-bit signal typedef int address_t; // Define 'address_t' as an integer byte_t data_byte; address_t mem_address; initial begin data_byte = 'hFF; mem_address = 1024; $display("Data: 0x%0h, Address: %0d", data_byte, mem_address); end endmodule ``` #### `struct` (Structures) - Groups variables of different types into a single named unit. - Similar to C structs. ```systemverilog module struct_example; typedef struct packed { // 'packed' makes it bit-addressable bit [7:0] cmd; bit [15:0] addr; bit [31:0] data; } transaction_s; // Define a new type 'transaction_s' transaction_s my_transaction; initial begin my_transaction.cmd = 'h01; my_transaction.addr = 'h1000; my_transaction.data = 'hDEADBEEF; $display("Cmd: 0x%0h, Addr: 0x%0h, Data: 0x%0h", my_transaction.cmd, my_transaction.addr, my_transaction.data); // Can assign entire structs transaction_s another_transaction = my_transaction; another_transaction.data = 'hC0FFEE; $display("Another Data: 0x%0h", another_transaction.data); end endmodule ``` - `packed` keyword means the members are stored contiguously without gaps, useful for hardware interfaces. - `unpacked` struct members are stored in native word boundaries, which is the default if `packed` is not specified. #### `union` (Unions) - Allows different data types to share the same memory location. - Only one member of a union can be active at any given time. ```systemverilog module union_example; typedef union packed { // 'packed' for bit-addressable bit [31:0] int_val; bit [7:0] byte_array[4]; // Array of 4 bytes } data_u; data_u my_data; initial begin my_data.int_val = 'hAABBCCDD; $display("As integer: 0x%0h", my_data.int_val); // 0xAABBCCDD $display("As bytes: %0h %0h %0h %0h", my_data.byte_array[3], my_data.byte_array[2], my_data.byte_array[1], my_data.byte_array[0]); // AA BB CC DD (assuming big-endian for display) my_data.byte_array[0] = 'hEE; // Modify one byte $display("Int after byte modify: 0x%0h", my_data.int_val); // 0xAABBCCEE end endmodule ``` - `packed` unions are common for manipulating data at different granularities (e.g., bit, byte, word). ### SystemVerilog: Interfaces and Modports Interfaces are a crucial feature for connecting verification components to the DUT (Design Under Test) in a structured and reusable way, especially within an OOP testbench. #### Interfaces - Encapsulate communication signals and protocols into a single, reusable construct. - Improve readability and reduce connection errors. - Can contain wires, regs, parameters, functions, tasks, and even other interfaces. ```systemverilog // Define an interface interface axi_lite_if (input bit clk, input bit rst); logic awvalid; logic awready; logic [31:0] awaddr; logic wvalid; logic wready; logic [31:0] wdata; logic [3:0] wstrb; logic bvalid; logic bready; logic [1:0] bresp; logic arvalid; logic arready; logic [31:0] araddr; logic rvalid; logic rready; logic [31:0] rdata; logic [1:0] rresp; // A common clocking block for synchronization clocking cb_master @(posedge clk); default input #1step output #1; output awvalid, wvalid, arvalid, bready, rready; input awready, wready, bvalid, bresp, arready, rvalid, rdata, rresp; output awaddr, wdata, wstrb, araddr; endclocking clocking cb_slave @(posedge clk); default input #1step output #1; input awvalid, wvalid, arvalid, bready, rready; output awready, wready, bvalid, bresp, arready, rvalid, rdata, rresp; input awaddr, wdata, wstrb, araddr; endclocking // Task for a master to perform a write task master_write(input bit [31:0] addr, input bit [31:0] data); @(cb_master); cb_master.awvalid ### SystemVerilog OOP: Interview Questions #### Q1: What are the main differences between SystemVerilog OOP and general-purpose OOP languages like Python/Java? **A1:** While sharing core OOP principles, SystemVerilog OOP has key differences: - **Purpose:** Primarily for hardware verification (testbenches), not general application development. - **Execution Model:** Interacts heavily with event-driven simulation time, tasks can consume time (`#delay`). - **Pointers/Handles:** Objects are accessed via class handles, which are essentially pointers. `null` handles are common. - **Memory Management:** Automatic garbage collection for objects, but handles must be managed. - **Inheritance:** Only single inheritance is supported. - **Encapsulation:** Uses `local` and `protected` keywords, unlike Python's conventions. - **Concurrency:** Tasks and functions within classes can interact with parallel `always` and `initial` blocks. - **Randomization & Constraints:** Built-in features (`rand`, `constraint`, `randomize()`) for generating test stimuli. - **Interfaces/Modports:** Unique constructs for connecting verification components to hardware designs. #### Q2: Explain the role of `virtual` methods in SystemVerilog. **A2:** `virtual` methods enable polymorphism. When a method in a base class is declared `virtual`, and a derived class overrides it, calling that method through a base class handle will execute the version of the method defined in the *actual object type* it's pointing to, not the base class. This allows for dynamic method dispatch, where the specific method implementation is chosen at runtime based on the object's type, not the handle's type. This is fundamental for creating flexible and reusable verification components (e.g., in UVM). #### Q3: What is the purpose of `local` and `protected` keywords in SystemVerilog classes? **A3:** These keywords control access to class members (properties and methods), achieving encapsulation: - **`local`:** Members declared `local` are accessible *only within the class itself*. They are not visible to any derived classes or to outside code. This is for truly internal implementation details. - **`protected`:** Members declared `protected` are accessible *within the class itself and by any of its derived (sub)classes*. They are not visible to outside code. This allows subclasses to access and potentially modify internal state inherited from the base class, while still encapsulating it from external entities. By default, members are public. #### Q4: When would you use a `virtual class` and `pure virtual function`? **A4:** You would use a `virtual class` and `pure virtual function` to achieve abstraction and define an interface that concrete classes must adhere to. - **`virtual class`:** Used when you want to define a base class that cannot be instantiated directly. It serves as a blueprint for other classes, forcing them to implement certain behaviors. - **`pure virtual function`:** A `virtual function` declared with `= 0;` within a `virtual class`. It has no implementation in the `virtual class` itself. Any non-virtual class that `extends` this `virtual class` *must* provide an implementation for all `pure virtual functions`. If it doesn't, it also becomes a `virtual class`. This pattern ensures that all derived classes provide specific implementations for common operations, guaranteeing a consistent interface. #### Q5: Explain the benefits of using `interfaces` and `modports` in a SystemVerilog testbench. **A5:** - **Interfaces:** - **Encapsulation:** Group related signals, parameters, functions, and tasks into a single entity, simplifying connections. - **Reusability:** The interface can be reused across different modules and testbenches. - **Reduced Errors:** Reduces the chance of connection mismatches (e.g., wrong signal names, incorrect widths) by providing a single point of definition. - **Abstraction:** Hides the complexity of signal connections from the modules using the interface. - **Modports:** - **Directionality:** Explicitly define the direction of signals (input/output) from the perspective of the module or class connecting to the interface. - **Error Checking:** The compiler can check for incorrect signal usage (e.g., trying to drive an input signal), catching errors early. - **Role Definition:** Clearly indicates the role of a module (e.g., master, slave, monitor) within a protocol by specifying its view of the interface signals. Together, interfaces and modports significantly improve the modularity, readability, and robustness of SystemVerilog testbenches, especially in large-scale verification projects.