Verilog: loop-local genvar scoping - #2091
Draft
kroening wants to merge 4 commits into
Draft
Conversation
Per 1800-2017 27.4, a genvar that is declared in the header of a loop generate construct is local to that loop. Two loops in the same scope may hence both declare a genvar with the same name. Such genvars are no longer added to the enclosing scope; their value is tracked in the genvar environment, which is also what references to them are now resolved against, and they are removed from that environment once the loop has been elaborated. Genvars that are declared separately from the loop are unaffected, and remain shared between loops. This fixes the KNOWNBUG added in #2085.
A genvar that is declared in the header of a loop generate construct is local to that loop, 1800-2017 27.4. It does not have a symbol of its own, and hence a reference to it must be resolved using the genvar environment rather than the symbol table. Any symbol with the same base name that is visible from the scope that contains the loop is shadowed for the extent of the loop, i.e., in the loop condition, the iteration expression, and the loop body. The genvar environment now records, for each genvar that is local to a loop, the scope that contains that loop. A reference resolves to the genvar when the symbol that resolve() finds is not declared inside the loop. Genvars that are declared separately from the loop generate construct are unaffected, as these do have a symbol.
The elaboration of module instances, of their parameter values, and of parameter overrides recurses through the set_genvars module items that the elaboration of the generate constructs has produced, but did not restore the genvar environment when doing so. This went unnoticed for genvars that are declared separately from the loop generate construct, as these have a symbol that resolve() finds, albeit with the value the genvar has once the loop has terminated. A genvar that is declared in the header of the loop generate construct is local to that loop, 1800-2017 27.4, and does not have a symbol, and hence failed to resolve altogether. The genvar environment is now restored from the set_genvars module item in elaborate_module_instances, parameterize_instantiated_modules, and process_parameter_override, which also yields the value the genvar has in the given iteration of the loop.
The loop-local genvar fix makes the genvar in the part selects of the port connections evaluate per iteration, and hence this test now passes. Leaving it as KNOWNBUG fails the KNOWNBUG checks CI job, which runs test.pl -K and expects KNOWNBUG tests to fail.
Collaborator
Author
|
Note on overlap with #2067: both PRs flip `regression/verilog/generate/generate-inst3.desc` from KNOWNBUG to CORE, and #2067 additionally adds `regression/verilog/port-connections/output_port_genvar1.{sv,desc}` as a CORE test for a genvar-indexed output port connection in a generate loop, which this PR also fixes. Whichever lands second will need a trivial conflict resolution in `generate-inst3.desc` and in `verilog_elaborate_module_instances.cpp`. |
kroening
marked this pull request as draft
August 12, 2026 15:03
Collaborator
Author
|
I am contemplating whether it would not be easier to simply create an unnamed scope for each generate- |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Per 1800-2017 27.4, a
genvarthat is declared in the header of a loop generateconstruct is local to that loop. Two loops in the same scope may hence both
declare a genvar with the same name:
This fixes the KNOWNBUG added in #2085, which previously failed with
definition of symbol `gi' conflicts with earlier definition.Root cause
elaborate_generate_declcreated a symbol for the genvar usinghierarchical_identifier(base_name), i.e. in the scope that encloses the loop,irrespective of whether the genvar was declared in the loop header or
separately. The second loop hence tried to insert a symbol that was already
there.
Approach
A genvar that is declared in the header of a loop generate construct is now
recognised as loop-local by
elaborate_generate_for, which callselaborate_generate_declwith a newloop_localflag. Such a genvar is nolonger added to the enclosing scope. Its value is tracked in the
genvarsenvironment, which is what the genvar's uses are now resolved against. This
works both while elaborating the loop and when type checking the elaborated
module items, since these carry the genvar state in the
set_genvarsitems thatthe elaboration produces already.
Once the loop has been elaborated, the loop-local genvars are removed from the
genvar environment, i.e. they go out of scope, restoring any genvar of the same
name that they shadowed (nested loops). A reference to the genvar after the loop
is now rejected (
genvar_scope4).Resolution order
Since a loop-local genvar has no symbol, but may well have the same base name as
a symbol that is visible from the scope that contains the loop, the genvar
environment takes precedence over such a symbol for the extent of the loop, i.e.
in the loop condition, the iteration expression, and the loop body:
To that end, the genvar environment records, for each genvar that is local to a
loop, the scope that contains that loop. A reference resolves to the genvar
when the symbol that
resolvefinds is not declared inside the loop; a symboldeclared inside the loop, e.g. a function argument, still wins. Outside of the
loop the enclosing symbol is found as before.
Genvars during the elaboration of module instances
The elaboration of module instances, of their parameter values, and of parameter
overrides recurses through the
set_genvarsmodule items that the elaboration ofthe generate constructs produces, but did not restore the genvar environment
when doing so. This went unnoticed for genvars that have a symbol, as
resolvefinds these (albeit with the value the genvar has once the loop has terminated),
but a loop-local genvar has no symbol and hence failed to resolve at all:
elaborate_module_instances,parameterize_instantiated_modulesandprocess_parameter_overridenow restore the genvar environment from theset_genvarsitem, in the same way #2067 does for the first two of these. As aside effect, port connections and parameter values that use a genvar which is
not loop-local now also see the value the genvar has in the given iteration,
which is the bug that #2067 fixes;
generate/generate-inst3passes with thischange, but is left as
KNOWNBUGhere so that #2067 flips it.Header-declared vs. separately declared genvars
Only genvars declared in the loop header are loop-local. A genvar that is
declared separately, e.g.
genvar gi;in the module body, remains a symbol inthe enclosing scope and may be shared by several loops; that path is unchanged
(
genvar_scope2).A loop-local genvar that clashes with a symbol that is already present in the
scope that contains the loop is still reported as a conflict, with the same
message as before (
genvar_scope3).Tests
generate/genvar_scope1is nowCORE, and checks that the two loops yieldindependent instances with the expected values (
main.b1[i].wisi,main.b2[i].wis10+i).generate/genvar_scope2(new): a module-scopedgenvarshared by twosequential loops.
generate/genvar_scope3(new): a loop-header genvar clashing with a wire inthe same scope as the loop is still an error.
generate/genvar_scope4(new): the loop-header genvar is not visible afterthe loop.
generate/genvar_scope5(new): a loop-header genvar shadows a wire with thesame name in a scope that encloses the loop.
generate/genvar_scope6(new): likewise for alocalparamand for a genvarthat is declared in an enclosing scope.
generate/genvar_scope7(new): two nested loops both declaring a genvar withthe same name in their header.
generate/genvar_scope8(new): a loop-local genvar in the port connections(input and output) and in the parameter values of a module instance.
regression/verilog(testandtest-z3),regression/ebmc,regression/smv,regression/vlindexand the unit tests all pass.