CIRCT 20.0.0git
Loading...
Searching...
No Matches
SVExtractTestCode.cpp
Go to the documentation of this file.
1//===- SVExtractTestCode.cpp - SV Simulation Extraction Pass --------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This transformation pass extracts simulation constructs to submodules. It
10// will take simulation operations, write, finish, assert, assume, and cover and
11// extract them and the dataflow into them into a separate module. This module
12// is then instantiated in the original module.
13//
14//===----------------------------------------------------------------------===//
15
28#include "mlir/IR/Builders.h"
29#include "mlir/IR/IRMapping.h"
30#include "mlir/Pass/Pass.h"
31#include "llvm/ADT/SetVector.h"
32
33#include <set>
34
35namespace circt {
36namespace sv {
37#define GEN_PASS_DEF_SVEXTRACTTESTCODE
38#include "circt/Dialect/SV/SVPasses.h.inc"
39} // namespace sv
40} // namespace circt
41
42using namespace mlir;
43using namespace circt;
44using namespace sv;
45
46using BindTable = DenseMap<StringAttr, SmallDenseMap<StringAttr, sv::BindOp>>;
47
48//===----------------------------------------------------------------------===//
49// StubExternalModules Helpers
50//===----------------------------------------------------------------------===//
51
52// Reimplemented from SliceAnalysis to use a worklist rather than recursion and
53// non-insert ordered set. Implement this as a DFS and not a BFS so that the
54// order is stable across changes to intermediary operations. (It is then
55// necessary to use the _operands_ as a worklist and not the _operations_.)
56static void
57getBackwardSliceSimple(Operation *rootOp, SetVector<Operation *> &backwardSlice,
58 llvm::function_ref<bool(Operation *)> filter) {
59 SmallVector<Value> worklist(rootOp->getOperands());
60
61 while (!worklist.empty()) {
62 Value operand = worklist.pop_back_val();
63 Operation *definingOp = operand.getDefiningOp();
64
65 if (!definingOp ||
66 definingOp->hasTrait<mlir::OpTrait::IsIsolatedFromAbove>())
67 continue;
68
69 // Evaluate whether we should keep this def.
70 // This is useful in particular to implement scoping; i.e. return the
71 // transitive backwardSlice in the current scope.
72 if (filter && !filter(definingOp))
73 continue;
74
75 if (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();
82 // TODO: determine whether we want to recurse backward into the other
83 // blocks of parentOp, which are not technically backward unless they
84 // flow into us. For now, just bail.
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);
90 } else {
91 llvm_unreachable("No definingOp and not a block argument.");
92 }
93
94 backwardSlice.insert(definingOp);
95 }
96}
97
98// Compute the ops defining the blocks a set of ops are in.
99static void blockSlice(SetVector<Operation *> &ops,
100 SetVector<Operation *> &blocks) {
101 for (auto op : ops) {
102 while (!isa<hw::HWModuleOp>(op->getParentOp())) {
103 op = op->getParentOp();
104 blocks.insert(op);
105 }
106 }
107}
108
109static void computeSlice(SetVector<Operation *> &roots,
110 SetVector<Operation *> &results,
111 llvm::function_ref<bool(Operation *)> filter) {
112 for (auto *op : roots)
113 getBackwardSliceSimple(op, results, filter);
114}
115
116// Return a backward slice started from `roots` until dataflow reaches to an
117// operations for which `filter` returns false.
118static SetVector<Operation *>
119getBackwardSlice(SetVector<Operation *> &roots,
120 llvm::function_ref<bool(Operation *)> filter) {
121 SetVector<Operation *> results;
122 computeSlice(roots, results, filter);
123
124 // Get Blocks
125 SetVector<Operation *> blocks;
126 blockSlice(roots, blocks);
127 blockSlice(results, blocks);
128
129 // Make sure dataflow to block args (if conds, etc) is included
130 computeSlice(blocks, results, filter);
131
132 results.insert(roots.begin(), roots.end());
133 results.insert(blocks.begin(), blocks.end());
134 return results;
135}
136
137// Return a backward slice started from opertaions for which `rootFn` returns
138// true.
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))
146 roots.insert(op);
147 });
148 return getBackwardSlice(roots, filterFn);
149}
150
151static StringAttr getNameForPort(Value val,
152 SmallVector<mlir::Attribute> modulePorts) {
153 if (auto bv = dyn_cast<BlockArgument>(val))
154 return cast<StringAttr>(modulePorts[bv.getArgNumber()]);
155
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();
163 }
164 } else if (auto inst = dyn_cast<hw::InstanceOp>(op)) {
165 auto index = cast<mlir::OpResult>(val).getResultNumber();
166 SmallString<64> portName = inst.getInstanceName();
167 portName += ".";
168 auto resultName = inst.getOutputName(index);
169 if (resultName && !resultName.getValue().empty())
170 portName += resultName.getValue();
171 else
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"))
176 return name;
177 if (auto namehint = op->getAttrOfType<StringAttr>("sv.namehint"))
178 return namehint;
179 }
180 }
181
182 return StringAttr::get(val.getContext(), "");
183}
184
185// Given a set of values, construct a module and bind instance of that module
186// that passes those values through. Returns the new module and the instance
187// pointing to it.
189 SetVector<Value> &inputs,
190 IRMapping &cutMap, StringRef suffix,
191 Attribute path, Attribute fileName,
192 BindTable &bindTable) {
193 // Filter duplicates and track duplicate reads of elements so we don't
194 // make ports for them
195 SmallVector<Value> realInputs;
196 DenseMap<Value, Value> dups; // wire,reg,lhs -> read
197 DenseMap<Value, SmallVector<Value>>
198 realReads; // port mapped read -> dup reads
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);
204 continue;
205 }
206 dups[op] = v;
207 }
208 realInputs.push_back(v);
209 }
210
211 // Create the extracted module right next to the original one.
212 OpBuilder b(op);
213
214 // Construct the ports, this is just the input Values
215 SmallVector<hw::PortInfo> ports;
216 {
217 Namespace portNames;
218 auto srcPorts = op.getInputNames();
219 for (auto port : llvm::enumerate(realInputs)) {
220 auto name = getNameForPort(port.value(), srcPorts).getValue();
221 name = portNames.newName(name.empty() ? "port_" + Twine(port.index())
222 : name);
223 ports.push_back({{b.getStringAttr(name), port.value().getType(),
224 hw::ModulePort::Direction::Input},
225 port.index()});
226 }
227 }
228
229 // Create the module, setting the output path if indicated.
230 auto newMod = b.create<hw::HWModuleOp>(
231 op.getLoc(),
232 b.getStringAttr(getVerilogModuleNameAttr(op).getValue() + suffix), ports);
233 if (path)
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"));
238 newMod.setPrivate();
239
240 // Update the mapping from old values to cloned values
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()));
245 }
246 cutMap.map(op.getBodyBlock(), newMod.getBodyBlock());
247
248 // Add an instance in the old module for the extracted module
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(
253 ("__ETC_" + getVerilogModuleNameAttr(op).getValue() + suffix)
254 .str())));
255 inst.setDoNotPrintAttr(b.getUnitAttr());
256 b = OpBuilder::atBlockEnd(
257 &op->getParentOfType<mlir::ModuleOp>()->getRegion(0).front());
258
259 auto bindOp = b.create<sv::BindOp>(op.getLoc(), op.getNameAttr(),
260 inst.getInnerSymAttr().getSymName());
261 bindTable[op.getNameAttr()][inst.getInnerSymAttr().getSymName()] = bindOp;
262 if (fileName)
263 bindOp->setAttr("output_file", fileName);
264 return newMod;
265}
266
267// Some blocks have terminators, some don't
268static void setInsertPointToEndOrTerminator(OpBuilder &builder, Block *block) {
269 if (!block->empty() && isa<hw::HWModuleOp>(block->getParentOp()))
270 builder.setInsertionPoint(&block->back());
271 else
272 builder.setInsertionPointToEnd(block);
273}
274
275// Shallow clone, which we use to not clone the content of blocks, doesn't
276// clone the regions, so create all the blocks we need and update the mapping.
277static void addBlockMapping(IRMapping &cutMap, Operation *oldOp,
278 Operation *newOp) {
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());
285 }
286 }
287}
288
289// Check if op has any operand using a value that isn't yet defined.
290static bool hasOoOArgs(hw::HWModuleOp newMod, Operation *op) {
291 for (auto arg : op->getOperands()) {
292 auto *argOp = arg.getDefiningOp(); // may be null
293 if (!argOp)
294 continue;
295 if (argOp->getParentOfType<hw::HWModuleOp>() != newMod)
296 return true;
297 }
298 return false;
299}
300
301// Update any operand which was emitted before its defining op was.
302static void updateOoOArgs(SmallVectorImpl<Operation *> &lateBoundOps,
303 IRMapping &cutMap) {
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));
309 }
310}
311
312// Do the cloning, which is just a pre-order traversal over the module looking
313// for marked ops.
314static void migrateOps(hw::HWModuleOp oldMod, hw::HWModuleOp newMod,
315 SetVector<Operation *> &depOps, IRMapping &cutMap,
316 hw::InstanceGraph &instanceGraph) {
317 igraph::InstanceGraphNode *newModNode = instanceGraph.lookup(newMod);
318 SmallVector<Operation *, 16> lateBoundOps;
319 OpBuilder b = OpBuilder::atBlockBegin(newMod.getBodyBlock());
320 oldMod.walk<WalkOrder::PreOrder>([&](Operation *op) {
321 if (depOps.count(op)) {
322 setInsertPointToEndOrTerminator(b, cutMap.lookup(op->getBlock()));
323 auto newOp = b.cloneWithoutRegions(*op, cutMap);
324 addBlockMapping(cutMap, op, newOp);
325 if (hasOoOArgs(newMod, newOp))
326 lateBoundOps.push_back(newOp);
327 if (auto instance = dyn_cast<hw::InstanceOp>(op)) {
329 instanceGraph.lookup(instance.getModuleNameAttr().getAttr());
330 newModNode->addInstance(instance, instMod);
331 }
332 }
333 });
334 updateOoOArgs(lateBoundOps, cutMap);
335}
336
337// Check if the module has already been bound.
338static bool isBound(hw::HWModuleLike op, hw::InstanceGraph &instanceGraph) {
339 auto *node = instanceGraph.lookup(op);
340 return llvm::any_of(node->uses(), [](igraph::InstanceRecord *a) {
341 auto inst = a->getInstance<hw::HWInstanceLike>();
342 if (!inst)
343 return false;
344
345 return inst.getDoNotPrint();
346 });
347}
348
349// Add any existing bindings to the bind table.
350static void addExistingBinds(Block *topLevelModule, BindTable &bindTable) {
351 for (auto bind : topLevelModule->getOps<BindOp>()) {
352 hw::InnerRefAttr boundRef = bind.getInstance();
353 bindTable[boundRef.getModule()][boundRef.getName()] = bind;
354 }
355}
356
357// Inline any modules that only have inputs for test code.
358static void
360 BindTable &bindTable, SmallPtrSetImpl<Operation *> &opsToErase,
361 llvm::DenseSet<hw::InnerRefAttr> &innerRefUsedByNonBindOp) {
362
363 // Check if the module only has inputs.
364 if (oldMod.getNumOutputPorts() != 0)
365 return;
366
367 // Check if it's ok to inline. We cannot inline the module if there exists a
368 // declaration with an inner symbol referred by non-bind ops (e.g. hierpath).
369 auto oldModName = oldMod.getModuleNameAttr();
370 for (auto port : oldMod.getPortList()) {
371 auto sym = port.getSym();
372 if (sym) {
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";
380 return;
381 }
382 }
383 }
384 }
385
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";
394 return;
395 }
396 }
397 }
398 }
399
400 // Get the instance graph node for the old module.
401 igraph::InstanceGraphNode *node = instanceGraph.lookup(oldMod);
402 assert(!node->noUses() &&
403 "expected module for inlining to be instantiated at least once");
404
405 // Iterate through each instance of the module.
406 OpBuilder b(oldMod);
407 bool allInlined = true;
408 for (igraph::InstanceRecord *use : llvm::make_early_inc_range(node->uses())) {
409 // If there is no instance, move on.
410 auto instLike = use->getInstance<hw::HWInstanceLike>();
411 if (!instLike) {
412 allInlined = false;
413 continue;
414 }
415
416 // If the instance had a symbol, we can't inline it without more work.
417 hw::InstanceOp inst = cast<hw::InstanceOp>(instLike.getOperation());
418 if (inst.getInnerSym().has_value()) {
419 allInlined = false;
420 auto diag =
421 oldMod.emitWarning()
422 << "module " << oldMod.getModuleName()
423 << " cannot be inlined because there is an instance with a symbol";
424 diag.attachNote(inst.getLoc());
425 continue;
426 }
427
428 // Build a mapping from module block arguments to instance inputs.
429 IRMapping mapping;
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));
434
435 // Inline the body at the instantiation site.
436 hw::HWModuleOp instParent =
437 cast<hw::HWModuleOp>(use->getParent()->getModule());
438 igraph::InstanceGraphNode *instParentNode =
439 instanceGraph.lookup(instParent);
440 SmallVector<Operation *, 16> lateBoundOps;
441 b.setInsertionPoint(inst);
442 // Namespace that tracks inner symbols in the parent module.
443 hw::InnerSymbolNamespace nameSpace(instParent);
444 // A map from old inner symbols to new ones.
445 DenseMap<mlir::StringAttr, mlir::StringAttr> symMapping;
446
447 for (auto &op : *oldMod.getBodyBlock()) {
448 // If the op was erased by instance extraction, don't copy it over.
449 if (opsToErase.contains(&op))
450 continue;
451
452 // If the op has an inner sym, first create a new inner sym for it.
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();
457 auto newName =
458 b.getStringAttr(nameSpace.newName(oldName.getValue()));
459 auto result = symMapping.insert({oldName, newName});
460 (void)result;
461 assert(result.second && "inner symbols must be unique");
462 }
463 }
464 }
465
466 // For instances in the bind table, update the bind with the new parent.
467 if (auto innerInst = dyn_cast<hw::InstanceOp>(op)) {
468 if (auto innerInstSym = innerInst.getInnerSymAttr()) {
469 auto it =
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;
478 auto newInnerRef =
479 hw::InnerRefAttr::get(instParent.getModuleNameAttr(), newName);
480 OpBuilder::InsertionGuard g(b);
481 // Clone bind operations.
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);
487 }
488 }
489 }
490
491 // For all ops besides the output, clone into the parent body.
492 if (!isa<hw::OutputOp>(op)) {
493 Operation *clonedOp = b.clone(op, mapping);
494 // If some of the operands haven't been cloned over yet, due to cycles,
495 // remember to revisit this op.
496 if (hasOoOArgs(instParent, clonedOp))
497 lateBoundOps.push_back(clonedOp);
498
499 // If the cloned op is an instance, record it within the new parent in
500 // the instance graph.
501 if (auto innerInst = dyn_cast<hw::InstanceOp>(clonedOp)) {
502 igraph::InstanceGraphNode *innerInstModule =
503 instanceGraph.lookup(innerInst.getModuleNameAttr().getAttr());
504 instParentNode->addInstance(innerInst, innerInstModule);
505 }
506
507 // If the cloned op has an inner sym, then attach an updated inner sym.
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()));
516 }
517 auto innerSym = hw::InnerSymAttr::get(op.getContext(), properties);
518 innerSymOp.setInnerSymbolAttr(innerSym);
519 }
520 }
521 }
522 }
523
524 // Map over any ops that didn't have their operands mapped when cloned.
525 updateOoOArgs(lateBoundOps, mapping);
526
527 // Erase the old instantiation site.
528 assert(inst.use_empty() && "inlined instance should have no uses");
529 use->erase();
530 opsToErase.insert(inst);
531 }
532
533 // If all instances were inlined, remove the module.
534 if (allInlined) {
535 // Erase old bind statements.
536 for (auto [_, bind] : bindTable[oldMod.getNameAttr()])
537 bind.erase();
538 bindTable[oldMod.getNameAttr()].clear();
539 instanceGraph.erase(node);
540 opsToErase.insert(oldMod);
541 }
542}
543
544static bool isAssertOp(hw::HWSymbolCache &symCache, Operation *op) {
545 // Symbols not in the cache will only be fore instances added by an extract
546 // phase and are not instances that could possibly have extract flags on them.
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"))
550 return true;
551
552 // If the format of assert is "ifElseFatal", PrintOp is lowered into
553 // ErrorOp. So we have to check message contents whether they encode
554 // verifications. See FIRParserAsserts for more details.
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]");
562 return false;
563 }
564
565 return isa<AssertOp, FinishOp, FWriteOp, AssertConcurrentOp, FatalOp,
566 verif::AssertOp, verif::ClockedAssertOp>(op);
567}
568
569static bool isCoverOp(hw::HWSymbolCache &symCache, Operation *op) {
570 // Symbols not in the cache will only be fore instances added by an extract
571 // phase and are not instances that could possibly have extract flags on them.
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"))
575 return true;
576 return isa<CoverOp, CoverConcurrentOp, verif::CoverOp, verif::ClockedCoverOp>(
577 op);
578}
579
580static bool isAssumeOp(hw::HWSymbolCache &symCache, Operation *op) {
581 // Symbols not in the cache will only be fore instances added by an extract
582 // phase and are not instances that could possibly have extract flags on them.
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"))
586 return true;
587
588 return isa<AssumeOp, AssumeConcurrentOp, verif::AssumeOp,
589 verif::ClockedAssumeOp>(op);
590}
591
592/// Return true if the operation belongs to the design.
593bool isInDesign(hw::HWSymbolCache &symCache, Operation *op,
594 bool disableInstanceExtraction = false,
595 bool disableRegisterExtraction = false) {
596
597 // Module outputs are marked as designs.
598 if (isa<hw::OutputOp>(op))
599 return true;
600
601 // If an op has an innner sym, don't extract.
602 if (auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(op))
603 if (auto innerSym = innerSymOp.getInnerSymAttr())
604 if (!innerSym.empty())
605 return true;
606
607 // Check whether the operation is a verification construct. Instance op could
608 // be used as verification construct so make sure to check this property
609 // first.
610 if (isAssertOp(symCache, op) || isCoverOp(symCache, op) ||
611 isAssumeOp(symCache, op))
612 return false;
613
614 // For instances and regiseters, check by passed arguments.
615 if (isa<hw::InstanceOp>(op))
616 return disableInstanceExtraction;
617 if (isa<seq::FirRegOp>(op))
618 return disableRegisterExtraction;
619
620 // Since we are not tracking dataflow through SV assignments, and we don't
621 // extract SV declarations (e.g. wire, reg or logic), so just read is part of
622 // the design.
623 if (isa<sv::ReadInOutOp>(op))
624 return true;
625
626 // If the op has regions, we visit sub-regions later.
627 if (op->getNumRegions() > 0)
628 return false;
629
630 // Otherwise, operations with memory effects as a part design.
631 return !mlir::isMemoryEffectFree(op);
632}
633
634//===----------------------------------------------------------------------===//
635// StubExternalModules Pass
636//===----------------------------------------------------------------------===//
637
638namespace {
639
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;
648 }
649 void runOnOperation() override;
650
651private:
652 // Run the extraction on a module, and return true if test code was extracted.
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;
658 // Find Operations of interest.
659 SetVector<Operation *> roots;
660 module->walk([&fn, &roots, &hasError](Operation *op) {
661 if (fn(op)) {
662 roots.insert(op);
663 if (op->getNumResults()) {
664 op->emitError("Extracting op with result");
665 hasError = true;
666 }
667 }
668 });
669 if (hasError) {
670 signalPassFailure();
671 return false;
672 }
673 // No Ops? No problem.
674 if (roots.empty())
675 return false;
676
677 // Find the data-flow and structural ops to clone. Result includes roots.
678 // Track dataflow until it reaches to design parts except for constants that
679 // can be cloned freely.
680 auto opsToClone = getBackwardSlice(roots, [&](Operation *op) {
681 return !opsInDesign.count(op) ||
682 op->hasTrait<mlir::OpTrait::ConstantLike>();
683 });
684
685 // Find the dataflow into the clone set
686 SetVector<Value> inputs;
687 for (auto *op : opsToClone) {
688 for (auto arg : op->getOperands()) {
689 auto argOp = arg.getDefiningOp(); // may be null
690 if (!opsToClone.count(argOp))
691 inputs.insert(arg);
692 }
693 // Erase cloned operations.
694 opsToErase.insert(op);
695 }
696
697 numOpsExtracted += opsToClone.size();
698
699 // Make a module to contain the clone set, with arguments being the cut
700 IRMapping cutMap;
701 auto bmod = createModuleForCut(module, inputs, cutMap, suffix, path,
702 bindFile, bindTable);
703
704 // Register the newly created module in the instance graph.
705 instanceGraph->addHWModule(bmod);
706
707 // do the clone
708 migrateOps(module, bmod, opsToClone, cutMap, *instanceGraph);
709
710 // erase old operations of interest eagerly, removing from erase set.
711 for (auto *op : roots) {
712 opsToErase.erase(op);
713 op->erase();
714 }
715
716 return true;
717 }
718
719 // Instance graph we are using and maintaining.
720 hw::InstanceGraph *instanceGraph = nullptr;
721};
722
723} // end anonymous namespace
724
725void SVExtractTestCodeImplPass::runOnOperation() {
726 this->instanceGraph = &getAnalysis<circt::hw::InstanceGraph>();
727
728 auto top = getOperation();
729
730 // It takes extra effort to inline modules which contains inner symbols
731 // referred through hierpaths or unknown operations since we have to update
732 // inner refs users globally. However we do want to inline modules which
733 // contain bound instances so create a set of inner refs used by non bind op
734 // in order to allow bind ops.
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);
741 });
742 });
743
744 auto *topLevelModule = top.getBody();
745 auto assertDir =
746 top->getAttrOfType<hw::OutputFileAttr>("firrtl.extract.assert");
747 auto assumeDir =
748 top->getAttrOfType<hw::OutputFileAttr>("firrtl.extract.assume");
749 auto coverDir =
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");
755 auto coverBindFile =
756 top->getAttrOfType<hw::OutputFileAttr>("firrtl.extract.cover.bindfile");
757
758 hw::HWSymbolCache symCache;
759 symCache.addDefinitions(top);
760 symCache.freeze();
761
762 auto isAssert = [&symCache](Operation *op) -> bool {
763 return isAssertOp(symCache, op);
764 };
765
766 auto isAssume = [&symCache](Operation *op) -> bool {
767 return isAssumeOp(symCache, op);
768 };
769
770 auto isCover = [&symCache](Operation *op) -> bool {
771 return isCoverOp(symCache, op);
772 };
773
774 // Collect modules that are already bound and add the bound instance(s) to the
775 // bind table, so they can be updated if the instance(s) live inside a module
776 // that gets inlined later.
777 BindTable bindTable;
778 addExistingBinds(topLevelModule, bindTable);
779
780 for (auto &op : llvm::make_early_inc_range(topLevelModule->getOperations())) {
781 if (auto rtlmod = dyn_cast<hw::HWModuleOp>(op)) {
782 // Extract two sets of ops to different modules. This will add modules,
783 // but not affect modules in the symbol table. If any instance of the
784 // module is bound, then extraction is skipped. This avoids problems
785 // where certain simulators dislike having binds that target bound
786 // modules.
787 if (isBound(rtlmod, *instanceGraph))
788 continue;
789
790 // In the module is in test harness, we don't have to extract from it.
791 if (rtlmod->hasAttr("firrtl.extract.do_not_extract")) {
792 rtlmod->removeAttr("firrtl.extract.do_not_extract");
793 continue;
794 }
795
796 // Get a set for operations in the design. We can extract operations that
797 // don't belong to the design.
798 auto opsInDesign = getBackwardSlice(
799 rtlmod,
800 /*rootFn=*/
801 [&](Operation *op) {
802 return isInDesign(symCache, op, disableInstanceExtraction,
803 disableRegisterExtraction);
804 },
805 /*filterFn=*/{});
806
807 SmallPtrSet<Operation *, 32> opsToErase;
808 bool anyThingExtracted = false;
809 anyThingExtracted |=
810 doModule(rtlmod, isAssert, "_assert", assertDir, assertBindFile,
811 bindTable, opsToErase, opsInDesign);
812 anyThingExtracted |=
813 doModule(rtlmod, isAssume, "_assume", assumeDir, assumeBindFile,
814 bindTable, opsToErase, opsInDesign);
815 anyThingExtracted |=
816 doModule(rtlmod, isCover, "_cover", coverDir, coverBindFile,
817 bindTable, opsToErase, opsInDesign);
818
819 // If nothing is extracted and the module has an output, we are done.
820 if (!anyThingExtracted && rtlmod.getNumOutputPorts() != 0)
821 continue;
822
823 // Here, erase extracted operations as well as dead operations.
824 // `opsToErase` includes extracted operations but doesn't contain all
825 // dead operations. Even though it's not ideal to perform non-trivial DCE
826 // here but we have to delete dead operations that might be an user of an
827 // extracted operation.
828 auto opsAlive = getBackwardSlice(
829 rtlmod,
830 /*rootFn=*/
831 [&](Operation *op) {
832 // Don't remove instances not to eliminate extracted instances
833 // introduced above. However we do want to erase old instances in
834 // the original module extracted into verification parts so identify
835 // such instances by querying to `opsToErase`.
836 return isInDesign(symCache, op,
837 /*disableInstanceExtraction=*/true,
838 disableRegisterExtraction) &&
839 !opsToErase.contains(op);
840 },
841 /*filterFn=*/{});
842
843 // Walk the module and add dead operations to `opsToErase`.
844 op.walk([&](Operation *operation) {
845 // Skip the module itself.
846 if (&op == operation)
847 return;
848
849 // Update `opsToErase`.
850 if (opsAlive.count(operation))
851 opsToErase.erase(operation);
852 else
853 opsToErase.insert(operation);
854 });
855
856 // Inline any modules that only have inputs for test code.
857 if (!disableModuleInlining)
858 inlineInputOnly(rtlmod, *instanceGraph, bindTable, opsToErase,
859 innerRefUsedByNonBindOp);
860
861 numOpsErased += opsToErase.size();
862 while (!opsToErase.empty()) {
863 Operation *op = *opsToErase.begin();
864 op->walk([&](Operation *erasedOp) { opsToErase.erase(erasedOp); });
865 op->dropAllUses();
866 op->erase();
867 }
868 }
869 }
870
871 // We have to wait until all the instances are processed to clean up the
872 // annotations.
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");
878 }
879
880 markAnalysesPreserved<circt::hw::InstanceGraph>();
881}
882
883std::unique_ptr<Pass>
884circt::sv::createSVExtractTestCodePass(bool disableInstanceExtraction,
885 bool disableRegisterExtraction,
886 bool disableModuleInlining) {
887 return std::make_unique<SVExtractTestCodeImplPass>(disableInstanceExtraction,
888 disableRegisterExtraction,
889 disableModuleInlining);
890}
assert(baseType &&"element must be base type")
static void migrateOps(hw::HWModuleOp oldMod, hw::HWModuleOp newMod, SetVector< Operation * > &depOps, IRMapping &cutMap, hw::InstanceGraph &instanceGraph)
DenseMap< StringAttr, SmallDenseMap< StringAttr, sv::BindOp > > BindTable
static bool isAssumeOp(hw::HWSymbolCache &symCache, Operation *op)
static SetVector< Operation * > getBackwardSlice(SetVector< Operation * > &roots, llvm::function_ref< bool(Operation *)> filter)
static bool isCoverOp(hw::HWSymbolCache &symCache, Operation *op)
static bool isBound(hw::HWModuleLike op, hw::InstanceGraph &instanceGraph)
static void addExistingBinds(Block *topLevelModule, BindTable &bindTable)
static hw::HWModuleOp createModuleForCut(hw::HWModuleOp op, SetVector< Value > &inputs, IRMapping &cutMap, StringRef suffix, Attribute path, Attribute fileName, BindTable &bindTable)
bool isInDesign(hw::HWSymbolCache &symCache, Operation *op, bool disableInstanceExtraction=false, bool disableRegisterExtraction=false)
Return true if the operation belongs to the design.
static void computeSlice(SetVector< Operation * > &roots, SetVector< Operation * > &results, llvm::function_ref< bool(Operation *)> filter)
static void blockSlice(SetVector< Operation * > &ops, SetVector< Operation * > &blocks)
static bool isAssertOp(hw::HWSymbolCache &symCache, Operation *op)
static void setInsertPointToEndOrTerminator(OpBuilder &builder, Block *block)
static void inlineInputOnly(hw::HWModuleOp oldMod, hw::InstanceGraph &instanceGraph, BindTable &bindTable, SmallPtrSetImpl< Operation * > &opsToErase, llvm::DenseSet< hw::InnerRefAttr > &innerRefUsedByNonBindOp)
static void updateOoOArgs(SmallVectorImpl< Operation * > &lateBoundOps, IRMapping &cutMap)
static StringAttr getNameForPort(Value val, SmallVector< mlir::Attribute > modulePorts)
static void getBackwardSliceSimple(Operation *rootOp, SetVector< Operation * > &backwardSlice, llvm::function_ref< bool(Operation *)> filter)
static bool hasOoOArgs(hw::HWModuleOp newMod, Operation *op)
static void addBlockMapping(IRMapping &cutMap, Operation *oldOp, Operation *newOp)
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:85
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
Definition SymCache.cpp:23
This stores lookup tables to make manipulating and working with the IR more efficient.
Definition HWSymCache.h:27
mlir::Operation * getDefinition(mlir::Attribute attr) const override
Lookup a definition for 'symbol' in the cache.
Definition HWSymCache.h:56
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.
name(self)
Definition hw.py:329
StringAttr getVerilogModuleNameAttr(Operation *module)
Returns the verilog module name attribute or symbol name of any module-like operations.
Definition HWOps.cpp:539
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.
Definition hw.py:1
Definition sv.py:1