CIRCT  20.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.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(),
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
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<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.
350 static 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.
358 static 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 
544 static 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 
569 static 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 
580 static 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.
593 bool 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 
638 namespace {
639 
640 struct 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 
651 private:
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 
725 void 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 
883 std::unique_ptr<Pass>
884 circt::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")
@ 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:85
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:329
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
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: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: DebugAnalysis.h:21
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition: seq.py:21
Definition: sv.py:1