Loading [MathJax]/extensions/tex2jax.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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// As the terminology in this pass is a constant source of confusion, FIGURE 1
13// below, and the accompanying description of terms is provided to clarify what
14// is going on.
15//
16// BEFORE AFTER
17// +-------------+ +-------------+
18// | DUT | | DUT |
19// | | | +---------+ |
20// | | | | WRAPPER | |
21// | | ====> | | | |
22// | LOGIC | | | LOGIC | |
23// | | | +---------+ |
24// +-------------+ +-------------+
25//
26// FIGURE 1: A graphical view of this pass
27//
28// In FIGURE 1, the DUT (design under test which roughly is the unit of
29// compilation excluding all testharness, testbenches, or tests) is the module
30// in the circuit which is annotated with a `MarkDUTAnnotation`. Inside the
31// DUT, there exists some LOGIC. This is the complete contents of all
32// operations inside the DUT module. Logically, this pass takes the LOGIC and
33// puts it into a new MODULE, called the WRAPPER.
34//
35// This pass operates in two modes, moderated by a `moveDut` boolean parameter
36// on the controlling annotation. When `moveDut=false`, the DUT in FIGURE 1 is
37// treated as the design under test. When `moveDut=true`, the WRAPPER in FIGURE
38// 1 is treated as the design under test. Mechanically, this means that
39// annotations on the DUT will be moved to the wrapper.
40//
41// The names of the WRAPPER and DUT change based on the mode. If
42// `moveDut=false`, then the WRAPPER is named using the `name` field of the
43// controlling annotation and the DUT gets the name of the original DUT. If
44// `moveDut=true`, then the DUT is named using the `name` field and the WRAPPER
45// gets the name of the original DUT.
46//
47// This pass is closely coupled to `ExtractInstances`. This pass is intended to
48// be used to create a "space" where modules can be extracted or groups of
49// modules can be extracted to. Commonly, the LOGIC will be extracted to the
50// WRAPPER, memories will be extracted to a MEMORIES module, and other things
51// (clock gates and blackboxes) will be extracted to other modules.
52//
53//===----------------------------------------------------------------------===//
54
66#include "circt/Support/Debug.h"
67#include "mlir/Pass/Pass.h"
68#include "llvm/Support/Debug.h"
69
70#define DEBUG_TYPE "firrtl-inject-dut-hier"
71
72namespace circt {
73namespace firrtl {
74#define GEN_PASS_DEF_INJECTDUTHIERARCHY
75#include "circt/Dialect/FIRRTL/Passes.h.inc"
76} // namespace firrtl
77} // namespace circt
78
79using namespace circt;
80using namespace firrtl;
81
82namespace {
83struct InjectDUTHierarchy
84 : public circt::firrtl::impl::InjectDUTHierarchyBase<InjectDUTHierarchy> {
85 void runOnOperation() override;
86};
87} // namespace
88
89/// Add an extra level of hierarchy to a hierarchical path that places the
90/// wrapper instance after the DUT. This appends to the existing path
91/// immediately after the `dut`.
92///
93/// E.g., this is converting:
94///
95/// firrtl.hierpath [@Top::@dut, @DUT]
96///
97/// Into:
98///
99/// firrtl.hierpath [@Top::@dut, @DUT::@wrapper, @Wrapper]
100///
101/// The `oldDutNameAttr` parameter controls the insertion point. I.e., this is
102/// the location of a module wehre an insertion will happen. By separating this
103/// from the `dut` paramter, this allows this function to work for both the
104/// `moveDut=false` and `moveDut=true` cases. In the `moveDut=false` case the
105/// `dut` and `oldDutNameAttr` refer to the same module.
106static void addHierarchy(hw::HierPathOp path, FModuleOp dut,
107 InstanceOp wrapperInst, StringAttr oldDutNameAttr) {
108
109 auto namepath = path.getNamepath().getValue();
110
111 size_t nlaIdx = 0;
112 SmallVector<Attribute> newNamepath;
113 newNamepath.reserve(namepath.size() + 1);
114 while (path.modPart(nlaIdx) != oldDutNameAttr)
115 newNamepath.push_back(namepath[nlaIdx++]);
116 newNamepath.push_back(hw::InnerRefAttr::get(dut.getModuleNameAttr(),
117 getInnerSymName(wrapperInst)));
118
119 // Add the extra level of hierarchy.
120 if (auto dutRef = dyn_cast<hw::InnerRefAttr>(namepath[nlaIdx]))
121 newNamepath.push_back(hw::InnerRefAttr::get(
122 wrapperInst.getModuleNameAttr().getAttr(), dutRef.getName()));
123 else
124 newNamepath.push_back(
125 FlatSymbolRefAttr::get(wrapperInst.getModuleNameAttr().getAttr()));
126
127 // Add anything left over.
128 auto back = namepath.drop_front(nlaIdx + 1);
129 newNamepath.append(back.begin(), back.end());
130 path.setNamepathAttr(ArrayAttr::get(dut.getContext(), newNamepath));
131}
132
133void InjectDUTHierarchy::runOnOperation() {
134 LLVM_DEBUG(debugPassHeader(this) << "\n";);
135
136 CircuitOp circuit = getOperation();
137
138 /// The wrapper module that is created inside the DUT to house all its logic.
139 FModuleOp wrapper;
140
141 /// The name of the new module to create under the DUT.
142 StringAttr wrapperName;
143
144 /// If true, then move the MarkDUTAnnotation to the newly created module.
145 bool moveDut = false;
146
147 /// Mutable indicator that an error occurred for some reason. If this is ever
148 /// true, then the pass can just signalPassFailure.
149 bool error = false;
150
151 // Do not remove the injection annotation as this is necessary to additionally
152 // influence ExtractInstances.
153 for (Annotation anno : AnnotationSet(circuit)) {
154 if (!anno.isClass(injectDUTHierarchyAnnoClass))
155 continue;
156
157 auto name = anno.getMember<StringAttr>("name");
158 if (!name) {
159 emitError(circuit->getLoc())
160 << "contained a malformed "
161 "'sifive.enterprise.firrtl.InjectDUTHierarchyAnnotation' "
162 "annotation that did not contain a 'name' field";
163 error = true;
164 continue;
165 }
166
167 if (wrapperName) {
168 emitError(circuit->getLoc())
169 << "contained multiple "
170 "'sifive.enterprise.firrtl.InjectDUTHierarchyAnnotation' "
171 "annotations when at most one is allowed";
172 error = true;
173 continue;
174 }
175
176 wrapperName = name;
177 if (auto moveDutAnnoAttr = anno.getMember<BoolAttr>("moveDut"))
178 moveDut = moveDutAnnoAttr.getValue();
179 }
180
181 if (error)
182 return signalPassFailure();
183
184 // The prerequisites for the pass to run were not met. Indicate that no work
185 // was done and exit.
186 if (!wrapperName)
187 return markAllAnalysesPreserved();
188
189 // A DUT must exist in order to continue. The pass could silently ignore this
190 // case and do nothing, but it is better to provide an error.
191 InstanceInfo &instanceInfo = getAnalysis<InstanceInfo>();
192 if (!instanceInfo.getDut()) {
193 emitError(circuit->getLoc())
194 << "contained a '" << injectDUTHierarchyAnnoClass << "', but no '"
195 << dutAnnoClass << "' was provided";
196 return signalPassFailure();
197 }
198
199 /// The design-under-test (DUT). This is kept up-to-date by the pass as the
200 /// DUT changes due to internal logic.
201 FModuleOp dut = cast<FModuleOp>(instanceInfo.getDut());
202
203 // Create a module that will become the new DUT. The original DUT is renamed
204 // to become the wrapper. This is done to save copying into the wrapper.
205 // While the logical movement is "copy the body of the DUT into a wrapper", it
206 // is mechanically more straigthforward to make the DUT the wrappper. After
207 // this block finishes, the "dut" and "wrapper" variables are set correctly.
208 // This logic is intentionally put into a block to avoid confusion while the
209 // dut and wrapper do not match the logical definition.
210 OpBuilder b(circuit.getContext());
211 CircuitNamespace circuitNS(circuit);
212 b.setInsertionPointAfter(dut);
213
214 // After this, he original DUT module is now the "wrapper". The new module we
215 // just created becomes the "DUT".
216 auto oldDutNameAttr = dut.getNameAttr();
217 wrapper = dut;
218 dut = b.create<FModuleOp>(dut.getLoc(), oldDutNameAttr,
219 dut.getConventionAttr(), dut.getPorts());
220
221 // Finish setting up the DUT and the Wrapper. This depends on if we are in
222 // `moveDut` mode or not. If `moveDut=false` (the normal, legacy behavior),
223 // then the wrapper is a wrapper of logic inside the original DUT. The newly
224 // created module to instantiate the wrapper becomes the DUT. In this mode,
225 // we need to move all annotations over to the new DUT. If `moveDut=true`,
226 // then we need to move all the annotations/information from the wrapper onto
227 // the DUT.
228 //
229 // This pass shouldn't create new public modules. It should only preserve the
230 // existing public modules. In "moveDut" mode, then the wrapper is the new
231 // DUT and we should move the publicness from the old DUT to the wrapper.
232 // When not in "moveDut" mode, then the wrapper should be made private.
233 //
234 // Note: `movedDut=true` violates the FIRRTL ABI unless the user it doing
235 // something clever with module prefixing. Because this annotation is already
236 // outside the specification, this workflow is allowed even though it violates
237 // the FIRRTL ABI. The mid-term plan is to remove this pass to avoid the tech
238 // debt that it creates.
239 auto emptyArray = b.getArrayAttr({});
240 auto name = circuitNS.newName(wrapperName.getValue());
241 if (moveDut) {
242 dut.setPrivate();
243 dut.setPortAnnotationsAttr(emptyArray);
244 dut.setName(b.getStringAttr(name));
245 // The DUT name has changed. Rewrite instances to use the new DUT name.
246 InstanceGraph &instanceGraph = getAnalysis<InstanceGraph>();
247 for (auto *use : instanceGraph.lookup(wrapper.getNameAttr())->uses()) {
248 auto instanceOp = use->getInstance<InstanceOp>();
249 if (!instanceOp) {
250 use->getInstance().emitOpError()
251 << "instantiates the design-under-test, but "
252 "is not a 'firrtl.instance'";
253 return signalPassFailure();
254 }
255 instanceOp.setModuleNameAttr(FlatSymbolRefAttr::get(dut.getNameAttr()));
256 }
257 } else {
258 dut.setVisibility(wrapper.getVisibility());
259 dut.setAnnotationsAttr(wrapper.getAnnotationsAttr());
260 dut.setPortAnnotationsAttr(wrapper.getPortAnnotationsAttr());
261 wrapper.setPrivate();
262 wrapper.setAnnotationsAttr(emptyArray);
263 wrapper.setPortAnnotationsAttr(emptyArray);
264 wrapper.setName(name);
265 }
266
267 // Instantiate the wrapper inside the DUT and wire it up.
268 b.setInsertionPointToStart(dut.getBodyBlock());
269 hw::InnerSymbolNamespace dutNS(dut);
270 auto wrapperInst =
271 b.create<InstanceOp>(b.getUnknownLoc(), wrapper, wrapper.getModuleName(),
272 NameKindEnum::DroppableName, ArrayRef<Attribute>{},
273 ArrayRef<Attribute>{}, false, false,
274 hw::InnerSymAttr::get(b.getStringAttr(
275 dutNS.newName(wrapper.getModuleName()))));
276 for (const auto &pair : llvm::enumerate(wrapperInst.getResults())) {
277 Value lhs = dut.getArgument(pair.index());
278 Value rhs = pair.value();
279 if (dut.getPortDirection(pair.index()) == Direction::In)
280 std::swap(lhs, rhs);
281 emitConnect(b, b.getUnknownLoc(), lhs, rhs);
282 }
283
284 // Compute a set of paths that are used _inside_ the wrapper.
285 DenseSet<StringAttr> dutPaths, dutPortSyms;
286 for (auto anno : AnnotationSet(dut)) {
287 auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
288 if (sym)
289 dutPaths.insert(sym.getAttr());
290 }
291 for (size_t i = 0, e = dut.getNumPorts(); i != e; ++i) {
292 auto portSym = dut.getPortSymbolAttr(i);
293 if (portSym)
294 dutPortSyms.insert(portSym.getSymName());
295 for (auto anno : AnnotationSet::forPort(dut, i)) {
296 auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
297 if (sym)
298 dutPaths.insert(sym.getAttr());
299 }
300 }
301
302 LLVM_DEBUG({
303 llvm::dbgs() << "DUT Symbol Users:\n";
304 for (auto path : dutPaths)
305 llvm::dbgs() << " - " << FlatSymbolRefAttr::get(path) << "\n";
306 llvm::dbgs() << "Port Symbols:\n";
307 for (auto sym : dutPortSyms)
308 llvm::dbgs() << " - " << FlatSymbolRefAttr::get(sym) << "\n";
309 });
310
311 // Update NLAs involving the DUT.
312 //
313 // In the `moveDut=true` case, the WRAPPER will have the `MarkDUTAnnotation`
314 // moved onto it and this will be the "design-under-test" from the perspective
315 // of later passes. In the `moveDut=false` case, the DUT will be the
316 // "design-under-test.
317 //
318 // There are three cases to consider:
319 // 1. The DUT or a DUT port is a leaf ref. Do nothing.
320 // 2. The DUT is the root. Update the root module to be the wrapper.
321 // 3. The NLA passes through the DUT. Remove the original InnerRef and
322 // replace it with two InnerRefs: (1) on the DUT and (2) one the wrapper.
323 LLVM_DEBUG(llvm::dbgs() << "Processing hierarchical paths:\n");
324 auto &nlaTable = getAnalysis<NLATable>();
325 DenseMap<StringAttr, hw::HierPathOp> dutRenames;
326 for (auto nla : llvm::make_early_inc_range(nlaTable.lookup(oldDutNameAttr))) {
327 LLVM_DEBUG(llvm::dbgs() << " - " << nla << "\n");
328 auto namepath = nla.getNamepath().getValue();
329
330 // The DUT is the root module. Just update the root module to point at the
331 // wrapper.
332 //
333 // TODO: It _may_ be desirable to only do this in the `moveDut=true` case.
334 // In the `moveDut=false` case, this will change the semantic of the
335 // annotation if the annotation user is assuming that the annotation root is
336 // locked to the DUT. However, annotations are not supposed to have
337 // semantics like this.
338 if (nla.root() == oldDutNameAttr) {
339 assert(namepath.size() > 1 && "namepath size must be greater than one");
340 SmallVector<Attribute> newNamepath{hw::InnerRefAttr::get(
341 wrapper.getNameAttr(),
342 cast<hw::InnerRefAttr>(namepath.front()).getName())};
343 auto tail = namepath.drop_front();
344 newNamepath.append(tail.begin(), tail.end());
345 nla->setAttr("namepath", b.getArrayAttr(newNamepath));
346 continue;
347 }
348
349 // The path ends at the DUT. This may be a reference path (ends in
350 // hw::InnerRefAttr) or a module path (ends in FlatSymbolRefAttr). There
351 // are a number of patterns to disambiguate:
352 //
353 // NOTE: the _DUT_ is the new DUT and all the original DUT contents are put
354 // inside the DUT in the _wrapper_.
355 //
356 // 1. Reference path on DUT port. Do nothing.
357 // 2. Reference path on component. Add hierarchy
358 // 3. Module path on DUT/DUT port. Clone path, add hier to original path.
359 // 4. Module path on component. Add hierarchy.
360 //
361 if (nla.leafMod() == oldDutNameAttr) {
362 // Case (1): ref path targeting a DUT port. Do nothing. When
363 // `moveDut=true`, this is always false.
364 if (nla.isComponent() && dutPortSyms.count(nla.ref()))
365 continue;
366
367 // Case (3): the module path is used by the DUT module or a port. Create a
368 // clone of the path and update dutRenames so that this path symbol will
369 // get updated for annotations on the DUT or on its ports.
370 if (nla.isModule() && dutPaths.contains(nla.getSymNameAttr())) {
371 OpBuilder::InsertionGuard guard(b);
372 b.setInsertionPoint(nla);
373 auto clone = cast<hw::HierPathOp>(b.clone(*nla));
374 clone.setSymNameAttr(b.getStringAttr(
375 circuitNS.newName(clone.getSymNameAttr().getValue())));
376 dutRenames.insert({nla.getSymNameAttr(), clone});
377 }
378
379 // Cases (2), (3), and (4): fallthrough to add hierarchy to original path.
380 }
381
382 addHierarchy(nla, dut, wrapperInst, oldDutNameAttr);
383 }
384
385 SmallVector<Annotation> newAnnotations;
386 auto removeAndUpdateNLAs = [&](Annotation anno) -> bool {
387 auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
388 if (!sym)
389 return false;
390 if (!dutRenames.count(sym.getAttr()))
391 return false;
392 anno.setMember(
393 "circt.nonlocal",
394 FlatSymbolRefAttr::get(dutRenames[sym.getAttr()].getSymNameAttr()));
395 newAnnotations.push_back(anno);
396 return true;
397 };
398
399 // Replace any annotations on the DUT or DUT ports to use the cloned path.
400 AnnotationSet annotations(dut);
401 annotations.removeAnnotations(removeAndUpdateNLAs);
402 annotations.addAnnotations(newAnnotations);
403 annotations.applyToOperation(dut);
404 for (size_t i = 0, e = dut.getNumPorts(); i != e; ++i) {
405 newAnnotations.clear();
406 auto annotations = AnnotationSet::forPort(dut, i);
407 annotations.removeAnnotations(removeAndUpdateNLAs);
408 annotations.addAnnotations(newAnnotations);
409 annotations.applyToPort(dut, i);
410 }
411
412 // Update rwprobe operations' local innerrefs within the module.
413 wrapper.walk([&](RWProbeOp rwp) {
414 rwp.setTargetAttr(hw::InnerRefAttr::get(wrapper.getModuleNameAttr(),
415 rwp.getTarget().getName()));
416 });
417}
418
419//===----------------------------------------------------------------------===//
420// Pass Creation
421//===----------------------------------------------------------------------===//
422
423std::unique_ptr<mlir::Pass> circt::firrtl::createInjectDUTHierarchyPass() {
424 return std::make_unique<InjectDUTHierarchy>();
425}
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, StringAttr oldDutNameAttr)
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...
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.
This graph tracks modules and where they are instantiated.
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
void error(Twine message)
Definition LSPUtils.cpp:16
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