Skip to content

Commit c112bb0

Browse files
committed
feat: use non-local annotations to mark memories with extmodule
This fixes #8994. We use NLA here to pass information from LowerMemory to LayerSink to avoid marking memories with an extmodule as not effectful. Signed-off-by: Tianrui Wei <[email protected]>
1 parent 40962a3 commit c112bb0

File tree

2 files changed

+52
-2
lines changed

2 files changed

+52
-2
lines changed

lib/Dialect/FIRRTL/Transforms/LayerSink.cpp

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h"
1414
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
1515
#include "circt/Dialect/FIRRTL/Passes.h"
16+
#include "circt/Dialect/HW/HWOps.h"
1617
#include "circt/Support/Debug.h"
1718
#include "mlir/IR/Dominance.h"
1819
#include "mlir/IR/Threading.h"
@@ -34,6 +35,9 @@ using namespace circt;
3435
using namespace firrtl;
3536
using namespace mlir;
3637

38+
static constexpr llvm::StringLiteral
39+
kExtMemoryEffectNLAAttrName("circt.layerSink.externalMemoryEffect");
40+
3741
//===----------------------------------------------------------------------===//
3842
// Helpers
3943
//===----------------------------------------------------------------------===//
@@ -87,7 +91,10 @@ static bool cloneable(Operation *op) {
8791
namespace {
8892
class EffectInfo {
8993
public:
90-
EffectInfo(CircuitOp circuit, InstanceGraph &instanceGraph) {
94+
EffectInfo(CircuitOp circuit, InstanceGraph &instanceGraph,
95+
DenseSet<StringAttr> explicitEffectfulModules)
96+
: explicitEffectfulModules(std::move(explicitEffectfulModules)) {
97+
DenseSet<InstanceGraphNode *> visited;
9198
instanceGraph.walkPostOrder(
9299
[&](auto &node) { update(node.getModule().getOperation()); });
93100
}
@@ -111,6 +118,7 @@ class EffectInfo {
111118
}
112119

113120
private:
121+
DenseSet<StringAttr> explicitEffectfulModules;
114122
/// Record whether the module contains any effectful ops.
115123
void update(FModuleOp moduleOp) {
116124
moduleOp.getBodyBlock()->walk([&](Operation *op) {
@@ -130,6 +138,14 @@ class EffectInfo {
130138
if (!annos.empty())
131139
return markEffectful(moduleOp);
132140

141+
if (explicitEffectfulModules.contains(moduleOp.getModuleNameAttr()))
142+
return markEffectful(moduleOp);
143+
144+
// Treat generated memory modules as effectful since they model stateful
145+
// hardware even though they have no body.
146+
if (isa<FMemModuleOp>(moduleOp.getOperation()))
147+
return markEffectful(moduleOp);
148+
133149
for (auto annos : moduleOp.getPortAnnotations())
134150
if (!cast<ArrayAttr>(annos).empty())
135151
return markEffectful(moduleOp);
@@ -486,7 +502,19 @@ void LayerSinkPass::runOnOperation() {
486502
<< "\n"
487503
<< "Circuit: '" << circuit.getName() << "'\n";);
488504
auto &instanceGraph = getAnalysis<InstanceGraph>();
489-
EffectInfo effectInfo(circuit, instanceGraph);
505+
506+
DenseSet<StringAttr> explicitEffectModules;
507+
SmallVector<hw::HierPathOp, 4> effectNLAs;
508+
for (auto nla : circuit.getOps<hw::HierPathOp>()) {
509+
if (!nla->hasAttr(kExtMemoryEffectNLAAttrName))
510+
continue;
511+
if (auto leaf = nla.leafMod())
512+
explicitEffectModules.insert(leaf);
513+
effectNLAs.push_back(nla);
514+
}
515+
516+
EffectInfo effectInfo(circuit, instanceGraph,
517+
std::move(explicitEffectModules));
490518

491519
std::atomic<bool> changed(false);
492520
parallelForEach(&getContext(), circuit.getOps<FModuleOp>(),
@@ -495,6 +523,9 @@ void LayerSinkPass::runOnOperation() {
495523
changed = true;
496524
});
497525

526+
for (auto nla : effectNLAs)
527+
nla.erase();
528+
498529
if (!changed)
499530
markAllAnalysesPreserved();
500531
else

lib/Dialect/FIRRTL/Transforms/LowerMemory.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "llvm/ADT/STLExtras.h"
2525
#include "llvm/ADT/STLFunctionalExtras.h"
2626
#include "llvm/ADT/SmallPtrSet.h"
27+
#include "llvm/ADT/StringRef.h"
2728
#include "llvm/Support/Parallel.h"
2829
#include <optional>
2930
#include <set>
@@ -189,6 +190,9 @@ LowerMemoryPass::getMemoryModulePorts(const FirMemory &mem) {
189190
return ports;
190191
}
191192

193+
static constexpr llvm::StringLiteral
194+
kExtMemoryEffectNLAAttrName("circt.layerSink.externalMemoryEffect");
195+
192196
FMemModuleOp
193197
LowerMemoryPass::emitMemoryModule(MemOp op, const FirMemory &mem,
194198
const SmallVectorImpl<PortInfo> &ports) {
@@ -208,6 +212,21 @@ LowerMemoryPass::emitMemoryModule(MemOp op, const FirMemory &mem,
208212
mem.numReadWritePorts, mem.dataWidth, mem.maskBits, mem.readLatency,
209213
mem.writeLatency, mem.depth,
210214
*symbolizeRUWBehavior(static_cast<uint32_t>(mem.readUnderWrite)));
215+
// Mark the generated external memory as effectful so downstream passes do not
216+
// treat it as pure and drop the wrapper wiring around it. We create a
217+
// temporary HierPathOp, which LayerSink will consume and erase.
218+
auto circuit = op->getParentOfType<CircuitOp>();
219+
OpBuilder nlaBuilder(circuit);
220+
nlaBuilder.setInsertionPointToEnd(circuit.getBodyBlock());
221+
auto pathAttr = nlaBuilder.getArrayAttr(SmallVector<Attribute, 1>{
222+
FlatSymbolRefAttr::get(moduleOp.getModuleNameAttr())});
223+
auto nlaName = circuitNamespace.newName(
224+
(moduleOp.getModuleNameAttr().getValue() + "_effect_nla").str());
225+
auto nla = nlaBuilder.create<hw::HierPathOp>(
226+
mem.loc, StringAttr::get(&getContext(), nlaName), pathAttr);
227+
nla->setAttr(kExtMemoryEffectNLAAttrName,
228+
FlatSymbolRefAttr::get(moduleOp.getModuleNameAttr()));
229+
SymbolTable::setSymbolVisibility(nla, SymbolTable::Visibility::Private);
211230
SymbolTable::setSymbolVisibility(moduleOp, SymbolTable::Visibility::Private);
212231
return moduleOp;
213232
}

0 commit comments

Comments
 (0)