A data structure for recording elaboration results, i.e., functions, types, and parameters.
An elabindex is most useful when you are going to be adding new elaboration results. To simply examine elaboration results, you may use a vl-elabscopes. This is a data structure used in an elabindex, but also has a set of functions appropriate for using it in an applicative manner.
One of the biggest difficulties in processing SystemVerilog is the complicated dependencies among functions, parameters, and types. There is no way to unravel these dependencies so that we can (e.g.) resolve parameter values first, then types, then function definitions: in fact, each of these can depend on all the others. So they must all be processed in one step, so that if a parameter depends on a function definition, we can go and resolve that function definition, which may depend on other parameters and types, before coming back to the original parameter with the function's resolved definition.
The step that resolves all of these is called elaboration. During elaboration, we unparameterize modules and traverse the design in order to resolve types and replace constant expressions with their values where possible. Elaboration is complicated by the fact that there is no set order for dependencies; we must be able to go look up an arbitrary parameter/function/type anywhere in the module and resolve it before continuing.
This style of traversal is difficult for our applicative-style data structures; when we go to a different part of the design, resolve something, and come back to another part of the design, it is expensive to record the result that we obtained in the design, especially since it has been processed (e.g.) a scopestack.
An elabindex is our solution to this problem. It is designed to allow us to efficiently record and look up results while performing both treelike and non-treelike traversals of the design.
An elabindex contains a scopestack and an elabscopes. A scopestack contains all the smarts necessary to look up items correctly (which can be nontrivial when you factor in imports, etc.). The elabscopes contains analogous scopes as the scopestack, but these scopes record elaboration information and are designed to be easily updated, as well as the subscopes contained within. To make writes to a scope permanent, each time we go up a scope, we pop the scope off of the elabscopes, but write it into the parent scope so that next time we go into that scope, we'll get our most recent updates.
An exception to this: Some scopes are anonymous, e.g., a block statement inside an always block. Such a scope can't be accessed by name from within the surrounding context -- it can only be reached through a treelike traversal, i.e., processing all the always blocks in a module. When going up in scope from some such a scope, we don't write it to the parent scope because it doesn't have a name to index it by.
A tricky use case: Suppose we are working in some anonymous scope, e.g., a block statement inside an always. Such a scope can't be accessed by name from within the surrounding context -- it can only be reached through a treelike traversal, i.e., processing all the always blocks in a module. Then, suppose we need to look up some parameter located in a package. We need to go into that parameter's context to compute its value, then pop back to our previous context.
How can we accomplish this? Some ideas and their flaws:
Our actual implementation:
Keep a stack of traversal instructions that say how to get back to the previous scope from the scope you reached. Each such traversal is a sequence of instructions, where each instruction is one of:
Distinguishing between named and anonymous scopes in this way allows named scopes to be updated via "random access", i.e. following parameter/function/type dependencies. Anonymous scopes don't need to be updated this way because parameter/function/type dependencies won't take you back to one. (An exception, arguably, is a block scope inside a function definition. But this should only matter if the function calls itself recursively, which we don't support for now anyway.)