So I'm updating one of my testbenches and I want to create an array of objects. For example:
A_class a_instance[num];
I also want to pass these objects to modules and other created objects.
B_class b_instance = new (a_instance[0]);
C_mod c_modinst (.a(a_instance[0]));
The biggest issue is that a_instance isn't yet initialized.
If you try and initialize with
initial begin
for(genvar i = 0; i < num; i++) a_instance[i] = new();
end
This won't work. Has something to do with object and module creation running before initial lines. The initial statement is too late, a null object was passed in and that's what the object and modules will have.
When I was passing a non array, it would work b/c the declaration included an assignment:
A_class a_instance = new();
but you can't call new on an array.
Here's what appears to work:
A_class a_instance = '{num{A_class::create()}};
This surprised me as I am using replication. It looks like each instance points to its own object. This resolves the problem. Now on the declaration line, I can initialize the objects. Passing the objects around works well now.
Update from Idan's comment:
A_class a_instance = '{default:A_class::create()};
This uses the default syntax for filling in an array. Much nicer than the replication mechanism.
About the create function: SystemVerilog doesn't allow you to call new on a class type so I use a create function instead:
class a;
function new();
$display("creating a");
endfunction
static function a create();
class a_inst;
a_inst = new();
return a_inst;
endfunction
endclass
I believe others refer to this as a factory create function or something like that. Now creating a is as easy as calling a::create().
Passing an object to a module doesn't work since modules are "connected" during elaboration. Object creation occurs only at runtime.
ReplyDeleteReplication creates several different objects because that's like writing the create() method several times.
I think that you can also use the following:
A_class a_instance [num]= '{default:A_class::create()};
Maybe that conveys the idea in a more meaningful way.
Can you please try it and update if that works for you?
The constructor abstraction is indeed used in a factory, but it is meant for something else. Can you please elaborate on why you're passing objects to modules?
Hi Idan,
ReplyDeleteThanks for the default tip. That works great. I updated the blog to reflect that.
Passing an object to a module is legal and very useful.
The SV spec's formal syntax shows that a data_type can be an input to a module, and data_type includes class_type.
Your explanation of Replication is the logical conclusion considering how it behaves, but I didn't see anything in the SV spec explaining that. Perhaps you can point it out. Specifically I am surprised that replication or default calls the function each time as opposed to copying the results of a single call.
I do not know the terminology of OVM/UVM and other systems, so I am not sure what is meant by a factory. I believe it is what I do with the create call.
Passing objects to modules are useful when you want a programmatic way of passing information in and out of modules. Specifically useful for calling functions.
Hey Nahum,
DeleteIn my perspective, modules are design entities, while classes are TB entities. That is why I never thought of passing objects to modules. Your suggestion is new to me.
For using reusable methods inside modules, you can also use interfaces. Methods can be declared inside interfaces, and interfaces can be easily passed to the modules. Since interfaces are also created at elaboration, like modules, the null objects problem does not arise.
By the way, as I see it, interfaces are both design and TB entities, and bridge the gap between them.
I do remember one time where we instantiated objects, and actually defined the class itself, inside the module definition, for coverage collection.
Regarding the replication, please look at SystemVerilog 2009, section 5.11 (Array literals) in page 43. It shows how replication is expanded. Specifically, from the LRM:
int n[1:2][1:6] = '{2{'{3{4, 5}}}}; // same as
'{'{4,5,4,5,4,5},'{4,5,4,5,4,5}}
Although I didn't find it written explicitly, I assume that the spirit of the LRM is that replication should be expanded. Thus, the constructor is called several times, one time for each element in the array.
Regarding factory: that's some way to create an instance, without knowing exactly which type gets created. It's an abstraction to the constructor. The object that gets created can be either the base class, or one of the extended classes. For example, if class A is the base, and class A1 is an extension, then you when you call the create() method, you don't know which one is created. Either A or A1. But due to polymorphism, you do not care. You can later on try to cast it if you want. The factory holds a certain "blueprint" object, and creates the new object by copying the blueprint. The user can change the blueprint to A or A1 at any point in time, as desired.
This can also be used without class extensions, and still be useful, by changing the rand_mode() and constraint_mode() of variables and constraints in the blueprint (as long as the copy method retains the randomization modes of variables and constraints).
In VMM, this is can be used to create different scenarios.
Factory needs some more elaborate explanation, so I hope this concise paragraph doesn't confuse anyone.