Fixing Xcelium Hierarchical Name Error (NOTDOT) on Module Parameters in SystemVerilog
When developing testbenches or integrating IP blocks in SystemVerilog, you might occasionally reference a configuration parameter from another module directly, such as using rtl.PARAM_NAME. While compilers like Synopsys VCS and Siemens Questa might let this slide without a warning, Cadence Xcelium often stops elaboration cold with an error like this:
xmelab: *E,NOTDOT (./testbench.sv,2|33): Hierarchical name ('rtl.TEST_VALUE') not allowed within a constant expression [4(IEEE)].
Why does this difference across simulators occur, what does the IEEE 1800 standard actually mandate, and how can you write portable code that compiles cleanly across all toolchains?
1. Why the IEEE 1800 Standard Prohibits Hierarchical References in Constants
The core issue lies in the definition of a constant expression. In SystemVerilog, module parameters and localparam variables must be evaluated during elaboration before the simulator can allocate memory, generate structural hierarchy, or unroll generate blocks.
According to IEEE Std 1800-2017, Section 11.2.1 (Constant expressions):
"A constant expression shall not contain any hierarchical references."
When you assign localparam test = rtl.TEST_VALUE;, the right-hand side must be an elaboration-time constant expression. Because dot notation indicating another scope is classified as a hierarchical reference, the standard strictly prohibits it. Allowing cross-module hierarchical paths inside constant expressions introduces circular dependencies and order-of-elaboration ambiguities that the LRM aims to prevent.
2. Is rtl a Module Name or an Instance Name?
In your code snippet:
module rtl;
parameter TEST_VALUE = 10;
endmodule
module tb;
localparam test = rtl.TEST_VALUE;
endmodule
At first glance, rtl appears to be a module definition name rather than an explicit instance. However, when top-level modules are not instantiated inside a parent wrapper, simulators instantiate them implicitly as top-level root instances having the same identifier as their module declaration.
Therefore, rtl.TEST_VALUE is interpreted as a hierarchical reference to the top-level instance rtl. Even if a simulator attempted to interpret rtl as a static module type name, the SystemVerilog grammar does not support referencing parameters through a bare module type name using dot notation; dot-syntax navigation is reserved for hierarchical scopes and structure/class member selection.
3. Why Do VCS and Questa Permit This While Xcelium Errors Out?
EDA vendors frequently implement custom language extensions or relax standard restrictions for usability:
- Synopsys VCS and Siemens Questa: Both compilers detect that
rtl.TEST_VALUEresolves to an un-overridden parameter of a top-level root instance whose value is statically known at parse time. As a proprietary tolerance/convenience, their pre-elaboration steps resolve the reference early and substitute the literal value. - Cadence Xcelium: Xcelium adheres much more strictly to the IEEE 1800 LRM standard checks. Since dot notation across module boundaries matches the grammar of a hierarchical identifier, it immediately trips the rule forbidding hierarchical names in constant expressions.
4. The Portable Solution: Use SystemVerilog Packages
Relying on vendor-specific tolerance makes your codebase fragile and non-portable. The idiomatic, fully IEEE-compliant approach to sharing configuration parameters between designs and testbenches is to encapsulate them inside a package.
// design_pkg.sv
package design_pkg;
parameter TEST_VALUE = 10;
endpackage : design_pkg
// design.sv
module rtl;
import design_pkg::*;
// Module logic can now reference TEST_VALUE directly
endmodule : rtl
// testbench.sv
module tb;
// Package scope resolution (::) is explicitly valid in constant expressions
localparam test = design_pkg::TEST_VALUE;
initial begin
$display("TEST_VALUE = %0d", test);
end
endmodule : tb
Unlike dot notation (.), package scope resolution (::) is strictly permitted in constant expressions by the IEEE standard. Moving shared constants into packages eliminates simulator discrepancy, guarantees clean elaboration across VCS, Questa, and Xcelium, and cleanly decouples your testbench from design hierarchy.