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"
46using 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)
118static 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());
139static 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);
173 return StringAttr::get(val.getContext(), portName);
174 }
else if (op->getNumResults() == 1) {
175 if (
auto name = op->getAttrOfType<StringAttr>(
"name"))
177 if (
auto namehint = op->getAttrOfType<StringAttr>(
"sv.namehint"))
182 return StringAttr::get(val.getContext(),
"");
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(),
224 hw::ModulePort::Direction::Input},
234 newMod->setAttr(
"output_file", path);
235 if (
auto fragments = op->getAttr(emit::getFragmentsAttrName()))
236 newMod->setAttr(emit::getFragmentsAttrName(), fragments);
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(),
252 hw::InnerSymAttr::get(b.getStringAttr(
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) {
374 auto innerRef = hw::InnerRefAttr::get(oldModName, property.getName());
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) {
389 auto innerRef = hw::InnerRefAttr::get(oldModName, property.getName());
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);
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;
479 hw::InnerRefAttr::get(instParent.getModuleNameAttr(), newName);
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()];
513 properties.push_back(hw::InnerSymPropertiesAttr::get(
514 op.getContext(), newSymName, property.getFieldID(),
515 property.getSymVisibility()));
517 auto innerSym = hw::InnerSymAttr::get(op.getContext(), properties);
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);
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);
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>(
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);
640struct 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);
725void 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");
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>();
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.
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
This stores lookup tables to make manipulating and working with the IR more efficient.
mlir::Operation * getDefinition(mlir::Attribute attr) const override
Lookup a definition for 'symbol' in the cache.
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
void erase(igraph::InstanceGraphNode *node) override
Erases a module, updating links to entry.
This is a Node in the InstanceGraph.
llvm::iterator_range< UseIterator > uses()
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.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
This is an edge in the InstanceGraph.
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.