Skip to content

Fixes and features to make VeeR-EH1 work - #495

Merged
soronpo merged 57 commits into
mainfrom
training
Aug 17, 2026
Merged

Fixes and features to make VeeR-EH1 work#495
soronpo merged 57 commits into
mainfrom
training

Conversation

@soronpo

@soronpo soronpo commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

Oron Port and others added 30 commits August 12, 2026 21:43
…edge branch of its own

`VerilogProcToVHDL` Rule 2 only recognized an if-reset-else-clock process whose
`else` was a single guard-less branch, so it could hand that branch the clock
edge in place. An `else` holding nothing but a conditional is not that shape: it
reads as `if (rst) ... else if (c) ...`, leaving no branch to carry the edge. The
rule fell through, the process kept its edge-style sensitivity list, and the
backend printed `process (rising_edge(clk), rising_edge(rst))`, which is not
legal VHDL. Nothing reported it, and the Verilog backend was unaffected, so it
only surfaced downstream.

That shape is the ordinary way to write an FSM under an async reset, so the rule
now covers the whole chain the reset heads. A guard-less `else` still takes the
edge in place; otherwise a fresh `else if (clk.<edge>)` branch is chained after
the reset branch and the rest of the chain is nested inside it under a header of
its own. Restricted to a conditional statement, since restructuring the chain of
a conditional expression would detach its branches from the header whose value
they produce.

Re-homing the tail needs both its ownership and its chain link redirected, which
two reference patches on one member cannot express. The chain link is redirected
by replacing what it points at instead, scoped with a `RefFilter` to the chain
head alone, keeping it to a single patch list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ot a second patch phase

Banks what the `VerilogProcToVHDL` fix turned up, all of it general enough to
catch the next stage author rather than specific to that stage.

The `/new-stage` skill gains a recipe for the collision that makes a second
`db.patch()` phase look inevitable: a member needing two of its references
redirected at once. `Patch.ChangeRef` reaches the member-list patch table like
any other patch, so two on one member throw, and `Replace + ChangeRef` does not
merge either. The way out is to redirect one of them by replacing what the
reference points at, keyed on the old target with `ChangeRefOnly` and a
`RefFilter` narrowing it to the holder. `ChangeRefOnly` is dropped from the patch
table outright, so it cannot collide even with an Add already keyed on that
target.

Two mistakes join the list. A `Patch.Replace` cannot carry a new `ownerRef`,
since `replaceMember` keeps only `repMember.getRefs` and `getRefs` excludes
ownership: the minted reference is purged and the next `getOwner` dies with an
unrecognizable `key not found`, usually inside a later stage. And matching a
conditional chain by arity skips the most common spelling, because an `else`
holding nothing but a conditional flattens into an `else if` chain.

`RefFilter.OfMembers` was documented as matching references *to* the given
members, which is backwards: `originMember` is the member holding the reference,
as its `Outside`/`Inside` siblings already say.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hat branch away

A `max` is at least each of its own branches and a `min` at most each of its, whatever those
branches are, so a comparison between such a chain and one of its own branches is either an
answer outright or a comparison with what is left of the chain. That shape is not exotic: it is
what a width taken as the COMMON width of two operands meets when it comes back to one of them,
which every binary operation over unrelated parametric widths does.

The identity lands in two places, because it is asked at two levels. As an expression rewrite,
so a design that has to hold `x(W1) + y(W2)` in `W1` bits requires `W1 >= W2` and says so,
rather than restating the common width it went through. And as a fallback in the width-fit
decision, so `max(W1, W2) >= W1` is proven rather than left undecided. The decision keeps its
existing answers first: the max/min elimination reads a mixed chain by its constants and is
deliberately lenient, so the identity only ever turns an undecided answer into a decided one.

Proving it is what lets a stacked resize through a common width fold away, once the fold asks
whether the inner resize loses anything rather than whether it strictly widens: a value resized
to a width that is at least its own and back again recovers itself, so only the operand whose
width really changes carries a resize.

A condition can now fold to a constant where the width proof could not decide it, so a
constraint whose condition folded to `true` requires nothing and is no longer recorded. One that
folded to `false` is kept: an assumption that cannot hold is worth the noise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ard's reach

Fixes #482

`dfhdl.top` was a top-level member of the `dfhdl` package, so every `import dfhdl.*`
bound the name `top`. A user's own design class named `top` (the standard Verilog
top-module name) then lost name resolution to it from any OTHER compilation unit: a
package member referenced across files is the lowest-precedence binding, below a
wildcard import. `new top(WIDTH = 8)` was checked against the annotation's constructor
and reported "dfhdl.top does not have a parameter WIDTH", naming neither the collision
nor what the identifier had resolved to. #465 had fixed only the declaration-site half
(#458), by qualifying the plugin's injected annotation.

The annotation now lives at `dfhdl.hw.annotation.top`, beside the other user-facing
hardware annotations, and reaches user code only through an explicit import or the
`@hw.annotation.top` spelling. Nothing named `top` enters scope through `import dfhdl.*`.

`dfhdl.hw.annotation` becomes a PACKAGE rather than an object to host it: `top` needs
five option sets that live downstream in `lib` and cannot be compiled into core's
object, while a package is open across subprojects. `constraints` stays in the same
file, since `HWAnnotation` is sealed.

The plugin's auto-injection is spelled `_root_.dfhdl.hw.annotation.top`, so an
auto-topped design still needs no import; only a hand-written `@top` does.
`rightmostName`-based detection keeps matching every spelling.

`ElaborationChecksSpec` takes its import on line 1 rather than a new line, because 46
of its assertions pin absolute line numbers.

A test-scope `type top = dfhdl.hw.annotation.top` alias would have spared the test
files their import, and was rejected: it reproduces this very bug inside lib's test
scope, where the regression fixture lives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Points at the one submodule commit that adds `import dfhdl.hw.annotation.top` to the
three `serv` entry points, so the benchmarks project still compiles after #482.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
What DFHDL does when a width relation is neither provably held nor provably violated, which is the
normal state of affairs once widths are design parameters: it accepts the operation and states the
relation it assumed as a static assertion in the generated design.

Covers the three-way answer and what makes an undecidable relation load-bearing enough to state,
the static assertion as a structurally derived species and what `ToED`, `OrderMembers` and the
elaboration check do with it, the guard as its own record along with retraction and
materialization, the minimization that reads the user's own assertions as facts, the checks that
generate and the ones that deliberately do not, and the per-dialect printing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s, and never decides anything

The width algebra substituted a non-top design parameter with its applied OR DEFAULT value. While
a design elaborates its own body there is no instance to take an applied value from, so it took
the default, and every width decision inside a parametric sub-design was made on a value the
design may well not have. The elaboration root escaped only by being guarded out of the
substitution entirely, which is why the same class elaborated cleanly standalone and failed the
moment it was nested.

It went wrong in both directions. An operation the applied value made perfectly legal was
rejected, reporting the width symbolically while having decided it numerically, which is the shape
of the report: nothing can prove `9 > W` for an opaque `W`. And an operation the applied value
made illegal was accepted, stating no contract at all, so a module whose parameter is overridable
truncated silently at every value its default did not cover.

A parameter is now substituted only where an instantiation actually supplies a value. A design's
own body never does, so its parameters stay the free variables they are and it states the same
contract whether it is elaborated standalone or as a child. A decision made in the PARENT still
resolves, and should: there the applied value is what the operation is about.

Fixes #479

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`dout <> din` between mismatched widths reported ``Operation: `apply` ``, twice, once per
direction. The name an error carries is the `CTName` of whichever `trydf` traps it, and `CTName`
resolves to the enclosing method's name unless the site passes one. `<>` did pass one. It was
never the trap that fired: `DFVal.TC.apply` wrapped `conv` in a `trydf` of its own, so the width
mismatch was caught one level below the operator, stamped with that method's name, and logged as
an error the `<>` trap could no longer rename. What reached the operator was a `DFError.Derived`,
which the report filters out. The mirrored pair came from the same swallowing: the connect
retries the flipped direction when the first throws, and both directions logged their own error.

A conversion is not an operation. It is the receiving half of the `<>`, `:=`, `init` or `sel` the
user wrote, and that operation's trap is the one that names it, so `TC.apply` no longer traps at
all. Every path reaching it already sits under an operator's `trydf`, which the `:=` route, going
through `Exact1.apply` straight to `conv`, had been demonstrating all along by reporting `:=`
correctly. `Compare.apply` and its `DFXInt` override had the same shape and lose their traps for
the same reason; dropping the override's also restores the flipped-direction retry a swallowed
exception was suppressing. The same reasoning applies to the shared `DFDecimal` builder every
integer and fixed-point constructor delegates to, which was where their width checks were being
named; `DFSInt.apply` had been relying on it and now traps for itself.

The rest is naming. Seven operator givens took the enclosing `apply` while holding the `ValueOf`
of the very operator they implement, so `^`, `&`, `|`, `++`, `>>`, `<<` and `**` say so now. A
type constructor names its type: `Bits constructor`, `UInt.to constructor`, `SFix constructor`.
Domain blocks join ports and variables in naming what is being constructed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…, and the design says so

Reading a parameter as a Scala value specializes the body to it: the `if`
branch not taken and the `for` iteration that never ran leave nothing behind,
so the design that comes out is the one for that value and no other. The
generated module went on exposing the parameter as overridable all the same,
and the width algebra went on treating it as a free variable, which is why a
relation the branch itself guarantees could not be seen.

The record is the purity marking, not anything kept where the reading happens.
`PureCheckPhase` already names a read parameter on the design's own
`@pure(true, ...)`, and re-attributes every application of a marked parameter
at the call site, so a parameter read deep in a child marks the parameter of
every design that feeds it. A parent that reads nothing itself is specialized
just as surely, and states its own value.

So a data-impure parameter folds to its value in `IntExprCalc` under
`AppliedExpr` (the one case where a parameter with no instantiation site folds,
the elaboration root included) and the design states `param == value` at
materialization, where it joins the same minimization as every other
constraint. Nothing is stated where an overriding instantiation cannot exist:
a method design, a blackbox, or a parametrically-typed parameter, whose value
has no literal of its own type to be compared against.

Additive cancellation now walks both sides of the tree rather than the left
spine, so `x + (y - x)` states `y`: a relative width adjustment written as the
distance to another width is that width said the long way round.

Fixes #480

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… declarations

Advances the submodule to 7b658bf, which adds benchmarks/veer_eh1/ (provenance,
upstream Apache-2.0 license, package clock/reset defaults, and the two constant
headers) and gives the benchmarks repo its own .scalafmt.conf aligning `<>`,
`=`, `=>` and `:=`.

The constant headers take different Scala forms because the two Verilog headers
they come from do not scope the same way: common_defines.vh is a global include,
so its macros are top-level package definitions, while global.h is included
inside 20 module bodies and is therefore an object that each design `export`s,
which is what puts the names on the type as the baseline's localparams are.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
None of the three submodules recorded a branch, so a clone or `submodule update`
checks each one out detached. That is normal git behaviour and harmless until
you commit inside one: `git push origin main` from a detached HEAD resolves
`main` to the stale local branch ref, pushes nothing, and reports success. It
cost two silent no-op pushes in the benchmarks repo this week, and the parent
pointer referenced commits the remote did not have.

Recording `branch = main` lets `git submodule update --remote` track main and
makes the intended branch explicit at the point someone reads .gitmodules.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Advances the submodule to 8efac57, porting veer_types.sv.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`&`, `|` and `^` each name two operations: the binary bitwise/logical one and
the unary reduction. `MergeAssocFunc` keyed its associative merge on the `op`
symbol alone, so it happily absorbed one form into the other and the reduction
was simply lost: `a.^ ^ b.^` elaborated to `a ^ b.^`, and `(a ^ b).^` to
`a ^ b`. Both backends then faithfully printed the corrupted IR, SystemVerilog
silently (an 8-bit expression truncated into a 1-bit net) and VHDL as a type
error.

Only the multi-operand form of an associative op is associative at all, so the
merge now requires both funcs to be in it. A chain of one form alone still
merges, which is the whole of what the simplification was for.

Fixes #483

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ations

Banks the #483 species: an `op`-keyed predicate reasons about symbols, `&`/`|`/`^`
name both a binary operation and a unary reduction, and arity is the only thing
that tells them apart. Also records why the report arrives as a printer bug and
why both nestings need probing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…at pins a parameter

The skill covered the mechanics of translation but not the choices *between*
equally-legal spellings, which is where the emitted HDL either tracks the gold or
drifts from it. From the first two VeeR-EH1 helpers:

A Verilog `assign` is a connection, so `<>` rather than `:=`. A named value beats
a variable -- declare a VAR only where the baseline drives the bits separately,
which is exactly when one named value cannot express it. Bit logic uses `&`, `|`,
`~` for x-value equivalence, and the emitter picks `&` or `&&` from the operand
types by itself. Concatenation is a tuple. `reduce`/`foldLeft` over DFHDL values
need an explicit `[T <> VAL]`, and a seeded `foldLeft` emits the flat chain a
`reduce` breaks into a paren group.

Also a new subsection on parameters: what the elaboration *reads*, it pins. That
is correct behaviour, but it turns one generic module into a specialised copy per
instantiation, which is rarely what a port wants. `clog2` takes an `Int <> CONST`
directly, slice bounds take one too, and a `generate`-style choice becomes a
`.sel` on a constant condition rather than a Scala `if` -- so the design keeps
both arms and stays generic.

Plus the packed-struct section: field order is the baseline's, `: Int <> CONST`
is what keeps a constant's name in the output, and an include's *scope* decides
whether it maps to package-level definitions or an `export`ed object.

Advances benchmarks to 9c52f7a.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Advances the submodule to 2e599af.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Advances the submodule to 27b42c5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`cloneAnonValueAndDepsHere` reused the original's `DFType` instance, so the
clone shared the original's `TypeRef` object. Type references are
reference-counted before a patch purges them, but the count comes from the
pre-patch member list and cannot see a member the same batch adds: a stage
that clones a value and removes the original in one patch (`NameRegAliases`
with a reg init) drops the count to zero and leaves the clone holding a
dangling reference. Only parametric widths carry a type reference at all,
which is why literal-width designs were unaffected.

The clone now mints its own type references through the new
`copyWithNewRefsHere`, so the added meta-design DB no longer depends on the
original member surviving.

Fixes DFHDL#485.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…al proof

`.reg(n, init = ...)` says a delay chain more directly than declaring and
chaining registers, but it names its flops after the source signal, so they stop
matching the baseline's net names. equiv_make pairs by identical wire name, so
the internal anchors are lost -- measured on rvsyncss, where the paired-cell
count drops from 12 to 6. Free on two flops, since the proof closes on the
outputs alone; not free on a module big enough that induction needs the anchors.

Advances benchmarks to eac5952.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ecide, not an unknown

The state analysis re-seeded a slice from `idxLowRef.getIntOpt`, so a bound
that depends on a design parameter collapsed to `Slice.Unknown`, and the
collapse was wrong in both directions at once.

Reading, it was conservative to the point of being false: `Slice.Unknown`
never proves containment, so `v(31, MB)` read out of a `v` assigned in full
answered `Tri.Unknown` and `v` was reported as a latch under RT (issue #484).
Writing, the same collapse over-claimed. The seeded slice of a
parametrically-sized selection is `Slice.Full` (its `widthIntOpt` is `None`),
and shifting `Full` leaves it `Full`, so `v(MB - 1, 0) := x` banked the WHOLE
of `v` as written and a genuinely partial assignment passed the check.

Both halves are the same missing composition. `departial`'s per-step slice
calculus moves to `DFVal.Alias.Partial.composeSlice`, which maps a slice into
the selected value's coordinates as a linear form, and the state analysis uses
it for `ApplyRange` and `SelectField` in the read and the write direction
(`ApplyIdx` keeps its whole-value approximation). `departial` itself is
unchanged in behaviour, now expressed through the shared helper.

`Coverage` then keeps the symbolic regions instead of degrading them to
"touched", and decides a containment query over them: a slice lies within its
own value's bounds, so a coverage spanning the whole value contains every slice
of it whatever the endpoints are, and anything genuinely partial goes to a
sweep that extends the covered prefix by a region provably starting at or
before the cursor and ending after it. Two complementary parametric writes
therefore cover the variable between them for every parameter assignment, while
one that covers only part of it still reports the latch.

The suite was green both before and after, which says the whole branch was
untested rather than that the change is inert, so the tests pin the accepting
shape and the rejecting one on each side: the elaboration check for RT and the
`ExplicitState` output for DF.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…and a name or a function call in VHDL

The `ApplyRange` criteria of `NamedVerilogSelection` named the selected value
only when the selection was partial, but prefix legality has nothing to do
with the selection width: the printer emits `[hi:lo]` either way, so a
full-width `.bits(19, 0)` over an operation result printed
`(a + 20'd1)[19:0]`, a part-select on a parenthesized expression that no
strict frontend reads. The same guard also let a select over a select
(`a[15:0][15:0]`) and over a concatenation (`{...}[19:0]`) through. The width
guard is gone; a selected value without a Verilog name is named
unconditionally, while `hasVerilogName` keeps named prefixes exempt and
`isAllowedMultipleReferences` keeps the legal `vec[i][hi:lo]` / `s.f[hi:lo]`
chains inline.

The VHDL backend had the same genus a level wider, because
`NamedVHDLSelection` only ever ran for v93 pattern matching. A VHDL slice or
index prefix must be a name or a function call, so a selection over an
anonymous expression (`(unsigned(a) + 1)(19 downto 0)`, `(a or b)(3)`) and
over the TYPE conversions `unsigned(...)`/`signed(...)`, even of a named
value (`a.uint(15, 0)`), printed prefixes both GHDL and NVC reject. The stage
now runs for every VHDL dialect (the v93 match-selector rule is gated inside
the criteria), and a `hasVHDLName` predicate mirrors
`VHDLValPrinter.csDFValAliasAsIs`, form by form: conversions that print as
function calls are legal prefixes and stay inline (`to_slv(...)(19 downto 0)`
is untouched), the three type-conversion renderings are not, and a selection
chain is a name whenever its own root is, which the naming fixpoint repairs
independently.

Both backends also shared a consumer-side hole: the criteria scan visits
anonymous members only, so a NAMED selection (`val s = (a | b)(7, 0)`,
`val s = u.signed(20, 1)`) never surfaced its operand demand, and the very
shape the stage handles inline slipped through the moment the user bound it
to a val. The criteria entry point now derives the demand from the prefix
value's side, by re-asking the reading selection's own criteria; duplicate
demands merge in grouping, and named results fall to the existing filter.

Every previously-illegal probe output now analyzes clean under iverilog,
yosys, GHDL, and NVC, and the already-legal controls print byte-identically.
The suite was green with no reference output changed before the fix, which
says the whole branch was untested; the new `NamedSelectionSpec` tests pin
each shape and each was verified to fail with its guard reverted.

Fixes #486

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
IR: DFBits renamed to DFBitsWL with an added lowIdxRef; object DFBits remains
as the zero-based apply/unapply view. Frontend: DFBits[W] = DFBitsWL[W, 0],
user-facing BitsHL(hi, lo) constructor, width-only compatibility across low
indices via the generalized Candidate/TC/Compare/ops machinery, and absolute
index selection (with the new BitIndexLow/BitIndexHigh checks) on low-indexed
values. Selection results always normalize to low 0; a nonzero low arises only
from explicit BitsHL construction. Backends render [hi:lo] / (hi downto lo).

Still open: DFacsimile nonzero-low data offsets, BitsHL selection tests and
backend print-spec cases, testApps validation, struct/vector BitsHL cells.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DFacsimile translates absolute selection indices to relative data offsets
(dynamic indexing of a low-indexed vector is refused), verified by a both-tiers
simulation test. Match expressions rebase a nonzero-low selector to zero-based
bits once at construction, so relative patterns and bind ranges hold across all
backends; an equal-width low-drop cast prints as .bits and a cast into a
nonzero-low type as .as(...). The remaining strict zero-based printer/stage
arms are generalized, a DFBitsWL Singleton given supports type-only spellings
(struct fields), superseding the removed DFBits given, and the natural-check
message now says "natural". New pinned coverage: absolute selection and its
diagnostics, backend [9:2] / (9 downto 2) rendering, a VHDL composite (record
field, vector cells), and v2001 struct flattening offsets. User-guide BitsHL
section (prefer Bits(width); no reversed direction) and IR reference updated.

Known follow-ups (pre-existing, tracked separately): v2001 struct-field
bit-select flattens to an illegal chained part-select; a literal range-select
check over struct-field BitsHL values may fail to reduce at compile time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The generalized selection givens move to an OpsLP low-priority trait (the
CandidateLP/WidthLP idiom), dropping the NotGiven dispatch. Range selection
now has two statically-checked forms: the W-form (HighIdx[W, L] bound) covers
term-constructed receivers whose width is a reduced literal, and a new H-form
(DFBitsHL[H, L] receiver) covers annotation-path receivers (e.g. a
BitsHL[9, 2] <> VAL struct field) by binding H structurally from the
unreduced RangeWidth application, checking bounds on H directly with nothing
to collapse. Given-candidate backtracking on a failed using-clause is what
lets the W-form's stuck check fall through to the H-form, so struct-field
range selection compiles again (pinned in the VHDL composite test) while
out-of-range literals still fail at compile time on both paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… into the chain

DropStructsVecs stage 2 folded a partial chain into one range selection over
the flattened declaration, but only for links present in the replacement map.
A selection INTO a Bits-typed link (a bits field select or a vector bits-cell
select, never themselves replaced) dangled and selected into the folded range,
emitting an illegal chained select under v95/v2001 (`p[8:1][5]`), silently.

The chain extractor now matches transitively through anonymous links (a named
link still legally breaks the chain as its own net), the walk translates a
Bits link's absolute indices by the link's low index (the BitsHL correction),
and a single-bit result folds to a bit selection rather than a one-bit
part-select, keeping runtime indices legal where part-select bounds must be
constant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… elaboration would

The printer synthesizes the bound expressions `width - 1` and `width + low - 1`,
which are never built as values, so elaboration's SimplifyFunc additive
cancellation can never reach them: a `BitsHL(idxHigh, idxLow)` declaration
(width stored as the cone `(idxHigh - idxLow) + 1`) emitted its high bound as
the unreduced `((HI - LO) + 1) + LO - 1` instead of the `HI` the user wrote.

The bound helpers now collect signed additive terms across anonymous DFInt32
`+`/`-` cones and cancel ident-transparent `=~`-equal opposite-sign pairs, the
SimplifyFunc term calculus applied at print. Terms keep their references, so a
surviving term renders with the plain spelling's relative naming; an anonymous
constant folds into the offset while a named constant stays a symbolic term
(IntExprCalc's linear form is unusable here: it folds named constants to their
data, erasing the user's spelling). Non-cancelling cones print byte-identically
to before; carry-widened widths improve from `(W + 1) - 1` to `W`. This also
retires the associative-reduction TODO in `uboundCS`.

Closes #489

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two lessons from the VeeR-EH1 beh_lib helpers, neither of which is a DFHDL fact.

The transcription rule was too narrow. It said a conversion the compiler did not
ask for is a smell, which caught half the cases; the actual cause was predicting
that DFHDL would reject the obvious spelling and pre-emptively working around an
error never raised. "I think it will not typecheck" is not a reason until the
compiler says so, and the diagnostics name the fix when it does. Nine real
corrections are tabulated, with the point that none was caught by compiling, by
reading the emitted HDL, or by formal equivalence. Worst of the three smells is
an operator the baseline did not use: `==` against XNOR on one bit is equivalent,
so it passes every check and is visible only to a reader holding both files.

And a verification-discipline note, because seven distinct green signals in one
port meant nothing -- a sed that did not match, a sed -i that rewrote the gold
filename, a semantically null mutation, a grep matching the failure line as well
as the success line, head masking an exit code, probe classes sharing a file, and
an ANSI prefix defeating a `^\[error\]` anchor. Assert the mutation applied and
the control fails before believing a pass.

Also records BitsHL's declaration-vs-expression rules, the carry operators,
macro-modules as `@inline def`s, when a VAR is warranted, and that `.toScalaInt`
neither causes nor cures parameter pinning.

Advances benchmarks to f37d70e.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
soronpo and others added 26 commits August 14, 2026 23:32
…, L] spelling

A `BitsHL[H, L]` spelling with non-literal bounds collapses its width to `Int`
at the spelling site (the guarded fold disproves const-ness of a constant
singleton), erasing `H` from the type. Two consequences, each fixed at the one
place the information still exists:

- The type-only spelling in term position (`BitsHL[HI.type, LO.type] <> IN`)
  had no constructor at all and resolved to an unusable widened value. A no-arg
  `DFBitsHL.apply` now takes the bounds from the EXPLICIT type arguments via
  `ValueOf`, the apply site being the one place the high bound survives.

- A constructor-form value (`BitsHL(HI, LO) <> IN`) could not convert to the
  collapsed parameter type `DFBitsWL[Int, LO.type]`: the candidate conversion
  targeted low-0 `DFBits[Int]` only, and the `fromTC` fallback needs a target
  type instance no given can produce once `H` is erased. The conversion target
  is generalized to `DFBitsWL[Int, L]`, the width staying pinned at `Int` so
  literal-width targets keep the width-checked TC route. The relabel is
  type-level only; the IR carries the true bounds and drives all checks.

Fixes #490

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ummoning

The collapse of non-literal width types to `Int` (987e0b0) left no type-position
spelling from which a constant-bound width operation could be summoned back: a
`Struct` field typed `BitsHL[H, L] <> VAL` with constant `Int <> CONST` bounds
erased `H` at the spelling site, so the field's `DFType` had no given to serve it.

The old Sig-based operations return, now under `IntP.Sig.Ops` (exported by the
frontend), keeping TYPE-position arithmetic (`Bits[P1.type - P2.type]`) symbolic
as `Sig1`/`Sig2` nodes instead of collapsing. The `Sig` given instances, until now
placeholders, reconstruct the operation's `DFConstInt32` from the spelled operand
types via `ValueOf`, reusing the value-level `IntParam` operators so the recovered
constant matches the constructor-form width tree exactly.

`DFBitsHL` is redefined over `Sig.Ops.RangeWidth`, and the `DFBitsWL` type-only
given drops its `Singleton` width bound so a Sig-carried width resolves through
the same path. Value-level operations stay on the collapsing operators (issue
#431 remains fixed); only the type-position spellings are precise.

Fixes #491

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… retires

#491 is fixed, so a constant-bound BitsHL is usable as a Struct field. The note
warning it was not now says what it does instead, with the emitted form.

Advances benchmarks to b66331f, which converts the last three non-zero-base
fields in the VeeR-EH1 type package.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A related domain may now declare its own input clock port: a clock fully
synchronous with the clock of its related target (typically a gated version
of it), while the reset is still shared through the relation. Identity is
the dcl's design-relative name (domain `active` with dcl `clk` identifies
as `active_clk`, independent of `@flatten`), so same-named derived clocks
of the same origin refer to one clock across the hierarchy.

- core: the related-domain check now permits `Clk <> IN` only, with
  dedicated errors for output/var clocks and any reset dcl.
- AddClkRst: resolves derived-clock identities globally, mint-when-driven:
  if any same-identity dcl is explicitly connected (directly or via a
  parent's `child.active.clk` by-name selection), a distinct
  `Clk_<relName>` opaque is minted and the magnet flow threads the gated
  clock by type; otherwise the dcls retype to their origin's opaque and
  collapse onto the origin clock net (the ungated form). Nested gating
  chains through derived origins.
- ToED: clock resolves to the nearest related-chain member with a clk dcl,
  reset still resolves through the full chain (honoring includeReset); no
  `@timing.clock` is moved onto derived clock ports (no create_clock).
- DropDomains: by-name selection paths of domain-nested ports follow the
  port's flattened name.
- SanityCheck/MagnetMap: ports nested in domain blocks are legal by-name
  selection targets and magnet points (Flattened collection), and
  domain-nested magnet points propagate under their design-relative name.
- DB: device-top clock-location check exempts internally-driven clk dcls
  (generalizes the clk-VAR escape); pbnsToPort opened to the compiler.

Tests: driven/collapse/nested/cross-design-unification and applied-twice
in AddClkRstSpec; async shared reset, related-of-related, collapse, and
includeReset=false in ToEDSpec; a four-level gated-clock threading test in
ConnectMagnetsSpec; pass-through naming in AddMagnetsSpec; by-name
flattened-path rewrite in DropDomainsSpec; error checks in
ElaborationChecksSpec. Docs: "Derived Clocks (Gated Clocks)" in the
design-domains guide; skill notes in verilog-to-dfhdl and new-stage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…thands

Every RT container now provides three shorthand domain classes, each exactly
equivalent to (and manifesting as) a plain RTDomain with the corresponding
annotations:

- RTRelatedDomain: `@timing.related(this)` of the enclosing container,
  injected at construction so it precedes any subclass body member.
- RTDerivedClkDomain: RTRelatedDomain plus a `val clk = Clk <> IN` derived
  clock port (built via direct DFVal.Dcl, since core compiles pluginless).
- RTTransparentDomain: RTRelatedDomain plus `@flattenMode.transparent`, for
  regrouping internal logic into related domains with zero naming impact.

Being container members, the related target is selected by the instantiation
path: `new gated.RTTransparentDomain` relates to `gated` rather than to the
enclosing design.

PrintCodeStringSpec pins the manifestation of all three and the
path-prefixed form; the design-domains guide documents the shorthands and
when to use each.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ference

`@timing.related(Foo)` did not re-elaborate: inside the design's own body the
bare class name resolves to the companion, and a bare `this` would resolve to
the annotated domain when the annotation sits inside a nested domain body.
A design target now prints as `Foo.this` (from the design's `dclName`, since
the annotation is printed within that class's body); domain targets keep
their val-name reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A region is a scoping construct rather than a domain in its own right: it
places logic under a timing context with no observable footprint, neither a
clock identity nor a naming one. The name follows the construct's use site,
where its value lives: `new active.RTRegion: <logic>` opens a region of the
`active` domain. Dropping the `Domain` suffix is deliberate; the two
shorthands that create a grouping with a footprint keep it.

The docs section is reframed accordingly (two domain shorthands plus one
scoping construct) and gains "The Domain-and-Regions Pattern": declare a
timing context once (e.g. `new RTDerivedClkDomain {}`) and open sparse
regions of it wherever pieces of logic naturally live, none of them paying
a naming cost, so code order follows the dataflow rather than the clock
grouping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… yosys equiv blind spot

Lessons from porting `dec_gpr_ctl` (bumped in the benchmarks submodule):

- The domain-and-regions composition for a derived clock. Registers placed
  directly in an `RTDerivedClkDomain` take the domain-name prefix, and
  marking that same domain transparent is worse: it strips the prefix from
  the `clk` dcl too, which then collides with the design's own `clk` and the
  pair emits as `clk_0`/`clk_1`, renaming the design's clock port. The clock
  goes in a named domain and the logic in `RTRegion`s of it, placed where the
  baseline declares those flops. Reach the members with `import`; `export` is
  rejected outright because the region's type is anonymous.
- `BitsHL` covers a non-zero-base bit *range*, not an array: a `Vec` is
  0-based, so a baseline `[31:1]` array is indexed shifted by one, and the
  shift belongs at the `Vec` subscript alone.
- A `Bits` never compares against a Scala `Int`; the compare needs `.uint`.
- A parameter that is a pure function of another belongs in the body, where
  it emits as a `localparam` in the parameter port list. Its derivation must
  be transcribed rather than simplified, since guards like `(N == 1) ? 1 :
  $clog2(N)` exist to avoid a zero width.

Plus a new section on proving a port against its baseline with yosys:

- `read_slang` instead of `read_verilog`, because yosys cannot parse the
  `'{default: ...}` assignment pattern the emitter uses for a vector reset
  (yosys#6120, filed upstream).
- A `Vec` and a Verilog packed array flatten in opposite order, so
  `equiv_make` pairs those aggregates by name and mispairs them bit for bit;
  rename them out of the way and pair the state through canonical-order taps
  added to both wrappers.
- `equiv_simple`/`equiv_induct` ignore the CLK net. Verified rather than
  assumed: tying a derived clock to `1'b0` in both wrappers still reports
  "Equivalence successfully proven" against a gate whose flop was moved to
  the root clock. Moving a flop between clocks is therefore not a valid
  negative control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pre-SystemVerilog Verilog dialects have no unpacked-array assignment, so
a `Vec` reset emitted a PACKED replication into an UNPACKED array
(`mem <= {4{8'h00}}`). iverilog and yosys reject it outright; verilator reads
it as an assignment pattern initializing element 0 only, which would be
silent wrong hardware rather than a syntax error.

The new stage lowers a whole-vector drive into an element-wise one, in three
rules: a declaration's `init` becomes an `initial` block, a connection becomes
per-cell connections, and an assignment is unrolled where it stands. It runs
for verilog.v95/v2001, and for any backend under the new `dropWholeVecAssign`
option (`--drop-whole-vec-assign`), and sits before `DropStructsVecs` in
`BackendPrepStage` so the vector type is still there to unroll. That ordering
is deliberately NOT a dependency: it would drag the whole pre-backend pipeline
into every direct `.dropStructsVecs` invocation.

Only a declaration whose vector type actually reaches the backend is lowered,
which under the old dialects means a block-ram variable; anything else is
flattened to `Bits` and its whole-vector drive is already legal. An anonymous
composition (`all(x)`, a `Vector(a, b, c)` concatenation) is taken apart into
its own operands and needs no constant; any other source is taken apart by
selecting cell by cell, which does. A plain vector-to-vector drive is left
alone. Only the outermost dimension unrolls: below it the cell type is either
flattened to `Bits` or, in SystemVerilog, legal as an array literal.

A uniform source loops over the declaration's OWN element-count parameter, so
a parametric length stays parametric. Rule 2 emits connections rather than the
`process(all)` the issue's shape suggests: the only sources it lowers read
nothing, so that process would carry an empty sensitivity list and never
trigger ("@* found no sensitivities" under iverilog). Rule 1 is Verilog-only,
VHDL having no `initial` construct and no need of the lowering.

The Verilog printer's own vector-init workaround is removed rather than kept
as a fallback: a whole-vector init that reaches a dialect which cannot inline
it is now `unsupported`, since the stage owns the lowering.

Three latent backend bugs the stage exposed, each fixed and pinned:

- A `for` loop iterator was declared inside the unnamed procedural block
  ("Variable declaration in unnamed block requires SystemVerilog"). Iterators
  now join the module's declaration region, like the process-local
  declarations already there. This broke any user loop in a v95/v2001 process.
- A named process printed as `myblock : always_comb`. Verilog names a BLOCK,
  so the label belongs on the `begin`: `initial begin : mem_init`.
- `DropProcessAll` derived its explicit sensitivity list by walking block
  kinds it enumerates, and loop blocks were missing, so a signal read only
  inside a `for` body never reached `always @(...)`. Loop bodies, `while`
  guards and `for` ranges are now walked.

And one that the loop fix in turn exposed: an ARRAY cannot be named in a
Verilog event control (it takes expressions, and `@*` is undefined over arrays
besides being absent from v95), so `always @(mem)` was rejected by iverilog
and yosys alike. A constant-index read now contributes just that cell, which
is both precise and nameable; only a dynamic-index or whole read falls back to
listing every cell, `@(mem[0] or mem[1] or ...)`, the form synthesis has
always required here. VHDL names the array signal itself and is left alone.

Verified against iverilog, verilator and yosys on v95 and v2001 (and
verilator on sv2009 under the option), with the AES cipher simulation re-run
across every tool/dialect combination. No reference output changed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The manual-proving section already prescribes read_slang for DFHDL
output; the training repo's cav now does this automatically for both
sides when the plugin is present (CAV_FRONTEND=auto), so say so where
the prescription lives.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Sr34sJ2wXWXZ9ENf817aT
Revised connection semantics for related-domain derived clocks, so that
clocks connect automatically only in a predictable way:

- A related domain may declare `Clk <> OUT`: the internal gating site,
  driven from its own design scope (`active.clk <> icgOut.as(active.Clk)`)
  and sourcing every same-named derived clock in scope through the magnet
  flow (the veer.sv structure, where the core creates its gated clocks).
- Same-named derived clocks within a clock group always form one clock:
  AddClkRst mints the distinct `Clk_<relName>` type unconditionally, and
  the mint-when-driven detection and collapse-to-origin fallback are gone.
  A derived clock is never implicitly merged onto its origin clock; the
  ungated form is an explicit connection at a wrapper.
- A derived clock group with no source anywhere surfaces as a top-level
  input port: sourceless Clk-kind magnet targets (magnetUnmatchedTargets,
  now exposed by MagnetMap in deterministic groupByOrdered order) climb
  the whole hierarchy in AddMagnets, so a forgotten gated-clock connection
  becomes a visible port instead of a silently dead or wrongly merged
  clock.

ConnectMagnetsSpec pins the new behaviors (an output derived clock
sourcing a sibling consumer; the sourceless bubble-to-top chain),
AddClkRstSpec covers OUT minting, and the former collapse tests are
re-harvested under always-mint semantics. Docs updated accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Clk <> OUT counterpart of RTDerivedClkDomain: the internal gating
site, whose design scope drives the derived clock (e.g. from an ICG
output) and exports it to every same-named derived clock in scope.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nEqual

Cache entries stay valid across formatting/doc edits (the code digest hashes
typed trees), so stored members carry positions the live run no longer has,
and value unification at adoption (globals, refTable re-uniting) broke on
exactly the edits the digest is designed to survive.

Meta now defines two named comparisons and derives no CanEqual, so a direct
`meta == meta` does not compile and every site names which notion it means:

- `sameIdentityAs` (name + annotations, precisely the digest-visible fields):
  implemented by equals/hashCode, so member case-class equality composes it
  implicitly and cached members unify with their live counterparts.
- `sameDclAs` (all fields): "same declaration", anchored on position. Used by
  DesignLoadKey's intra-run gate equality (without it, same-named designs from
  different declarations unify into one) and UniqueDesigns' grouping (reachable
  via adopted cache children, which skip elaboration's dclName enumeration).

The hashCode change flushed out a latent nondeterminism: magnetConnectionMap
was a hash Map iterated by ConnectMagnets to emit connections, with only a
by-name sort on top (which ties for same-named points). It is now an
insertion-ordered ListMap, so connection order follows instantiation order;
one ConnectMagnetsSpec expectation updates from the old hash-derived order.

Tests: MetaSpec pins both notions; SubDesignCacheSpec adds the position-drift
adoption regression (fails with "Failed reference check!" without the fix);
UniqueDesignsSpec pins that same-named distinct declarations stay separate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Meta gains `namespace: String` (default ""), the Scala package path of the
declaration it describes. It is digest-visible (a package clause is in the
typed tree), so it joins `sameIdentityAs`/`hashCode`/`prot_=~`; regular values
keep "" since their namespace is their design scope. The eventual packages
feature places a named type by relating its namespace to the top design's.

Capture, three paths:
- plugin: `metaGen` takes a namespace argument; design classes and method
  declarations (`genDclMeta`) record their enclosing package via `mkNamespace`.
  Namespaces stop at the package level deliberately: enclosing objects are
  scoping, not namespacing.
- macros: `TypeMetaGen` builds a full declaration Meta (name, namespace,
  position, doc) from the class symbol inside the struct and enum derivation
  macros, mirroring the plugin's `Position.fromAbsPath` convention.
- runtime fallbacks: the product/reflection struct path, the enum-companion
  path, and the opaque `ClassEv` path record name + `getPackageName`, which
  agrees with the macro's package-level namespace (`SameFields.check` compares
  the two constructions by type equality).

`NamedDFType` (DFStruct, DFEnum, DFOpaque, DFView) carries `meta: Meta`
instead of a bare `name: String`: `name` reads `meta.name`, `updateName` goes
through `meta.setName` (preserving namespace/position/doc under UniqueNames
renames), and type identity becomes name + namespace + annotations +
structure, composed through Meta's equality. Tuple structs are structural and
get `Meta.named` (root namespace); DropRTProcess's synthesized state enum
likewise.

Five positional extractors that would have silently bound a Meta where a
String stood (four `DFStruct(name, _)`, one `DFEnum(name, _, _)`) had dead
binders, renamed to `_`.

Zero output diff: the full suite passes unchanged from a cleared-cache state.
MetaSpec extends to namespace identity and pins the captured type meta
(struct/enum position + doc, opaque name + namespace, tuple root namespace).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…amed types

Closes the phase-2 loose ends ahead of the packages-feature emission phase:

- Global values: the plugin now passes the declaring package on every named
  value's `setMeta` (MetaContext/DFC carry it), and `DFC.getMeta` keeps it only
  at GLOBAL scope (no owner in context): a global constant records its package
  (gold veer packages hold parameters too), while a design-scoped value's
  namespace stays its design ("").

- Opaques: `ClassEv` captures the declaration's position, doc, and package at
  materialization, so the `DFOpaque` given and every `ce`-holding `as`-op build
  full meta; direct-instance callers (Clk()/Rst(), stage-minted magnets) keep
  the runtime name+package fallback. The general-opaque FQN-hash id retires to
  0 (identity is meta: name + namespace, like structs and enums); magnets keep
  their per-instance id, and `prot_=~`/`isSimilarTo` compare meta identity.
  The dead `opaqueType` extension (cast the Int id to TFE, zero callers) is
  removed.

- Doc comments on named types now emit at the type declaration through the
  shared `csNamedDFTypeDcl` choke point: `/** */` in DFHDL code, `/* */` above
  SystemVerilog typedefs, `--` above VHDL type declarations. Macro-captured
  docstrings include the raw comment markers, unlike the plugin's cooked form,
  so `sanitizedDocstring` (internals) normalizes them; without it the printers
  double-frame (`/**/** doc */*/`).

Tests: the SAME "Docstrings on named types" design (documented struct, enum,
and opaque) is pinned with exact expected output in PrintCodeStringSpec,
PrintVerilogCodeSpec, and PrintVHDLCodeSpec (appended at file end; earlier
tests there embed their own source positions). MetaSpec pins global-vs-local
value namespace, opaque position, and the inert general-opaque id. The backend
doc tests fail with the emission line reverted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…printer

Named types, global constants, and global static functions are placed by the
namespace rules (printing.Namespacing): a namespace equal to or an ancestor of
the TOP design's stays in the general global defs file; anything else lands in
a dedicated package named by the namespace RELATIVE to the top's (dots to
underscores). Two namespaces mapping to one package name, or a package name
colliding with a design name, are hard errors. Clk/Rst/Magnet opaques are
language-level and never packaged.

Emission: shared partition in DFTypePrinter/Printer (packagedTypeDcls +
packagedGlobalDecls + packagedContents) with cross-package topological
ordering, sub-DB dedup, placement overriding design-locality, and hoisting of
global-placed types referenced by packaged content into the global file (a
package file cannot reference a type declared inside a design). Package files
join printedDB ahead of designs and csDB after the globals section.

References qualify instead of importing, so same-named declarations from
different packages can never collide: SystemVerilog prints `pkg::name` (type
names, enum entries, global constants, static-function calls), the DFHDL
printer prints fully qualified `<namespace>.name` and renders each package as
a real Scala `package <namespace>:` section. v95/v2001 keep merging everything
into the single global header.

Fixed along the way: DFC gains `getDclMeta` (design-block dclMeta keeps its
namespace; the value-oriented `getMeta` owner gate was nulling def and child
class design namespaces); global-scope method copies (the pre-existing
non-unification mints one block per global call nest) dedup by `sameDclAs` at
printing; DFSpec's mock top now carries the concrete spec's package namespace
via `@metaContextIgnore` (the plugin's static injection would stamp `dfhdl`).

The object-hierarchy question was settled back to packages-only: a Scala
object is a value (aliasable, importable), so objects remain scoping.

Pinned end-to-end in PrintCodeStringSpec and PrintVerilogCodeSpec over
PkgFixtures.scala: sibling packages typespkg1/typespkg2 in one file, with
typespkg2 referencing typespkg1 (struct field + qualified static call) and
typespkg1 referencing uniquely-named general globals.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…SIWYG)

A named type now emits under its own name in both backends, which is what the
translation flows compare against (cav's interface_precheck string-compares
port types: gold `veer_types::lsu_pkt_t` vs the prefixed form failed every
struct-port module).

The prefixes were quietly separating types from values, so UniqueNames now
does it explicitly: type and value identifiers share one HDL namespace (SV in
scope, VHDL case-insensitively), so the FINAL (post-rename) global type names
are reserved against every value renamer, and each design's local type names
against that design's values. Keyword avoidance for type names already came
from the type renamers' reservedNames. Two collision renames this correctly
produced are pinned in PrintVHDLCodeSpec (signal `state_0` vs enum `State_0`
-> `state_0_0`; port `p` vs record `P` -> `p_0`).

All 111 HDL reference files regenerate mechanically: prefix removal plus the
column realignment that follows from shorter type names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each namespace-derived package emits as a full VHDL package unit: the spec
holds the type declarations with their conversion-function prototypes, then
the constants and method prototypes in dependency order; the body holds the
conversion-function and method bodies. Visibility is by `use` clauses (no
qualification in VHDL): a package uses the general package plus every package
preceding it in the cross-package topological order (dependencies are
guaranteed to precede), and design files use all emitted packages.

The general `<top>_pkg` now carries only global-placed content: packaged named
types are excluded from its type collection, conversion functions, spec decls,
and method bodies (which also pick up the same-declaration dedup), while
design-local types hoisted by packaged references are included. Architecture
declarative regions no longer re-declare packaged types (they arrive through
the `use` clause; a local re-declaration would shadow and break type identity).

The shared printing layer exposes the typed per-package entries
(packagedGlobalDeclEntries + csPackagedGlobalDecl) so VHDL can assemble its
spec/body split; Verilog keeps consuming the flat rendered form.

Pinned end-to-end in PrintVHDLCodeSpec "Namespace-derived type packages" over
the same PkgFixtures design as the Verilog and DFHDL pins: typespkg1/typespkg2
package units with records, enums, subtypes, conversion functions, constants,
and the static function, cross-package use-visibility, and the entity ports
typed by packaged records.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two backend-facing completions of the namespace-derived packages feature.

`DropPackages` (new stage, last in BackendPrepStage) serves verilog.v95/v2001,
which have no packages: it folds each packaged declaration's package name into
its own name (`typespkg1_PkgEnum`, `typespkg1_pkgCalc`) and clears its
namespace, so everything lands in the single global defs header under names
that still say where they came from and cannot collide across packages. It
covers exactly what the packaged emission places: named types, global
constants, and the global HDL methods — whose placement analysis moves out of
`Printer` into `analysis.HDLMethodAnalysis` so the stage and the printers
cannot drift apart.

`UniqueNames` now scopes the uniqueness of a global declaration by the package
it is emitted into, rather than across all of them: every printer references a
packaged declaration through its package, so two packages may hold the same
simple name. The general defs group is uniquified first and reserved for every
package (a package's content sits alongside the general globals), while two
packages never see each other unqualified. It needs no backend flag — a backend
without packages reaches this stage with the namespaces already flattened away
by `DropPackages`, leaving the single general scope it wants.

VHDL switches from `use work.<pkg>.all` to selected names (`work.<pkg>.<name>`)
for packaged types, enum literals, conversion functions, constants and method
calls — the same collision-proofing SystemVerilog gets from `pkg::`, and what
makes package-scoped uniqueness safe there. Verified to analyze under ghdl
(--std=93 and --std=08) and nvc, case choices included.

Also fixes `ComposedDFTypeReplacement` dropping a struct's non-matching fields
when rewriting the matching ones.

Known residual: a global HDL method's name is still globally unique, since
same-named design blocks are enumerated by elaboration, which is not
package-aware.
…s wrong

The devdoc covers the Verilog-backend representation choice for a DFVector:
why packed is the default and unpacked the exception, the two halves of the
decision (supportsPackedVector is a TYPE property, hasMemAccessPattern a usage
one) and why that split is what makes it sound, the rendering consequences
(dimension placement, aggregate order, the streaming-reversal cast,
part-selects), and a standalone argument for why the two forms can never meet
in one operation. The user-facing rules stay in the type-system guide; this is
the "why it is shaped this way" companion.

Two things measured while writing it rather than taken from the source:

- An index-labeled aggregate (`'{0: e0, 1: e1, ...}`) IS legal on a packed
  array and would have been representation-independent, needing no reversal
  and no `unpackedOrder` flag. slang and verilator accept it; iverilog -g2012
  and yosys's read_verilog reject it. So the positional-reversed form buys
  portability across the partial frontends, which is the same trade as
  avoiding `'{default:}` on unpacked arrays (yosys#6120). Recorded as the
  reason, since the source does not say why.
- The namespace-derived package file is emitted with no `default_nettype /
  `timescale header while every other file has both, and slang refuses the
  mix, so any design using a type package fails to elaborate as a file set.
  Listed under open issues; it is not this feature's, but it lands on the same
  output and blocks the flow that consumes it.

Also corrects the verilog-to-dfhdl skill: it claimed a port literally named
`clk` collides with the magnet. It does not. `val clk = Clk <> IN` in a design
that also has registers emits one `clk` port, the registers still clock on it,
and `clk.actual` reads it as a Bit -- which is how lsu_clkdomain drives a
derived clock from the root clock the way an ungated ICG does. What collides is
a non-magnet port whose name shadows the magnet's.

The benchmarks bump carries lsu_clkdomain, lsu_trigger, the veer_types package
and the clock-domain traits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ation

A DFHDL vector now prints as a packed descending array (element 0 at the
LSB end, DFHDL index i = SV index i) under the SystemVerilog dialects,
matching what hand-written baselines declare for bus-like array ports
(SystemVerilog cannot connect a packed port to an unpacked one, so the
old unpacked-only form was an incompatible interface).

Unpacked survives only where required: the pre-SV dialects, cell types
that cannot form a packed array (integer atoms, real, string, time, and
signed cells, whose element part-select would silently lose signedness),
and declarations whose usage follows the memory access pattern, so
block-RAM/ROM inference is preserved. The usage classification is
DFVal.hasMemAccessPattern in DFValAnalysis; the printer combines it with
the type-level packability into unpackedVectorDcls (init references are
representation-neutral on both sides).

Bits<->vector casts preserve the DFHDL element-0-at-MSB bit order via a
scalar-cell-granular streaming reversal ({<<W{...}}) or the element
concatenation; aggregates keep the order-free idx:value keys, listed
descending for packed targets. Verified against verilator and slang
(vanilla yosys's own parser accepts no assignment pattern on packed
targets; its slang frontend does).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`default_nettype` and `timescale` are compilation-unit state, not file state,
so a file that omits them inherits whatever the previously compiled file left
behind. The package files are compiled ahead of the designs and carried no
header of their own, which left their compilation dependent on ordering.

They now emit the same `csLibrary` header a design file does, which also
subsumes the global defs include they were emitting by hand.
…ces it

A global value's refTable bindings live in its own DesignContext, injected
into the run's DB only at first reference (refTW -> injectGlobalCtx). The
SimplifyFunc extractors run on the raw IR args before any refTW, so a
never-yet-referenced global alias operand (an object-scoped Int <> CONST
whose value is another const) crashed with `Missing ref` the moment an
extractor stripped it, e.g. as the left operand of `-` (SelfCancelling) or
as a max operand against a literal (MaxMinChainAbsorb). Injecting each
operand's global context up front is exactly what refTW does moments later,
idempotent, and covers every extractor including the global-scope ones.

Fixes #494

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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