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