CIRCT  20.0.0git
InjectDUTHierarchy.cpp
Go to the documentation of this file.
1 //===- InjectDUTHierarchy.cpp - Add hierarchy above the DUT ---------------===//
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 // Implementation of the SiFive transform InjectDUTHierarchy. This moves all
10 // the logic inside the DUT into a new module named using an annotation.
11 //
12 //===----------------------------------------------------------------------===//
13 
21 #include "circt/Dialect/HW/HWOps.h"
23 #include "circt/Support/Debug.h"
24 #include "mlir/Pass/Pass.h"
25 #include "llvm/Support/Debug.h"
26 
27 #define DEBUG_TYPE "firrtl-inject-dut-hier"
28 
29 namespace circt {
30 namespace firrtl {
31 #define GEN_PASS_DEF_INJECTDUTHIERARCHY
32 #include "circt/Dialect/FIRRTL/Passes.h.inc"
33 } // namespace firrtl
34 } // namespace circt
35 
36 using namespace circt;
37 using namespace firrtl;
38 
39 namespace {
40 struct InjectDUTHierarchy
41  : public circt::firrtl::impl::InjectDUTHierarchyBase<InjectDUTHierarchy> {
42  void runOnOperation() override;
43 };
44 } // namespace
45 
46 /// Add an extra level of hierarchy to a hierarchical path that places the
47 /// wrapper instance after the DUT. E.g., this is converting:
48 ///
49 /// firrtl.hierpath [@Top::@dut, @DUT]
50 ///
51 /// Int:
52 ///
53 /// firrtl.hierpath [@Top::@dut, @DUT::@wrapper, @Wrapper]
54 static void addHierarchy(hw::HierPathOp path, FModuleOp dut,
55  InstanceOp wrapperInst) {
56  auto namepath = path.getNamepath().getValue();
57 
58  size_t nlaIdx = 0;
59  SmallVector<Attribute> newNamepath;
60  newNamepath.reserve(namepath.size() + 1);
61  while (path.modPart(nlaIdx) != dut.getNameAttr())
62  newNamepath.push_back(namepath[nlaIdx++]);
63  newNamepath.push_back(hw::InnerRefAttr::get(dut.getModuleNameAttr(),
64  getInnerSymName(wrapperInst)));
65 
66  // Add the extra level of hierarchy.
67  if (auto dutRef = dyn_cast<hw::InnerRefAttr>(namepath[nlaIdx]))
68  newNamepath.push_back(hw::InnerRefAttr::get(
69  wrapperInst.getModuleNameAttr().getAttr(), dutRef.getName()));
70  else
71  newNamepath.push_back(
72  FlatSymbolRefAttr::get(wrapperInst.getModuleNameAttr().getAttr()));
73 
74  // Add anything left over.
75  auto back = namepath.drop_front(nlaIdx + 1);
76  newNamepath.append(back.begin(), back.end());
77  path.setNamepathAttr(ArrayAttr::get(dut.getContext(), newNamepath));
78 }
79 
80 void InjectDUTHierarchy::runOnOperation() {
81  LLVM_DEBUG(debugPassHeader(this) << "\n";);
82 
83  CircuitOp circuit = getOperation();
84 
85  /// The design-under-test (DUT). This is kept up-to-date by the pass as the
86  /// DUT changes due to internal logic.
87  FModuleOp dut;
88 
89  /// The wrapper module that is created inside the DUT to house all its logic.
90  FModuleOp wrapper;
91 
92  /// The name of the new module to create under the DUT.
93  StringAttr wrapperName;
94 
95  /// Mutable indicator that an error occurred for some reason. If this is ever
96  /// true, then the pass can just signalPassFailure.
97  bool error = false;
98 
99  AnnotationSet::removeAnnotations(circuit, [&](Annotation anno) {
101  return false;
102 
103  auto name = anno.getMember<StringAttr>("name");
104  if (!name) {
105  emitError(circuit->getLoc())
106  << "contained a malformed "
107  "'sifive.enterprise.firrtl.InjectDUTHierarchyAnnotation' "
108  "annotation that did not contain a 'name' field";
109  error = true;
110  return false;
111  }
112 
113  if (wrapperName) {
114  emitError(circuit->getLoc())
115  << "contained multiple "
116  "'sifive.enterprise.firrtl.InjectDUTHierarchyAnnotation' "
117  "annotations when at most one is allowed";
118  error = true;
119  return false;
120  }
121 
122  wrapperName = name;
123  return true;
124  });
125 
126  if (error)
127  return signalPassFailure();
128 
129  // The prerequisites for the pass to run were not met. Indicate that no work
130  // was done and exit.
131  if (!wrapperName)
132  return markAllAnalysesPreserved();
133 
134  // TODO: Combine this logic with GrandCentral and other places that need to
135  // find the DUT. Consider changing the MarkDUTAnnotation scattering to put
136  // this information on the Circuit so that we don't have to dig through all
137  // the modules to find the DUT.
138  for (auto mod : circuit.getOps<FModuleOp>()) {
140  continue;
141  if (dut) {
142  auto diag = emitError(mod.getLoc())
143  << "is marked with a '" << dutAnnoClass << "', but '"
144  << dut.getModuleName()
145  << "' also had such an annotation (this should "
146  "be impossible!)";
147  diag.attachNote(dut.getLoc()) << "the first DUT was found here";
148  error = true;
149  break;
150  }
151  dut = mod;
152  }
153 
154  if (error)
155  return signalPassFailure();
156 
157  // If a hierarchy annotation was provided, ensure that a DUT annotation also
158  // exists. The pass could silently ignore this case and do nothing, but it is
159  // better to provide an error.
160  if (wrapperName && !dut) {
161  emitError(circuit->getLoc())
162  << "contained a '" << injectDUTHierarchyAnnoClass << "', but no '"
163  << dutAnnoClass << "' was provided";
164  error = true;
165  }
166 
167  if (error)
168  return signalPassFailure();
169 
170  // Create a module that will become the new DUT. The original DUT is renamed
171  // to become the wrapper. This is done to save copying into the wrapper.
172  // While the logical movement is "copy the body of the DUT into a wrapper", it
173  // is mechanically more straigthforward to make the DUT the wrappper. After
174  // this block finishes, the "dut" and "wrapper" variables are set correctly.
175  // This logic is intentionally put into a block to avoid confusion while the
176  // dut and wrapper do not match the logical definition.
177  OpBuilder b(circuit.getContext());
178  CircuitNamespace circuitNS(circuit);
179  {
180  b.setInsertionPointAfter(dut);
181  auto newDUT = b.create<FModuleOp>(dut.getLoc(), dut.getNameAttr(),
182  dut.getConventionAttr(), dut.getPorts(),
183  dut.getAnnotations());
184 
185  SymbolTable::setSymbolVisibility(newDUT, dut.getVisibility());
186  dut.setName(b.getStringAttr(circuitNS.newName(wrapperName.getValue())));
187 
188  // The original DUT module is now the wrapper. The new module we just
189  // created becomse the DUT.
190  wrapper = dut;
191  dut = newDUT;
192 
193  // Finish setting up the wrapper. It can have no annotations.
195  [](auto, auto) { return true; });
196  AnnotationSet::removeAnnotations(wrapper, [](auto) { return true; });
197  }
198 
199  // Instantiate the wrapper inside the DUT and wire it up.
200  b.setInsertionPointToStart(dut.getBodyBlock());
201  hw::InnerSymbolNamespace dutNS(dut);
202  auto wrapperInst =
203  b.create<InstanceOp>(b.getUnknownLoc(), wrapper, wrapper.getModuleName(),
204  NameKindEnum::DroppableName, ArrayRef<Attribute>{},
205  ArrayRef<Attribute>{}, false,
206  hw::InnerSymAttr::get(b.getStringAttr(
207  dutNS.newName(wrapper.getModuleName()))));
208  for (const auto &pair : llvm::enumerate(wrapperInst.getResults())) {
209  Value lhs = dut.getArgument(pair.index());
210  Value rhs = pair.value();
211  if (dut.getPortDirection(pair.index()) == Direction::In)
212  std::swap(lhs, rhs);
213  emitConnect(b, b.getUnknownLoc(), lhs, rhs);
214  }
215 
216  // Compute a set of paths that are used _inside_ the wrapper.
217  DenseSet<StringAttr> dutPaths, dutPortSyms;
218  for (auto anno : AnnotationSet(dut)) {
219  auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
220  if (sym)
221  dutPaths.insert(sym.getAttr());
222  }
223  for (size_t i = 0, e = dut.getNumPorts(); i != e; ++i) {
224  auto portSym = dut.getPortSymbolAttr(i);
225  if (portSym)
226  dutPortSyms.insert(portSym.getSymName());
227  for (auto anno : AnnotationSet::forPort(dut, i)) {
228  auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
229  if (sym)
230  dutPaths.insert(sym.getAttr());
231  }
232  }
233 
234  LLVM_DEBUG({
235  llvm::dbgs() << "DUT Symbol Users:\n";
236  for (auto path : dutPaths)
237  llvm::dbgs() << " - " << FlatSymbolRefAttr::get(path) << "\n";
238  llvm::dbgs() << "Port Symbols:\n";
239  for (auto sym : dutPortSyms)
240  llvm::dbgs() << " - " << FlatSymbolRefAttr::get(sym) << "\n";
241  });
242 
243  // Update NLAs involving the DUT.
244  //
245  // NOTE: the _DUT_ is the new DUT and all the original DUT contents are put
246  // inside the DUT in the _wrapper_.
247  //
248  // There are three cases to consider:
249  // 1. The DUT or a DUT port is a leaf ref. Do nothing.
250  // 2. The DUT is the root. Update the root module to be the wrapper.
251  // 3. The NLA passes through the DUT. Remove the original InnerRef and
252  // replace it with two InnerRefs: (1) on the DUT and (2) one the wrapper.
253  LLVM_DEBUG(llvm::dbgs() << "Processing hierarchical paths:\n");
254  auto &nlaTable = getAnalysis<NLATable>();
255  DenseMap<StringAttr, hw::HierPathOp> dutRenames;
256  for (auto nla : llvm::make_early_inc_range(nlaTable.lookup(dut))) {
257  LLVM_DEBUG(llvm::dbgs() << " - " << nla << "\n");
258  auto namepath = nla.getNamepath().getValue();
259 
260  // The DUT is the root module. Just update the root module to point at the
261  // wrapper.
262  if (nla.root() == dut.getNameAttr()) {
263  assert(namepath.size() > 1 && "namepath size must be greater than one");
264  SmallVector<Attribute> newNamepath{hw::InnerRefAttr::get(
265  wrapper.getNameAttr(),
266  cast<hw::InnerRefAttr>(namepath.front()).getName())};
267  auto tail = namepath.drop_front();
268  newNamepath.append(tail.begin(), tail.end());
269  nla->setAttr("namepath", b.getArrayAttr(newNamepath));
270  continue;
271  }
272 
273  // The path ends at the DUT. This may be a reference path (ends in
274  // hw::InnerRefAttr) or a module path (ends in FlatSymbolRefAttr). There
275  // are a number of patterns to disambiguate:
276  //
277  // NOTE: the _DUT_ is the new DUT and all the original DUT contents are put
278  // inside the DUT in the _wrapper_.
279  //
280  // 1. Reference path on port. Do nothing.
281  // 2. Reference path on component. Add hierarchy
282  // 3. Module path on DUT/DUT port. Clone path, add hier to original path.
283  // 4. Module path on component. Ad dhierarchy.
284  //
285  if (nla.leafMod() == dut.getNameAttr()) {
286  // Case (1): ref path targeting a port. Do nothing.
287  if (nla.isComponent() && dutPortSyms.count(nla.ref()))
288  continue;
289 
290  // Case (3): the module path is used by the DUT module or a port. Create a
291  // clone of the path and update dutRenames so that this path symbol will
292  // get updated for annotations on the DUT or on its ports.
293  if (nla.isModule() && dutPaths.contains(nla.getSymNameAttr())) {
294  OpBuilder::InsertionGuard guard(b);
295  b.setInsertionPoint(nla);
296  auto clone = cast<hw::HierPathOp>(b.clone(*nla));
297  clone.setSymNameAttr(b.getStringAttr(
298  circuitNS.newName(clone.getSymNameAttr().getValue())));
299  dutRenames.insert({nla.getSymNameAttr(), clone});
300  }
301 
302  // Cases (2), (3), and (4): fallthrough to add hierarchy to original path.
303  }
304 
305  addHierarchy(nla, dut, wrapperInst);
306  }
307 
308  SmallVector<Annotation> newAnnotations;
309  auto removeAndUpdateNLAs = [&](Annotation anno) -> bool {
310  auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
311  if (!sym)
312  return false;
313  if (!dutRenames.count(sym.getAttr()))
314  return false;
315  anno.setMember(
316  "circt.nonlocal",
317  FlatSymbolRefAttr::get(dutRenames[sym.getAttr()].getSymNameAttr()));
318  newAnnotations.push_back(anno);
319  return true;
320  };
321 
322  // Replace any annotations on the DUT or DUT ports to use the cloned path.
323  AnnotationSet annotations(dut);
324  annotations.removeAnnotations(removeAndUpdateNLAs);
325  annotations.addAnnotations(newAnnotations);
326  annotations.applyToOperation(dut);
327  for (size_t i = 0, e = dut.getNumPorts(); i != e; ++i) {
328  newAnnotations.clear();
329  auto annotations = AnnotationSet::forPort(dut, i);
330  annotations.removeAnnotations(removeAndUpdateNLAs);
331  annotations.addAnnotations(newAnnotations);
332  annotations.applyToPort(dut, i);
333  }
334 }
335 
336 //===----------------------------------------------------------------------===//
337 // Pass Creation
338 //===----------------------------------------------------------------------===//
339 
340 std::unique_ptr<mlir::Pass> circt::firrtl::createInjectDUTHierarchyPass() {
341  return std::make_unique<InjectDUTHierarchy>();
342 }
assert(baseType &&"element must be base type")
static void addHierarchy(hw::HierPathOp path, FModuleOp dut, InstanceOp wrapperInst)
Add an extra level of hierarchy to a hierarchical path that places the wrapper instance after the DUT...
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
bool hasAnnotation(StringRef className) const
Return true if we have an annotation with the specified class name.
static bool removePortAnnotations(Operation *module, llvm::function_ref< bool(unsigned, Annotation)> predicate)
Remove all port annotations from a module or extmodule for which predicate returns true.
static AnnotationSet forPort(FModuleLike op, size_t portNo)
Get an annotation set for the specified port.
This class provides a read-only projection of an annotation.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
void setMember(StringAttr name, Attribute value)
Add or set a member of the annotation to a value.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
constexpr const char * injectDUTHierarchyAnnoClass
constexpr const char * dutAnnoClass
std::unique_ptr< mlir::Pass > createInjectDUTHierarchyPass()
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Emit a connect between two values.
Definition: FIRRTLUtils.cpp:25
StringAttr getInnerSymName(Operation *op)
Return the StringAttr for the inner_sym name, if it exists.
Definition: FIRRTLOps.h:108
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
llvm::raw_ostream & debugPassHeader(const mlir::Pass *pass, int width=80)
Write a boilerplate header for a pass to the debug stream.
Definition: Debug.cpp:31
The namespace of a CircuitOp, generally inhabited by modules.
Definition: Namespace.h:24