CIRCT  19.0.0git
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 
19 #include "circt/Dialect/HW/HWOps.h"
22 #include "circt/Dialect/SV/SVOps.h"
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 
35 namespace circt {
36 namespace sv {
37 #define GEN_PASS_DEF_SVEXTRACTTESTCODE
38 #include "circt/Dialect/SV/SVPasses.h.inc"
39 } // namespace sv
40 } // namespace circt
41 
42 using namespace mlir;
43 using namespace circt;
44 using namespace sv;
45 
46 using 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_.)
56 static void
57 getBackwardSliceSimple(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.
99 static 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 
109 static 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.
118 static SetVector<Operation *>
119 getBackwardSlice(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.
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))
146  roots.insert(op);
147  });
148  return getBackwardSlice(roots, filterFn);
149 }
150 
151 static 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.getResultName(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(),
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->setAttr("doNotPrint", b.getBoolAttr(true));
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
268 static 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.
277 static 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.
290 static 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.
302 static 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.
314 static 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)) {
328  igraph::InstanceGraphNode *instMod =
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.
338 static 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();
342  if (!inst)
343  return false;
344  return inst->hasAttr("doNotPrint");
345  });
346 }
347 
348 // Add any existing bindings to the bind table.
349 static void addExistingBinds(Block *topLevelModule, BindTable &bindTable) {
350  for (auto bind : topLevelModule->getOps<BindOp>()) {
351  hw::InnerRefAttr boundRef = bind.getInstance();
352  bindTable[boundRef.getModule()][boundRef.getName()] = bind;
353  }
354 }
355 
356 // Inline any modules that only have inputs for test code.
357 static void
359  BindTable &bindTable, SmallPtrSetImpl<Operation *> &opsToErase,
360  llvm::DenseSet<hw::InnerRefAttr> &innerRefUsedByNonBindOp) {
361 
362  // Check if the module only has inputs.
363  if (oldMod.getNumOutputPorts() != 0)
364  return;
365 
366  // Check if it's ok to inline. We cannot inline the module if there exists a
367  // declaration with an inner symbol referred by non-bind ops (e.g. hierpath).
368  auto oldModName = oldMod.getModuleNameAttr();
369  for (auto port : oldMod.getPortList()) {
370  auto sym = port.getSym();
371  if (sym) {
372  for (auto property : sym) {
373  auto innerRef = hw::InnerRefAttr::get(oldModName, property.getName());
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";
379  return;
380  }
381  }
382  }
383  }
384 
385  for (auto op : oldMod.getBodyBlock()->getOps<hw::InnerSymbolOpInterface>()) {
386  if (auto innerSym = op.getInnerSymAttr()) {
387  for (auto property : innerSym) {
388  auto innerRef = hw::InnerRefAttr::get(oldModName, property.getName());
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";
393  return;
394  }
395  }
396  }
397  }
398 
399  // Get the instance graph node for the old module.
400  igraph::InstanceGraphNode *node = instanceGraph.lookup(oldMod);
401  assert(!node->noUses() &&
402  "expected module for inlining to be instantiated at least once");
403 
404  // Iterate through each instance of the module.
405  OpBuilder b(oldMod);
406  bool allInlined = true;
407  for (igraph::InstanceRecord *use : llvm::make_early_inc_range(node->uses())) {
408  // If there is no instance, move on.
409  auto instLike = use->getInstance<hw::HWInstanceLike>();
410  if (!instLike) {
411  allInlined = false;
412  continue;
413  }
414 
415  // If the instance had a symbol, we can't inline it without more work.
416  hw::InstanceOp inst = cast<hw::InstanceOp>(instLike.getOperation());
417  if (inst.getInnerSym().has_value()) {
418  allInlined = false;
419  auto diag =
420  oldMod.emitWarning()
421  << "module " << oldMod.getModuleName()
422  << " cannot be inlined because there is an instance with a symbol";
423  diag.attachNote(inst.getLoc());
424  continue;
425  }
426 
427  // Build a mapping from module block arguments to instance inputs.
428  IRMapping mapping;
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));
433 
434  // Inline the body at the instantiation site.
435  hw::HWModuleOp instParent =
436  cast<hw::HWModuleOp>(use->getParent()->getModule());
437  igraph::InstanceGraphNode *instParentNode =
438  instanceGraph.lookup(instParent);
439  SmallVector<Operation *, 16> lateBoundOps;
440  b.setInsertionPoint(inst);
441  // Namespace that tracks inner symbols in the parent module.
442  hw::InnerSymbolNamespace nameSpace(instParent);
443  // A map from old inner symbols to new ones.
444  DenseMap<mlir::StringAttr, mlir::StringAttr> symMapping;
445 
446  for (auto &op : *oldMod.getBodyBlock()) {
447  // If the op was erased by instance extraction, don't copy it over.
448  if (opsToErase.contains(&op))
449  continue;
450 
451  // If the op has an inner sym, first create a new inner sym for it.
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();
456  auto newName =
457  b.getStringAttr(nameSpace.newName(oldName.getValue()));
458  auto result = symMapping.insert({oldName, newName});
459  (void)result;
460  assert(result.second && "inner symbols must be unique");
461  }
462  }
463  }
464 
465  // For instances in the bind table, update the bind with the new parent.
466  if (auto innerInst = dyn_cast<hw::InstanceOp>(op)) {
467  if (auto innerInstSym = innerInst.getInnerSymAttr()) {
468  auto it =
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;
477  auto newInnerRef =
478  hw::InnerRefAttr::get(instParent.getModuleNameAttr(), newName);
479  OpBuilder::InsertionGuard g(b);
480  // Clone bind operations.
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);
486  }
487  }
488  }
489 
490  // For all ops besides the output, clone into the parent body.
491  if (!isa<hw::OutputOp>(op)) {
492  Operation *clonedOp = b.clone(op, mapping);
493  // If some of the operands haven't been cloned over yet, due to cycles,
494  // remember to revisit this op.
495  if (hasOoOArgs(instParent, clonedOp))
496  lateBoundOps.push_back(clonedOp);
497 
498  // If the cloned op is an instance, record it within the new parent in
499  // the instance graph.
500  if (auto innerInst = dyn_cast<hw::InstanceOp>(clonedOp)) {
501  igraph::InstanceGraphNode *innerInstModule =
502  instanceGraph.lookup(innerInst.getModuleNameAttr().getAttr());
503  instParentNode->addInstance(innerInst, innerInstModule);
504  }
505 
506  // If the cloned op has an inner sym, then attach an updated inner sym.
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()];
512  properties.push_back(hw::InnerSymPropertiesAttr::get(
513  op.getContext(), newSymName, property.getFieldID(),
514  property.getSymVisibility()));
515  }
516  auto innerSym = hw::InnerSymAttr::get(op.getContext(), properties);
517  innerSymOp.setInnerSymbolAttr(innerSym);
518  }
519  }
520  }
521  }
522 
523  // Map over any ops that didn't have their operands mapped when cloned.
524  updateOoOArgs(lateBoundOps, mapping);
525 
526  // Erase the old instantiation site.
527  assert(inst.use_empty() && "inlined instance should have no uses");
528  use->erase();
529  opsToErase.insert(inst);
530  }
531 
532  // If all instances were inlined, remove the module.
533  if (allInlined) {
534  // Erase old bind statements.
535  for (auto [_, bind] : bindTable[oldMod.getNameAttr()])
536  bind.erase();
537  bindTable[oldMod.getNameAttr()].clear();
538  instanceGraph.erase(node);
539  opsToErase.insert(oldMod);
540  }
541 }
542 
543 static bool isAssertOp(hw::HWSymbolCache &symCache, Operation *op) {
544  // Symbols not in the cache will only be fore instances added by an extract
545  // phase and are not instances that could possibly have extract flags on them.
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"))
549  return true;
550 
551  // If the format of assert is "ifElseFatal", PrintOp is lowered into
552  // ErrorOp. So we have to check message contents whether they encode
553  // verifications. See FIRParserAsserts for more details.
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]");
561  return false;
562  }
563 
564  return isa<AssertOp, FinishOp, FWriteOp, AssertConcurrentOp, FatalOp,
565  verif::AssertOp, verif::ClockedAssertOp>(op);
566 }
567 
568 static bool isCoverOp(hw::HWSymbolCache &symCache, Operation *op) {
569  // Symbols not in the cache will only be fore instances added by an extract
570  // phase and are not instances that could possibly have extract flags on them.
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"))
574  return true;
575  return isa<CoverOp, CoverConcurrentOp, verif::CoverOp, verif::ClockedCoverOp>(
576  op);
577 }
578 
579 static bool isAssumeOp(hw::HWSymbolCache &symCache, Operation *op) {
580  // Symbols not in the cache will only be fore instances added by an extract
581  // phase and are not instances that could possibly have extract flags on them.
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"))
585  return true;
586 
587  return isa<AssumeOp, AssumeConcurrentOp, verif::AssumeOp,
588  verif::ClockedAssumeOp>(op);
589 }
590 
591 /// Return true if the operation belongs to the design.
592 bool isInDesign(hw::HWSymbolCache &symCache, Operation *op,
593  bool disableInstanceExtraction = false,
594  bool disableRegisterExtraction = false) {
595 
596  // Module outputs are marked as designs.
597  if (isa<hw::OutputOp>(op))
598  return true;
599 
600  // If an op has an innner sym, don't extract.
601  if (auto innerSymOp = dyn_cast<hw::InnerSymbolOpInterface>(op))
602  if (auto innerSym = innerSymOp.getInnerSymAttr())
603  if (!innerSym.empty())
604  return true;
605 
606  // Check whether the operation is a verification construct. Instance op could
607  // be used as verification construct so make sure to check this property
608  // first.
609  if (isAssertOp(symCache, op) || isCoverOp(symCache, op) ||
610  isAssumeOp(symCache, op))
611  return false;
612 
613  // For instances and regiseters, check by passed arguments.
614  if (isa<hw::InstanceOp>(op))
615  return disableInstanceExtraction;
616  if (isa<seq::FirRegOp>(op))
617  return disableRegisterExtraction;
618 
619  // Since we are not tracking dataflow through SV assignments, and we don't
620  // extract SV declarations (e.g. wire, reg or logic), so just read is part of
621  // the design.
622  if (isa<sv::ReadInOutOp>(op))
623  return true;
624 
625  // If the op has regions, we visit sub-regions later.
626  if (op->getNumRegions() > 0)
627  return false;
628 
629  // Otherwise, operations with memory effects as a part design.
630  return !mlir::isMemoryEffectFree(op);
631 }
632 
633 //===----------------------------------------------------------------------===//
634 // StubExternalModules Pass
635 //===----------------------------------------------------------------------===//
636 
637 namespace {
638 
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;
647  }
648  void runOnOperation() override;
649 
650 private:
651  // Run the extraction on a module, and return true if test code was extracted.
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;
657  // Find Operations of interest.
658  SetVector<Operation *> roots;
659  module->walk([&fn, &roots, &hasError](Operation *op) {
660  if (fn(op)) {
661  roots.insert(op);
662  if (op->getNumResults()) {
663  op->emitError("Extracting op with result");
664  hasError = true;
665  }
666  }
667  });
668  if (hasError) {
669  signalPassFailure();
670  return false;
671  }
672  // No Ops? No problem.
673  if (roots.empty())
674  return false;
675 
676  // Find the data-flow and structural ops to clone. Result includes roots.
677  // Track dataflow until it reaches to design parts except for constants that
678  // can be cloned freely.
679  auto opsToClone = getBackwardSlice(roots, [&](Operation *op) {
680  return !opsInDesign.count(op) ||
681  op->hasTrait<mlir::OpTrait::ConstantLike>();
682  });
683 
684  // Find the dataflow into the clone set
685  SetVector<Value> inputs;
686  for (auto *op : opsToClone) {
687  for (auto arg : op->getOperands()) {
688  auto argOp = arg.getDefiningOp(); // may be null
689  if (!opsToClone.count(argOp))
690  inputs.insert(arg);
691  }
692  // Erase cloned operations.
693  opsToErase.insert(op);
694  }
695 
696  numOpsExtracted += opsToClone.size();
697 
698  // Make a module to contain the clone set, with arguments being the cut
699  IRMapping cutMap;
700  auto bmod = createModuleForCut(module, inputs, cutMap, suffix, path,
701  bindFile, bindTable);
702 
703  // Register the newly created module in the instance graph.
704  instanceGraph->addHWModule(bmod);
705 
706  // do the clone
707  migrateOps(module, bmod, opsToClone, cutMap, *instanceGraph);
708 
709  // erase old operations of interest eagerly, removing from erase set.
710  for (auto *op : roots) {
711  opsToErase.erase(op);
712  op->erase();
713  }
714 
715  return true;
716  }
717 
718  // Instance graph we are using and maintaining.
719  hw::InstanceGraph *instanceGraph = nullptr;
720 };
721 
722 } // end anonymous namespace
723 
724 void SVExtractTestCodeImplPass::runOnOperation() {
725  this->instanceGraph = &getAnalysis<circt::hw::InstanceGraph>();
726 
727  auto top = getOperation();
728 
729  // It takes extra effort to inline modules which contains inner symbols
730  // referred through hierpaths or unknown operations since we have to update
731  // inner refs users globally. However we do want to inline modules which
732  // contain bound instances so create a set of inner refs used by non bind op
733  // in order to allow bind ops.
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);
740  });
741  });
742 
743  auto *topLevelModule = top.getBody();
744  auto assertDir =
745  top->getAttrOfType<hw::OutputFileAttr>("firrtl.extract.assert");
746  auto assumeDir =
747  top->getAttrOfType<hw::OutputFileAttr>("firrtl.extract.assume");
748  auto coverDir =
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");
754  auto coverBindFile =
755  top->getAttrOfType<hw::OutputFileAttr>("firrtl.extract.cover.bindfile");
756 
757  hw::HWSymbolCache symCache;
758  symCache.addDefinitions(top);
759  symCache.freeze();
760 
761  auto isAssert = [&symCache](Operation *op) -> bool {
762  return isAssertOp(symCache, op);
763  };
764 
765  auto isAssume = [&symCache](Operation *op) -> bool {
766  return isAssumeOp(symCache, op);
767  };
768 
769  auto isCover = [&symCache](Operation *op) -> bool {
770  return isCoverOp(symCache, op);
771  };
772 
773  // Collect modules that are already bound and add the bound instance(s) to the
774  // bind table, so they can be updated if the instance(s) live inside a module
775  // that gets inlined later.
776  BindTable bindTable;
777  addExistingBinds(topLevelModule, bindTable);
778 
779  for (auto &op : llvm::make_early_inc_range(topLevelModule->getOperations())) {
780  if (auto rtlmod = dyn_cast<hw::HWModuleOp>(op)) {
781  // Extract two sets of ops to different modules. This will add modules,
782  // but not affect modules in the symbol table. If any instance of the
783  // module is bound, then extraction is skipped. This avoids problems
784  // where certain simulators dislike having binds that target bound
785  // modules.
786  if (isBound(rtlmod, *instanceGraph))
787  continue;
788 
789  // In the module is in test harness, we don't have to extract from it.
790  if (rtlmod->hasAttr("firrtl.extract.do_not_extract")) {
791  rtlmod->removeAttr("firrtl.extract.do_not_extract");
792  continue;
793  }
794 
795  // Get a set for operations in the design. We can extract operations that
796  // don't belong to the design.
797  auto opsInDesign = getBackwardSlice(
798  rtlmod,
799  /*rootFn=*/
800  [&](Operation *op) {
801  return isInDesign(symCache, op, disableInstanceExtraction,
802  disableRegisterExtraction);
803  },
804  /*filterFn=*/{});
805 
806  SmallPtrSet<Operation *, 32> opsToErase;
807  bool anyThingExtracted = false;
808  anyThingExtracted |=
809  doModule(rtlmod, isAssert, "_assert", assertDir, assertBindFile,
810  bindTable, opsToErase, opsInDesign);
811  anyThingExtracted |=
812  doModule(rtlmod, isAssume, "_assume", assumeDir, assumeBindFile,
813  bindTable, opsToErase, opsInDesign);
814  anyThingExtracted |=
815  doModule(rtlmod, isCover, "_cover", coverDir, coverBindFile,
816  bindTable, opsToErase, opsInDesign);
817 
818  // If nothing is extracted and the module has an output, we are done.
819  if (!anyThingExtracted && rtlmod.getNumOutputPorts() != 0)
820  continue;
821 
822  // Here, erase extracted operations as well as dead operations.
823  // `opsToErase` includes extracted operations but doesn't contain all
824  // dead operations. Even though it's not ideal to perform non-trivial DCE
825  // here but we have to delete dead operations that might be an user of an
826  // extracted operation.
827  auto opsAlive = getBackwardSlice(
828  rtlmod,
829  /*rootFn=*/
830  [&](Operation *op) {
831  // Don't remove instances not to eliminate extracted instances
832  // introduced above. However we do want to erase old instances in
833  // the original module extracted into verification parts so identify
834  // such instances by querying to `opsToErase`.
835  return isInDesign(symCache, op,
836  /*disableInstanceExtraction=*/true,
837  disableRegisterExtraction) &&
838  !opsToErase.contains(op);
839  },
840  /*filterFn=*/{});
841 
842  // Walk the module and add dead operations to `opsToErase`.
843  op.walk([&](Operation *operation) {
844  // Skip the module itself.
845  if (&op == operation)
846  return;
847 
848  // Update `opsToErase`.
849  if (opsAlive.count(operation))
850  opsToErase.erase(operation);
851  else
852  opsToErase.insert(operation);
853  });
854 
855  // Inline any modules that only have inputs for test code.
856  if (!disableModuleInlining)
857  inlineInputOnly(rtlmod, *instanceGraph, bindTable, opsToErase,
858  innerRefUsedByNonBindOp);
859 
860  numOpsErased += opsToErase.size();
861  while (!opsToErase.empty()) {
862  Operation *op = *opsToErase.begin();
863  op->walk([&](Operation *erasedOp) { opsToErase.erase(erasedOp); });
864  op->dropAllUses();
865  op->erase();
866  }
867  }
868  }
869 
870  // We have to wait until all the instances are processed to clean up the
871  // annotations.
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");
877  }
878 
879  markAnalysesPreserved<circt::hw::InstanceGraph>();
880 }
881 
882 std::unique_ptr<Pass>
883 circt::sv::createSVExtractTestCodePass(bool disableInstanceExtraction,
884  bool disableRegisterExtraction,
885  bool disableModuleInlining) {
886  return std::make_unique<SVExtractTestCodeImplPass>(disableInstanceExtraction,
887  disableRegisterExtraction,
888  disableModuleInlining);
889 }
assert(baseType &&"element must be base type")
@ Input
Definition: HW.h:35
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 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 SetVector< Operation * > getBackwardSlice(SetVector< Operation * > &roots, 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:72
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.
Definition: InstanceGraph.h:55
def name(self)
Definition: hw.py:195
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
StringRef getFragmentsAttrName()
Return the name of the fragments array attribute.
Definition: EmitOps.h:32
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.
Definition: HWOps.cpp:533
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: DebugAnalysis.h:21
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition: seq.py:20
Definition: sv.py:1