SystemVerilog Gotcha: (when copying) a struct is not a class by another name
SystemVerilog has two “similar” data types that allow variables to be grouped together in a handy package: the struct and the class. I’ve heard it often said, when explaining what a class (an object-oriented data type) is, that it is just like a C struct with functions. I used to have no problem with that, until, when reviewing and debugging testbench code, I started seeing some problems related to the way classes have to be treated differently to structs. One of the most common errors I’ve found is when data structures composed of classes are copied.
Consider the following:
class FooDataClass; integer D1; integer D2; endclass struct { integer D1; integer D2; } FooDataStruct; program test; FooDataClass X[] = new[5]; // array of classes FooDataClass Y[]; // array of classes FooDataStruct A[] = new[5]; // array of structs FooDataStruct B[]; // array of structs ... Y = X; // copy array of classes B = A; // copy array of structs ... endprogram
In the case of the struct, it’s possible to copy the dynamic array A[] to B[], sizing B to the same as A automatically. It’s equivalent to:
B = new[A.size()]; foreach(A[i]) begin B[i].D1 = A[i].D1; // copy value of A[i].D1 to B[i].D1 B[i].D2 = A[i].D2; // copy value of A[i].D2 to B[i].D2 end
Importantly, B[] has it’s very own copy of the values of variables D1 and D2 for each element in the array. So, if A[2].D1 was modified, B[2].D1 would not be.
In the case of the class, when we copy the dynamic array X[] to Y[], Y is still sized to the same as X automatically, but something subtly different happens (that often catches people out), with D1 and D2. When arrays of classes are copied, the references (aka handles) to the class are copied, not the values of the class members themselves. It’s equivalent to:
Y = new[X.size()]; foreach(X[i]) begin Y[i] = X[i]; // copy reference to class X[i] into Y[i] end
This means that Y[] does not have its own copy of the values of variables D1 and D2 for each element in the array. If X[2].D1 was modified Y[2].D1 would also be modified. In fact they are the same variable, pointed to by the reference Y[2], which is equal to X[2]. That’s, most often times, not the desired behavior of the code. Coding errors like this can be tricky to track down and may stay hidden for some time.
Once you’ve created a class, people might start using it all over the place. People, who might not have access to modify your class code, only use it. This has an impact on the above mentioned difference between classes and structs. If you want to enable people to make copies of the data values, as opposed to just the references, then you (as the developer of the class), should provide a mechanism to “deep copy” the class. So with FooDataClass we might do:
function void FooDataClass::copy(FooDataClass c); this.D1 = c.D1; this.D2 = c.D2; endfunction
Here we copy the values of another class of the same type into our class. It is now possible to make Y[] have its very own copy of the values of variables D1 and D2 for each element in the array. So, if X[2].D1 was modified, Y[2].D1 would not be. However, we still have to do something different for classes than we do for structs. Something like:
// copy of values of values in X[] to Y[] foreach(X[i]) begin Y[i] = new; // create new instance of the class Y[i].copy(X[i]); // copies *values* in class from X to Y end
So, when you are reviewing code, it’s always prudent to look for places where classes or things containing classes are explicitly or implicitly copied.
January 22nd, 2008 at 7:37 am
Hi Verilab,
just a short note, you define A,B as classes and X,Y as structs in the code, but refer to A,B as structs and X,Y as classes in the text. This is confusing, I guess the text is correct?!
What would happen if I didn’t have arrays of classes/structs but a single class and a single struct, I guess the same rule would apply, i.e. copy-by-reference for classes and copy-by-value for structs?
There’s no Copy-Constructor in SV, is there? Would be handy …
Rgds,
Daniel
January 23rd, 2008 at 2:28 am
Hi Daniel
Thanks for pointing out that error (now corrected). I fixed the variable names and also added the new() of the elements in Y[], inside the foreach loop, for clarity.
Yes, when a struct is copied the values are copied. When a class is copied the reference is copied, or a shallow copy is taken (as described in IEEE1800-2005, Section 7.11). I find people typically remember that when they are dealing with the class itself. When classes are inside something else, it’s easy to forget, because we are copying the thing and may not even have visibility of its contents. Arrays of classes fall into that category, but a class composed of other classes is another big one to watch.
There is no method for a deep copy/clone defined in the SystemVerilog language. Both the AVM and VMM define methods for copying objects as part of the methodology. In Vera we had a built-in method called object_copy() as part of the language. This provided a deep copy of any object, without you having to write anything yourself. I found it useful, but the problem was that there was no control, so you could end up copying something *very* large.
Of course when writing custom copy functions we sometimes put bugs in them too.
Jason