2. Using OOP in SystemVerilog

Using Obejcts

Using class in Sv is analogy to C++, refering to variables and routines in an object with the . notation.

1
2
3
4
BusTran b;
b = new;
b.addr = 32'h42;
b.dispaly(); // Call a routine

In strict OOP, the only access to variables in an object should be through its public methods such as get() and put().
While the get() and put() methods are fine for compilers, GUIs, and APIs, you should stick with public variables that can be directly accessed anywhere in the testbench.

Static Variables vs. Global Variables

Sometimes, we need a variable that is shared by all objects of a certain type.
Without OOP, we would probably create a global variable, which is used by one small piece of code, but is visible to the entire testbench.

Using Static variable

Example, creating static variable inside a class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class BusTran;
static int count = 0; // Number of objects created, shared across objects.
int id; // not static, each object has its own copy.
// track objects flow through design
// Unique instance ID
function new; // COnstructor
id = count++; // Set ID, and change the value of count
endfunction
endclass

BusTran b1, b2;
initial begin
b1 = new; // First object, id = 0;
b2 = new; // 2nd object, id = 1;
end

Each time a new object is constructed, it is tagged with a unique value, and count is incremented.
SystemVerilog does not allow printing the address of an object, but we can create an ID field. Whenever tempting to make a global variable, consider making a class-level static variable.

Initializing static variables

Cannot do this in the class constructor, because it’s called for every single new object.
Intead, using the initial block before the first object is constructed.

1
2
3
4
5
6
7
8
9
10
11
12
class static;
static int count;
task initialize(int val);
count = val;
endtask
endclass

static s; // s is still a null pointer
initial begin
s.initialize(42); // This is legal, as the task only uses static variables
// that are not created in the constructor
end

Class Routines (Method)

A routine (a.k.a. method) in a class is just a task or function defined inside the scope of the class.

Define Routines Outside of the Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class BusTran;
bit [31:0] addr, crc, data[8];
extern function void display();
endclass
// class body(BusTran) class scope opertater(::) method(display)
function void BusTran::display(); // this is a prototype
$display("@%0d: BusTran addr=%h, crc=%h", addr, crc);
$write("\tdata[0-7]=");
foreach (data[i])
$write(data[i]);
$display();
endfunction

class PCI_Tran;
bit [31:0] addr, data; // Use realistic names
extern function void display();
endclass
function void PCI_Tran::display();
$display("@%0d: PCI: addr=%h, data=%h", addr, data);
endfunction

class Broken;
int id;
extern function void display;
endclass
function void display; // Missing Broken::
$display("Broken: id=%0d", id); // Error, id not found
endfunction

Scoping rules

A scope is a block of code such as a module, program, task, function, class, or begin - end block. The for and foreach loops automatically create a block so that an index variable can be declared or created local to the scope of the loop.

Declare all your variables in the smallest scope that encloses all uses of the variable.

Using One class Inside Another

1
2
3
4
5
6
7
8
9
10
11
12
13
class BusTran;
bit [31:0]addr, crc, data[8];
Statistics stats; // a hanlder of another class
// remember to instantiaste the objects,
// otherwise stats os null and the call to start fails.
function new();
stats = new(); // best to instantiate in the constructor
endfunction

task create_packet();
stats.start();
endtask
endclass

Compilation order issue

Sometimes you need to compile a class that includes another class that is not yet defined. The declaration of the included class’s handle causes an error, as the compiler does not recognize the new type. Declare the class name with a typedef statement, as shown below.

1
2
3
4
5
6
7
8
typedef class Statistics;
class BusTran;
Statistics stats;
...
endclass
class Statistics;
...
endclass

Dynamic Obejcts

Use ref to pass the address of scalar variable (noarray, nonobject), so the routine can modify it.

1
2
3
4
5
6
7
8
9
10
11
// Transmit a packet onto a 32-bit bus
task transmit(BusTran bt);
CBbus.rx_data <= bt.data;
bt.timestamp = $time; // cannot modify the handle, need to use ref
endtask
BusTran b;
initial begin
b = new();
b.addr = 42;
transmit(b);
end

Modifying objects in flight

A very common mistake is forgetting to create a new object for each transaction in the testbench,

1
2
3
4
5
6
7
8
9
10
11
12
task generator_bad(int n);
BusTran b;
b = new();
// Create one new object
repeat (n) begin
b.addr = $random();
// Initialize variables
$display("Sending addr=%h", b.addr);
transmit(b);
// Send it into the DUT
end
endtask

So every time through the loop, generator_bad changes the object at the same time it is being transmitted. When you run this, the $display shows many addr values, but all transmitted BusTrans have the same value of addr. The bug occurs if transmit stores the object and keeps using it even after transmit returns. If your transmit task does not keep a reference to the object, you can recycle the same object over and over.

1
2
3
4
5
6
7
8
9
task generator_good(int n);
BusTran b;
repeat (n) begin
b = new(); // Create one new object
b.addr = $random();// Initialize variables
$display("Sending addr=%h", b.addr);
transmit(b); // Send it into the DUT
end
endtask

You can make arrays of handles, each of which refers to an object, e.g., BusTran Barray[10].

Copying objects

You may want to make a copy of an object to keep a routine from modifying the original, or in a generator to preserve the constraints. You can either use the simple, built-in copy available with new , or you can write your own for more complex classes.

1
2
3
4
5
6
7
8
9
class BusTrain;
...
endclass

BusTran src, dst;
initial begin
src = new;
dst = new src;
end

This is a shallow copy, blindly transcribing values from source to destination. If the class contains a handle to another class, only the top level object is copied by new, not the lower level one. Example as following,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BusTran;
bit [31:0] addr, crc, data[8];
static int count = 0;
int id;
Statistics stats;
function new;
stats = new;
id = count++;
endfunction
endclass

BusTran src, dst;
initial begin
src = new;
// Create first object
src.stats.startT = 42;
dst = new src;
// Copy src to dst
dst.stats.startT = 84; // Changes stats for dst & src
end

Objects and handles after copy with new

Note:

  • it doesn’t call the custormized new function.
  • both objects point to the same Statistics object and both have the same id.

Writing your own simple copy function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class BusTran;
bit [31:0] addr, crc, data[8];
Statistics stats;
static int count = 0;
int id;
function new;
stats = new;
id = count++;
endfunction
function BusTran copy;
copy = new;
copy.addr = addr;
copy.crc = crc;
copy.data = data;
copy.stats = stats.copy;
id = count++;
endfunction
endclass

Note that you also need to write a copy for the Statistics class, and every other class in the hierarchy.