Skip to content

Unified Verilog hierarchical reference resolver#1508

Draft
gitmodimo wants to merge 3 commits into
nickg:masterfrom
gitmodimo:vlog-hier-ref-unified
Draft

Unified Verilog hierarchical reference resolver#1508
gitmodimo wants to merge 3 commits into
nickg:masterfrom
gitmodimo:vlog-hier-ref-unified

Conversation

@gitmodimo
Copy link
Copy Markdown
Contributor

Adds a single elab-time two-pass resolver that handles all hierarchical access patterns uniformly:
downward, upward, cross-instance, $root-anchored, and through generate blocks.

Why this is one large change

Two earlier local attempts tackled hier-refs incrementally: a sem-time downward resolver for
parent.child.signal within the lexical scope, and an elab-time upward resolver for cross-instance
access after the full tree was built. Neither generalised well — the sem-time pass set I_REF to the
prefix head which conflicted with later elab-time rebinding; cloned instances shared resolver state
across clones; and reheat had to independently recompute aliases that elab had already derived. Fixing
one edge case kept breaking another because two resolvers with different lifetime assumptions were
sharing the same node slots.

Those attempts didn't pan out, so this branch starts fresh with a unified design: one resolver, one
pass over the full instance tree, one alias computation shared by elab and reheat. The scope is large
because hierarchical references touch every pipeline stage — parser, sem, elab, lowering, JIT, reheat,
and VPI. Splitting it would leave intermediate states where some access patterns silently produce wrong
results.

Pipeline changes

  • Parser: p_hierarchical_path is now the single entry point for all dotted identifiers. Event triggers
    (->, ->>), disable, and hier-ref task/function calls all route through it.
  • Sem: V_HIER_REF nodes pass through with deferred type checking. Lvalue checks accept them
    unconditionally — the elab resolver owns validation.
  • Elab: Two-pass resolver. A best-effort pre-elab pass resolves downward refs to parameters (needed for
    constant expressions in reg widths and generate conditions). A post-elab pass walks the full instance
    tree and resolves everything else.
  • Lowering: Single V_HIER_REF code path via vlog_hier_unit_alias(). Parameters are inlined as
    constants; runtime variables use link_package(alias) + link_var. Adds force/release lowering and
    V_CONCAT non-blocking assignment lvalue support.
  • JIT: irgen_instance_entry stores PUTPRIV so link_package can find each clone's context for
    cross-instance access.
  • MIR: mir_alias_unit maps per-clone aliases to shared units so multi-instance modules resolve
    correctly.
  • Reheat: Mirrors the elab alias computation from persistent data — no reheat-specific logic.

Design disciplines (enforced by the implementation)

  1. No per-decl-kind branches in resolver tail lookup
  2. Single parser hier-path helper used everywhere
  3. Kind-agnostic scope walk (new scope kinds adapt automatically)
  4. Single canonical-name encoder (vlog_canonical_scope_name)
  5. Lvalue checks cover future parent kinds

Also included

  • ->> (nonblocking trigger) token and parser support
  • Instance-array bracket parsing at the syntax level
  • force/release statement lowering

Blocked spec tests

19 spec tests are committed but commented out in testlist.txt, each annotated with its specific
orthogonal blocker (e.g. V_FORK sem, defparam, program/endprogram). When each blocker lands,
uncommenting the test is the only change needed.

@gitmodimo gitmodimo marked this pull request as draft April 20, 2026 14:22
@gitmodimo gitmodimo force-pushed the vlog-hier-ref-unified branch from 4423b90 to 8c377f7 Compare April 23, 2026 13:53
Rafał Hibner added 2 commits April 27, 2026 12:37
Bring Verilog hierarchical references to a single elab-time
resolver consuming a language-neutral scope tree shared with
VHDL.  Replaces the previous stack of sem-time + elab-time
mechanisms with one pass run after the full instance tree is
built.

Resolver:

  * Anchor-table dispatch on V_HIER_REF.I_SUBKIND
    (RELATIVE / $root / $unit) — adding a new anchor (this/super)
    is an enum value plus a dispatch case, not a rewrite.
  * Synthetic-root sentinel always present at the head of the
    walk-up chain; bare <top>.<sig> references reach a sibling
    top under multi-top elab via library-prefix synthesis.
  * The resolver writes the resolved target's dotted path on
    I_IDENT2 and re-binds I_REF to the tail decl.  I_VALUE is
    not written — it would be a cross-tree edge that
    object_visit follows, letting re-deferred resolution passes
    fan out into V_HIER_REFs in unrelated bodies.

Named procedural blocks (IEEE 1800-2017 §23.6) become
hier-addressable scopes:

  * V_BLOCK gains an I_IDENT2 slot for the canonical scope name.
  * elab_verilog_block recurses through procedural control-flow
    on the parent's tree before vlog_new_instance copies it,
    pre-stamping every nested named V_BLOCK with its dotted.
  * Named V_BLOCKs are added to copy_instance_pred and
    copy_generate_pred so each clone / iteration owns its own
    node and its own per-clone scope name.
  * vlog_lower_stmts pushes a named-block frame on entry to a
    stamped V_BLOCK; V_REFs to block-local decls reach the
    wrapper unit's signal storage via link_package + link_var.
    link_package is re-emitted at every use site so the SSA
    value is live in the current basic block.
  * elab_verilog_proc_blocks descends V_INITIAL/V_ALWAYS bodies
    through the full procedural-control-flow set
    (V_IF/V_COND, V_CASE/V_CASE_ITEM, V_FOR_LOOP, V_FOREVER,
    V_REPEAT, V_WHILE, V_DO_WHILE, V_WAIT, V_TIMING) to reach
    named V_BLOCKs at any depth.
  * elab_verilog_sub_blocks descends procedural control-flow
    at the top level of a V_BLOCK body too.

Cache-hit clone-sharing is gated: bodies containing per-clone
state (tracked through vlog_has_per_clone_state — single source
of truth shared by copy_instance_pred, copy_generate_pred, and
elab_module_needs_per_clone_state) get a fresh ei (and therefore
a fresh MIR) per clone.

DEBUG assertions:

  * Forward — every Verilog body deferred for hier-ref
    resolution must have a scope_tree entry.
  * Reverse — every named V_BLOCK reachable inside any deferred
    body must have I_IDENT2 set and that dotted must hash to a
    scope_tree entry.
  * Post-resolver — every V_HIER_REF must have I_IDENT2 set and
    I_VALUE clear.

Other corrections that landed alongside the unified resolver
work and are needed for the regression set to pass: PSL next_a
support (see psl-fsm.c, psl-dump.c), terminal-link escape
handling on diag output, and removal of obsolete sem-time
hier-ref errors.
Comprehensive test coverage for the unified hierarchical-reference
resolver and named-procedural-block scopes:

  * vlog46-99 — pre-existing Verilog regression tests that became
    reachable through the unified resolver, covering downward,
    upward, and cross-module references; generate-block scopes;
    forward continuous-assign XMRs; force/release; \$root and
    rooted-absolute paths; static task-local hier-refs.
  * vlog100 — multi-top elaboration baseline.
  * vlog136 — module-instance XMR through a VHDL top, with
    nested named procedural blocks and continuous assigns
    reading block-local values.
  * vlog137 — multi-top elab with VHDL siblings.
  * vlog138 — cross-top hier-ref under multi-top elab; both
    \$root.<sibling>.<sig> and bare <sibling>.<sig> exercised
    in both directions, with a 3-top phase.
  * vlog139 — Verilog hier-ref reaching across a VHDL parent
    boundary to a sibling Verilog instance.
  * vlog140 — multi-instance module containing a named
    procedural block; per-clone storage and force/release.
  * vlog141 — VHDL component-binding two instances of a
    Verilog module that contains a named V_BLOCK.
  * vlog142 — named procedural block inside a for-generate
    iteration; per-iteration deep-copy.
  * vlog143 — named procedural blocks nested inside
    procedural V_IF/V_CASE/V_FOR_LOOP.
  * vlog144 — multi-instance module containing only a
    V_HIER_REF (no named blocks); pure-V_HIER_REF clone gating.

Unit-test additions:

  * test/vlog/href_bracket.v, href_disable.v, href_nbtrigger.v,
    href_taskcall.v — parse-time + sem-time pinning of the
    hier-path forms exercised through the resolver.
  * test/test_vlog.c — updated test_href1 to expect elaboration-
    time resolution rather than parse-time errors for upward
    references.

PSL test additions inherited alongside the resolver work
(psl23.vhd next_a case, gold/psl23.txt, gold/issue1512.xml,
issue1512.vhd, issue1433.sh fixed-string grep fix) reflect
correctness fixes that landed in the same series and are
required for the regression set to pass.
@gitmodimo gitmodimo force-pushed the vlog-hier-ref-unified branch from 0cba1f4 to 9f964cf Compare April 27, 2026 12:40
Two coupled additions:

vlog_lower_systf_param: V_REF whose decl is a V_VAR_DECL inside an
active named-procedural-block frame is now eagerly evaluated
through the named-block-frame redirect (link_package + link_var
into the wrapper unit's signal storage), instead of returning
MIR_NULL_VALUE.  Without this, $display "%h" on a block-local
read empty because the standard VPI handle table doesn't address
named-block locals — same visibility gap that motivated the
named-block frame in vlog_lower_select.  Mirrors the V_HIER_REF
sys-task-param case.

vlog145.sh: combined-corner regression — multi-top elab, named
procedural block in one of the tops, run under reheat (-r).  No
existing test covered this combination: vlog136-144 each cover
one or two corners but not all three.  The named-block local
must survive serialise/restore and the cross-top reference must
work after rebuild.

4-flavour gate verified clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant