My previous post showed how SystemVerilog class variables can refer to base and derived objects. This post shows you how to use the $cast() system task to copy between base and derived class variables.
Start with a base class for an automobile with a color property.
class Automobile; string color; endclass
Now extend this to make a Pickup class with a bed in the back to carry big things.
class Pickup extends Automobile; int bed_size; endclass
A place for everything
As you create Automobile and Pickup objects, you need a place to hold them. How about a parking lot? This class has an array of Automobile class variables. This can hold handles to base Automobiles and derived Pickups, just like a real parking lot. Include methods to park and retrieve vehicles.
class ParkingLot; Automobile spaces; function void park(Automobile a, int spot); spaces[spot] = a; endfunction function Automobile retrieve(int spot); return spaces[spot]; endfunction endclass
Let’s try this out! Declare some class variables, construct the parking lot, then an Automobile object, park it, then retrieve it.
Automobile a1, a2; ParkingLot lot; initial begin lot = new(); // Construct the parking lot a1 = new(); // Construct a blue Automobile a1.color = "blue"; lot.park(a1, 0); // Park it in spot 0 // Run some errands, then come back to get your car
a2 = lot.retrieve(0);
// Retrieve automobile with different variable
$display("Found automobile %p", a2.color);end
Try this code on your favorite simulator. You should see that the blue car was parked in space 0, and was successfully retrieved.
Dude, where is my car?
Construct a red pickup and park it in spot 3.
Pickup p1, p2; initial begin p1 = new(); p1.color = "red"; lot.park(p1, 3); end
Wait, is that legal? The park() method has the formal argument a that is an Automobile class variable. You just tried to assign it a Pickup handle. The previous post showed that a base class variable can hold a handle to base and derived objects.
OK, you’ve now parked the Pickup object in space 3. Can you get it back? Use a different class variable to make sure the assignment worked.
p2 = lot.retreive(3); // Won’t compile
When you compile this code, you get an error that you can’t assign to a Pickup variable from an Automobile variable. This is because retrieve() returns an Automobile variable. The compiler does not know if that variable refers to an Automobile or Pickup object.
Think about this in the real world. Spaces in a parking lot can hold cars, pickups, motorcycles, or might be empty. If you parked your pickup in space 3, you expect to find your vehicle there – the space better have the same type! But you won’t know this until you retrieve it at run time.
In SystemVerilog, how do you check the type at run time? Back in June I showed how to do this for enumerated variables. The same $cast() method also works on class variables. Retrieve your pickup from space 3 with the following.
You can check if this was a Pickup object with this extra code.
if (! $cast(p2, lot.retrieve(3)))
$fatal("The vehicle in space 3 is not a pickup");
Why did I use a $fatal()? If this is not a Pickup object, you have a bug in your code, so stop immediately and fix it. Make your code more reusable with a helpful message. The person who encounters this error may not know why you are parking pickups!
OOP cheat sheet
Here is your cheat sheet for assignments between base and derived class variables. Print this and put on your wall as a guide while you continue the journey to be a SystemVerilog expert.
Attack of the (UVM) clones
A similar pattern occurs when you try to clone an object in UVM. Maybe you want to clone a transaction object before sending its handle to the scoreboard. Here is a transaction class.
class tx_item extends uvm_sequence_item; ...
The clone() method was declared in uvm_object and returns a handle of type uvm_object. Eventually, everything in UVM is derived from this base class, including uvm_sequence_item.
class uvm_object; virtual function uvm_object clone(); ...
Declare two txm_item variables and create an object with the name “tx”.
tx_item t1, t2; t1 = tx_item::type_id::create(“tx”); // Create a tx_item object
Try to clone the object, and you will get a compile error.
t2 = t1.clone(); // Error, won't compile
Why? t2’s type is for a derived class, while clone() returns a base uvm_object handle. You can’t assign a base to a derived. The solution is the $cast() system task to check the type of the object that clone() returns.
Can you rewrite this with a custom error message?
When you need to assign between two variables of different types, and the source might have a value incompatible with the destination, use $cast() to check the value. The check happens at runtime. This works for class variables, especially when a base variable has a handle to a derived object. If you want to give a helpful message, call $cast() as a function.