26 #include "mlir/IR/Builders.h"
27 #include "mlir/IR/IRMapping.h"
28 #include "llvm/ADT/SetVector.h"
33 using namespace circt;
36 using BindTable = DenseMap<StringAttr, SmallDenseMap<StringAttr, sv::BindOp>>;
48 llvm::function_ref<
bool(Operation *)> filter) {
49 SmallVector<Value> worklist(rootOp->getOperands());
51 while (!worklist.empty()) {
52 Value operand = worklist.pop_back_val();
53 Operation *definingOp = operand.getDefiningOp();
56 definingOp->hasTrait<mlir::OpTrait::IsIsolatedFromAbove>())
62 if (filter && !filter(definingOp))
66 if (!backwardSlice.contains(definingOp))
67 for (
auto newOperand : llvm::reverse(definingOp->getOperands()))
68 worklist.push_back(newOperand);
69 }
else if (
auto blockArg = operand.dyn_cast<BlockArgument>()) {
70 Block *block = blockArg.getOwner();
71 Operation *parentOp = block->getParentOp();
75 assert(parentOp->getNumRegions() == 1 &&
76 parentOp->getRegion(0).getBlocks().size() == 1);
77 if (!backwardSlice.contains(parentOp))
78 for (
auto newOperand : llvm::reverse(parentOp->getOperands()))
79 worklist.push_back(newOperand);
81 llvm_unreachable(
"No definingOp and not a block argument.");
84 backwardSlice.insert(definingOp);
90 SetVector<Operation *> &blocks) {
92 while (!isa<hw::HWModuleOp>(op->getParentOp())) {
93 op = op->getParentOp();
100 SetVector<Operation *> &results,
101 llvm::function_ref<
bool(Operation *)> filter) {
102 for (
auto *op : roots)
108 static SetVector<Operation *>
110 llvm::function_ref<
bool(Operation *)> filter) {
111 SetVector<Operation *> results;
115 SetVector<Operation *> blocks;
122 results.insert(roots.begin(), roots.end());
123 results.insert(blocks.begin(), blocks.end());
129 static SetVector<Operation *>
131 llvm::function_ref<
bool(Operation *)> rootFn,
132 llvm::function_ref<
bool(Operation *)> filterFn) {
133 SetVector<Operation *> roots;
134 module.walk([&](Operation *op) {
135 if (!isa<hw::HWModuleOp>(op) && rootFn(op))
142 SmallVector<mlir::Attribute> modulePorts) {
143 if (
auto bv = val.dyn_cast<BlockArgument>())
144 return modulePorts[bv.getArgNumber()].cast<StringAttr>();
146 if (
auto *op = val.getDefiningOp()) {
147 if (
auto readinout = dyn_cast<ReadInOutOp>(op)) {
148 if (
auto *readOp = readinout.getInput().getDefiningOp()) {
149 if (
auto wire = dyn_cast<WireOp>(readOp))
150 return wire.getNameAttr();
151 if (
auto reg = dyn_cast<RegOp>(readOp))
152 return reg.getNameAttr();
154 }
else if (
auto inst = dyn_cast<hw::InstanceOp>(op)) {
155 auto index = val.cast<mlir::OpResult>().getResultNumber();
156 SmallString<64> portName = inst.getInstanceName();
158 auto resultName = inst.getResultName(index);
159 if (resultName && !resultName.getValue().empty())
160 portName += resultName.getValue();
162 Twine(index).toVector(portName);
164 }
else if (op->getNumResults() == 1) {
165 if (
auto name = op->getAttrOfType<StringAttr>(
"name"))
167 if (
auto namehint = op->getAttrOfType<StringAttr>(
"sv.namehint"))
180 IRMapping &cutMap, StringRef suffix,
181 Attribute path, Attribute fileName,
185 SmallVector<Value> realInputs;
186 DenseMap<Value, Value> dups;
187 DenseMap<Value, SmallVector<Value>>
190 if (
auto readinout = dyn_cast_or_null<ReadInOutOp>(v.getDefiningOp())) {
191 auto op = readinout.getInput();
192 if (dups.count(op)) {
193 realReads[dups[op]].push_back(v);
198 realInputs.push_back(v);
205 SmallVector<hw::PortInfo> ports;
208 auto srcPorts = op.getInputNames();
209 for (
auto port : llvm::enumerate(realInputs)) {
211 name = portNames.
newName(name.empty() ?
"port_" + Twine(port.index())
213 ports.push_back({{b.getStringAttr(name), port.value().getType(),
224 newMod->setAttr(
"output_file", path);
225 newMod.setCommentAttr(b.getStringAttr(
"VCS coverage exclude_file"));
228 for (
auto port : llvm::enumerate(realInputs)) {
229 cutMap.map(port.value(), newMod.getBody().getArgument(port.index()));
230 for (
auto extra : realReads[port.value()])
231 cutMap.map(extra, newMod.getBody().getArgument(port.index()));
233 cutMap.map(op.getBodyBlock(), newMod.getBodyBlock());
236 b = OpBuilder::atBlockTerminator(op.getBodyBlock());
237 auto inst = b.create<hw::InstanceOp>(
238 op.getLoc(), newMod, newMod.getName(), realInputs, ArrayAttr(),
242 inst->setAttr(
"doNotPrint", b.getBoolAttr(
true));
243 b = OpBuilder::atBlockEnd(
244 &op->getParentOfType<mlir::ModuleOp>()->getRegion(0).front());
246 auto bindOp = b.create<sv::BindOp>(op.getLoc(), op.getNameAttr(),
247 inst.getInnerSymAttr().getSymName());
248 bindTable[op.getNameAttr()][inst.getInnerSymAttr().getSymName()] = bindOp;
250 bindOp->setAttr(
"output_file", fileName);
256 if (!block->empty() && isa<hw::HWModuleOp>(block->getParentOp()))
257 builder.setInsertionPoint(&block->back());
259 builder.setInsertionPointToEnd(block);
266 assert(oldOp->getNumRegions() == newOp->getNumRegions());
267 for (
size_t i = 0, e = oldOp->getNumRegions(); i != e; ++i) {
268 auto &oldRegion = oldOp->getRegion(i);
269 auto &newRegion = newOp->getRegion(i);
270 for (
auto oi = oldRegion.begin(), oe = oldRegion.end(); oi != oe; ++oi) {
271 cutMap.map(&*oi, &newRegion.emplaceBlock());
278 for (
auto arg : op->getOperands()) {
279 auto *argOp = arg.getDefiningOp();
291 for (
auto *op : lateBoundOps)
292 for (
unsigned argidx = 0, e = op->getNumOperands(); argidx < e; ++argidx) {
293 Value arg = op->getOperand(argidx);
294 if (cutMap.contains(arg))
295 op->setOperand(argidx, cutMap.lookup(arg));
302 SetVector<Operation *> &depOps, IRMapping &cutMap,
305 SmallVector<Operation *, 16> lateBoundOps;
306 OpBuilder b = OpBuilder::atBlockBegin(newMod.getBodyBlock());
307 oldMod.walk<WalkOrder::PreOrder>([&](Operation *op) {
308 if (depOps.count(op)) {
310 auto newOp = b.cloneWithoutRegions(*op, cutMap);
313 lateBoundOps.push_back(newOp);
314 if (
auto instance = dyn_cast<hw::InstanceOp>(op)) {
316 instanceGraph.lookup(instance.getModuleNameAttr().getAttr());
326 auto *node = instanceGraph.lookup(op);
328 auto inst = a->getInstance();
331 return inst->hasAttr(
"doNotPrint");
337 for (
auto bind : topLevelModule->getOps<BindOp>()) {
338 hw::InnerRefAttr boundRef = bind.getInstance();
339 bindTable[boundRef.getModule()][boundRef.getName()] = bind;
346 BindTable &bindTable, SmallPtrSetImpl<Operation *> &opsToErase,
347 llvm::DenseSet<hw::InnerRefAttr> &innerRefUsedByNonBindOp) {
350 if (oldMod.getNumOutputPorts() != 0)
355 auto oldModName = oldMod.getModuleNameAttr();
356 for (
auto port : oldMod.getPortList()) {
357 auto sym = port.getSym();
359 for (
auto property : sym) {
361 if (innerRefUsedByNonBindOp.count(innerRef)) {
362 oldMod.emitWarning() <<
"module " << oldMod.getModuleName()
363 <<
" is an input only module but cannot "
364 "be inlined because a signal "
365 << port.
name <<
" is referred by name";
372 for (
auto op : oldMod.getBodyBlock()->getOps<hw::InnerSymbolOpInterface>()) {
373 if (
auto innerSym = op.getInnerSymAttr()) {
374 for (
auto property : innerSym) {
376 if (innerRefUsedByNonBindOp.count(innerRef)) {
377 op.emitWarning() <<
"module " << oldMod.getModuleName()
378 <<
" is an input only module but cannot be inlined "
379 "because signals are referred by name";
389 "expected module for inlining to be instantiated at least once");
393 bool allInlined =
true;
396 auto instLike = use->getInstance<hw::HWInstanceLike>();
403 hw::InstanceOp inst = cast<hw::InstanceOp>(instLike.getOperation());
404 if (inst.getInnerSym().has_value()) {
408 <<
"module " << oldMod.getModuleName()
409 <<
" cannot be inlined because there is an instance with a symbol";
410 diag.attachNote(inst.getLoc());
416 assert(inst.getInputs().size() == oldMod.getNumInputPorts());
417 auto inputPorts = oldMod.getBodyBlock()->getArguments();
418 for (
size_t i = 0, e = inputPorts.size(); i < e; ++i)
419 mapping.map(inputPorts[i], inst.getOperand(i));
423 cast<hw::HWModuleOp>(use->getParent()->getModule());
425 instanceGraph.lookup(instParent);
426 SmallVector<Operation *, 16> lateBoundOps;
427 b.setInsertionPoint(inst);
429 hw::InnerSymbolNamespace nameSpace(instParent);
431 DenseMap<mlir::StringAttr, mlir::StringAttr> symMapping;
433 for (
auto &op : *oldMod.getBodyBlock()) {
435 if (opsToErase.contains(&op))
439 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(op)) {
440 if (
auto innerSym = innerSymOp.getInnerSymAttr()) {
441 for (
auto property : innerSym) {
442 auto oldName =
property.getName();
444 b.getStringAttr(nameSpace.newName(oldName.getValue()));
445 auto result = symMapping.insert({oldName, newName});
447 assert(result.second &&
"inner symbols must be unique");
453 if (
auto innerInst = dyn_cast<hw::InstanceOp>(op)) {
454 if (
auto innerInstSym = innerInst.getInnerSymAttr()) {
456 bindTable[oldMod.getNameAttr()].find(innerInstSym.getSymName());
457 if (it != bindTable[oldMod.getNameAttr()].end()) {
458 sv::BindOp bind = it->second;
459 auto oldInnerRef = bind.getInstanceAttr();
460 auto it = symMapping.find(oldInnerRef.getName());
461 assert(it != symMapping.end() &&
462 "inner sym mapping must be already populated");
463 auto newName = it->second;
466 OpBuilder::InsertionGuard g(b);
468 b.setInsertionPoint(bind);
469 sv::BindOp clonedBind = cast<sv::BindOp>(b.clone(*bind, mapping));
470 clonedBind.setInstanceAttr(newInnerRef);
471 bindTable[instParent.getModuleNameAttr()][newName] =
472 cast<sv::BindOp>(clonedBind);
478 if (!isa<hw::OutputOp>(op)) {
479 Operation *clonedOp = b.clone(op, mapping);
483 lateBoundOps.push_back(clonedOp);
487 if (
auto innerInst = dyn_cast<hw::InstanceOp>(clonedOp)) {
489 instanceGraph.lookup(innerInst.getModuleNameAttr().getAttr());
490 instParentNode->
addInstance(innerInst, innerInstModule);
494 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(clonedOp)) {
495 if (
auto oldInnerSym = innerSymOp.getInnerSymAttr()) {
496 SmallVector<hw::InnerSymPropertiesAttr> properties;
497 for (
auto property : oldInnerSym) {
498 auto newSymName = symMapping[
property.getName()];
500 op.getContext(), newSymName, property.getFieldID(),
501 property.getSymVisibility()));
504 innerSymOp.setInnerSymbolAttr(innerSym);
514 assert(inst.use_empty() &&
"inlined instance should have no uses");
516 opsToErase.insert(inst);
522 for (
auto [_, bind] : bindTable[oldMod.getNameAttr()])
524 bindTable[oldMod.getNameAttr()].clear();
525 instanceGraph.erase(node);
526 opsToErase.insert(oldMod);
530 static bool isAssertOp(hw::HWSymbolCache &symCache, Operation *op) {
533 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
534 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
535 if (mod->getAttr(
"firrtl.extract.assert.extra"))
541 if (
auto error = dyn_cast<ErrorOp>(op)) {
542 if (
auto message = error.getMessage())
543 return message->startswith(
"assert:") ||
544 message->startswith(
"assert failed (verification library)") ||
545 message->startswith(
"Assertion failed") ||
546 message->startswith(
"assertNotX:") ||
547 message->contains(
"[verif-library-assert]");
551 return isa<AssertOp, FinishOp, FWriteOp, AssertConcurrentOp, FatalOp>(op);
554 static bool isCoverOp(hw::HWSymbolCache &symCache, Operation *op) {
557 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
558 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
559 if (mod->getAttr(
"firrtl.extract.cover.extra"))
561 return isa<CoverOp, CoverConcurrentOp>(op);
564 static bool isAssumeOp(hw::HWSymbolCache &symCache, Operation *op) {
567 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
568 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
569 if (mod->getAttr(
"firrtl.extract.assume.extra"))
572 return isa<AssumeOp, AssumeConcurrentOp>(op);
577 bool disableInstanceExtraction =
false,
578 bool disableRegisterExtraction =
false) {
581 if (isa<hw::OutputOp>(op))
585 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(op))
586 if (
auto innerSym = innerSymOp.getInnerSymAttr())
587 if (!innerSym.empty())
598 if (isa<hw::InstanceOp>(op))
599 return disableInstanceExtraction;
600 if (isa<seq::FirRegOp>(op))
601 return disableRegisterExtraction;
606 if (isa<sv::ReadInOutOp>(op))
610 if (op->getNumRegions() > 0)
614 return !mlir::isMemoryEffectFree(op);
623 struct SVExtractTestCodeImplPass
624 :
public SVExtractTestCodeBase<SVExtractTestCodeImplPass> {
625 SVExtractTestCodeImplPass(
bool disableInstanceExtraction,
626 bool disableRegisterExtraction,
627 bool disableModuleInlining) {
628 this->disableInstanceExtraction = disableInstanceExtraction;
629 this->disableRegisterExtraction = disableRegisterExtraction;
630 this->disableModuleInlining = disableModuleInlining;
632 void runOnOperation()
override;
636 bool doModule(
hw::HWModuleOp module, llvm::function_ref<
bool(Operation *)> fn,
637 StringRef suffix, Attribute path, Attribute bindFile,
638 BindTable &bindTable, SmallPtrSetImpl<Operation *> &opsToErase,
639 SetVector<Operation *> &opsInDesign) {
640 bool hasError =
false;
642 SetVector<Operation *> roots;
643 module->walk([&fn, &roots, &hasError](Operation *op) {
646 if (op->getNumResults()) {
647 op->emitError(
"Extracting op with result");
664 return !opsInDesign.count(op) ||
665 op->hasTrait<mlir::OpTrait::ConstantLike>();
670 for (
auto *op : opsToClone) {
671 for (
auto arg : op->getOperands()) {
672 auto argOp = arg.getDefiningOp();
673 if (!opsToClone.count(argOp))
677 opsToErase.insert(op);
680 numOpsExtracted += opsToClone.size();
685 bindFile, bindTable);
688 instanceGraph->addHWModule(bmod);
691 migrateOps(module, bmod, opsToClone, cutMap, *instanceGraph);
694 for (
auto *op : roots) {
695 opsToErase.erase(op);
708 void SVExtractTestCodeImplPass::runOnOperation() {
709 this->instanceGraph = &getAnalysis<circt::hw::InstanceGraph>();
711 auto top = getOperation();
718 DenseSet<hw::InnerRefAttr> innerRefUsedByNonBindOp;
719 top.walk([&](Operation *op) {
720 if (!isa<sv::BindOp>(op))
721 for (
auto attr : op->getAttrs())
722 attr.getValue().walk([&](hw::InnerRefAttr attr) {
723 innerRefUsedByNonBindOp.insert(attr);
727 auto *topLevelModule = top.getBody();
729 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assert");
731 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assume");
733 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.cover");
734 auto assertBindFile =
735 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assert.bindfile");
736 auto assumeBindFile =
737 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assume.bindfile");
739 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.cover.bindfile");
741 hw::HWSymbolCache symCache;
742 symCache.addDefinitions(top);
745 auto isAssert = [&symCache](Operation *op) ->
bool {
749 auto isAssume = [&symCache](Operation *op) ->
bool {
753 auto isCover = [&symCache](Operation *op) ->
bool {
763 for (
auto &op : llvm::make_early_inc_range(topLevelModule->getOperations())) {
764 if (
auto rtlmod = dyn_cast<hw::HWModuleOp>(op)) {
770 if (
isBound(rtlmod, *instanceGraph))
774 if (rtlmod->hasAttr(
"firrtl.extract.do_not_extract")) {
775 rtlmod->removeAttr(
"firrtl.extract.do_not_extract");
785 return isInDesign(symCache, op, disableInstanceExtraction,
786 disableRegisterExtraction);
790 SmallPtrSet<Operation *, 32> opsToErase;
791 bool anyThingExtracted =
false;
793 doModule(rtlmod, isAssert,
"_assert", assertDir, assertBindFile,
794 bindTable, opsToErase, opsInDesign);
796 doModule(rtlmod, isAssume,
"_assume", assumeDir, assumeBindFile,
797 bindTable, opsToErase, opsInDesign);
799 doModule(rtlmod, isCover,
"_cover", coverDir, coverBindFile,
800 bindTable, opsToErase, opsInDesign);
803 if (!anyThingExtracted && rtlmod.getNumOutputPorts() != 0)
821 disableRegisterExtraction) &&
822 !opsToErase.contains(op);
827 op.walk([&](Operation *operation) {
829 if (&op == operation)
833 if (opsAlive.count(operation))
834 opsToErase.erase(operation);
836 opsToErase.insert(operation);
840 if (!disableModuleInlining)
842 innerRefUsedByNonBindOp);
844 numOpsErased += opsToErase.size();
845 while (!opsToErase.empty()) {
846 Operation *op = *opsToErase.begin();
847 op->walk([&](Operation *erasedOp) { opsToErase.erase(erasedOp); });
856 for (
auto &op : topLevelModule->getOperations())
857 if (isa<hw::HWModuleOp, hw::HWModuleExternOp>(op)) {
858 op.removeAttr(
"firrtl.extract.assert.extra");
859 op.removeAttr(
"firrtl.extract.cover.extra");
860 op.removeAttr(
"firrtl.extract.assume.extra");
863 markAnalysesPreserved<circt::hw::InstanceGraph>();
866 std::unique_ptr<Pass>
868 bool disableRegisterExtraction,
869 bool disableModuleInlining) {
870 return std::make_unique<SVExtractTestCodeImplPass>(disableInstanceExtraction,
871 disableRegisterExtraction,
872 disableModuleInlining);
assert(baseType &&"element must be base type")
llvm::SmallVector< StringAttr > inputs
A namespace that is used to store existing names and generate new names in some scope within the IR.
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
This is a Node in the InstanceGraph.
bool noUses()
Return true if there are no more instances of this module.
InstanceRecord * addInstance(InstanceOpInterface instance, InstanceGraphNode *target)
Record a new instance op in the body of this module.
llvm::iterator_range< UseIterator > uses()
This is an edge in the InstanceGraph.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
std::map< std::string, std::set< std::string > > InstanceGraph
Iterates over the handshake::FuncOp's in the program to build an instance graph.
StringAttr getVerilogModuleNameAttr(Operation *module)
Returns the verilog module name attribute or symbol name of any module-like operations.
std::unique_ptr< mlir::Pass > createSVExtractTestCodePass(bool disableInstanceExtraction=false, bool disableRegisterExtraction=false, bool disableModuleInlining=false)
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)