Why downcasting is not allowed in SystemVerilog?

Why downcasting (casting from parent class to child class) is not allowed in SystemVerilog?

In reply to Sagar Shah:

First, I recommend against using the terms parent and child when talking about inheritance. See Misnomer in the term "child class" | Verification Academy

Then read use of $cast | Verification Academy

In reply to dave_59:

Thanks Dave.

In above link you have mentioned that,
It is always legal to assign a derived class object handle to a base class variable because Derived class object inherited everything defined by the base class type. There is nothing you can’t reference from the base class variable.

Now refer below code, In below code after assigning derived class object handle to a base class variable I can’t access/reference member which is defined in Derived class (here int unsigned d) but still we can cast it, and we can’t cast reverse, why?

class base;
  int unsigned b;
endclass : base

class derived extends base;
  int unsigned d;
endclass : derived

module top();
  base B;
  derived D;

  initial begin
    D = new();
    D.b = 'hB;
    D.d = 'hD;
    if ($cast(B, D)) begin
      $display ("B.b:%0h", B.b);
      $display ("B.d:%0h", B.d);
    end
  end
endmodule : top

//Output:
//Error-[MFNF] Member not found
//"B."
//  Could not find member 'd' in class 'base', at "top.sv", 1.

In reply to Sagar Shah:

Sagar,

Read the links provided by Dave thoroughly and experiment as much as you can, then you’ll figure out the answer yourself.

I am trying to explain this in a slightly different manner, hoping it helps you understand the concept easily.

  1. A class is a user defined data type. The size of class object (created using new()) depends on the members of the class. In SV, a class object is always pointed using a handle (unlike pointers in C).
  2. In your example, any object of class Base requires 4 bytes (for int unsigned b). So the class handle B can only point to an object of 4 bytes (and not more than that).
  3. Where as, any object of class Derived requires 8 bytes (for both b, d). So the class handle D can point to an object of 8 bytes.
  4. When we assign a derived handle to base handle using $cast(B, D), though B is still pointing to the derived object we are essentially losing access to 4 bytes that are additionally added as part of the derived class (as B handle can point only to 4 bytes as mentioned in point (2)). That is the reason you get the error. Since you are losing access to the information, you call it as downcast.
  5. In order to assign a derived handle to base handle B = D, $cast is not required at all i.e., B=D is as good as $cast(B, D).
  6. $cast is used in cases where a base handle is to be assigned to the derived handle (given that the base handle already points to a derived object).

Ex:



Base B;
Derived D;

D = new();  // 8-byte object created and pointed by handle D
B = D;
$cast(D, B);  // Works fine, as B already pointing to an object of Derived class that is of 8 byte.

B = new(); // B now started pointing to an object of type Base Class which is only of 4 bytes
$cast(D, B); // ERROR:Type Compatibility issue, as B is pointing only to a 4-byte object here, so we cannot assign to an 8-byte handle as the details of the other 4-bytes is unknown.


Thanks

In reply to S.P.Rajkumar.V:

Hi,

I understand the use of $cast with downcasting. But why we need downcasting in the first place, what is the real time usecase?
Can’t we directly use extended class object to access it’s method/varibale ?
Why we first upcast extended class object to base class and then downcast?
What is the real usecase of downcasting?

Thanks.

I remember this in a simple manner that the parent class pointer (handle) can access the memory of variables defined in its own class (32-bits of memory for int unsigned b in this case). The derived class pointer can parse through the base class variable memory and its own memory (32+32 bits of memory space for int unsigned b and d in this case).

Remember that the LRM refers to call super.new in the first line for any derived class constructor.

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.

So when we create the object of derived class, the first memory chunk is allocated to the base class variables(32-bits ‘b’ in this case). Then the next memory chunk is allocated to the derived class variables (32-bit ‘d’ in this case).

By keeping this in mind, when we use base handle=derived object, then the base class handle is allowed to parse through the first 32 bit memory locations that is denoted by int unsigned b. So, B.b works fine.

Now when we try to access B.d, the base class handle “B” is not allowed to access more than 32-bit locations, so it results in an error.

In case of casting, $cast(D2,B), remember that the object is still of derived class which has 64-bit of memory allocation. So, when we do D2.d, the derived class handle is allowed to access all the memory location and it works fine.

On the contrary, if the object is of class B, then this casting will fail since the allocated memory is less (32-bit is allocated for base class object) and derived class handle needs more memory to access. Hence the $cast fails.

A somwehat similar C++ thread is interesting to read in this scenario.

As a side note, the casting is required for generalization of code. The testbench components will have a generalized handle of base class and different derived class objects can be passed which results in different behavior. Refer to this thread for more information.

I know this is not noted a standard procedure to understand this concept, but this trick makes life simple (atleast for me :-)).