Repository navigation
Resolve bare upward subroutine calls through the instance tree (§23.8.1, §23.11, §26.3) - #175
philipaxer wants to merge 10 commits into
Conversation
…it (§23.9, §27.5, §27.6) Each module, interface, program, nested module and checker gets a ScopeTree before elaboration: the definition body and its generate blocks, all arms included, with the names each scope declares and its imports. A library module gets its tree when it is adopted. Building the tree writes each construct's implicit name into the AST. Elaboration and instance inlining read that name and hold no counter. §27.5: the parser records a branch with no `begin` whose one item is a conditional generate. Such a branch opens no scope: no rename, no scope stamp, and its inner construct takes the outer construct's number and implicit name. Its labels are declared in the enclosing scope. §27.6: an implicit name gets leading zeros until no explicit name of its scope conflicts: `genblk02` beside `parameter genblk2`, or beside a block labelled `genblk2`, even in an arm that is not selected.
…3.9, §27.5, §27.6)
…rename (§23.9, §26.3, §23.8.1) New elaborate/bind.rs walks every position of a definition after its scope tree is built and writes `bound` on the head of each hierarchical identifier: a declaration on the tree's scope chain (§23.9), Upward (§23.8.1), or no target. No target means a subroutine, procedural block or class hides the name (§13.5, §6.5, §9.3.4, §8), it resolves through an import or $unit (§26.3, §3.12.1), or it is rooted or scoped. A class with an unknown base, or a randomize-with on an object, leaves without a target any name some class declares (§8.25, §18.7). `Bound` is 4 bytes and tells a position the binder did not walk from these; it is not serialized and nothing reads it but the audit. XEZIM_BIND_AUDIT prints each name with its binding and what rename_decls_in_iter, run over a copy of the definition, made of it. The tree keys a nested definition by its parent path (§23.4), takes enum labels inside structs and on nets, assertion labels, and named blocks under an unnamed block with no declaration (§9.3.4), and separates type parameters and gate instances from the kinds the rename touches. Adopted library definitions are bound and their trees kept.
…nvisited positions (§23.9, §26.3, §8, §13.5, §18.7) tests/bind.rs asserts each name's binding, the rename's outcome and the kind of any difference. The nested definitions in tests/scope_tree.rs are looked up by parent path.
…rate rename (§23.9, §26.3, §18.7, §3.12.1) The binder writes each subroutine declaration's own id on a new `decl: Bound` field, not serialized. `rename_decls_in_iter` builds a gate from the declarations it renames, and `rewrite_expr_impl` consults it before taking a name from the port map: a name the rename maps to a subroutine of the block is rewritten when its binding is that subroutine, and keeps its spelling when it binds to a formal, local, member, import, label, `$unit` or a deeper declaration. The name-based rule remains for data names under either rename, an unbound definition and an unanalysed reference. The parser marks the name of a `$unit::f(...)` call as having no target, and the binder keeps that outcome, so the call reaches the unit function from a block that declares `f`. `randomize() with` hides the members of the receiver's class when the receiver is one name declared with a class's own name: a local, formal, loop variable, block-level data or port. Any other object receiver hides every name some class declares. A base class named through a package is looked up by its last segment. The bind audit prints `target=<kind>`: what the tree declares under the name, hiding aside.
…et kind and the randomize receiver (§23.9, §26.3, §6.19, §6.21, §14.3, §18.7, §18.12, §3.12.1) Eight tests in `tests/bind.rs` now expect a hidden subroutine name to keep its spelling and to show no disagreement. Their binding assertions are unchanged. New tests cover the target kind, the declaration's id, nested data named like a block's subroutine, `$unit::f()` and the receiver forms of `randomize() with`.
…§23.9, §9.2.3, §11.13, §14.3, §16.12, §19, §28, §8) rename_item_decls rewrites final blocks, gates, properties, sequences, lets, covergroups, clocking blocks, class bodies and out-of-class constraints through the block's rename, limited to the block's subroutine names. The gate in rewrite_expr_impl decides each reference by its binding, so a class member, a formal or a local keeps its spelling. Data at these positions keeps its bare name. A class body gets the definition-relative key; the runtime adds the instance from this. The bind audit reports a subroutine the rename misses at these positions as other, and data there as group B.
…e, let, covergroup, clocking and class positions (§23.9, §8.3, §13.5, §16.8) Nine audit tests recorded these positions as missed. They now expect the renamed key and no disagreement. Their binding assertions are unchanged.
….1, §23.11, §26.3) The binder records on each module instantiation the scope of its definition's tree that holds it, and generate-for expansion records the loop indices on each instance. The inliner keeps, per instance, the parent's tree, that scope and those indices. A bare name the binder leaves to the hierarchy is looked up from the instantiation's scope, then from each enclosing instance's. When the first declaration found is a function or task, the name is rewritten to its key: `g.f`, `u.f`, `u.g[1].f`. Data found first, or nothing found, leaves the name as it was. Imports of enclosing scopes are not searched. A bound instance searches its target's definition scope. The bind audit prints each resolved call with its instance and key.
…6.3, §27.4) A child in a block, two parameterized parents, nested generate loops, a grandchild and a bound instance get the key of the subroutine found upward. The compilation unit, the child's import and a parent's data stop the search; a parent's import is not searched.
|
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:
Two cases are left: a Published in xezim 603c9bf3 with xezim-core 212dde0. |
Part 5 of the series for aionhw/xezim#952, which replaces name-string scoping in generate blocks with a scope tree and a binder.
Depends on #174: until that merges, this branch also shows its commits. New here:
69dc4e2Resolve bare upward subroutine calls through the instance tree (§23.8.1, §23.11, §26.3)1fa0f94Test the keys bare upward calls are rewritten to (§23.8.1, §23.11, §26.3, §27.4)Changes:
g.f,u.f,u.g[1].f. If data is found first, or nothing is, the name stays as it was.No xezim source changes. The runtime already finds the keys.
Programs
Verilator 5.055 rejects upward calls, so the expected values come from §23.8.1.
f(the issue program)ffft;t();, function body, continuous assignp03)$unitfunction and child's own import before the blockf, top declaresf$root.top.g.f()andg.f()from the childBefore this PR, a child reached a parent instance's function only through one bare-name table entry. With one parent instance that gave the right value. With two, both children got the same one, and a top-level function of the same name won over the parent's.
Regressions
None found. Of the 177 corpus programs whose output changed, 105 state an expected value: all 105 reach it now, none did before. The other 72 have no expected value; I read them by group, and each now gives the §23.8.1 value.
Audit
The audit binds each definition on its own, so its Upward lines stay as they are. Counted per instance, the resolved calls are the ones the inliner now rewrites. The ones not renamed resolve to a top-level function and keep the bare name. All other audit counts are unchanged: repo and svt 0/0/0, probes A 0 B 10, the corpus A 154 B 778 other 11.
Artifacts against the previous PR: repo 821 the same and 1 different, svt 798 the same. 50 and 230 programs give no artifact on either side. The one difference is
p23h.sv, where a child calls a function of its parent instance. That call is now spelledw.inst_x.helper, and the output is the same.Corpus programs that changed
189 programs have a resolved upward call. All ran on both binaries, and 177 changed output. Verilator rejects 175 of those 177 at the upward call. The other 2 read a static local of a block task upward, and they match Verilator now but did not before.
Memory
stress_explicit.sv, median of three compiles against the previous PR: peak RSS +2.0% (+1.5% on an earlier build of the same source), artifact size the same.Not covered
disable g.tby path (§9.6.2): the next PR.fin the parent module,int fin its block and the child in that block, the call should reach the parent'sf(3); it prints 100, the same as before.def@offset, because the source maps are released before those bodies are rewritten.Not checked: UVM beyond the suite. Programs outside the changed set were compiled for the audit, not simulated.
Tests after the rebase onto main
Rebased onto core
5b0b02eand xezim2e4e7f99. The results above were measured before the rebase, against the previous PR of the series.cargo test: 471 passed, 0 failed.cargo test --release --test hierarchy(built without LTO): 840 passed, 0 failed.The series
This PR is one part of a stacked series for aionhw/xezim#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 aionhw/xezim#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, anddisable g.tmatched by leaf.Approach.
scope_tree.rs).elaborate/bind.rs).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=1prints 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):
genblk<n>, §27.6 zero padding, §27.5 direct nesting) come from itdisableby path and by label is bound like other names (§9.6.2)genblk<n>Independent of the series: aionhw/xezim#969 (time scope and clocking of a block in an instance) and aionhw/xezim#970 (a subroutine with no written lifetime stays static on the compiled tier).
Still to come.
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
hierarchyand 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.