SystemVerilog: The finer details of $unit versus $root.
Another installment of “Longwinded Answers to Frequent SystemVerilog Questions: $root versus $unit”
Believe me – I tried to make this shorter. It’s difficult for me to explain things without a historical perspective.
Verilog was invented to be an interpreted language. Verilog-XL was (and still is) an interpretive engine with single compilation unit use model. In an interpreted engine, all of the source code is parsed and loaded into memory. This means you have to specify all the source files of a design, including the source files of any required libraries, within a single command line before simulating.
VCS (Verilog Compiled Simulator) continued this single compilation use model even though it compiled the code into a machine object saved on disk. Later, it introduced an incremental compile feature that only compiled certain files that needed it, but you still had to specify all the source files on the command line. This is not the same as separate compilation available in most software programming languages where source code can be converted into machine code independently.
Tools such as NCsim and Modelsim introduced the concept of separate compilation, loosely based on the work library concept from VHDL. This is relatively easy to do in Verilog because module definitions are self contained. It turns out that module instantiation syntax is easy to recognize, so the compiler does not need to see the definition of module that is being instantiating beforehand. However, parameter overrides and hierarchical references limit the amount of machine code you can generate in the compilation step. The elaboration step that follows does a lot of the work of generating machine code to handle this.
Superlog, the predecessor to SystemVerilog, was also invented as an interpreted language. It introduced the concept of $root as a global scope that allowed any kind of declaration (data types, classes, variables) along with module definitions nested in that global scope. Any uninstantiated module becomes implicitly instantiated in $root. That’s fine for a single compilation unit, but you can no longer separately compile every module because they may have dependencies on declarations outside their scope. For example
class C;
endclass
module top;
C c_h;
endmodule
There’s no problem if this is compiled as a single file, but if module top were to be compiled separately from the class C definition, it wouldn’t know what the identifier C was supposed to represent, and wouldn’t be able to parse the file.
So the IEEE committee borrowed the concept of packages from VHDL and standardized the concept of a compilation unit. A package allows you to compile definitions in a separate step and import those definitions into another compilation step. Packages create separate namespaces for those definitions as wall as imposing compilation order dependencies.
A compilation unit formalizes a scope that represents what is visible in a compilation step – called $unit in SystemVerilog. If you have a design that is compiled as a single compilation unit, there is really no conceptual difference between $unit and $root. However, once you have a design with multiple compilation units, then $unit represents the top level of each compilation unit, and there is nothing in $root except for the implicitly instantiated module instances. The only time you need to use $root or $unit is when a local name in the current scope hides a name in a higher level scope. For example
Compilation unit 1
function void print;
$display("comp1");
endfunction
module mod1;
mod2 m2(); function void print; $display("mod1"); endfunction
initial $unit::print(); // prints “comp1” //print() would print “mod1”) endmodule
Compilation unit 2
function void print;
$display("comp2");
endfunction
module mod2;
mod3 mod1(); // same name as top-level module function void print; $display("mod2"); endfunction
initial $root.mod1.print(); // print “mod1” // mod1.print() would print “mod3” endmodule
module mod3; function void print; $display("mod3"); endfunction endmodule
This example prints “comp1” and “mod1” in either order. Note that there is no way for compilation unit 1 to directly refer to anything in compilation unit 2, or the other way around.
I hope this clears up some of the confusion between $root and $unit in SystemVerilog.
Dave Rich
Comments
Leave a Reply
You must be logged in to post a comment.
Dave,
Good explanation. It seemed though like there was not consistent interpretation of what a compilation unit actually was across EDA vendors. The 2005 spec had described this one or more files and tools like ncsim consider the compilation unit to be all files compiled at the same time. Questa by default considered each file a separate compilation unit (which you can change via a compiler flag). I assume the effect of $unit is impacted by this interpretation.
Hopefully the ambiguity around compilation units being cleared up in the next SV specification.
Jeff
Hi Jeff,
The latest LRM leaves the exact mechanism for how a tool defines the files that make up a compilation unit up to the implementation. However, the LRM does specify that a tool must support at least two use models: all files in one compilation unit, and each file a separate compilation unit.
Simulators that support those two use models will have a consistent interpretation of a compilation unit.
Dave
Hi Dave,
This article is really helpful. I wanted to know how the compilation flow happens for system verilog files. Assume that the files are listed in an order, where dependencies are taken care of. The simulator starts a compilation unit with a say (module/interface/program block definition), then if it encounters a package include. It creates a separate compilation unit for the package. Simulator finishes the package compilation and then comes back to the compilation unit where it let things off. Is this order of compilation correct?
If this is correct, then only packages create separate compilation units/steps apart from the single compilation unit that takes care of all the other files. Please clarify this. Thank you for your patience
Regards,
Bharadwaj
Hi Dave,
Hoping you are well 🙂 Absolutely useful article, as all others that you write.
I had a followup question based on this article.
How do you think this following code should behave? Should it compile?
% cat test1.sv
logic test1;
module file1;
`include "base.sv"
endmodule
% cat test2.sv
logic test2;
module file2;
`include "base.sv"
endmodule
% cat base.sv
class base;
int inst;
function void func;
case(inst)
1 : $unit::test1 = 1'b1;
2 : $unit::test2 = 1'b1;
endcase
endfunction
endclass
% vlog test1.sv test2.sv
Thanks,
–SK
It depends. If the default is compiling each file in separate compilation units, test1 and test2 do not exist in the same $unit, so it would be an error. But if they are in the same compilation unit, there is no error.
I think -mfcu makes the “vlog test1.sv test2.sv -mfcu” a single compilation unit. I remember it now. Thanks.
So I am seeing this prevent:
`ifdef CKT_WITH_JTAG
`include “ckt_optional_jtag_instance.sv”
`endif
Does that make sense? compiling the ckt_optional_jtag_instances.sv says Instances are not allowed in compilation-unit scope/$unit.
Shucks!
hi dave,
i am trying to put import uvm_pkg::* in tb-top.
but i am getting error
Error-[SV-PIU] Package import statement in $unit scope.
compiler in vcs.
i tried checking error but could not get any hint.
Importing in $unit is perfectly legal works with vcs on EDAPlayground.