CIRCT  20.0.0git
AddSeqMemPorts.cpp
Go to the documentation of this file.
1 //===- AddSeqMemPorts.cpp - Add extra ports to memories ---------*- C++ -*-===//
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 // This file defines the AddSeqMemPorts pass. This pass will add extra ports
9 // to memory modules.
10 //
11 //===----------------------------------------------------------------------===//
12 
23 #include "circt/Dialect/SV/SVOps.h"
24 #include "mlir/Pass/Pass.h"
25 #include "llvm/ADT/PostOrderIterator.h"
26 #include "llvm/ADT/STLFunctionalExtras.h"
27 #include "llvm/Support/Path.h"
28 
29 namespace circt {
30 namespace firrtl {
31 #define GEN_PASS_DEF_ADDSEQMEMPORTS
32 #include "circt/Dialect/FIRRTL/Passes.h.inc"
33 } // namespace firrtl
34 } // namespace circt
35 
36 using namespace circt;
37 using namespace firrtl;
38 
39 namespace {
40 struct AddSeqMemPortsPass
41  : public circt::firrtl::impl::AddSeqMemPortsBase<AddSeqMemPortsPass> {
42  void runOnOperation() override;
43  LogicalResult processAddPortAnno(Location loc, Annotation anno);
44  LogicalResult processFileAnno(Location loc, StringRef metadataDir,
45  Annotation anno);
46  LogicalResult processAnnos(CircuitOp circuit);
47  void createOutputFile(igraph::ModuleOpInterface moduleOp);
48  LogicalResult processMemModule(FMemModuleOp mem);
49  LogicalResult processModule(FModuleOp moduleOp);
50 
51  /// Get the cached namespace for a module.
52  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike moduleOp) {
53  return moduleNamespaces.try_emplace(moduleOp, moduleOp).first->second;
54  }
55 
56  /// Obtain an inner reference to an operation, possibly adding an `inner_sym`
57  /// to that operation.
58  hw::InnerRefAttr getInnerRefTo(Operation *op) {
60  [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
61  return getModuleNamespace(mod);
62  });
63  }
64 
65  /// This represents the collected information of the memories in a module.
66  struct MemoryInfo {
67  /// The list of extra ports added to this module to support additional ports
68  /// on all memories underneath this module.
69  std::vector<std::pair<unsigned, PortInfo>> extraPorts;
70 
71  /// This is an ordered list of instance paths to all memories underneath
72  /// this module. This will record each memory once for every port added,
73  /// which is for some reason the format of the metadata file.
74  std::vector<SmallVector<Attribute>> instancePaths;
75  };
76 
77  CircuitNamespace circtNamespace;
78  /// This maps a module to information about the memories.
79  DenseMap<Operation *, MemoryInfo> memInfoMap;
80  DenseMap<Attribute, Operation *> innerRefToInstanceMap;
81 
82  InstanceGraph *instanceGraph;
83  InstanceInfo *instanceInfo;
84 
85  /// If the metadata output file was specified in an annotation.
86  StringAttr outputFile;
87 
88  /// This is the list of every port to be added to each sequential memory.
89  SmallVector<PortInfo> userPorts;
90  /// This is an attribute holding the metadata for extra ports.
91  ArrayAttr extraPortsAttr;
92 
93  /// Cached module namespaces.
94  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
95 
96  bool anythingChanged;
97 };
98 } // end anonymous namespace
99 
100 LogicalResult AddSeqMemPortsPass::processAddPortAnno(Location loc,
101  Annotation anno) {
102  auto name = anno.getMember<StringAttr>("name");
103  if (!name)
104  return emitError(
105  loc, "AddSeqMemPortAnnotation requires field 'name' of string type");
106 
107  auto input = anno.getMember<BoolAttr>("input");
108  if (!input)
109  return emitError(
110  loc, "AddSeqMemPortAnnotation requires field 'input' of boolean type");
111  auto direction = input.getValue() ? Direction::In : Direction::Out;
112 
113  auto width = anno.getMember<IntegerAttr>("width");
114  if (!width)
115  return emitError(
116  loc, "AddSeqMemPortAnnotation requires field 'width' of integer type");
117  auto type = UIntType::get(&getContext(), width.getInt());
118  userPorts.push_back({name, type, direction});
119  return success();
120 }
121 
122 LogicalResult AddSeqMemPortsPass::processFileAnno(Location loc,
123  StringRef metadataDir,
124  Annotation anno) {
125  if (outputFile)
126  return emitError(
127  loc, "circuit has two AddSeqMemPortsFileAnnotation annotations");
128 
129  auto filename = anno.getMember<StringAttr>("filename");
130  if (!filename)
131  return emitError(loc,
132  "AddSeqMemPortsFileAnnotation requires field 'filename' "
133  "of string type");
134 
135  SmallString<128> outputFilePath(metadataDir);
136  llvm::sys::path::append(outputFilePath, filename.getValue());
137  outputFile = StringAttr::get(&getContext(), outputFilePath);
138  return success();
139 }
140 
141 LogicalResult AddSeqMemPortsPass::processAnnos(CircuitOp circuit) {
142  auto loc = circuit.getLoc();
143 
144  // Find the metadata directory.
145  auto dirAnno =
147  StringRef metadataDir = "metadata";
148  if (dirAnno) {
149  auto dir = dirAnno.getMember<StringAttr>("dirname");
150  if (!dir)
151  return emitError(loc, "MetadataDirAnnotation requires field 'dirname' of "
152  "string type");
153  metadataDir = dir.getValue();
154  }
155 
156  // Remove the annotations we care about.
157  bool error = false;
158  AnnotationSet::removeAnnotations(circuit, [&](Annotation anno) {
159  if (error)
160  return false;
161  if (anno.isClass(addSeqMemPortAnnoClass)) {
162  error = failed(processAddPortAnno(loc, anno));
163  return true;
164  }
166  error = failed(processFileAnno(loc, metadataDir, anno));
167  return true;
168  }
169  return false;
170  });
171  return failure(error);
172 }
173 
174 LogicalResult AddSeqMemPortsPass::processMemModule(FMemModuleOp mem) {
175  // Error if all instances are not under the effective DUT.
176  if (!instanceInfo->allInstancesInEffectiveDesign(mem)) {
177  auto diag = mem->emitOpError()
178  << "cannot have ports added to it because it is instantiated "
179  "both under and not under the design-under-test (DUT)";
180  for (auto *instNode : instanceGraph->lookup(mem)->uses()) {
181  auto instanceOp = instNode->getInstance();
182  if (auto layerBlockOp = instanceOp->getParentOfType<LayerBlockOp>()) {
183  diag.attachNote(instanceOp.getLoc())
184  << "this instance is under a layer block";
185  diag.attachNote(layerBlockOp.getLoc())
186  << "the innermost layer block is here";
187  continue;
188  }
189  if (!instanceInfo->allInstancesInEffectiveDesign(
190  instNode->getParent()->getModule())) {
191  diag.attachNote(instanceOp.getLoc())
192  << "this instance is inside a module that is instantiated outside "
193  "the design";
194  }
195  }
196  return failure();
197  }
198 
199  // We have to add the user ports to every mem module.
200  size_t portIndex = mem.getNumPorts();
201  auto &memInfo = memInfoMap[mem];
202  auto &extraPorts = memInfo.extraPorts;
203  for (auto const &p : userPorts)
204  extraPorts.emplace_back(portIndex, p);
205  mem.insertPorts(extraPorts);
206  // Attach the extraPorts metadata.
207  mem.setExtraPortsAttr(extraPortsAttr);
208  return success();
209 }
210 
211 LogicalResult AddSeqMemPortsPass::processModule(FModuleOp moduleOp) {
212  auto *context = &getContext();
213  auto builder = OpBuilder(moduleOp.getContext());
214  auto &memInfo = memInfoMap[moduleOp];
215  auto &extraPorts = memInfo.extraPorts;
216  // List of ports added to submodules which must be connected to this module's
217  // ports.
218  SmallVector<Value> values;
219 
220  // The base index to use when adding ports to the current module.
221  unsigned firstPortIndex = moduleOp.getNumPorts();
222 
223  auto result = moduleOp.walk([&](Operation *op) {
224  if (auto inst = dyn_cast<InstanceOp>(op)) {
225  auto submodule = inst.getReferencedModule(*instanceGraph);
226 
227  auto subMemInfoIt = memInfoMap.find(submodule);
228  // If there are no extra ports, we don't have to do anything.
229  if (subMemInfoIt == memInfoMap.end() ||
230  subMemInfoIt->second.extraPorts.empty())
231  return WalkResult::advance();
232  auto &subMemInfo = subMemInfoIt->second;
233  // Find out how many memory ports we have to add.
234  auto &subExtraPorts = subMemInfo.extraPorts;
235 
236  // Add the extra ports to the instance operation.
237  auto clone = inst.cloneAndInsertPorts(subExtraPorts);
238  inst.replaceAllUsesWith(
239  clone.getResults().drop_back(subExtraPorts.size()));
240  instanceGraph->replaceInstance(inst, clone);
241  inst->erase();
242  inst = clone;
243 
244  // Connect each submodule port up to the parent module ports.
245  for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
246  auto &[firstSubIndex, portInfo] = subExtraPorts[i];
247  // This is the index of the user port we are adding.
248  auto userIndex = i % userPorts.size();
249  auto const &sramPort = userPorts[userIndex];
250  // Construct a port name, e.g. "sram_0_user_inputs".
251  auto sramIndex = extraPorts.size() / userPorts.size();
252  auto portName =
253  StringAttr::get(context, "sram_" + Twine(sramIndex) + "_" +
254  sramPort.name.getValue());
255  auto portDirection = sramPort.direction;
256  auto portType = sramPort.type;
257  // Record the extra port.
258  extraPorts.push_back(
259  {firstPortIndex,
260  {portName, type_cast<FIRRTLType>(portType), portDirection}});
261  // If this is the DUT, then add a DontTouchAnnotation to any added ports
262  // to guarantee that it won't be removed.
263  if (instanceInfo->isEffectiveDut(moduleOp))
264  extraPorts.back().second.annotations.addDontTouch();
265  // Record the instance result for now, so that we can connect it to the
266  // parent module port after we actually add the ports.
267  values.push_back(inst.getResult(firstSubIndex + i));
268  }
269 
270  // We don't want to collect the instance paths or attach inner_syms to
271  // the instance path if we aren't creating the output file.
272  if (outputFile) {
273  // We record any instance paths to memories which are rooted at the
274  // current module.
275  auto &instancePaths = memInfo.instancePaths;
276  auto ref = getInnerRefTo(inst);
277  innerRefToInstanceMap[ref] = inst;
278  // If its a mem module, this is the start of a path to the module.
279  if (isa<FMemModuleOp>(submodule))
280  instancePaths.push_back({ref});
281  // Copy any paths through the submodule to memories, adding the ref to
282  // the current instance.
283  for (const auto &subPath : subMemInfo.instancePaths) {
284  instancePaths.push_back(subPath);
285  instancePaths.back().push_back(ref);
286  }
287  }
288 
289  return WalkResult::advance();
290  }
291 
292  return WalkResult::advance();
293  });
294 
295  if (result.wasInterrupted())
296  return failure();
297 
298  // Add the extra ports to this module.
299  moduleOp.insertPorts(extraPorts);
300 
301  // Get an existing invalid value or create a new one.
302  DenseMap<Type, InvalidValueOp> invalids;
303  auto getOrCreateInvalid = [&](Type type) -> InvalidValueOp {
304  auto it = invalids.find(type);
305  if (it != invalids.end())
306  return it->getSecond();
307  return invalids
308  .insert({type,
309  builder.create<InvalidValueOp>(builder.getUnknownLoc(), type)})
310  .first->getSecond();
311  };
312 
313  // Connect the submodule ports to the parent module ports.
314  DenseMap<Operation *, OpBuilder::InsertPoint> instToInsertionPoint;
315  for (unsigned i = 0, e = values.size(); i < e; ++i) {
316  auto &[firstArg, port] = extraPorts[i];
317  Value modulePort = moduleOp.getArgument(firstArg + i);
318  Value instPort = values[i];
319  Operation *instOp = instPort.getDefiningOp();
320  auto insertPoint = instToInsertionPoint.find(instOp);
321  if (insertPoint == instToInsertionPoint.end())
322  builder.setInsertionPointAfter(instOp);
323  else
324  builder.restoreInsertionPoint(insertPoint->getSecond());
325  if (port.direction == Direction::In)
326  std::swap(modulePort, instPort);
327  auto connectOp =
328  builder.create<MatchingConnectOp>(port.loc, modulePort, instPort);
329  instToInsertionPoint[instOp] = builder.saveInsertionPoint();
330  // If the connect was created inside a WhenOp, then the port needs to be
331  // invalidated to make a legal circuit.
332  if (port.direction == Direction::Out &&
333  connectOp->getParentOfType<WhenOp>()) {
334  builder.setInsertionPointToStart(moduleOp.getBodyBlock());
335  builder.create<MatchingConnectOp>(port.loc, modulePort,
336  getOrCreateInvalid(port.type));
337  }
338  }
339  return success();
340 }
341 
342 void AddSeqMemPortsPass::createOutputFile(igraph::ModuleOpInterface moduleOp) {
343  // Insert the verbatim at the bottom of the circuit.
344  auto circuit = getOperation();
345  auto builder = OpBuilder::atBlockEnd(circuit.getBodyBlock());
346 
347  // Output buffer.
348  std::string buffer;
349  llvm::raw_string_ostream os(buffer);
350 
351  SymbolTable &symTable = getAnalysis<SymbolTable>();
352  HierPathCache cache(circuit, symTable);
353 
354  // The current parameter to the verbatim op.
355  unsigned paramIndex = 0;
356  // Parameters to the verbatim op.
357  SmallVector<Attribute> params;
358  // Small cache to reduce the number of parameters passed to the verbatim.
359  DenseMap<Attribute, unsigned> usedParams;
360 
361  // Helper to add a symbol to the verbatim, hitting the cache on the way.
362  auto addSymbol = [&](Attribute ref) {
363  auto it = usedParams.find(ref);
364  unsigned index;
365  if (it != usedParams.end()) {
366  index = it->second;
367  } else {
368  index = paramIndex;
369  usedParams[ref] = paramIndex++;
370  params.push_back(ref);
371  }
372  os << "{{" << index << "}}";
373  };
374 
375  // The current sram we are processing.
376  unsigned sramIndex = 0;
377  auto &instancePaths = memInfoMap[moduleOp].instancePaths;
378  auto dutSymbol = FlatSymbolRefAttr::get(moduleOp.getModuleNameAttr());
379 
380  auto loc = builder.getUnknownLoc();
381  // Put the information in a verbatim operation.
382  builder.create<emit::FileOp>(loc, outputFile, [&] {
383  for (auto instancePath : instancePaths) {
384  // Note: Reverse instancepath to construct the NLA.
385  SmallVector<Attribute> path(llvm::reverse(instancePath));
386  os << sramIndex++ << " -> ";
387  addSymbol(dutSymbol);
388  os << ".";
389 
390  auto nlaSymbol = cache.getRefFor(builder.getArrayAttr(path));
391  addSymbol(nlaSymbol);
392  NamedAttrList fields;
393  // There is no current client for the distinct attr, but it will be used
394  // by OM::path once the metadata is moved to OM, instead of the verbatim.
395  auto id = DistinctAttr::create(UnitAttr::get(builder.getContext()));
396  fields.append("id", id);
397  fields.append("class", builder.getStringAttr("circt.tracker"));
398  fields.append("circt.nonlocal", nlaSymbol);
399  // Now add the nonlocal annotation to the leaf instance.
400  auto *leafInstance = innerRefToInstanceMap[instancePath.front()];
401 
402  AnnotationSet annos(leafInstance);
403  annos.addAnnotations(builder.getDictionaryAttr(fields));
404  annos.applyToOperation(leafInstance);
405 
406  os << "\n";
407  }
408  builder.create<sv::VerbatimOp>(loc, buffer, ValueRange{},
409  builder.getArrayAttr(params));
410  });
411  anythingChanged = true;
412 }
413 
414 void AddSeqMemPortsPass::runOnOperation() {
415  auto *context = &getContext();
416  auto circuit = getOperation();
417  instanceGraph = &getAnalysis<InstanceGraph>();
418  instanceInfo = &getAnalysis<InstanceInfo>();
419  circtNamespace = CircuitNamespace(circuit);
420  // Clear the state.
421  userPorts.clear();
422  memInfoMap.clear();
423  outputFile = {};
424  anythingChanged = false;
425 
426  // Collect the annotations from the circuit.
427  if (failed(processAnnos(circuit)))
428  return signalPassFailure();
429 
430  // SFC adds the ports in the opposite order they are attached, so we reverse
431  // the list here to match exactly.
432  std::reverse(userPorts.begin(), userPorts.end());
433 
434  // Process the extra ports so we can attach it as metadata on to each memory.
435  SmallVector<Attribute> extraPorts;
436  auto ui32Type = IntegerType::get(context, 32, IntegerType::Unsigned);
437  for (auto &userPort : userPorts) {
438  SmallVector<NamedAttribute, 3> attrs;
439  attrs.emplace_back(StringAttr::get(context, "name"), userPort.name);
440  attrs.emplace_back(
441  StringAttr::get(context, "direction"),
443  context, userPort.direction == Direction::In ? "input" : "output"));
444  attrs.emplace_back(
445  StringAttr::get(context, "width"),
447  ui32Type,
448  type_cast<FIRRTLBaseType>(userPort.type).getBitWidthOrSentinel()));
449  extraPorts.push_back(DictionaryAttr::get(context, attrs));
450  }
451  extraPortsAttr = ArrayAttr::get(context, extraPorts);
452 
453  // If there are no user ports, don't do anything.
454  if (!userPorts.empty()) {
455  // Update ports statistic.
456  numAddedPorts += userPorts.size();
457 
458  // Visit the modules in post-order starting from the effective
459  // design-under-test. Skip any modules that are wholly outside the design.
460  // If any memories are partially inside and outside the design then error.
461  for (auto *node : llvm::post_order(
462  instanceGraph->lookup(instanceInfo->getEffectiveDut()))) {
463  auto op = node->getModule();
464 
465  // Skip anything wholly _not_ in the design.
466  if (!instanceInfo->anyInstanceInEffectiveDesign(op))
467  continue;
468 
469  // Process the module or memory.
470  if (auto moduleOp = dyn_cast<FModuleOp>(*op)) {
471  if (failed(processModule(moduleOp)))
472  return signalPassFailure();
473  } else if (auto mem = dyn_cast<FMemModuleOp>(*op)) {
474  if (failed(processMemModule(mem)))
475  return signalPassFailure();
476  }
477  }
478 
479  // We handle the DUT differently than the rest of the modules.
480  auto effectiveDut = instanceInfo->getEffectiveDut();
481  if (auto *dut = dyn_cast<FModuleOp>(&effectiveDut)) {
482  // For each instance of the dut, add the instance ports, but tie the port
483  // to 0 instead of wiring them to the parent.
484  for (auto *instRec : instanceGraph->lookup(effectiveDut)->uses()) {
485  auto inst = cast<InstanceOp>(*instRec->getInstance());
486  auto &dutMemInfo = memInfoMap[*dut];
487  // Find out how many memory ports we have to add.
488  auto &subExtraPorts = dutMemInfo.extraPorts;
489  // If there are no extra ports, we don't have to do anything.
490  if (subExtraPorts.empty())
491  continue;
492 
493  // Add the extra ports to the instance operation.
494  auto clone = inst.cloneAndInsertPorts(subExtraPorts);
495  inst.replaceAllUsesWith(
496  clone.getResults().drop_back(subExtraPorts.size()));
497  instanceGraph->replaceInstance(inst, clone);
498  inst->erase();
499  inst = clone;
500 
501  // Tie each port to 0.
502  OpBuilder builder(context);
503  builder.setInsertionPointAfter(inst);
504  for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
505  auto &[firstResult, portInfo] = subExtraPorts[i];
506  if (portInfo.direction == Direction::Out)
507  continue;
508  auto value = inst.getResult(firstResult + i);
509  auto type = value.getType();
510  auto attr = getIntZerosAttr(type);
511  auto zero = builder.create<ConstantOp>(portInfo.loc, type, attr);
512  builder.create<MatchingConnectOp>(portInfo.loc, value, zero);
513  }
514  }
515  }
516  }
517 
518  // If there is an output file, create it.
519  if (outputFile)
520  createOutputFile(instanceInfo->getEffectiveDut());
521 
522  if (anythingChanged)
523  markAnalysesPreserved<InstanceGraph>();
524  else
525  markAllAnalysesPreserved();
526 }
527 
528 std::unique_ptr<mlir::Pass> circt::firrtl::createAddSeqMemPortsPass() {
529  return std::make_unique<AddSeqMemPortsPass>();
530 }
static std::unique_ptr< llvm::ToolOutputFile > createOutputFile(StringRef fileName, StringRef dirname, SharedEmitterState &emitter)
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
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.
Annotation getAnnotation(StringRef className) const
If this annotation set has an annotation with the specified class name, return it.
This class provides a read-only projection of an annotation.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
This graph tracks modules and where they are instantiated.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
constexpr const char * metadataDirectoryAttrName
hw::InnerRefAttr getInnerRefTo(const hw::InnerSymTarget &target, GetNamespaceCallback getNamespace)
Obtain an inner reference to the target (operation or port), adding an inner symbol as necessary.
constexpr const char * addSeqMemPortAnnoClass
constexpr const char * addSeqMemPortsFileAnnoClass
std::unique_ptr< mlir::Pass > createAddSeqMemPortsPass()
IntegerAttr getIntZerosAttr(Type type)
Utility for generating a constant zero attribute.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
The namespace of a CircuitOp, generally inhabited by modules.
Definition: Namespace.h:24
A cache of existing HierPathOps, mostly used to facilitate HierPathOp reuse.