28 #include "mlir/IR/Builders.h"
29 #include "mlir/IR/IRMapping.h"
30 #include "mlir/Pass/Pass.h"
31 #include "llvm/ADT/SetVector.h"
37 #define GEN_PASS_DEF_SVEXTRACTTESTCODE
38 #include "circt/Dialect/SV/SVPasses.h.inc"
43 using namespace circt;
46 using BindTable = DenseMap<StringAttr, SmallDenseMap<StringAttr, sv::BindOp>>;
58 llvm::function_ref<
bool(Operation *)> filter) {
59 SmallVector<Value> worklist(rootOp->getOperands());
61 while (!worklist.empty()) {
62 Value operand = worklist.pop_back_val();
63 Operation *definingOp = operand.getDefiningOp();
66 definingOp->hasTrait<mlir::OpTrait::IsIsolatedFromAbove>())
72 if (filter && !filter(definingOp))
76 if (!backwardSlice.contains(definingOp))
77 for (
auto newOperand : llvm::reverse(definingOp->getOperands()))
78 worklist.push_back(newOperand);
79 }
else if (
auto blockArg = dyn_cast<BlockArgument>(operand)) {
80 Block *block = blockArg.getOwner();
81 Operation *parentOp = block->getParentOp();
85 assert(parentOp->getNumRegions() == 1 &&
86 parentOp->getRegion(0).getBlocks().size() == 1);
87 if (!backwardSlice.contains(parentOp))
88 for (
auto newOperand : llvm::reverse(parentOp->getOperands()))
89 worklist.push_back(newOperand);
91 llvm_unreachable(
"No definingOp and not a block argument.");
94 backwardSlice.insert(definingOp);
100 SetVector<Operation *> &blocks) {
101 for (
auto op : ops) {
102 while (!isa<hw::HWModuleOp>(op->getParentOp())) {
103 op = op->getParentOp();
110 SetVector<Operation *> &results,
111 llvm::function_ref<
bool(Operation *)> filter) {
112 for (
auto *op : roots)
118 static SetVector<Operation *>
120 llvm::function_ref<
bool(Operation *)> filter) {
121 SetVector<Operation *> results;
125 SetVector<Operation *> blocks;
132 results.insert(roots.begin(), roots.end());
133 results.insert(blocks.begin(), blocks.end());
139 static SetVector<Operation *>
141 llvm::function_ref<
bool(Operation *)> rootFn,
142 llvm::function_ref<
bool(Operation *)> filterFn) {
143 SetVector<Operation *> roots;
144 module.walk([&](Operation *op) {
145 if (!isa<hw::HWModuleOp>(op) && rootFn(op))
152 SmallVector<mlir::Attribute> modulePorts) {
153 if (
auto bv = dyn_cast<BlockArgument>(val))
154 return cast<StringAttr>(modulePorts[bv.getArgNumber()]);
156 if (
auto *op = val.getDefiningOp()) {
157 if (
auto readinout = dyn_cast<ReadInOutOp>(op)) {
158 if (
auto *readOp = readinout.getInput().getDefiningOp()) {
159 if (
auto wire = dyn_cast<WireOp>(readOp))
160 return wire.getNameAttr();
161 if (
auto reg = dyn_cast<RegOp>(readOp))
162 return reg.getNameAttr();
164 }
else if (
auto inst = dyn_cast<hw::InstanceOp>(op)) {
165 auto index = cast<mlir::OpResult>(val).getResultNumber();
166 SmallString<64> portName = inst.getInstanceName();
168 auto resultName = inst.getOutputName(index);
169 if (resultName && !resultName.getValue().empty())
170 portName += resultName.getValue();
172 Twine(index).toVector(portName);
174 }
else if (op->getNumResults() == 1) {
175 if (
auto name = op->getAttrOfType<StringAttr>(
"name"))
177 if (
auto namehint = op->getAttrOfType<StringAttr>(
"sv.namehint"))
189 SetVector<Value> &inputs,
190 IRMapping &cutMap, StringRef suffix,
191 Attribute path, Attribute fileName,
195 SmallVector<Value> realInputs;
196 DenseMap<Value, Value> dups;
197 DenseMap<Value, SmallVector<Value>>
199 for (
auto v : inputs) {
200 if (
auto readinout = dyn_cast_or_null<ReadInOutOp>(v.getDefiningOp())) {
201 auto op = readinout.getInput();
202 if (dups.count(op)) {
203 realReads[dups[op]].push_back(v);
208 realInputs.push_back(v);
215 SmallVector<hw::PortInfo> ports;
218 auto srcPorts = op.getInputNames();
219 for (
auto port : llvm::enumerate(realInputs)) {
221 name = portNames.
newName(name.empty() ?
"port_" + Twine(port.index())
223 ports.push_back({{b.getStringAttr(name), port.value().getType(),
234 newMod->setAttr(
"output_file", path);
237 newMod.setCommentAttr(b.getStringAttr(
"VCS coverage exclude_file"));
241 for (
auto port : llvm::enumerate(realInputs)) {
242 cutMap.map(port.value(), newMod.getBody().getArgument(port.index()));
243 for (
auto extra : realReads[port.value()])
244 cutMap.map(extra, newMod.getBody().getArgument(port.index()));
246 cutMap.map(op.getBodyBlock(), newMod.getBodyBlock());
249 b = OpBuilder::atBlockTerminator(op.getBodyBlock());
250 auto inst = b.create<hw::InstanceOp>(
251 op.getLoc(), newMod, newMod.getName(), realInputs, ArrayAttr(),
255 inst.setDoNotPrintAttr(b.getUnitAttr());
256 b = OpBuilder::atBlockEnd(
257 &op->getParentOfType<mlir::ModuleOp>()->getRegion(0).front());
259 auto bindOp = b.create<sv::BindOp>(op.getLoc(), op.getNameAttr(),
260 inst.getInnerSymAttr().getSymName());
261 bindTable[op.getNameAttr()][inst.getInnerSymAttr().getSymName()] = bindOp;
263 bindOp->setAttr(
"output_file", fileName);
269 if (!block->empty() && isa<hw::HWModuleOp>(block->getParentOp()))
270 builder.setInsertionPoint(&block->back());
272 builder.setInsertionPointToEnd(block);
279 assert(oldOp->getNumRegions() == newOp->getNumRegions());
280 for (
size_t i = 0, e = oldOp->getNumRegions(); i != e; ++i) {
281 auto &oldRegion = oldOp->getRegion(i);
282 auto &newRegion = newOp->getRegion(i);
283 for (
auto oi = oldRegion.begin(), oe = oldRegion.end(); oi != oe; ++oi) {
284 cutMap.map(&*oi, &newRegion.emplaceBlock());
291 for (
auto arg : op->getOperands()) {
292 auto *argOp = arg.getDefiningOp();
304 for (
auto *op : lateBoundOps)
305 for (
unsigned argidx = 0, e = op->getNumOperands(); argidx < e; ++argidx) {
306 Value arg = op->getOperand(argidx);
307 if (cutMap.contains(arg))
308 op->setOperand(argidx, cutMap.lookup(arg));
315 SetVector<Operation *> &depOps, IRMapping &cutMap,
318 SmallVector<Operation *, 16> lateBoundOps;
319 OpBuilder b = OpBuilder::atBlockBegin(newMod.getBodyBlock());
320 oldMod.walk<WalkOrder::PreOrder>([&](Operation *op) {
321 if (depOps.count(op)) {
323 auto newOp = b.cloneWithoutRegions(*op, cutMap);
326 lateBoundOps.push_back(newOp);
327 if (
auto instance = dyn_cast<hw::InstanceOp>(op)) {
329 instanceGraph.lookup(instance.getModuleNameAttr().getAttr());
339 auto *node = instanceGraph.lookup(op);
341 auto inst = a->getInstance<hw::HWInstanceLike>();
345 return inst.getDoNotPrint();
351 for (
auto bind : topLevelModule->getOps<BindOp>()) {
352 hw::InnerRefAttr boundRef = bind.getInstance();
353 bindTable[boundRef.getModule()][boundRef.getName()] = bind;
360 BindTable &bindTable, SmallPtrSetImpl<Operation *> &opsToErase,
361 llvm::DenseSet<hw::InnerRefAttr> &innerRefUsedByNonBindOp) {
364 if (oldMod.getNumOutputPorts() != 0)
369 auto oldModName = oldMod.getModuleNameAttr();
370 for (
auto port : oldMod.getPortList()) {
371 auto sym = port.getSym();
373 for (
auto property : sym) {
375 if (innerRefUsedByNonBindOp.count(innerRef)) {
376 oldMod.emitWarning() <<
"module " << oldMod.getModuleName()
377 <<
" is an input only module but cannot "
378 "be inlined because a signal "
379 << port.
name <<
" is referred by name";
386 for (
auto op : oldMod.getBodyBlock()->getOps<hw::InnerSymbolOpInterface>()) {
387 if (
auto innerSym = op.getInnerSymAttr()) {
388 for (
auto property : innerSym) {
390 if (innerRefUsedByNonBindOp.count(innerRef)) {
391 op.emitWarning() <<
"module " << oldMod.getModuleName()
392 <<
" is an input only module but cannot be inlined "
393 "because signals are referred by name";
403 "expected module for inlining to be instantiated at least once");
407 bool allInlined =
true;
410 auto instLike = use->getInstance<hw::HWInstanceLike>();
417 hw::InstanceOp inst = cast<hw::InstanceOp>(instLike.getOperation());
418 if (inst.getInnerSym().has_value()) {
422 <<
"module " << oldMod.getModuleName()
423 <<
" cannot be inlined because there is an instance with a symbol";
424 diag.attachNote(inst.getLoc());
430 assert(inst.getInputs().size() == oldMod.getNumInputPorts());
431 auto inputPorts = oldMod.getBodyBlock()->getArguments();
432 for (
size_t i = 0, e = inputPorts.size(); i < e; ++i)
433 mapping.map(inputPorts[i], inst.getOperand(i));
437 cast<hw::HWModuleOp>(use->getParent()->getModule());
439 instanceGraph.lookup(instParent);
440 SmallVector<Operation *, 16> lateBoundOps;
441 b.setInsertionPoint(inst);
443 hw::InnerSymbolNamespace nameSpace(instParent);
445 DenseMap<mlir::StringAttr, mlir::StringAttr> symMapping;
447 for (
auto &op : *oldMod.getBodyBlock()) {
449 if (opsToErase.contains(&op))
453 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(op)) {
454 if (
auto innerSym = innerSymOp.getInnerSymAttr()) {
455 for (
auto property : innerSym) {
456 auto oldName =
property.getName();
458 b.getStringAttr(nameSpace.newName(oldName.getValue()));
459 auto result = symMapping.insert({oldName, newName});
461 assert(result.second &&
"inner symbols must be unique");
467 if (
auto innerInst = dyn_cast<hw::InstanceOp>(op)) {
468 if (
auto innerInstSym = innerInst.getInnerSymAttr()) {
470 bindTable[oldMod.getNameAttr()].find(innerInstSym.getSymName());
471 if (it != bindTable[oldMod.getNameAttr()].end()) {
472 sv::BindOp bind = it->second;
473 auto oldInnerRef = bind.getInstanceAttr();
474 auto it = symMapping.find(oldInnerRef.getName());
475 assert(it != symMapping.end() &&
476 "inner sym mapping must be already populated");
477 auto newName = it->second;
480 OpBuilder::InsertionGuard g(b);
482 b.setInsertionPoint(bind);
483 sv::BindOp clonedBind = cast<sv::BindOp>(b.clone(*bind, mapping));
484 clonedBind.setInstanceAttr(newInnerRef);
485 bindTable[instParent.getModuleNameAttr()][newName] =
486 cast<sv::BindOp>(clonedBind);
492 if (!isa<hw::OutputOp>(op)) {
493 Operation *clonedOp = b.clone(op, mapping);
497 lateBoundOps.push_back(clonedOp);
501 if (
auto innerInst = dyn_cast<hw::InstanceOp>(clonedOp)) {
503 instanceGraph.lookup(innerInst.getModuleNameAttr().getAttr());
504 instParentNode->
addInstance(innerInst, innerInstModule);
508 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(clonedOp)) {
509 if (
auto oldInnerSym = innerSymOp.getInnerSymAttr()) {
510 SmallVector<hw::InnerSymPropertiesAttr> properties;
511 for (
auto property : oldInnerSym) {
512 auto newSymName = symMapping[
property.getName()];
514 op.getContext(), newSymName, property.getFieldID(),
515 property.getSymVisibility()));
518 innerSymOp.setInnerSymbolAttr(innerSym);
528 assert(inst.use_empty() &&
"inlined instance should have no uses");
530 opsToErase.insert(inst);
536 for (
auto [_, bind] : bindTable[oldMod.getNameAttr()])
538 bindTable[oldMod.getNameAttr()].clear();
539 instanceGraph.erase(node);
540 opsToErase.insert(oldMod);
544 static bool isAssertOp(hw::HWSymbolCache &symCache, Operation *op) {
547 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
548 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
549 if (mod->getAttr(
"firrtl.extract.assert.extra"))
555 if (
auto error = dyn_cast<ErrorOp>(op)) {
556 if (
auto message = error.getMessage())
557 return message->starts_with(
"assert:") ||
558 message->starts_with(
"assert failed (verification library)") ||
559 message->starts_with(
"Assertion failed") ||
560 message->starts_with(
"assertNotX:") ||
561 message->contains(
"[verif-library-assert]");
565 return isa<AssertOp, FinishOp, FWriteOp, AssertConcurrentOp, FatalOp,
566 verif::AssertOp, verif::ClockedAssertOp>(op);
569 static bool isCoverOp(hw::HWSymbolCache &symCache, Operation *op) {
572 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
573 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
574 if (mod->getAttr(
"firrtl.extract.cover.extra"))
576 return isa<CoverOp, CoverConcurrentOp, verif::CoverOp, verif::ClockedCoverOp>(
580 static bool isAssumeOp(hw::HWSymbolCache &symCache, Operation *op) {
583 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
584 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
585 if (mod->getAttr(
"firrtl.extract.assume.extra"))
588 return isa<AssumeOp, AssumeConcurrentOp, verif::AssumeOp,
589 verif::ClockedAssumeOp>(op);
594 bool disableInstanceExtraction =
false,
595 bool disableRegisterExtraction =
false) {
598 if (isa<hw::OutputOp>(op))
602 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(op))
603 if (
auto innerSym = innerSymOp.getInnerSymAttr())
604 if (!innerSym.empty())
615 if (isa<hw::InstanceOp>(op))
616 return disableInstanceExtraction;
617 if (isa<seq::FirRegOp>(op))
618 return disableRegisterExtraction;
623 if (isa<sv::ReadInOutOp>(op))
627 if (op->getNumRegions() > 0)
631 return !mlir::isMemoryEffectFree(op);
640 struct SVExtractTestCodeImplPass
641 :
public circt::sv::impl::SVExtractTestCodeBase<SVExtractTestCodeImplPass> {
642 SVExtractTestCodeImplPass(
bool disableInstanceExtraction,
643 bool disableRegisterExtraction,
644 bool disableModuleInlining) {
645 this->disableInstanceExtraction = disableInstanceExtraction;
646 this->disableRegisterExtraction = disableRegisterExtraction;
647 this->disableModuleInlining = disableModuleInlining;
649 void runOnOperation()
override;
653 bool doModule(
hw::HWModuleOp module, llvm::function_ref<
bool(Operation *)> fn,
654 StringRef suffix, Attribute path, Attribute bindFile,
655 BindTable &bindTable, SmallPtrSetImpl<Operation *> &opsToErase,
656 SetVector<Operation *> &opsInDesign) {
657 bool hasError =
false;
659 SetVector<Operation *> roots;
660 module->walk([&fn, &roots, &hasError](Operation *op) {
663 if (op->getNumResults()) {
664 op->emitError(
"Extracting op with result");
681 return !opsInDesign.count(op) ||
682 op->hasTrait<mlir::OpTrait::ConstantLike>();
686 SetVector<Value> inputs;
687 for (
auto *op : opsToClone) {
688 for (
auto arg : op->getOperands()) {
689 auto argOp = arg.getDefiningOp();
690 if (!opsToClone.count(argOp))
694 opsToErase.insert(op);
697 numOpsExtracted += opsToClone.size();
702 bindFile, bindTable);
705 instanceGraph->addHWModule(bmod);
708 migrateOps(module, bmod, opsToClone, cutMap, *instanceGraph);
711 for (
auto *op : roots) {
712 opsToErase.erase(op);
725 void SVExtractTestCodeImplPass::runOnOperation() {
726 this->instanceGraph = &getAnalysis<circt::hw::InstanceGraph>();
728 auto top = getOperation();
735 DenseSet<hw::InnerRefAttr> innerRefUsedByNonBindOp;
736 top.walk([&](Operation *op) {
737 if (!isa<sv::BindOp>(op))
738 for (
auto attr : op->getAttrs())
739 attr.getValue().walk([&](hw::InnerRefAttr attr) {
740 innerRefUsedByNonBindOp.insert(attr);
744 auto *topLevelModule = top.getBody();
746 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assert");
748 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assume");
750 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.cover");
751 auto assertBindFile =
752 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assert.bindfile");
753 auto assumeBindFile =
754 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assume.bindfile");
756 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.cover.bindfile");
758 hw::HWSymbolCache symCache;
759 symCache.addDefinitions(top);
762 auto isAssert = [&symCache](Operation *op) ->
bool {
766 auto isAssume = [&symCache](Operation *op) ->
bool {
770 auto isCover = [&symCache](Operation *op) ->
bool {
780 for (
auto &op : llvm::make_early_inc_range(topLevelModule->getOperations())) {
781 if (
auto rtlmod = dyn_cast<hw::HWModuleOp>(op)) {
787 if (
isBound(rtlmod, *instanceGraph))
791 if (rtlmod->hasAttr(
"firrtl.extract.do_not_extract")) {
792 rtlmod->removeAttr(
"firrtl.extract.do_not_extract");
802 return isInDesign(symCache, op, disableInstanceExtraction,
803 disableRegisterExtraction);
807 SmallPtrSet<Operation *, 32> opsToErase;
808 bool anyThingExtracted =
false;
810 doModule(rtlmod, isAssert,
"_assert", assertDir, assertBindFile,
811 bindTable, opsToErase, opsInDesign);
813 doModule(rtlmod, isAssume,
"_assume", assumeDir, assumeBindFile,
814 bindTable, opsToErase, opsInDesign);
816 doModule(rtlmod, isCover,
"_cover", coverDir, coverBindFile,
817 bindTable, opsToErase, opsInDesign);
820 if (!anyThingExtracted && rtlmod.getNumOutputPorts() != 0)
838 disableRegisterExtraction) &&
839 !opsToErase.contains(op);
844 op.walk([&](Operation *operation) {
846 if (&op == operation)
850 if (opsAlive.count(operation))
851 opsToErase.erase(operation);
853 opsToErase.insert(operation);
857 if (!disableModuleInlining)
859 innerRefUsedByNonBindOp);
861 numOpsErased += opsToErase.size();
862 while (!opsToErase.empty()) {
863 Operation *op = *opsToErase.begin();
864 op->walk([&](Operation *erasedOp) { opsToErase.erase(erasedOp); });
873 for (
auto &op : topLevelModule->getOperations())
874 if (isa<hw::HWModuleOp, hw::HWModuleExternOp>(op)) {
875 op.removeAttr(
"firrtl.extract.assert.extra");
876 op.removeAttr(
"firrtl.extract.cover.extra");
877 op.removeAttr(
"firrtl.extract.assume.extra");
880 markAnalysesPreserved<circt::hw::InstanceGraph>();
883 std::unique_ptr<Pass>
885 bool disableRegisterExtraction,
886 bool disableModuleInlining) {
887 return std::make_unique<SVExtractTestCodeImplPass>(disableInstanceExtraction,
888 disableRegisterExtraction,
889 disableModuleInlining);
assert(baseType &&"element must be base type")
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.
StringRef getFragmentsAttrName()
Return the name of the fragments array attribute.
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)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)