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.getResultName(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->setAttr(
"doNotPrint", b.getBoolAttr(
true));
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();
344 return inst->hasAttr(
"doNotPrint");
350 for (
auto bind : topLevelModule->getOps<BindOp>()) {
351 hw::InnerRefAttr boundRef = bind.getInstance();
352 bindTable[boundRef.getModule()][boundRef.getName()] = bind;
359 BindTable &bindTable, SmallPtrSetImpl<Operation *> &opsToErase,
360 llvm::DenseSet<hw::InnerRefAttr> &innerRefUsedByNonBindOp) {
363 if (oldMod.getNumOutputPorts() != 0)
368 auto oldModName = oldMod.getModuleNameAttr();
369 for (
auto port : oldMod.getPortList()) {
370 auto sym = port.getSym();
372 for (
auto property : sym) {
374 if (innerRefUsedByNonBindOp.count(innerRef)) {
375 oldMod.emitWarning() <<
"module " << oldMod.getModuleName()
376 <<
" is an input only module but cannot "
377 "be inlined because a signal "
378 << port.
name <<
" is referred by name";
385 for (
auto op : oldMod.getBodyBlock()->getOps<hw::InnerSymbolOpInterface>()) {
386 if (
auto innerSym = op.getInnerSymAttr()) {
387 for (
auto property : innerSym) {
389 if (innerRefUsedByNonBindOp.count(innerRef)) {
390 op.emitWarning() <<
"module " << oldMod.getModuleName()
391 <<
" is an input only module but cannot be inlined "
392 "because signals are referred by name";
402 "expected module for inlining to be instantiated at least once");
406 bool allInlined =
true;
409 auto instLike = use->getInstance<hw::HWInstanceLike>();
416 hw::InstanceOp inst = cast<hw::InstanceOp>(instLike.getOperation());
417 if (inst.getInnerSym().has_value()) {
421 <<
"module " << oldMod.getModuleName()
422 <<
" cannot be inlined because there is an instance with a symbol";
423 diag.attachNote(inst.getLoc());
429 assert(inst.getInputs().size() == oldMod.getNumInputPorts());
430 auto inputPorts = oldMod.getBodyBlock()->getArguments();
431 for (
size_t i = 0, e = inputPorts.size(); i < e; ++i)
432 mapping.map(inputPorts[i], inst.getOperand(i));
436 cast<hw::HWModuleOp>(use->getParent()->getModule());
438 instanceGraph.lookup(instParent);
439 SmallVector<Operation *, 16> lateBoundOps;
440 b.setInsertionPoint(inst);
442 hw::InnerSymbolNamespace nameSpace(instParent);
444 DenseMap<mlir::StringAttr, mlir::StringAttr> symMapping;
446 for (
auto &op : *oldMod.getBodyBlock()) {
448 if (opsToErase.contains(&op))
452 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(op)) {
453 if (
auto innerSym = innerSymOp.getInnerSymAttr()) {
454 for (
auto property : innerSym) {
455 auto oldName =
property.getName();
457 b.getStringAttr(nameSpace.newName(oldName.getValue()));
458 auto result = symMapping.insert({oldName, newName});
460 assert(result.second &&
"inner symbols must be unique");
466 if (
auto innerInst = dyn_cast<hw::InstanceOp>(op)) {
467 if (
auto innerInstSym = innerInst.getInnerSymAttr()) {
469 bindTable[oldMod.getNameAttr()].find(innerInstSym.getSymName());
470 if (it != bindTable[oldMod.getNameAttr()].end()) {
471 sv::BindOp bind = it->second;
472 auto oldInnerRef = bind.getInstanceAttr();
473 auto it = symMapping.find(oldInnerRef.getName());
474 assert(it != symMapping.end() &&
475 "inner sym mapping must be already populated");
476 auto newName = it->second;
479 OpBuilder::InsertionGuard g(b);
481 b.setInsertionPoint(bind);
482 sv::BindOp clonedBind = cast<sv::BindOp>(b.clone(*bind, mapping));
483 clonedBind.setInstanceAttr(newInnerRef);
484 bindTable[instParent.getModuleNameAttr()][newName] =
485 cast<sv::BindOp>(clonedBind);
491 if (!isa<hw::OutputOp>(op)) {
492 Operation *clonedOp = b.clone(op, mapping);
496 lateBoundOps.push_back(clonedOp);
500 if (
auto innerInst = dyn_cast<hw::InstanceOp>(clonedOp)) {
502 instanceGraph.lookup(innerInst.getModuleNameAttr().getAttr());
503 instParentNode->
addInstance(innerInst, innerInstModule);
507 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(clonedOp)) {
508 if (
auto oldInnerSym = innerSymOp.getInnerSymAttr()) {
509 SmallVector<hw::InnerSymPropertiesAttr> properties;
510 for (
auto property : oldInnerSym) {
511 auto newSymName = symMapping[
property.getName()];
513 op.getContext(), newSymName, property.getFieldID(),
514 property.getSymVisibility()));
517 innerSymOp.setInnerSymbolAttr(innerSym);
527 assert(inst.use_empty() &&
"inlined instance should have no uses");
529 opsToErase.insert(inst);
535 for (
auto [_, bind] : bindTable[oldMod.getNameAttr()])
537 bindTable[oldMod.getNameAttr()].clear();
538 instanceGraph.erase(node);
539 opsToErase.insert(oldMod);
543 static bool isAssertOp(hw::HWSymbolCache &symCache, Operation *op) {
546 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
547 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
548 if (mod->getAttr(
"firrtl.extract.assert.extra"))
554 if (
auto error = dyn_cast<ErrorOp>(op)) {
555 if (
auto message = error.getMessage())
556 return message->starts_with(
"assert:") ||
557 message->starts_with(
"assert failed (verification library)") ||
558 message->starts_with(
"Assertion failed") ||
559 message->starts_with(
"assertNotX:") ||
560 message->contains(
"[verif-library-assert]");
564 return isa<AssertOp, FinishOp, FWriteOp, AssertConcurrentOp, FatalOp,
565 verif::AssertOp, verif::ClockedAssertOp>(op);
568 static bool isCoverOp(hw::HWSymbolCache &symCache, Operation *op) {
571 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
572 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
573 if (mod->getAttr(
"firrtl.extract.cover.extra"))
575 return isa<CoverOp, CoverConcurrentOp, verif::CoverOp, verif::ClockedCoverOp>(
579 static bool isAssumeOp(hw::HWSymbolCache &symCache, Operation *op) {
582 if (
auto inst = dyn_cast<hw::InstanceOp>(op))
583 if (
auto *mod = symCache.getDefinition(inst.getModuleNameAttr()))
584 if (mod->getAttr(
"firrtl.extract.assume.extra"))
587 return isa<AssumeOp, AssumeConcurrentOp, verif::AssumeOp,
588 verif::ClockedAssumeOp>(op);
593 bool disableInstanceExtraction =
false,
594 bool disableRegisterExtraction =
false) {
597 if (isa<hw::OutputOp>(op))
601 if (
auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(op))
602 if (
auto innerSym = innerSymOp.getInnerSymAttr())
603 if (!innerSym.empty())
614 if (isa<hw::InstanceOp>(op))
615 return disableInstanceExtraction;
616 if (isa<seq::FirRegOp>(op))
617 return disableRegisterExtraction;
622 if (isa<sv::ReadInOutOp>(op))
626 if (op->getNumRegions() > 0)
630 return !mlir::isMemoryEffectFree(op);
639 struct SVExtractTestCodeImplPass
640 :
public circt::sv::impl::SVExtractTestCodeBase<SVExtractTestCodeImplPass> {
641 SVExtractTestCodeImplPass(
bool disableInstanceExtraction,
642 bool disableRegisterExtraction,
643 bool disableModuleInlining) {
644 this->disableInstanceExtraction = disableInstanceExtraction;
645 this->disableRegisterExtraction = disableRegisterExtraction;
646 this->disableModuleInlining = disableModuleInlining;
648 void runOnOperation()
override;
652 bool doModule(
hw::HWModuleOp module, llvm::function_ref<
bool(Operation *)> fn,
653 StringRef suffix, Attribute path, Attribute bindFile,
654 BindTable &bindTable, SmallPtrSetImpl<Operation *> &opsToErase,
655 SetVector<Operation *> &opsInDesign) {
656 bool hasError =
false;
658 SetVector<Operation *> roots;
659 module->walk([&fn, &roots, &hasError](Operation *op) {
662 if (op->getNumResults()) {
663 op->emitError(
"Extracting op with result");
680 return !opsInDesign.count(op) ||
681 op->hasTrait<mlir::OpTrait::ConstantLike>();
685 SetVector<Value> inputs;
686 for (
auto *op : opsToClone) {
687 for (
auto arg : op->getOperands()) {
688 auto argOp = arg.getDefiningOp();
689 if (!opsToClone.count(argOp))
693 opsToErase.insert(op);
696 numOpsExtracted += opsToClone.size();
701 bindFile, bindTable);
704 instanceGraph->addHWModule(bmod);
707 migrateOps(module, bmod, opsToClone, cutMap, *instanceGraph);
710 for (
auto *op : roots) {
711 opsToErase.erase(op);
724 void SVExtractTestCodeImplPass::runOnOperation() {
725 this->instanceGraph = &getAnalysis<circt::hw::InstanceGraph>();
727 auto top = getOperation();
734 DenseSet<hw::InnerRefAttr> innerRefUsedByNonBindOp;
735 top.walk([&](Operation *op) {
736 if (!isa<sv::BindOp>(op))
737 for (
auto attr : op->getAttrs())
738 attr.getValue().walk([&](hw::InnerRefAttr attr) {
739 innerRefUsedByNonBindOp.insert(attr);
743 auto *topLevelModule = top.getBody();
745 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assert");
747 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assume");
749 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.cover");
750 auto assertBindFile =
751 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assert.bindfile");
752 auto assumeBindFile =
753 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.assume.bindfile");
755 top->getAttrOfType<hw::OutputFileAttr>(
"firrtl.extract.cover.bindfile");
757 hw::HWSymbolCache symCache;
758 symCache.addDefinitions(top);
761 auto isAssert = [&symCache](Operation *op) ->
bool {
765 auto isAssume = [&symCache](Operation *op) ->
bool {
769 auto isCover = [&symCache](Operation *op) ->
bool {
779 for (
auto &op : llvm::make_early_inc_range(topLevelModule->getOperations())) {
780 if (
auto rtlmod = dyn_cast<hw::HWModuleOp>(op)) {
786 if (
isBound(rtlmod, *instanceGraph))
790 if (rtlmod->hasAttr(
"firrtl.extract.do_not_extract")) {
791 rtlmod->removeAttr(
"firrtl.extract.do_not_extract");
801 return isInDesign(symCache, op, disableInstanceExtraction,
802 disableRegisterExtraction);
806 SmallPtrSet<Operation *, 32> opsToErase;
807 bool anyThingExtracted =
false;
809 doModule(rtlmod, isAssert,
"_assert", assertDir, assertBindFile,
810 bindTable, opsToErase, opsInDesign);
812 doModule(rtlmod, isAssume,
"_assume", assumeDir, assumeBindFile,
813 bindTable, opsToErase, opsInDesign);
815 doModule(rtlmod, isCover,
"_cover", coverDir, coverBindFile,
816 bindTable, opsToErase, opsInDesign);
819 if (!anyThingExtracted && rtlmod.getNumOutputPorts() != 0)
837 disableRegisterExtraction) &&
838 !opsToErase.contains(op);
843 op.walk([&](Operation *operation) {
845 if (&op == operation)
849 if (opsAlive.count(operation))
850 opsToErase.erase(operation);
852 opsToErase.insert(operation);
856 if (!disableModuleInlining)
858 innerRefUsedByNonBindOp);
860 numOpsErased += opsToErase.size();
861 while (!opsToErase.empty()) {
862 Operation *op = *opsToErase.begin();
863 op->walk([&](Operation *erasedOp) { opsToErase.erase(erasedOp); });
872 for (
auto &op : topLevelModule->getOperations())
873 if (isa<hw::HWModuleOp, hw::HWModuleExternOp>(op)) {
874 op.removeAttr(
"firrtl.extract.assert.extra");
875 op.removeAttr(
"firrtl.extract.cover.extra");
876 op.removeAttr(
"firrtl.extract.assume.extra");
879 markAnalysesPreserved<circt::hw::InstanceGraph>();
882 std::unique_ptr<Pass>
884 bool disableRegisterExtraction,
885 bool disableModuleInlining) {
886 return std::make_unique<SVExtractTestCodeImplPass>(disableInstanceExtraction,
887 disableRegisterExtraction,
888 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)