introduction
After a Verilog design has been parsed, the design must have the modules being instantiated linked to modules being defined, the parameters propagated among the various modules and hierarchical references resolved. This phase is called static elaboration.
During static elaboration the following steps are executed:
- top-level modules and tree of instantiations under each of them are identified.
- Deparam statements and module instantiations are processed to calculate the parameter values of the modules for every instantiation.
- Hierarchy names are checked for their validity and usage. These hierarchical names remain in designs.
- Generate statements are processed. For generate unrolled, if/case generate selects the correct concurrent statements to be present in the module.
- Instance arrays are flattened.
- Depending on hierarchical name usage and defparam values the same module gets different signatures. They are replicated and proper values are passed within them hierarchically before attaching them to the corresponding instantiations.
- In each module, parameter values are replaced with constant values by evaluating the constant expression assigned to them. Constant expressions include constant function calls, some system function calls AMS function calls and all operators.
- Function and task calls are checked for their validity (if they are defined in higher level of hierarchy and used in lower level).
The following example shows how utility works on RTL design.
Detailed description
Top level module extraction
After analysis, module instantiations are bound name based. -y, -v options and uselib are processed to bind unresolved instantiations. After instance binding top-level modules can be identified. According to verilog 95 rule any un-instatntiated module is assumed to be top-level design and needs elaboration.
Hierarchical tree creation
In elaboration for each top-level module a pseudo hierarchical tree is created considering the top level module as root. Each module instantiation within the top level will be considered as a child of the root and instantiations within the child will be grand children of the root and children of previous child. In this way the full grown hierarchical tree is created.
The parse tree is like the following :
after static elaboration, the hierarchical tree will look like this :
Each node of the hierarchical tree contains the following information:
- A list of declared parameters and their value pair. The default or overwritten value of parameter is stored along with the tree node where the default or overwritten expression is declared
- A list of hierarchical identifiers referenced in that module. For each hierarchical identifier a placeholder is maintained to store the tree node where the first object referenced by the hierarchical identifier is defined. The declaration scope of the hierarchical identifier is also stored.
- A list of defparams and their value pair. All the hierarchical identifiers for defparams are also inserted in the list of hierarchical identifiers.
- Pointer to the module represented by the node
- Pointer to the instantiation for which the node is created. The instantiations within the generate construct are to be ignored in hierarchical tree creation, as generate constructs can not be elaborated until the parameters associated with a module have obtained their actual values. When an instantiation is processed to create the hierarchical tree, the hierarchy under the instantiated module is created, and then the parameter values of instantiated modules are overwritten according to the parameter value assignment list of the instantiation. However, the overwriting of parameter values is not performed in actual parse tree, the new values of the parameters are stored in corresponding pseudo tree node replacing their default stored values.
Hierarchical identifier resolution
After tree creation, the next step is hierarchical identifier resolution. To resolve a hierarchical identifier, the first object referenced by the hierarchical identifier is searched, starting from the scope where the hierarchical identifier is defined. To search the first referenced object, the following steps are performed(sequentially):
- The first referenced object is searched in the scope where the hierarchical identifier is defined. If it is not in this scope, the parent scope is searched. This hierarchically upward scope searching is continued until module scope is reached.
- Check if the object is the module containing the hierarchical identifier itself.
- Check if the object is any instance instantiated in that module.
- Search the object traversing hierarchically upward. In each hierarchical parent node the following steps are performed for searching
- i) Check if the object is the module representing that node.
- ii) Check if the object is any instance instantiated in that module.
- iii) Check if the object is any function/task declared in that module.
- Check if the object is any top level module.
- Check if the object name matches with any uniquely instantiated module.
When the first object is resolved, the remaining objects referenced by the hierarchical identifiers are checked for validity. If the hierarchical identifier is determined to be valid identifier, the tree node (where the first object is declared) is stored in the corresponding tree node (containing the hierarchical identifier).
Parameter value evaluation
The value of a parameter can depend on the value of other parameters or on itself. For example.
In the above design, the value of parameter p1
depends on the value of parameter p
of module child
. On the other hand value of parameter p2
depends on parameter p1
of module top
and parameter p
of module child
.
Again, for the following design, the value of a parameter p
depends on itself.
For the above design, to evaluate parameter p
, parameter q
of module child
is to be evaluated (and again, the evaluation of parameter q
depends on evaluation of p
). So this is a circular dependency and parameter p
and q
can not be evaluated.
To check circular dependency while evaluating each parameter, the following steps are to be performed:
- Create a duplet containing the parameter identifier to be evaluated and the hierarchical tree node where the parameter is declared.
- A unique list of duplets is maintained to check that if same duplet is created twice.
- Extract the parameters on which the default/overwritten value of the parameter depend.
- Try to evaluate the value of dependent parameters. To evaluate each dependent parameter first create another duplet containing the dependent parameter and its declaration node.
- Check if the new duplet is existed in the unique list of duplets. If exists, it is concluded that there is circular dependency and evaluation of the parameter fails. Otherwise the above two steps and this one is repeated to evaluate all dependent parameters.
- After evaluating dependent parameters, evaluate the value of actual parameter. Moreover, many times it may be possible that all the parameters can not be evaluated in one pass of the full grown hierarchical tree due to dependency. So to evaluate all parameters and check circular dependency the following algorithm is to be followed:
- Traverse each hierarchical tree to evaluate all parameters.
- Examine the number of unresolved parameters in this traversal.
- If unresolved parameters exist, the hierarchical tree is to be traversed again to evaluate parameters.
- If in current traversal the unresolved parameter count remains the same, it is concluded that circular dependency exists. Otherwise if unresolved parameter count is greater than zero, the hierarchy is to be traversed again.
- Repeat comparison and traversal if required.
Signature creation
A signature is created for each hierarchical tree node considering its parameter values, hierarchical references, and the signatures of its children. If a module is instantiated multiple times at different level of hierarchy, a hierarchical reference within that module can refer to different objects in different modules.
The following example illustrates the effect of hierarchical reference on signature:
Hierarchical identifier child.p
in gChild
refers to parameter p
of module child for the hierarchy top.I.I2(10)
, and again it refers parameter p
of module mod
for hierarchy top2.child.I3(50)
.
The next example illustrates the effect of signatures of child nodes:
In the above example, for hierarchy top.I1.I
the value of parameter p
of gChild
is 20, but for hierarchy top.I2.I
the value of parameter p
of gChild
is 10. So the signature of module child will be unique for instance I1
and I2
. The elaborated design will be:
Copy modules for different signatures
When a module is instantiated multiple times, each instance can have different signatures or a group of instances can have same signature. Moreover multiple modules of same name (from different libraries) can be instantiated in a design. To consider this for each module, a hash table is maintained. The hash table associates each signature of the module with an array of hierarchical tree nodes containing that signature. Another hash table is maintained to associate module pointer with the previous hash table for that module.
Modification of parameters in actual parse-tree
In elaboration, the overwritten values of parameters are stored in a corresponding hierarchical tree node. After copying the modules for different signatures, the parse-tree is modified to set the actual values of parameters. Moreover, the resolved defparam
s are removed from the parse tree, and the task/function calls (situated hierarchically upwards) are bound, extracting information from the corresponding hierarchical tree node.
Generate construct and array instance elaboration
When the parameters associated with a module become defined, the generate instantiations and array instances can be resolved. In this stage, each module is examined to elaborate generate constructs and array instances declared within that module.
Generate construct elaboration
Generate statements allow control over the declaration of variables, functions, tasks, and instantiations.
- Generated declarations and instantiations can be conditionally instantiated into a design.
- Generated variable declarations and instantiations can be instantiated into a design multiple times.
- Generated instances have unique identifier names.
Generated instantiations are one or more of the following:
- modules
- user defined primitives
- Verilog gate primitives
- continuous assignments
- initial blocks
- defparam statements
- always blocks.
Generated statements are created using one of the following three methods:
-
generate-conditional elaboration
A generate-conditional is an if-else-if generate construct that permits modules, user defined primitives, Verilog gate primitives, continuous assignments, initial blocks and always blocks to be conditionally instantiated into another module based on an expression (condition expression) that is deterministic at elaboration time. So in elaboration the condition expression of generate-conditional is evaluated. If the condition is true, the true statement of the generate-conditional is elaborated. Otherwise the false statement of generate-conditional is elaborated.
-
generate-case elaboration
A generate case construct permits modules, user defined primitives, Verilog gate primitives, continuous assignments, initial blocks and always blocks to be conditionally instantiated into another module based on a select one-of-many case construct. So in elaboration the case condition expression and the condition expressions of case-items are evaluated. The value of the case condition is compared with the values of case-item conditions. If any case-item condition matches with the case condition, corresponding case-item statement is elaborated. If no case-item condition matches with the case condition, the default statement (if any) is elaborated.
-
generate-loop elaboration
A generate-loop permits one or more variable declarations, modules, user defined primitives, gate primitives, continuous assignments, initial blocks and always blocks to be instantiated multiple times using a for-loop. The index loop variable used in a generate for-loop is declared as a genvar. The initial, final and increment value of the loop index variable are determined. From these the iteration count of the loop can be determined. The generated name for the scope at each iteration of the loop is created by adding “[genvar’s value]” string to the end of the generate block identifier for the loop.
for example:
The generated instance names are
blk[0].g1, blk[1].g1, blk[2].g1, blk[3].g1
, and generated nets areblk[0].t1, blk[1].t1, blk[2].t1, blk[3].t1
. The unrolled loop will beTo perform the above, before elaborating the statements in each iteration of the for-loop, the objects declared within the generate block of the loop are copied with the new generated names, and those copied objects are stored in a hash table associating old identifiers with the newly created identifiers. While elaborating the statements of the for-loop, these newly created objects replace the old ones. In each iteration, the hierarchical identifier references are also stored, so that after that iteration the hierarchical identifiers can be modified.
for example :
For I = 0, the hierarchical identifier b2.p will be modified to b1[0].b2.p For I = 1, hierarchical identifier b2.p will be modified to b1[1].b2.p
-
elaboration of module, user defined primitive and gate instantiation
If these constructs are within a generate-loop, the constructs are copied and the instance names are modified as stated above. Otherwise, the constructs are detached from generate construct and added to module item list.
-
elaboration of function/task and variable declaration
If these declarations are within a generate-loop, the declarations are copied and the names of the declared objects are modified as stated above. Otherwise, the declarations are detached from the generate construct and added to module item list.
-
elaboration of always-construct, initial statements
If these statements are within a generate-loop, the statements are copied. If the statement of always/initial is a sequential/parallel block, the name of the block is modified. Otherwise the statements are detached from generate construct and added to module item list.
-
elaboration of other items
If these items are within a generate loop, the items are copied and added to module item list. Otherwise the items are only detached form generate construct and added to module item list.
After generate construct is elaborated, the original generate construct is removed from the parse-tree.
Array instance elaboration
There are many situations when repetitive instances are required. These instances shall differ from each other only by the index of the vector to which they are connected. In order to specify an array of instances, the instance name shall be followed by the range specification. An array of instances shall have a continuous range. One identifier shall be associated with only one range to declare an array of instances.
For examples:
The above array instances is equivalent to:
In array instance elaboration, multiple single instances are created from the instance array depending on the width of the range associated with the instance name. For single instance creation, the bit length of each port expression in the declared instance-array shall be compared with the bit length of each single-instance port or terminal in the instantiated module or primitive. For each port or terminal where the bit length of the instance-array port expression is the same as the bit length of the single-instance port, the instance array port expression shall be connected to each single-instance port. If bit lengths are different, each instance shall get a part-select of the port expression as specified in the range.
The following steps are performed for array instance elaboration:
- For each port or terminal where the bit length of the instance array port expression is different from the bit length of the single-instance port, a wire type object is created in the module scope. The word range of the created object will be same as the bit length of the instance array port expression. If the direction of the single-instance port for the terminal is input, the instance array port expression is set as the initial value of the declared net object. Each instance shall get a part-select of the created object as the terminal expression.
- The terminal expressions of each elaborated single-instance will be as follows:
- If the bit length of the terminal is equal to multiplication of bit length of the single-instance port and width of instance range, the terminal expression of the single instance will be a part select of the created net object (as stated in above step). Otherwise the terminal expression of the single instance will be same as the corresponding terminal expression of the instance array.
- The instance name of the elaborated single instance will be “instance name of instance array [range index]”.
- If the direction of the formal is inout, tran gates will be created to handle inout properties of the port. The number of created tran gates will depend on the bit length of the formal. One terminal of the tran gate will be bit select of the created single-instance terminal and another terminal will be bit select of the corresponding instance array terminal.
- After creating all single instances, the terminals of the instance array are examined. For each terminal of the instance array, if the direction of the formal is output, a continuous assignment is created. The left hand side of the assignment will be the terminal expression of the instance array and the right hand side of the assignment will be net type object (created in first step for this terminal).
- Original instance array is removed from the parse-tree.
Example:
Hierarchical tree expansion
After generate construct and array instance elaboration, some module instantiations are added in the module item list. These instantiations will create a new hierarchy, so the hierarchical tree is expanded in this stage if required. This expansion will introduce some additional hierarchical references, defparams, and generate constructs. To process the introduced constructs, hierarchical identifier resolution, parameter evaluation, signature creation, module copy, module updating, generate/array instance elaboration, and hierarchical tree expansion are performed until all generate constructs are elaborated.
Modification of hierarchical references and master name of instantiations
In elaboration modules are replicated for different signatures. Due to replication the master name of the instantiations and the hierarchical references can be changed.
Example:
Deletion of parameter value assignment list from instantiations
The last step of the elaboration is to delete parameter value assignment list from module instantiations as the parameters associated with the modules now obtain overwritten values.
Constant Expression Replacement
VERILOG_REPLACE_CONST_EXPRS
compile flag.
- Bounds of packed and unpacked ranges in all data declartions
- Default values of all declared objects if those are constant
- Constant bit/part/slice expressions from all identifier references.
- Delay values
Limitations
There are SystemVerilog corner cases in which the prettyprint output of the elaborated parse tree can be illegal SystemVerilog, or have incorrect behavior.
In some such cases, the following runtime flags can be used to prettyprint a legal output file.
- module type parameter override
- Class type parameter override
- same function identifier in different packages
- hierarchical reference of parameter
- parameter of different instances of interface
- parameter override of some items of interface array
- parameter override of some items of virtual interface array
- parameter in self-referencing class
- generate block and identifier declared in generate blocks
- nested interface parameter type