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->allInstancesInEffectiveDesign(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 (auto layerBlockOp = instanceOp->getParentOfType<LayerBlockOp>()) {
186  diag.attachNote(instanceOp.getLoc())
187  << "this instance is under a layer block";
188  diag.attachNote(layerBlockOp.getLoc())
189  << "the innermost layer block is here";
190  continue;
191  }
192  if (!instanceInfo->allInstancesInEffectiveDesign(
193  instNode->getParent()->getModule())) {
194  diag.attachNote(instanceOp.getLoc())
195  << "this instance is inside a module that is instantiated outside "
196  "the design";
197  }
198  }
199  return failure();
200  }
201 
202  // We have to add the user ports to every mem module.
203  size_t portIndex = mem.getNumPorts();
204  auto &memInfo = memInfoMap[mem];
205  auto &extraPorts = memInfo.extraPorts;
206  for (auto const &p : userPorts)
207  extraPorts.emplace_back(portIndex, p);
208  mem.insertPorts(extraPorts);
209  // Attach the extraPorts metadata.
210  mem.setExtraPortsAttr(extraPortsAttr);
211  return success();
212 }
213 
214 LogicalResult AddSeqMemPortsPass::processModule(FModuleOp moduleOp) {
215  auto *context = &getContext();
216  auto builder = OpBuilder(moduleOp.getContext());
217  auto &memInfo = memInfoMap[moduleOp];
218  auto &extraPorts = memInfo.extraPorts;
219  // List of ports added to submodules which must be connected to this module's
220  // ports.
221  SmallVector<Value> values;
222 
223  // The base index to use when adding ports to the current module.
224  unsigned firstPortIndex = moduleOp.getNumPorts();
225 
226  auto result = moduleOp.walk([&](Operation *op) {
227  if (auto inst = dyn_cast<InstanceOp>(op)) {
228  auto submodule = inst.getReferencedModule(*instanceGraph);
229 
230  auto subMemInfoIt = memInfoMap.find(submodule);
231  // If there are no extra ports, we don't have to do anything.
232  if (subMemInfoIt == memInfoMap.end() ||
233  subMemInfoIt->second.extraPorts.empty())
234  return WalkResult::advance();
235  auto &subMemInfo = subMemInfoIt->second;
236  // Find out how many memory ports we have to add.
237  auto &subExtraPorts = subMemInfo.extraPorts;
238 
239  // Add the extra ports to the instance operation.
240  auto clone = inst.cloneAndInsertPorts(subExtraPorts);
241  inst.replaceAllUsesWith(
242  clone.getResults().drop_back(subExtraPorts.size()));
243  instanceGraph->replaceInstance(inst, clone);
244  inst->erase();
245  inst = clone;
246 
247  // Connect each submodule port up to the parent module ports.
248  for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
249  auto &[firstSubIndex, portInfo] = subExtraPorts[i];
250  // This is the index of the user port we are adding.
251  auto userIndex = i % userPorts.size();
252  auto const &sramPort = userPorts[userIndex];
253  // Construct a port name, e.g. "sram_0_user_inputs".
254  auto sramIndex = extraPorts.size() / userPorts.size();
255  auto portName =
256  StringAttr::get(context, "sram_" + Twine(sramIndex) + "_" +
257  sramPort.name.getValue());
258  auto portDirection = sramPort.direction;
259  auto portType = sramPort.type;
260  // Record the extra port.
261  extraPorts.push_back(
262  {firstPortIndex,
263  {portName, type_cast<FIRRTLType>(portType), portDirection}});
264  // If this is the DUT, then add a DontTouchAnnotation to any added ports
265  // to guarantee that it won't be removed.
266  if (instanceInfo->isEffectiveDut(moduleOp))
267  extraPorts.back().second.annotations.addDontTouch();
268  // Record the instance result for now, so that we can connect it to the
269  // parent module port after we actually add the ports.
270  values.push_back(inst.getResult(firstSubIndex + i));
271  }
272 
273  // We don't want to collect the instance paths or attach inner_syms to
274  // the instance path if we aren't creating the output file.
275  if (outputFile) {
276  // We record any instance paths to memories which are rooted at the
277  // current module.
278  auto &instancePaths = memInfo.instancePaths;
279  auto ref = getInnerRefTo(inst);
280  innerRefToInstanceMap[ref] = inst;
281  // If its a mem module, this is the start of a path to the module.
282  if (isa<FMemModuleOp>(submodule))
283  instancePaths.push_back({ref});
284  // Copy any paths through the submodule to memories, adding the ref to
285  // the current instance.
286  for (const auto &subPath : subMemInfo.instancePaths) {
287  instancePaths.push_back(subPath);
288  instancePaths.back().push_back(ref);
289  }
290  }
291 
292  return WalkResult::advance();
293  }
294 
295  return WalkResult::advance();
296  });
297 
298  if (result.wasInterrupted())
299  return failure();
300 
301  // Add the extra ports to this module.
302  moduleOp.insertPorts(extraPorts);
303 
304  // Get an existing invalid value or create a new one.
305  DenseMap<Type, InvalidValueOp> invalids;
306  auto getOrCreateInvalid = [&](Type type) -> InvalidValueOp {
307  auto it = invalids.find(type);
308  if (it != invalids.end())
309  return it->getSecond();
310  return invalids
311  .insert({type,
312  builder.create<InvalidValueOp>(builder.getUnknownLoc(), type)})
313  .first->getSecond();
314  };
315 
316  // Connect the submodule ports to the parent module ports.
317  DenseMap<Operation *, OpBuilder::InsertPoint> instToInsertionPoint;
318  for (unsigned i = 0, e = values.size(); i < e; ++i) {
319  auto &[firstArg, port] = extraPorts[i];
320  Value modulePort = moduleOp.getArgument(firstArg + i);
321  Value instPort = values[i];
322  Operation *instOp = instPort.getDefiningOp();
323  auto insertPoint = instToInsertionPoint.find(instOp);
324  if (insertPoint == instToInsertionPoint.end())
325  builder.setInsertionPointAfter(instOp);
326  else
327  builder.restoreInsertionPoint(insertPoint->getSecond());
328  if (port.direction == Direction::In)
329  std::swap(modulePort, instPort);
330  auto connectOp =
331  builder.create<MatchingConnectOp>(port.loc, modulePort, instPort);
332  instToInsertionPoint[instOp] = builder.saveInsertionPoint();
333  // If the connect was created inside a WhenOp, then the port needs to be
334  // invalidated to make a legal circuit.
335  if (port.direction == Direction::Out &&
336  connectOp->getParentOfType<WhenOp>()) {
337  builder.setInsertionPointToStart(moduleOp.getBodyBlock());
338  builder.create<MatchingConnectOp>(port.loc, modulePort,
339  getOrCreateInvalid(port.type));
340  }
341  }
342  return success();
343 }
344 
345 void AddSeqMemPortsPass::createOutputFile(igraph::ModuleOpInterface moduleOp) {
346  // Insert the verbatim at the bottom of the circuit.
347  auto circuit = getOperation();
348  auto builder = OpBuilder::atBlockEnd(circuit.getBodyBlock());
349 
350  // Output buffer.
351  std::string buffer;
352  llvm::raw_string_ostream os(buffer);
353 
354  SymbolTable &symTable = getAnalysis<SymbolTable>();
355  HierPathCache cache(circuit, symTable);
356 
357  // The current parameter to the verbatim op.
358  unsigned paramIndex = 0;
359  // Parameters to the verbatim op.
360  SmallVector<Attribute> params;
361  // Small cache to reduce the number of parameters passed to the verbatim.
362  DenseMap<Attribute, unsigned> usedParams;
363 
364  // Helper to add a symbol to the verbatim, hitting the cache on the way.
365  auto addSymbol = [&](Attribute ref) {
366  auto it = usedParams.find(ref);
367  unsigned index;
368  if (it != usedParams.end()) {
369  index = it->second;
370  } else {
371  index = paramIndex;
372  usedParams[ref] = paramIndex++;
373  params.push_back(ref);
374  }
375  os << "{{" << index << "}}";
376  };
377 
378  // The current sram we are processing.
379  unsigned sramIndex = 0;
380  auto &instancePaths = memInfoMap[moduleOp].instancePaths;
381  auto dutSymbol = FlatSymbolRefAttr::get(moduleOp.getModuleNameAttr());
382 
383  auto loc = builder.getUnknownLoc();
384  // Put the information in a verbatim operation.
385  builder.create<emit::FileOp>(loc, outputFile, [&] {
386  for (auto instancePath : instancePaths) {
387  // Note: Reverse instancepath to construct the NLA.
388  SmallVector<Attribute> path(llvm::reverse(instancePath));
389  os << sramIndex++ << " -> ";
390  addSymbol(dutSymbol);
391  os << ".";
392 
393  auto nlaSymbol = cache.getRefFor(builder.getArrayAttr(path));
394  addSymbol(nlaSymbol);
395  NamedAttrList fields;
396  // There is no current client for the distinct attr, but it will be used
397  // by OM::path once the metadata is moved to OM, instead of the verbatim.
398  auto id = DistinctAttr::create(UnitAttr::get(builder.getContext()));
399  fields.append("id", id);
400  fields.append("class", builder.getStringAttr("circt.tracker"));
401  fields.append("circt.nonlocal", nlaSymbol);
402  // Now add the nonlocal annotation to the leaf instance.
403  auto *leafInstance = innerRefToInstanceMap[instancePath.front()];
404 
405  AnnotationSet annos(leafInstance);
406  annos.addAnnotations(builder.getDictionaryAttr(fields));
407  annos.applyToOperation(leafInstance);
408 
409  os << "\n";
410  }
411  builder.create<sv::VerbatimOp>(loc, buffer, ValueRange{},
412  builder.getArrayAttr(params));
413  });
414  anythingChanged = true;
415 }
416 
417 void AddSeqMemPortsPass::runOnOperation() {
418  auto *context = &getContext();
419  auto circuit = getOperation();
420  instanceGraph = &getAnalysis<InstanceGraph>();
421  instanceInfo = &getAnalysis<InstanceInfo>();
422  circtNamespace = CircuitNamespace(circuit);
423  // Clear the state.
424  userPorts.clear();
425  memInfoMap.clear();
426  outputFile = {};
427  anythingChanged = false;
428 
429  // Collect the annotations from the circuit.
430  if (failed(processAnnos(circuit)))
431  return signalPassFailure();
432 
433  // SFC adds the ports in the opposite order they are attached, so we reverse
434  // the list here to match exactly.
435  std::reverse(userPorts.begin(), userPorts.end());
436 
437  // Process the extra ports so we can attach it as metadata on to each memory.
438  SmallVector<Attribute> extraPorts;
439  auto ui32Type = IntegerType::get(context, 32, IntegerType::Unsigned);
440  for (auto &userPort : userPorts) {
441  SmallVector<NamedAttribute, 3> attrs;
442  attrs.emplace_back(StringAttr::get(context, "name"), userPort.name);
443  attrs.emplace_back(
444  StringAttr::get(context, "direction"),
446  context, userPort.direction == Direction::In ? "input" : "output"));
447  attrs.emplace_back(
448  StringAttr::get(context, "width"),
450  ui32Type,
451  type_cast<FIRRTLBaseType>(userPort.type).getBitWidthOrSentinel()));
452  extraPorts.push_back(DictionaryAttr::get(context, attrs));
453  }
454  extraPortsAttr = ArrayAttr::get(context, extraPorts);
455 
456  // If there are no user ports, don't do anything.
457  if (!userPorts.empty()) {
458  // Update ports statistic.
459  numAddedPorts += userPorts.size();
460 
461  // Visit the modules in post-order starting from the effective
462  // design-under-test. Skip any modules that are wholly outside the design.
463  // If any memories are partially inside and outside the design then error.
464  for (auto *node : llvm::post_order(
465  instanceGraph->lookup(instanceInfo->getEffectiveDut()))) {
466  auto op = node->getModule();
467 
468  // Skip anything wholly _not_ in the design.
469  if (!instanceInfo->anyInstanceInEffectiveDesign(op))
470  continue;
471 
472  // Process the module or memory.
473  if (auto moduleOp = dyn_cast<FModuleOp>(*op)) {
474  if (failed(processModule(moduleOp)))
475  return signalPassFailure();
476  } else if (auto mem = dyn_cast<FMemModuleOp>(*op)) {
477  if (failed(processMemModule(mem)))
478  return signalPassFailure();
479  }
480  }
481 
482  // We handle the DUT differently than the rest of the modules.
483  auto effectiveDut = instanceInfo->getEffectiveDut();
484  if (auto *dut = dyn_cast<FModuleOp>(&effectiveDut)) {
485  // For each instance of the dut, add the instance ports, but tie the port
486  // to 0 instead of wiring them to the parent.
487  for (auto *instRec : instanceGraph->lookup(effectiveDut)->uses()) {
488  auto inst = cast<InstanceOp>(*instRec->getInstance());
489  auto &dutMemInfo = memInfoMap[*dut];
490  // Find out how many memory ports we have to add.
491  auto &subExtraPorts = dutMemInfo.extraPorts;
492  // If there are no extra ports, we don't have to do anything.
493  if (subExtraPorts.empty())
494  continue;
495 
496  // Add the extra ports to the instance operation.
497  auto clone = inst.cloneAndInsertPorts(subExtraPorts);
498  inst.replaceAllUsesWith(
499  clone.getResults().drop_back(subExtraPorts.size()));
500  instanceGraph->replaceInstance(inst, clone);
501  inst->erase();
502  inst = clone;
503 
504  // Tie each port to 0.
505  OpBuilder builder(context);
506  builder.setInsertionPointAfter(inst);
507  for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
508  auto &[firstResult, portInfo] = subExtraPorts[i];
509  if (portInfo.direction == Direction::Out)
510  continue;
511  auto value = inst.getResult(firstResult + i);
512  auto type = value.getType();
513  auto attr = getIntZerosAttr(type);
514  auto zero = builder.create<ConstantOp>(portInfo.loc, type, attr);
515  builder.create<MatchingConnectOp>(portInfo.loc, value, zero);
516  }
517  }
518  }
519  }
520 
521  // If there is an output file, create it.
522  if (outputFile)
523  createOutputFile(instanceInfo->getEffectiveDut());
524 
525  if (anythingChanged)
526  markAnalysesPreserved<InstanceGraph>();
527  else
528  markAllAnalysesPreserved();
529 }
530 
531 std::unique_ptr<mlir::Pass> circt::firrtl::createAddSeqMemPortsPass() {
532  return std::make_unique<AddSeqMemPortsPass>();
533 }
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.