CIRCT  19.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 "circt/Support/Debug.h"
25 #include "llvm/Support/Debug.h"
26 
27 #define DEBUG_TYPE "firrtl-inject-dut-hier"
28 
29 using namespace circt;
30 using namespace firrtl;
31 
32 namespace {
33 struct InjectDUTHierarchy : public InjectDUTHierarchyBase<InjectDUTHierarchy> {
34  void runOnOperation() override;
35 };
36 } // namespace
37 
38 /// Add an extra level of hierarchy to a hierarchical path that places the
39 /// wrapper instance after the DUT. E.g., this is converting:
40 ///
41 /// firrtl.hierpath [@Top::@dut, @DUT]
42 ///
43 /// Int:
44 ///
45 /// firrtl.hierpath [@Top::@dut, @DUT::@wrapper, @Wrapper]
46 static void addHierarchy(hw::HierPathOp path, FModuleOp dut,
47  InstanceOp wrapperInst) {
48  auto namepath = path.getNamepath().getValue();
49 
50  size_t nlaIdx = 0;
51  SmallVector<Attribute> newNamepath;
52  newNamepath.reserve(namepath.size() + 1);
53  while (path.modPart(nlaIdx) != dut.getNameAttr())
54  newNamepath.push_back(namepath[nlaIdx++]);
55  newNamepath.push_back(hw::InnerRefAttr::get(dut.getModuleNameAttr(),
56  getInnerSymName(wrapperInst)));
57 
58  // Add the extra level of hierarchy.
59  if (auto dutRef = dyn_cast<hw::InnerRefAttr>(namepath[nlaIdx]))
60  newNamepath.push_back(hw::InnerRefAttr::get(
61  wrapperInst.getModuleNameAttr().getAttr(), dutRef.getName()));
62  else
63  newNamepath.push_back(
64  FlatSymbolRefAttr::get(wrapperInst.getModuleNameAttr().getAttr()));
65 
66  // Add anything left over.
67  auto back = namepath.drop_front(nlaIdx + 1);
68  newNamepath.append(back.begin(), back.end());
69  path.setNamepathAttr(ArrayAttr::get(dut.getContext(), newNamepath));
70 }
71 
72 void InjectDUTHierarchy::runOnOperation() {
73  LLVM_DEBUG(debugPassHeader(this) << "\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 =
195  b.create<InstanceOp>(b.getUnknownLoc(), wrapper, wrapper.getModuleName(),
196  NameKindEnum::DroppableName, ArrayRef<Attribute>{},
197  ArrayRef<Attribute>{}, false,
198  hw::InnerSymAttr::get(b.getStringAttr(
199  dutNS.newName(wrapper.getModuleName()))));
200  for (const auto &pair : llvm::enumerate(wrapperInst.getResults())) {
201  Value lhs = dut.getArgument(pair.index());
202  Value rhs = pair.value();
203  if (dut.getPortDirection(pair.index()) == Direction::In)
204  std::swap(lhs, rhs);
205  emitConnect(b, b.getUnknownLoc(), lhs, rhs);
206  }
207 
208  // Compute a set of paths that are used _inside_ the wrapper.
209  DenseSet<StringAttr> dutPaths, dutPortSyms;
210  for (auto anno : AnnotationSet(dut)) {
211  auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
212  if (sym)
213  dutPaths.insert(sym.getAttr());
214  }
215  for (size_t i = 0, e = dut.getNumPorts(); i != e; ++i) {
216  auto portSym = dut.getPortSymbolAttr(i);
217  if (portSym)
218  dutPortSyms.insert(portSym.getSymName());
219  for (auto anno : AnnotationSet::forPort(dut, i)) {
220  auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
221  if (sym)
222  dutPaths.insert(sym.getAttr());
223  }
224  }
225 
226  LLVM_DEBUG({
227  llvm::dbgs() << "DUT Symbol Users:\n";
228  for (auto path : dutPaths)
229  llvm::dbgs() << " - " << FlatSymbolRefAttr::get(path) << "\n";
230  llvm::dbgs() << "Port Symbols:\n";
231  for (auto sym : dutPortSyms)
232  llvm::dbgs() << " - " << FlatSymbolRefAttr::get(sym) << "\n";
233  });
234 
235  // Update NLAs involving the DUT.
236  //
237  // NOTE: the _DUT_ is the new DUT and all the original DUT contents are put
238  // inside the DUT in the _wrapper_.
239  //
240  // There are three cases to consider:
241  // 1. The DUT or a DUT port is a leaf ref. Do nothing.
242  // 2. The DUT is the root. Update the root module to be the wrapper.
243  // 3. The NLA passes through the DUT. Remove the original InnerRef and
244  // replace it with two InnerRefs: (1) on the DUT and (2) one the wrapper.
245  LLVM_DEBUG(llvm::dbgs() << "Processing hierarchical paths:\n");
246  auto &nlaTable = getAnalysis<NLATable>();
247  DenseMap<StringAttr, hw::HierPathOp> dutRenames;
248  for (auto nla : llvm::make_early_inc_range(nlaTable.lookup(dut))) {
249  LLVM_DEBUG(llvm::dbgs() << " - " << nla << "\n");
250  auto namepath = nla.getNamepath().getValue();
251 
252  // The DUT is the root module. Just update the root module to point at the
253  // wrapper.
254  if (nla.root() == dut.getNameAttr()) {
255  assert(namepath.size() > 1 && "namepath size must be greater than one");
256  SmallVector<Attribute> newNamepath{hw::InnerRefAttr::get(
257  wrapper.getNameAttr(),
258  cast<hw::InnerRefAttr>(namepath.front()).getName())};
259  auto tail = namepath.drop_front();
260  newNamepath.append(tail.begin(), tail.end());
261  nla->setAttr("namepath", b.getArrayAttr(newNamepath));
262  continue;
263  }
264 
265  // The path ends at the DUT. This may be a reference path (ends in
266  // hw::InnerRefAttr) or a module path (ends in FlatSymbolRefAttr). There
267  // are a number of patterns to disambiguate:
268  //
269  // NOTE: the _DUT_ is the new DUT and all the original DUT contents are put
270  // inside the DUT in the _wrapper_.
271  //
272  // 1. Reference path on port. Do nothing.
273  // 2. Reference path on component. Add hierarchy
274  // 3. Module path on DUT/DUT port. Clone path, add hier to original path.
275  // 4. Module path on component. Ad dhierarchy.
276  //
277  if (nla.leafMod() == dut.getNameAttr()) {
278  // Case (1): ref path targeting a port. Do nothing.
279  if (nla.isComponent() && dutPortSyms.count(nla.ref()))
280  continue;
281 
282  // Case (3): the module path is used by the DUT module or a port. Create a
283  // clone of the path and update dutRenames so that this path symbol will
284  // get updated for annotations on the DUT or on its ports.
285  if (nla.isModule() && dutPaths.contains(nla.getSymNameAttr())) {
286  OpBuilder::InsertionGuard guard(b);
287  b.setInsertionPoint(nla);
288  auto clone = cast<hw::HierPathOp>(b.clone(*nla));
289  clone.setSymNameAttr(b.getStringAttr(
290  circuitNS.newName(clone.getSymNameAttr().getValue())));
291  dutRenames.insert({nla.getSymNameAttr(), clone});
292  }
293 
294  // Cases (2), (3), and (4): fallthrough to add hierarchy to original path.
295  }
296 
297  addHierarchy(nla, dut, wrapperInst);
298  }
299 
300  SmallVector<Annotation> newAnnotations;
301  auto removeAndUpdateNLAs = [&](Annotation anno) -> bool {
302  auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
303  if (!sym)
304  return false;
305  if (!dutRenames.count(sym.getAttr()))
306  return false;
307  anno.setMember(
308  "circt.nonlocal",
309  FlatSymbolRefAttr::get(dutRenames[sym.getAttr()].getSymNameAttr()));
310  newAnnotations.push_back(anno);
311  return true;
312  };
313 
314  // Replace any annotations on the DUT or DUT ports to use the cloned path.
315  AnnotationSet annotations(dut);
316  annotations.removeAnnotations(removeAndUpdateNLAs);
317  annotations.addAnnotations(newAnnotations);
318  annotations.applyToOperation(dut);
319  for (size_t i = 0, e = dut.getNumPorts(); i != e; ++i) {
320  newAnnotations.clear();
321  auto annotations = AnnotationSet::forPort(dut, i);
322  annotations.removeAnnotations(removeAndUpdateNLAs);
323  annotations.addAnnotations(newAnnotations);
324  annotations.applyToPort(dut, i);
325  }
326 }
327 
328 //===----------------------------------------------------------------------===//
329 // Pass Creation
330 //===----------------------------------------------------------------------===//
331 
332 std::unique_ptr<mlir::Pass> circt::firrtl::createInjectDUTHierarchyPass() {
333  return std::make_unique<InjectDUTHierarchy>();
334 }
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:54
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:24
StringAttr getInnerSymName(Operation *op)
Return the StringAttr for the inner_sym name, if it exists.
Definition: FIRRTLOps.h:107
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