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