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