28 #include "mlir/IR/Builders.h"
29 #include "mlir/IR/IRMapping.h"
30 #include "llvm/ADT/SetVector.h"
35 using namespace circt;
38 using BindTable = DenseMap<StringAttr, SmallDenseMap<StringAttr, sv::BindOp>>;
50 llvm::function_ref<
bool(Operation *)> filter) {
51 SmallVector<Value> worklist(rootOp->getOperands());
53 while (!worklist.empty()) {
54 Value operand = worklist.pop_back_val();
55 Operation *definingOp = operand.getDefiningOp();
58 definingOp->hasTrait<mlir::OpTrait::IsIsolatedFromAbove>())
64 if (filter && !filter(definingOp))
68 if (!backwardSlice.contains(definingOp))
69 for (
auto newOperand : llvm::reverse(definingOp->getOperands()))
70 worklist.push_back(newOperand);
71 }
else if (
auto blockArg = operand.dyn_cast<BlockArgument>()) {
72 Block *block = blockArg.getOwner();
73 Operation *parentOp = block->getParentOp();
77 assert(parentOp->getNumRegions() == 1 &&
78 parentOp->getRegion(0).getBlocks().size() == 1);
79 if (!backwardSlice.contains(parentOp))
80 for (
auto newOperand : llvm::reverse(parentOp->getOperands()))
81 worklist.push_back(newOperand);
83 llvm_unreachable(
"No definingOp and not a block argument.");
86 backwardSlice.insert(definingOp);
92 SetVector<Operation *> &blocks) {
94 while (!isa<hw::HWModuleOp>(op->getParentOp())) {
95 op = op->getParentOp();
102 SetVector<Operation *> &results,
103 llvm::function_ref<
bool(Operation *)> filter) {
104 for (
auto *op : roots)
110 static SetVector<Operation *>
112 llvm::function_ref<
bool(Operation *)> filter) {
113 SetVector<Operation *> results;
117 SetVector<Operation *> blocks;
124 results.insert(roots.begin(), roots.end());
125 results.insert(blocks.begin(), blocks.end());
131 static SetVector<Operation *>
133 llvm::function_ref<
bool(Operation *)> rootFn,
134 llvm::function_ref<
bool(Operation *)> filterFn) {
135 SetVector<Operation *> roots;
136 module.walk([&](Operation *op) {
137 if (!isa<hw::HWModuleOp>(op) && rootFn(op))
144 SmallVector<mlir::Attribute> modulePorts) {
145 if (
auto bv = val.dyn_cast<BlockArgument>())
146 return modulePorts[bv.getArgNumber()].cast<StringAttr>();
148 if (
auto *op = val.getDefiningOp()) {
149 if (
auto readinout = dyn_cast<ReadInOutOp>(op)) {
150 if (
auto *readOp = readinout.getInput().getDefiningOp()) {
151 if (
auto wire = dyn_cast<WireOp>(readOp))
152 return wire.getNameAttr();
153 if (
auto reg = dyn_cast<RegOp>(readOp))
154 return reg.getNameAttr();
156 }
else if (
auto inst = dyn_cast<hw::InstanceOp>(op)) {
157 auto index = val.cast<mlir::OpResult>().getResultNumber();
158 SmallString<64> portName = inst.getInstanceName();
160 auto resultName = inst.getResultName(index);
161 if (resultName && !resultName.getValue().empty())
162 portName += resultName.getValue();
164 Twine(index).toVector(portName);
166 }
else if (op->getNumResults() == 1) {
167 if (
auto name = op->getAttrOfType<StringAttr>(
"name"))
169 if (
auto namehint = op->getAttrOfType<StringAttr>(
"sv.namehint"))
182 IRMapping &cutMap, StringRef suffix,
183 Attribute path, Attribute fileName,
187 SmallVector<Value> realInputs;
188 DenseMap<Value, Value> dups;
189 DenseMap<Value, SmallVector<Value>>
192 if (
auto readinout = dyn_cast_or_null<ReadInOutOp>(v.getDefiningOp())) {
193 auto op = readinout.getInput();
194 if (dups.count(op)) {
195 realReads[dups[op]].push_back(v);
200 realInputs.push_back(v);
207 SmallVector<hw::PortInfo> ports;
210 auto srcPorts = op.getInputNames();
211 for (
auto port : llvm::enumerate(realInputs)) {
213 name = portNames.
newName(name.empty() ?
"port_" + Twine(port.index())
215 ports.push_back({{b.getStringAttr(name), port.value().getType(),
226 newMod->setAttr(
"output_file", path);
229 newMod.setCommentAttr(b.getStringAttr(
"VCS coverage exclude_file"));
233 for (
auto port : llvm::enumerate(realInputs)) {
234 cutMap.map(port.value(), newMod.getBody().getArgument(port.index()));
235 for (
auto extra : realReads[port.value()])
236 cutMap.map(extra, newMod.getBody().getArgument(port.index()));
238 cutMap.map(op.getBodyBlock(), newMod.getBodyBlock());
241 b = OpBuilder::atBlockTerminator(op.getBodyBlock());
242 auto inst = b.create<hw::InstanceOp>(
243 op.getLoc(), newMod, newMod.getName(), realInputs, ArrayAttr(),
247 inst->setAttr(
"doNotPrint", b.getBoolAttr(
true));
248 b = OpBuilder::atBlockEnd(
249 &op->getParentOfType<mlir::ModuleOp>()->getRegion(0).front());
251 auto bindOp = b.create<sv::BindOp>(op.getLoc(), op.getNameAttr(),
252 inst.getInnerSymAttr().getSymName());
253 bindTable[op.getNameAttr()][inst.getInnerSymAttr().getSymName()] = bindOp;
255 bindOp->setAttr(
"output_file", fileName);
261 if (!block->empty() && isa<hw::HWModuleOp>(block->getParentOp()))
262 builder.setInsertionPoint(&block->back());
264 builder.setInsertionPointToEnd(block);
271 assert(oldOp->getNumRegions() == newOp->getNumRegions());
272 for (
size_t i = 0, e = oldOp->getNumRegions(); i != e; ++i) {
273 auto &oldRegion = oldOp->getRegion(i);
274 auto &newRegion = newOp->getRegion(i);
275 for (
auto oi = oldRegion.begin(), oe = oldRegion.end(); oi != oe; ++oi) {
276 cutMap.map(&*oi, &newRegion.emplaceBlock());
283 for (
auto arg : op->getOperands()) {
284 auto *argOp = arg.getDefiningOp();
296 for (
auto *op : lateBoundOps)
297 for (
unsigned argidx = 0, e = op->getNumOperands(); argidx < e; ++argidx) {
298 Value arg = op->getOperand(argidx);
299 if (cutMap.contains(arg))
300 op->setOperand(argidx, cutMap.lookup(arg));
307 SetVector<Operation *> &depOps, IRMapping &cutMap,
310 SmallVector<Operation *, 16> lateBoundOps;
311 OpBuilder b = OpBuilder::atBlockBegin(newMod.getBodyBlock());
312 oldMod.walk<WalkOrder::PreOrder>([&](Operation *op) {
313 if (depOps.count(op)) {
315 auto newOp = b.cloneWithoutRegions(*op, cutMap);
318 lateBoundOps.push_back(newOp);
319 if (
auto instance = dyn_cast<hw::InstanceOp>(op)) {
321 instanceGraph.lookup(instance.getModuleNameAttr().getAttr());
331 auto *node = instanceGraph.lookup(op);
333 auto inst = a->getInstance();
336 return inst->hasAttr(
"doNotPrint");
342 for (
auto bind : topLevelModule->getOps<BindOp>()) {
343 hw::InnerRefAttr boundRef = bind.getInstance();
344 bindTable[boundRef.getModule()][boundRef.getName()] = bind;
351 BindTable &bindTable, SmallPtrSetImpl<Operation *> &opsToErase,
352 llvm::DenseSet<hw::InnerRefAttr> &innerRefUsedByNonBindOp) {
355 if (oldMod.getNumOutputPorts() != 0)
360 auto oldModName = oldMod.getModuleNameAttr();
361 for (
auto port : oldMod.getPortList()) {
362 auto sym = port.getSym();
364 for (
auto property : sym) {
366 if (innerRefUsedByNonBindOp.count(innerRef)) {
367 oldMod.emitWarning() <<
"module " << oldMod.getModuleName()
368 <<
" is an input only module but cannot "
369 "be inlined because a signal "
370 << port.
name <<
" is referred by name";
377 for (
auto op : oldMod.getBodyBlock()->getOps<hw::InnerSymbolOpInterface>()) {
378 if (
auto innerSym = op.getInnerSymAttr()) {
379 for (
auto property : innerSym) {
381 if (innerRefUsedByNonBindOp.count(innerRef)) {
382 op.emitWarning() <<
"module " << oldMod.getModuleName()
383 <<
" is an input only module but cannot be inlined "
384 "because signals are referred by name";
394 "expected module for inlining to be instantiated at least once");
398 bool allInlined =
true;
401 auto instLike = use->getInstance<hw::HWInstanceLike>();
408 hw::InstanceOp inst = cast<hw::InstanceOp>(instLike.getOperation());
409 if (inst.getInnerSym().has_value()) {
413 <<
"module " << oldMod.getModuleName()
414 <<
" cannot be inlined because there is an instance with a symbol";
415 diag.attachNote(inst.getLoc());
421 assert(inst.getInputs().size() == oldMod.getNumInputPorts());
422 auto inputPorts = oldMod.getBodyBlock()->getArguments();
423 for (
size_t i = 0, e = inputPorts.size(); i < e; ++i)
424 mapping.map(inputPorts[i], inst.getOperand(i));
428 cast<hw::HWModuleOp>(use->getParent()->getModule());
430 instanceGraph.lookup(instParent);
431 SmallVector<Operation *, 16> lateBoundOps;
432 b.setInsertionPoint(inst);
434 hw::InnerSymbolNamespace nameSpace(instParent);
436 DenseMap<mlir::StringAttr, mlir::StringAttr> symMapping;
438 for (
auto &op : *oldMod.getBodyBlock()) {
440 if (opsToErase.contains(&op))
444 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(op)) {
445 if (
auto innerSym = innerSymOp.getInnerSymAttr()) {
446 for (
auto property : innerSym) {
447 auto oldName =
property.getName();
449 b.getStringAttr(nameSpace.newName(oldName.getValue()));
450 auto result = symMapping.insert({oldName, newName});
452 assert(result.second &&
"inner symbols must be unique");
458 if (
auto innerInst = dyn_cast<hw::InstanceOp>(op)) {
459 if (
auto innerInstSym = innerInst.getInnerSymAttr()) {
461 bindTable[oldMod.getNameAttr()].find(innerInstSym.getSymName());
462 if (it != bindTable[oldMod.getNameAttr()].end()) {
463 sv::BindOp bind = it->second;
464 auto oldInnerRef = bind.getInstanceAttr();
465 auto it = symMapping.find(oldInnerRef.getName());
466 assert(it != symMapping.end() &&
467 "inner sym mapping must be already populated");
468 auto newName = it->second;
471 OpBuilder::InsertionGuard g(b);
473 b.setInsertionPoint(bind);
474 sv::BindOp clonedBind = cast<sv::BindOp>(b.clone(*bind, mapping));
475 clonedBind.setInstanceAttr(newInnerRef);
476 bindTable[instParent.getModuleNameAttr()][newName] =
477 cast<sv::BindOp>(clonedBind);
483 if (!isa<hw::OutputOp>(op)) {
484 Operation *clonedOp = b.clone(op, mapping);
488 lateBoundOps.push_back(clonedOp);
492 if (
auto innerInst = dyn_cast<hw::InstanceOp>(clonedOp)) {
494 instanceGraph.lookup(innerInst.getModuleNameAttr().getAttr());
495 instParentNode->
addInstance(innerInst, innerInstModule);
499 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(clonedOp)) {
500 if (
auto oldInnerSym = innerSymOp.getInnerSymAttr()) {
501 SmallVector<hw::InnerSymPropertiesAttr> properties;
502 for (
auto property : oldInnerSym) {
503 auto newSymName = symMapping[
property.getName()];
505 op.getContext(), newSymName, property.getFieldID(),
506 property.getSymVisibility()));
509 innerSymOp.setInnerSymbolAttr(innerSym);
519 assert(inst.use_empty() &&
"inlined instance should have no uses");
521 opsToErase.insert(inst);
527 for (
auto [_, bind] : bindTable[oldMod.getNameAttr()])
529 bindTable[oldMod.getNameAttr()].clear();
530 instanceGraph.erase(node);
531 opsToErase.insert(oldMod);
535 static bool isAssertOp(hw::HWSymbolCache &symCache, Operation *op) {
538 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
539 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
540 if (mod->getAttr(
"firrtl.extract.assert.extra"))
546 if (
auto error = dyn_cast<ErrorOp>(op)) {
547 if (
auto message = error.getMessage())
548 return message->starts_with(
"assert:") ||
549 message->starts_with(
"assert failed (verification library)") ||
550 message->starts_with(
"Assertion failed") ||
551 message->starts_with(
"assertNotX:") ||
552 message->contains(
"[verif-library-assert]");
556 return isa<AssertOp, FinishOp, FWriteOp, AssertConcurrentOp, FatalOp,
557 verif::AssertOp>(op);
560 static bool isCoverOp(hw::HWSymbolCache &symCache, Operation *op) {
563 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
564 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
565 if (mod->getAttr(
"firrtl.extract.cover.extra"))
567 return isa<CoverOp, CoverConcurrentOp, verif::CoverOp>(op);
570 static bool isAssumeOp(hw::HWSymbolCache &symCache, Operation *op) {
573 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
574 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
575 if (mod->getAttr(
"firrtl.extract.assume.extra"))
578 return isa<AssumeOp, AssumeConcurrentOp, verif::AssumeOp>(op);
583 bool disableInstanceExtraction =
false,
584 bool disableRegisterExtraction =
false) {
587 if (isa<hw::OutputOp>(op))
591 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(op))
592 if (
auto innerSym = innerSymOp.getInnerSymAttr())
593 if (!innerSym.empty())
604 if (isa<hw::InstanceOp>(op))
605 return disableInstanceExtraction;
606 if (isa<seq::FirRegOp>(op))
607 return disableRegisterExtraction;
612 if (isa<sv::ReadInOutOp>(op))
616 if (op->getNumRegions() > 0)
620 return !mlir::isMemoryEffectFree(op);
629 struct SVExtractTestCodeImplPass
630 :
public SVExtractTestCodeBase<SVExtractTestCodeImplPass> {
631 SVExtractTestCodeImplPass(
bool disableInstanceExtraction,
632 bool disableRegisterExtraction,
633 bool disableModuleInlining) {
634 this->disableInstanceExtraction = disableInstanceExtraction;
635 this->disableRegisterExtraction = disableRegisterExtraction;
636 this->disableModuleInlining = disableModuleInlining;
638 void runOnOperation()
override;
642 bool doModule(
hw::HWModuleOp module, llvm::function_ref<
bool(Operation *)> fn,
643 StringRef suffix, Attribute path, Attribute bindFile,
644 BindTable &bindTable, SmallPtrSetImpl<Operation *> &opsToErase,
645 SetVector<Operation *> &opsInDesign) {
646 bool hasError =
false;
648 SetVector<Operation *> roots;
649 module->walk([&fn, &roots, &hasError](Operation *op) {
652 if (op->getNumResults()) {
653 op->emitError(
"Extracting op with result");
670 return !opsInDesign.count(op) ||
671 op->hasTrait<mlir::OpTrait::ConstantLike>();
676 for (
auto *op : opsToClone) {
677 for (
auto arg : op->getOperands()) {
678 auto argOp = arg.getDefiningOp();
679 if (!opsToClone.count(argOp))
683 opsToErase.insert(op);
686 numOpsExtracted += opsToClone.size();
691 bindFile, bindTable);
694 instanceGraph->addHWModule(bmod);
697 migrateOps(module, bmod, opsToClone, cutMap, *instanceGraph);
700 for (
auto *op : roots) {
701 opsToErase.erase(op);
714 void SVExtractTestCodeImplPass::runOnOperation() {
715 this->instanceGraph = &getAnalysis<circt::hw::InstanceGraph>();
717 auto top = getOperation();
724 DenseSet<hw::InnerRefAttr> innerRefUsedByNonBindOp;
725 top.walk([&](Operation *op) {
726 if (!isa<sv::BindOp>(op))
727 for (
auto attr : op->getAttrs())
728 attr.getValue().walk([&](hw::InnerRefAttr attr) {
729 innerRefUsedByNonBindOp.insert(attr);
733 auto *topLevelModule = top.getBody();
735 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assert");
737 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assume");
739 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.cover");
740 auto assertBindFile =
741 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assert.bindfile");
742 auto assumeBindFile =
743 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assume.bindfile");
745 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.cover.bindfile");
747 hw::HWSymbolCache symCache;
748 symCache.addDefinitions(top);
751 auto isAssert = [&symCache](Operation *op) ->
bool {
755 auto isAssume = [&symCache](Operation *op) ->
bool {
759 auto isCover = [&symCache](Operation *op) ->
bool {
769 for (
auto &op : llvm::make_early_inc_range(topLevelModule->getOperations())) {
770 if (
auto rtlmod = dyn_cast<hw::HWModuleOp>(op)) {
776 if (
isBound(rtlmod, *instanceGraph))
780 if (rtlmod->hasAttr(
"firrtl.extract.do_not_extract")) {
781 rtlmod->removeAttr(
"firrtl.extract.do_not_extract");
791 return isInDesign(symCache, op, disableInstanceExtraction,
792 disableRegisterExtraction);
796 SmallPtrSet<Operation *, 32> opsToErase;
797 bool anyThingExtracted =
false;
799 doModule(rtlmod, isAssert,
"_assert", assertDir, assertBindFile,
800 bindTable, opsToErase, opsInDesign);
802 doModule(rtlmod, isAssume,
"_assume", assumeDir, assumeBindFile,
803 bindTable, opsToErase, opsInDesign);
805 doModule(rtlmod, isCover,
"_cover", coverDir, coverBindFile,
806 bindTable, opsToErase, opsInDesign);
809 if (!anyThingExtracted && rtlmod.getNumOutputPorts() != 0)
827 disableRegisterExtraction) &&
828 !opsToErase.contains(op);
833 op.walk([&](Operation *operation) {
835 if (&op == operation)
839 if (opsAlive.count(operation))
840 opsToErase.erase(operation);
842 opsToErase.insert(operation);
846 if (!disableModuleInlining)
848 innerRefUsedByNonBindOp);
850 numOpsErased += opsToErase.size();
851 while (!opsToErase.empty()) {
852 Operation *op = *opsToErase.begin();
853 op->walk([&](Operation *erasedOp) { opsToErase.erase(erasedOp); });
862 for (
auto &op : topLevelModule->getOperations())
863 if (isa<hw::HWModuleOp, hw::HWModuleExternOp>(op)) {
864 op.removeAttr(
"firrtl.extract.assert.extra");
865 op.removeAttr(
"firrtl.extract.cover.extra");
866 op.removeAttr(
"firrtl.extract.assume.extra");
869 markAnalysesPreserved<circt::hw::InstanceGraph>();
872 std::unique_ptr<Pass>
874 bool disableRegisterExtraction,
875 bool disableModuleInlining) {
876 return std::make_unique<SVExtractTestCodeImplPass>(disableInstanceExtraction,
877 disableRegisterExtraction,
878 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.
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)