Skip to content

Test that the bind audit marks callees (§23.8.1) - #975

Closed
philipaxer wants to merge 26 commits into
aionhw:mainfrom
philipaxer:up/6a-binder-complete
Closed

philipaxer wants to merge 26 commits into
aionhw:mainfrom
philipaxer:up/6a-binder-complete

Conversation

@philipaxer

@philipaxer philipaxer commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

Part 10 of the series for #952, which replaces name-string scoping in generate blocks with a scope tree and a binder.

Depends on #974: until that merges, this branch also shows its commits. New here:

  • eca2b6ed Test that the bind audit marks callees (§23.8.1)
  • 2a45db1e Sync core revision

Core side: aionhw/xezim-core#182 (pinned at add1d1b; the last commit, "Sync core revision", sets the pin).

Tests:

  • tests/hierarchy/bind_audit.rs: the audit lines of a block function's calls now read pos=process/call and pos=final/call.

The results and what is not covered are in the core PR.

Tests after the rebase onto main

Rebased onto core 5b0b02e and xezim 2e4e7f99. The results above were measured before the rebase, against the previous PR of the series.

  • core cargo test: 495 passed, 0 failed.
  • xezim cargo test --release --test hierarchy (built without LTO): 901 passed, 0 failed.
  • Before the rebase, on the previous PR of the series: full xezim suite with the release profile 6455 passed, 0 failed, 1 ignored. CI runs the full suites on this PR.

The series

This PR is one part of a stacked series for #952. This section is the same in every part, so each PR can be read on its own.

Problem. A generate block is a scope (§23.9, §27.5, §27.6), but xezim resolved names in generate blocks by string. The elaborator renamed declarations textually (f → g.f) from per-block name lists, and the simulator retried a missed name by its last segment at run time. Each fix to that scheme, including the one for #897, broke other legal programs: a local hiding a block function was renamed anyway, items outside the copied list kept the bare name, two unlabelled blocks with the same variable collided, a child instance's call reached the wrong parent, and disable g.t matched by leaf.

Approach.

  • Core builds a scope tree for each definition: its generate blocks, all arms included, each with its declarations (scope_tree.rs).
  • A binder resolves every simple name against that tree and writes the result on the identifier: a declaration, a local or other hiding name, or "upward" (§23.8.1) (elaborate/bind.rs).
  • The generate rename then follows the binding instead of name lists. The names it produces (g.f, u.g[1].x, genblk2.t) are the keys the runtime already uses, so xezim needs almost no source change.
  • XEZIM_BIND_AUDIT=1 prints each binding next to what the rename did, so every disagreement can be listed.

Parts (core / xezim; each depends on the one before and repeats its commits until that merges):

Part Core xezim What it does
1 aionhw/xezim-core#171 #962 Scope tree; generate scope names (genblk<n>, §27.6 zero padding, §27.5 direct nesting) come from it
2 aionhw/xezim-core#172 #963 Binder and audit; no behaviour change
3 aionhw/xezim-core#173 #964 The rename follows the binding: a name a local, formal, class member or import hides keeps its spelling
4 aionhw/xezim-core#174 #965 The rename reaches final blocks, classes, gates, properties, sequences, let, covergroups, clocking and constraints
5 aionhw/xezim-core#175 #966 Bare upward calls from child instances resolve through the instance tree (§23.8.1)
6 aionhw/xezim-core#176 #967 disable by path and by label is bound like other names (§9.6.2)
7 aionhw/xezim-core#177 #968 Data of unlabelled generate blocks is scoped under genblk<n>
8 aionhw/xezim-core#180 #971 Data is renamed by binding at every position; the upward search continues past a variable
9 aionhw/xezim-core#181 #974 Typedefs of a block take the block's key
10 aionhw/xezim-core#182 #975 The binder covers the remaining legal bare names; no output change
11 aionhw/xezim-core#183 #981 Undeclared names are reported by binding: a bare name in an instance that nothing declares is an error, where it read a value found by its last segment
12 aionhw/xezim-core#184 #989 The generate rename takes its spelling from the binding; the name maps, the rename gate and the audit's comparison are removed (core -1,020/+454)
13 aionhw/xezim-core#185 #990 One generate walker instead of two; genvars are also substituted in properties, sequences, covergroups, gates and formal defaults of a loop (§27.4) (core -347/+101)

Independent of the series: #969 (time scope and clocking of a block in an instance) and #970 (a subroutine with no written lifetime stays static on the compiled tier).

Still to come.

  • Held until there is feedback: duplicate-declaration diagnostics.
  • Not planned: keys for classes in generate blocks. A class in a generate loop stays one class.

The run-time retries by leaf name stay. They serve shapes the binder cannot decide before the run (a class method calling its instance's task, g[k].f() with a variable index, virtual interfaces) and name resolution outside generate blocks (interface ports, $root, packages).

Size so far (parts 1 to 13 against main): core +4,476/-1,066 outside tests (net +3,410), +2,458 tests; xezim +198/-101 outside tests, mostly the core pin, +3,550 tests.

Testing. Every part passed the full xezim suite and the core suite before the rebase onto main. After the rebase, part 7 passed the full suites on main, and parts 8 to 13 passed the hierarchy and core suites. CI runs the rest. Each part's audit and artifact identity against the previous part are in its own text. Expected values come from IEEE 1800-2023 and are checked against Verilator 5.055 where it accepts the program.

Review order. Parts 1 and 2 are the base; the rest read from them. Reviewing those two first settles the direction.

The object model builds the tree on its own parse and reads each construct's
implicit name. A directly nested branch opens no generate scope; its inner
blocks are walked in the enclosing scope.
Each HierarchicalIdentifier built by the simulator starts unbound. The audit
variable is listed in the env var registry.
…§23.9)

The function declaration built for a compiled task carries the task's `decl`.
… §13.5.3, §18.7, §18.12, §3.12.1)

New `generate_block_hiding` runs programs where an inner scope hides a block's
subroutine, and programs where a name still binds to it. `bind_audit` expects
`target=` on each line and no rename of the hidden name, which no longer
disagrees.
…l block, gate, property, sequence, let and constraint (§23.9, §8, §9.2.3, §11.13, §16.12, §18.5.11, §28)

The bind audit test's final-block call now reads as renamed to g.f, with no
group.
…11, §26.3)

Ten programs: a child in a block, a block function before the module's,
a parent instance's before the top's, generate loops in an instance,
nested and unlabelled blocks, a grandchild and a bound instance, task
enables, function bodies and continuous assigns of the child. The
child's unit and imports come first, a parent's import is not visible,
rooted and dotted calls are unchanged. The bind audit prints where each
upward call resolves.
Core names the data of an unlabelled generate block `genblk1.x`, so a
read of `genblk1.x` finds its signal directly. The value lookup and the
sensitivity lookup no longer retry a missed name with its `genblk<n>`
segments removed, which would read a module-level `x` for `genblk1.x`.
…§23.9)

Two unlabelled blocks or case arms each own their variable, parameter
and net; a block's variable hides the module's, at the top and in an
instance; a labelled block in two unlabelled ones is two scopes; ports
filled by `.*` and `.d, .q` read the block's net; `genblk1.x` and
`genblk02.x` reach the data from outside. A bare read of a direct nest's
data from the module is undeclared (§23.9).
Core's `VarDeclarator` carries the declaration id the binder writes
(§23.9). The declarators the simulator builds for a function's result
variable and for scalarized struct members are not in any scope tree.
…§6.21, §23.8.1)

A nested local of a block task hides the block's variable. Final blocks,
gates, classes, clocking blocks, properties and sequences of a block read
the block's own variable or parameter, at the top, in instances and in
loop iterations. A block parameter sizes a process local. A class member,
local or formal named like a block hides its data in an instance. A
constraint reads block data by its bare name. A bare call from a child
passes a variable of that name in its parent's block.
Two blocks each size their variables, function formals and results,
casts and struct members by their own typedef, at the top, in an
instance and in unlabelled blocks. A block's typedef hides the module's
inside the block only, reaches a nested block, a process local, a task
local, a loop variable and a constraint cast, and is hidden by a
procedural typedef. A block struct reaches a child port in two instances.
An enum typedef of a block names its members.
The audit pin of a block function's calls reads `pos=process/call` and
`pos=final/call`; the local of the same name keeps `pos=process`.
This was referenced Nov 8, 2026
@philipaxer
philipaxer marked this pull request as ready for review November 8, 2026 19:28
@aionhw

aionhw commented Nov 9, 2026

Copy link
Copy Markdown
Owner

Integrated by cherry-pick with @philipaxer's authorship preserved, as the whole series: xezim-core 38f31c3…d802056 (core #171–#183) and xezim 163df958…554812e6 (#962–#981). Each layer has a code commit and a test commit, with #952 in the subjects. The per-layer "Sync core revision" commits were replaced by one sync to the final core revision.

Follow-ups for gaps found in review, each checked against the reference simulator:

  • VPI reads of an unlabelled generate block's variables (top.genblk1.x) read the block's value again: 1b5b8584, ddddd148.
  • disable g1.blk and disable r[0].w stop the named block, matched by full path, not one with the same label elsewhere: 2c0d0298, 786612c5.
  • A typedef that depends on the genvar of a generate-for inside an instance has each iteration's width: 6cb836c, e159ff0c.
  • $unit::x in a generate block that declares x reads the unit's x: 3ca0d09, 181e269b, 629050a8.
  • Implicit nets used in a generate block belong to that block, one per iteration, and g[0].n reaches them: df52996, 0f0c6ef, 38a8498c.
  • The binder's hidden-name search is indexed by name. It was a linear scan per name, quadratic in one scope's locals: 2a80522, a1e7912.

Two cases are left: a disable path naming a nested block (still matched by label), and classes declared in generate loops.

Published in xezim 603c9bf3 with xezim-core 212dde0.
Validation: the full xezim suite passes (6610 tests, 0 failed, 1 ignored), with xezim-core 514 and parser 233 tests, the JIT smoke and DPI fallback tests, and a 1.92 MSRV check.

@aionhw aionhw closed this Nov 9, 2026
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.

2 participants