CIRCT  20.0.0git
CreateSiFiveMetadata.cpp
Go to the documentation of this file.
1 //===- CreateSiFiveMetadata.cpp - Create various metadata -------*- 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 //
9 // This file defines the CreateSiFiveMetadata pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
24 #include "circt/Dialect/SV/SVOps.h"
25 #include "mlir/IR/ImplicitLocOpBuilder.h"
26 #include "mlir/IR/Location.h"
27 #include "mlir/Pass/Pass.h"
28 #include "llvm/ADT/DepthFirstIterator.h"
29 #include "llvm/ADT/STLExtras.h"
30 #include "llvm/ADT/StringSwitch.h"
31 #include "llvm/Support/JSON.h"
32 #include "llvm/Support/Path.h"
33 
34 namespace circt {
35 namespace firrtl {
36 #define GEN_PASS_DEF_CREATESIFIVEMETADATA
37 #include "circt/Dialect/FIRRTL/Passes.h.inc"
38 } // namespace firrtl
39 } // namespace circt
40 
41 using namespace circt;
42 using namespace firrtl;
43 
44 namespace {
45 
46 struct ObjectModelIR {
47  ObjectModelIR(
48  CircuitOp circtOp, InstanceGraph &instanceGraph,
49  InstanceInfo &instanceInfo,
50  DenseMap<Operation *, hw::InnerSymbolNamespace> &moduleNamespaces)
51  : context(circtOp->getContext()), circtOp(circtOp),
52  circtNamespace(CircuitNamespace(circtOp)),
53  instancePathCache(InstancePathCache(instanceGraph)),
54  instanceInfo(instanceInfo), moduleNamespaces(moduleNamespaces) {}
55 
56  // Add the tracker annotation to the op and get a PathOp to the op.
57  PathOp createPathRef(Operation *op, hw::HierPathOp nla,
58  mlir::ImplicitLocOpBuilder &builderOM) {
59 
60  auto id = DistinctAttr::create(UnitAttr::get(context));
61  TargetKind kind = TargetKind::Reference;
62  // If op is null, then create an empty path.
63  if (op) {
64  NamedAttrList fields;
65  fields.append("id", id);
66  fields.append("class", StringAttr::get(context, "circt.tracker"));
67  if (nla)
68  fields.append("circt.nonlocal", mlir::FlatSymbolRefAttr::get(nla));
69  AnnotationSet annos(op);
70  annos.addAnnotations(DictionaryAttr::get(context, fields));
71  annos.applyToOperation(op);
72  if (isa<InstanceOp, FModuleLike>(op))
73  kind = TargetKind::Instance;
74  }
75 
76  // Create the path operation.
77  return builderOM.create<PathOp>(kind, id);
78  }
79 
80  void createMemorySchema() {
81 
82  auto unknownLoc = mlir::UnknownLoc::get(context);
83  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
84  unknownLoc, circtOp.getBodyBlock());
85 
86  // Add all the properties of a memory as fields of the class.
87  // The types must match exactly with the FMemModuleOp attribute type.
88 
89  mlir::Type extraPortsType[] = {
90  StringType::get(context), // name
91  StringType::get(context), // direction
92  FIntegerType::get(context) // Width
93  };
94  StringRef extraPortFields[3] = {"name", "direction", "width"};
95 
96  extraPortsClass = builderOM.create<ClassOp>(
97  "ExtraPortsMemorySchema", extraPortFields, extraPortsType);
98 
99  mlir::Type classFieldTypes[13] = {
100  StringType::get(context),
101  FIntegerType::get(context),
102  FIntegerType::get(context),
103  FIntegerType::get(context),
104  FIntegerType::get(context),
105  FIntegerType::get(context),
106  FIntegerType::get(context),
107  FIntegerType::get(context),
108  FIntegerType::get(context),
109  ListType::get(context, cast<PropertyType>(PathType::get(context))),
110  BoolType::get(context),
112  context, cast<PropertyType>(
113  detail::getInstanceTypeForClassLike(extraPortsClass))),
114  ListType::get(context, cast<PropertyType>(StringType::get(context))),
115  };
116 
117  memorySchemaClass = builderOM.create<ClassOp>(
118  "MemorySchema", memoryParamNames, classFieldTypes);
119 
120  // Now create the class that will instantiate metadata class with all the
121  // memories of the circt.
122  SmallVector<PortInfo> mports;
123  memoryMetadataClass = builderOM.create<ClassOp>(
124  builderOM.getStringAttr("MemoryMetadata"), mports);
125  }
126 
127  void createRetimeModulesSchema() {
128  auto unknownLoc = mlir::UnknownLoc::get(context);
129  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
130  unknownLoc, circtOp.getBodyBlock());
131  Type classFieldTypes[] = {StringType::get(context)};
132  retimeModulesSchemaClass = builderOM.create<ClassOp>(
133  "RetimeModulesSchema", retimeModulesParamNames, classFieldTypes);
134 
135  SmallVector<PortInfo> mports;
136  retimeModulesMetadataClass = builderOM.create<ClassOp>(
137  builderOM.getStringAttr("RetimeModulesMetadata"), mports);
138  }
139 
140  void addRetimeModule(FModuleLike module) {
141  if (!retimeModulesSchemaClass)
142  createRetimeModulesSchema();
143  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
144  module->getLoc(), retimeModulesMetadataClass.getBodyBlock());
145 
146  // Create the path operation.
147  auto modEntry =
148  builderOM.create<StringConstantOp>(module.getModuleNameAttr());
149  auto object = builderOM.create<ObjectOp>(retimeModulesSchemaClass,
150  module.getModuleNameAttr());
151 
152  auto inPort = builderOM.create<ObjectSubfieldOp>(object, 0);
153  builderOM.create<PropAssignOp>(inPort, modEntry);
154  auto portIndex = retimeModulesMetadataClass.getNumPorts();
155  SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
156  {portIndex,
157  PortInfo(builderOM.getStringAttr(module.getName() + "_field"),
158  object.getType(), Direction::Out)}};
159  retimeModulesMetadataClass.insertPorts(newPorts);
160  auto blockarg = retimeModulesMetadataClass.getBodyBlock()->addArgument(
161  object.getType(), module->getLoc());
162  builderOM.create<PropAssignOp>(blockarg, object);
163  }
164 
165  void addBlackBoxModulesSchema() {
166  auto unknownLoc = mlir::UnknownLoc::get(context);
167  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
168  unknownLoc, circtOp.getBodyBlock());
169  Type classFieldTypes[] = {StringType::get(context)};
170  blackBoxModulesSchemaClass =
171  builderOM.create<ClassOp>("SitestBlackBoxModulesSchema",
172  blackBoxModulesParamNames, classFieldTypes);
173  SmallVector<PortInfo> mports;
174  blackBoxMetadataClass = builderOM.create<ClassOp>(
175  builderOM.getStringAttr("SitestBlackBoxMetadata"), mports);
176  }
177 
178  void addBlackBoxModule(FExtModuleOp module) {
179  if (!blackBoxModulesSchemaClass)
180  addBlackBoxModulesSchema();
181  StringRef defName = *module.getDefname();
182  if (!blackboxModules.insert(defName).second)
183  return;
184  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
185  module.getLoc(), blackBoxMetadataClass.getBodyBlock());
186  auto modEntry = builderOM.create<StringConstantOp>(module.getDefnameAttr());
187  auto object = builderOM.create<ObjectOp>(blackBoxModulesSchemaClass,
188  module.getModuleNameAttr());
189 
190  auto inPort = builderOM.create<ObjectSubfieldOp>(object, 0);
191  builderOM.create<PropAssignOp>(inPort, modEntry);
192  auto portIndex = blackBoxMetadataClass.getNumPorts();
193  SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
194  {portIndex,
195  PortInfo(builderOM.getStringAttr(module.getName() + "_field"),
196  object.getType(), Direction::Out)}};
197  blackBoxMetadataClass.insertPorts(newPorts);
198  auto blockarg = blackBoxMetadataClass.getBodyBlock()->addArgument(
199  object.getType(), module->getLoc());
200  builderOM.create<PropAssignOp>(blockarg, object);
201  }
202 
203  void addMemory(FMemModuleOp mem) {
204  if (!memorySchemaClass)
205  createMemorySchema();
206  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
207  mem.getLoc(), memoryMetadataClass.getBodyBlock());
208  auto createConstField = [&](Attribute constVal) -> Value {
209  if (auto boolConstant = dyn_cast_or_null<mlir::BoolAttr>(constVal))
210  return builderOM.create<BoolConstantOp>(boolConstant);
211  if (auto intConstant = dyn_cast_or_null<mlir::IntegerAttr>(constVal))
212  return builderOM.create<FIntegerConstantOp>(intConstant);
213  if (auto strConstant = dyn_cast_or_null<mlir::StringAttr>(constVal))
214  return builderOM.create<StringConstantOp>(strConstant);
215  return {};
216  };
217  auto nlaBuilder = OpBuilder::atBlockBegin(circtOp.getBodyBlock());
218 
219  auto memPaths = instancePathCache.getAbsolutePaths(mem);
220  SmallVector<Value> memoryHierPaths;
221  SmallVector<Value> finalInstanceNames;
222  // Add metadata for memory paths that are in the design. There are two
223  // slightly different code paths here. If no instance is in the design,
224  // then just skip the memory. If some instances are in the designs and some
225  // are not, then add paths for memories in the design. For memories which
226  // are then _not_ in the design, give them unresolvable distinct attribute
227  // paths. The LowerClasses pass will later treat these as "optimized away"
228  // and create an empty path.
229  //
230  // TODO: This incongruity seems bad. Can we instead not generate metadata
231  // for any path not in the design?
232  bool inDut = instanceInfo.anyInstanceInEffectiveDesign(mem);
233  if (inDut) {
234  for (auto memPath : memPaths) {
235  {
236  igraph::InstanceOpInterface finalInst = memPath.leaf();
237  finalInstanceNames.emplace_back(builderOM.create<StringConstantOp>(
238  finalInst.getInstanceNameAttr()));
239  }
240  SmallVector<Attribute> namepath;
241  bool foundDut = false;
242  // The hierpath will be created to the pre-extracted
243  // instance, thus drop the leaf instance of the path, which can be
244  // extracted in subsequent passes.
245  igraph::InstanceOpInterface preExtractedLeafInstance;
246  for (auto inst : llvm::drop_end(memPath)) {
247  if (!foundDut) {
248  if (!instanceInfo.isEffectiveDut(
249  inst->getParentOfType<FModuleOp>()))
250  continue;
251  foundDut = true;
252  }
253 
254  // This path is not in the design. Do not record it.
255  if (inst->getParentOfType<LayerBlockOp>()) {
256  namepath.clear();
257  break;
258  }
259 
260  namepath.emplace_back(firrtl::getInnerRefTo(
261  inst, [&](auto mod) -> hw::InnerSymbolNamespace & {
262  return getModuleNamespace(mod);
263  }));
264  preExtractedLeafInstance = inst;
265  }
266  PathOp pathRef;
267  if (!namepath.empty()) {
268  // This is a path that is in the design.
269  auto nla = nlaBuilder.create<hw::HierPathOp>(
270  mem->getLoc(),
271  nlaBuilder.getStringAttr(circtNamespace.newName("memNLA")),
272  nlaBuilder.getArrayAttr(namepath));
273  nla.setVisibility(SymbolTable::Visibility::Private);
274  pathRef = createPathRef(preExtractedLeafInstance, nla, builderOM);
275  } else {
276  // This is a path _not_ in the design.
277  //
278  // TODO: This unresolvable distinct seems sketchy.
279  pathRef = createPathRef({}, {}, builderOM);
280  }
281 
282  // Create the path operation.
283  memoryHierPaths.push_back(pathRef);
284  }
285  }
286  auto finalInstNamesList = builderOM.create<ListCreateOp>(
287  ListType::get(context, cast<PropertyType>(StringType::get(context))),
288  finalInstanceNames);
289  auto hierpaths = builderOM.create<ListCreateOp>(
290  ListType::get(context, cast<PropertyType>(PathType::get(context))),
291  memoryHierPaths);
292  SmallVector<Value> memFields;
293 
294  auto object = builderOM.create<ObjectOp>(memorySchemaClass, mem.getName());
295  SmallVector<Value> extraPortsList;
296  ClassType extraPortsType;
297  for (auto attr : mem.getExtraPortsAttr()) {
298 
299  auto port = cast<DictionaryAttr>(attr);
300  auto portName = createConstField(port.getAs<StringAttr>("name"));
301  auto direction = createConstField(port.getAs<StringAttr>("direction"));
302  auto width = createConstField(port.getAs<IntegerAttr>("width"));
303  auto extraPortsObj =
304  builderOM.create<ObjectOp>(extraPortsClass, "extraPorts");
305  extraPortsType = extraPortsObj.getType();
306  auto inPort = builderOM.create<ObjectSubfieldOp>(extraPortsObj, 0);
307  builderOM.create<PropAssignOp>(inPort, portName);
308  inPort = builderOM.create<ObjectSubfieldOp>(extraPortsObj, 2);
309  builderOM.create<PropAssignOp>(inPort, direction);
310  inPort = builderOM.create<ObjectSubfieldOp>(extraPortsObj, 4);
311  builderOM.create<PropAssignOp>(inPort, width);
312  extraPortsList.push_back(extraPortsObj);
313  }
314  auto extraPorts = builderOM.create<ListCreateOp>(
315  memorySchemaClass.getPortType(22), extraPortsList);
316  for (auto field : llvm::enumerate(memoryParamNames)) {
317  auto propVal = createConstField(
318  llvm::StringSwitch<TypedAttr>(field.value())
319  .Case("name", builderOM.getStringAttr(mem.getName()))
320  .Case("depth", mem.getDepthAttr())
321  .Case("width", mem.getDataWidthAttr())
322  .Case("maskBits", mem.getMaskBitsAttr())
323  .Case("readPorts", mem.getNumReadPortsAttr())
324  .Case("writePorts", mem.getNumWritePortsAttr())
325  .Case("readwritePorts", mem.getNumReadWritePortsAttr())
326  .Case("readLatency", mem.getReadLatencyAttr())
327  .Case("writeLatency", mem.getWriteLatencyAttr())
328  .Case("hierarchy", {})
329  .Case("inDut", BoolAttr::get(context, inDut))
330  .Case("extraPorts", {})
331  .Case("preExtInstName", {}));
332  if (!propVal) {
333  if (field.value() == "hierarchy")
334  propVal = hierpaths;
335  else if (field.value() == "preExtInstName")
336  propVal = finalInstNamesList;
337  else
338  propVal = extraPorts;
339  }
340 
341  // The memory schema is a simple class, with input tied to output. The
342  // arguments are ordered such that, port index i is the input that is tied
343  // to i+1 which is the output.
344  // The following `2*index` translates the index to the memory schema input
345  // port number.
346  auto inPort =
347  builderOM.create<ObjectSubfieldOp>(object, 2 * field.index());
348  builderOM.create<PropAssignOp>(inPort, propVal);
349  }
350  auto portIndex = memoryMetadataClass.getNumPorts();
351  SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
352  {portIndex, PortInfo(builderOM.getStringAttr(mem.getName() + "_field"),
353  object.getType(), Direction::Out)}};
354  memoryMetadataClass.insertPorts(newPorts);
355  auto blockarg = memoryMetadataClass.getBodyBlock()->addArgument(
356  object.getType(), mem->getLoc());
357  builderOM.create<PropAssignOp>(blockarg, object);
358  }
359 
360  ObjectOp instantiateSifiveMetadata(FModuleOp topMod) {
361  if (!blackBoxMetadataClass && !memoryMetadataClass &&
362  !retimeModulesMetadataClass && !instanceInfo.hasDut())
363  return {};
364  auto builder = mlir::ImplicitLocOpBuilder::atBlockEnd(
365  mlir::UnknownLoc::get(circtOp->getContext()), circtOp.getBodyBlock());
366  SmallVector<PortInfo> mports;
367  auto sifiveMetadataClass = builder.create<ClassOp>(
368  builder.getStringAttr("SiFive_Metadata"), mports);
369  builder.setInsertionPointToStart(sifiveMetadataClass.getBodyBlock());
370 
371  auto addPort = [&](Value obj, StringRef fieldName) {
372  auto portIndex = sifiveMetadataClass.getNumPorts();
373  SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
374  {portIndex, PortInfo(builder.getStringAttr(fieldName + "_field_" +
375  Twine(portIndex)),
376  obj.getType(), Direction::Out)}};
377  sifiveMetadataClass.insertPorts(newPorts);
378  auto blockarg = sifiveMetadataClass.getBodyBlock()->addArgument(
379  obj.getType(), topMod->getLoc());
380  builder.create<PropAssignOp>(blockarg, obj);
381  };
382  if (blackBoxMetadataClass)
383  addPort(
384  builder.create<ObjectOp>(blackBoxMetadataClass,
385  builder.getStringAttr("blackbox_metadata")),
386  "blackbox");
387 
388  if (memoryMetadataClass)
389  addPort(
390  builder.create<ObjectOp>(memoryMetadataClass,
391  builder.getStringAttr("memory_metadata")),
392  "memory");
393 
394  if (retimeModulesMetadataClass)
395  addPort(builder.create<ObjectOp>(
396  retimeModulesMetadataClass,
397  builder.getStringAttr("retime_modules_metadata")),
398  "retime");
399 
400  if (instanceInfo.hasDut()) {
401  auto dutMod = instanceInfo.getDut();
402 
403  // This can handle multiple DUTs or multiple paths to a DUT.
404  // Create a list of paths to the DUTs.
405  SmallVector<Value, 2> pathOpsToDut;
406 
407  auto dutPaths = instancePathCache.getAbsolutePaths(dutMod);
408  // For each path to the DUT.
409  for (auto dutPath : dutPaths) {
410  SmallVector<Attribute> namepath;
411  // Construct the list of inner refs to the instances in the path.
412  for (auto inst : dutPath)
413  namepath.emplace_back(firrtl::getInnerRefTo(
414  inst, [&](auto mod) -> hw::InnerSymbolNamespace & {
415  return getModuleNamespace(mod);
416  }));
417  if (namepath.empty())
418  continue;
419  // The path op will refer to the leaf instance in the path (and not the
420  // actual DUT module!!).
421  auto leafInst = dutPath.leaf();
422  auto nlaBuilder = OpBuilder::atBlockBegin(circtOp.getBodyBlock());
423  auto nla = nlaBuilder.create<hw::HierPathOp>(
424  dutMod->getLoc(),
425  nlaBuilder.getStringAttr(circtNamespace.newName("dutNLA")),
426  nlaBuilder.getArrayAttr(namepath));
427  nla.setVisibility(SymbolTable::Visibility::Private);
428  // Create the path ref op and record it.
429  pathOpsToDut.emplace_back(createPathRef(leafInst, nla, builder));
430  }
431  // Create the list of paths op and add it as a field of the class.
432  auto pathList = builder.create<ListCreateOp>(
433  ListType::get(context, cast<PropertyType>(PathType::get(context))),
434  pathOpsToDut);
435  addPort(pathList, "dutModulePath");
436  }
437 
438  builder.setInsertionPointToEnd(topMod.getBodyBlock());
439  return builder.create<ObjectOp>(sifiveMetadataClass,
440  builder.getStringAttr("sifive_metadata"));
441  }
442 
443  /// Get the cached namespace for a module.
444  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
445  return moduleNamespaces.try_emplace(module, module).first->second;
446  }
447  MLIRContext *context;
448  CircuitOp circtOp;
449  CircuitNamespace circtNamespace;
450  InstancePathCache instancePathCache;
451  InstanceInfo &instanceInfo;
452  /// Cached module namespaces.
453  DenseMap<Operation *, hw::InnerSymbolNamespace> &moduleNamespaces;
454  ClassOp memorySchemaClass, extraPortsClass;
455  ClassOp memoryMetadataClass;
456  ClassOp retimeModulesMetadataClass, retimeModulesSchemaClass;
457  ClassOp blackBoxModulesSchemaClass, blackBoxMetadataClass;
458  StringRef memoryParamNames[13] = {
459  "name", "depth", "width", "maskBits",
460  "readPorts", "writePorts", "readwritePorts", "writeLatency",
461  "readLatency", "hierarchy", "inDut", "extraPorts",
462  "preExtInstName"};
463  StringRef retimeModulesParamNames[1] = {"moduleName"};
464  StringRef blackBoxModulesParamNames[1] = {"moduleName"};
465  llvm::SmallDenseSet<StringRef> blackboxModules;
466 }; // namespace
467 
468 class CreateSiFiveMetadataPass
469  : public circt::firrtl::impl::CreateSiFiveMetadataBase<
470  CreateSiFiveMetadataPass> {
471  LogicalResult emitRetimeModulesMetadata(ObjectModelIR &omir);
472  LogicalResult emitSitestBlackboxMetadata(ObjectModelIR &omir);
473  LogicalResult emitMemoryMetadata(ObjectModelIR &omir);
474  void runOnOperation() override;
475 
476  /// Get the cached namespace for a module.
477  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
478  return moduleNamespaces.try_emplace(module, module).first->second;
479  }
480  /// Cached module namespaces.
481  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
482  CircuitOp circuitOp;
483  // Precomputed instanceinfo analysis
484  InstanceInfo *instanceInfo;
485 
486 public:
487  CreateSiFiveMetadataPass(bool replSeqMem, StringRef replSeqMemFile) {
488  this->replSeqMem = replSeqMem;
489  this->replSeqMemFile = replSeqMemFile.str();
490  }
491 };
492 } // end anonymous namespace
493 
494 /// This function collects all the firrtl.mem ops and creates a verbatim op with
495 /// the relevant memory attributes.
496 LogicalResult
497 CreateSiFiveMetadataPass::emitMemoryMetadata(ObjectModelIR &omir) {
498  if (!replSeqMem)
499  return success();
500 
502  auto addSymbolToVerbatimOp =
503  [&](Operation *op,
504  llvm::SmallVectorImpl<Attribute> &symbols) -> SmallString<8> {
505  Attribute symbol;
506  if (auto module = dyn_cast<FModuleLike>(op))
507  symbol = FlatSymbolRefAttr::get(module);
508  else
509  symbol = firrtl::getInnerRefTo(
510  op, [&](auto mod) -> hw::InnerSymbolNamespace & {
511  return getModuleNamespace(mod);
512  });
513 
514  auto [it, inserted] = symbolIndices.try_emplace(symbol, symbols.size());
515  if (inserted)
516  symbols.push_back(symbol);
517 
518  SmallString<8> str;
519  ("{{" + Twine(it->second) + "}}").toVector(str);
520  return str;
521  };
522  // This lambda, writes to the given Json stream all the relevant memory
523  // attributes. Also adds the memory attrbutes to the string for creating the
524  // memmory conf file.
525  auto createMemMetadata = [&](FMemModuleOp mem,
526  llvm::json::OStream &jsonStream,
527  std::string &seqMemConfStr,
528  SmallVectorImpl<Attribute> &jsonSymbols,
529  SmallVectorImpl<Attribute> &seqMemSymbols) {
530  omir.addMemory(mem);
531  // Get the memory data width.
532  auto width = mem.getDataWidth();
533  // Metadata needs to be printed for memories which are candidates for
534  // macro replacement. The requirements for macro replacement::
535  // 1. read latency and write latency of one.
536  // 2. undefined read-under-write behavior.
537  if (mem.getReadLatency() != 1 || mem.getWriteLatency() != 1 || width <= 0)
538  return;
539  auto memExtSym = FlatSymbolRefAttr::get(SymbolTable::getSymbolName(mem));
540  auto symId = seqMemSymbols.size();
541  seqMemSymbols.push_back(memExtSym);
542  // Compute the mask granularity.
543  auto isMasked = mem.isMasked();
544  auto maskGran = width;
545  if (isMasked)
546  maskGran /= mem.getMaskBits();
547  // Now create the config string for the memory.
548  std::string portStr;
549  for (uint32_t i = 0; i < mem.getNumWritePorts(); ++i) {
550  if (!portStr.empty())
551  portStr += ",";
552  portStr += isMasked ? "mwrite" : "write";
553  }
554  for (uint32_t i = 0; i < mem.getNumReadPorts(); ++i) {
555  if (!portStr.empty())
556  portStr += ",";
557  portStr += "read";
558  }
559  for (uint32_t i = 0; i < mem.getNumReadWritePorts(); ++i) {
560  if (!portStr.empty())
561  portStr += ",";
562  portStr += isMasked ? "mrw" : "rw";
563  }
564 
565  auto maskGranStr =
566  !isMasked ? "" : " mask_gran " + std::to_string(maskGran);
567  seqMemConfStr = (StringRef(seqMemConfStr) + "name {{" + Twine(symId) +
568  "}} depth " + Twine(mem.getDepth()) + " width " +
569  Twine(width) + " ports " + portStr + maskGranStr + "\n")
570  .str();
571 
572  // Do not emit any JSON for memories which are not in the DUT.
573  if (!instanceInfo->anyInstanceInEffectiveDesign(mem))
574  return;
575  // This adds a Json array element entry corresponding to this memory.
576  jsonStream.object([&] {
577  jsonStream.attribute("module_name",
578  addSymbolToVerbatimOp(mem, jsonSymbols));
579  jsonStream.attribute("depth", (int64_t)mem.getDepth());
580  jsonStream.attribute("width", (int64_t)width);
581  jsonStream.attribute("masked", isMasked);
582  jsonStream.attribute("read", mem.getNumReadPorts());
583  jsonStream.attribute("write", mem.getNumWritePorts());
584  jsonStream.attribute("readwrite", mem.getNumReadWritePorts());
585  if (isMasked)
586  jsonStream.attribute("mask_granularity", (int64_t)maskGran);
587  jsonStream.attributeArray("extra_ports", [&] {
588  for (auto attr : mem.getExtraPorts()) {
589  jsonStream.object([&] {
590  auto port = cast<DictionaryAttr>(attr);
591  auto name = port.getAs<StringAttr>("name").getValue();
592  jsonStream.attribute("name", name);
593  auto direction = port.getAs<StringAttr>("direction").getValue();
594  jsonStream.attribute("direction", direction);
595  auto width = port.getAs<IntegerAttr>("width").getUInt();
596  jsonStream.attribute("width", width);
597  });
598  }
599  });
600  // Record all the hierarchy names.
601  jsonStream.attributeArray("hierarchy", [&] {
602  // Get the absolute path for the parent memory, to create the
603  // hierarchy names.
604  auto paths = omir.instancePathCache.getAbsolutePaths(mem);
605  for (auto p : paths) {
606  if (p.empty())
607  continue;
608 
609  // Only include the memory paths that are in the design. This means
610  // that the path has to both include the design and not be under a
611  // layer.
612  auto dutMod = instanceInfo->getEffectiveDut();
613  bool inDut = false, underLayer = false;
614  for (auto inst : p) {
615  auto parent = inst->getParentOfType<FModuleOp>();
616  inDut |= parent == dutMod;
617  if (inst->getParentOfType<LayerBlockOp>())
618  underLayer = true;
619  }
620  if (!inDut || underLayer)
621  continue;
622 
623  auto top = p.top();
624  std::string hierName =
625  addSymbolToVerbatimOp(top->getParentOfType<FModuleOp>(),
626  jsonSymbols)
627  .c_str();
628  auto finalInst = p.leaf();
629  for (auto inst : llvm::drop_end(p)) {
630  auto parentModule = inst->getParentOfType<FModuleOp>();
631  if (instanceInfo->getDut() == parentModule)
632  hierName =
633  addSymbolToVerbatimOp(parentModule, jsonSymbols).c_str();
634 
635  hierName = hierName + "." +
636  addSymbolToVerbatimOp(inst, jsonSymbols).c_str();
637  }
638  hierName += ("." + finalInst.getInstanceName()).str();
639 
640  jsonStream.value(hierName);
641  }
642  });
643  });
644  };
645 
646  std::string dutJsonBuffer;
647  llvm::raw_string_ostream dutOs(dutJsonBuffer);
648  llvm::json::OStream dutJson(dutOs, 2);
649  SmallVector<Attribute, 8> seqMemSymbols;
650  SmallVector<Attribute, 8> jsonSymbols;
651 
652  std::string seqMemConfStr;
653  dutJson.array([&] {
654  for (auto mem : circuitOp.getOps<FMemModuleOp>())
655  createMemMetadata(mem, dutJson, seqMemConfStr, jsonSymbols,
656  seqMemSymbols);
657  });
658 
659  auto *context = &getContext();
660  auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
661  circuitOp.getBodyBlock());
662  AnnotationSet annos(circuitOp);
663  auto dirAnno = annos.getAnnotation(metadataDirectoryAttrName);
664  StringRef metadataDir = "metadata";
665  if (dirAnno)
666  if (auto dir = dirAnno.getMember<StringAttr>("dirname"))
667  metadataDir = dir.getValue();
668 
669  // Use unknown loc to avoid printing the location in the metadata files.
670  {
671  SmallString<128> seqMemsJsonPath(metadataDir);
672  llvm::sys::path::append(seqMemsJsonPath, "seq_mems.json");
673  builder.create<emit::FileOp>(seqMemsJsonPath, [&] {
674  builder.create<sv::VerbatimOp>(dutJsonBuffer, ValueRange{},
675  builder.getArrayAttr(jsonSymbols));
676  });
677  }
678 
679  {
680  if (replSeqMemFile.empty()) {
681  emitError(circuitOp->getLoc())
682  << "metadata emission failed, the option "
683  "`-repl-seq-mem-file=<filename>` is mandatory for specifying a "
684  "valid seq mem metadata file";
685  return failure();
686  }
687 
688  builder.create<emit::FileOp>(replSeqMemFile, [&] {
689  builder.create<sv::VerbatimOp>(seqMemConfStr, ValueRange{},
690  builder.getArrayAttr(seqMemSymbols));
691  });
692  }
693 
694  return success();
695 }
696 
697 /// This will search for a target annotation and remove it from the operation.
698 /// If the annotation has a filename, it will be returned in the output
699 /// argument. If the annotation is missing the filename member, or if more than
700 /// one matching annotation is attached, it will print an error and return
701 /// failure.
702 static LogicalResult removeAnnotationWithFilename(Operation *op,
703  StringRef annoClass,
704  StringRef &filename) {
705  filename = "";
706  bool error = false;
707  AnnotationSet::removeAnnotations(op, [&](Annotation anno) {
708  // If there was a previous error or its not a match, continue.
709  if (error || !anno.isClass(annoClass))
710  return false;
711 
712  // If we have already found a matching annotation, error.
713  if (!filename.empty()) {
714  op->emitError("more than one ") << annoClass << " annotation attached";
715  error = true;
716  return false;
717  }
718 
719  // Get the filename from the annotation.
720  auto filenameAttr = anno.getMember<StringAttr>("filename");
721  if (!filenameAttr) {
722  op->emitError(annoClass) << " requires a filename";
723  error = true;
724  return false;
725  }
726 
727  // Require a non-empty filename.
728  filename = filenameAttr.getValue();
729  if (filename.empty()) {
730  op->emitError(annoClass) << " requires a non-empty filename";
731  error = true;
732  return false;
733  }
734 
735  return true;
736  });
737 
738  // If there was a problem above, return failure.
739  return failure(error);
740 }
741 
742 /// This function collects the name of each module annotated and prints them
743 /// all as a JSON array.
744 LogicalResult
745 CreateSiFiveMetadataPass::emitRetimeModulesMetadata(ObjectModelIR &omir) {
746 
747  auto *context = &getContext();
748 
749  // Get the filename, removing the annotation from the circuit.
750  StringRef filename;
752  filename)))
753  return failure();
754 
755  if (filename.empty())
756  return success();
757 
758  // Create a string buffer for the json data.
759  std::string buffer;
760  llvm::raw_string_ostream os(buffer);
761  llvm::json::OStream j(os, 2);
762 
763  // The output is a json array with each element a module name.
764  unsigned index = 0;
765  SmallVector<Attribute> symbols;
766  SmallString<3> placeholder;
767  j.array([&] {
768  for (auto module : circuitOp.getBodyBlock()->getOps<FModuleLike>()) {
769  // The annotation has no supplemental information, just remove it.
770  if (!AnnotationSet::removeAnnotations(module, retimeModuleAnnoClass) ||
771  !instanceInfo->anyInstanceInEffectiveDesign(module))
772  continue;
773 
774  // We use symbol substitution to make sure we output the correct thing
775  // when the module goes through renaming.
776  j.value(("{{" + Twine(index++) + "}}").str());
777  symbols.push_back(SymbolRefAttr::get(module.getModuleNameAttr()));
778  omir.addRetimeModule(module);
779  }
780  });
781 
782  // Put the retime information in a verbatim operation.
783  auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
784  circuitOp.getBodyBlock());
785  builder.create<emit::FileOp>(filename, [&] {
786  builder.create<sv::VerbatimOp>(builder.getStringAttr(buffer), ValueRange{},
787  builder.getArrayAttr(symbols));
788  });
789  return success();
790 }
791 
792 /// This function finds all external modules which will need to be generated for
793 /// the test harness to run.
794 LogicalResult
795 CreateSiFiveMetadataPass::emitSitestBlackboxMetadata(ObjectModelIR &omir) {
796 
797  // Any extmodule with these annotations should be excluded from the blackbox
798  // list.
799  std::array<StringRef, 6> blackListedAnnos = {
802 
803  auto *context = &getContext();
804 
805  // Get the filenames from the annotations.
806  StringRef dutFilename, testFilename;
808  dutFilename)) ||
810  circuitOp, sitestTestHarnessBlackBoxAnnoClass, testFilename)))
811  return failure();
812 
813  // If we don't have either annotation, no need to run this pass.
814  if (dutFilename.empty() && testFilename.empty())
815  return success();
816 
817  // Find all extmodules in the circuit. Check if they are black-listed from
818  // being included in the list. If they are not, separate them into two
819  // groups depending on if theyre in the DUT or the test harness.
820  SmallVector<StringRef> dutModules;
821  SmallVector<StringRef> testModules;
822  for (auto extModule : circuitOp.getBodyBlock()->getOps<FExtModuleOp>()) {
823  // If the module doesn't have a defname, then we can't record it properly.
824  // Just skip it.
825  if (!extModule.getDefname())
826  continue;
827 
828  // If its a generated blackbox, skip it.
829  AnnotationSet annos(extModule);
830  if (llvm::any_of(blackListedAnnos, [&](auto blackListedAnno) {
831  return annos.hasAnnotation(blackListedAnno);
832  }))
833  continue;
834 
835  // Record the defname of the module.
836  if (instanceInfo->anyInstanceInEffectiveDesign(extModule)) {
837  dutModules.push_back(*extModule.getDefname());
838  } else {
839  testModules.push_back(*extModule.getDefname());
840  }
841  omir.addBlackBoxModule(extModule);
842  }
843 
844  // This is a helper to create the verbatim output operation.
845  auto createOutput = [&](SmallVectorImpl<StringRef> &names,
846  StringRef filename) {
847  if (filename.empty())
848  return;
849 
850  // Sort and remove duplicates.
851  std::sort(names.begin(), names.end());
852  names.erase(std::unique(names.begin(), names.end()), names.end());
853 
854  // The output is a json array with each element a module name. The
855  // defname of a module can't change so we can output them verbatim.
856  std::string buffer;
857  llvm::raw_string_ostream os(buffer);
858  llvm::json::OStream j(os, 2);
859  j.array([&] {
860  for (auto &name : names)
861  j.value(name);
862  });
863 
864  // Put the information in a verbatim operation.
865  auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
866  circuitOp.getBodyBlock());
867 
868  builder.create<emit::FileOp>(filename, [&] {
869  builder.create<emit::VerbatimOp>(StringAttr::get(context, buffer));
870  });
871  };
872 
873  createOutput(testModules, testFilename);
874  createOutput(dutModules, dutFilename);
875 
876  return success();
877 }
878 
879 void CreateSiFiveMetadataPass::runOnOperation() {
880  auto circuits = getOperation().getOps<CircuitOp>();
881  if (circuits.empty())
882  return;
883 
884  circuitOp = *circuits.begin();
885 
886  if (!llvm::hasSingleElement(circuits)) {
887  mlir::emitError(circuitOp.getLoc(),
888  "cannot process multiple circuit operations")
889  .attachNote((*std::next(circuits.begin())).getLoc())
890  << "second circuit here";
891  return signalPassFailure();
892  }
893 
894  auto &instanceGraph = getAnalysis<InstanceGraph>();
895  instanceInfo = &getAnalysis<InstanceInfo>();
896  ObjectModelIR omir(circuitOp, instanceGraph, *instanceInfo, moduleNamespaces);
897 
898  if (failed(emitRetimeModulesMetadata(omir)) ||
899  failed(emitSitestBlackboxMetadata(omir)) ||
900  failed(emitMemoryMetadata(omir)))
901  return signalPassFailure();
902  auto *node = instanceGraph.getTopLevelNode();
903  if (FModuleOp topMod = dyn_cast<FModuleOp>(*node->getModule()))
904  if (auto objectOp = omir.instantiateSifiveMetadata(topMod)) {
905  auto portIndex = topMod.getNumPorts();
906  SmallVector<std::pair<unsigned, PortInfo>> ports = {
907  {portIndex,
908  PortInfo(StringAttr::get(objectOp->getContext(), "metadataObj"),
909  AnyRefType::get(objectOp->getContext()), Direction::Out)}};
910  topMod.insertPorts(ports);
911  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
912  topMod->getLoc(), topMod.getBodyBlock());
913  auto objectCast = builderOM.create<ObjectAnyRefCastOp>(objectOp);
914  builderOM.create<PropAssignOp>(topMod.getArgument(portIndex), objectCast);
915  }
916 
917  // This pass modifies the hierarchy, InstanceGraph is not preserved.
918 
919  // Clear pass-global state as required by MLIR pass infrastructure.
920  circuitOp = {};
921  instanceInfo = {};
922 }
923 
924 std::unique_ptr<mlir::Pass>
926  StringRef replSeqMemFile) {
927  return std::make_unique<CreateSiFiveMetadataPass>(replSeqMem, replSeqMemFile);
928 }
static LogicalResult removeAnnotationWithFilename(Operation *op, StringRef annoClass, StringRef &filename)
This will search for a target annotation and remove it from the operation.
int32_t width
Definition: FIRRTL.cpp:36
static std::vector< mlir::Value > toVector(mlir::ValueRange range)
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...
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.
igraph::InstanceGraphNode * getTopLevelNode() override
Get the node corresponding to the top-level module of a circuit.
igraph::ModuleOpInterface getDut()
Return the design-under-test if one is defined for the circuit, otherwise return null.
bool isEffectiveDut(igraph::ModuleOpInterface op)
Return true if this module is the design-under-test and the circuit has a design-under-test.
bool hasDut()
Return true if this circuit has a design-under-test.
igraph::ModuleOpInterface getEffectiveDut()
Return the "effective" design-under-test.
bool anyInstanceInEffectiveDesign(igraph::ModuleOpInterface op)
Return true if any instance of this module is within (or transitively within) the effective design.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
ClassType getInstanceTypeForClassLike(ClassLike classOp)
Definition: FIRRTLOps.cpp:1835
igraph::InstancePathCache InstancePathCache
constexpr const char * blackBoxAnnoClass
constexpr const char * sitestBlackBoxAnnoClass
constexpr const char * metadataDirectoryAttrName
constexpr const char * sitestTestHarnessBlackBoxAnnoClass
PathOp createPathRef(Operation *op, hw::HierPathOp nla, mlir::ImplicitLocOpBuilder &builderOM)
Add the tracker annotation to the op and get a PathOp to the op.
std::unique_ptr< mlir::Pass > createCreateSiFiveMetadataPass(bool replSeqMem=false, mlir::StringRef replSeqMemFile="")
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 * dataTapsBlackboxClass
constexpr const char * memTapBlackboxClass
constexpr const char * blackBoxPathAnnoClass
constexpr const char * retimeModulesFileAnnoClass
constexpr const char * blackBoxInlineAnnoClass
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
This holds the name and type that describes the module's ports.
A data structure that caches and provides absolute paths to module instances in the IR.