Skip to content

Commit ae8a0a6

Browse files
committed
[ImportVerilog] Support use-before-declare across module bodies
Predeclare module-scope variables, nets, interface instances, and module instances when slang is allowed to resolve names before their declarations. The predeclaration walk recurses through generated scopes, creates storage before expanding interfaces, and instantiates modules before earlier procedural code lowers, so forward references through local names, interface members, and hierarchical instance paths can resolve. Declaration initializers and net declaration assignments are lowered after the module body has populated the value map, then attached back to the original Moore declaration ops. This keeps the compatibility behavior behind --allow-use-before-declare and preserves existing diagnostics when the flag is absent. Add focused regression coverage for direct module declarations, generated scopes, interface instances, and module instances, plus broader expression/procedural compatibility matrices for common testbench patterns.
1 parent 926f5ea commit ae8a0a6

9 files changed

Lines changed: 2114 additions & 1 deletion

lib/Conversion/ImportVerilog/ImportVerilogInternals.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,11 @@ struct Context {
426426
/// because ScopedHashTable stores values by copy.
427427
SmallVector<std::unique_ptr<InterfaceLowering>> interfaceInstanceStorage;
428428

429+
/// Module instances already emitted by the `--allow-use-before-declare`
430+
/// predeclaration pass. These are skipped during the later source-order
431+
/// module-body walk to avoid emitting duplicate instances.
432+
DenseSet<const slang::ast::InstanceSymbol *> predeclaredInstances;
433+
429434
/// Cached virtual interface layouts (type + field order).
430435
DenseMap<const slang::ast::InstanceBodySymbol *, VirtualInterfaceLowering>
431436
virtualIfaceLowerings;

lib/Conversion/ImportVerilog/Structure.cpp

Lines changed: 310 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "ImportVerilogInternals.h"
1010
#include "slang/ast/Compilation.h"
1111
#include "slang/ast/symbols/ClassSymbols.h"
12+
#include "llvm/ADT/STLFunctionalExtras.h"
1213
#include "llvm/ADT/ScopeExit.h"
1314

1415
using namespace circt;
@@ -407,11 +408,19 @@ struct ModuleVisitor : public BaseVisitor {
407408
using slang::ast::MultiPortSymbol;
408409
using slang::ast::PortSymbol;
409410

411+
if (context.options.allowUseBeforeDeclare.value_or(false))
412+
if (context.predeclaredInstances.contains(&instNode))
413+
return success();
414+
410415
// Interface instances are expanded inline into individual variable/net ops
411416
// rather than creating a moore.instance op.
412417
auto defKind = instNode.body.getDefinition().definitionKind;
413-
if (defKind == slang::ast::DefinitionKind::Interface)
418+
if (defKind == slang::ast::DefinitionKind::Interface) {
419+
if (context.options.allowUseBeforeDeclare.value_or(false))
420+
if (context.interfaceInstances.lookup(&instNode))
421+
return success();
414422
return expandInterfaceInstance(instNode);
423+
}
415424

416425
auto *moduleLowering = context.convertModuleHeader(&instNode.body);
417426
if (!moduleLowering)
@@ -700,6 +709,10 @@ struct ModuleVisitor : public BaseVisitor {
700709

701710
// Handle variables.
702711
LogicalResult visit(const slang::ast::VariableSymbol &varNode) {
712+
if (context.options.allowUseBeforeDeclare.value_or(false))
713+
if (context.valueSymbols.lookup(&varNode))
714+
return success();
715+
703716
auto loweredType = context.convertType(*varNode.getDeclaredType());
704717
if (!loweredType)
705718
return failure();
@@ -725,6 +738,10 @@ struct ModuleVisitor : public BaseVisitor {
725738

726739
// Handle nets.
727740
LogicalResult visit(const slang::ast::NetSymbol &netNode) {
741+
if (context.options.allowUseBeforeDeclare.value_or(false))
742+
if (context.valueSymbols.lookup(&netNode))
743+
return success();
744+
728745
auto loweredType = context.convertType(*netNode.getDeclaredType());
729746
if (!loweredType)
730747
return failure();
@@ -1356,8 +1373,280 @@ Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
13561373
}
13571374
}
13581375

1376+
const bool allowUseBeforeDeclare =
1377+
options.allowUseBeforeDeclare.value_or(false);
1378+
SmallVector<std::pair<moore::VariableOp, const slang::ast::Expression *>>
1379+
pendingVariableInitializers;
1380+
SmallVector<std::pair<moore::NetOp, const slang::ast::Expression *>>
1381+
pendingNetDeclarationAssignments;
1382+
1383+
struct ModulePredeclaration {
1384+
Context &context;
1385+
OpBuilder &builder;
1386+
SmallVector<std::pair<moore::VariableOp, const slang::ast::Expression *>>
1387+
&pendingVariableInitializers;
1388+
SmallVector<std::pair<moore::NetOp, const slang::ast::Expression *>>
1389+
&pendingNetDeclarationAssignments;
1390+
1391+
LogicalResult declareVariable(const slang::ast::VariableSymbol &varNode,
1392+
Location loc, StringRef blockNamePrefix) {
1393+
auto loweredType = context.convertType(*varNode.getDeclaredType());
1394+
if (!loweredType)
1395+
return failure();
1396+
1397+
auto varOp = moore::VariableOp::create(
1398+
builder, loc,
1399+
moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
1400+
builder.getStringAttr(Twine(blockNamePrefix) + varNode.name),
1401+
Value{});
1402+
context.valueSymbols.insert(&varNode, varOp);
1403+
1404+
const auto &canonTy = varNode.getType().getCanonicalType();
1405+
if (const auto *vi = canonTy.as_if<slang::ast::VirtualInterfaceType>())
1406+
if (failed(context.registerVirtualInterfaceMembers(varNode, *vi, loc)))
1407+
return failure();
1408+
1409+
if (const auto *init = varNode.getInitializer())
1410+
pendingVariableInitializers.push_back({varOp, init});
1411+
return success();
1412+
}
1413+
1414+
LogicalResult declareNet(const slang::ast::NetSymbol &netNode, Location loc,
1415+
StringRef blockNamePrefix) {
1416+
auto loweredType = context.convertType(*netNode.getDeclaredType());
1417+
if (!loweredType)
1418+
return failure();
1419+
1420+
auto netkind = convertNetKind(netNode.netType.netKind);
1421+
if (netkind == moore::NetKind::Interconnect ||
1422+
netkind == moore::NetKind::UserDefined ||
1423+
netkind == moore::NetKind::Unknown)
1424+
return mlir::emitError(loc, "unsupported net kind `")
1425+
<< netNode.netType.name << "`";
1426+
1427+
auto netOp = moore::NetOp::create(
1428+
builder, loc,
1429+
moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
1430+
builder.getStringAttr(Twine(blockNamePrefix) + netNode.name), netkind,
1431+
Value{});
1432+
context.valueSymbols.insert(&netNode, netOp);
1433+
1434+
if (const auto *init = netNode.getInitializer())
1435+
pendingNetDeclarationAssignments.push_back({netOp, init});
1436+
return success();
1437+
}
1438+
1439+
SmallString<64>
1440+
getGenerateBlockPrefix(const slang::ast::GenerateBlockSymbol &genNode,
1441+
StringRef blockNamePrefix) {
1442+
SmallString<64> prefix = blockNamePrefix;
1443+
if (!genNode.name.empty() ||
1444+
genNode.getParentScope()->asSymbol().kind !=
1445+
slang::ast::SymbolKind::GenerateBlockArray) {
1446+
prefix += genNode.getExternalName();
1447+
prefix += '.';
1448+
}
1449+
return prefix;
1450+
}
1451+
1452+
LogicalResult predeclareStorageGenerateBlock(
1453+
const slang::ast::GenerateBlockSymbol &genNode,
1454+
StringRef blockNamePrefix) {
1455+
if (genNode.isUninstantiated)
1456+
return success();
1457+
return predeclareStorageScope(
1458+
genNode, getGenerateBlockPrefix(genNode, blockNamePrefix));
1459+
}
1460+
1461+
LogicalResult predeclareInterfaceGenerateBlock(
1462+
const slang::ast::GenerateBlockSymbol &genNode,
1463+
StringRef blockNamePrefix) {
1464+
if (genNode.isUninstantiated)
1465+
return success();
1466+
return predeclareInterfaceScope(
1467+
genNode, getGenerateBlockPrefix(genNode, blockNamePrefix));
1468+
}
1469+
1470+
LogicalResult predeclareModuleInstanceGenerateBlock(
1471+
const slang::ast::GenerateBlockSymbol &genNode,
1472+
StringRef blockNamePrefix) {
1473+
if (genNode.isUninstantiated)
1474+
return success();
1475+
return predeclareModuleInstanceScope(
1476+
genNode, getGenerateBlockPrefix(genNode, blockNamePrefix));
1477+
}
1478+
1479+
LogicalResult predeclareGenerateBlockArray(
1480+
const slang::ast::GenerateBlockArraySymbol &genArrNode,
1481+
StringRef blockNamePrefix,
1482+
llvm::function_ref<
1483+
LogicalResult(const slang::ast::GenerateBlockSymbol &, StringRef)>
1484+
predeclareBlock) {
1485+
SmallString<64> prefix = blockNamePrefix;
1486+
prefix += genArrNode.getExternalName();
1487+
prefix += '_';
1488+
auto prefixBaseLen = prefix.size();
1489+
1490+
for (const auto *entry : genArrNode.entries) {
1491+
prefix.resize(prefixBaseLen);
1492+
if (entry->arrayIndex)
1493+
prefix += entry->arrayIndex->toString();
1494+
else
1495+
Twine(entry->constructIndex).toVector(prefix);
1496+
prefix += '.';
1497+
1498+
if (failed(predeclareBlock(*entry, prefix)))
1499+
return failure();
1500+
}
1501+
return success();
1502+
}
1503+
1504+
LogicalResult predeclareStorageMember(const slang::ast::Symbol &member,
1505+
StringRef blockNamePrefix) {
1506+
auto loc = context.convertLocation(member.location);
1507+
if (const auto *varNode = member.as_if<slang::ast::VariableSymbol>())
1508+
return declareVariable(*varNode, loc, blockNamePrefix);
1509+
1510+
if (const auto *netNode = member.as_if<slang::ast::NetSymbol>())
1511+
return declareNet(*netNode, loc, blockNamePrefix);
1512+
1513+
if (const auto *genNode = member.as_if<slang::ast::GenerateBlockSymbol>())
1514+
return predeclareStorageGenerateBlock(*genNode, blockNamePrefix);
1515+
1516+
if (const auto *genArrNode =
1517+
member.as_if<slang::ast::GenerateBlockArraySymbol>())
1518+
return predeclareGenerateBlockArray(
1519+
*genArrNode, blockNamePrefix,
1520+
[&](const slang::ast::GenerateBlockSymbol &gen, StringRef prefix) {
1521+
return predeclareStorageGenerateBlock(gen, prefix);
1522+
});
1523+
1524+
return success();
1525+
}
1526+
1527+
LogicalResult predeclareInterfaceMember(const slang::ast::Symbol &member,
1528+
StringRef blockNamePrefix) {
1529+
auto loc = context.convertLocation(member.location);
1530+
if (const auto *instNode = member.as_if<slang::ast::InstanceSymbol>()) {
1531+
if (instNode->body.getDefinition().definitionKind ==
1532+
slang::ast::DefinitionKind::Interface)
1533+
return ModuleVisitor(context, loc, blockNamePrefix)
1534+
.expandInterfaceInstance(*instNode);
1535+
return success();
1536+
}
1537+
1538+
if (const auto *genNode = member.as_if<slang::ast::GenerateBlockSymbol>())
1539+
return predeclareInterfaceGenerateBlock(*genNode, blockNamePrefix);
1540+
1541+
if (const auto *genArrNode =
1542+
member.as_if<slang::ast::GenerateBlockArraySymbol>())
1543+
return predeclareGenerateBlockArray(
1544+
*genArrNode, blockNamePrefix,
1545+
[&](const slang::ast::GenerateBlockSymbol &gen, StringRef prefix) {
1546+
return predeclareInterfaceGenerateBlock(gen, prefix);
1547+
});
1548+
1549+
return success();
1550+
}
1551+
1552+
LogicalResult
1553+
predeclareModuleInstanceMember(const slang::ast::Symbol &member,
1554+
StringRef blockNamePrefix) {
1555+
auto loc = context.convertLocation(member.location);
1556+
if (const auto *instNode = member.as_if<slang::ast::InstanceSymbol>()) {
1557+
if (instNode->body.getDefinition().definitionKind !=
1558+
slang::ast::DefinitionKind::Interface) {
1559+
if (failed(ModuleVisitor(context, loc, blockNamePrefix)
1560+
.visit(*instNode)))
1561+
return failure();
1562+
context.predeclaredInstances.insert(instNode);
1563+
}
1564+
return success();
1565+
}
1566+
1567+
if (const auto *genNode = member.as_if<slang::ast::GenerateBlockSymbol>())
1568+
return predeclareModuleInstanceGenerateBlock(*genNode, blockNamePrefix);
1569+
1570+
if (const auto *genArrNode =
1571+
member.as_if<slang::ast::GenerateBlockArraySymbol>())
1572+
return predeclareGenerateBlockArray(
1573+
*genArrNode, blockNamePrefix,
1574+
[&](const slang::ast::GenerateBlockSymbol &gen, StringRef prefix) {
1575+
return predeclareModuleInstanceGenerateBlock(gen, prefix);
1576+
});
1577+
1578+
return success();
1579+
}
1580+
1581+
LogicalResult predeclareStorageScope(const slang::ast::Scope &scope,
1582+
StringRef blockNamePrefix) {
1583+
for (auto &member : scope.members())
1584+
if (failed(predeclareStorageMember(member, blockNamePrefix)))
1585+
return failure();
1586+
return success();
1587+
}
1588+
1589+
LogicalResult predeclareInterfaceScope(const slang::ast::Scope &scope,
1590+
StringRef blockNamePrefix) {
1591+
for (auto &member : scope.members())
1592+
if (failed(predeclareInterfaceMember(member, blockNamePrefix)))
1593+
return failure();
1594+
return success();
1595+
}
1596+
1597+
LogicalResult predeclareModuleInstanceScope(const slang::ast::Scope &scope,
1598+
StringRef blockNamePrefix) {
1599+
for (auto &member : scope.members())
1600+
if (failed(predeclareModuleInstanceMember(member, blockNamePrefix)))
1601+
return failure();
1602+
return success();
1603+
}
1604+
1605+
LogicalResult predeclareScope(const slang::ast::Scope &scope,
1606+
StringRef blockNamePrefix) {
1607+
// First create variables and nets for the whole generated scope tree so
1608+
// later phases can bind port connections or hierarchical references to
1609+
// declarations that appear later in source.
1610+
if (failed(predeclareStorageScope(scope, blockNamePrefix)))
1611+
return failure();
1612+
1613+
// Then expand interface instances. Interface expansion may lower
1614+
// continuous assignments or procedures from the interface body, so all
1615+
// storage symbols must already be available.
1616+
if (failed(predeclareInterfaceScope(scope, blockNamePrefix)))
1617+
return failure();
1618+
1619+
// Finally instantiate modules. This makes later hierarchical references
1620+
// to instance internals available before earlier procedural blocks lower.
1621+
return predeclareModuleInstanceScope(scope, blockNamePrefix);
1622+
}
1623+
};
1624+
1625+
// Slang can resolve module-scope references to declarations that appear later
1626+
// in the source when `--allow-use-before-declare` is enabled. ImportVerilog
1627+
// still has to create Moore declarations before lowering any expression that
1628+
// may refer to them. Walk generated scopes as well as the direct module body,
1629+
// and pre-expand interface instances so earlier hierarchical references like
1630+
// `bus.sig` can find the expanded member values. Since Moore modules are
1631+
// graph regions, initializer operands can be attached after all declarations
1632+
// have been registered.
1633+
if (allowUseBeforeDeclare) {
1634+
ModulePredeclaration predecl{*this, builder, pendingVariableInitializers,
1635+
pendingNetDeclarationAssignments};
1636+
if (failed(predecl.predeclareScope(*module, "")))
1637+
return failure();
1638+
}
1639+
1640+
auto isPredeclaredMember = [&](const slang::ast::Symbol &member) {
1641+
return allowUseBeforeDeclare &&
1642+
(member.kind == slang::ast::SymbolKind::Variable ||
1643+
member.kind == slang::ast::SymbolKind::Net);
1644+
};
1645+
13591646
// Convert the body of the module.
13601647
for (auto &member : module->members()) {
1648+
if (isPredeclaredMember(member))
1649+
continue;
13611650
auto loc = convertLocation(member.location);
13621651
if (failed(member.visit(ModuleVisitor(*this, loc))))
13631652
return failure();
@@ -1367,6 +1656,26 @@ Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
13671656
return failure();
13681657
}
13691658

1659+
// Lower deferred declaration assignments after the full module body has had a
1660+
// chance to create any additional symbols, such as generated block members.
1661+
if (allowUseBeforeDeclare) {
1662+
for (auto [netOp, init] : pendingNetDeclarationAssignments) {
1663+
auto dstType = cast<moore::RefType>(netOp.getType()).getNestedType();
1664+
auto rvalue = convertRvalueExpression(*init, dstType);
1665+
if (!rvalue)
1666+
return failure();
1667+
netOp.getAssignmentMutable().assign(rvalue);
1668+
}
1669+
1670+
for (auto [varOp, init] : pendingVariableInitializers) {
1671+
auto dstType = cast<moore::RefType>(varOp.getType()).getNestedType();
1672+
auto rvalue = convertRvalueExpression(*init, dstType);
1673+
if (!rvalue)
1674+
return failure();
1675+
varOp.getInitialMutable().assign(rvalue);
1676+
}
1677+
}
1678+
13701679
// Create additional ops to drive input port values onto the corresponding
13711680
// internal variables and nets, and to collect output port values for the
13721681
// terminator.

0 commit comments

Comments
 (0)