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