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