Skip to content

Commit 078433e

Browse files
committed
analysis: Fixes and improvements for ref port driver tracking
1 parent 9ba9e3d commit 078433e

File tree

4 files changed

+174
-54
lines changed

4 files changed

+174
-54
lines changed

include/slang/ast/LSPUtilities.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ class SLANG_EXPORT LSPUtilities {
192192
///
193193
/// Unlike @a visitLSPs, this method will expand indirect references such as
194194
/// modport port connections and ref port connections.
195+
///
196+
/// @param alloc The allocator to use for any AST nodes that need to be created
197+
/// to represent expanded selections.
195198
static void expandIndirectLSPs(BumpAllocator& alloc, const Expression& expr,
196199
EvalContext& evalContext, LSPCallback callback,
197200
bool isLValue = true);

source/analysis/DriverTracker.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,16 @@ void DriverTracker::propagateIndirectDrivers(AnalysisContext& context, DriverAll
144144
LSPUtilities::expandIndirectLSPs(
145145
context.alloc, *originalDriver->prefixExpression, evalCtx,
146146
[&](const ValueSymbol& symbol, const Expression& lsp, bool isLValue) {
147+
// If the LSP maps to the same symbol as the original driver,
148+
// skip it to avoid infinite recursion. This can happen only if
149+
// this is a ref port and `expandIndirectLSPs` determined that
150+
// the driver doesn't actually apply to the port due to a
151+
// non-overlapping internal connection expression.
152+
if (&symbol == item.first) {
153+
SLANG_ASSERT(symbol.isConnectedToRefPort());
154+
return;
155+
}
156+
147157
addFromLSP(context, driverAlloc, originalDriver->kind,
148158
originalDriver->flags, *originalDriver->containingSymbol, symbol,
149159
lsp, isLValue, evalCtx, hierPortDrivers);
@@ -367,20 +377,28 @@ void DriverTracker::addDriver(AnalysisContext& context, DriverAlloc& driverAlloc
367377
}
368378

369379
// Keep track of "indirect" drivers separately so we can revisit them at the end of analysis.
370-
if (symbol.kind == SymbolKind::ModportPort || symbol.isConnectedToRefPort()) {
371-
auto updater = [&](auto& item) { item.second.emplace_back(&driver, bounds); };
372-
indirectDrivers.try_emplace_and_visit(&symbol, updater, updater);
380+
auto indirectUpdater = [&](auto& item) { item.second.emplace_back(&driver, bounds); };
381+
if (symbol.kind == SymbolKind::ModportPort) {
382+
indirectDrivers.try_emplace_and_visit(&symbol, indirectUpdater, indirectUpdater);
383+
384+
// We don't do overlap detection for modports but we will still track them for downstream
385+
// users to query later.
386+
driverMap.insert(bounds, &driver, driverAlloc);
387+
return;
388+
}
389+
390+
if (symbol.isConnectedToRefPort()) {
391+
indirectDrivers.try_emplace_and_visit(&symbol, indirectUpdater, indirectUpdater);
373392

374-
if (symbol.kind != SymbolKind::ModportPort && !driver.isFromSideEffect) {
393+
if (!driver.isFromSideEffect) {
375394
// Ref port drivers are side effects that need to be applied to
376395
// non-canonical instances.
377396
hierPortDrivers.push_back({&driver, &symbol, nullptr});
378397
}
379398

380-
// We don't do overlap detection for modport / ref ports (the drivers apply to the
381-
// underlying connection) but we will still track them for downstream users to query later.
382-
driverMap.insert(bounds, &driver, driverAlloc);
383-
return;
399+
// For ref ports we will continue on and do normal overlap checking,
400+
// since the ref port might not actually apply if it has an internal
401+
// connection expression that points somewhere else.
384402
}
385403

386404
if (driverMap.empty()) {

source/ast/LSPUtilities.cpp

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include "slang/ast/LSPUtilities.h"
99

1010
#include "slang/ast/ASTVisitor.h"
11+
#include "slang/ast/EvalContext.h"
12+
#include "slang/ast/TypeProvider.h"
1113

1214
namespace slang::ast {
1315

@@ -62,17 +64,13 @@ void LSPUtilities::stringifyLSP(const Expression& expr, EvalContext& evalContext
6264
}
6365
}
6466

65-
std::optional<DriverBitRange> LSPUtilities::getBounds(const Expression& prefixExpression,
66-
EvalContext& evalContext,
67-
const Type& rootType) {
67+
static std::optional<DriverBitRange> computeBounds(SmallVector<const Expression*>& path,
68+
size_t skip, EvalContext& evalContext,
69+
const Type& rootType) {
6870
auto type = &rootType.getCanonicalType();
6971
DriverBitRange result{0, type->getSelectableWidth() - 1};
7072

71-
SmallVector<const Expression*> path;
72-
visitComponents(prefixExpression, /* includeRoot */ false,
73-
[&](const Expression& expr) { path.push_back(&expr); });
74-
75-
for (size_t i = path.size(); i > 0; i--) {
73+
for (size_t i = path.size() - skip; i > 0; i--) {
7674
uint64_t start, width;
7775
auto& elem = *path[i - 1];
7876
if (elem.kind == ExpressionKind::MemberAccess) {
@@ -112,29 +110,14 @@ std::optional<DriverBitRange> LSPUtilities::getBounds(const Expression& prefixEx
112110
return result;
113111
}
114112

115-
static Expression* translateSelector(BumpAllocator& alloc, Expression& value,
116-
const Expression& sourceSelector) {
117-
// TODO: need to translate selector indices because types may be only
118-
// equivalent and not matching.
119-
switch (sourceSelector.kind) {
120-
case ExpressionKind::ElementSelect: {
121-
auto& es = sourceSelector.as<ElementSelectExpression>();
122-
return alloc.emplace<ElementSelectExpression>(*es.type, value, es.selector(),
123-
es.sourceRange);
124-
}
125-
case ExpressionKind::RangeSelect: {
126-
auto& rs = sourceSelector.as<RangeSelectExpression>();
127-
return alloc.emplace<RangeSelectExpression>(rs.getSelectionKind(), *rs.type, value,
128-
rs.left(), rs.right(), rs.sourceRange);
129-
}
130-
case ExpressionKind::MemberAccess: {
131-
auto& ma = sourceSelector.as<MemberAccessExpression>();
132-
return alloc.emplace<MemberAccessExpression>(*ma.type, value, ma.member,
133-
ma.sourceRange);
134-
}
135-
default:
136-
SLANG_UNREACHABLE;
137-
}
113+
std::optional<DriverBitRange> LSPUtilities::getBounds(const Expression& prefixExpression,
114+
EvalContext& evalContext,
115+
const Type& rootType) {
116+
SmallVector<const Expression*> path;
117+
visitComponents(prefixExpression, /* includeRoot */ false,
118+
[&](const Expression& expr) { path.push_back(&expr); });
119+
120+
return computeBounds(path, 0, evalContext, rootType);
138121
}
139122

140123
static bool expandRefPortConn(BumpAllocator& alloc, EvalContext& evalContext, const Expression& lsp,
@@ -180,10 +163,61 @@ static bool expandRefPortConn(BumpAllocator& alloc, EvalContext& evalContext, co
180163
// glue the uncovered portion of the LSP onto the external connection.
181164
// The const_cast here is okay because we never mutate anything during analysis.
182165
auto newExpr = const_cast<Expression*>(&externalConn);
183-
for (size_t i = elemsToRemove; i < lspPath.size(); i++) {
184-
auto elem = lspPath[lspPath.size() - 1 - i];
185-
newExpr = translateSelector(alloc, *newExpr, *elem);
166+
167+
// First, replicate all of the selects for unpacked types. The only way that
168+
// types can mismatch here is for fixed size arrays, which can have differing
169+
// ranges, so translate those along the way.
170+
for (; elemsToRemove < lspPath.size(); elemsToRemove++) {
171+
auto& ct = newExpr->type->getCanonicalType();
172+
if (ct.isIntegral())
173+
break;
174+
175+
auto elem = lspPath[lspPath.size() - 1 - elemsToRemove];
176+
if (elem->kind == ExpressionKind::MemberAccess) {
177+
auto& ma = elem->as<MemberAccessExpression>();
178+
newExpr = alloc.emplace<MemberAccessExpression>(*ma.type, *newExpr, ma.member,
179+
ma.sourceRange);
180+
continue;
181+
}
182+
183+
auto targetDim = ct.getFixedRange();
184+
auto translateIndex = [&](int32_t index) {
185+
if (targetDim.isLittleEndian())
186+
return targetDim.upper() - index;
187+
else
188+
return targetDim.lower() + index;
189+
};
190+
191+
auto selection = elem->evalSelector(evalContext, /* enforceBounds */ true);
192+
SLANG_ASSERT(selection);
193+
194+
selection->left = translateIndex(selection->left);
195+
selection->right = translateIndex(selection->right);
196+
197+
if (elem->kind == ExpressionKind::ElementSelect) {
198+
newExpr = &ElementSelectExpression::fromConstant(alloc, *newExpr,
199+
selection->lower(),
200+
evalContext.astCtx);
201+
}
202+
else {
203+
newExpr = &RangeSelectExpression::fromConstant(alloc, *newExpr, *selection,
204+
evalContext.astCtx);
205+
}
186206
}
207+
208+
// For remaining integral path components, compute the bounds and then
209+
// recreate a corresponding select tree that achieves those same bounds.
210+
if (elemsToRemove < lspPath.size()) {
211+
auto bounds = computeBounds(lspPath, elemsToRemove, evalContext, *newExpr->type);
212+
SLANG_ASSERT(bounds);
213+
214+
// Note that this can't overflow here because it's a packed type
215+
// so the total width is bounded.
216+
ConstantRange range{int32_t(bounds->second), int32_t(bounds->first)};
217+
newExpr = &Expression::buildPackedSelectTree(alloc, *newExpr, range,
218+
evalContext.astCtx);
219+
}
220+
187221
LSPUtilities::visitLSPs(*newExpr, evalContext, callback, isLValue);
188222
}
189223
return true;
@@ -268,8 +302,7 @@ void LSPUtilities::expandIndirectLSP(BumpAllocator& alloc, EvalContext& evalCont
268302

269303
if (!anyRefPorts) {
270304
// No ref ports or modports involved, so just invoke the callback directly.
271-
// TODO:
272-
// callback(symbol, lsp, isLValue);
305+
callback(symbol, lsp, isLValue);
273306
}
274307
}
275308

tests/unittests/analysis/MultiAssignTests.cpp

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,12 +1217,11 @@ endmodule
12171217

12181218
TEST_CASE("Multi assign through ref ports 2") {
12191219
auto& code = R"(
1220-
// module r({a, b});
1221-
// ref logic a;
1222-
// output logic b;
1223-
1224-
// assign a = 1;
1225-
// endmodule
1220+
module r({a, b});
1221+
ref logic a;
1222+
output logic b;
1223+
assign a = 1;
1224+
endmodule
12261225
12271226
module v(ref .a(foo.b));
12281227
struct { logic a; logic b; } foo;
@@ -1232,12 +1231,26 @@ endmodule
12321231
module w(ref .a(foo[0][1]));
12331232
logic [1:0] foo[2][3];
12341233
assign foo[0][1][0] = 1;
1234+
assign foo[0][1][1] = 1;
1235+
1236+
assign foo[1][0] = 1;
1237+
assign foo[1][0][1] = 1;
1238+
endmodule
1239+
1240+
module x(ref .a(foo), .b(foo));
1241+
logic foo;
1242+
assign foo = 1;
1243+
endmodule
1244+
1245+
module y(ref .a(foo[0]));
1246+
logic [5:3] foo[2];
1247+
assign foo[0][5] = 1;
12351248
endmodule
12361249
12371250
module top;
1238-
// logic [1:0] q;
1239-
// r r1(q);
1240-
// assign q[1] = 1;
1251+
logic [1:0] q;
1252+
r r1(q);
1253+
assign q[1] = 1;
12411254
12421255
logic s [1:0][3:1];
12431256
v v1(s[1][1]);
@@ -1246,14 +1259,67 @@ module top;
12461259
logic [1:0] t [1:0][3:1];
12471260
w w1(t[1][1]);
12481261
assign t[1][1][0] = 1;
1262+
1263+
logic u, v;
1264+
x x1(u, v);
1265+
assign {u, v} = 1;
1266+
1267+
logic [2:4] z;
1268+
y y1(z);
1269+
assign z[2:3] = 1;
12491270
endmodule
12501271
)";
12511272

12521273
Compilation compilation;
12531274
AnalysisManager analysisManager;
12541275

12551276
auto [diags, design] = analyze(code, compilation, analysisManager);
1256-
REQUIRE(diags.size() == 2);
1277+
REQUIRE(diags.size() == 6);
1278+
CHECK(diags[0].code == diag::MultipleContAssigns);
1279+
CHECK(diags[1].code == diag::MultipleContAssigns);
1280+
CHECK(diags[2].code == diag::MultipleContAssigns);
1281+
CHECK(diags[3].code == diag::MultipleContAssigns);
1282+
CHECK(diags[4].code == diag::MultipleContAssigns);
1283+
CHECK(diags[5].code == diag::MultipleContAssigns);
1284+
}
1285+
1286+
TEST_CASE("Multi assign through ref ports 3") {
1287+
auto& code = R"(
1288+
module y(ref .a(foo));
1289+
logic [5:3] foo;
1290+
assign foo[3] = 1;
1291+
endmodule
1292+
1293+
module b(ref logic[7:0] a [2:6]);
1294+
assign a[3][5:4] = 1;
1295+
endmodule
1296+
1297+
typedef struct { logic a[2:8]; } bar_t;
1298+
module c(ref bar_t a);
1299+
assign a.a[4:5] = '{1, 0};
1300+
endmodule
1301+
1302+
module top;
1303+
logic [2:4] z;
1304+
y y1(z);
1305+
assign z[4] = 1;
1306+
1307+
struct packed { logic a; logic b; } [1:4] a [5:1];
1308+
b b1(a);
1309+
assign a[4][2].b = 1;
1310+
1311+
bar_t d;
1312+
c c1(d);
1313+
assign d.a[4:5] = '{0, 1};
1314+
endmodule
1315+
)";
1316+
1317+
Compilation compilation;
1318+
AnalysisManager analysisManager;
1319+
1320+
auto [diags, design] = analyze(code, compilation, analysisManager);
1321+
REQUIRE(diags.size() == 3);
12571322
CHECK(diags[0].code == diag::MultipleContAssigns);
12581323
CHECK(diags[1].code == diag::MultipleContAssigns);
1324+
CHECK(diags[2].code == diag::MultipleContAssigns);
12591325
}

0 commit comments

Comments
 (0)