Dig a Pool of Specialized SystemVerilog Classes
Introduction
SystemVerilog classes are a great way to encapsulate both variables and the routines that operates on them. What if you want to reuse the methods but change the type of properties? Use a parameter and specialize it!
Definition
In Object-Oriented Programming (OOP), encapsulate variables and routines in a class. A class variable is called a property and a class routine is a method. By grouping them together, the result is more reusable than if they were kept separate.
The First Assignment
Your manager asks you to write a class to represent a stack. When you tell her that SystemVerilog already has a queue that can be used as a LIFO (Last In, First Out), she responds, “Yes, but on the last project, someone kept switching between push_front(), push_back(), and pop_front(), pop_back(), causing testbench bugs. Give me a stack class with just push() and pop().” Phew!
Your first attempt might look like the following, with no error checking.
class Stack;
bit[31:0] items[100];
int idx = -1;
string name;
function new(string name);
this.name = name;
endfunction
function push(input bit[31:0] val);
items[++idx] = val;
endfunction
function bit[31:0] pop();
return items[idx--];
endfunction
endclass
Here is a short test.
Stack s;
initial begin
s = new("s32"); // Construct a Stack object
s.push('1); // Push all 1’s
$display("pop()=%x", s.pop()); // Expect FFFF_FFFF
end
That was good, now specialize it
Your manager liked that solution but asked why you only used 32-bit numbers. After all, the next project has a 64-bit data path. Can you make a compatible stack class? How about 128-bits?
Rather than make separate classes for each width, create a parameterized class. This one has a default width of 4 bits so it will obvious if you forget to specify the correct width.
class Stack #(parameter int WIDTH=4);
bit[WIDTH-1:0] items[100];
int idx = -1;
function push(input bit[WIDTH-1:0] val); …
function bit[WIDTH:0] pop(); …
Declare two handles for this class, first with the default width, and another for 64-bit values.
Stack s4; // Handle to a 4-bit stack object
Stack #(64) s64; // Handle to a 64-bit stack object
Try these two stack types with the test code from the first section.
When you give a value for a parameter when declaring a class handle (or use the default value), it is called “specialization”. Under the hood, it is as if the compiler creates a new type (but you can’t use it directly). You can imagine it creates the following for a specialization of WIDTH=64.
class _Stack__64; // Equivalent class for specialization: Stack #(64) s64
bit[64-1:0] items[100];
int idx = -1;
function push(input bit[64-1:0] val); …
function bit[64-1:0] pop(); …
endclass
A group of stacks
A verification engineer saw this parameterized class and said that he wanted to make a group of these stacks as part of the reference model. However, when he tried the following code, it wouldn’t compile.
Stack q[$]; // A queue of stack handles
initial begin
q.push_back(s4);
q.push_back(s64); // Bad assign to Stack#(4) from Stack#(64)
end
The problem is that the two specializations, Stack#(4) and Stack#(64), are different types, and you can’t assign between them, not even with the $cast() system task. You can’t group dissimilar types.
Twisting by the Pool
You can group multiple objects together if they have the same type. Wrap this group in a class with methods to make it easier to manipulate them. UVM calls this a “pool”.
(The formal name for this group is an “aggregate” which means each object can exist on its own. The opposite is “composition”, such as an Animal class that is composed from Head and Body classes.)
Here is the Pool class. The class defines T, a type based on the width. (Note that the keyword “parameter” is optional.)
class Pool #(int WIDTH=4); // Pool class
typedef Stack #(WIDTH) T; // Type of objects in pool
T queue[$]; // Queue of specialized handles
function void add(input Stack s);
queue.push_back(s);
endfunction
// Print the name of all objects
function void print();
foreach (queue[i])
$display("Pool[%0d] name='%s'", i, queue[i].name);
endfunction
endclass
Here is code to test the Stack and Pool classes.
Stack #(4) s0, s1;
Pool #(4) p4;
initial begin
p4 = new(); // Construct a pool of Stack#(4) handles
s0 = new("zero"); // Construct a 4-bit Stack object
p4.add(s0); // Add it to the pool
s1 = new("one"); // Construct another 4-bit Stack
p4.add(s1); // Add it to the pool
p4.print(); // Print the names of all Stack objects
end
What happens if you create a stack of 8-bit values and try to add this to the Pool #(4) instance?
More Experiments
The code above showed how to parameterize a class with a value to create vectors of different widths. What if you want to make a stack of real numbers? SystemVerilog also supports type parameters. Here is the start of a stack class where you can change the type. I have deliberately left out the push() and pop() methods so you can write them yourself.
class Stack_T #(type T = int);
int idx;
T items[100]; // Store the stack made of items of type T
// What do the push() and pop() methods look like?
endclass
// Declare handle to various type stacks
Stack_T stack_int; // Stack of int’s
Stack_T #(bit[15:0]) stack_bit16; // Stack of 16-bit values
Stack_T #(real) stack_real; // Stack of reals
Learn More
You can learn more about these topics including Oriented Programming with the Siemens SystemVerilog for Verification course. It is offered in instructor led format by our industry expert instructors, or in a self-paced on-demand format. It can also be tailored to address your specific design goals and show you how to set up an environment for reuse for additional designs. Also, you can now earn a digital badge/level 1 certificate by taking our Advanced Topics Badging Exam. This will enable you to showcase your knowledge of this topic by displaying the badge in your social media and email signature.