0% found this document useful (0 votes)
18 views50 pages

Chap5 Oops Concepts

Section 5.1 introduces Object-Oriented Programming (OOP) in SystemVerilog, highlighting its advantages over traditional Verilog, such as combining data and behavior in classes and enabling dynamic memory allocation. OOP enhances testbench scalability and maintainability by allowing the use of objects instead of manual array management. The section emphasizes the importance of focusing on nouns (data objects) rather than verbs (actions) to create a more abstract and modular testbench structure.

Uploaded by

Renuga Devi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
18 views50 pages

Chap5 Oops Concepts

Section 5.1 introduces Object-Oriented Programming (OOP) in SystemVerilog, highlighting its advantages over traditional Verilog, such as combining data and behavior in classes and enabling dynamic memory allocation. OOP enhances testbench scalability and maintainability by allowing the use of objects instead of manual array management. The section emphasizes the importance of focusing on nouns (data objects) rather than verbs (actions) to create a more abstract and modular testbench structure.

Uploaded by

Renuga Devi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 50

Here's a clear explanation of the key ideas in Section 5.

1: Introduction to Object-Oriented
Programming (OOP) in SystemVerilog, with some context and examples to help make it
practical.

📘 What’s the Problem with Traditional Verilog


(Procedural Style)?
Verilog and languages like C separate:

 Data (e.g., addresses, commands, data values)


 Code (the logic that acts on the data)

This leads to:

 Scattered data structures (arrays for each field of a transaction)


 Tight coupling between testbench and specific hardware signals
 Static limitations (e.g., array size fixed at compile time)

🔧 Example in Verilog:

To store 100 transactions:

reg [31:0] addr_array [0:99];


reg [31:0] data_array [0:99];
reg [2:0] cmd_array [0:99];
// You must manage all arrays manually, index by index.

If your test needs 101 transactions? You must change the array size and recompile. Not
scalable.

💡 What Does OOP Offer in SystemVerilog?


Object-Oriented Programming allows:

 Combining data and behavior into a single unit (a class).


 Creating dynamic objects (no need to fix sizes at compile time).
 Writing cleaner, higher-level code that manipulates transactions, not just bits.

🎯 Benefits of Using OOP in SystemVerilog Testbenches:


Procedural Verilog OOP in SystemVerilog
Manually manage arrays Use objects (dynamic)
Procedural Verilog OOP in SystemVerilog
Low-level signal toggling Transaction-based modeling
Code and data are separate Code and data are together (in classes)
Hard to maintain Easier to reuse and update

🧾 Simple SystemVerilog OOP Example


Define a transaction class:
class Transaction;
rand bit [31:0] addr;
rand bit [31:0] data;
rand bit [2:0] cmd;

function void print();


$display("Addr: %0h, Data: %0h, Cmd: %0d", addr, data, cmd);
endfunction
endclass

Use it in your testbench:


initial begin
Transaction t;
t = new();
t.randomize();
t.print();
end

🔍 What this does:

 Defines a template for transactions.


 Allows randomization, encapsulation, and reuse.
 Replaces the need for 3 parallel arrays.

🧠 Summary
Traditional Verilog testbenches are low-level and rigid.
OOP in SystemVerilog introduces:

 Classes to encapsulate transactions.


 Dynamic memory allocation.
 More abstract, maintainable, and scalable testbenches.

This shift is especially important in UVM (Universal Verification Methodology), where


everything is class-based.
Would you like a complete example of an OOP-based testbench using classes?

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.

🎯 Key Concept: Focus on Nouns, Not Verbs


What it Means:

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.

🚗 Analogy: Testbench is Like a Modern Car


 Old cars needed the driver to manually control many low-level things (like engine
timing or choke).
 Modern cars hide the complexity—you just turn the key, press gas, and go.

🔁 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.

🧱 Structure of a Modern Testbench (in OOP style)


A testbench is broken into components, each responsible for one part of the verification
process.

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.

🧾 Example: A Transaction-Oriented View


Instead of:

// Procedural style (verb-focused)


write_addr = 32'h1000;
write_data = 32'hDEADBEEF;
write_enable = 1;

You do:

// OOP style (noun-focused)


Packet pkt;
pkt = new();
pkt.addr = 32'h1000;
pkt.data = 32'hDEADBEEF;
pkt.write = 1;
driver.send(pkt);

The Packet class now bundles all related data together. The driver class is responsible for
knowing how to send that packet to the DUT.

🧠 Why Is This Better?


 Cleaner and more abstract code
 Easier to maintain and debug
 Testbench components are reusable
 You can randomize, print, and track a transaction object easily
 Test logic is decoupled from design details

✅ Summary of This Step


 Think in nouns: focus on what data you're working with (transactions).
 Each class should model either:
o A data object (like a packet), or
o A control block (like a driver or monitor)
 This way, your testbench becomes modular, abstract, and easy to scale.

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.

📦 Sample 5.1: A Simple Transaction Class


Here’s the class from the book:

class Transaction;
bit [31:0] addr, csm, data[8];

function void display();


$display("Transaction: %h", addr);
endfunction: display

function void calc_csm();


csm = addr ^ data.xor;
endfunction: calc_csm

endclass: Transaction

🧠 What’s Happening Here?


✅ Class Definition
class 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];

 addr: A 32-bit address.


 csm: A checksum value (calculated later).
 data[8]: An array of eight 32-bit data values.

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

 Prints the value of addr.


 Note: The label : display at the end helps match this endfunction to its function
name—very helpful when blocks are nested.

🔍 Function 2: calc_csm()
function void calc_csm();
csm = addr ^ data.xor;
endfunction: calc_csm

 Calculates a simple checksum using XOR:


o addr ^ data.xor means XOR the address with the XOR of all elements in
data.
o data.xor is a built-in SystemVerilog reduction method that XORs all array
elements together.
 The result is stored in csm.

✅ Class End Label


endclass: Transaction

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.

You’re free to follow any naming style, but consistency is key.

✅ Next Step: How to Use This Class


Would you like to see how to create an object from this class and use the display and
calc_csm methods in a testbench example?

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.

📦 Sample 5.2: Declaring a Class Inside a Package


This example shows how to define a class (Transaction) inside a package.

// File: abc.svh
package abc;

class Transaction;
// Class body (data + functions/tasks)
endclass

endpackage

🧠 Why Use a Package?

 Groups related definitions (classes, parameters, functions) into a single namespace


 Makes your testbench modular and reusable
 Allows multiple files to import and share the same class

🔁 How to Use the Package in Another File (Sample 5.3)


program automatic test;
import abc::*; // Bring in everything from package abc

Transaction tr; // Declare an object of type Transaction


// Test code goes here

endprogram

🧠 Explanation of Key Lines:


 import abc::*;
o Brings in everything (*) from the package named abc.
o Now you can use Transaction without writing abc::Transaction.
 Transaction tr;
o Declares a variable (handle) of class Transaction.
 program automatic test;
o A program block is used in SystemVerilog testbenches (we'll cover this more
later).
o automatic means each execution gets its own storage (just like automatic
variables in a task).
✅ Summary So Far
Concept What it Does
package Groups classes and other definitions together
import abc::* Makes everything in abc available in your file
Transaction tr; Declares an object (handle) of class Transaction

🧪 Want to Try It?


Would you like me to show you a full example with:

 A package file that defines the Transaction class, and


 A test file that imports it, creates a transaction, and calls its methods?

This will help solidify what you've just learned.

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.

📘 OOP Terms in SystemVerilog — Simplified with Verilog


Comparison
OOP Term What It Means Verilog Analogy
Class A blueprint for creating objects. Like a module
Object An actual instance created from a class. Like an instantiated module
Handle A reference/pointer to an object. Like a module instance name
Property A variable inside a class. Like a reg or wire
Method A function or task inside a class. Like a task or function
Prototype The function/task header (no body yet). Like a function declaration

🏠 Analogy: Class vs Object vs Handle


Think of a Class Like a House Blueprint:

 🏗️Class: The blueprint of a house (not a real house yet).


 🏠 Object: A real house built using the blueprint.
 📬 Handle: The house’s address. It uniquely identifies the object.

Inside the House:


 💡 Properties: Lights, temperature, door locks — variables that store state.
 🔘 Methods: The switches or remotes — functions that change or use those properties.

🔧 Example in SystemVerilog
class House;
bit porch_light;

function void turn_on_porch_light();


porch_light = 1;
endfunction
endclass

module test;
initial begin
House h1; // Declare a handle
h1 = new(); // Create the object

h1.turn_on_porch_light(); // Call a method


end
endmodule

🧠 Breakdown:

 House is the class (like a blueprint).


 h1 is a handle (like a house address).
 new() constructs the actual object (the real house).
 turn_on_porch_light() is a method that sets the state.

🔄 Verilog vs OOP: When They "Act" Matters


Feature Verilog Modules SystemVerilog Classes
Created at Compile time Run time
Used for Structural design Testbench behavior
Reusability Limited High (via OOP features)

✅ Summary of This Step


You now understand:

 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.

🧠 Key Concepts in This Section


🔹 What is a Handle?

 A handle is a pointer to an object in SystemVerilog.


 Example:
 Transaction tr; // 'tr' is a handle to a Transaction object

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:

 Transaction tr; is like Transaction* tr = NULL;


 tr = new(); is similar to tr = malloc(sizeof(Transaction)); — it allocates
memory dynamically.

🧾 Sample 5.4 (Implied in the Text)


Let me write the code that this section is describing:

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

🧠 What Happens in Memory?


Assuming:

 addr = 32 bits
 csm = 32 bits
 data[8] = 8 × 32 bits

That’s 10 × 32-bit words = 40 bytes.

But SystemVerilog also adds some internal housekeeping:

 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.

✅ Sample 5.5: Basic Custom Constructor


class Transaction;
logic [31:0] addr, csm, data[8];

function new();
addr = 3; // set a fixed value
data = '{default:5}; // fill all elements with 5
endfunction
endclass

Explanation:

 When you call Transaction tr = new();, it:


1. Allocates memory
2. Sets addr = 3
3. Sets all 8 data elements to 5
4. Leaves csm uninitialized (default = X)

✅ Sample 5.6: Constructor with Arguments


class Transaction;
logic [31:0] addr, csm, data[8];

function new(input logic [31:0] a = 3, d = 5);


addr = a;
data = '{default:d};
endfunction
endclass

module test;
initial begin
Transaction tr;
tr = new(.a(10)); // 'addr' becomes 10, 'data[]' is all 5 (default)
end
endmodule

Key Points:

 You can pass arguments to new() just like to any function.


 If you don't pass an argument, the default value is used.
🔎 How Does SV Know Which new() to Use?

SystemVerilog chooses the constructor based on the handle’s type.

See Sample 5.7:

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.

🧠 Summary of Section 5.6.2 and 5.6.3


Concept Explanation
new() Special function to construct and initialize objects
Custom constructor You define new() with your own initialization logic
Default values Let you avoid repeating code when default setup is fine
Constructor with args Lets you pass custom values when building an object
Handle type decides SV uses the class of the handle on the left to choose the constructor
new() to call

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.

🔹 5.6.3: Separating the Declaration and Construction


🧠 Key Idea:

Avoid this:
Transaction tr = new(); // BAD practice

While legal, it's not recommended because:

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:

Instead, separate declaration and construction like this:

Transaction tr; // Declare handle


initial begin
tr = new(); // Construct object inside a procedural block
end

This gives you:

 Precise control over when objects are constructed.


 Better clarity in simulation flow and debug.

🔹 5.6.4: The Difference Between new() and new[]


Both new() and new[] are used to allocate memory, but they serve very different purposes.

Expression Purpose Used For


new() Construct a single object Classes (objects)
new[] Allocate a dynamic array Arrays of values

✅ Example of new() (for classes):


Packet p;
p = new(); // Creates ONE Packet object

✅ Example of new[] (for arrays):


int my_array[]; // Declare dynamic array
my_array = new[10]; // Allocate space for 10 integers

⚠️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.

Let's walk through it step-by-step.

🔹 5.6.5: Getting a Handle on Objects


✅ Key Principle:

A handle is not the object itself. It’s just a reference (or pointer) to where the object lives in
memory.

📌 Sample 5.8 — Allocating Multiple Objects


Transaction t1, t2; // Declare two handles

t1 = new(); // Create 1st object


t2 = t1; // t2 now points to the same object as t1
t1 = new(); // Create 2nd object; t1 now points to a new object

🧠 What’s Happening?

Step Handle t1 Handle t2 Objects in Memory


After t1 = new(); → Obj1 null 1 object
After t2 = t1; → Obj1 → Obj1 still 1 object
After t1 = new(); → Obj2 → Obj1 now 2 objects

 t2still points to the first object


 t1now points to a second object
 Both handles are pointing to separate memory locations
🧠 Real-World Analogy (from the book):

Think of badges at a conference.

 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

🔍 Why is this useful?


In traditional Verilog, you had to predefine a fixed number of "things" (like transactions):

Transaction tr_array[100]; // Fixed-size array

If you needed 101, you’d be stuck.

✅ But with dynamic objects, you can:

 Create as many as needed during simulation:


 for (int i = 0; i < num_trans; i++) begin
 tr = new(); // Create on demand
 // do something with tr
 end
 Reuse handles to point to different objects as needed
 Dynamically manage memory (more on that in the next section: deallocation)

📌 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.

🔹 5.7 Object Deallocation – Reclaiming Memory


🧠 What’s the problem?

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.

✅ How does SystemVerilog handle this?

Through Garbage Collection — an automatic process that:

 Tracks handles (pointers) to objects.


 Frees the object’s memory when no handles point to it.

This is called reference counting (conceptually, though simulators might optimize it


differently).

📌 Example (Sample 5.9, explained in steps):


Transaction t; // Declare a handle

t = new(); // Creates object #1

t = new(); // Creates object #2, t now points to it, object #1 has no


handle → deleted

t = null; // t points to nothing, object #2 has no handle → deleted

So:

 After t = new(); (second time), object #1 is unreachable → deleted.


 After t = null; → object #2 is unreachable → deleted.

📍 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

🚫 When Objects Aren’t Deleted Automatically:

1. Still referenced in:


o Arrays
o Queues
o Linked lists
o Class properties
2. Active threads (forked processes inside the object)

To free them, you must set all handles to null or let the thread complete.

🔄 Analogy Recap (from 5.6.5):


Think of each object as a person at a conference, and the badge as the handle.

 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)

🧾 Sample 5.10 Explained


Transaction t; // Declare a handle
t = new(); // Construct a new object (allocate memory)
t.addr = 32'h42; // Set the value of the addr variable
t.display(); // Call a method (function) on the object

✅ This works just like how you'd access module members in Verilog (inst.signal)—but
here, you're using it on a class object.

🧠 OOP Best Practice: Encapsulation


In strict object-oriented programming (OOP), you're not supposed to access class variables
directly. Instead, you should use:

 get_addr() – to read a variable


 set_addr(val) – to set a variable

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:

 Direct variable access is allowed and encouraged in testbenches.


 This is because:
o You want maximum control over your test data.
o You're often using randomization to generate values (addr = $urandom();).
o Hiding variables behind functions makes constrained-random testing
harder.

✅ When to Use Getters/Setters (get/put methods)


Only when you're:

 Developing reusable Verification IP (VIP)


 Selling or sharing your class with others as a black box
 Trying to enforce data integrity

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?

In SystemVerilog, a method is just a:


 function or
 task

…that is defined inside a class.

Methods are how you make objects do things — print themselves, calculate values, send data,
etc.

🧾 Sample 5.11 – Explained


class Transaction;
bit [31:0] addr, csm, data [8];

function void display();


$display("@%0t: TR addr=%h, csm=%h, data=%p", $time, addr, csm, data);
endfunction
endclass

 This is a method display() inside class Transaction.


 %p is used to print the whole array data.

class PCI_Tran;
bit [31:0] addr, data;

function void display();


$display("@%0t: PCI: addr=%h, data=%h", $time, addr, data);
endfunction
endclass

 Another class with its own method named display().

Then, we have:

Transaction t;
PCI_Tran pc;

initial begin
t = new(); // Construct a Transaction object
t.display(); // Calls Transaction::display()

pc = new(); // Construct a PCI_Tran object


pc.display(); // Calls PCI_Tran::display()
end

🧠 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

The simulation output might look something like:

@0: TR addr=00000000, csm=xxxxxxxx, data='{0, 0, 0, 0, 0, 0, 0, 0}


@0: PCI: addr=00000000, data=00000000

(Assuming no initialization of variables, so they default to 0 or X.)

🔄 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.

In SystemVerilog, especially for large classes, it's a good idea to:

 Declare (prototype) the method inside the class


 Define (write the full body) the method outside the class

This makes the class declaration cleaner and easier to read.

🧾 Example: Declaring vs. Defining a Method

✅ Prototype inside class:


class Transaction;
bit [31:0] addr;
function void display(); // Just a prototype (no body here)
endclass
✅ Out-of-body definition:
function void Transaction::display();
$display("Transaction addr = %h", addr);
endfunction

 Transaction::display() tells SystemVerilog:


This is the method display() that belongs to the Transaction class.

🧠 Why Use This?

1. Cleaner class definitions: Especially if your class has lots of methods.


2. Easier maintenance: You can organize code like a header + source file model.
3. Clearer interface: You can see what the class does without reading every method
body.

📌 Syntax Breakdown
function [return_type] ClassName::FunctionName ([args]);
// function body
endfunction

Likewise for tasks:

task ClassName::TaskName ([args]);


// task body
endtask

🧪 Example With Task


class Packet;
bit [15:0] src, dest;
task print(); // Prototype only
endclass

task Packet::print(); // Body outside


$display("src = %h, dest = %h", src, dest);
endtask

✅ Summary

Term Meaning

Prototype Declares a method inside a class (no body)

Out-of-body method Defines the method body outside the class


Term Meaning

ClassName::methodName() Links the method to the class

Would you like to continue to Section 5.11 – Class Data Initialization?

Great — you're doing an excellent job progressing through the chapter!

🔹 5.11 Static Variables vs. Global Variables


This section introduces the static keyword — an essential tool for creating shared data
across all objects of a class without using global variables.

🔸 Local vs. Shared Variables

 By default, every time you call new() to create an object, it gets its own copy of the
class variables.

Example:

Transaction t1 = new(); // has addr, csm, data


Transaction t2 = new(); // has its own addr, csm, data

These two objects do not share any of their variables.

🔸 What if you want to share?

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.

✅ Static Variable to the Rescue

A static variable in a class is:

 Shared among all objects of the class


 Stored only once, not per object
 Not tied to any particular object
🧾 Example: Static Variable to Count Transactions
class Transaction;
static int count = 0; // Shared across all objects
function new();
count++; // Every time a new object is created, increment
endfunction
endclass

You could then print how many transactions were created:

$display("Total transactions: %0d", Transaction::count);

Note: We access static members using the class name like Transaction::count, not through
an object like t.count.

❗ Don’t Confuse With Global Variables

Global Variable Static Class Variable


Visible everywhere Visible only in the class
Pollutes namespace Encapsulated (tidier)
Anyone can modify Controlled access

📌 Summary

 Use static to create shared variables within a class.


 Avoid global variables unless absolutely necessary.
 Access static variables using ClassName::variable.

Would you like to move on to 5.12 Static Methods, which continue building on this
concept?

Great — you’re moving through this OOP chapter in SystemVerilog smoothly!

🔹 5.11.1 A Simple Static Variable


This section expands on what we just discussed about static variables by applying it in a
practical use case: assigning unique IDs to each object when it's created.
🧾 Sample Code Breakdown: Assign Unique IDs to Each Object

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);

t2 = new(); // Second object


$display("Second: id = %0d, count = %0d", t2.id, Transaction::count);
end
endmodule

🔍 Key Concepts

Feature What it does


static int count One shared variable for all objects
int id Each object has its own unique id
new() Constructor sets id = count, then increments count
Transaction::count Access static variable via class name

📊 Visualizing It

After t1 = new();:

 t1.id = 0
 Transaction::count = 1

After t2 = new();:

 t2.id = 1
 Transaction::count = 2

So:

 count is shared and keeps incrementing.


 id is unique per object.
This is very useful in testbenches for tracking which object is doing what (e.g., tracing
packets or transactions).

✅ Why this is Better Than Globals

 No pollution of global scope


 Easier to debug and trace
 Self-contained logic within the class

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.

🔹 5.11.4 Static Methods


✅ What is a Static Method?

A static method is:

 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.

🔧 Sample 5.17: Explained


class Config;
int num_trans;
function new(int val);
num_trans = val;
endfunction
endclass

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

And the calling code:

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

❗ Why Can't Static Methods Access Instance Variables?

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.

🧠 Think of It Like This:

 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:

🔍 5.12 Scoping Rules — Summary


🔹 What Is a Scope?

A scope is any block of code that can contain variables:

 Module
 Program
 Task
 Function
 Class
 Begin-end block
 for / foreach loop

Each of these can declare its own local variables.

✅ Variable Lookup in SystemVerilog

SystemVerilog resolves variable names using hierarchical lookup:

1. Starts at the current scope.


2. If not found, it goes upward through the enclosing scopes.
3. You can use $root to access a global scope.

$root.top.limit // Absolute path to variable

⚠️Danger of Name Clashes

If a variable is not declared in a local scope, SystemVerilog might find a matching variable
in a higher scope, often silently and incorrectly.

❌ Example (Sample 5.19):


program automatic test;
int i;
class Bad;
logic [31:0] data[];
function void display();
// Forgot to declare i
for (i = 0; i < data.size(); i++)
$display("data[%0d] = %x", i, data[i]);
endfunction
endclass
endprogram

 iis not declared in the function, so it uses test.i.


 Modifies a global variable unintentionally. Big debugging headache.

✅ Fix: Encapsulation in a Package

If you move the class to a package, it won’t see program-level variables like i.

✔️Example (Sample 5.20):


package Better;
class Bad;
logic [31:0] data[];
function void display();
for (i = 0; i < data.size(); i++) // ❌ Compile error now
$display("data[%0d] = %x", i, data[i]);
endfunction
endclass
endpackage

Now i must be declared inside display() — helps catch the bug at compile time.

🔄 Best Practices

Tip Why

Declare variables at the innermost scope Prevents accidental reuse/conflicts

Use different variable names in different scopes Improves clarity

Move classes to packages Encapsulates logic and avoids name collisions

Avoid depending on $root references They reduce modularity and portability

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.

✅ What is this in SystemVerilog?


The keyword this is used to refer explicitly to a class member (variable or method) when
there’s a naming conflict with local variables or function arguments.

📌 Common Use Case: Constructor Argument Name == Class Variable

Here's the classic example:

🔁 Without this:
class Transaction;
string name;
function new(string name);
name = name; // ❌ Ambiguous: both refer to the argument!
endfunction
endclass

 This assigns the parameter to itself, not to the class member.

✅ With this:
class Transaction;
string name;
function new(string name);
this.name = name; // ✅ Left = class variable, Right = argument
endfunction
endclass

 this.name refers to the class variable.


 name alone refers to the function argument.
 Now it works as intended.

📦 Sample 5.21 Recap (paraphrased)

You're likely referencing this sample:

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

Some developers like using identical names for clarity:

function new(string name);


this.name = name;
endfunction

Others think it's confusing or lazy, and prefer:

function new(string name_input);


name = name_input;
endfunction

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:

✅ Full SystemVerilog Code: Class Containment Example


// Define the Statistics class
class Statistics;
time startT; // Start time of the transaction
static int ntrans = 0; // Total number of transactions
static time total_elapsed_time = 0; // Cumulative time

// Start the timer


function void start();
startT = $time;
endfunction

// Stop the timer and update counters


function void stop();
time how_long = $time - startT;
ntrans++;
total_elapsed_time += how_long;
endfunction
endclass

// Define the Transaction class that contains a Statistics object


class Transaction;
bit [31:0] addr, csm, data[8]; // Transaction data
Statistics stats; // Handle to Statistics object

// Constructor to instantiate Statistics


function new();
stats = new();
endfunction

// Simulate transmitting the transaction and track timing


task transmit_me();
stats.start(); // Record start time
#100; // Simulate transmission delay
stats.stop(); // Record stop time and update stats
endtask
endclass

// Testbench module
module test;
Transaction t;

initial begin
t = new(); // Create a Transaction object
t.transmit_me(); // Simulate sending the transaction

// Display the results


$display("Total Transactions = %0d", Statistics::ntrans);
$display("Total Time Elapsed = %0t", Statistics::total_elapsed_time);
end
endmodule

🔍 What This Code Demonstrates:

 Transaction class uses a Statistics object (stats) to time the simulated


transmission.
 Static members of Statistics (ntrans and total_elapsed_time) are shared across
all Transaction instances.
 The testbench runs a single transaction and prints the statistics at the end.

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:

✅ How Big or Small Should a Class Be?

 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).

Example of too small:

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:

 Do group related data and behavior into one class.


 Do not over-engineer with too many tiny classes unless you're building reusable IP
or frameworks.
 Use encapsulation and partitioning when the class grows organically large or covers
unrelated functionality.

📌 Summary Rule of Thumb:

"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?

5.13.2 Compilation Order Issue

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

Great! Here's a complete SystemVerilog example that demonstrates:

 ✅ Class hierarchy
 ✅ typedef class forward declaration
 ✅ Dynamic object creation
 ✅ Static variables and methods
 ✅ Passing objects via handles
 ✅ Object composition (class inside another)

🌐 Full Example: Transaction, Statistics, Transmitter


// Typedef forward declaration to avoid compilation order issues
typedef class Statistics;

// Statistics class definition


class Statistics;
time startT;
static int count = 0;
static time total_elapsed_time;

function void start();


startT = $time;
endfunction

function void stop();


time elapsed = $time - startT;
count++;
total_elapsed_time += elapsed;
endfunction

static function void show_stats();


$display("Total transactions = %0d, Total time = %0t", count,
total_elapsed_time);
endfunction
endclass

// Transaction class that uses Statistics


class Transaction;
int id;
static int id_counter = 0;
Statistics stats;

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

// Transmitter class that sends transactions


class Transmitter;
function void send(Transaction t);
t.execute(); // handle is passed, modifies original object
endfunction
endclass

// Test module
module test;
Transaction t1, t2;
Transmitter tx;

initial begin
tx = new();

t1 = new();
t2 = new();

tx.send(t1);
tx.send(t2);

// Display aggregated stats from static method


Statistics::show_stats();
end
endmodule

🧠 Concepts Illustrated:

Concept Where it's used


typedef class Statistics; Forward declare Statistics
Object inside another class Transaction contains a Statistics
Static vars & methods Statistics::count, show_stats()
Dynamic object creation t1 = new();, stats = new();
Passing handles tx.send(t) passes handle to execute()

Would you like a version of this using constrained-random stimulus too?

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).

✅ Sample Code: Passing Objects to Methods


class Transaction;
rand bit [31:0] addr;
rand bit [31:0] data[4];

function void display();


$display("addr = %h, data = %p", addr, data);
endfunction
endclass
// Task to transmit a transaction
task transmit(input Transaction tr);
// Corrupt the first data word
tr.data[0] = ~tr.data[0];
$display("Inside transmit:");
tr.display();
endtask

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();

transmit(t); // Pass handle

$display("After transmit:");
t.display();
end
endmodule

🧠 Key Learning Points:

Concept Description
transmit(input Transaction
tr) The object is passed by value (i.e., the handle is copied).

Changes persist because tr and t both point to the same


Modifies tr.data[0]
object.

Would require ref, but not needed to change the contents of


Modifying handle itself
object.

🧪 Output Would Look Like:


Before transmit:
addr = deadbeef, data = '{aaaa_bbbb, 1234_5678, 0, ffff_ffff}
Inside transmit:
addr = deadbeef, data = '{5555_4444, 1234_5678, 0, ffff_ffff}
After transmit:
addr = deadbeef, data = '{5555_4444, 1234_5678, 0, ffff_ffff}

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.

❌ Sample 5.26 — Incorrect (No ref → handle isn't updated)


class Transaction;
bit [31:0] addr;
endclass

function void create(Transaction tr); // ❌ Bug: missing ref


tr = new(); // Creates new object, but only
updates local copy
tr.addr = 42;
endfunction

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

✅ Sample 5.27 — Correct (Use ref to update handle)


class Transaction;
bit [31:0] addr;
endclass

function void create(ref Transaction tr); // ✅ Correct: ref


tr = new(); // Now updates caller's handle
tr.addr = 42;
endfunction

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

Modify object properties (fields) ✅ Yes Optional

Modify the handle itself (e.g., new) ❌ No ✅ Required

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:

1. The handle itself (i.e., what the variable points to)


2. The object the handle points to (i.e., the actual data/fields in the object)

🔍 SystemVerilog Handle Behavior


🔸 Case 1: Without ref (Default: input)

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.

💡 Example (Without ref)


class Transaction;
int addr;
endclass

function void update(Transaction tr); // no ref


tr.addr = 99; // ✅ Updates the object's field (works)
tr = new(); // ❌ Creates a new object, but only changes the local
copy
tr.addr = 42;
endfunction

Transaction t;
initial begin
t = new();
t.addr = 1;

update(t);

$display("t.addr = %0d", t.addr); // Output: 99, not 42


end

✅ 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.

🔸 Case 2: With ref

When you pass the handle with ref, any reassignment or modification affects the actual
handle in the caller’s scope.

💡 Example (With ref)


class Transaction;
int addr;
endclass

function void update(ref Transaction tr);


tr = new(); // ✅ Reassign the caller’s handle to a new object
tr.addr = 42; // ✅ Set data in the new object
endfunction

Transaction t;
initial begin
update(t);

$display("t.addr = %0d", t.addr); // Output: 42


end

✅ Explanation:

 A new object was created.


 The ref keyword allows the caller’s handle t to now point to the new object.
 Changes to fields like addr are reflected in the caller as well.

🧠 Summary

Action Without ref With ref


Modify object fields ✅ Works ✅ Works
Reassign handle to new object ❌ Local only ✅ Global
Caller sees new object ❌ ✅
Caller sees field changes ✅ ✅

Would you like a visual diagram to help lock this in?


This section is about a very common and subtle bug in SystemVerilog testbenches when
dealing with dynamic objects like transactions. Let's break it down so it’s crystal clear.

🧠 Key Idea: Reusing a Single Object = BAD


If you create only one object and reuse it in a loop — and if any other process (e.g.,
transmit) takes time or uses threads — you're modifying the same object while it's still
being used.

This leads to corruption of data — for example, all your transactions might appear to have
the same addr.

🔴 Buggy Code: Only One Object Created


❌ generator_bad:
task generator_bad (input int n);
Transaction t;
t = new(); // ❗ Only one object created

repeat (n) begin


t.addr = $random(); // Update same object each time
$display ("Sending addr=%h", t.addr);
transmit(t); // Transmit same object
end
endtask

❗ 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.

✅ Correct Code: New Object Per Iteration


✅ generator_good:
task generator_good (input int n);
Transaction t;
repeat (n) begin
t = new(); // ✅ New object each time
t.addr = $random();
$display ("Sending addr=%h", t.addr);
transmit(t); // Each transmit gets a fresh object
end
endtask

✔ What this fixes:

 Each Transaction object is unique.


 Even if transmit() uses threading/delay, its data won't be overwritten.
 Prevents race conditions and hard-to-debug simulation bugs.

🧠 Why this matters in SystemVerilog


Unlike in static Verilog, objects in SystemVerilog are dynamically allocated and accessed
via handles. When multiple handles point to the same object, any change via one handle
affects them all.

This is powerful but dangerous when misused — just like passing around references or
pointers in C++/Java.

✅ Final Tip:

Always ask yourself:

"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:

🧩 SystemVerilog Handle vs. Object Diagram


+---------------------+ +---------------------+
| Handle (t) |---->| Object (Transaction)|
+---------------------+ +---------------------+
| |
| |
v v
+---------------------+ +---------------------+
| addr = 42 | | addr = 99 |
| data[0] = 0xFF | | data[0] = 0xAA |
+---------------------+ +---------------------+
🔍 Key Points:

 Handle (t): A reference to an object. In SystemVerilog, handles are used to point to


dynamically allocated objects.
 Object (Transaction): The actual instance of a class containing data members like
addr and data.
 Reusing Handles: If you reuse the same handle (t) without creating a new object in
each iteration, all iterations will modify the same object, leading to potential data
corruption.

✅ Best Practice: Create a New Object in Each Iteration

To ensure each transaction is independent and avoids overwriting data, instantiate a new
object in each loop iteration:

task generator_good(input int n);


Transaction t;
repeat (n) begin
t = new(); // Create a new object each time
t.addr = $random(); // Assign random address
t.data[0] = $random(); // Assign random data
$display("Sending addr=%h, data[0]=%h", t.addr, t.data[0]);
transmit(t); // Send the transaction
end
endtask

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.

✅ Let's Clarify: What Is an Array of Handles?

In SystemVerilog:

 An object is an instance of a class, created using new().


 A handle is a reference (or pointer) to that object.
 You can create an array of handles, not actual objects.
 Every element in the array starts as null unless you explicitly call new().
🔧 Example Code: Sample 5.30 – Array of Handles
class Transaction;
rand bit [31:0] addr;

function void display();


$display("Address = %h", addr);
endfunction
endclass

Transaction tarray[10]; // Array of 10 handles

initial begin
// Step 1: Create a new object at each index
foreach (tarray[i]) begin
tarray[i] = new();
tarray[i].addr = $random;
end

// Step 2: Use/display the objects


foreach (tarray[i]) begin
tarray[i].display();
end
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

 An array of handles is like an array of pointers in C/C++.


 You must individually allocate objects for each element.
 You can use this array to keep track of many transactions, packets, or other objects in
your testbench.

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.

🧠 Key Concept: Shallow Copy vs. Deep Copy


When you copy an object using new original_obj; in SystemVerilog, it performs a
shallow copy:

 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.

✅ Sample: Shallow Copy – Problem Demonstration


class Statistics;
time startT;
endclass

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

Transaction src, dst;

initial begin
src = new();
src.stats.startT = 42;

dst = new src; // Shallow copy of src


dst.stats.startT = 96; // Modifies src.stats as well!

$display("src.stats.startT = %0t", src.stats.startT); // Displays 96


(unexpected!)
$display("dst.stats.startT = %0t", dst.stats.startT); // Displays 96
end

⚠️What Happened?

After dst = new src;:

 dst.stats and src.stats point to the same Statistics object.


 Changing dst.stats.startT also changes src.stats.startT.

✅ Solution: Implement a Deep Copy Manually


You need to explicitly create a new instance for each handle inside the class. Here's how:

🔁 Deep Copy Version


class Statistics;
time startT;

function Statistics copy();


Statistics s = new();
s.startT = this.startT;
return s;
endfunction
endclass

class Transaction;
bit [31:0] addr, csm, data [8];
static int count = 0;
int id;
Statistics stats;

function new();
stats = new();
id = count++;
endfunction

function Transaction copy();


Transaction t = new();
t.addr = this.addr;
t.csm = this.csm;
t.data = this.data;
t.stats = this.stats.copy(); // Deep copy!
return t;
endfunction
endclass

Transaction src, dst;

initial begin
src = new();
src.stats.startT = 42;

dst = src.copy(); // Deep copy


dst.stats.startT = 96;

$display("src.stats.startT = %0t", src.stats.startT); // 42


$display("dst.stats.startT = %0t", dst.stats.startT); // 96
end

✅ Summary

Copy Method Affects Only New Object? Handles Re-created?


new original_obj; ❌ No — shared handles ❌ No
Custom copy() ✅ Yes — deep copy ✅ Yes
Would you like help building a utility function that performs deep copying for more complex
nested classes or arrays of objects?

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.

Let’s break it down clearly:

🔍 Visibility Keywords in SystemVerilog


Keyword Meaning
public Accessible from anywhere (default in SystemVerilog)
local Only accessible from within the same class
protected Accessible within the class and its subclasses (children)

✅ SystemVerilog’s Default: public


Unlike C++/Java (which default to private), SystemVerilog makes everything public
unless stated otherwise.

This is intentional, and in the context of testbenches, it’s actually helpful:

 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;

// Local method (private-style)


local function void compute_checksum();
checksum = addr ^ data;
endfunction

// 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);

// Direct access to public field — allowed


t.checksum = 32'h0BAD_C0DE; // simulate error injection
end

🧠 Why Not Make Everything local?


In normal software development:

 You protect internal data to avoid bugs and promote stable APIs.

But in testbenches:

 You want full control.


 You often intentionally create bad inputs (like a wrong checksum).
 Limiting access (e.g., making checksum local) makes this harder.

🚫 If You Used local or protected


local bit [31:0] checksum; // Now this line prevents external access!

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.

🔷 Layered Testbench Overview (Figure 5.9)


+--------------------+
| Test | ← Defines the stimulus scenario (e.g., random or
directed)
+--------------------+

+--------------------+
| Generator | ← Creates transactions (packets) and sends them to
the driver
+--------------------+

+--------------------+
| Driver | ← Drives transactions to the DUT interface (e.g.,
via bus signals)
+--------------------+

+--------------------+
| DUT | ← Design Under Test (your RTL block)
+--------------------+

+--------------------+
| Monitor | ← Listens to DUT output signals and reconstructs
transactions
+--------------------+

+--------------------+
| Scoreboard | ← Compares expected vs. actual output transactions
+--------------------+

+--------------------+
| Checker | ← Performs deeper protocol or data integrity checks
+--------------------+

+--------------------+
| Functional Coverage| ← Tracks functional metrics (Did we hit all
scenarios?)
+--------------------+

Each block is a class, and they communicate using transaction objects.

💬 How Do These Blocks Exchange Data?


You use transaction objects (like Transaction class) as the standard "currency" passed
between blocks.
There are two main ways:

1. Procedural Method Calls


o The generator calls the driver's method and passes a transaction.
o The driver sends the transaction to the DUT.
2. generator.generate_transaction(t);
3. driver.drive(t);
4. Data Structures (e.g., FIFOs / Mailboxes)
o More flexible and realistic.
o The generator puts a transaction in a mailbox.
o The driver gets the transaction from the mailbox.
5. class Generator;
6. mailbox gen2drv;
7. function void generate();
8. Transaction t = new();
9. // fill t
10. gen2drv.put(t);
11. endfunction
12. endclass
13.
14. class Driver;
15. mailbox gen2drv;
16. task run();
17. Transaction t;
18. gen2drv.get(t);
19. // drive it to DUT
20. endtask
21. endclass

✅ Why This Structure Is Powerful


 You can isolate, reuse, and test each component.
 You can inject faults, monitor behavior, check correctness, and collect coverage
independently.
 It mirrors the physical and logical separation in real hardware verification workflows.

🔑 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?

You might also like