Skip to content

Verilog: pass instance parameters by name everywhere; remove -v95#1022

Open
nanavati wants to merge 3 commits into
B-Lang-org:mainfrom
nanavati:named-instance-params
Open

Verilog: pass instance parameters by name everywhere; remove -v95#1022
nanavati wants to merge 3 commits into
B-Lang-org:mainfrom
nanavati:named-instance-params

Conversation

@nanavati

@nanavati nanavati commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Instance parameters are now always passed by name; the Either in vi_inst_params is gone, along with the -v95 flag that was its main client. Follows the #997 discussion; supersedes #1021 (if this is preferred, #1021 can close — if #1021 lands first, this rebases trivially).

The insight

The one non--v95 client of positional parameters was the Classic foreign-function-bound-to-a-module flavor (in-tree: Fork.bs), where the hand-written module's parameter names were "unknowable." But the declaration does name each parameter slot: the type variable. So the convention becomes: the module's parameter names are the declaration's type variable names, and bsc emits

Fork #(.iw(32), .ow(64)) instance_vfork_0(.i(...), .o(...));

Fork.bs conforms to the existing Fork.v by renaming its own type variables (foreign vfork :: Bit iw -> Bit ow = "Fork",("i","o")) — no Verilog changes. This is a strict upgrade in failure mode: the old positional contract (type-argument order must match the .v parameter declaration order) failed silently as a miswired netlist; a name mismatch fails loudly at Verilog elaboration.

What's in it

  • Name threading: MakeSymTab captures the declaration's type variable names in quantification order (the same order the type arguments arrive at applications) into VarForg; they ride ICForeign.fTyVarNames and pair with the concrete values in AConv, so ANoInlineFun now carries [(String, Integer)].
  • Width discipline (new error T0159): for a foreign function bound to a module, every bit width must be a bare type variable or a literal. A type-function width (Bit (TAdd n 1)) has no name to pass and would force the .v to reimplement bsc's type arithmetic (with its exact ceiling/rounding semantics) to size its ports — rejected, with guidance to give the derived width its own variable. "True" foreign functions (no port list) are unaffected, as are BSV (* noinline *) functions (enforced monomorphic by T0111, hence parameterless).
  • Either collapse: vi_inst_params :: [(VId, Maybe VExpr)], matching how ports have always worked; printer simplified; pv95params deleted.
  • -v95 removal: the flag's only remaining substance was positional parameters plus dropping $signed/$unsigned (replacing the cast with a comment — lossy, not compatibility). It also hasn't delivered V95-clean output in years: the shipped primitive library uses localparam (SizedFIFO, Probe) and named parameter instantiation (SyncFIFOLevel) ungated. Relands the flag-removal intent of chore(comp): remove -v95 flag #719/Slightly modernize Verilog, take 2 #400 (cc @thoughtpolice); $signed is now always emitted. User guide updated.
  • Format bumps: .bo (ICForeign gains the names field) and .ba (ANoInlineFun shape) headers bumped to 20260706-1.

Migration

For out-of-tree Classic code using foreign ... = "mod",(ports) with numeric type arguments (believed to be nearly nil): name the declaration's type variables after the module's parameter names (or vice versa). Everything else is unaffected.

Testing

Related: #997, #1021, #719, #400.

🤖 Generated with Claude Code

@matx-amy

Copy link
Copy Markdown
Contributor

reviewed, 👍

nanavati and others added 3 commits July 15, 2026 01:02
The noinline-function instantiation path was the only site emitting
positional instance parameters unconditionally.  It serves two
populations: BSV noinline functions, which are enforced monomorphic
(T0111) and so never have parameters at all, and Classic foreign
functions applied at numeric types (e.g. Fork), whose ITNum type
arguments are passed as instance parameters.  The latter are positional
by necessity, not style: the foreign declaration syntax names only
ports, so the hand-written Verilog module's parameter names (Fork.v's
iw/ow) are unknown to the compiler, and the values are passed in
type-argument order matching the parameter declaration order.

Route the always-empty noinline case through the named side and
document the Classic-foreign contract at both the emission site and the
ANoInlineFun type.  No change to generated output (empty parameter
lists render identically on either side).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The one client of positional instance parameters that was not the -v95
fallback was the Classic foreign-function-bound-to-a-module flavor
(in-tree: Fork.bs), where the hand-written module's parameter names were
unknowable.  But the declaration names each parameter slot: the type
variable.  The convention is now that the module's parameter names are
the declaration's type variable names, and bsc emits them by name:

    Fork #(.iw(32), .ow(64)) instance_vfork_0(...);

Fork.bs conforms to the existing Fork.v by renaming its type variables
to iw/ow.  This is a strict upgrade in failure mode: the positional
contract (type-argument order matches the .v parameter declaration
order) failed silently as a miswired netlist; a name mismatch fails
loudly at Verilog elaboration.

The names ride from the declaration (MakeSymTab, in quantification
order, which is the type-argument order at applications) through
VarForg and ICForeign.fTyVarNames, pairing with concrete values in
AConv, so ANoInlineFun now carries [(String, Integer)].  A new check
(T0159) requires every bit width of a module-bound foreign function to
be a bare type variable or literal: a type-function width has no name
to pass and would force the .v to reimplement bsc's type arithmetic to
size its ports.  True foreign functions (no port list) and BSV noinline
functions (monomorphic per T0111, hence parameterless) are unaffected.

With that, vi_inst_params loses its Either and becomes the named list
[(VId, Maybe VExpr)], matching ports.  The -v95 flag is removed (its
substance was positional parameters plus silently DROPPING $signed
casts, and its output has not been V95-clean in years anyway -- the
shipped library uses localparam and named parameters ungated); this
relands the flag-removal intent of PRs B-Lang-org#719/B-Lang-org#400.  $signed/$unsigned
are now always emitted.  The bsc.verilog/v95 tests continue as
bsc.verilog/verilog_params, minus the flag runs.

Format bumps: .bo (ICForeign gains the names field) and .ba
(ANoInlineFun shape), both to 20260706-1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The generated Verilog stopped conforming to Verilog-1995 long before
the flag was removed, so a request for it now explains that (P0176,
EObsolete) instead of claiming the flag is unknown.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019cqQdWKp7jr73GHQYnvhiq
@nanavati
nanavati force-pushed the named-instance-params branch from 3098e47 to cf7dc26 Compare July 15, 2026 12:13
@nanavati

Copy link
Copy Markdown
Collaborator Author

Rebased onto current main (941eecf) and force-pushed (cf7dc26). What the rebase resolved: Re-applied named-parameter emission across upstream's port-splitting part 2: foreign-call VMInst port construction is now grouped per argument (iports ++ oports, instance list form) — plus the GenABin header refactor (headerBS).

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