Skip to content

Resolve a generate block's time scope and clocking blocks through its instance - #969

Closed
philipaxer wants to merge 2 commits into
aionhw:mainfrom
philipaxer:up/fix-frame-scope
Closed

philipaxer wants to merge 2 commits into
aionhw:mainfrom
philipaxer:up/fix-frame-scope

Conversation

@philipaxer

@philipaxer philipaxer commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

xezim only, based on main; the core pin is unchanged. Found while working on #952.

A block subroutine has one table entry u.g.t, so its frame scope is u.g. Processes in a block carry the same scope. current_module_def compared that to instance paths exactly and fell back to the top module's unit. The clocking lookup only tried u.g.cb.

scopes_to_instance walks a scope outward to the nearest enclosing instance.

  • current_module_def takes the definition from that instance (§3.2, §3.14.2.1).
  • resolve_clocking_key tries each scope on the way, innermost first, and stops at the instance (§14.3, §23.9). A clocking block in the block itself still wins.

The frame scope stays u.g, because name resolution binds block data through it.

Results

Program Before After
block task in an instance, timescale (p24) 0 0.003000, 0 0.006000 3 3.000000, 6 6.000000
block task in an instance, clocking (p25) 0 x, 0 x 1 5, 3 5
process in a block of an instance proc 0 0.003000 proc 3 3.000000

Expected values come from the LRM. Verilator 5.055 agrees, except that %m in a block task repeats the block name there, and it printed nothing for the block task in an interface instance, so that test rests on the LRM alone.

Suite before the rebase onto main: 6315 passed, 0 failed, 1 ignored (6297 / 0 / 1 before; 18 new tests).

After the rebase onto main: 6350 passed, 0 failed, 1 ignored (main: 6332, 0, 1; the 18 new tests).

Not covered

  • A top-level clocking block hides an instance's block of the same name: m 4 9 where m 1 5, m 3 5 is right (§23.9). resolve_clocking_key tries the raw name before the scopes.
  • scope_def_module attributes a block process in an instance to the top module. This only affects diagnostics text.
  • Reading a clockvar of a clocking block in an outer block from a nested block: @(cb) works, cb.e reads x, also at the top level.
  • g.cb.e by path is rejected at elaboration.

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.

… instance (§3.2, §3.14.2.1, §14.3, §23.9)

A scope name such as `u.g` or `u.g[1].h` is walked outward to the nearest
enclosing instance by `scopes_to_instance`. The current module definition, and
so the time unit and precision, comes from that instance. This covers block
subroutines called bare or by path, and processes in a block. A bare clocking
name is tried in each scope on the way, innermost first, and not past the
instance. The frame scope itself stays `u.g`, which name resolution uses.
….1, §14.3, §23.9)

Tasks, functions and processes in generate blocks of instances, bare and path
calls, generate-for, nested blocks, instance arrays, interfaces, and a clocking
block in the module and in the block. Forms in a top-level block already worked
and are kept as the contrast.
This was referenced Nov 8, 2026
@aionhw

aionhw commented Nov 9, 2026

Copy link
Copy Markdown
Owner

Integrated by cherry-pick with @philipaxer's authorship preserved: 194f1a60 and the tests 5daa153c, on top of the generate-scope series.

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