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