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.
Related
#1
To understand the behavior of new() and inheritance, I made a simple class and added class child_class:
// Example 1
class parent_class;
logic [31:0] addr;
logic [7:0] data;
bit parity;
function new(logic [31:0] add, logic [7:0] dat);
addr = add;
data = dat;
endfunction
endclass
class child_class extends parent_class;
bit [31:0] data;
static int count;
function new();
count++;
data = count;
endfunction
endclass
module inheritence;
initial begin
parent_class p;
p = new(32'h1234, 8'hff);
$display("Value of addr = %0d data = %0d",p.addr,p.data);
p.addr = 10;
p.data = 20;
$display("Value of addr = %0d data = %0d",p.addr,p.data);
end
endmodule
But, when I executed it, I got the error message:
class child_class extends parent_class;
|
xmvlog: *E,FAABP1 (testbench.sv,21|38): Task/function call, or property/sequence instance does not specify all required formal arguments.
function new(logic [31:0] add, logic [7:0] dat);
Q1. Why can't I make the child class? How do I correctly derive the class and use it?
#2
If I remove class child_class in my code above,
class parent_class;
logic [31:0] addr;
logic [7:0] data;
bit parity;
function new(logic [31:0] add, logic [7:0] dat);
addr = add;
data = dat;
endfunction
endclass
module inheritence;
initial begin
parent_class p;
p = new(32'h1234, 8'hff);
$display("Value of addr = %0d data = %0d",p.addr,p.data);
p.addr = 10;
p.data = 20;
$display("Value of addr = %0d data = %0d",p.addr,p.data);
end
endmodule
There is no error message for child class. What's the difference between them?
From IEEE Std 1800-2017, section 8.7 Constructors:
The new method of a derived class shall first call its base class
constructor [super.new() as described in 8.15].
From section 8.15 Super:
A super.new call shall be the first statement executed in the
constructor. This is because the superclass shall be initialized
before the current class and, if the user code does not provide an
initialization, the compiler shall insert a call to super.new
automatically.
The error message is telling you that the new function in the child_class class is implicitly making a call to the new function in the parent_class class, but that new function requires 2 arguments.
One way to fix the error is to explicitly call spuer.new in child_class with 2 arguments. For example, this code fixes the error:
class child_class extends parent_class;
bit [31:0] data;
static int count;
function new();
super.new(0, data);
count++;
data = count;
endfunction
endclass
You must decide if this how you want your code to behave.
To answer the 2nd question, since you no longer have the derived class (child_class) which has the syntax error, you no longer get an error. Your module properly calls new from parent_class with the correct number of arguments (2).
I am not from system-verilog and from C C++/shell.
Assume that I created a character variable "testing" and I am not using that variable anywhere inside my program. Due to that, one byte wasted when my program at execution mode.
Assume that I wrote that in a server program inside an infinite loop.
In this case, it will affect the performance of server program since it is using a variable which is not being used.Hence wastage of memory.This may affect other program when that program is waiting for free space at RAM/cache from disk.
Hence better to remove all warnings when we obtain during compilation.
This is applicable to all living beings at each planet/universe handling the space across the planets/universe [if applicable:) ]
You need to learn parent class, constructor, destructor/uploaded functions at parent/child class when performing inheritance.
https://www.chipverify.com/systemverilog/systemverilog-inheritance
I have a class with a rand data member i. This class (child) is a member of class parent, which also has a data member i. I would like to constrain the value of i in the child class to be the same as the value of i in the parent class. I want to do something like:
c.randomize with {i==this.i;};
but the this.i doesn't seem to refer to the i data member of the parent class. (Why?)
I can do this:
function void f;
int dummy = i;
c.randomize with {i==dummy;};
endfunction
or this:
function void f;
c.randomize with {i==m.blk.p.i;}; // yuck!
endfunction
but wonder if there is a better (built-in, non-hacky) way of distinguishing between the two is.
MCVE:
class child;
rand int i;
endclass
class parent;
child c = new;
int i=1;
function void f;
c.randomize with {i==this.i;};
endfunction
endclass
module m;
initial begin : blk
parent p = new;
p.f;
$display("%p", p);
end
endmodule
https://www.edaplayground.com/x/2_8P
You want {i==local::i}. See section 18.7.1 of the 1800-2017 LRM
The reason this.i does not do what you expect is the combination of these two rules:
all class methods, including the built-in randomize method, have a built-in this argument. So c.method(args) is really method(args, c) and this becomes a variable local to the method set to the value of c
Identifiers within the with clause try to bind into the scope being randomized first before searching locally at the point where calling randomize().
So i and this.i refer to the same class variable just as if you wrote
class A;
bit i;
function void method;
i = 1;
this.i = 2;
endfunction
endclass
class base;
int a = 15;
endclass
class extended extends base;
int b = 2;
endclass
module top;
initial begin
base base;
extended extend;
extend = new();
base = new();
$cast(extend, base);
$display(extend.a);
end
endmodule
I'm trying to undersatnd $cast method in systemverilog as the above code, But I've got error messages.
ncsim> source /incisiv/15.20/tools/inca/files/ncsimrc
ncsim> run
$cast(extend, base);
|
ncsim: *E,BCLCST (./testbench.sv,18|8): Invalid cast: a value with the class datatype '$unit_0x4ccdf83b::base' cannot be assigned to a class variable with the datatype '$unit_0x4ccdf83b::extended'.
15
ncsim: *W,RNQUIE: Simulation is complete.
ncsim> exit
Exit code expected: 0, received: 1
Why does it make a error?
update 2
I've got some more test code for understanding the $cast().
test code.1
class base;
int a = 15;
endclass
class extended extends base;
int b = 2;
endclass
module top;
initial begin
base b;
extended e;
e = new();
b = new();
$cast(b, e);
//b=e;
$display(e.a);
end
endmodule
test code 2
class base;
int a = 15;
endclass
class extended extends base;
int b = 2;
endclass
module top;
initial begin
base b;
extended e;
e = new();
b = new();
//$cast(b, e);
b=e;
$display(e.a);
end
endmodule
When I compiled the both test code.1 and test code.2, the result the same.
So I'm confused that why do we use '$cast()' methods?
Your $cast is failing correctly as specified by the LRM. You have constructed a base class type object and stored its handle in a base class variable. (BTW, a bad idea to use the same name for both as you have now hidden the base type name). Now you are trying to assign the base handle to class variable of class type extend. $cast fails because the object your are tying to assign a handle to extended never constructed an extend object. Had the cast been allowed to succeed, the original handle in extended would have been replaced with a handle to a base object, and the reference to extend.b would be fatal since that variable does not exist.
The purpose of $cast is when you have handle stored in a base class variable, and that handle refers to an extended class object. The $cast allows you to move that handle to an extended class variable by checking the object it refers to first before making the assignment.
Please see my seminar on SystemVerilog OOP as well as short post on class terminology.
I suggest the following example for studying. the last 'printer()' statement will fail because you cannot cast non descendant of ext to 'ext' int he function
class base;
local string a;
function new();
a = "I am a";
endfunction
function print();
$display(a);
endfunction
endclass
class ext extends base;
local string b;
function new();
b = "i am b";
endfunction
function print();
$display(b);
endfunction
endclass
function printer(base p);
ext e;
$cast(e, p);
e.print();
p.print();
endfunction
program P;
base b = new();
ext e = new();
initial begin
printer(e);
printer(b); // << this will fail
end
endprogram
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
class my_a;
int member1 = 1;
endclass
class my_ea extends my_a;
int member1 = 2;
endclass
Now when I do
my_a A;
my_ea EA;
EA =new();
A=EA;
EA = new(); has given handle to object of type my_ea to class variable EA.
A=EA; passes the same handle (pointer value which points to object of my_ea) to A. So, A.member1 should refer to value 2.
But it refers to value 1. Why?
So far, System-Verilog does not allow overriding of class variables. Only virtual methods can be overridden.
There is nothing like virtual for class members, so parent class can never directly access them. When using class_object.member, the particular class is referred to. Henceforth, this is not possible.
You cannot redefine an existing member by extending a class. You should use virtual methods to access (get/set) them. For instance, I added "get_member1" function to your code, and it returns 2 when called from a base class handle as you wanted.
class my_a;
int member1 = 1;
virtual function int get_member1();
return member1;
endfunction
endclass
class my_ea extends my_a;
int member1 = 2;
virtual function int get_member1();
return member1;
endfunction
endclass
module tb;
initial begin
my_a A;
my_ea EA;
EA =new();
A=EA;
$display("%0d", A.get_member1());
end
endmodule
You can similarly define "set_member1(int value)" function and use it to change its value.
In your case A.member1 should return the original member of its own class. When you are overriding a class members, you are basically hiding the overridden members. Super/base class can never access overridden member in its subclass.
As far as I know, only method identified with virtual, randomize() function and class constraint can be overridden without hiding them from the base class - thus they allow base class to reference to them (polymorphism)
For more info, please find here IEEE 1800-2012 in section 8.14 Overridden members.