CIRCT  19.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 
22 #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/FileSystem.h"
28 #include "llvm/Support/Parallel.h"
29 #include "llvm/Support/Path.h"
30 
31 namespace circt {
32 namespace firrtl {
33 #define GEN_PASS_DEF_ADDSEQMEMPORTS
34 #include "circt/Dialect/FIRRTL/Passes.h.inc"
35 } // namespace firrtl
36 } // namespace circt
37 
38 using namespace circt;
39 using namespace firrtl;
40 
41 namespace {
42 struct AddSeqMemPortsPass
43  : public circt::firrtl::impl::AddSeqMemPortsBase<AddSeqMemPortsPass> {
44  void runOnOperation() override;
45  LogicalResult processAddPortAnno(Location loc, Annotation anno);
46  LogicalResult processFileAnno(Location loc, StringRef metadataDir,
47  Annotation anno);
48  LogicalResult processAnnos(CircuitOp circuit);
49  void createOutputFile(igraph::ModuleOpInterface module);
50  InstanceGraphNode *findDUT();
51  void processMemModule(FMemModuleOp mem);
52  LogicalResult processModule(FModuleOp module, bool isDUT);
53 
54  /// Get the cached namespace for a module.
55  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
56  return moduleNamespaces.try_emplace(module, module).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 
87  /// If the metadata output file was specified in an annotation.
88  StringAttr outputFile;
89 
90  /// This is the list of every port to be added to each sequential memory.
91  SmallVector<PortInfo> userPorts;
92  /// This is an attribute holding the metadata for extra ports.
93  ArrayAttr extraPortsAttr;
94 
95  /// Cached module namespaces.
96  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
97 
98  bool anythingChanged;
99 };
100 } // end anonymous namespace
101 
102 LogicalResult AddSeqMemPortsPass::processAddPortAnno(Location loc,
103  Annotation anno) {
104  auto name = anno.getMember<StringAttr>("name");
105  if (!name)
106  return emitError(
107  loc, "AddSeqMemPortAnnotation requires field 'name' of string type");
108 
109  auto input = anno.getMember<BoolAttr>("input");
110  if (!input)
111  return emitError(
112  loc, "AddSeqMemPortAnnotation requires field 'input' of boolean type");
113  auto direction = input.getValue() ? Direction::In : Direction::Out;
114 
115  auto width = anno.getMember<IntegerAttr>("width");
116  if (!width)
117  return emitError(
118  loc, "AddSeqMemPortAnnotation requires field 'width' of integer type");
119  auto type = UIntType::get(&getContext(), width.getInt());
120  userPorts.push_back({name, type, direction});
121  return success();
122 }
123 
124 LogicalResult AddSeqMemPortsPass::processFileAnno(Location loc,
125  StringRef metadataDir,
126  Annotation anno) {
127  if (outputFile)
128  return emitError(
129  loc, "circuit has two AddSeqMemPortsFileAnnotation annotations");
130 
131  auto filename = anno.getMember<StringAttr>("filename");
132  if (!filename)
133  return emitError(loc,
134  "AddSeqMemPortsFileAnnotation requires field 'filename' "
135  "of string type");
136 
137  SmallString<128> outputFilePath(metadataDir);
138  llvm::sys::path::append(outputFilePath, filename.getValue());
139  outputFile = StringAttr::get(&getContext(), outputFilePath);
140  return success();
141 }
142 
143 LogicalResult AddSeqMemPortsPass::processAnnos(CircuitOp circuit) {
144  auto loc = circuit.getLoc();
145 
146  // Find the metadata directory.
147  auto dirAnno =
149  StringRef metadataDir = "metadata";
150  if (dirAnno) {
151  auto dir = dirAnno.getMember<StringAttr>("dirname");
152  if (!dir)
153  return emitError(loc, "MetadataDirAnnotation requires field 'dirname' of "
154  "string type");
155  metadataDir = dir.getValue();
156  }
157 
158  // Remove the annotations we care about.
159  bool error = false;
160  AnnotationSet::removeAnnotations(circuit, [&](Annotation anno) {
161  if (error)
162  return false;
163  if (anno.isClass(addSeqMemPortAnnoClass)) {
164  error = failed(processAddPortAnno(loc, anno));
165  return true;
166  }
168  error = failed(processFileAnno(loc, metadataDir, anno));
169  return true;
170  }
171  return false;
172  });
173  return failure(error);
174 }
175 
176 InstanceGraphNode *AddSeqMemPortsPass::findDUT() {
177  // Find the DUT module.
178  for (auto *node : *instanceGraph) {
179  if (AnnotationSet::hasAnnotation(node->getModule(), dutAnnoClass))
180  return node;
181  }
182  return instanceGraph->getTopLevelNode();
183 }
184 
185 void AddSeqMemPortsPass::processMemModule(FMemModuleOp mem) {
186  // We have to add the user ports to every mem module.
187  size_t portIndex = mem.getNumPorts();
188  auto &memInfo = memInfoMap[mem];
189  auto &extraPorts = memInfo.extraPorts;
190  for (auto &p : userPorts)
191  extraPorts.emplace_back(portIndex, p);
192  mem.insertPorts(extraPorts);
193  // Attach the extraPorts metadata.
194  mem.setExtraPortsAttr(extraPortsAttr);
195 }
196 
197 LogicalResult AddSeqMemPortsPass::processModule(FModuleOp module, bool isDUT) {
198  auto *context = &getContext();
199  // Insert the new port connections at the end of the module.
200  auto builder = OpBuilder::atBlockEnd(module.getBodyBlock());
201  auto &memInfo = memInfoMap[module];
202  auto &extraPorts = memInfo.extraPorts;
203  // List of ports added to submodules which must be connected to this module's
204  // ports.
205  SmallVector<Value> values;
206 
207  // The base index to use when adding ports to the current module.
208  unsigned firstPortIndex = module.getNumPorts();
209 
210  for (auto &op : llvm::make_early_inc_range(*module.getBodyBlock())) {
211  if (auto inst = dyn_cast<InstanceOp>(op)) {
212  auto submodule = inst.getReferencedModule(*instanceGraph);
213 
214  auto subMemInfoIt = memInfoMap.find(submodule);
215  // If there are no extra ports, we don't have to do anything.
216  if (subMemInfoIt == memInfoMap.end() ||
217  subMemInfoIt->second.extraPorts.empty())
218  continue;
219  auto &subMemInfo = subMemInfoIt->second;
220  // Find out how many memory ports we have to add.
221  auto &subExtraPorts = subMemInfo.extraPorts;
222 
223  // Add the extra ports to the instance operation.
224  auto clone = inst.cloneAndInsertPorts(subExtraPorts);
225  inst.replaceAllUsesWith(
226  clone.getResults().drop_back(subExtraPorts.size()));
227  instanceGraph->replaceInstance(inst, clone);
228  inst->erase();
229  inst = clone;
230 
231  // Connect each submodule port up to the parent module ports.
232  for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
233  auto &[firstSubIndex, portInfo] = subExtraPorts[i];
234  // This is the index of the user port we are adding.
235  auto userIndex = i % userPorts.size();
236  auto &sramPort = userPorts[userIndex];
237  // Construct a port name, e.g. "sram_0_user_inputs".
238  auto sramIndex = extraPorts.size() / userPorts.size();
239  auto portName =
240  StringAttr::get(context, "sram_" + Twine(sramIndex) + "_" +
241  sramPort.name.getValue());
242  auto portDirection = sramPort.direction;
243  auto portType = sramPort.type;
244  // Record the extra port.
245  extraPorts.push_back(
246  {firstPortIndex,
247  {portName, type_cast<FIRRTLType>(portType), portDirection}});
248  // If this is the DUT, then add a DontTouchAnnotation to any added ports
249  // to guarantee that it won't be removed.
250  if (isDUT)
251  extraPorts.back().second.annotations.addDontTouch();
252  // Record the instance result for now, so that we can connect it to the
253  // parent module port after we actually add the ports.
254  values.push_back(inst.getResult(firstSubIndex + i));
255  }
256 
257  // We don't want to collect the instance paths or attach inner_syms to
258  // the instance path if we aren't creating the output file.
259  if (outputFile) {
260  // We record any instance paths to memories which are rooted at the
261  // current module.
262  auto &instancePaths = memInfo.instancePaths;
263  auto ref = getInnerRefTo(inst);
264  innerRefToInstanceMap[ref] = inst;
265  // If its a mem module, this is the start of a path to the module.
266  if (isa<FMemModuleOp>(submodule))
267  instancePaths.push_back({ref});
268  // Copy any paths through the submodule to memories, adding the ref to
269  // the current instance.
270  for (const auto &subPath : subMemInfo.instancePaths) {
271  instancePaths.push_back(subPath);
272  instancePaths.back().push_back(ref);
273  }
274  }
275  }
276  }
277 
278  // Add the extra ports to this module.
279  module.insertPorts(extraPorts);
280 
281  // Connect the submodule ports to the parent module ports.
282  for (unsigned i = 0, e = values.size(); i < e; ++i) {
283  auto &[firstArg, port] = extraPorts[i];
284  Value modulePort = module.getArgument(firstArg + i);
285  Value instPort = values[i];
286  if (port.direction == Direction::In)
287  std::swap(modulePort, instPort);
288  builder.create<MatchingConnectOp>(port.loc, modulePort, instPort);
289  }
290  return success();
291 }
292 
293 void AddSeqMemPortsPass::createOutputFile(igraph::ModuleOpInterface module) {
294  // Insert the verbatim at the bottom of the circuit.
295  auto circuit = getOperation();
296  auto builder = OpBuilder::atBlockEnd(circuit.getBodyBlock());
297 
298  // Output buffer.
299  std::string buffer;
300  llvm::raw_string_ostream os(buffer);
301 
302  SymbolTable &symTable = getAnalysis<SymbolTable>();
303  HierPathCache cache(circuit, symTable);
304 
305  // The current parameter to the verbatim op.
306  unsigned paramIndex = 0;
307  // Parameters to the verbatim op.
308  SmallVector<Attribute> params;
309  // Small cache to reduce the number of parameters passed to the verbatim.
310  DenseMap<Attribute, unsigned> usedParams;
311 
312  // Helper to add a symbol to the verbatim, hitting the cache on the way.
313  auto addSymbol = [&](Attribute ref) {
314  auto it = usedParams.find(ref);
315  unsigned index;
316  if (it != usedParams.end()) {
317  index = it->second;
318  } else {
319  index = paramIndex;
320  usedParams[ref] = paramIndex++;
321  params.push_back(ref);
322  }
323  os << "{{" << index << "}}";
324  };
325 
326  // The current sram we are processing.
327  unsigned sramIndex = 0;
328  auto &instancePaths = memInfoMap[module].instancePaths;
329  auto dutSymbol = FlatSymbolRefAttr::get(module.getModuleNameAttr());
330 
331  auto loc = builder.getUnknownLoc();
332  // Put the information in a verbatim operation.
333  builder.create<emit::FileOp>(loc, outputFile, [&] {
334  for (auto instancePath : instancePaths) {
335  // Note: Reverse instancepath to construct the NLA.
336  SmallVector<Attribute> path(llvm::reverse(instancePath));
337  os << sramIndex++ << " -> ";
338  addSymbol(dutSymbol);
339  os << ".";
340 
341  auto nlaSymbol = cache.getRefFor(builder.getArrayAttr(path));
342  addSymbol(nlaSymbol);
343  NamedAttrList fields;
344  // There is no current client for the distinct attr, but it will be used
345  // by OM::path once the metadata is moved to OM, instead of the verbatim.
346  auto id = DistinctAttr::create(UnitAttr::get(builder.getContext()));
347  fields.append("id", id);
348  fields.append("class", builder.getStringAttr("circt.tracker"));
349  fields.append("circt.nonlocal", nlaSymbol);
350  // Now add the nonlocal annotation to the leaf instance.
351  auto *leafInstance = innerRefToInstanceMap[instancePath.front()];
352 
353  AnnotationSet annos(leafInstance);
354  annos.addAnnotations(builder.getDictionaryAttr(fields));
355  annos.applyToOperation(leafInstance);
356 
357  os << "\n";
358  }
359  builder.create<sv::VerbatimOp>(loc, buffer, ValueRange{},
360  builder.getArrayAttr(params));
361  });
362  anythingChanged = true;
363 }
364 
365 void AddSeqMemPortsPass::runOnOperation() {
366  auto *context = &getContext();
367  auto circuit = getOperation();
368  instanceGraph = &getAnalysis<InstanceGraph>();
369  circtNamespace = CircuitNamespace(circuit);
370  // Clear the state.
371  userPorts.clear();
372  memInfoMap.clear();
373  outputFile = {};
374  anythingChanged = false;
375 
376  // Collect the annotations from the circuit.
377  if (failed(processAnnos(circuit)))
378  return signalPassFailure();
379 
380  // SFC adds the ports in the opposite order they are attached, so we reverse
381  // the list here to match exactly.
382  std::reverse(userPorts.begin(), userPorts.end());
383 
384  auto *dutNode = findDUT();
385 
386  // Process the extra ports so we can attach it as metadata on to each memory.
387  SmallVector<Attribute> extraPorts;
388  auto ui32Type = IntegerType::get(context, 32, IntegerType::Unsigned);
389  for (auto &userPort : userPorts) {
390  SmallVector<NamedAttribute, 3> attrs;
391  attrs.emplace_back(StringAttr::get(context, "name"), userPort.name);
392  attrs.emplace_back(
393  StringAttr::get(context, "direction"),
395  context, userPort.direction == Direction::In ? "input" : "output"));
396  attrs.emplace_back(
397  StringAttr::get(context, "width"),
399  ui32Type,
400  type_cast<FIRRTLBaseType>(userPort.type).getBitWidthOrSentinel()));
401  extraPorts.push_back(DictionaryAttr::get(context, attrs));
402  }
403  extraPortsAttr = ArrayAttr::get(context, extraPorts);
404 
405  // If there are no user ports, don't do anything.
406  if (userPorts.size() > 0) {
407  // Update ports statistic.
408  numAddedPorts += userPorts.size();
409 
410  // Visit the nodes in post-order.
411  for (auto *node : llvm::post_order(dutNode)) {
412  auto op = node->getModule();
413  if (auto module = dyn_cast<FModuleOp>(*op)) {
414  if (failed(processModule(module, /*isDUT=*/node == dutNode)))
415  return signalPassFailure();
416  } else if (auto mem = dyn_cast<FMemModuleOp>(*op)) {
417  processMemModule(mem);
418  }
419  }
420 
421  // We handle the DUT differently than the rest of the modules.
422  if (auto dut = dyn_cast<FModuleOp>(*dutNode->getModule())) {
423  // For each instance of the dut, add the instance ports, but tie the port
424  // to 0 instead of wiring them to the parent.
425  for (auto *instRec : dutNode->uses()) {
426  auto inst = cast<InstanceOp>(*instRec->getInstance());
427  auto &dutMemInfo = memInfoMap[dut];
428  // Find out how many memory ports we have to add.
429  auto &subExtraPorts = dutMemInfo.extraPorts;
430  // If there are no extra ports, we don't have to do anything.
431  if (subExtraPorts.size() == 0)
432  continue;
433 
434  // Add the extra ports to the instance operation.
435  auto clone = inst.cloneAndInsertPorts(subExtraPorts);
436  inst.replaceAllUsesWith(
437  clone.getResults().drop_back(subExtraPorts.size()));
438  instanceGraph->replaceInstance(inst, clone);
439  inst->erase();
440  inst = clone;
441 
442  // Tie each port to 0.
443  OpBuilder builder(context);
444  builder.setInsertionPointAfter(inst);
445  for (unsigned i = 0, e = subExtraPorts.size(); i < e; ++i) {
446  auto &[firstResult, portInfo] = subExtraPorts[i];
447  if (portInfo.direction == Direction::Out)
448  continue;
449  auto value = inst.getResult(firstResult + i);
450  auto type = value.getType();
451  auto attr = getIntZerosAttr(type);
452  auto zero = builder.create<ConstantOp>(portInfo.loc, type, attr);
453  builder.create<MatchingConnectOp>(portInfo.loc, value, zero);
454  }
455  }
456  }
457  }
458 
459  // If there is an output file, create it.
460  if (outputFile)
461  createOutputFile(dutNode->getModule<igraph::ModuleOpInterface>());
462 
463  if (anythingChanged)
464  markAnalysesPreserved<InstanceGraph>();
465  else
466  markAllAnalysesPreserved();
467 }
468 
469 std::unique_ptr<mlir::Pass> circt::firrtl::createAddSeqMemPortsPass() {
470  return std::make_unique<AddSeqMemPortsPass>();
471 }
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.
bool hasAnnotation(StringRef className) const
Return true if we have an annotation with the specified class name.
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.
This is a Node in the InstanceGraph.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
constexpr const char * metadataDirectoryAttrName
constexpr const char * dutAnnoClass
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.