Problem
Grounded directly in a real gap surfaced applying RFC-027 to a real design: which of a board's inst-declared capacitors should carry #[bypass(...)]? The clean, semantically-correct answer for a decoupling capacitor instantiated via a reusable fn — the common, idiomatic pattern RFC-006 exists to support —
pub fn decouple(vdd: Pin, gnd: Pin) {
inst c: C_100n
net _: vdd, c.A
net _: gnd, c.B
}
cannot be expressed today. RFC-027's #[bypass(INST.PIN, CAPACITANCE)] requires a literal INST.PIN reference — an already-declared top-level instance name plus one of its pins. Inside decouple's body, the bypassed pin is the Pin-typed parameter vdd, not a named top-level instance's pin — there is no INST.PIN an author can write. The only way to carry the attribute today would be to inline all 25 real call sites into 25 hand-written capacitor instances, destroying exactly the abstraction RFC-006 exists to provide — a real, disclosed regression, not a hypothetical one.
Confirmed against real source (src/check/expand.rs): resolve_pin_ref — the existing, single mechanism that resolves any bare PinRef — already checks scope.bindings first and resolves a fn parameter of type Pin through Binding::Pin((String, String)), a real (instance path, logical pin name) pair populated per call site (bindings.insert(param.name.name.clone(), Binding::Pin(target)), confirmed at the call-expansion site). This is exactly the same resolution path RFC-027's own #[bypass(...)], #[crystal_oscillator(...)], and #[switching_converter(...)] target arguments already use for a top-level INST.PIN/INST reference — the gap is not that resolution is impossible, but that RFC-027's own design (written before this case was considered) implicitly assumed its target arguments would only ever appear at the design's top level, never inside a fn body.
Goals
- Let
#[bypass(...)]'s (and every other RFC-027 attribute's instance/pin-reference argument) target afn's ownPin-typed parameter, not only a top-levelINST.PIN, so a decouplingfn's own internal capacitor can carry the attribute. - Each real call site of the
fnproduces its own real, individually-resolved CSV row — the bypassed pin resolves to whatever concrete(instance, pin)that specific call site actually binds, exactly as if thefn's body had been hand-inlined at that call site (RFC-006's own expansion discipline, applied here to attribute-argument resolution rather than toinst/netstatements). - Extend all three RFC-027 attributes whose target argument is a pin/instance reference (
#[bypass],#[crystal_oscillator],#[switching_converter]) uniformly, not#[bypass]alone — the same underlying gap (afn-internal reference that RFC-027 didn't anticipate) applies identically to all three.
Non-goals
- Not a new resolution mechanism.
resolve_pin_refandBinding::Pinalready exist and already do exactly this job for every other barePinRefin the language (net members,place/decouplearguments, per RFC-006's own nested-call design). This RFC does not add a new binding/resolution concept — it only extends which grammatical positions RFC-027's attribute-argument parser is willing to accept a bare (unqualified)PinRefin, and confirms that the checker calls the same existingresolve_pin_reffunction for that position it already calls for every other pin reference. - Not new semantics for an attribute inside a
fnbody vs. at the top level.#[bypass(vdd, 100nF)]insidedecouple's body means exactly what#[bypass(mcu.VDD, 100nF)]would mean if hand-written at a top-levelinst c: C_100nline targeting the same pin — the attribute's meaning is unchanged; only the argument's resolution path differs (through a call-site binding instead of a literal top-level name), and that resolution happens once per real call site, at expansion. - Not extending
#[ground(...)],#[high_current(...)], or#[impedance(...)]. These three attach to anet, not an instance/pin reference — anet's own name is already resolvable identically whether declared inside afnbody or at the top level (RFC-006's existing per-call-site net-naming scheme, unchanged since RFC-024's DR-030). There is no analogous gap for these three; this RFC does not touch them. - Not solving the general "attribute argument grammar" question for hypothetical future attributes. This RFC extends exactly the three RFC-027 attributes whose argument shape is a bare pin/instance reference, closing a real, concrete gap RFC-027 itself left open — not a speculative general mechanism for arbitrary future attribute argument kinds.
Design
pub fn decouple(vdd: Pin, gnd: Pin) {
#[bypass(vdd, 100nF)]
inst c: C_100n
net _: vdd, c.A
net _: gnd, c.B
}
design Board {
inst mcu: MCU_ESP32S3
// ... real call sites, one per bypassed pin ...
decouple(mcu.VDD1, mcu.GND)
decouple(mcu.VDD2, mcu.GND)
// ... 23 more ...
}
#[bypass(TARGET, CAPACITANCE)]'sTARGETargument (previouslyINST.PINonly) now also accepts a barePin-typedfnparameter name — the exact samePinRefgrammar the language already has (an identifier, optionally followed by.pin), simply used with the.pinpart omitted, which is already validPinRefsyntax everywhere else in the language (e.g. inside anetmember list:net _: vdd, c.A—vddthere is already a bare, unqualifiedPinRef).- The same extension applies identically to
#[crystal_oscillator(PARENT_INST, PIN_1, PIN_2)]'s three arguments and to#[switching_converter(inductor: INST, ...)]'s instance arguments, wherever the referenced value is itself aPin-typedfnparameter rather than a top-level instance name. - Checking reuses
resolve_pin_ref(or the existing instance-argument equivalent for#[switching_converter]'s whole-instance arguments) exactly as-is — no new function, no new binding table. The checker for these three attributes' target arguments is corrected to call the same resolver every other pin/instance reference in the language already calls, rather than assuming a top-level-only name. - At expansion (RFC-006's existing per-call-site inlining pass), each real call to a
fncontaining an attribute-bearinginstproduces its own real, fully-resolved attribute instance — the attribute's target resolves to that call site's actual bound(instance, pin), exactly as RFC-006 already does for everyinst/netstatement inside a calledfn's body. Afncalled 25 times with an attribute inside it produces 25 real, independently-resolved attribute facts, one per call site — never one shared/ambiguous fact for the wholefndefinition.
Type-system-first test
Not a rule/DRC proposal — the check is the exact same reference-resolution check RFC-027 already specified for these three attributes' target arguments, now simply applied at the correct, already-existing resolution function (resolve_pin_ref) rather than a narrower top-level-only lookup. No new check is introduced; an unresolvable reference (e.g. a plain identifier that is neither a top-level instance name nor a bound fn parameter in scope) is still a compile error naming what wasn't found — unchanged in kind from RFC-027's own diagnostic.
Conceptual impact
Low. No new core concept, no new binding mechanism, no new grammar token. This RFC corrects a real, narrow oversight in RFC-027's own target-argument checker — extending which existing, already-resolvable reference forms (a bare Pin-typed fn parameter, which the language's PinRef grammar and resolve_pin_ref function already fully support everywhere else) three specific attributes are willing to accept.
Coherence matrix row
| Concepts | Grammar | Oracle | Diagnostics | Netlist | Compat | Trust |
|---|---|---|---|---|---|---|
| Low | Low | Low | Low | Low | Low | High |
Grammar (Low): no new syntax — a bare PinRef (identifier, no .pin) is already valid grammar everywhere in the language; this RFC only widens which attribute-argument positions accept it. Netlist (Low): the eight CSV files RFC-027 already specified are unaffected in shape — this RFC only affects how many rows are produced and what each row's target-reference field resolves to per real call site; no new file, no new column. Trust (High): each attribute instance, after resolution, is exactly as real and individually-checked as a hand-written top-level one — RFC-006's own per-call-site expansion discipline is reused unchanged, not weakened. Compat (Low): purely additive; every existing top-level #[bypass(INST.PIN, ...)]/#[crystal_oscillator(...)]/#[switching_converter(...)] attribute (written before this RFC) is unaffected, unchanged in meaning.
Gradeability
- Reference resolution for these three attributes' target arguments now runs through
resolve_pin_ref/the existing instance-resolution equivalent, at the same point (per real call site, during RFC-006's expansion pass) every otherfn-body pin/instance reference is already resolved. - An unresolvable reference (an identifier that is neither a bound
fnparameter nor a top-level instance in scope) remains a compile error, unchanged in kind and code from RFC-027. - No new residual-DRC surface — see Type-system-first test above.
AI-generatability
High. A model that already understands fn parameter binding (RFC-006) and #[bypass(...)]'s shape (RFC-027) needs to learn exactly one fact: the same bare-identifier form already legal inside a net member list (net _: vdd, c.A) is equally legal as an RFC-027 attribute's target argument. No new syntax to memorize, no special case distinguishing "top-level bypass" from "fn-internal bypass" beyond which name happens to be in scope.
Alternatives
- Require flattening every
fncall containing a bypass-worthy capacitor into a hand-written top-levelinst— this is the real, rejected status quo this RFC exists to fix: it destroys RFC-006's entire reason for existing (reusable, parameterized sub-circuits) for the sole purpose of attaching one attribute, a real and disproportionate cost. - Leave
fn-internal bypass capacitors permanently un-annotatable, relying solely on Quilter's own auto-detection for these — this was the interim, honestly-disclosed position before this RFC (RFC-027's own Non-goals already frame omission as a designed-for, not broken, state: an author who omits the attribute gets Quilter's real auto-detection, which runs on the plain netlist regardless). This RFC is not required for CoHDL to function — Quilter's detector already handles the unannotated case correctly, as the real suppliedbypass_capacitors.csv(25 realC→U2rows, auto-detected) demonstrates. This RFC is accepted because it closes a real, avoidable gap between "what an author can state explicitly" and "what is semantically true," at essentially zero marginal mechanism cost (the resolver already exists) — not because the unannotated state was ever broken. - A new, separate
fn-scoped attribute-declaration mechanism, distinct from RFC-027's existing attributes — rejected: the existing attributes' grammar (a barePinRef/instance-name argument) already covers this case once the checker calls the resolver that already exists; inventing a parallel mechanism would duplicate machinery for no benefit, the same "prefer extending an existing concept" discipline this project applies throughout (e.g. RFC-016's DR-022 rationale). - Extend all seven RFC-027 attributes uniformly, including the three net-attached ones — considered and rejected:
#[ground]/#[high_current]/#[impedance]attach to anet, and anet's own name already resolves identically per call site whether declared inside afnbody or at the top level (unchanged since RFC-024/RFC-006) — there is no analogous unresolvable-reference gap for these three, so extending them would be addressing a problem that doesn't exist for that subset.
Compatibility
Purely additive. Every existing #[bypass(INST.PIN, ...)], #[crystal_oscillator(INST, PIN_1, PIN_2)], and #[switching_converter(inductor: INST, ...)] attribute at the design top level is completely unaffected, unchanged in meaning and in every emitted CSV byte.
Depends on: RFC-027 (Quilter physics-constraint hints, already Accepted) — this RFC extends three of its seven attributes' argument-resolution rules. Reuses RFC-006's existing per-call-site expansion and Binding/resolve_pin_ref machinery unchanged — no new dependency.
Tooling & operations
cohdl build's CSV emission forbypass_capacitors.csv/crystal_oscillators.csv/switching_converters.csvis unchanged in format — each real call site simply now may contribute a real row where before it silently could not carry the attribute at all.cohdl fmtrequires no new rule — the attribute's canonical single-line form is unchanged; only the identifier appearing inside its argument list may now be afnparameter name instead of a top-level instance name, whichfmtalready renders identically (both are just identifiers).- No new error-code sub-case beyond what RFC-027 already reserved (unresolved pin/instance reference) — the same diagnostic now correctly fires (or doesn't) for a
fn-parameter reference the same way it already does for a top-level one.
Teaching cost
Very low. An author who already knows fn parameters can be referenced as bare pins inside a net member list (RFC-006's own established pattern) needs no new mental model — the exact same reference form is now also legal as one of these three attributes' target arguments.
Failure modes
- An author writes
#[bypass(vdd, 100nF)]outside anyfnbody, wherevddisn't a bound parameter or a real top-level instance — caught immediately as an unresolved reference, the same diagnostic RFC-027 already specifies. - An author expects one shared CSV row for a
fndefinition rather than one row per call site — incorrect; every real call site that binds the attribute-bearing parameter produces its own independent row, exactly mirroring RFC-006's existing "expansion produces what hand-writing would have" discipline. - An author tries to extend this pattern to
#[ground]/#[high_current]/#[impedance]— these three are unaffected by this RFC (see Non-goals); anet's name was never the gap.
Migration path
No existing design requires migration — this is purely additive new-reference-form support. A real, optional, non-mechanical follow-up: any existing decouple-style fn whose internal capacitor is a genuine bypass cap can now add #[bypass(vdd, 100nF)] (or the equivalent for #[crystal_oscillator]/#[switching_converter]) to its own body — genuine, disclosed authoring work, not required by this RFC's completion bar.
Decision
Accepted — 2026-07-20. #[bypass(...)], #[crystal_oscillator(...)], and #[switching_converter(...)] (all RFC-027) now accept a bare Pin-typed fn parameter as a target/instance argument, in addition to a literal top-level INST.PIN/INST reference — reusing the existing resolve_pin_ref/Binding::Pin resolution machinery (RFC-006) unchanged, with zero new grammar or resolution concept. Each real call site of an attribute-bearing fn produces its own independently-resolved CSV row, exactly mirroring RFC-006's existing per-call-site expansion discipline. #[ground]/#[high_current]/#[impedance] are unaffected — they attach to a net, which has no analogous gap. Recorded as DR-034 (see note 7). Language Specification (note 10) updates the "Quilter physics-constraint hints" section in place to document the extended target-argument grammar for these three attributes.