How to mimic static constructor in SystemVerilog? - macros

I have a parent class and a lot of child classes. And I want to instantiate all the child classes and cast them into a parent object array/queue so I can do something useful things using the parent array/queue.
The code is below:
class parent;
endclass
class child1 extends parent;
endclass
class child2 extends parent;
endclass
program top;
child1 c1;
child2 c2;
parent p, p_arr[$];
initial
begin
c1 = new;
assert ($cast(p,c1)) else $fatal;
p_arr.push_back(p);
c2 = new;
assert ($cast(p,c2)) else $fatal;
p_arr.push_back(p);
// Doing useful things with p_arr
// .......
end
endprogram
I wonder if there is a way make this more tidy.
So I change my code into this:
begin
assert ($cast(p,child1::new())) else $fatal; <---- QuestaSim compiler complained about "child1::new"
p_arr.push_back(p);
assert ($cast(p,child2.new())) else $fatal; <---- QuestaSim compiler complained about "child2.new"
p_arr.push_back(p);
end
But the compiler complains about directly using static constructor "child1::new()" or "child1.new()". Does SystemVerilog support such a feature? (If it is a simulator incapability, can you state what simulator support it?) If not, is there is tidy way to do it? For example using macro?
The problem with macro is that I have to use two macros to do it.
One macro declares the instance at the top of the function/program first:
child1 c1;
And then another macro initializes the instance and put itinto array in the body of the function/program:
c1 = new;
assert ($cast(p,c1)) else $fatal;
p_arr.push_back(p);
Is there a way to use one macro to do this instead of two macro?
---------------------EDIT------------------------
Thank #Tudor and #dave_59 for the answer, the example worked for p = child1::new(). I realized my parent class is a parameterized class. And syntax work for them as well.
p = child1_class #(.WIDTH(WIDTH))::new();
p_arr.push_back(p);
p = child2_class #(.WIDTH(WIDTH))::new();
p_arr.push_back(p);
Unfortunately for a new constructor, I have to explicitly specified what object the new is assigned to. And the following syntax does not work.
p_arr.push_back(child1::new());

The syntax described in the LRM for scoped constructor calls is:
child1::new();
If you're lucky, your simulator supports it.
What you don't need in your code, though, are the $cast(...) statements. Since you're down-casting (going from sub-class to parent class), the following would also be legal:
p = child1::new();
If you're even luckier, you can even shorten it to:
p_arr.push_back(child1::new());
but again, this depends on what your simulator supports.

The SystemVerilog BNF requires that new() appear on the RHS of an assignment. But there's no need to use $cast when making an assignment from an extended class to a base class variable. So you can do:
module top;
parent p, p_arr[$];
initial
begin
p = child1::new();
p_arr.push_back(p);
p = child2::new();
p_arr.push_back(p);
// Doing useful things with p_arr
// .......
end
endmodule
You can also do things like
p_arr = '{3{null}};
p_arr[0] = child1::new();
p_arr[1] = child2::new();
p_arr[2] = child1::new();

Related

is Systemverilog polymorphism different from other languages (e.g. C++)

In languages like C++, the virtual method is called based on the object pointer value. Systemverilog LRM specifies that in case of virtual methods, only the method in the latest derived class takes effect.
The following text is from the LRM:
A method of a class may be identified with the keyword virtual.
Virtual methods are a basic polymorphic construct. A virtual method
shall override a method in all of its base classes, whereas a
non-virtual method shall only override a method in that class and its
descendants. One way to view this is that there is only one
implementation of a virtual method per class hierarchy, and it is
always the one in the latest derived class.
I don't know how to interpret this statement. The above statement seems to suggest that irrespective of the object handle value, only the latest definition in the derived class is resolved.
I tried the example from LRM but results are as the way I would expect in other languages like C++. Here is the code:
class BasePacket;
int A = 1;
int B = 2;
function void printA;
$display("BasePacket::A is %d", A);
endfunction : printA
virtual function void printB;
$display("BasePacket::B is %d", B);
endfunction : printB
endclass : BasePacket
class My_Packet extends BasePacket;
int A = 3;
int B = 4;
function void printA;
$display("My_Packet::A is %d", A);
endfunction: printA
virtual function void printB;
$display("My_Packet::B is %d", B);
endfunction : printB
endclass : My_Packet
BasePacket P1 = new;
My_Packet P2 = new;
initial begin
P1.printA; // displays 'BasePacket::A is 1'
P1.printB; // displays 'BasePacket::B is 2'
P1 = P2; // P1 has a handle to a My_packet object
P1.printA; // displays 'BasePacket::A is 1'
P1.printB; // displays 'My_Packet::B is 4' – latest derived method
P2.printA; // displays 'My_Packet::A is 3'
P2.printB; // displays 'My_Packet::B is 4'
end
I created a small code snippet to test this and somehow, it does not match what LRM says or what dave_59 seems to suggest (unless I have completely misinterpreted).
module x;
class B1;
virtual function void printme;
$display("Class B1");
endfunction : printme
endclass : B1
class B2 extends B1;
virtual function void printme;
$display("Class B2");
endfunction : printme
endclass : B2
class B3 extends B2;
virtual function void printme;
$display("Class B3");
endfunction : printme
endclass : B3
B1 b1_handle = new;
B2 b2_handle = new;
B3 b3_handle = new;
initial begin
b1_handle.printme;
b1_handle = b2_handle;
b1_handle.printme;
b1_handle = b3_handle;
b1_handle.printme;
end
endmodule
Here is the output:
Class B1 Class B2 Class B3
So, execution is based on value of the handle and not the latest implementation of virtual method. For example, the following line should have printed Class B3 if latest virtual method is resolved while it prints Class B.
b1_handle = b2_handle; b1_handle.printme;
BTW, the way the simulator behaves is exactly the way I expect it to. This expectation is based on what I have seen in C++. Only the statement in LRM confuses me.
SystemVerilog’s OOP model comes from Java (Both developed from work at Sun Microsystems). In the case of virtual methods, that’s the same as C++.
The SystemVerilog LRM matches your understanding, but said in a slightly different way if you think about it long enough.

Why can't I call a constructor outside an initial or always block?

I tried to run this code using Synopsys VCS:
class parent;
int a = 10;
endclass
class child extends parent;
int b = 10;
endclass
module main;
parent P;
child C;
P = new();
C = new();
initial begin
$display("a=%d\n",C.a);
end
endmodule
It is giving an error at the object creation after the handle declaration. The error is as follows:
Error-[SE] Syntax error Following verilog source has syntax error : "class.sv", 20: token is '=' P = new();
However, when I change the module "main" to this
module main;
parent P = new();
child C = new();
initial begin
$display("a=%d\n",C.a);
end
endmodule
I get no such error. Why is this?
The error is caused by the fact that these two lines:
P = new();
C = new();
are procedural code - they must be inside an initial or always block. They are procedural code, because they call a function - the constructor "new".
These two lines, however, are declarative code:
parent P = new();
child C = new();
and so they are legal outside an initial or always block. They are declarative code because they declare two objects (P of class parent and C of class child). These objects are also initialised by calling their constructors. These two objects have module scope - they will be visible throughout the module.
It is also legal to put these two lines of code inside an initial or always block (as long as they appear before any procedural code). In that case, the two objects will have block scope - they will only be visible inside that block.

Store reference to array/queue in SystemVerilog

I'd like to store a reference to an array/queue inside a class. It's doesn't seem possible to do this, though.
I'd like to do something like this:
class some_class;
// class member that points to the 'q' supplied as a constructor arg
??? q_ref;
function new(ref int q[$]);
this.q_ref = q;
endfunction
endclass
If q_ref is merely defined as int q_ref[$], then the assignment operator will create a copy, which isn't what I want. I'd like changes in 'q' to be visible inside the class.
Is there some hidden section in the LRM that shows how this can be done?
I'm not looking for the obvious "you have to wrap the array/queue in a class answer", but for something that allows me to interact with code that uses native arrays/queues.
There are only three variable types in SystemVerilog that can store references: class, event, and virtual interfaces variables.
You have to wrap the array/queue as a member in a class object. Then, any method of that class can be used in an event expression. Any change to a member of the class object causes a re-evaluation of that method. See the last paragraph and example in section 9.4.2 Event control of the 1800-2012 LRM.
So, the only solution for you would be to wrap the queue in a class. The latter is always assigned by a reference, as in this example:
class QueueRef #(type T = int);
T queue[$];
function void push_back(T t);
queue.push_back(t);
endfunction // push_back
endclass // Queue
class some_class;
QueueRef q_ref;
function new(QueueRef q);
this.q_ref = q;
endfunction
endclass
program test;
QueueRef q = new;
some_class c = new (q);
initial begin
q.push_back(1);
q.push_back(2);
$display(c.q_ref.queue);
end
endprogram // test

Avoid repeating a superclass' package name in Matlab

How can I avoid repeating a long tedious package name in matlab classes in the following cases:
When specifying the Superclass, e.g. classdef Class < tediouspkgname.Superclass
When calling the superclass constructor, e.g. obj = obj#tediouspkgname.Superclass(...).
When calling superclass methods, e.g. val = somefunc#tediouspkgname.Superclas(...).
I'm looking for an equivalent of matlabs import statement, which is not usable in these cases unfortunately.
MWE:
Lets have a folder called +tediouspkgname/ in our Matlab path. So Matlab recognizes there's a package called tediouspkgname.
Lets have a Class ExampleClass which is saved in the file +tediouspkgname/ExampleClass.m:
classdef ExampleClass
properties
p
end
methods
function obj = ExampleClass(p)
obj.p = p;
end
function print(obj)
fprintf('p=%s\n',obj.p);
end
end
end
Let there be another Class, derived from ExampleClass, living in the file
+tediouspkgname/DerivedClass.m:
classdef DerivedClass < tediouspkgname.ExampleClass
methods
function obj = DerivedClass(p)
obj = obj#tediouspkgname.ExampleClass(p);
end
function print(obj)
print#tediouspkgname.ExampleClass(obj);
fprintf('--Derived.\n');
end
end
end
I want the following commands to work without errors while mentioning tediouspkgname. as little as possible:
e = tediouspkgname.ExampleClass('Hello');
e.print();
o = tediouspkgname.DerivedClass('World');
o.print();
In particular, this definition of DerivedClass gives me the error ExampleClass is not a valid base class:
classdef DerivedClass < tediouspkgname.ExampleClass
methods
function obj = DerivedClass(p)
obj = obj#tediouspkgname.ExampleClass(p);
end
function print(obj)
import tediouspkgname.ExampleClass
print#ExampleClass(obj);
fprintf('--Derived.\n');
end
end
end
You have two examples at the command line:
e = tediouspkgname.ExampleClass('Hello');
e.print();
o = tediouspkgname.DerivedClass('World');
o.print();
For these cases, you can use import at the command line:
import tediouspkgname.*
e = ExampleClass('Hello');
e.print();
o = DerivedClass('World');
o.print();
and it should work fine.
For the other cases you have (in the class definition line, and when calling a superclass method), you need to use the fully qualified name including the package.
I dislike this aspect of the MATLAB OO system. It's not just that it's tedious to write out the fully qualified name; it means that if you change the name of your package, or move a class from one package to another, you have to manually go through your whole codebase in order to find-and-replace one package name for another.

Is there any method to know whether a member is declared random or not in a class in SV

// Current Class
class x;
rand int a;
int b; // b is nonrandom as of now
function new();
endfunction
function abc;
// if a != ref.a, where ref is reference object of class x, declared somewhere else
a.rand_mode(0);
endfunciton
// Future Possible Class
class x;
rand int a;
rand int b; // b is also a random variable now
function new();
endfunction
function abc;
// if a != ref.a, where ref is reference object of class x, declared somewhere else
a.rand_mode(0);
// if b != ref.b, where ref is reference object of class x, declared somewhere else
b.rand_mode(0);
endfunciton
So in function abc, depending upon whether a rand member value matches or doesn't match with the value of that member in reference class, that rand declared members of class x, should be active or inactive accordinly.
Purpose - I need to check if a rand variable matches with reference class value then only it should be randomized, otherwise not.
I want to generalize method abc, for all possible future variations (So I don't need to modify it, as done in the above example), and as I don't know, when a class member may become rand or nonrand member, Is there any inbuilt method to know, whether a member of a class is declared as rand or not in that class?
You could change your perspective on the problem slightly. Instead of trying to disable randomization for fields that are declared rand, why not say that when they get randomized, they should keep their value?
According to this nice post, there's a new construct in SV 2012, const'(...) that would work in this case. Unfortunately I don't think many vendors support it. Your randomize() call would look like this:
if (!rand_obj.randomize() with {
const'(a) != ref_obj.a -> a == const'(a);
})
$fatal(0, "rand error");
Let's dissect this code. const(a) will sample the value of a prior to doing any sort of randomization. If the value of a before randomization is not equal to the reference value, then we have the second part of the constraint that says a should keep its value. I've tried this code on two simulators but it wasn't supported by either (though it should be legal SV 2012 syntax). Maybe you're lucky enough to have a vendor that supports it.
You can write such constraints even for state variables, as they will still hold.
If you can't get the const syntax to work in your simulator, then the same post shows how you could work around the issue. You could store the values prior to randomization inside the object and use those in the constraint:
class some_class;
rand bit [2:0] a;
bit [2:0] b;
bit [2:0] pre_rand_a;
bit [2:0] pre_rand_b;
function void pre_randomize();
pre_rand_a = a;
pre_rand_b = b;
endfunction
endclass
When you want to randomize, you'd add the following constraints:
if (!rand_obj.randomize() with {
pre_rand_a != ref_obj.a -> a == pre_rand_a;
pre_rand_b != ref_obj.b -> b == pre_rand_b;
})
$fatal(0, "rand error");
You can find a full example on EDAPlayground.
You mention that your function that does randomization is defined outside of the object. Because of that, the pre_rand_* fields can't be local/protected, which isn't very nice. You should consider making the function a class member and pass the reference object to it, so that you can enforce proper encapsulation.
This isn't possible as SystemVerilog doesn't provide any reflection capabilities. You could probably figure this out using the VPI, but I'm not sure how complete the implementation of the VPI is for classes.
Based on what you want to do, I'd say it anyway doesn't make sense to implement such a query just to future proof your code in case some fields will one day become rand. Just as how you add the rand modifier to the field, you can also add it to the list of fields for which randomization should be disabled. Both code locations reside in the same file, so it's difficult to miss.
One certain simulator will return -1 when interrogating a state variable's rand_mode(), but this is non-standard. The LRM explicitly states that it's a compile error to call rand_mode() on non-random fields.