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