In our “ram” library element, we compile in accessor tasks during simulation. Currently, we globally define LIB_VENDOR or LIB_BEHAVIORAL to control implementation. So we have code like:
module ram;
`ifdef LIB_VENDOR
vendor_ram uINST ();
task RamWrite (input a, output b);
begin uINST.VendorRamWrite(a, b); end
endtask
`else
behavioral_ram uINST ();
task RamWrite (input a, output b);
begin uINST.BehavioralRamWrite(a, b); end
endtask
`endif
endmodule
We use the RamWrite task to normalize the task names, arguments, etc, between the various implementations (or to print an error if the implementation doesn’t support a backdoor task).
Now we’d like to migrate to having the implementation controlled by parameter (for example, we may want to sometimes use “RTL RAM” for small memories). I was thinking that the following should work:
module ram #(parameter Vendor = 0) ();
generate
if (Vendor) begin : impl_vendor
vendor_ram uINST ();
end
else begin : impl_rtl
behavioral_ram uINST ();
end
task RamWrite (input a, output b);
begin
if (Vendor) impl_vendor.uINST.VendorRamWrite(a, b);
else impl_rtl.uINST.BehavioralRamWrite(a, b);
end
endtask // RamWrite
endgenerate
endmodule
module vendor_ram;
task VendorRamWrite (input a, output b);
begin $display("vendor ram write"); end
endtask // VendorRamWrite
endmodule
module behavioral_ram;
task BehavioralRamWrite (input a, output b);
begin $display("behav ram write"); end
endtask // BehavioralRamWrite
endmodule
… But this doesn’t work. It seems that SV, for the instances, only “goes into” one of two paths, but within the ram.RamWrite task, it wants both to be there. This doesn’t seem necessary … I believe I understand the classic issues of calling a task within an array of instances, etc, but in this case I thought it should work. Does this seem like a vendor-specific tool issue, and if not, what’s the “recommended” way to do this kind of thing? Note this is design-side SV code, so it would be nice to keep the verif-side complexities (UVM, classes, etc) out of it … if possible.