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