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  // Memory hierarchy is relevant only for memories under DUT.
223  bool inDut = instanceInfo.anyInstanceUnderEffectiveDut(mem);
224  if (inDut) {
225  for (auto memPath : memPaths) {
226  {
227  igraph::InstanceOpInterface finalInst = memPath.leaf();
228  finalInstanceNames.emplace_back(builderOM.create<StringConstantOp>(
229  finalInst.getInstanceNameAttr()));
230  }
231  SmallVector<Attribute> namepath;
232  bool foundDut = false;
233  // The hierpath will be created to the pre-extracted
234  // instance, thus drop the leaf instance of the path, which can be
235  // extracted in subsequent passes.
236  igraph::InstanceOpInterface preExtractedLeafInstance;
237  for (auto inst : llvm::drop_end(memPath)) {
238  if (!foundDut) {
239  if (!instanceInfo.isEffectiveDut(
240  inst->getParentOfType<FModuleOp>()))
241  continue;
242  foundDut = true;
243  }
244 
245  namepath.emplace_back(firrtl::getInnerRefTo(
246  inst, [&](auto mod) -> hw::InnerSymbolNamespace & {
247  return getModuleNamespace(mod);
248  }));
249  preExtractedLeafInstance = inst;
250  }
251  PathOp pathRef;
252  if (!namepath.empty()) {
253  auto nla = nlaBuilder.create<hw::HierPathOp>(
254  mem->getLoc(),
255  nlaBuilder.getStringAttr(circtNamespace.newName("memNLA")),
256  nlaBuilder.getArrayAttr(namepath));
257  pathRef = createPathRef(preExtractedLeafInstance, nla, builderOM);
258  } else {
259  pathRef = createPathRef({}, {}, builderOM);
260  }
261 
262  // Create the path operation.
263  memoryHierPaths.push_back(pathRef);
264  }
265  }
266  auto finalInstNamesList = builderOM.create<ListCreateOp>(
267  ListType::get(context, cast<PropertyType>(StringType::get(context))),
268  finalInstanceNames);
269  auto hierpaths = builderOM.create<ListCreateOp>(
270  ListType::get(context, cast<PropertyType>(PathType::get(context))),
271  memoryHierPaths);
272  SmallVector<Value> memFields;
273 
274  auto object = builderOM.create<ObjectOp>(memorySchemaClass, mem.getName());
275  SmallVector<Value> extraPortsList;
276  ClassType extraPortsType;
277  for (auto attr : mem.getExtraPortsAttr()) {
278 
279  auto port = cast<DictionaryAttr>(attr);
280  auto portName = createConstField(port.getAs<StringAttr>("name"));
281  auto direction = createConstField(port.getAs<StringAttr>("direction"));
282  auto width = createConstField(port.getAs<IntegerAttr>("width"));
283  auto extraPortsObj =
284  builderOM.create<ObjectOp>(extraPortsClass, "extraPorts");
285  extraPortsType = extraPortsObj.getType();
286  auto inPort = builderOM.create<ObjectSubfieldOp>(extraPortsObj, 0);
287  builderOM.create<PropAssignOp>(inPort, portName);
288  inPort = builderOM.create<ObjectSubfieldOp>(extraPortsObj, 2);
289  builderOM.create<PropAssignOp>(inPort, direction);
290  inPort = builderOM.create<ObjectSubfieldOp>(extraPortsObj, 4);
291  builderOM.create<PropAssignOp>(inPort, width);
292  extraPortsList.push_back(extraPortsObj);
293  }
294  auto extraPorts = builderOM.create<ListCreateOp>(
295  memorySchemaClass.getPortType(22), extraPortsList);
296  for (auto field : llvm::enumerate(memoryParamNames)) {
297  auto propVal = createConstField(
298  llvm::StringSwitch<TypedAttr>(field.value())
299  .Case("name", builderOM.getStringAttr(mem.getName()))
300  .Case("depth", mem.getDepthAttr())
301  .Case("width", mem.getDataWidthAttr())
302  .Case("maskBits", mem.getMaskBitsAttr())
303  .Case("readPorts", mem.getNumReadPortsAttr())
304  .Case("writePorts", mem.getNumWritePortsAttr())
305  .Case("readwritePorts", mem.getNumReadWritePortsAttr())
306  .Case("readLatency", mem.getReadLatencyAttr())
307  .Case("writeLatency", mem.getWriteLatencyAttr())
308  .Case("hierarchy", {})
309  .Case("inDut", BoolAttr::get(context, inDut))
310  .Case("extraPorts", {})
311  .Case("preExtInstName", {}));
312  if (!propVal) {
313  if (field.value() == "hierarchy")
314  propVal = hierpaths;
315  else if (field.value() == "preExtInstName")
316  propVal = finalInstNamesList;
317  else
318  propVal = extraPorts;
319  }
320 
321  // The memory schema is a simple class, with input tied to output. The
322  // arguments are ordered such that, port index i is the input that is tied
323  // to i+1 which is the output.
324  // The following `2*index` translates the index to the memory schema input
325  // port number.
326  auto inPort =
327  builderOM.create<ObjectSubfieldOp>(object, 2 * field.index());
328  builderOM.create<PropAssignOp>(inPort, propVal);
329  }
330  auto portIndex = memoryMetadataClass.getNumPorts();
331  SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
332  {portIndex, PortInfo(builderOM.getStringAttr(mem.getName() + "_field"),
333  object.getType(), Direction::Out)}};
334  memoryMetadataClass.insertPorts(newPorts);
335  auto blockarg = memoryMetadataClass.getBodyBlock()->addArgument(
336  object.getType(), mem->getLoc());
337  builderOM.create<PropAssignOp>(blockarg, object);
338  }
339 
340  ObjectOp instantiateSifiveMetadata(FModuleOp topMod) {
341  if (!blackBoxMetadataClass && !memoryMetadataClass &&
342  !retimeModulesMetadataClass)
343  return {};
344  auto builder = mlir::ImplicitLocOpBuilder::atBlockEnd(
345  mlir::UnknownLoc::get(circtOp->getContext()), circtOp.getBodyBlock());
346  SmallVector<PortInfo> mports;
347  auto sifiveMetadataClass = builder.create<ClassOp>(
348  builder.getStringAttr("SiFive_Metadata"), mports);
349  builder.setInsertionPointToStart(sifiveMetadataClass.getBodyBlock());
350 
351  auto addPort = [&](Value obj, StringRef fieldName) {
352  auto portIndex = sifiveMetadataClass.getNumPorts();
353  SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
354  {portIndex, PortInfo(builder.getStringAttr(fieldName + "_field_" +
355  Twine(portIndex)),
356  obj.getType(), Direction::Out)}};
357  sifiveMetadataClass.insertPorts(newPorts);
358  auto blockarg = sifiveMetadataClass.getBodyBlock()->addArgument(
359  obj.getType(), topMod->getLoc());
360  builder.create<PropAssignOp>(blockarg, obj);
361  };
362  if (blackBoxMetadataClass)
363  addPort(
364  builder.create<ObjectOp>(blackBoxMetadataClass,
365  builder.getStringAttr("blackbox_metadata")),
366  "blackbox");
367 
368  if (memoryMetadataClass)
369  addPort(
370  builder.create<ObjectOp>(memoryMetadataClass,
371  builder.getStringAttr("memory_metadata")),
372  "memory");
373 
374  if (retimeModulesMetadataClass)
375  addPort(builder.create<ObjectOp>(
376  retimeModulesMetadataClass,
377  builder.getStringAttr("retime_modules_metadata")),
378  "retime");
379 
380  if (instanceInfo.hasDut()) {
381  auto dutMod = instanceInfo.getDut();
382 
383  // This can handle multiple DUTs or multiple paths to a DUT.
384  // Create a list of paths to the DUTs.
385  SmallVector<Value, 2> pathOpsToDut;
386 
387  auto dutPaths = instancePathCache.getAbsolutePaths(dutMod);
388  // For each path to the DUT.
389  for (auto dutPath : dutPaths) {
390  SmallVector<Attribute> namepath;
391  // Construct the list of inner refs to the instances in the path.
392  for (auto inst : dutPath)
393  namepath.emplace_back(firrtl::getInnerRefTo(
394  inst, [&](auto mod) -> hw::InnerSymbolNamespace & {
395  return getModuleNamespace(mod);
396  }));
397  if (namepath.empty())
398  continue;
399  // The path op will refer to the leaf instance in the path (and not the
400  // actual DUT module!!).
401  auto leafInst = dutPath.leaf();
402  auto nlaBuilder = OpBuilder::atBlockBegin(circtOp.getBodyBlock());
403  auto nla = nlaBuilder.create<hw::HierPathOp>(
404  dutMod->getLoc(),
405  nlaBuilder.getStringAttr(circtNamespace.newName("dutNLA")),
406  nlaBuilder.getArrayAttr(namepath));
407  // Create the path ref op and record it.
408  pathOpsToDut.emplace_back(createPathRef(leafInst, nla, builder));
409  }
410  // Create the list of paths op and add it as a field of the class.
411  auto pathList = builder.create<ListCreateOp>(
412  ListType::get(context, cast<PropertyType>(PathType::get(context))),
413  pathOpsToDut);
414  addPort(pathList, "dutModulePath");
415  }
416 
417  builder.setInsertionPointToEnd(topMod.getBodyBlock());
418  return builder.create<ObjectOp>(sifiveMetadataClass,
419  builder.getStringAttr("sifive_metadata"));
420  }
421 
422  /// Get the cached namespace for a module.
423  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
424  return moduleNamespaces.try_emplace(module, module).first->second;
425  }
426  MLIRContext *context;
427  CircuitOp circtOp;
428  CircuitNamespace circtNamespace;
429  InstancePathCache instancePathCache;
430  InstanceInfo &instanceInfo;
431  /// Cached module namespaces.
432  DenseMap<Operation *, hw::InnerSymbolNamespace> &moduleNamespaces;
433  ClassOp memorySchemaClass, extraPortsClass;
434  ClassOp memoryMetadataClass;
435  ClassOp retimeModulesMetadataClass, retimeModulesSchemaClass;
436  ClassOp blackBoxModulesSchemaClass, blackBoxMetadataClass;
437  StringRef memoryParamNames[13] = {
438  "name", "depth", "width", "maskBits",
439  "readPorts", "writePorts", "readwritePorts", "writeLatency",
440  "readLatency", "hierarchy", "inDut", "extraPorts",
441  "preExtInstName"};
442  StringRef retimeModulesParamNames[1] = {"moduleName"};
443  StringRef blackBoxModulesParamNames[1] = {"moduleName"};
444  llvm::SmallDenseSet<StringRef> blackboxModules;
445 }; // namespace
446 
447 class CreateSiFiveMetadataPass
448  : public circt::firrtl::impl::CreateSiFiveMetadataBase<
449  CreateSiFiveMetadataPass> {
450  LogicalResult emitRetimeModulesMetadata(ObjectModelIR &omir);
451  LogicalResult emitSitestBlackboxMetadata(ObjectModelIR &omir);
452  LogicalResult emitMemoryMetadata(ObjectModelIR &omir);
453  void runOnOperation() override;
454 
455  /// Get the cached namespace for a module.
456  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
457  return moduleNamespaces.try_emplace(module, module).first->second;
458  }
459  /// Cached module namespaces.
460  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
461  CircuitOp circuitOp;
462  // Precomputed instanceinfo analysis
463  InstanceInfo *instanceInfo;
464 
465 public:
466  CreateSiFiveMetadataPass(bool replSeqMem, StringRef replSeqMemFile) {
467  this->replSeqMem = replSeqMem;
468  this->replSeqMemFile = replSeqMemFile.str();
469  }
470 };
471 } // end anonymous namespace
472 
473 /// This function collects all the firrtl.mem ops and creates a verbatim op with
474 /// the relevant memory attributes.
475 LogicalResult
476 CreateSiFiveMetadataPass::emitMemoryMetadata(ObjectModelIR &omir) {
477  if (!replSeqMem)
478  return success();
479 
481  auto addSymbolToVerbatimOp =
482  [&](Operation *op,
483  llvm::SmallVectorImpl<Attribute> &symbols) -> SmallString<8> {
484  Attribute symbol;
485  if (auto module = dyn_cast<FModuleLike>(op))
486  symbol = FlatSymbolRefAttr::get(module);
487  else
488  symbol = firrtl::getInnerRefTo(
489  op, [&](auto mod) -> hw::InnerSymbolNamespace & {
490  return getModuleNamespace(mod);
491  });
492 
493  auto [it, inserted] = symbolIndices.try_emplace(symbol, symbols.size());
494  if (inserted)
495  symbols.push_back(symbol);
496 
497  SmallString<8> str;
498  ("{{" + Twine(it->second) + "}}").toVector(str);
499  return str;
500  };
501  // This lambda, writes to the given Json stream all the relevant memory
502  // attributes. Also adds the memory attrbutes to the string for creating the
503  // memmory conf file.
504  auto createMemMetadata = [&](FMemModuleOp mem,
505  llvm::json::OStream &jsonStream,
506  std::string &seqMemConfStr,
507  SmallVectorImpl<Attribute> &jsonSymbols,
508  SmallVectorImpl<Attribute> &seqMemSymbols) {
509  omir.addMemory(mem);
510  // Get the memory data width.
511  auto width = mem.getDataWidth();
512  // Metadata needs to be printed for memories which are candidates for
513  // macro replacement. The requirements for macro replacement::
514  // 1. read latency and write latency of one.
515  // 2. undefined read-under-write behavior.
516  if (mem.getReadLatency() != 1 || mem.getWriteLatency() != 1 || width <= 0)
517  return;
518  auto memExtSym = FlatSymbolRefAttr::get(SymbolTable::getSymbolName(mem));
519  auto symId = seqMemSymbols.size();
520  seqMemSymbols.push_back(memExtSym);
521  // Compute the mask granularity.
522  auto isMasked = mem.isMasked();
523  auto maskGran = width;
524  if (isMasked)
525  maskGran /= mem.getMaskBits();
526  // Now create the config string for the memory.
527  std::string portStr;
528  for (uint32_t i = 0; i < mem.getNumWritePorts(); ++i) {
529  if (!portStr.empty())
530  portStr += ",";
531  portStr += isMasked ? "mwrite" : "write";
532  }
533  for (uint32_t i = 0; i < mem.getNumReadPorts(); ++i) {
534  if (!portStr.empty())
535  portStr += ",";
536  portStr += "read";
537  }
538  for (uint32_t i = 0; i < mem.getNumReadWritePorts(); ++i) {
539  if (!portStr.empty())
540  portStr += ",";
541  portStr += isMasked ? "mrw" : "rw";
542  }
543 
544  auto maskGranStr =
545  !isMasked ? "" : " mask_gran " + std::to_string(maskGran);
546  seqMemConfStr = (StringRef(seqMemConfStr) + "name {{" + Twine(symId) +
547  "}} depth " + Twine(mem.getDepth()) + " width " +
548  Twine(width) + " ports " + portStr + maskGranStr + "\n")
549  .str();
550 
551  // Do not emit any JSON for memories which are not in the DUT.
552  if (!instanceInfo->anyInstanceUnderEffectiveDut(mem))
553  return;
554  // This adds a Json array element entry corresponding to this memory.
555  jsonStream.object([&] {
556  jsonStream.attribute("module_name",
557  addSymbolToVerbatimOp(mem, jsonSymbols));
558  jsonStream.attribute("depth", (int64_t)mem.getDepth());
559  jsonStream.attribute("width", (int64_t)width);
560  jsonStream.attribute("masked", isMasked);
561  jsonStream.attribute("read", mem.getNumReadPorts());
562  jsonStream.attribute("write", mem.getNumWritePorts());
563  jsonStream.attribute("readwrite", mem.getNumReadWritePorts());
564  if (isMasked)
565  jsonStream.attribute("mask_granularity", (int64_t)maskGran);
566  jsonStream.attributeArray("extra_ports", [&] {
567  for (auto attr : mem.getExtraPorts()) {
568  jsonStream.object([&] {
569  auto port = cast<DictionaryAttr>(attr);
570  auto name = port.getAs<StringAttr>("name").getValue();
571  jsonStream.attribute("name", name);
572  auto direction = port.getAs<StringAttr>("direction").getValue();
573  jsonStream.attribute("direction", direction);
574  auto width = port.getAs<IntegerAttr>("width").getUInt();
575  jsonStream.attribute("width", width);
576  });
577  }
578  });
579  // Record all the hierarchy names.
580  SmallVector<std::string> hierNames;
581  jsonStream.attributeArray("hierarchy", [&] {
582  // Get the absolute path for the parent memory, to create the
583  // hierarchy names.
584  auto paths = omir.instancePathCache.getAbsolutePaths(mem);
585  for (auto p : paths) {
586  if (p.empty())
587  continue;
588 
589  auto top = p.top();
590  std::string hierName =
591  addSymbolToVerbatimOp(top->getParentOfType<FModuleOp>(),
592  jsonSymbols)
593  .c_str();
594  auto finalInst = p.leaf();
595  for (auto inst : llvm::drop_end(p)) {
596  auto parentModule = inst->getParentOfType<FModuleOp>();
597  if (instanceInfo->getDut() == parentModule)
598  hierName =
599  addSymbolToVerbatimOp(parentModule, jsonSymbols).c_str();
600 
601  hierName = hierName + "." +
602  addSymbolToVerbatimOp(inst, jsonSymbols).c_str();
603  }
604  hierName += ("." + finalInst.getInstanceName()).str();
605 
606  hierNames.push_back(hierName);
607  // Only include the memory paths that are under the effective DUT.
608  auto dutMod = instanceInfo->getEffectiveDut();
609  if (llvm::any_of(p, [&](circt::igraph::InstanceOpInterface inst) {
610  return dutMod == inst->getParentOfType<FModuleOp>();
611  }))
612  jsonStream.value(hierName);
613  }
614  });
615  });
616  };
617 
618  std::string dutJsonBuffer;
619  llvm::raw_string_ostream dutOs(dutJsonBuffer);
620  llvm::json::OStream dutJson(dutOs, 2);
621  SmallVector<Attribute, 8> seqMemSymbols;
622  SmallVector<Attribute, 8> jsonSymbols;
623 
624  std::string seqMemConfStr;
625  dutJson.array([&] {
626  for (auto mem : circuitOp.getOps<FMemModuleOp>())
627  createMemMetadata(mem, dutJson, seqMemConfStr, jsonSymbols,
628  seqMemSymbols);
629  });
630 
631  auto *context = &getContext();
632  auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
633  circuitOp.getBodyBlock());
634  AnnotationSet annos(circuitOp);
635  auto dirAnno = annos.getAnnotation(metadataDirectoryAttrName);
636  StringRef metadataDir = "metadata";
637  if (dirAnno)
638  if (auto dir = dirAnno.getMember<StringAttr>("dirname"))
639  metadataDir = dir.getValue();
640 
641  // Use unknown loc to avoid printing the location in the metadata files.
642  {
643  SmallString<128> seqMemsJsonPath(metadataDir);
644  llvm::sys::path::append(seqMemsJsonPath, "seq_mems.json");
645  builder.create<emit::FileOp>(seqMemsJsonPath, [&] {
646  builder.create<sv::VerbatimOp>(dutJsonBuffer, ValueRange{},
647  builder.getArrayAttr(jsonSymbols));
648  });
649  }
650 
651  {
652  if (replSeqMemFile.empty()) {
653  emitError(circuitOp->getLoc())
654  << "metadata emission failed, the option "
655  "`-repl-seq-mem-file=<filename>` is mandatory for specifying a "
656  "valid seq mem metadata file";
657  return failure();
658  }
659 
660  builder.create<emit::FileOp>(replSeqMemFile, [&] {
661  builder.create<sv::VerbatimOp>(seqMemConfStr, ValueRange{},
662  builder.getArrayAttr(seqMemSymbols));
663  });
664  }
665 
666  return success();
667 }
668 
669 /// This will search for a target annotation and remove it from the operation.
670 /// If the annotation has a filename, it will be returned in the output
671 /// argument. If the annotation is missing the filename member, or if more than
672 /// one matching annotation is attached, it will print an error and return
673 /// failure.
674 static LogicalResult removeAnnotationWithFilename(Operation *op,
675  StringRef annoClass,
676  StringRef &filename) {
677  filename = "";
678  bool error = false;
679  AnnotationSet::removeAnnotations(op, [&](Annotation anno) {
680  // If there was a previous error or its not a match, continue.
681  if (error || !anno.isClass(annoClass))
682  return false;
683 
684  // If we have already found a matching annotation, error.
685  if (!filename.empty()) {
686  op->emitError("more than one ") << annoClass << " annotation attached";
687  error = true;
688  return false;
689  }
690 
691  // Get the filename from the annotation.
692  auto filenameAttr = anno.getMember<StringAttr>("filename");
693  if (!filenameAttr) {
694  op->emitError(annoClass) << " requires a filename";
695  error = true;
696  return false;
697  }
698 
699  // Require a non-empty filename.
700  filename = filenameAttr.getValue();
701  if (filename.empty()) {
702  op->emitError(annoClass) << " requires a non-empty filename";
703  error = true;
704  return false;
705  }
706 
707  return true;
708  });
709 
710  // If there was a problem above, return failure.
711  return failure(error);
712 }
713 
714 /// This function collects the name of each module annotated and prints them
715 /// all as a JSON array.
716 LogicalResult
717 CreateSiFiveMetadataPass::emitRetimeModulesMetadata(ObjectModelIR &omir) {
718 
719  auto *context = &getContext();
720 
721  // Get the filename, removing the annotation from the circuit.
722  StringRef filename;
724  filename)))
725  return failure();
726 
727  if (filename.empty())
728  return success();
729 
730  // Create a string buffer for the json data.
731  std::string buffer;
732  llvm::raw_string_ostream os(buffer);
733  llvm::json::OStream j(os, 2);
734 
735  // The output is a json array with each element a module name.
736  unsigned index = 0;
737  SmallVector<Attribute> symbols;
738  SmallString<3> placeholder;
739  j.array([&] {
740  for (auto module : circuitOp.getBodyBlock()->getOps<FModuleLike>()) {
741  // The annotation has no supplemental information, just remove it.
742  if (!AnnotationSet::removeAnnotations(module, retimeModuleAnnoClass))
743  continue;
744 
745  // We use symbol substitution to make sure we output the correct thing
746  // when the module goes through renaming.
747  j.value(("{{" + Twine(index++) + "}}").str());
748  symbols.push_back(SymbolRefAttr::get(module.getModuleNameAttr()));
749  omir.addRetimeModule(module);
750  }
751  });
752 
753  // Put the retime information in a verbatim operation.
754  auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
755  circuitOp.getBodyBlock());
756  builder.create<emit::FileOp>(filename, [&] {
757  builder.create<sv::VerbatimOp>(builder.getStringAttr(buffer), ValueRange{},
758  builder.getArrayAttr(symbols));
759  });
760  return success();
761 }
762 
763 /// This function finds all external modules which will need to be generated for
764 /// the test harness to run.
765 LogicalResult
766 CreateSiFiveMetadataPass::emitSitestBlackboxMetadata(ObjectModelIR &omir) {
767 
768  // Any extmodule with these annotations should be excluded from the blackbox
769  // list.
770  std::array<StringRef, 6> blackListedAnnos = {
773 
774  auto *context = &getContext();
775 
776  // Get the filenames from the annotations.
777  StringRef dutFilename, testFilename;
779  dutFilename)) ||
781  circuitOp, sitestTestHarnessBlackBoxAnnoClass, testFilename)))
782  return failure();
783 
784  // If we don't have either annotation, no need to run this pass.
785  if (dutFilename.empty() && testFilename.empty())
786  return success();
787 
788  // Find all extmodules in the circuit. Check if they are black-listed from
789  // being included in the list. If they are not, separate them into two
790  // groups depending on if theyre in the DUT or the test harness.
791  SmallVector<StringRef> dutModules;
792  SmallVector<StringRef> testModules;
793  for (auto extModule : circuitOp.getBodyBlock()->getOps<FExtModuleOp>()) {
794  // If the module doesn't have a defname, then we can't record it properly.
795  // Just skip it.
796  if (!extModule.getDefname())
797  continue;
798 
799  // If its a generated blackbox, skip it.
800  AnnotationSet annos(extModule);
801  if (llvm::any_of(blackListedAnnos, [&](auto blackListedAnno) {
802  return annos.hasAnnotation(blackListedAnno);
803  }))
804  continue;
805 
806  // Record the defname of the module.
807  if (instanceInfo->anyInstanceUnderEffectiveDut(extModule)) {
808  dutModules.push_back(*extModule.getDefname());
809  } else {
810  testModules.push_back(*extModule.getDefname());
811  }
812  omir.addBlackBoxModule(extModule);
813  }
814 
815  // This is a helper to create the verbatim output operation.
816  auto createOutput = [&](SmallVectorImpl<StringRef> &names,
817  StringRef filename) {
818  if (filename.empty())
819  return;
820 
821  // Sort and remove duplicates.
822  std::sort(names.begin(), names.end());
823  names.erase(std::unique(names.begin(), names.end()), names.end());
824 
825  // The output is a json array with each element a module name. The
826  // defname of a module can't change so we can output them verbatim.
827  std::string buffer;
828  llvm::raw_string_ostream os(buffer);
829  llvm::json::OStream j(os, 2);
830  j.array([&] {
831  for (auto &name : names)
832  j.value(name);
833  });
834 
835  // Put the information in a verbatim operation.
836  auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
837  circuitOp.getBodyBlock());
838 
839  builder.create<emit::FileOp>(filename, [&] {
840  builder.create<emit::VerbatimOp>(StringAttr::get(context, buffer));
841  });
842  };
843 
844  createOutput(testModules, testFilename);
845  createOutput(dutModules, dutFilename);
846 
847  return success();
848 }
849 
850 void CreateSiFiveMetadataPass::runOnOperation() {
851  auto circuits = getOperation().getOps<CircuitOp>();
852  if (circuits.empty())
853  return;
854 
855  circuitOp = *circuits.begin();
856 
857  if (!llvm::hasSingleElement(circuits)) {
858  mlir::emitError(circuitOp.getLoc(),
859  "cannot process multiple circuit operations")
860  .attachNote((*std::next(circuits.begin())).getLoc())
861  << "second circuit here";
862  return signalPassFailure();
863  }
864 
865  auto &instanceGraph = getAnalysis<InstanceGraph>();
866  instanceInfo = &getAnalysis<InstanceInfo>();
867  ObjectModelIR omir(circuitOp, instanceGraph, *instanceInfo, moduleNamespaces);
868 
869  if (failed(emitRetimeModulesMetadata(omir)) ||
870  failed(emitSitestBlackboxMetadata(omir)) ||
871  failed(emitMemoryMetadata(omir)))
872  return signalPassFailure();
873  auto *node = instanceGraph.getTopLevelNode();
874  if (FModuleOp topMod = dyn_cast<FModuleOp>(*node->getModule()))
875  if (auto objectOp = omir.instantiateSifiveMetadata(topMod)) {
876  auto portIndex = topMod.getNumPorts();
877  SmallVector<std::pair<unsigned, PortInfo>> ports = {
878  {portIndex,
879  PortInfo(StringAttr::get(objectOp->getContext(), "metadataObj"),
880  AnyRefType::get(objectOp->getContext()), Direction::Out)}};
881  topMod.insertPorts(ports);
882  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
883  topMod->getLoc(), topMod.getBodyBlock());
884  auto objectCast = builderOM.create<ObjectAnyRefCastOp>(objectOp);
885  builderOM.create<PropAssignOp>(topMod.getArgument(portIndex), objectCast);
886  }
887 
888  // This pass modifies the hierarchy, InstanceGraph is not preserved.
889 
890  // Clear pass-global state as required by MLIR pass infrastructure.
891  circuitOp = {};
892  instanceInfo = {};
893 }
894 
895 std::unique_ptr<mlir::Pass>
897  StringRef replSeqMemFile) {
898  return std::make_unique<CreateSiFiveMetadataPass>(replSeqMem, replSeqMemFile);
899 }
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.
bool anyInstanceUnderEffectiveDut(igraph::ModuleOpInterface op)
Return true if at least one instance is under (or transitively under) the effective design-under-test...
igraph::ModuleOpInterface getEffectiveDut()
Return the "effective" design-under-test.
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:1833
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.