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