Chap5 Oops Concepts
Chap5 Oops Concepts
1: Introduction to Object-Oriented
Programming (OOP) in SystemVerilog, with some context and examples to help make it
practical.
🔧 Example in Verilog:
If your test needs 101 transactions? You must change the array size and recompile. Not
scalable.
🧠 Summary
Traditional Verilog testbenches are low-level and rigid.
OOP in SystemVerilog introduces:
You're doing great—this is a very important conceptual section. Let's go through Section 5.2:
"Think of Nouns, Not Verbs" slowly and clearly.
Instead of thinking about what actions your testbench performs (like create, send, check,
report), you should think about what your testbench is dealing with—the objects (or
"nouns") such as:
Transactions
Packets
Bus cycles
Commands
Data samples
These are the core units of communication in your testbench and between modules.
🔁 In the same way, a well-designed testbench hides low-level details and allows you to
work with higher-level objects (like Packet or Transaction) instead of constantly
manipulating signals directly.
Component Purpose
Generator Creates transactions
Driver Sends transactions to the DUT (Design Under Test)
Monitor Observes signals from DUT and creates received transactions
Scoreboard Compares expected vs. actual results
Transaction (or Packet) The data unit passed around and manipulated
These components are often implemented as classes in SystemVerilog.
You do:
The Packet class now bundles all related data together. The driver class is responsible for
knowing how to send that packet to the DUT.
Would you like to now move into actually writing a basic transaction class (Packet) and a
simple driver class to see this idea in practice?
You're progressing very well—now we're getting hands-on with your first real class in
SystemVerilog! Let’s break down Section 5.3: Your First Class and understand Sample 5.1
clearly and slowly.
class Transaction;
bit [31:0] addr, csm, data[8];
endclass: Transaction
This starts the definition of a new class called Transaction. It will bundle data and related
functionality into one reusable unit.
🧾 Variables / Properties
bit [31:0] addr, csm, data[8];
This class is modeling a generic "transaction" with address + data, common in bus protocols.
🔍 Function 1: display()
function void display();
$display("Transaction: %h", addr);
endfunction: display
🔍 Function 2: calc_csm()
function void calc_csm();
csm = addr ^ data.xor;
endfunction: calc_csm
This ends the class. The label : Transaction helps you trace which class block is closing,
especially when there are multiple nested blocks or long files.
📌 Style Note
Classes → Transaction, Packet → Start with uppercase.
Constants → DATA_WIDTH, MAX_SIZE → All uppercase.
Variables → addr, csm, opcode → Lowercase.
Perfect—we're now learning how to organize your classes using packages, which is a
crucial skill in SystemVerilog for writing clean, modular code.
Let’s carefully walk through Samples 5.2 and 5.3, step by step.
// File: abc.svh
package abc;
class Transaction;
// Class body (data + functions/tasks)
endclass
endpackage
endprogram
You're doing an excellent job following along—this is a very important section because it
clarifies the core OOP terms you'll use constantly when writing SystemVerilog testbenches.
Let's walk through Section 5.5: OOP Terminology step by step, with simplified
explanations and comparisons to regular Verilog.
🔧 Example in SystemVerilog
class House;
bit porch_light;
module test;
initial begin
House h1; // Declare a handle
h1 = new(); // Create the object
🧠 Breakdown:
The key OOP terms and how they map to Verilog concepts
Why we use classes (blueprints) to build objects (real things)
What a handle is (a reference to an object)
The meaning of properties and methods
Would you like to move to the next step, where we’ll start using new() and dynamic object
creation (Section 5.6), or do you want a short exercise to practice what we’ve learned so far?
You're continuing to move through the OOP basics really well. Now, in Section 5.6.1, you're
learning about something fundamental to using classes in SystemVerilog: handles and the
new() function.
Let’s break this down clearly and slowly so you understand exactly what’s happening.
But at this point, tr is not yet connected to an actual object. It’s just a reference and
is initialized to null by default.
🔹 What is new()?
The new() function constructs (creates) the object in memory and returns its address
(which the handle stores).
It:
o Allocates memory
o Initializes values (e.g., 0 for 2-state bit, X for 4-state logic)
o Returns a handle to the object
🔹 Analogy to C
If you've used C:
class Transaction;
bit [31:0] addr, csm;
bit [31:0] data[8];
endclass
module test;
initial begin
Transaction tr; // Declare a handle (set to null)
tr = new(); // Create (construct) a Transaction object in memory
end
endmodule
addr = 32 bits
csm = 32 bits
data[8] = 8 × 32 bits
Type info
Virtual method pointers
Possibly more memory if you use logic (4-state)
So new() allocates enough space for all this, just like an object constructor in C++ or a
class instantiation in Java.
✅ Summary
Concept Meaning
tr A handle (pointer) to an object
new() Allocates memory and creates an object
null The initial value of any handle before new()
Instantiation The act of creating a new object
Constructor Another term for the new() method
Would you like to move on to custom constructors (user-defined new() functions) next?
Awesome! You're now getting into custom constructors in SystemVerilog, a key step
toward writing reusable and clean object-oriented testbenches. Let's go through this slowly
and clearly.
🧠 What is a Custom Constructor in SystemVerilog?
The new() function is a special method in SystemVerilog classes.
It’s used to initialize class properties at the time of object creation.
You don’t specify a return type for new() — SystemVerilog automatically returns
the correct object type.
function new();
addr = 3; // set a fixed value
data = '{default:5}; // fill all elements with 5
endfunction
endclass
Explanation:
module test;
initial begin
Transaction tr;
tr = new(.a(10)); // 'addr' becomes 10, 'data[]' is all 5 (default)
end
endmodule
Key Points:
class Transaction;
logic [31:0] addr, csm, data[8];
endclass
class Driver;
Transaction tr;
function new();
tr = new(); // This calls Transaction’s new(), NOT Driver’s
endfunction
endclass
Even though the Driver class has its own new() function, the call tr = new(); invokes
Transaction’s constructor because tr is a Transaction handle.
Would you like to try creating your own class with a constructor that takes arguments, or
move on to Section 5.6.3: Separating declaration and construction?
You're doing a great job going step by step through the SystemVerilog OOP chapter. Let's
walk through Sections 5.6.3 and 5.6.4 together, as both are very important for writing
predictable and maintainable code.
Avoid this:
Transaction tr = new(); // BAD practice
1. It calls the constructor (new()) before the simulation enters the procedural block.
2. You might lose control over initialization order, especially when several objects
depend on each other.
3. If you’re in a non-automatic block, the object is constructed only once at time zero,
not each time the block runs.
✅ Recommended Practice:
⚠️Note: new() can take arguments (for constructors), but new[] can only take the array size.
🧠 Summary
Concept Bad Practice Good Practice
Transaction tr = Transaction tr; tr =
Object declaration + construction new(); new();
For creating one object Use new()
For creating array of
Use new[]
objects/values
Would you like to move on to the next section (likely about destructors or object usage), or
try writing your own example using both new() and new[]?
You're doing a great job staying focused through this chapter—Section 5.6.5 is crucial
because it teaches a concept that often confuses new SystemVerilog OOP users: the
difference between a handle and an object.
A handle is not the object itself. It’s just a reference (or pointer) to where the object lives in
memory.
🧠 What’s Happening?
People = Objects
Badges = Handles
You can hand your badge to someone else → like assigning t2 = t1
You can create a new badge → like t1 = new();
You can reuse or lose a badge → the handle can be reassigned or set to null
📌 Summary
Concept Meaning
Handle A reference or pointer to an object
Object The actual memory-allocated data structure
t2 = t1; Now both handles point to the same object
t1 = new(); Creates a new object; t1 points to it, t2 still points to old one
Dynamic object creation More flexible and memory-efficient than static arrays
Would you like to continue to Section 5.7: Object Deallocation, which explains what
happens to unused objects?
Great! You're now on Section 5.7 – Object Deallocation, which wraps up the basics of
memory management in SystemVerilog’s OOP model. Let’s break it down clearly and
slowly.
When your testbench creates thousands of transactions, memory usage can grow quickly. If
unused objects aren't cleaned up, your simulation may run out of memory.
So:
📍 Key Points:
Concept Meaning
Garbage Automatic memory cleanup of unreferenced objects
Concept Meaning
collection
Reference
Objects are deleted when no handles point to them
counting
null handle Explicitly clearing a handle (i.e., t = null;) helps GC
If objects are in linked structures or threads, you must clear those
Manual cleanup
references manually
To free them, you must set all handles to null or let the thread complete.
If nobody holds the badge, the organizer can free the seat (delete the object).
But if someone still holds the badge, even if the person is gone, the seat isn’t freed.
✅ Summary:
Objects live in memory until no handles point to them.
SystemVerilog automatically deallocates them — you don’t manually free() them
like in C.
But if you keep handles around (e.g., in arrays) or spawn threads, they stick
around — so be careful.
Would you like to try a small example using multiple objects and handle clearing? Or shall
we move on to Section 5.8: Class Members?
You're doing great so far! Now you're on Section 5.8 – Using Objects in SystemVerilog’s
OOP. This is a key part where everything you’ve learned—classes, objects, and handles—
starts being used in real code.
🔹 5.8 Using Objects
Once you've created an object using new(), you interact with it using the dot (.) operator
to access:
Properties (variables)
Methods (functions/tasks)
✅ This works just like how you'd access module members in Verilog (inst.signal)—but
here, you're using it on a class object.
Why?
Because if you ever change how addr is calculated or stored, you don’t have to change
every place it's used—only the method logic.
But…
❗ Reality in Testbenches
SystemVerilog testbenches are different from full-blown software systems. So:
Example:
class PCI_Packet;
protected bit [15:0] address;
function void set_addr(bit [15:0] a);
address = a;
endfunction
function bit [15:0] get_addr();
return address;
endfunction
endclass
In your own testbench, this is not necessary unless you need encapsulation for reuse or
security.
🔄 Summary
Concept Real-world Advice
Use . to access members obj.addr = 5; and obj.display();
Avoid unnecessary get/set methods Unless you're building reusable IP
Testbench goal = control, not hiding data So direct access is OK! ✅
Ready to move on to 5.9 Class Methods, where we dive deeper into how to write and use
functions/tasks in your classes?
Great! You're now at Section 5.9 – Class Methods, a natural continuation of using objects.
Let's break this down clearly.
🔹 What Is a Method?
Methods are how you make objects do things — print themselves, calculate values, send data,
etc.
class PCI_Tran;
bit [31:0] addr, data;
Then, we have:
Transaction t;
PCI_Tran pc;
initial begin
t = new(); // Construct a Transaction object
t.display(); // Calls Transaction::display()
🧠 Key Concepts
Even though both classes have a method named display(), SystemVerilog knows
which one to call based on the object handle.
This is an early look at something called polymorphism — the same method name
doing different things in different classes (we’ll go deeper into that in Chapter 8).
🧪 Output Example
🔄 Summary
Concept Example
Method = function/task in a class function void display()
Same method name in multiple classes is
t.display() vs. pc.display()
OK
Handle t → class Transaction, so it uses that
Calls resolved by handle type
version
Would you like to move on to Section 5.10 – Prototypes and Out-of-Body Methods next?
Perfect! Let's move into Section 5.10 – Prototypes and Out-of-Body Methods — a key part
of writing clean, modular OOP code in SystemVerilog.
🔹 What Is a Prototype?
A prototype is just the function or task header — basically, the method's declaration,
without its body.
📌 Syntax Breakdown
function [return_type] ClassName::FunctionName ([args]);
// function body
endfunction
✅ Summary
Term Meaning
By default, every time you call new() to create an object, it gets its own copy of the
class variables.
Example:
Let’s say you want to keep a counter of how many transactions were created. If you used
a global variable, every part of your testbench could see and modify it — even by mistake.
That’s risky.
Note: We access static members using the class name like Transaction::count, not through
an object like t.count.
📌 Summary
Would you like to move on to 5.12 Static Methods, which continue building on this
concept?
Here's a corrected and cleaned-up version of the code from Sample 5.14:
class Transaction;
static int count = 0; // Shared among all objects
int id; // Unique to each object
function new();
id = count; // Assign current count as this object's ID
count++; // Increment count for the next object
endfunction
endclass
module test;
Transaction t1, t2;
initial begin
t1 = new(); // First object
$display("First: id = %0d, count = %0d", t1.id, Transaction::count);
🔍 Key Concepts
📊 Visualizing It
After t1 = new();:
t1.id = 0
Transaction::count = 1
After t2 = new();:
t2.id = 1
Transaction::count = 2
So:
Would you like to proceed to 5.12 Static Methods, where we learn how to define
functions/tasks that belong to the class rather than any object?
Great! You're now diving into static methods, a powerful OOP concept in SystemVerilog.
A method (function or task) that belongs to the class itself, not to any particular
object.
It can only access static variables.
It can be called even before any object is created.
class Transaction;
static Config cfg;
static int count = 0;
int id;
// Static method
static function void display_statics();
if (cfg == null)
$display("ERROR: configuration not set");
else
$display("Transaction cfg.num_trans=%0d, count=%0d", cfg.num_trans,
count);
endfunction
endclass
module test;
Config cfg;
initial begin
cfg = new(42); // Create a Config object
Transaction::cfg = cfg; // Assign it to the static cfg
variable
Transaction::display_statics(); // Call static method without
creating any Transaction objects
end
endmodule
🔍 Key Points
Concept Description
static Config cfg; One shared config object for all Transaction instances
display_statics() Static method that reads cfg and count
Transaction::display_statics() Called using the class name, not an object
Because id is an instance variable, and no objects
Cannot access id
exist yet
Instance variables (like id) are only created when you new an object. If no object exists,
there's no memory allocated for those variables — so trying to access them would cause
errors.
Static methods are like announcements made on a loudspeaker — anyone can hear
them, even if no person is standing nearby.
Instance methods are like talking to someone — there has to be a specific person
(object) there.
Would you like to move on to 5.12 Inheritance, which introduces how one class can reuse
and extend another?
Great! You're now exploring SystemVerilog scoping rules, which are essential for writing
clean, bug-free testbenches. Let's summarize and clarify the key points from Section 5.12
using your provided content:
Module
Program
Task
Function
Class
Begin-end block
for / foreach loop
If a variable is not declared in a local scope, SystemVerilog might find a matching variable
in a higher scope, often silently and incorrectly.
If you move the class to a package, it won’t see program-level variables like i.
Now i must be declared inside display() — helps catch the bug at compile time.
🔄 Best Practices
Tip Why
Would you like to proceed to 5.13 Inheritance, where we begin reusing and extending
classes?
Great, you're diving into 5.12.1: What is this? — a fundamental concept in object-oriented
programming (OOP), especially relevant when writing constructors or methods that deal with
name shadowing.
🔁 Without this:
class Transaction;
string name;
function new(string name);
name = name; // ❌ Ambiguous: both refer to the argument!
endfunction
endclass
✅ With this:
class Transaction;
string name;
function new(string name);
this.name = name; // ✅ Left = class variable, Right = argument
endfunction
endclass
class Transaction;
bit [31:0] addr, crc, data[8];
Statistics stats;
endclass
class Statistics;
time startT, stopT;
static int ntrans = 0;
static time total_elapsed_time;
endclass
This shows one class using another class via a member object (Statistics stats;) —
covered more in 5.13, but here it introduces the this keyword for clear variable access.
🧠 Note on Readability
Both are valid — it’s a style choice, though using this is standard in many OOP languages
and helpful for auto-generated code and IDEs.
Would you like to move on to 5.13: Using One Class Inside Another?
Here’s the full SystemVerilog code for the example discussed in Section 5.13, including
both the Statistics class and the Transaction class that contains it:
// Testbench module
module test;
Transaction t;
initial begin
t = new(); // Create a Transaction object
t.transmit_me(); // Simulate sending the transaction
Would you like me to modify this to show multiple transactions, or add assertions/debug
printouts inside the methods?
This section (5.13.1) is emphasizing good design principles for using classes in
SystemVerilog. Here's the core idea:
Too Small?
Avoid creating a class that only holds one or two variables unless there’s a
compelling reason (e.g., frequent reuse, encapsulated behavior like randomization
with constraints).
class Addr;
rand bit [31:0] a;
endclass
Creating tiny classes like this for every field adds unnecessary complexity, extra
indirection, and harder debugging.
Too Big?
If a class has too many methods and variables (often more than a page long),
consider splitting it into logical units or sub-classes for clarity, reusability, and
maintenance.
🔁 Practical Guideline:
"A class should represent a coherent concept. If it's too fine-grained or overly broad,
refactor."
Would you like a real-world SystemVerilog example that shows refactoring a too-small or
too-big class into a better structure?
When a class refers to another class that is not yet defined, the compiler will raise an error. You can
solve this by using a forward class declaration with typedef.
✅ Solution:
systemverilog
CopyEdit
typedef class Statistics; // Forward declaration
class Transaction;
Statistics stats; // OK now
endclass
class Statistics;
// Definition comes later
endclass
✅ Class hierarchy
✅ typedef class forward declaration
✅ Dynamic object creation
✅ Static variables and methods
✅ Passing objects via handles
✅ Object composition (class inside another)
function new();
this.id = id_counter++;
stats = new(); // instantiate inner object
endfunction
task execute();
$display("Transaction ID %0d starting...", id);
stats.start();
#10; // simulate some activity
stats.stop();
$display("Transaction ID %0d completed.", id);
endtask
endclass
// Test module
module test;
Transaction t1, t2;
Transmitter tx;
initial begin
tx = new();
t1 = new();
t2 = new();
tx.send(t1);
tx.send(t2);
🧠 Concepts Illustrated:
Thanks! Based on Section 5.14.1 you’ve quoted, here’s a SystemVerilog example that
demonstrates the key concept:
✅ When you pass an object to a method, you're passing the handle, not the object itself.
✅ This allows the method to modify the object's contents—but not the handle itself
(unless passed by ref).
module test;
Transaction t;
initial begin
t = new();
t.addr = 32'hDEADBEEF;
t.data = '{32'hAAAA_BBBB, 32'h1234_5678, 32'h0, 32'hFFFF_FFFF};
$display("Before transmit:");
t.display();
$display("After transmit:");
t.display();
end
endmodule
Concept Description
transmit(input Transaction
tr) The object is passed by value (i.e., the handle is copied).
Would you like to see the ref version next, showing how to modify the handle itself?
Great — this section clearly explains the difference between modifying the object vs.
modifying the handle that points to the object in SystemVerilog.
Here’s a complete working example that shows both the wrong way and the correct way to
create and assign a new object from inside a function.
module test_bad;
Transaction t;
initial begin
create(t); // Passes NULL to create()
if (t == null)
$display("t is still NULL after create()!"); // Will print this
else
$display("t.addr = %0d", t.addr);
end
endmodule
module test_good;
Transaction t;
initial begin
create(t); // Updates t to point to a new Transaction
if (t == null)
$display("t is still NULL!");
else
$display("t.addr = %0d", t.addr); // Will print 42
end
endmodule
🧠 Summary
Situation Use input (default) Use ref
Would you like to see how to write a method that creates and returns a new object instead of
modifying one via ref?
Great follow-up — let’s clarify how the behavior changes with and without ref, especially
in terms of:
When you pass a handle without ref, a copy of the handle is passed to the function or task.
That means:
✅ You can change the fields of the object the handle points to.
❌ You cannot reassign the caller’s handle to point to a new object — the change is
local.
Transaction t;
initial begin
t = new();
t.addr = 1;
update(t);
✅ Explanation:
The object field addr was updated to 99 using the original object.
But then a new object was created, and only the local copy of the handle (tr)
pointed to it.
So the change to addr = 42 went to the new object — and the caller’s handle t still
points to the original object.
When you pass the handle with ref, any reassignment or modification affects the actual
handle in the caller’s scope.
Transaction t;
initial begin
update(t);
✅ Explanation:
🧠 Summary
This leads to corruption of data — for example, all your transactions might appear to have
the same addr.
❗ What’s wrong:
t is created once.
Each iteration modifies the same object.
If transmit() uses the object across time (e.g., with #delays or fork), you end up
changing the object while it's still in use.
You may see different $display values, but all the data actually transmitted might be
the last value.
This is powerful but dangerous when misused — just like passing around references or
pointers in C++/Java.
✅ Final Tip:
"Is this object shared across time or threads? If yes — create a fresh one."
Would you like a visual flowchart or diagram to illustrate this handle vs object reuse issue?
Certainly! To help you visualize how SystemVerilog handles and objects interact, especially
in the context of object reuse in loops, here's a clear diagram illustrating the concepts:
To ensure each transaction is independent and avoids overwriting data, instantiate a new
object in each loop iteration:
By doing this, each transaction has its own unique object, preventing unintended data sharing
between iterations.
If you have further questions or need more detailed explanations on any part, feel free to ask!
You're absolutely right, and this section highlights an important concept in SystemVerilog
OOP: understanding the difference between objects and handles, especially in arrays.
In SystemVerilog:
initial begin
// Step 1: Create a new object at each index
foreach (tarray[i]) begin
tarray[i] = new();
tarray[i].addr = $random;
end
⚠️Common Pitfalls
Mistake Consequence
Not calling new() for each index Accessing tarray[i] will cause a null pointer error
Assuming you can new() the whole Won’t work. You must initialize each handle
array individually
💡 Summary
Would you like to see how to use dynamic arrays or queues of handles for even more
flexibility?
Great! You're exploring an important concept in SystemVerilog OOP: how object copying
works, and its implications when your class contains handles to other objects.
Shallow Copy: Only the handles and simple variables are copied. If your object
contains a handle to another object (e.g., Statistics inside Transaction), then only
the handle is copied — not the object it points to.
Deep Copy: You manually clone the lower-level objects to ensure both outer and
inner objects are independent.
class Transaction;
bit [31:0] addr, csm, data [8];
static int count = 0;
int id;
Statistics stats;
function new();
stats = new(); // Create a new Statistics object
id = count++; // Give unique ID
endfunction
endclass
initial begin
src = new();
src.stats.startT = 42;
⚠️What Happened?
class Transaction;
bit [31:0] addr, csm, data [8];
static int count = 0;
int id;
Statistics stats;
function new();
stats = new();
id = count++;
endfunction
initial begin
src = new();
src.stats.startT = 42;
✅ Summary
You're diving into a very practical topic in SystemVerilog OOP: visibility control —
specifically, when to use public, local, or protected access to class members.
You often need full control over object internals (e.g., to force errors, adjust state).
Making class members local or protected would add complexity, especially
during debugging or fault injection.
🔧 Example
class Transaction;
bit [31:0] addr;
bit [31:0] data;
bit [31:0] checksum;
// Public method
function void set_fields(bit [31:0] a, b);
addr = a;
data = b;
compute_checksum();
endfunction
endclass
Transaction t;
initial begin
t = new();
t.set_fields(32'hABCD, 32'h1234);
You protect internal data to avoid bugs and promote stable APIs.
But in testbenches:
initial begin
t = new();
t.checksum = 32'h1234; // ❌ Error: not accessible
end
To change it, you'd have to write a specific method just for test purposes — extra work!
✅ Summary
When to Use Use Case
public (default) Best for testbenches – easy debugging, injecting faults, fast development
local Use only when you need strict encapsulation within the class
protected Use in class hierarchies (e.g., base/derived classes sharing internals)
Would you like an example showing how protected is used in a class hierarchy (like a base
class and derived one)?
You're now stepping into the core structure of SystemVerilog testbenches — the layered
testbench architecture, which is a foundational concept in UVM (Universal Verification
Methodology) and object-oriented testbench design in general.
Let’s walk through the concept from this section and what it means in practice.
🔑 Key Concept
Every layer is a class. Every transaction is an object. Communication is done via
method calls or mailboxes (queues).
Would you like a complete code example showing a generator, driver, and mailbox in
SystemVerilog?