66// SPDX-License-Identifier: MIT
77// ------------------------------------------------------------------------------
88#include " ParameterBuilder.h"
9+ #include < optional>
10+ #include < variant>
911
1012#include " slang/ast/Compilation.h"
13+ #include " slang/ast/Expression.h"
14+ #include " slang/ast/SemanticFacts.h"
1115#include " slang/ast/symbols/InstanceSymbols.h"
1216#include " slang/ast/symbols/PortSymbols.h"
17+ #include " slang/ast/types/Type.h"
18+ #include " slang/syntax/AllSyntax.h"
1319
1420namespace slang ::ast {
1521
22+ // A constraint extracted from a `$static_assert` that pins a parameter of an interface port. The
23+ // `operand` is the other side of the equality; for a value parameter it is the value expression,
24+ // and for a type parameter (`isType`) it is a `type(...)` reference to resolve into the type.
25+ struct IfaceParamConstraint {
26+ std::string_view name;
27+ const syntax::ExpressionSyntax* operand;
28+ bool isType;
29+ };
30+
31+ // Returns the parameter name if `expr` is `portName.<param>`. The access can parse either as a
32+ // member-access expression or, when the left side could be a scope, as a dotted scoped name.
33+ static std::string_view asPortParamAccess (const syntax::ExpressionSyntax& expr,
34+ std::string_view portName) {
35+ if (expr.kind == syntax::SyntaxKind::MemberAccessExpression) {
36+ auto & access = expr.as <syntax::MemberAccessExpressionSyntax>();
37+ if (access.left ->kind != syntax::SyntaxKind::IdentifierName)
38+ return {};
39+ if (access.left ->as <syntax::IdentifierNameSyntax>().identifier .valueText () != portName)
40+ return {};
41+ return access.name .valueText ();
42+ }
43+
44+ if (expr.kind == syntax::SyntaxKind::ScopedName) {
45+ auto & scoped = expr.as <syntax::ScopedNameSyntax>();
46+ if (scoped.separator .kind != parsing::TokenKind::Dot)
47+ return {};
48+ if (scoped.left ->kind != syntax::SyntaxKind::IdentifierName ||
49+ scoped.right ->kind != syntax::SyntaxKind::IdentifierName) {
50+ return {};
51+ }
52+ if (scoped.left ->as <syntax::IdentifierNameSyntax>().identifier .valueText () != portName)
53+ return {};
54+ return scoped.right ->as <syntax::IdentifierNameSyntax>().identifier .valueText ();
55+ }
56+
57+ return {};
58+ }
59+
60+ // Given a `$static_assert` condition expression, see if it pins a parameter of the given
61+ // interface port to a constant. Two shapes are recognized (with either operand on the left):
62+ // - value: `<port>.<param> == <expr>`
63+ // - type: `type(<port>.<param>) == type(<expr>)`
64+ // On a match, returns the parameter name, the other operand, and whether it is a type constraint.
65+ static std::optional<IfaceParamConstraint> matchIfaceParamConstraint (
66+ const syntax::ExpressionSyntax& condition, std::string_view portName) {
67+ if (condition.kind != syntax::SyntaxKind::EqualityExpression)
68+ return std::nullopt ;
69+
70+ auto & binExpr = condition.as <syntax::BinaryExpressionSyntax>();
71+ auto & left = *binExpr.left ;
72+ auto & right = *binExpr.right ;
73+
74+ // Value form: `port.param == <expr>`.
75+ if (auto param = asPortParamAccess (left, portName); !param.empty ())
76+ return IfaceParamConstraint{param, &right, /* isType */ false };
77+ if (auto param = asPortParamAccess (right, portName); !param.empty ())
78+ return IfaceParamConstraint{param, &left, /* isType */ false };
79+
80+ // Type form: `type(port.param) == type(<expr>)`. Both operands are `type(...)` references; the
81+ // caller resolves the non-port operand (still a `type(...)`) into the override type.
82+ auto isTypeRef = [](const syntax::ExpressionSyntax& expr) {
83+ return expr.kind == syntax::SyntaxKind::TypeReference;
84+ };
85+ auto typeRefPort = [&](const syntax::ExpressionSyntax& expr) -> std::string_view {
86+ if (!isTypeRef (expr))
87+ return {};
88+ return asPortParamAccess (*expr.as <syntax::TypeReferenceSyntax>().expr , portName);
89+ };
90+
91+ if (isTypeRef (left) && isTypeRef (right)) {
92+ if (auto param = typeRefPort (left); !param.empty ())
93+ return IfaceParamConstraint{param, &right, /* isType */ true };
94+ if (auto param = typeRefPort (right); !param.empty ())
95+ return IfaceParamConstraint{param, &left, /* isType */ true };
96+ }
97+
98+ return std::nullopt ;
99+ }
100+
101+ // Scan the body of the instantiating module for `$static_assert` constraints that pin
102+ // parameters of the given default-instantiated interface port to specific values or types,
103+ // collecting them into @a overrides. This lets shallow / top-level interface-port elaboration
104+ // honor constraints like `$static_assert(my_if.PARAM == 2)` even though the port has no real
105+ // connection providing the value.
106+ //
107+ // Only asserts written directly in the module body are considered. Asserts nested in generate
108+ // blocks are skipped: their constraints are conditional, but the override has to be applied
109+ // before elaboration (so before we know which branches are taken), and pinning a parameter from
110+ // a possibly-untaken branch would be wrong.
111+ static void collectIfaceParamConstraints (const InterfacePortSymbol& port,
112+ const syntax::ModuleDeclarationSyntax& topBody,
113+ SmallVectorBase<IfaceParamConstraint>& constraints) {
114+ auto & def = *port.interfaceDef ;
115+
116+ for (auto member : topBody.members ) {
117+ if (member->kind != syntax::SyntaxKind::ElabSystemTask)
118+ continue ;
119+
120+ auto & task = member->as <syntax::ElabSystemTaskSyntax>();
121+ if (SemanticFacts::getElabSystemTaskKind (task.name ) != ElabSystemTaskKind::StaticAssert)
122+ continue ;
123+ if (!task.arguments || task.arguments ->parameters .empty ())
124+ continue ;
125+
126+ auto firstArg = task.arguments ->parameters [0 ];
127+ if (firstArg->kind != syntax::SyntaxKind::OrderedArgument)
128+ continue ;
129+
130+ // Unwrap the property/sequence wrappers down to a plain expression.
131+ auto & propExpr = *firstArg->as <syntax::OrderedArgumentSyntax>().expr ;
132+ if (propExpr.kind != syntax::SyntaxKind::SimplePropertyExpr)
133+ continue ;
134+
135+ auto & seqExpr = *propExpr.as <syntax::SimplePropertyExprSyntax>().expr ;
136+ if (seqExpr.kind != syntax::SyntaxKind::SimpleSequenceExpr)
137+ continue ;
138+
139+ auto & simpleSeq = seqExpr.as <syntax::SimpleSequenceExprSyntax>();
140+ if (simpleSeq.repetition )
141+ continue ;
142+
143+ auto match = matchIfaceParamConstraint (*simpleSeq.expr , port.name );
144+ if (!match)
145+ continue ;
146+
147+ // Only override parameters the interface actually declares.
148+ for (auto & decl : def.parameters ) {
149+ if (decl.name == match->name ) {
150+ constraints.push_back (*match);
151+ break ;
152+ }
153+ }
154+ }
155+ }
156+
157+ // A parameter override for a default-instantiated interface port, with the value/type already
158+ // resolved in the instantiating scope. Applied directly to the parameter builder.
159+ struct IfaceParamOverride {
160+ std::string_view name;
161+ std::variant<ConstantValue, const Type*> value;
162+ };
163+
164+ // Resolves each constraint's operand in the instantiating scope (where it was written) into a
165+ // concrete value or type.
166+ static void resolveIfaceParamOverrides (std::span<const IfaceParamConstraint> constraints,
167+ const ASTContext& context,
168+ SmallVectorBase<IfaceParamOverride>& overrides) {
169+ for (auto & c : constraints) {
170+ if (c.isType ) {
171+ auto & dataType = c.operand ->as <syntax::DataTypeSyntax>();
172+ auto & type = context.getCompilation ().getType (dataType, context);
173+ if (!type.isError ())
174+ overrides.push_back ({c.name , &type});
175+ }
176+ else {
177+ auto & expr = Expression::bind (*c.operand , context);
178+ if (auto value = context.tryEval (expr))
179+ overrides.push_back ({c.name , std::move (value)});
180+ }
181+ }
182+ }
183+
16184static Symbol* recurseDefaultIfaceInst (Compilation& comp, const InterfacePortSymbol& port,
185+ std::span<const IfaceParamOverride> paramOverrides,
17186 const InstanceSymbol*& firstInst,
18187 std::span<const ConstantRange>::iterator it,
19188 std::span<const ConstantRange>::iterator end) {
@@ -22,7 +191,16 @@ static Symbol* recurseDefaultIfaceInst(Compilation& comp, const InterfacePortSym
22191 ParameterBuilder paramBuilder (*def.getParentScope (), def.name , def.parameters );
23192 if (comp.hasFlag (CompilationFlags::AllowInvalidTop))
24193 paramBuilder.setSuppressErrors (true );
25- // TODO: set parameters based on $static_assert constraints.
194+
195+ // Apply any `$static_assert`-derived overrides. The values were already resolved in the
196+ // instantiating scope, so they're applied directly without an instance context.
197+ for (auto & ov : paramOverrides) {
198+ if (auto type = std::get_if<const Type*>(&ov.value ))
199+ paramBuilder.addTypeOverride (ov.name , **type);
200+ else
201+ paramBuilder.addValueOverride (ov.name , std::get<ConstantValue>(ov.value ));
202+ }
203+
26204 auto & body = InstanceBodySymbol::fromDefinition (comp, def, port.location , paramBuilder,
27205 InstanceFlags::None);
28206
@@ -39,7 +217,7 @@ static Symbol* recurseDefaultIfaceInst(Compilation& comp, const InterfacePortSym
39217
40218 SmallVector<const Symbol*> elements;
41219 for (uint32_t i = 0 ; i < range.width (); i++) {
42- auto symbol = recurseDefaultIfaceInst (comp, port, firstInst, it, end);
220+ auto symbol = recurseDefaultIfaceInst (comp, port, paramOverrides, firstInst, it, end);
43221 symbol->name = " " ;
44222 elements.push_back (symbol);
45223 }
@@ -57,19 +235,33 @@ void InstanceSymbol::connectDefaultIfacePorts() const {
57235 SLANG_ASSERT (parent);
58236
59237 auto & comp = parent->getCompilation ();
60- ASTContext context (*parent, LookupLocation::max);
238+ ASTContext context (body, LookupLocation::max);
239+
240+ // The body of the instantiating module may contain `$static_assert` constraints that
241+ // pin interface-port parameters to specific values; we honor those when synthesizing
242+ // the default interface instances below. Ideally one day the LRM will allow
243+ // specifying these constraints in the port declaration itself.
244+ auto bodySyntax = body.getSyntax () ? body.getSyntax ()->as_if <syntax::ModuleDeclarationSyntax>()
245+ : nullptr ;
61246
62247 SmallVector<const PortConnection*> conns;
63248 for (auto port : body.getPortList ()) {
64249 if (port->kind == SymbolKind::InterfacePort) {
65250 auto & ifacePort = port->as <InterfacePortSymbol>();
66251 if (ifacePort.interfaceDef ) {
252+ SmallVector<IfaceParamConstraint> constraints;
253+ SmallVector<IfaceParamOverride> paramOverrides;
254+ if (bodySyntax) {
255+ collectIfaceParamConstraints (ifacePort, *bodySyntax, constraints);
256+ resolveIfaceParamOverrides (constraints, context, paramOverrides);
257+ }
258+
67259 Symbol* inst;
68260 const ModportSymbol* modport = nullptr ;
69261 if (auto dims = ifacePort.getDeclaredRange ()) {
70262 const InstanceSymbol* firstInst = nullptr ;
71- inst = recurseDefaultIfaceInst (comp, ifacePort, firstInst, dims-> begin () ,
72- dims->end ());
263+ inst = recurseDefaultIfaceInst (comp, ifacePort, paramOverrides, firstInst ,
264+ dims->begin (), dims-> end ());
73265
74266 if (firstInst) {
75267 auto portRange = SourceRange{port->location ,
0 commit comments