Skip to content

More fixes - #465

Merged
soronpo merged 26 commits into
mainfrom
training
Aug 9, 2026
Merged

soronpo merged 26 commits into
mainfrom
training

Conversation

@soronpo

@soronpo soronpo commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

Oron Port and others added 26 commits August 7, 2026 04:09
Its deliberately-corrupted DBs made SanityCheck print its violation
diagnostic into every test log (CI and local) while the intercepting
tests passed, reading as a failure that never happened. The check itself
still runs after every stage in every stage test via StageRunner.

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

Resolves knowledge-gap tickets from the hog-fpga, castle-drawing and
starfield learner runs.

Operations reference (closes #113, #114 in part):
- add `Bit`/`Boolean` to the Comparison `Applies to:` line, plus a
  subsection covering them as operands and stating that ordering
  comparisons are rejected
- normalize the Selection section to the `Applies to:` convention and
  introduce that convention once at the top of the Operations part
- cross-link the `Bit`/`Boolean` and Enumeration type sections to the
  operations that apply to them, and link comparison-ops from
  from-verilog

Elaboration-time vs hardware (closes #109, #107, #103):
- qualify the "both branches" rule: Scala checks both, but only literal
  widths are tracked at the Scala type level, so a parameterized
  `generate if` needs no guard. Note the ED implicit `.toScalaBoolean`
  and the RT/DF hardware-`if` default
- drop "these loops are unrolled by the compiler", contradicted by the
  adjacent example: a process loop stays a loop
- document the process-scope iterator as a hardware value that cannot
  be read into Scala, with the design-scope-loop alternative
- qualify the part-select base as allowing a process loop iterator, and
  add the variable-base row to the from-verilog table

Verilog mappings (closes #106, #91, #112):
- document `localparam`: a `val` is emitted only when it is a DFHDL
  constant, which the `: Int <> CONST` ascription forces
- list an inline expression as a valid child-port connection
- document enum declaration scope, and that the generated typedef's
  placement follows usage rather than the Scala declaration site
- document the generated global definitions/package files, which were
  previously only visible inside generated snippets

Naming (closes #110):
- a port sharing a design class's name is not a collision; types and
  terms are separate namespaces. Remove the `@targetName` workaround
  that contradicted the page's own `new` recommendation

Bit concatenation (closes #95):
- document the per-bit connection loop and the ascribed-`var` lane
  accumulation as the two ways to build a value from a collection

All added snippets and generated HDL were compiled against
0.22.0+38-ba527c30-SNAPSHOT.
…` for HDL naming (#454)

Scala rejects `@targetName` on a top-level class, so a design could not
emit a module name different from its Scala class name. DFHDL naming now
reads a dedicated `@dfhdl.hw.annotation.setName` annotation in the
plugin's `getFinalName`, covering ports, variables, methods, design
parameters, and design classes (including top-level ones) through the one
mechanism. The annotation is a plain StaticAnnotation, kept out of IR
annotation lists; `@targetName` remains a purely Scala/JVM concern (the
AES defs keep it for erasure disambiguation alongside the new
annotation). Platforms submodule migrated accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e first block (#456)

A default value on a DFHDL parameter in a non-first (curried) parameter
block crashed scalac at Erasure: the companion default getter takes the
earlier blocks' parameters as arguments, which the plugin's parameter
threading cannot consume. MetaContextPlacerPhase now reports clear errors
instead: a design/interface class must declare all its DFHDL parameters
in a single parameter block, and a defaulted DFHDL parameter must be in
the first block. Plain Scala blocks stay free, so a dependent-typed DFHDL
block after a plain block (RTGenericRom's `depth.type`-sized ROM) remains
legal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes #459

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…named `top` (#458)

PreTyperPhase's auto-@top injection spelled the annotation as an unqualified
`top` identifier, which a design class itself named `top` (the common Verilog
top-module convention) captured, failing compilation with an inscrutable
"Cyclic reference involving class top". The injection is now anchored at the
root (`@_root_.dfhdl.top(true)`), immune to both the class-name capture and a
user package/object named `dfhdl`. TopAnnotPhase matches the annotation by
resolved symbol and the plugin's rightmostName-based detection matches the
qualified spelling, so no consumer changes.

Regression test: issues/i458.scala keeps the design class unannotated so the
auto-injection path is the one exercised; without the fix the whole lib test
scope fails compilation on it.

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

Fixes DFiantHDL#117

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ew relative `.eby` (#119)

An anonymous +/-/* cone assigned or connected to a wider value now
re-evaluates at the target's width and sign, matching Verilog's
assignment-context width propagation: every operand widens (recursively
through the anonymous cone) and the operations stay modular at the target
width. The carry spelling is kept where provably identical: a binary op
over leaf operands, same sign as the target, whose carry width fits the
target exactly (bare `a +^ b`) or exceeds it decidably (carry + truncating
resize). A promoted carry op is never extended, only truncated; extension
of a carry result is sign/op-dependent and was the root of the formally
confirmed miscompiles: result-level zero-extension of unsigned chains
meeting signed targets, shallow (top-only) promotion of nested chains,
zero-extension of a widened subtraction carry, and the illegal
EXTEND_S(<expr>) emission (out-of-range bit-select).

`.eby(k)` is a new relative widen-by operation on UInt/SInt/Bits, sugar
for `.resize(width + k)`. All printers render a widening relatively
(DFHDL `.eby(k)`, Verilog `EBY_U`/`EBY_S`, VHDL `eby`) exactly when the
target width is an anonymous `base + k` increment of the source width, so
parametric widening stays symbol-free while named/literal widths keep
their absolute spelling. The Verilog EXTEND_S/EBY_S operands are
guaranteed indexable by a new NamedVerilogSelection widening criterion
plus a fused sign-conversion emission; VHDL carry mul prints infix
(numeric_std mul is already full-width; `cmul` was emitted but never
defined in dfhdl_pkg).

Verified by per-output yosys SAT miter proofs against a hand-written
Verilog golden (19/19, from 10 failures before the fix) and by the full
test suite, with zero reference-output churn.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e canonical widening form

The IR gains one invariant: an arithmetic Func's type always equals its
(aligned) operands' type. The carry operators elaborate as modular funcs
over operands widened by explicit aliases (`x +^ y` is `x.eby(1) + y.eby(1)`
after common-width alignment), with no special-cased Func return type, and
the target-context widening in toDFXIntOf loses its carry-fit branch: the
widening always evaluates at the target, and the SPELLING of the result is
reconstructed at print time by two new DFValAnalysis extractors: `Eby`
(literal-delta widening alias) and `CarryFunc`, whose carry-width equations
(max+1 for +/-, operand-width sum for *) are decided symbolically via the
linear-form calculus, so a parametric `a *^ b` reconstructs even though its
operand delta is not a literal.

Printers: DFHDL prints CarryFunc as `x op^ y`; VHDL reconstructs cadd/csub
and prints a carry mul infix (numeric_std multiplication is already
full-width; the previously emitted `cmul` was never defined in dfhdl_pkg);
Verilog prints a carry func bare exactly when every consumer provides its
width context (a net RHS or a same-width func operand) and the explicit
EBY/EXTEND operand forms otherwise, so the self-determined-context
truncation genus is structurally impossible and the NamedVerilogSelection
carry-naming criteria are deleted. The Verilog-semantics warning machinery
derives carry-ness from the CarryFunc shape and walks through the operand
widening aliases.

Widening printouts prefer the RELATIVE `.eby(k)` spelling as the canonical
form whenever the delta is a known bit count, including between literal
widths (a widening between literals carries no spelling in the IR, so
`x.resize(9)` over an 8-bit x prints back as `x.eby(1)`); a width given by
a named parameter or constant keeps its absolute spelling by name.

A deliberate semantic edge of the unification: an anonymous explicit-carry
result consumed by a wider context now re-evaluates at that context (naming
the value pins its width), matching how every other anonymous arithmetic
expression composes.

Verified by the 19-case yosys SAT miter matrix against the Verilog golden
and the full test suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ns (#116)

The TC width-fit check decided only when the width difference folded to a
constant, so `SInt(2 * W) <> a * b` (RHS width W) errored as undefined.
The decision now falls back to a linear-form non-negativity proof over the
validity domain (every width >= 1): `2 * W >= W` is accepted for a free
parameter W, and a provably violated relation (`W >= 2 * W`) upgrades the
vague undefined error to the definitive larger-than one. A literal target
against a free parameter (`16 >= W`) stays undecidable and is still
conservatively rejected. The proof core moves into the shared Calc so it
runs in the same linearization mode as the diff; equality/similarity
queries remain proof-free.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…HDL-aware diagnostics (#455)

Fixes #455

A `reduce(_ ++ _)` over raw port part-selects can never typecheck: `reduce`
fixes its type parameter to the element type, which carries the port's own
modifier and, until now, the width argument's singleton type
(`Bits[LANE.type]`), and no operation result can land back on either. Three
independent defects hid behind the one report:

- A non-literal width survived as a singleton type. It now collapses where
  the value ENTERS the width algebra: `IntParam.fromValue`'s precise
  conversion is restricted to `Int & Singleton` (a `DFConstInt32` parameter
  falls to the wide conversion, now monomorphic at `IntParam[Int]`), so
  `Bits(LANE)` is `Bits[Int]` while `Bits(8)` stays `Bits[8]`, and the
  element ascription `Seq[Bits[Int] <> VAL]` makes the reduce work by plain
  subsumption, with no conversions and no adapters in the elaborated IR. An
  abstract `Int & Singleton` type parameter (generic code over literal
  widths, e.g. `Matrix`) still gets the precise conversion, deferring
  precision rather than dropping it. The explicitly written singleton type
  (`Bits[LANE.type]`) remains available through its `ValueOf` given.

- The plugin's `CustomReporter` re-rendered the same inline-expansion error
  up to three times, once at a corrupt position: a macro-synthesized tree's
  innermost frame pairs the user's source file with the span of the macro's
  own splice, and dropping the outer position chain exposed it while also
  bypassing the compiler's position-keyed dedup. The position is now
  normalized to the first inline frame that belongs to the compiled unit,
  and the normalized (position, message) pair is deduplicated in `isHidden`
  so the error count matches what is rendered.

- The type printers hid the modifier: a port-typed requirement printed as
  `<> VAL`, rendering the remaining mismatch identically on both sides.
  Both twins (the plugin's `modifierText` and core's `ShowType`) now name a
  port by its direction, and a type mismatch whose required side is a DFHDL
  value is re-issued without the compiler's postscript (the
  transparent-inline note and the import suggestions never apply to a DFHDL
  mismatch). The repro now reports once, at the user's expression:
  `Found: Bits[Int] <> VAL / Required: Bits[Int] <> IN`.

The workaround reported in the issue (ascribing the singleton element type)
compiled but failed elaboration with a width error; the collapsed
ascription and `.bits` are the correct spellings. Regression tests pin the
collapse and the working reduce in `SameWidthArithSpec` and the printed
types in `TypePrinterSpec`; the reporter behavior is manually verified,
since `assertPluginError` renders without going through `CustomReporter`.

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

Follow-up to the issue #455 diagnostics: the one error the un-ascribed
reduce-over-port-slices shape still (correctly) produces now carries a
dedicated remedy instead of a bare mismatch. The rewriter recognizes the
shape (a plain computed value against a declaration-modified requirement of
the SAME DFHDL type), identifies the enclosing call by name (the typed tree
does not exist yet at reporting time, but the unit's parse tree does, and
the innermost `Apply` with an argument containing the error span is the
call whose type parameter was committed), and spells the one-token fix with
the actual type: `.reduce[Bits[Int] <> VAL](...)`. An unidentified caller
keeps a conditional wording of the same note.

The rewriting itself (position normalization, dedup identity, postscript
drop, guide rails) moves out of `CustomReporter` into a shared
`DiagnosticRewriter` that `PluginTestPhase` now applies to its nested
snippet compilations, so `assertPluginError` specs assert on exactly what a
user reads; previously the reporter's behavior was manually verifiable
only. The normalization's unit-source anchor becomes an explicit parameter:
a nested diagnostic's position chain extends past the snippet's virtual
source into the enclosing real unit (the marker call site), so the
outermost frame does not identify the compiled unit there.

The new `assertSinglePluginError` asserts a snippet produces EXACTLY one
error, which pins the dedup on top of the message text: the typer re-raises
this mismatch through the inline expansion of `++`, once at a corrupt
macro-splice position, and all re-raises must collapse into one rendered
diagnostic. `TypePrinterSpec` pins the full guide-railed message that way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In a DFHDL compilation the compiler's selection-error addenda mislead
rather than help: the import-suggestion machinery proposes DFHDL's internal
conversions (`InitValue.fromValue`, `Exact.fromValue` and friends) for
EVERY receiver, including plain Scala ones, and the extension-attempt
transcript restates the receiver in raw types over dozens of lines. The
diagnostic rewriter now reduces a `NotAMember` error to its core sentence,
`value mem is not a member of UInt[Int] <> OUT`, cutting at the exact
addendum openers `ErrorReporting.selectErrorAddendum` can append (the
leading period there belongs to the addendum). A tried-but-failed extension
keeps that one fact as a bare ` (extension method tried)`; the did-you-mean
hint, which only appears when no other addendum does, is kept, and so is
the `-explain` explanation.

`TypePrinterSpec` pins all three shapes through the real rewriting path:
no extension involved, extension tried, and a plain `Int` receiver.

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

Upstream computes the hint only when no other addendum exists, and in a
DFHDL compilation the import-suggestion addendum is never empty: the
Exact/DFVal conversions make every selection on every receiver look
"available as an extension method", which is the same mechanism behind the
stripped import spam. So `x.toStrig` never said "did you mean x.toString?"
to a DFHDL user at all; stripping alone cannot restore what was never
computed.

The rewriter recomputes the hint after the strip, exactly the way
`NotAMember.msg` would have (`DidYouMean.memberCandidates`, `closestTo`,
`didYouMean`, all public API). The message's `site` and `proto` are private
constructor parameters, reached by reflection; any failure there just
means no hint. The extension-tried variant keeps its bare parenthetical
without a hint, matching upstream, where an attempted extension also
suppresses it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes #457

Applying `.width` on a DFType constructor is now the DFHDL counterpart of
Verilog's `$clog2` width derivation: construct the type with `.until`/`.to`
and recover the width the constructor computed, as a constant DFHDL `Int`
rather than a plain Scala `Int`, so a parametric width stays symbolic:

  val ADDR_WIDTH = UInt.until(DEPTH).width   // elaborates as clog2(DEPTH)

`.length` joins it for the types where "how many" is a natural question:
for `Bits`/`UInt`/`SInt` DFTypes and values it is the number of bits,
identical to `.width`; for vector DFTypes it is the number of elements,
matching the value-level vector `length` that already existed.

The user guide gains a "Width and Length Queries" section in the
type-system Operations reference; the `clog2` anti-pattern warning now
completes its own story with the recovery idiom; and the Vector Element
Access table drops a stale `vec.size` row (no such member exists) in favor
of the verified `length`/`width` rows.

`WidthLengthQueriesSpec` pins the elaborated constants, including the
symbolic parametric width and the vector length/width distinction.
`TypePrinterSpec`'s extension-tried diagnostic case moves from `o.length`,
which this change makes legal, to `Bit`'s `length`, which no extension
serves.

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

Follow-up to #457. `val ADDR_WIDTH = UInt.until(N).width` elaborated
without the binding's name: `toDFConst` mints a fresh NAMED const for a
literal width, but a parametric width already exists as an anonymous
member (`clog2(N)`, created inside the type constructor), and the handle
was returned untouched, so the printers inlined the expression at every
use site instead of declaring the constant.

The new `toDFConstQuery` sibling rebinds a pre-existing constant reached
under a named context through a named Ident, never a meta restamp (issue
#449), and the three user-facing query sites use it: `DFType.Ops.width`
(which the `length` delegates share), the vector type `length`, and the
value-level `DFVal.width`. The generated code then reads

  val ADDR_WIDTH: Int <> CONST = clog2(N)
  val a = UInt(ADDR_WIDTH) <> OUT

with the constant referenced by name everywhere, including width checks
(`.resize(ADDR_WIDTH)`).

`toDFConst` itself stays wrap-free on purpose: operations pass constants
as function ARGUMENTS under the operation's own context (`repeat`'s
count, for example), and a wrap there would steal the result's name for
an argument. The plugin's naming scope completes the picture: only the
spine apply of a `val` carries its name, so an inner query such as
`value.width.toScalaInt` stays anonymous and creates no member, and the
full suite shows no other printed-output change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolves DFiantHDL#115 and DFiantHDL#88

A derived parameter is not declared as a parameter in DFHDL: a parameter
default cannot reference a sibling parameter (now stated at the Parameter
Block Syntax `_default_` bullet), and no separate parameter is needed,
since the derived value is computed in the design body as a named
constant that serves everywhere a parameter would, port widths included.

The new subsection demonstrates the pattern with a generic register
parameterized solely by its initialization value (`Bits[Int] <> CONST`),
deriving the register length with the `.length` query and sizing the
ports with it. The generated Verilog and VHDL are included inline, both
captured from real runs and lint-verified (verilator, ghdl): the derived
constant appears by name (a Verilog localparam, a VHDL generic), the
parameter width is fixed by the applied argument while its value stays
overridable, and keeping two parameters consistent by hand, the mismatch
trap of dfhdl_by_agents#88, is ruled out by construction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…NCs printed natively per backend

A value-receiver width/length query now elaborates to a `DFVal.Func` with the
new `Op.width`/`Op.length` instead of materializing a constant, so the
generated code keeps the value-to-width relation: SystemVerilog spells
`$bits(x)`/`$size(x)`, VHDL spells `x'length`/`bitWidth(x)` (constant
arguments only: a design-level constant becomes a generic whose default cannot
name a port), and the pre-SV Verilog dialects inline the width parameter
expression (shared `AbstractValPrinter.csInlinedWidth`). Type-receiver queries
still materialize width constants.

- `Func` constData reads the argument's TYPE, so the query is constant over a
  non-constant argument; `calcFuncData` gets the matching type-driven case.
- `IntExprCalc` linearizes the queries through the argument type's width
  parameters, with product-base equivalence so `vec.width` matches `W * N`;
  `linearOfTypeWidth` moved into `Calc` (mode-consistent; its `widthIntOpt`
  shortcut folded top-design parameters through their defaults).
- `DropStructsVecs` folds length-over-vector into the element-count parameter
  value before flattening destroys it; width needs no fold.
- DFacsimile folds the queries to per-instance resolved constants
  (`paramDependent` walks the argument's type refs for them).
- Docs: the InitReg inter-dependent-parameters outputs now show
  `$bits(INIT)`/`INIT'length` (verilator/ghdl lint-verified); the type-system
  query section documents the native spellings.

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

`process(all): y := x` parses as a type ascription and used to fail with
obscure typer errors. Two interception points now report a dedicated error
spelling out the fix from the statement's own source: the PreTyper
parse-tree rewrite (well-formed trees; the statement is neutralized so no
follow-on errors surface) and a CustomReporter override for bodies that
fail the type parse (a parser error skips every plugin phase while the
typer still runs, so the recovered tree's ascription errors are rewritten
at reporting and collapsed onto one). The override is gated on a parser
error having been reported, so ordinary compilations never pay for it.
The nested snippet pipeline now types parser-recovered trees like the real
pipeline, making the override testable (`assertPluginErrors`);
RTProcessSpec is renamed ProcessSpec and hosts the new tests.

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

A narrow anonymous arithmetic chain inside a `.sel` assigned to a wider
target evaluated modularly at the operand width, with only the selection
result extended afterward, silently diverging from the Verilog `?:` it
translates (whose branch operands are context-determined). An anonymous
DFXInt-typed sel converted to a wider type now re-types to the target,
with each branch re-entering the widening and the condition untouched,
so `dx := c.sel(xb - xa, xa - xb)` emits `c ? (xb - xa) : (xa - xb)`
in the wider assignment context, faithful to the source intent.

The promotion machinery moves to its own CarryPromote file: widenedOpt
(the widening decision and fresh-func construction) and the
Verilog-semantics warning helpers; the general toDFXIntOf conversion
stays in DFDecimal and falls back to the leaf conversion when no
widening applies.

All six probe shapes (carry-fit, beyond-carry, unsigned, mixed-sign,
nested sel, sel-inside-arith) are SAT-proven equivalent to a hand-written
`?:` golden via a yosys miter.

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

A conditional expression in a type-free position (a connection RHS or an
operand of a wider operation) was typed by its branches and converted as
a leaf, evaluating the branches modularly at the narrow width and only
extending the selected result, silently diverging from the per-branch
assignments it lowers to. An anonymous DFXInt-typed conditional header
converted to a wider type now re-evaluates each branch at the target:
a fresh terminal ident over the widened re-evaluation is built inside
each branch block (branch-local named values stay in scope), the
superseded terminal is dropped explicitly (idents are consumed
positionally, so the sweep alone would keep the narrow cone alive), and
the header is revised in place to the target type, the same revision
its construction applies. The type-driven positions were already
faithful (each branch converts under the expected type at construction)
and are regression-pinned.

Mid-construction the designDB flat snapshot is unavailable (open-owner
state), so the conditional structure is recovered from the raw
creation-ordered member list via ref walks; and to keep that list
properly nested, MutableDB gains a re-entrant `insertingAfter` insertion
mode that places additions right after an anchor inside a built block.

All four probe shapes (connection, arith-operand, match, nested if) are
SAT-proven equivalent to a hand-written `?:` golden via a yosys miter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ings see through conditionals (#464)

Two remaining divergences of the target-context widening model and one
diagnostic gap:

- A shift's LEFT operand is context-determined in Verilog (the amount is
  self-determined), so an anonymous shift converted to a wider SAME-SIGN
  type now re-types to the target and its left operand re-enters the
  widening: the carry bit survives a `>>` and a `<<` pushes into the
  extension range. A sign-CROSSING shift context stays a leaf: a shift
  evaluates at its operand's own signedness (an arithmetic-vs-logical
  `>>` difference), so the conversion cannot move to the operands and
  the explicit spelling states the intent there.

- Unary minus is truncation-commutative and joins the widened arithmetic
  ops, so a negation cone re-evaluates at the target instead of wrapping
  narrow and extending.

- The Verilog-semantics narrow-chain detectors descend anonymous
  conditional-expression branches (via the recovery shared with the
  widening), so a chain hidden one `if`/`match` away from a `/`, `%`,
  comparison, or shift is no longer silently missed.

All six probe shapes are SAT-proven equivalent to a hand-written golden
via a yosys miter. Also drops two redundant `asInstanceOf[ir.DFDecimal]`
casts (`asIR` is precisely typed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NVC gained Verilog support (matured in 1.22), so NVC.scala is split
QuestaSim-style into NVCCommon + NVCVHDL + NVCVerilog. The Verilog
front-end analyzes with `-a --keywords=1364-1995|2001` (an analysis
option, unlike the VHDL front-end's global `--std`), predicts the
Verilog work-library units (one WORK.<NAME> per module plus
WORK.<TOP>.elab, no secondary architecture unit), and requires
NVC >= 1.22. The SystemVerilog dialects throw: NVC cannot parse the
generated sv output (size casts, unpacked-array typedefs).

Since NVC is one binary serving both languages, `nvc` is now a
both-languages selection like `questa`/`vivado`: bare `-t nvc` sets
both tools, and in the `a/b` slash syntax it resolves by slot
(first=Verilog, second=VHDL). AvailableTools holds the per-language
front-ends in the verilog*/vhdl* scopes plus a questa-style marker
object with the matching SimulatorOptions conversions.

Linting generated output additionally waits on an upstream fix:
released NVC rejects a block comment inside a `define macro body
(nickg/nvc#1636), which dfhdl_defs.vh uses
for its Verilator lint pragmas. Once a release carries the fix, raise
NVCVerilog's version floor and enable nvc in FullCompileSpec's Verilog
linters and testApps' verilogTools. Verified locally on NVC 1.22.1
that analyze/elaborate/run of the full AES CipherSim works end-to-end
once past that preprocessor limitation.

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

Xezim (https://github.com/aionhw/xezim) is a SystemVerilog simulator with
no local install channel, so it runs from the DFTools v1.2.0 `sim-xezim`
image (linux-x64 only): the `auto` tools-location falls back to the image
whenever no local `xezim` exists. A single invocation parses, elaborates,
and runs, so linting maps to `--compile -s <top>` and simulating to
`--simulate -s <top>`; all sv dialects map to `--sv2017` (1800-2023
grammar by default, no IEEE 1364 mode, so v95/v2001 throw). The simulate
command pins `--max-time 1000s` because xezim's default silently caps
simulated time at 100us, unlike the other simulators' unbounded default.

xezim exits 0 even when it reports errors, so the tool's ProcessLogger
owns error detection (compile `: error:` diagnostics, `Simulation
error:` elaboration failures, and runtime `** Error`/`** Fatal`).
Foreign IP DPI libraries are wired via `--dpi-lib`.

Verified end-to-end through the image (WSL apptainer): EmptyDesign lint
and simulate run green, and an AES lint fails correctly through the
logger. Broader enablement waits on upstream fixes, all reported with
minimal reproductions:
- aionhw/xezim#106: false "Implicit net under `default_nettype none" on
  explicit wire ports with unpacked-array typedef types (blocks the
  vector/opaque-port designs, e.g. the AES suite);
- aionhw/xezim#107: exit code 0 on parse/elaboration/runtime errors;
- aionhw/xezim#108: DPI-C imports inside a parameterized child module
  are silently no-op'd (blocks every DPI foreign IP, so the ips sim
  specs do not list xezim yet; found via a vga_monitor validation run
  that confirmed the rest of the chain works under xezim).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soronpo pushed a commit that referenced this pull request Aug 13, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant